From 7e45708c785129452f43caab9cfb31e86f8d75e4 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Fri, 19 Sep 2025 17:08:27 +0200 Subject: [PATCH 001/449] Release 0.7.2 (#285) prepare new release 0.7.2 --- build.gradle.kts | 2 +- gradle/libs.versions.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 67cb5d9..f3497c3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { allprojects { group = "io.modelcontextprotocol" - version = "0.7.2-alpha3" + version = "0.7.2" } dependencies { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2e884a6..088dd9c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -20,7 +20,7 @@ kotest = "6.0.3" awaitility = "4.3.0" # Samples -mcp-kotlin = "0.7.2-alpha3" +mcp-kotlin = "0.7.2" anthropic = "2.7.0" shadow = "8.1.1" From 9ceadc65f9b14eb07af9901aff8e8fb7343173fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Sep 2025 18:27:44 +0300 Subject: [PATCH 002/449] Bump org.jetbrains.kotlinx.kover from 0.9.1 to 0.9.2 (#286) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 088dd9c..a39d09d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ kotlin = "2.2.10" dokka = "2.0.0" atomicfu = "0.29.0" ktlint = "13.1.0" -kover = "0.9.1" +kover = "0.9.2" mavenPublish = "0.34.0" binaryCompatibilityValidatorPlugin = "0.18.1" From 0ed92f5b03ff237f632ce3d7d84084290563c4f0 Mon Sep 17 00:00:00 2001 From: Jeff Clyne Date: Sat, 27 Sep 2025 04:54:39 -0700 Subject: [PATCH 003/449] Conditionally imports the samples only if its the root project (#292) --- settings.gradle.kts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index 048676f..6346267 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -25,7 +25,9 @@ include( ":kotlin-sdk-test", ) -// Include sample projects as composite builds -includeBuild("samples/kotlin-mcp-client") -includeBuild("samples/kotlin-mcp-server") -includeBuild("samples/weather-stdio-server") +// Include sample projects as composite builds if this is the root project +if (gradle.parent == null) { + includeBuild("samples/kotlin-mcp-client") + includeBuild("samples/kotlin-mcp-server") + includeBuild("samples/weather-stdio-server") +} From d86813636eedf6310899eb58ae687612426e1e9c Mon Sep 17 00:00:00 2001 From: Maria Tigina <31625351+tiginamaria@users.noreply.github.com> Date: Mon, 29 Sep 2025 09:10:07 +0200 Subject: [PATCH 004/449] Introduce server sessions (#198) Introduce a `ServerSession` to manage multiple connections to the mcp server. `Protocall` logic of the `Server` was moved to `ServerSession`, while MCP resources logic remained in `Server` ## Motivation and Context 1. Fix the multiple connection issues reported for SSE transport 2. Avoid server duplications during configurations: currently every connection creates a session, but the server stays single ## How Has This Been Tested? Added `SseIntegrationTest` and `WebSocketIntegrationTest` integration tests to check connection with single and multiple clients ## Breaking Changes This change caused several depreciations: 1. The `Server` -> `Protocol` inheritance (which caused single connection issue) should be removed in future releases, now, all the methods inherited from `Protocall` marked as `Deprecated` with a `Warning` 2. Websocket Ktor server initialization changes in order not to create server instance per connection ## 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed ## Additional context --------- Co-authored-by: devcrocod --- .../kotlin/sdk/client/Client.kt | 4 +- .../kotlin/sdk/client/SSEClientTransport.kt | 6 + .../sdk/client/WebSocketClientTransport.kt | 7 + .../WebSocketMcpKtorClientExtensions.kt | 5 + .../kotlin/sdk/shared/Protocol.kt | 37 +- .../sdk/shared/WebSocketMcpTransport.kt | 10 +- kotlin-sdk-server/api/kotlin-sdk-server.api | 52 ++- .../kotlin/sdk/server/KtorServer.kt | 71 +-- .../kotlin/sdk/server/Server.kt | 405 +++--------------- .../kotlin/sdk/server/ServerSession.kt | 371 ++++++++++++++++ .../WebSocketMcpKtorServerExtensions.kt | 110 ++++- .../sdk/server/WebSocketMcpServerTransport.kt | 4 + .../kotlin/sdk/client/ClientTest.kt | 278 +++++++----- .../kotlin/sdk/client/SseTransportTest.kt | 30 +- .../sdk/integration/SseIntegrationTest.kt | 174 +++++++- .../integration/WebSocketIntegrationTest.kt | 196 +++++++++ .../kotlin/AbstractResourceIntegrationTest.kt | 12 +- .../sse/KotlinServerForTsClientSse.kt | 28 -- .../src/commonMain/kotlin/server.kt | 17 +- .../sample/server/McpWeatherServer.kt | 4 +- 20 files changed, 1205 insertions(+), 616 deletions(-) create mode 100644 kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt create mode 100644 kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/WebSocketIntegrationTest.kt diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt index ea63150..e21696b 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt @@ -157,9 +157,11 @@ public open class Client(private val clientInfo: Implementation, options: Client notification(InitializedNotification()) } catch (error: Throwable) { + logger.error(error) { "Failed to initialize client: ${error.message}" } close() + if (error !is CancellationException) { - throw IllegalStateException("Error connecting to transport: ${error.message}") + throw IllegalStateException("Error connecting to transport: ${error.message}", error) } throw error diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt index 950f37f..c0bcd54 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt @@ -1,5 +1,6 @@ package io.modelcontextprotocol.kotlin.sdk.client +import io.github.oshai.kotlinlogging.KotlinLogging import io.ktor.client.HttpClient import io.ktor.client.plugins.sse.ClientSSESession import io.ktor.client.plugins.sse.sseSession @@ -46,6 +47,8 @@ public class SseClientTransport( private val reconnectionTime: Duration? = null, private val requestBuilder: HttpRequestBuilder.() -> Unit = {}, ) : AbstractTransport() { + private val logger = KotlinLogging.logger {} + private val initialized: AtomicBoolean = AtomicBoolean(false) private val endpoint = CompletableDeferred() @@ -111,6 +114,8 @@ public class SseClientTransport( val text = response.bodyAsText() error("Error POSTing to endpoint (HTTP ${response.status}): $text") } + + logger.debug { "Client successfully sent message via SSE $endpoint" } } catch (e: Throwable) { _onError(e) throw e @@ -158,6 +163,7 @@ public class SseClientTransport( val path = if (eventData.startsWith("/")) eventData.substring(1) else eventData val endpointUrl = Url("$baseUrl/$path") endpoint.complete(endpointUrl.toString()) + logger.debug { "Client connected to endpoint: $endpointUrl" } } catch (e: Throwable) { _onError(e) endpoint.completeExceptionally(e) diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketClientTransport.kt index 4571907..ec3f947 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketClientTransport.kt @@ -1,5 +1,6 @@ package io.modelcontextprotocol.kotlin.sdk.client +import io.github.oshai.kotlinlogging.KotlinLogging import io.ktor.client.HttpClient import io.ktor.client.plugins.websocket.webSocketSession import io.ktor.client.request.HttpRequestBuilder @@ -10,6 +11,8 @@ import io.modelcontextprotocol.kotlin.sdk.shared.MCP_SUBPROTOCOL import io.modelcontextprotocol.kotlin.sdk.shared.WebSocketMcpTransport import kotlin.properties.Delegates +private val logger = KotlinLogging.logger {} + /** * Client transport for WebSocket: this will connect to a server over the WebSocket protocol. */ @@ -21,6 +24,8 @@ public class WebSocketClientTransport( override var session: WebSocketSession by Delegates.notNull() override suspend fun initializeSession() { + logger.debug { "Websocket session initialization started..." } + session = urlString?.let { client.webSocketSession(it) { requestBuilder() @@ -32,5 +37,7 @@ public class WebSocketClientTransport( header(HttpHeaders.SecWebSocketProtocol, MCP_SUBPROTOCOL) } + + logger.debug { "Websocket session initialization finished" } } } diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketMcpKtorClientExtensions.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketMcpKtorClientExtensions.kt index 77062ab..8647953 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketMcpKtorClientExtensions.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketMcpKtorClientExtensions.kt @@ -1,11 +1,14 @@ package io.modelcontextprotocol.kotlin.sdk.client +import io.github.oshai.kotlinlogging.KotlinLogging import io.ktor.client.HttpClient import io.ktor.client.request.HttpRequestBuilder import io.modelcontextprotocol.kotlin.sdk.Implementation import io.modelcontextprotocol.kotlin.sdk.LIB_VERSION import io.modelcontextprotocol.kotlin.sdk.shared.IMPLEMENTATION_NAME +private val logger = KotlinLogging.logger {} + /** * Returns a new WebSocket transport for the Model Context Protocol using the provided HttpClient. * @@ -36,6 +39,8 @@ public suspend fun HttpClient.mcpWebSocket( version = LIB_VERSION, ), ) + logger.debug { "Client started to connect to server" } client.connect(transport) + logger.debug { "Client finished to connect to server" } return client } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt index 675bfea..6eedfe6 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt @@ -41,7 +41,7 @@ import kotlin.reflect.typeOf import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds -private val LOGGER = KotlinLogging.logger { } +private val logger = KotlinLogging.logger { } public const val IMPLEMENTATION_NAME: String = "mcp-ktor" @@ -211,6 +211,7 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio } } + logger.info { "Starting transport" } return transport.start() } @@ -228,29 +229,29 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio } private suspend fun onNotification(notification: JSONRPCNotification) { - LOGGER.trace { "Received notification: ${notification.method}" } + logger.trace { "Received notification: ${notification.method}" } val handler = notificationHandlers[notification.method] ?: fallbackNotificationHandler if (handler == null) { - LOGGER.trace { "No handler found for notification: ${notification.method}" } + logger.trace { "No handler found for notification: ${notification.method}" } return } try { handler(notification) } catch (cause: Throwable) { - LOGGER.error(cause) { "Error handling notification: ${notification.method}" } + logger.error(cause) { "Error handling notification: ${notification.method}" } onError(cause) } } private suspend fun onRequest(request: JSONRPCRequest) { - LOGGER.trace { "Received request: ${request.method} (id: ${request.id})" } + logger.trace { "Received request: ${request.method} (id: ${request.id})" } val handler = requestHandlers[request.method] ?: fallbackRequestHandler if (handler === null) { - LOGGER.trace { "No handler found for request: ${request.method}" } + logger.trace { "No handler found for request: ${request.method}" } try { transport?.send( JSONRPCResponse( @@ -262,7 +263,7 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio ), ) } catch (cause: Throwable) { - LOGGER.error(cause) { "Error sending method not found response" } + logger.error(cause) { "Error sending method not found response" } onError(cause) } return @@ -270,7 +271,7 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio try { val result = handler(request, RequestHandlerExtra()) - LOGGER.trace { "Request handled successfully: ${request.method} (id: ${request.id})" } + logger.trace { "Request handled successfully: ${request.method} (id: ${request.id})" } transport?.send( JSONRPCResponse( @@ -279,7 +280,7 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio ), ) } catch (cause: Throwable) { - LOGGER.error(cause) { "Error handling request: ${request.method} (id: ${request.id})" } + logger.error(cause) { "Error handling request: ${request.method} (id: ${request.id})" } try { transport?.send( @@ -292,7 +293,7 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio ), ) } catch (sendError: Throwable) { - LOGGER.error(sendError) { + logger.error(sendError) { "Failed to send error response for request: ${request.method} (id: ${request.id})" } // Optionally implement fallback behavior here @@ -301,7 +302,7 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio } private fun onProgress(notification: ProgressNotification) { - LOGGER.trace { + logger.trace { "Received progress notification: token=${notification.params.progressToken}, progress=${notification.params.progress}/${notification.params.total}" } val progress = notification.params.progress @@ -314,7 +315,7 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio val error = Error( "Received a progress notification for an unknown token: ${McpJson.encodeToString(notification)}", ) - LOGGER.error { error.message } + logger.error { error.message } onError(error) return } @@ -389,9 +390,9 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio * Do not use this method to emit notifications! Use notification() instead. */ public suspend fun request(request: Request, options: RequestOptions? = null): T { - LOGGER.trace { "Sending request: ${request.method}" } + logger.trace { "Sending request: ${request.method}" } val result = CompletableDeferred() - val transport = this@Protocol.transport ?: throw Error("Not connected") + val transport = transport ?: throw Error("Not connected") if (this@Protocol.options?.enforceStrictCapabilities == true) { assertCapabilityForMethod(request.method) @@ -401,7 +402,7 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio val messageId = message.id if (options?.onProgress != null) { - LOGGER.trace { "Registering progress handler for request id: $messageId" } + logger.trace { "Registering progress handler for request id: $messageId" } _progressHandlers.update { current -> current.put(messageId, options.onProgress) } @@ -451,12 +452,12 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio val timeout = options?.timeout ?: DEFAULT_REQUEST_TIMEOUT try { withTimeout(timeout) { - LOGGER.trace { "Sending request message with id: $messageId" } + logger.trace { "Sending request message with id: $messageId" } this@Protocol.transport?.send(message) } return result.await() } catch (cause: TimeoutCancellationException) { - LOGGER.error { "Request timed out after ${timeout.inWholeMilliseconds}ms: ${request.method}" } + logger.error { "Request timed out after ${timeout.inWholeMilliseconds}ms: ${request.method}" } cancel( McpError( ErrorCode.Defined.RequestTimeout.code, @@ -473,7 +474,7 @@ 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) { - LOGGER.trace { "Sending notification: ${notification.method}" } + logger.trace { "Sending notification: ${notification.method}" } val transport = this.transport ?: error("Not connected") assertNotificationCapability(notification.method) diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt index 4a93676..85c60c0 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt @@ -1,5 +1,6 @@ package io.modelcontextprotocol.kotlin.sdk.shared +import io.github.oshai.kotlinlogging.KotlinLogging import io.ktor.websocket.Frame import io.ktor.websocket.WebSocketSession import io.ktor.websocket.close @@ -17,6 +18,8 @@ import kotlin.concurrent.atomics.ExperimentalAtomicApi public const val MCP_SUBPROTOCOL: String = "mcp" +private val logger = KotlinLogging.logger {} + /** * Abstract class representing a WebSocket transport for the Model Context Protocol (MCP). * Handles communication over a WebSocket session. @@ -40,6 +43,8 @@ public abstract class WebSocketMcpTransport : AbstractTransport() { protected abstract suspend fun initializeSession() override suspend fun start() { + logger.debug { "Starting websocket transport" } + if (!initialized.compareAndSet(expectedValue = false, newValue = true)) { error( "WebSocketClientTransport already started! " + @@ -53,7 +58,8 @@ public abstract class WebSocketMcpTransport : AbstractTransport() { while (true) { val message = try { session.incoming.receive() - } catch (_: ClosedReceiveChannelException) { + } catch (e: ClosedReceiveChannelException) { + logger.debug { "Closed receive channel, exiting" } return@launch } @@ -84,6 +90,7 @@ public abstract class WebSocketMcpTransport : AbstractTransport() { } override suspend fun send(message: JSONRPCMessage) { + logger.debug { "Sending message" } if (!initialized.load()) { error("Not connected") } @@ -96,6 +103,7 @@ public abstract class WebSocketMcpTransport : AbstractTransport() { error("Not connected") } + logger.debug { "Closing websocket session" } session.close() session.coroutineContext.job.join() } diff --git a/kotlin-sdk-server/api/kotlin-sdk-server.api b/kotlin-sdk-server/api/kotlin-sdk-server.api index ec09fea..56e524a 100644 --- a/kotlin-sdk-server/api/kotlin-sdk-server.api +++ b/kotlin-sdk-server/api/kotlin-sdk-server.api @@ -44,7 +44,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredTool { public fun toString ()Ljava/lang/String; } -public class io/modelcontextprotocol/kotlin/sdk/server/Server : io/modelcontextprotocol/kotlin/sdk/shared/Protocol { +public class io/modelcontextprotocol/kotlin/sdk/server/Server { public fun (Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;)V public final fun addPrompt (Lio/modelcontextprotocol/kotlin/sdk/Prompt;Lkotlin/jvm/functions/Function2;)V public final fun addPrompt (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lkotlin/jvm/functions/Function2;)V @@ -57,35 +57,20 @@ public class io/modelcontextprotocol/kotlin/sdk/server/Server : io/modelcontextp public final fun addTool (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;Lkotlin/jvm/functions/Function2;)V public static synthetic fun addTool$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V public final fun addTools (Ljava/util/List;)V - protected fun assertCapabilityForMethod (Lio/modelcontextprotocol/kotlin/sdk/Method;)V - protected fun assertNotificationCapability (Lio/modelcontextprotocol/kotlin/sdk/Method;)V - public fun assertRequestHandlerCapability (Lio/modelcontextprotocol/kotlin/sdk/Method;)V - public final fun createElicitation (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest$RequestedSchema;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun createElicitation$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest$RequestedSchema;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public final fun createMessage (Lio/modelcontextprotocol/kotlin/sdk/CreateMessageRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun createMessage$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Lio/modelcontextprotocol/kotlin/sdk/CreateMessageRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public final fun getClientCapabilities ()Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities; - public final fun getClientVersion ()Lio/modelcontextprotocol/kotlin/sdk/Implementation; + public final fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun connect (Lio/modelcontextprotocol/kotlin/sdk/shared/Transport;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun getPrompts ()Ljava/util/Map; public final fun getResources ()Ljava/util/Map; public final fun getTools ()Ljava/util/Map; - public final fun listRoots (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun listRoots$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public fun onClose ()V public final fun onClose (Lkotlin/jvm/functions/Function0;)V + public final fun onConnect (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 removePrompt (Ljava/lang/String;)Z public final fun removePrompts (Ljava/util/List;)I public final fun removeResource (Ljava/lang/String;)Z 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 sendLoggingMessage (Lio/modelcontextprotocol/kotlin/sdk/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; - public final fun sendResourceUpdated (Lio/modelcontextprotocol/kotlin/sdk/ResourceUpdatedNotification;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun sendToolListChanged (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class io/modelcontextprotocol/kotlin/sdk/server/ServerOptions : io/modelcontextprotocol/kotlin/sdk/shared/ProtocolOptions { @@ -94,6 +79,30 @@ public final class io/modelcontextprotocol/kotlin/sdk/server/ServerOptions : io/ public final fun getCapabilities ()Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities; } +public class io/modelcontextprotocol/kotlin/sdk/server/ServerSession : io/modelcontextprotocol/kotlin/sdk/shared/Protocol { + public fun (Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;)V + protected fun assertCapabilityForMethod (Lio/modelcontextprotocol/kotlin/sdk/Method;)V + protected fun assertNotificationCapability (Lio/modelcontextprotocol/kotlin/sdk/Method;)V + public fun assertRequestHandlerCapability (Lio/modelcontextprotocol/kotlin/sdk/Method;)V + public final fun createElicitation (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest$RequestedSchema;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun createElicitation$default (Lio/modelcontextprotocol/kotlin/sdk/server/ServerSession;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest$RequestedSchema;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun createMessage (Lio/modelcontextprotocol/kotlin/sdk/CreateMessageRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun createMessage$default (Lio/modelcontextprotocol/kotlin/sdk/server/ServerSession;Lio/modelcontextprotocol/kotlin/sdk/CreateMessageRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun getClientCapabilities ()Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities; + public final fun getClientVersion ()Lio/modelcontextprotocol/kotlin/sdk/Implementation; + public final fun listRoots (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun listRoots$default (Lio/modelcontextprotocol/kotlin/sdk/server/ServerSession;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public fun onClose ()V + 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 sendLoggingMessage (Lio/modelcontextprotocol/kotlin/sdk/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; + public final fun sendResourceUpdated (Lio/modelcontextprotocol/kotlin/sdk/ResourceUpdatedNotification;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun sendToolListChanged (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + public final class io/modelcontextprotocol/kotlin/sdk/server/SseServerTransport : io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport { public fun (Ljava/lang/String;Lio/ktor/server/sse/ServerSSESession;)V public fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -112,8 +121,13 @@ public final class io/modelcontextprotocol/kotlin/sdk/server/StdioServerTranspor } public final class io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensionsKt { + public static final fun mcpWebSocket (Lio/ktor/server/application/Application;Ljava/lang/String;Lkotlin/jvm/functions/Function0;)V + public static final fun mcpWebSocket (Lio/ktor/server/application/Application;Lkotlin/jvm/functions/Function0;)V public static final fun mcpWebSocket (Lio/ktor/server/routing/Route;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Lkotlin/jvm/functions/Function2;)V public static final fun mcpWebSocket (Lio/ktor/server/routing/Route;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Lkotlin/jvm/functions/Function2;)V + public static final fun mcpWebSocket (Lio/ktor/server/routing/Route;Lkotlin/jvm/functions/Function0;)V + public static final fun mcpWebSocket (Lio/ktor/server/routing/Routing;Ljava/lang/String;Lkotlin/jvm/functions/Function0;)V + public static final fun mcpWebSocket (Lio/ktor/server/routing/Routing;Lkotlin/jvm/functions/Function0;)V public static synthetic fun mcpWebSocket$default (Lio/ktor/server/routing/Route;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V public static synthetic fun mcpWebSocket$default (Lio/ktor/server/routing/Route;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V public static final fun mcpWebSocketTransport (Lio/ktor/server/routing/Route;Ljava/lang/String;Lkotlin/jvm/functions/Function2;)V diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt index 57bae05..934ba04 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt @@ -13,11 +13,29 @@ import io.ktor.server.routing.routing import io.ktor.server.sse.SSE import io.ktor.server.sse.ServerSSESession import io.ktor.server.sse.sse -import io.ktor.util.collections.ConcurrentMap import io.ktor.utils.io.KtorDsl +import kotlinx.atomicfu.AtomicRef +import kotlinx.atomicfu.atomic +import kotlinx.atomicfu.update +import kotlinx.collections.immutable.PersistentMap +import kotlinx.collections.immutable.toPersistentMap private val logger = KotlinLogging.logger {} +internal class SseTransportManager(transports: Map = emptyMap()) { + private val transports: AtomicRef> = atomic(transports.toPersistentMap()) + + fun getTransport(sessionId: String): SseServerTransport? = transports.value[sessionId] + + fun addTransport(transport: SseServerTransport) { + transports.update { it.put(transport.sessionId, transport) } + } + + fun removeTransport(sessionId: String) { + transports.update { it.remove(sessionId) } + } +} + @KtorDsl public fun Routing.mcp(path: String, block: ServerSSESession.() -> Server) { route(path) { @@ -25,85 +43,76 @@ public fun Routing.mcp(path: String, block: ServerSSESession.() -> Server) { } } -/** - * Configures the Ktor Application to handle Model Context Protocol (MCP) over Server-Sent Events (SSE). - */ +/* +* Configures the Ktor Application to handle Model Context Protocol (MCP) over Server-Sent Events (SSE). +*/ @KtorDsl public fun Routing.mcp(block: ServerSSESession.() -> Server) { - val transports = ConcurrentMap() + val sseTransportManager = SseTransportManager() sse { - mcpSseEndpoint("", transports, block) + mcpSseEndpoint("", sseTransportManager, block) } post { - mcpPostEndpoint(transports) + mcpPostEndpoint(sseTransportManager) } } @Suppress("FunctionName") -@Deprecated("Use mcp() instead", ReplaceWith("mcp(block)"), DeprecationLevel.WARNING) +@Deprecated("Use mcp() instead", ReplaceWith("mcp(block)"), DeprecationLevel.ERROR) public fun Application.MCP(block: ServerSSESession.() -> Server) { mcp(block) } @KtorDsl public fun Application.mcp(block: ServerSSESession.() -> Server) { - val transports = ConcurrentMap() - install(SSE) routing { - sse("/sse") { - mcpSseEndpoint("/message", transports, block) - } - - post("/message") { - mcpPostEndpoint(transports) - } + mcp(block) } } -private suspend fun ServerSSESession.mcpSseEndpoint( +internal suspend fun ServerSSESession.mcpSseEndpoint( postEndpoint: String, - transports: ConcurrentMap, + sseTransportManager: SseTransportManager, block: ServerSSESession.() -> Server, ) { - val transport = mcpSseTransport(postEndpoint, transports) + val transport = mcpSseTransport(postEndpoint, sseTransportManager) val server = block() server.onClose { logger.info { "Server connection closed for sessionId: ${transport.sessionId}" } - transports.remove(transport.sessionId) + sseTransportManager.removeTransport(transport.sessionId) } server.connect(transport) + logger.debug { "Server connected to transport for sessionId: ${transport.sessionId}" } } internal fun ServerSSESession.mcpSseTransport( postEndpoint: String, - transports: ConcurrentMap, + sseTransportManager: SseTransportManager, ): SseServerTransport { val transport = SseServerTransport(postEndpoint, this) - transports[transport.sessionId] = transport - + sseTransportManager.addTransport(transport) logger.info { "New SSE connection established and stored with sessionId: ${transport.sessionId}" } return transport } -internal suspend fun RoutingContext.mcpPostEndpoint(transports: ConcurrentMap) { - val sessionId: String = call.request.queryParameters["sessionId"] - ?: run { - call.respond(HttpStatusCode.BadRequest, "sessionId query parameter is not provided") - return - } +internal suspend fun RoutingContext.mcpPostEndpoint(sseTransportManager: SseTransportManager) { + val sessionId: String = call.request.queryParameters["sessionId"] ?: run { + call.respond(HttpStatusCode.BadRequest, "sessionId query parameter is not provided") + return + } logger.debug { "Received message for sessionId: $sessionId" } - val transport = transports[sessionId] + val transport = sseTransportManager.getTransport(sessionId) if (transport == null) { logger.warn { "Session not found for sessionId: $sessionId" } call.respond(HttpStatusCode.NotFound, "Session not found") diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt index ac71b5f..3e5aad8 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt @@ -3,58 +3,35 @@ package io.modelcontextprotocol.kotlin.sdk.server import io.github.oshai.kotlinlogging.KotlinLogging import io.modelcontextprotocol.kotlin.sdk.CallToolRequest import io.modelcontextprotocol.kotlin.sdk.CallToolResult -import io.modelcontextprotocol.kotlin.sdk.ClientCapabilities -import io.modelcontextprotocol.kotlin.sdk.CreateElicitationRequest -import io.modelcontextprotocol.kotlin.sdk.CreateElicitationRequest.RequestedSchema -import io.modelcontextprotocol.kotlin.sdk.CreateElicitationResult -import io.modelcontextprotocol.kotlin.sdk.CreateMessageRequest -import io.modelcontextprotocol.kotlin.sdk.CreateMessageResult -import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject -import io.modelcontextprotocol.kotlin.sdk.EmptyRequestResult import io.modelcontextprotocol.kotlin.sdk.GetPromptRequest import io.modelcontextprotocol.kotlin.sdk.GetPromptResult import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.InitializeRequest -import io.modelcontextprotocol.kotlin.sdk.InitializeResult -import io.modelcontextprotocol.kotlin.sdk.InitializedNotification -import io.modelcontextprotocol.kotlin.sdk.LATEST_PROTOCOL_VERSION import io.modelcontextprotocol.kotlin.sdk.ListPromptsRequest import io.modelcontextprotocol.kotlin.sdk.ListPromptsResult import io.modelcontextprotocol.kotlin.sdk.ListResourceTemplatesRequest import io.modelcontextprotocol.kotlin.sdk.ListResourceTemplatesResult import io.modelcontextprotocol.kotlin.sdk.ListResourcesRequest import io.modelcontextprotocol.kotlin.sdk.ListResourcesResult -import io.modelcontextprotocol.kotlin.sdk.ListRootsRequest -import io.modelcontextprotocol.kotlin.sdk.ListRootsResult import io.modelcontextprotocol.kotlin.sdk.ListToolsRequest import io.modelcontextprotocol.kotlin.sdk.ListToolsResult -import io.modelcontextprotocol.kotlin.sdk.LoggingMessageNotification import io.modelcontextprotocol.kotlin.sdk.Method -import io.modelcontextprotocol.kotlin.sdk.PingRequest import io.modelcontextprotocol.kotlin.sdk.Prompt import io.modelcontextprotocol.kotlin.sdk.PromptArgument -import io.modelcontextprotocol.kotlin.sdk.PromptListChangedNotification import io.modelcontextprotocol.kotlin.sdk.ReadResourceRequest import io.modelcontextprotocol.kotlin.sdk.ReadResourceResult import io.modelcontextprotocol.kotlin.sdk.Resource -import io.modelcontextprotocol.kotlin.sdk.ResourceListChangedNotification -import io.modelcontextprotocol.kotlin.sdk.ResourceUpdatedNotification -import io.modelcontextprotocol.kotlin.sdk.SUPPORTED_PROTOCOL_VERSIONS import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.Tool import io.modelcontextprotocol.kotlin.sdk.ToolAnnotations -import io.modelcontextprotocol.kotlin.sdk.ToolListChangedNotification -import io.modelcontextprotocol.kotlin.sdk.shared.Protocol import io.modelcontextprotocol.kotlin.sdk.shared.ProtocolOptions -import io.modelcontextprotocol.kotlin.sdk.shared.RequestOptions +import io.modelcontextprotocol.kotlin.sdk.shared.Transport import kotlinx.atomicfu.atomic import kotlinx.atomicfu.getAndUpdate import kotlinx.atomicfu.update import kotlinx.collections.immutable.minus +import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.toPersistentSet -import kotlinx.coroutines.CompletableDeferred -import kotlinx.serialization.json.JsonObject private val logger = KotlinLogging.logger {} @@ -77,27 +54,18 @@ public class ServerOptions(public val capabilities: ServerCapabilities, enforceS * @param serverInfo Information about this server implementation (name, version). * @param options Configuration options for the server. */ -public open class Server(private val serverInfo: Implementation, options: ServerOptions) : Protocol(options) { +public open class Server(private val serverInfo: Implementation, private val options: ServerOptions) { + private val sessions = atomic(persistentListOf()) + @Suppress("ktlint:standard:backing-property-naming") private var _onInitialized: (() -> Unit) = {} + @Suppress("ktlint:standard:backing-property-naming") + private var _onConnect: (() -> Unit) = {} + @Suppress("ktlint:standard:backing-property-naming") private var _onClose: () -> Unit = {} - /** - * The client's reported capabilities after initialization. - */ - public var clientCapabilities: ClientCapabilities? = null - private set - - /** - * The client's version information after initialization. - */ - public var clientVersion: Implementation? = null - private set - - private val capabilities: ServerCapabilities = options.capabilities - private val _tools = atomic(persistentMapOf()) private val _prompts = atomic(persistentMapOf()) private val _resources = atomic(persistentMapOf()) @@ -108,55 +76,83 @@ public open class Server(private val serverInfo: Implementation, options: Server public val resources: Map get() = _resources.value - init { - logger.debug { "Initializing MCP server with capabilities: $capabilities" } + public suspend fun close() { + logger.debug { "Closing MCP server" } + sessions.value.forEach { it.close() } + _onClose() + } - // Core protocol handlers - setRequestHandler(Method.Defined.Initialize) { request, _ -> - handleInitialize(request) - } - setNotificationHandler(Method.Defined.NotificationsInitialized) { - _onInitialized() - CompletableDeferred(Unit) - } + /** + * Starts a new server session with the given transport and initializes + * internal request handlers based on the server's capabilities. + * + * @param transport The transport layer to connect the session with. + * @return The initialized and connected server session. + */ + public suspend fun connect(transport: Transport): ServerSession { + val session = ServerSession(serverInfo, options) // Internal handlers for tools - if (capabilities.tools != null) { - setRequestHandler(Method.Defined.ToolsList) { _, _ -> + if (options.capabilities.tools != null) { + session.setRequestHandler(Method.Defined.ToolsList) { _, _ -> handleListTools() } - setRequestHandler(Method.Defined.ToolsCall) { request, _ -> + session.setRequestHandler(Method.Defined.ToolsCall) { request, _ -> handleCallTool(request) } } // Internal handlers for prompts - if (capabilities.prompts != null) { - setRequestHandler(Method.Defined.PromptsList) { _, _ -> + if (options.capabilities.prompts != null) { + session.setRequestHandler(Method.Defined.PromptsList) { _, _ -> handleListPrompts() } - setRequestHandler(Method.Defined.PromptsGet) { request, _ -> + session.setRequestHandler(Method.Defined.PromptsGet) { request, _ -> handleGetPrompt(request) } } // Internal handlers for resources - if (capabilities.resources != null) { - setRequestHandler(Method.Defined.ResourcesList) { _, _ -> + if (options.capabilities.resources != null) { + session.setRequestHandler(Method.Defined.ResourcesList) { _, _ -> handleListResources() } - setRequestHandler(Method.Defined.ResourcesRead) { request, _ -> + session.setRequestHandler(Method.Defined.ResourcesRead) { request, _ -> handleReadResource(request) } - setRequestHandler(Method.Defined.ResourcesTemplatesList) { _, _ -> + session.setRequestHandler(Method.Defined.ResourcesTemplatesList) { _, _ -> handleListResourceTemplates() } } + + logger.debug { "Server session connecting to transport" } + session.connect(transport) + logger.debug { "Server session successfully connected to transport" } + sessions.update { it.add(session) } + + _onConnect() + return session + } + + /** + * Registers a callback to be invoked when the new server session connected. + */ + public fun onConnect(block: () -> Unit) { + val old = _onConnect + _onConnect = { + old() + block() + } } /** * Registers a callback to be invoked when the server has completed initialization. */ + @Deprecated( + "Initialization moved to ServerSession, use ServerSession.onInitialized instead.", + ReplaceWith("ServerSession.onInitialized"), + DeprecationLevel.WARNING, + ) public fun onInitialized(block: () -> Unit) { val old = _onInitialized _onInitialized = { @@ -176,14 +172,6 @@ public open class Server(private val serverInfo: Implementation, options: Server } } - /** - * Called when the server connection is closing. - */ - override fun onClose() { - logger.info { "Server connection closing" } - _onClose() - } - /** * Registers a single tool. The client can then call this tool. * @@ -192,7 +180,7 @@ public open class Server(private val serverInfo: Implementation, options: Server * @throws IllegalStateException If the server does not support tools. */ public fun addTool(tool: Tool, handler: suspend (CallToolRequest) -> CallToolResult) { - if (capabilities.tools == null) { + if (options.capabilities.tools == null) { logger.error { "Failed to add tool '${tool.name}': Server does not support tools capability" } throw IllegalStateException("Server does not support tools capability. Enable it in ServerOptions.") } @@ -232,7 +220,7 @@ public open class Server(private val serverInfo: Implementation, options: Server * @throws IllegalStateException If the server does not support tools. */ public fun addTools(toolsToAdd: List) { - if (capabilities.tools == null) { + if (options.capabilities.tools == null) { logger.error { "Failed to add tools: Server does not support tools capability" } throw IllegalStateException("Server does not support tools capability.") } @@ -248,7 +236,7 @@ public open class Server(private val serverInfo: Implementation, options: Server * @throws IllegalStateException If the server does not support tools. */ public fun removeTool(name: String): Boolean { - if (capabilities.tools == null) { + if (options.capabilities.tools == null) { logger.error { "Failed to remove tool '$name': Server does not support tools capability" } throw IllegalStateException("Server does not support tools capability.") } @@ -275,7 +263,7 @@ public open class Server(private val serverInfo: Implementation, options: Server * @throws IllegalStateException If the server does not support tools. */ public fun removeTools(toolNames: List): Int { - if (capabilities.tools == null) { + if (options.capabilities.tools == null) { logger.error { "Failed to remove tools: Server does not support tools capability" } throw IllegalStateException("Server does not support tools capability.") } @@ -302,7 +290,7 @@ public open class Server(private val serverInfo: Implementation, options: Server * @throws IllegalStateException If the server does not support prompts. */ public fun addPrompt(prompt: Prompt, promptProvider: suspend (GetPromptRequest) -> GetPromptResult) { - if (capabilities.prompts == null) { + if (options.capabilities.prompts == null) { logger.error { "Failed to add prompt '${prompt.name}': Server does not support prompts capability" } throw IllegalStateException("Server does not support prompts capability.") } @@ -336,7 +324,7 @@ public open class Server(private val serverInfo: Implementation, options: Server * @throws IllegalStateException If the server does not support prompts. */ public fun addPrompts(promptsToAdd: List) { - if (capabilities.prompts == null) { + if (options.capabilities.prompts == null) { logger.error { "Failed to add prompts: Server does not support prompts capability" } throw IllegalStateException("Server does not support prompts capability.") } @@ -352,7 +340,7 @@ public open class Server(private val serverInfo: Implementation, options: Server * @throws IllegalStateException If the server does not support prompts. */ public fun removePrompt(name: String): Boolean { - if (capabilities.prompts == null) { + if (options.capabilities.prompts == null) { logger.error { "Failed to remove prompt '$name': Server does not support prompts capability" } throw IllegalStateException("Server does not support prompts capability.") } @@ -379,7 +367,7 @@ public open class Server(private val serverInfo: Implementation, options: Server * @throws IllegalStateException If the server does not support prompts. */ public fun removePrompts(promptNames: List): Int { - if (capabilities.prompts == null) { + if (options.capabilities.prompts == null) { logger.error { "Failed to remove prompts: Server does not support prompts capability" } throw IllegalStateException("Server does not support prompts capability.") } @@ -416,7 +404,7 @@ public open class Server(private val serverInfo: Implementation, options: Server mimeType: String = "text/html", readHandler: suspend (ReadResourceRequest) -> ReadResourceResult, ) { - if (capabilities.resources == null) { + if (options.capabilities.resources == null) { logger.error { "Failed to add resource '$name': Server does not support resources capability" } throw IllegalStateException("Server does not support resources capability.") } @@ -436,7 +424,7 @@ public open class Server(private val serverInfo: Implementation, options: Server * @throws IllegalStateException If the server does not support resources. */ public fun addResources(resourcesToAdd: List) { - if (capabilities.resources == null) { + if (options.capabilities.resources == null) { logger.error { "Failed to add resources: Server does not support resources capability" } throw IllegalStateException("Server does not support resources capability.") } @@ -452,7 +440,7 @@ public open class Server(private val serverInfo: Implementation, options: Server * @throws IllegalStateException If the server does not support resources. */ public fun removeResource(uri: String): Boolean { - if (capabilities.resources == null) { + if (options.capabilities.resources == null) { logger.error { "Failed to remove resource '$uri': Server does not support resources capability" } throw IllegalStateException("Server does not support resources capability.") } @@ -479,7 +467,7 @@ public open class Server(private val serverInfo: Implementation, options: Server * @throws IllegalStateException If the server does not support resources. */ public fun removeResources(uris: List): Int { - if (capabilities.resources == null) { + if (options.capabilities.resources == null) { logger.error { "Failed to remove resources: Server does not support resources capability" } throw IllegalStateException("Server does not support resources capability.") } @@ -499,123 +487,7 @@ public open class Server(private val serverInfo: Implementation, options: Server return removedCount } - /** - * Sends a ping request to the client to check connectivity. - * - * @return The result of the ping request. - * @throws IllegalStateException If for some reason the method is not supported or the connection is closed. - */ - public suspend fun ping(): EmptyRequestResult = request(PingRequest()) - - /** - * Creates a message using the server's sampling capability. - * - * @param params The parameters for creating a message. - * @param options Optional request options. - * @return The created message result. - * @throws IllegalStateException If the server does not support sampling or if the request fails. - */ - public suspend fun createMessage( - params: CreateMessageRequest, - options: RequestOptions? = null, - ): CreateMessageResult { - logger.debug { "Creating message with params: $params" } - return request(params, options) - } - - /** - * Lists the available "roots" from the client's perspective (if supported). - * - * @param params JSON parameters for the request, usually empty. - * @param options Optional request options. - * @return The list of roots. - * @throws IllegalStateException If the server or client does not support roots. - */ - public suspend fun listRoots( - params: JsonObject = EmptyJsonObject, - options: RequestOptions? = null, - ): ListRootsResult { - logger.debug { "Listing roots with params: $params" } - return request(ListRootsRequest(params), options) - } - - public suspend fun createElicitation( - message: String, - requestedSchema: RequestedSchema, - options: RequestOptions? = null, - ): CreateElicitationResult { - logger.debug { "Creating elicitation with message: $message" } - return request(CreateElicitationRequest(message, requestedSchema), options) - } - - /** - * Sends a logging message notification to the client. - * - * @param params The logging message notification parameters. - */ - public suspend fun sendLoggingMessage(params: LoggingMessageNotification) { - logger.trace { "Sending logging message: ${params.params.data}" } - notification(params) - } - - /** - * Sends a resource-updated notification to the client, indicating that a specific resource has changed. - * - * @param params Details of the updated resource. - */ - public suspend fun sendResourceUpdated(params: ResourceUpdatedNotification) { - logger.debug { "Sending resource updated notification for: ${params.params.uri}" } - notification(params) - } - - /** - * Sends a notification to the client indicating that the list of resources has changed. - */ - public suspend fun sendResourceListChanged() { - logger.debug { "Sending resource list changed notification" } - notification(ResourceListChangedNotification()) - } - - /** - * Sends a notification to the client indicating that the list of tools has changed. - */ - public suspend fun sendToolListChanged() { - logger.debug { "Sending tool list changed notification" } - notification(ToolListChangedNotification()) - } - - /** - * Sends a notification to the client indicating that the list of prompts has changed. - */ - public suspend fun sendPromptListChanged() { - logger.debug { "Sending prompt list changed notification" } - notification(PromptListChangedNotification()) - } - // --- Internal Handlers --- - - private suspend fun handleInitialize(request: InitializeRequest): InitializeResult { - logger.info { "Handling initialize request from client ${request.clientInfo}" } - clientCapabilities = request.capabilities - clientVersion = request.clientInfo - - val requestedVersion = request.protocolVersion - val protocolVersion = if (SUPPORTED_PROTOCOL_VERSIONS.contains(requestedVersion)) { - requestedVersion - } else { - logger.warn { - "Client requested unsupported protocol version $requestedVersion, falling back to $LATEST_PROTOCOL_VERSION" - } - LATEST_PROTOCOL_VERSION - } - - return InitializeResult( - protocolVersion = protocolVersion, - capabilities = capabilities, - serverInfo = serverInfo, - ) - } - private suspend fun handleListTools(): ListToolsResult { val toolList = tools.values.map { it.tool } return ListToolsResult(tools = toolList, nextCursor = null) @@ -666,147 +538,6 @@ public open class Server(private val serverInfo: Implementation, options: Server // If you have resource templates, return them here. For now, return empty. return ListResourceTemplatesResult(listOf()) } - - /** - * Asserts that the client supports the capability required for the given [method]. - * - * This method is automatically called by the [Protocol] framework before handling requests. - * Throws [IllegalStateException] if the capability is not supported. - * - * @param method The method for which we are asserting capability. - */ - override fun assertCapabilityForMethod(method: Method) { - logger.trace { "Asserting capability for method: ${method.value}" } - when (method.value) { - "sampling/createMessage" -> { - if (clientCapabilities?.sampling == null) { - logger.error { "Client capability assertion failed: sampling not supported" } - throw IllegalStateException("Client does not support sampling (required for ${method.value})") - } - } - - "roots/list" -> { - if (clientCapabilities?.roots == null) { - throw IllegalStateException("Client does not support listing roots (required for ${method.value})") - } - } - - "elicitation/create" -> { - if (clientCapabilities?.elicitation == null) { - throw IllegalStateException("Client does not support elicitation (required for ${method.value})") - } - } - - "ping" -> { - // No specific capability required - } - } - } - - /** - * Asserts that the server can handle the specified notification method. - * - * Throws [IllegalStateException] if the server does not have the capabilities required to handle this notification. - * - * @param method The notification method. - */ - override fun assertNotificationCapability(method: Method) { - logger.trace { "Asserting notification capability for method: ${method.value}" } - when (method.value) { - "notifications/message" -> { - if (capabilities.logging == null) { - logger.error { "Server capability assertion failed: logging not supported" } - throw IllegalStateException("Server does not support logging (required for ${method.value})") - } - } - - "notifications/resources/updated", - "notifications/resources/list_changed", - -> { - if (capabilities.resources == null) { - throw IllegalStateException( - "Server does not support notifying about resources (required for ${method.value})", - ) - } - } - - "notifications/tools/list_changed" -> { - if (capabilities.tools == null) { - throw IllegalStateException( - "Server does not support notifying of tool list changes (required for ${method.value})", - ) - } - } - - "notifications/prompts/list_changed" -> { - if (capabilities.prompts == null) { - throw IllegalStateException( - "Server does not support notifying of prompt list changes (required for ${method.value})", - ) - } - } - - "notifications/cancelled", - "notifications/progress", - -> { - // Always allowed - } - } - } - - /** - * Asserts that the server can handle the specified request method. - * - * Throws [IllegalStateException] if the server does not have the capabilities required to handle this request. - * - * @param method The request method. - */ - override fun assertRequestHandlerCapability(method: Method) { - logger.trace { "Asserting request handler capability for method: ${method.value}" } - when (method.value) { - "sampling/createMessage" -> { - if (capabilities.sampling == null) { - logger.error { "Server capability assertion failed: sampling not supported" } - throw IllegalStateException("Server does not support sampling (required for $method)") - } - } - - "logging/setLevel" -> { - if (capabilities.logging == null) { - throw IllegalStateException("Server does not support logging (required for $method)") - } - } - - "prompts/get", - "prompts/list", - -> { - if (capabilities.prompts == null) { - throw IllegalStateException("Server does not support prompts (required for $method)") - } - } - - "resources/list", - "resources/templates/list", - "resources/read", - -> { - if (capabilities.resources == null) { - throw IllegalStateException("Server does not support resources (required for $method)") - } - } - - "tools/call", - "tools/list", - -> { - if (capabilities.tools == null) { - throw IllegalStateException("Server does not support tools (required for $method)") - } - } - - "ping", "initialize" -> { - // No capability required - } - } - } } /** diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt new file mode 100644 index 0000000..471ebf0 --- /dev/null +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt @@ -0,0 +1,371 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.github.oshai.kotlinlogging.KotlinLogging +import io.modelcontextprotocol.kotlin.sdk.ClientCapabilities +import io.modelcontextprotocol.kotlin.sdk.CreateElicitationRequest +import io.modelcontextprotocol.kotlin.sdk.CreateElicitationRequest.RequestedSchema +import io.modelcontextprotocol.kotlin.sdk.CreateElicitationResult +import io.modelcontextprotocol.kotlin.sdk.CreateMessageRequest +import io.modelcontextprotocol.kotlin.sdk.CreateMessageResult +import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject +import io.modelcontextprotocol.kotlin.sdk.EmptyRequestResult +import io.modelcontextprotocol.kotlin.sdk.Implementation +import io.modelcontextprotocol.kotlin.sdk.InitializeRequest +import io.modelcontextprotocol.kotlin.sdk.InitializeResult +import io.modelcontextprotocol.kotlin.sdk.InitializedNotification +import io.modelcontextprotocol.kotlin.sdk.LATEST_PROTOCOL_VERSION +import io.modelcontextprotocol.kotlin.sdk.ListRootsRequest +import io.modelcontextprotocol.kotlin.sdk.ListRootsResult +import io.modelcontextprotocol.kotlin.sdk.LoggingMessageNotification +import io.modelcontextprotocol.kotlin.sdk.Method +import io.modelcontextprotocol.kotlin.sdk.Method.Defined +import io.modelcontextprotocol.kotlin.sdk.PingRequest +import io.modelcontextprotocol.kotlin.sdk.PromptListChangedNotification +import io.modelcontextprotocol.kotlin.sdk.ResourceListChangedNotification +import io.modelcontextprotocol.kotlin.sdk.ResourceUpdatedNotification +import io.modelcontextprotocol.kotlin.sdk.SUPPORTED_PROTOCOL_VERSIONS +import io.modelcontextprotocol.kotlin.sdk.ToolListChangedNotification +import io.modelcontextprotocol.kotlin.sdk.shared.Protocol +import io.modelcontextprotocol.kotlin.sdk.shared.RequestOptions +import kotlinx.coroutines.CompletableDeferred +import kotlinx.serialization.json.JsonObject + +private val logger = KotlinLogging.logger {} + +public open class ServerSession(private val serverInfo: Implementation, options: ServerOptions) : Protocol(options) { + @Suppress("ktlint:standard:backing-property-naming") + private var _onInitialized: (() -> Unit) = {} + + @Suppress("ktlint:standard:backing-property-naming") + private var _onClose: () -> Unit = {} + + init { + // Core protocol handlers + setRequestHandler(Method.Defined.Initialize) { request, _ -> + handleInitialize(request) + } + setNotificationHandler(Method.Defined.NotificationsInitialized) { + _onInitialized() + CompletableDeferred(Unit) + } + } + + /** + * The capabilities supported by the server, related to the session. + */ + private val serverCapabilities = options.capabilities + + /** + * The client's reported capabilities after initialization. + */ + public var clientCapabilities: ClientCapabilities? = null + private set + + /** + * The client's version information after initialization. + */ + public var clientVersion: Implementation? = null + private set + + /** + * Registers a callback to be invoked when the server has completed initialization. + */ + public fun onInitialized(block: () -> Unit) { + val old = _onInitialized + _onInitialized = { + old() + block() + } + } + + /** + * Registers a callback to be invoked when the server session is closing. + */ + public fun onClose(block: () -> Unit) { + val old = _onClose + _onClose = { + old() + block() + } + } + + /** + * Called when the server session is closing. + */ + override fun onClose() { + logger.debug { "Server connection closing" } + _onClose() + } + + /** + * Sends a ping request to the client to check connectivity. + * + * @return The result of the ping request. + * @throws IllegalStateException If for some reason the method is not supported or the connection is closed. + */ + public suspend fun ping(): EmptyRequestResult = request(PingRequest()) + + /** + * Creates a message using the server's sampling capability. + * + * @param params The parameters for creating a message. + * @param options Optional request options. + * @return The created message result. + * @throws IllegalStateException If the server does not support sampling or if the request fails. + */ + public suspend fun createMessage( + params: CreateMessageRequest, + options: RequestOptions? = null, + ): CreateMessageResult { + logger.debug { "Creating message with params: $params" } + return request(params, options) + } + + /** + * Lists the available "roots" from the client's perspective (if supported). + * + * @param params JSON parameters for the request, usually empty. + * @param options Optional request options. + * @return The list of roots. + * @throws IllegalStateException If the server or client does not support roots. + */ + public suspend fun listRoots( + params: JsonObject = EmptyJsonObject, + options: RequestOptions? = null, + ): ListRootsResult { + logger.debug { "Listing roots with params: $params" } + return request(ListRootsRequest(params), options) + } + + public suspend fun createElicitation( + message: String, + requestedSchema: RequestedSchema, + options: RequestOptions? = null, + ): CreateElicitationResult { + logger.debug { "Creating elicitation with message: $message" } + return request(CreateElicitationRequest(message, requestedSchema), options) + } + + /** + * Sends a logging message notification to the client. + * + * @param notification The logging message notification. + */ + public suspend fun sendLoggingMessage(notification: LoggingMessageNotification) { + logger.trace { "Sending logging message: ${notification.params.data}" } + notification(notification) + } + + /** + * Sends a resource-updated notification to the client, indicating that a specific resource has changed. + * + * @param notification Details of the updated resource. + */ + public suspend fun sendResourceUpdated(notification: ResourceUpdatedNotification) { + logger.debug { "Sending resource updated notification for: ${notification.params.uri}" } + notification(notification) + } + + /** + * Sends a notification to the client indicating that the list of resources has changed. + */ + public suspend fun sendResourceListChanged() { + logger.debug { "Sending resource list changed notification" } + notification(ResourceListChangedNotification()) + } + + /** + * Sends a notification to the client indicating that the list of tools has changed. + */ + public suspend fun sendToolListChanged() { + logger.debug { "Sending tool list changed notification" } + notification(ToolListChangedNotification()) + } + + /** + * Sends a notification to the client indicating that the list of prompts has changed. + */ + public suspend fun sendPromptListChanged() { + logger.debug { "Sending prompt list changed notification" } + notification(PromptListChangedNotification()) + } + + /** + * Asserts that the client supports the capability required for the given [method]. + * + * This method is automatically called by the [Protocol] framework before handling requests. + * Throws [IllegalStateException] if the capability is not supported. + * + * @param method The method for which we are asserting capability. + */ + override fun assertCapabilityForMethod(method: Method) { + logger.trace { "Asserting capability for method: ${method.value}" } + when (method) { + Defined.SamplingCreateMessage -> { + if (clientCapabilities?.sampling == null) { + logger.error { "Client capability assertion failed: sampling not supported" } + throw IllegalStateException("Client does not support sampling (required for ${method.value})") + } + } + + Defined.RootsList -> { + if (clientCapabilities?.roots == null) { + logger.error { "Client capability assertion failed: listing roots not supported" } + throw IllegalStateException("Client does not support listing roots (required for ${method.value})") + } + } + + Defined.ElicitationCreate -> { + if (clientCapabilities?.elicitation == null) { + logger.error { "Client capability assertion failed: elicitation not supported" } + throw IllegalStateException("Client does not support elicitation (required for ${method.value})") + } + } + + Defined.Ping -> { + // No specific capability required + } + + else -> { + // For notifications not specifically listed, no assertion by default + } + } + } + + /** + * Asserts that the server can handle the specified notification method. + * + * Throws [IllegalStateException] if the server does not have the capabilities required to handle this notification. + * + * @param method The notification method. + */ + override fun assertNotificationCapability(method: Method) { + logger.trace { "Asserting notification capability for method: ${method.value}" } + when (method) { + Defined.NotificationsMessage -> { + if (serverCapabilities.logging == null) { + logger.error { "Server capability assertion failed: logging not supported" } + throw IllegalStateException("Server does not support logging (required for ${method.value})") + } + } + + Defined.NotificationsResourcesUpdated, + Defined.NotificationsResourcesListChanged, + -> { + if (serverCapabilities.resources == null) { + throw IllegalStateException( + "Server does not support notifying about resources (required for ${method.value})", + ) + } + } + + Defined.NotificationsToolsListChanged -> { + if (serverCapabilities.tools == null) { + throw IllegalStateException( + "Server does not support notifying of tool list changes (required for ${method.value})", + ) + } + } + + Defined.NotificationsPromptsListChanged -> { + if (serverCapabilities.prompts == null) { + throw IllegalStateException( + "Server does not support notifying of prompt list changes (required for ${method.value})", + ) + } + } + + Defined.NotificationsCancelled, + Defined.NotificationsProgress, + -> { + // Always allowed + } + + else -> { + // For notifications not specifically listed, no assertion by default + } + } + } + + /** + * Asserts that the server can handle the specified request method. + * + * Throws [IllegalStateException] if the server does not have the capabilities required to handle this request. + * + * @param method The request method. + */ + override fun assertRequestHandlerCapability(method: Method) { + logger.trace { "Asserting request handler capability for method: ${method.value}" } + when (method) { + Defined.SamplingCreateMessage -> { + if (serverCapabilities.sampling == null) { + logger.error { "Server capability assertion failed: sampling not supported" } + throw IllegalStateException("Server does not support sampling (required for $method)") + } + } + + Defined.LoggingSetLevel -> { + if (serverCapabilities.logging == null) { + throw IllegalStateException("Server does not support logging (required for $method)") + } + } + + Defined.PromptsGet, + Defined.PromptsList, + -> { + if (serverCapabilities.prompts == null) { + throw IllegalStateException("Server does not support prompts (required for $method)") + } + } + + Defined.ResourcesList, + Defined.ResourcesTemplatesList, + Defined.ResourcesRead, + Defined.ResourcesSubscribe, + Defined.ResourcesUnsubscribe, + -> { + if (serverCapabilities.resources == null) { + throw IllegalStateException("Server does not support resources (required for $method)") + } + } + + Defined.ToolsCall, + Defined.ToolsList, + -> { + if (serverCapabilities.tools == null) { + throw IllegalStateException("Server does not support tools (required for $method)") + } + } + + Defined.Ping, Defined.Initialize -> { + // No capability required + } + + else -> { + // For notifications not specifically listed, no assertion by default + } + } + } + + private suspend fun handleInitialize(request: InitializeRequest): InitializeResult { + logger.debug { "Handling initialization request from client" } + clientCapabilities = request.capabilities + clientVersion = request.clientInfo + + val requestedVersion = request.protocolVersion + val protocolVersion = if (SUPPORTED_PROTOCOL_VERSIONS.contains(requestedVersion)) { + requestedVersion + } else { + logger.warn { + "Client requested unsupported protocol version $requestedVersion, falling back to $LATEST_PROTOCOL_VERSION" + } + LATEST_PROTOCOL_VERSION + } + + return InitializeResult( + protocolVersion = protocolVersion, + capabilities = serverCapabilities, + serverInfo = serverInfo, + ) + } +} diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt index a3d2fd3..1ceb764 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt @@ -1,12 +1,83 @@ package io.modelcontextprotocol.kotlin.sdk.server +import io.github.oshai.kotlinlogging.KotlinLogging +import io.ktor.server.application.Application +import io.ktor.server.application.install import io.ktor.server.routing.Route +import io.ktor.server.routing.Routing +import io.ktor.server.routing.routing import io.ktor.server.websocket.WebSocketServerSession +import io.ktor.server.websocket.WebSockets import io.ktor.server.websocket.webSocket +import io.ktor.utils.io.CancellationException +import io.ktor.utils.io.KtorDsl import io.modelcontextprotocol.kotlin.sdk.Implementation import io.modelcontextprotocol.kotlin.sdk.LIB_VERSION import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.shared.IMPLEMENTATION_NAME +import kotlinx.coroutines.awaitCancellation + +private val logger = KotlinLogging.logger {} + +/** + * Configures the Ktor Application to handle Model Context Protocol (MCP) over WebSocket. + */ +@KtorDsl +public fun Routing.mcpWebSocket(block: () -> Server) { + webSocket { + mcpWebSocketEndpoint(block) + } +} + +/** + * Configures the Ktor Application to handle Model Context Protocol (MCP) over WebSocket. + */ +@KtorDsl +public fun Routing.mcpWebSocket(path: String, block: () -> Server) { + webSocket(path) { + mcpWebSocketEndpoint(block) + } +} + +/** + * Configures the Ktor Application to handle Model Context Protocol (MCP) over WebSocket. + */ +@KtorDsl +public fun Application.mcpWebSocket(block: () -> Server) { + install(WebSockets) + + routing { + mcpWebSocket(block) + } +} + +/** + * Configures the Ktor Application to handle Model Context Protocol (MCP) over WebSocket at the specified path. + */ +@KtorDsl +public fun Application.mcpWebSocket(path: String, block: () -> Server) { + install(WebSockets) + + routing { + mcpWebSocket(path, block) + } +} + +internal suspend fun WebSocketServerSession.mcpWebSocketEndpoint(block: () -> Server) { + logger.info { "Ktor Server establishing new connection" } + val transport = createMcpTransport(this) + val server = block() + var session: ServerSession? = null + try { + session = server.connect(transport) + awaitCancellation() + } catch (e: CancellationException) { + session?.close() + } +} + +private fun createMcpTransport(webSocketSession: WebSocketServerSession): WebSocketMcpServerTransport = + WebSocketMcpServerTransport(webSocketSession) /** * Registers a WebSocket route that establishes an MCP (Model Context Protocol) server session. @@ -14,12 +85,28 @@ import io.modelcontextprotocol.kotlin.sdk.shared.IMPLEMENTATION_NAME * @param options Optional server configuration settings for the MCP server. * @param handler A suspend function that defines the server's behavior. */ +@Deprecated( + "Use mcpWebSocket with a lambda that returns a Server instance instead", + ReplaceWith("Routing.mcpWebSocket"), + DeprecationLevel.WARNING, +) public fun Route.mcpWebSocket(options: ServerOptions? = null, handler: suspend Server.() -> Unit = {}) { webSocket { createMcpServer(this, options, handler) } } +@Deprecated( + "Use mcpWebSocket with a lambda that returns a Server instance instead", + ReplaceWith("Routing.mcpWebSocket"), + DeprecationLevel.WARNING, +) +public fun Route.mcpWebSocket(block: () -> Server) { + webSocket { + block().connect(createMcpTransport(this)) + } +} + /** * Registers a WebSocket route at the specified [path] that establishes an MCP server session. * @@ -27,6 +114,11 @@ public fun Route.mcpWebSocket(options: ServerOptions? = null, handler: suspend S * @param options Optional server configuration settings for the MCP server. * @param handler A suspend function that defines the server's behavior. */ +@Deprecated( + "Use mcpWebSocket with a path and a lambda that returns a Server instance instead", + ReplaceWith("Routing.mcpWebSocket"), + DeprecationLevel.WARNING, +) public fun Route.mcpWebSocket(path: String, options: ServerOptions? = null, handler: suspend Server.() -> Unit = {}) { webSocket(path) { createMcpServer(this, options, handler) @@ -38,6 +130,11 @@ public fun Route.mcpWebSocket(path: String, options: ServerOptions? = null, hand * * @param handler A suspend function that defines the behavior of the transport layer. */ +@Deprecated( + "Use mcpWebSocket with a lambda that returns a Server instance instead", + ReplaceWith("Routing.mcpWebSocket"), + DeprecationLevel.WARNING, +) public fun Route.mcpWebSocketTransport(handler: suspend WebSocketMcpServerTransport.() -> Unit = {}) { webSocket { val transport = createMcpTransport(this) @@ -53,6 +150,11 @@ public fun Route.mcpWebSocketTransport(handler: suspend WebSocketMcpServerTransp * @param path The URL path at which to register the WebSocket route. * @param handler A suspend function that defines the behavior of the transport layer. */ +@Deprecated( + "Use mcpWebSocket with a path and a lambda that returns a Server instance instead", + ReplaceWith("Routing.mcpWebSocket"), + DeprecationLevel.WARNING, +) public fun Route.mcpWebSocketTransport(path: String, handler: suspend WebSocketMcpServerTransport.() -> Unit = {}) { webSocket(path) { val transport = createMcpTransport(this) @@ -62,6 +164,11 @@ public fun Route.mcpWebSocketTransport(path: String, handler: suspend WebSocketM } } +@Deprecated( + "Use mcpWebSocket with a lambda that returns a Server instance instead", + ReplaceWith("mcpWebSocket"), + DeprecationLevel.WARNING, +) private suspend fun Route.createMcpServer( session: WebSocketServerSession, options: ServerOptions?, @@ -87,6 +194,3 @@ private suspend fun Route.createMcpServer( handler(server) server.close() } - -private fun createMcpTransport(session: WebSocketServerSession): WebSocketMcpServerTransport = - WebSocketMcpServerTransport(session) diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpServerTransport.kt index 877fda5..0c0cc78 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpServerTransport.kt @@ -1,10 +1,13 @@ package io.modelcontextprotocol.kotlin.sdk.server +import io.github.oshai.kotlinlogging.KotlinLogging import io.ktor.http.HttpHeaders import io.ktor.server.websocket.WebSocketServerSession import io.modelcontextprotocol.kotlin.sdk.shared.MCP_SUBPROTOCOL import io.modelcontextprotocol.kotlin.sdk.shared.WebSocketMcpTransport +private val logger = KotlinLogging.logger {} + /** * Server-side implementation of the MCP (Model Context Protocol) transport over WebSocket. * @@ -12,6 +15,7 @@ import io.modelcontextprotocol.kotlin.sdk.shared.WebSocketMcpTransport */ public class WebSocketMcpServerTransport(override val session: WebSocketServerSession) : WebSocketMcpTransport() { override suspend fun initializeSession() { + logger.debug { "Checking session headers" } val subprotocol = session.call.request.headers[HttpHeaders.SecWebSocketProtocol] if (subprotocol != MCP_SUBPROTOCOL) { error("Invalid subprotocol: $subprotocol, expected $MCP_SUBPROTOCOL") diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt index 0294620..d132ae5 100644 --- a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt +++ b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt @@ -30,6 +30,7 @@ import io.modelcontextprotocol.kotlin.sdk.TextContent import io.modelcontextprotocol.kotlin.sdk.Tool import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions +import io.modelcontextprotocol.kotlin.sdk.server.ServerSession import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport import kotlinx.coroutines.CompletableDeferred @@ -241,7 +242,28 @@ class ClientTest { serverOptions, ) - server.setRequestHandler(Method.Defined.Initialize) { _, _ -> + val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() + + val client = Client( + clientInfo = Implementation(name = "test client", version = "1.0"), + options = ClientOptions( + capabilities = ClientCapabilities(sampling = EmptyJsonObject), + ), + ) + + val serverSessionResult = CompletableDeferred() + + listOf( + launch { + client.connect(clientTransport) + }, + launch { + serverSessionResult.complete(server.connect(serverTransport)) + }, + ).joinAll() + + val serverSession = serverSessionResult.await() + serverSession.setRequestHandler(Method.Defined.Initialize) { _, _ -> InitializeResult( protocolVersion = LATEST_PROTOCOL_VERSION, capabilities = ServerCapabilities( @@ -252,32 +274,13 @@ class ClientTest { ) } - server.setRequestHandler(Method.Defined.ResourcesList) { _, _ -> + serverSession.setRequestHandler(Method.Defined.ResourcesList) { _, _ -> ListResourcesResult(resources = emptyList(), nextCursor = null) } - server.setRequestHandler(Method.Defined.ToolsList) { _, _ -> + serverSession.setRequestHandler(Method.Defined.ToolsList) { _, _ -> ListToolsResult(tools = emptyList(), nextCursor = null) } - - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - - val client = Client( - clientInfo = Implementation(name = "test client", version = "1.0"), - options = ClientOptions( - capabilities = ClientCapabilities(sampling = EmptyJsonObject), - ), - ) - - listOf( - launch { - client.connect(clientTransport) - }, - launch { - server.connect(serverTransport) - }, - ).joinAll() - // Server supports resources and tools, but not prompts val caps = client.serverCapabilities assertEquals(ServerCapabilities.Resources(null, null), caps?.resources) @@ -368,24 +371,27 @@ class ClientTest { val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() + val serverSessionResult = CompletableDeferred() + listOf( launch { client.connect(clientTransport) println("Client connected") }, launch { - server.connect(serverTransport) + serverSessionResult.complete(server.connect(serverTransport)) println("Server connected") }, ).joinAll() + val serverSession = serverSessionResult.await() // These should not throw val jsonObject = buildJsonObject { put("name", "John") put("age", 30) put("isStudent", false) } - server.sendLoggingMessage( + serverSession.sendLoggingMessage( LoggingMessageNotification( params = LoggingMessageNotification.Params( level = LoggingLevel.info, @@ -393,11 +399,11 @@ class ClientTest { ), ), ) - server.sendResourceListChanged() + serverSession.sendResourceListChanged() // This should fail because the server doesn't have the tools capability val ex = assertFailsWith { - server.sendToolListChanged() + serverSession.sendToolListChanged() } assertTrue(ex.message?.contains("Server does not support notifying of tool list changes") == true) } @@ -418,7 +424,29 @@ class ClientTest { val def = CompletableDeferred() val defTimeOut = CompletableDeferred() - server.setRequestHandler(Method.Defined.ResourcesList) { _, _ -> + val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() + + val client = Client( + clientInfo = Implementation(name = "test client", version = "1.0"), + options = ClientOptions(capabilities = ClientCapabilities()), + ) + + val serverSessionResult = CompletableDeferred() + + listOf( + launch { + client.connect(clientTransport) + println("Client connected") + }, + launch { + serverSessionResult.complete(server.connect(serverTransport)) + println("Server connected") + }, + ).joinAll() + + val serverSession = serverSessionResult.await() + + serverSession.setRequestHandler(Method.Defined.ResourcesList) { _, _ -> // Simulate delay def.complete(Unit) try { @@ -431,24 +459,6 @@ class ClientTest { fail("Shouldn't have been called") } - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - - val client = Client( - clientInfo = Implementation(name = "test client", version = "1.0"), - options = ClientOptions(capabilities = ClientCapabilities()), - ) - - listOf( - launch { - client.connect(clientTransport) - println("Client connected") - }, - launch { - server.connect(serverTransport) - println("Server connected") - }, - ).joinAll() - val defCancel = CompletableDeferred() val job = launch { try { @@ -478,7 +488,27 @@ class ClientTest { ), ) - server.setRequestHandler(Method.Defined.ResourcesList) { _, _ -> + val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() + val client = Client( + clientInfo = Implementation(name = "test client", version = "1.0"), + options = ClientOptions(capabilities = ClientCapabilities()), + ) + + val serverSessionResult = CompletableDeferred() + + listOf( + launch { + client.connect(clientTransport) + println("Client connected") + }, + launch { + serverSessionResult.complete(server.connect(serverTransport)) + println("Server connected") + }, + ).joinAll() + + val serverSession = serverSessionResult.await() + serverSession.setRequestHandler(Method.Defined.ResourcesList) { _, _ -> // Simulate a delayed response // Wait ~100ms unless canceled try { @@ -492,23 +522,6 @@ class ClientTest { ListResourcesResult(resources = emptyList()) } - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - val client = Client( - clientInfo = Implementation(name = "test client", version = "1.0"), - options = ClientOptions(capabilities = ClientCapabilities()), - ) - - listOf( - launch { - client.connect(clientTransport) - println("Client connected") - }, - launch { - server.connect(serverTransport) - println("Server connected") - }, - ).joinAll() - // Request with 1 msec timeout should fail immediately val ex = assertFailsWith { withTimeout(1) { @@ -559,34 +572,6 @@ class ClientTest { serverOptions, ) - server.setRequestHandler(Method.Defined.Initialize) { _, _ -> - InitializeResult( - protocolVersion = LATEST_PROTOCOL_VERSION, - capabilities = ServerCapabilities( - resources = ServerCapabilities.Resources(null, null), - tools = ServerCapabilities.Tools(null), - ), - serverInfo = Implementation(name = "test", version = "1.0"), - ) - } - val serverListToolsResult = ListToolsResult( - tools = listOf( - Tool( - name = "testTool", - title = "testTool title", - description = "testTool description", - annotations = null, - inputSchema = Tool.Input(), - outputSchema = null, - ), - ), - nextCursor = null, - ) - - server.setRequestHandler(Method.Defined.ToolsList) { _, _ -> - serverListToolsResult - } - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() val client = Client( @@ -601,15 +586,50 @@ class ClientTest { receivedMessage = msg } + val serverSessionResult = CompletableDeferred() + listOf( launch { client.connect(clientTransport) + println("Client connected") }, launch { - server.connect(serverTransport) + serverSessionResult.complete(server.connect(serverTransport)) + println("Server connected") }, ).joinAll() + val serverSession = serverSessionResult.await() + + serverSession.setRequestHandler(Method.Defined.Initialize) { _, _ -> + InitializeResult( + protocolVersion = LATEST_PROTOCOL_VERSION, + capabilities = ServerCapabilities( + resources = ServerCapabilities.Resources(null, null), + tools = ServerCapabilities.Tools(null), + ), + serverInfo = Implementation(name = "test", version = "1.0"), + ) + } + + val serverListToolsResult = ListToolsResult( + tools = listOf( + Tool( + name = "testTool", + title = "testTool title", + description = "testTool description", + annotations = null, + inputSchema = Tool.Input(), + outputSchema = null, + ), + ), + nextCursor = null, + ) + + serverSession.setRequestHandler(Method.Defined.ToolsList) { _, _ -> + serverListToolsResult + } + val serverCapabilities = client.serverCapabilities assertEquals(ServerCapabilities.Tools(null), serverCapabilities?.tools) @@ -652,15 +672,25 @@ class ClientTest { ), ) + val serverSessionResult = CompletableDeferred() + listOf( - launch { client.connect(clientTransport) }, - launch { server.connect(serverTransport) }, + launch { + client.connect(clientTransport) + println("Client connected") + }, + launch { + serverSessionResult.complete(server.connect(serverTransport)) + println("Server connected") + }, ).joinAll() - val clientCapabilities = server.clientCapabilities + val serverSession = serverSessionResult.await() + + val clientCapabilities = serverSession.clientCapabilities assertEquals(ClientCapabilities.Roots(null), clientCapabilities?.roots) - val listRootsResult = server.listRoots() + val listRootsResult = serverSession.listRoots() assertEquals(listRootsResult.roots, clientRoots) } @@ -773,16 +803,28 @@ class ClientTest { // Track notifications var rootListChangedNotificationReceived = false - server.setNotificationHandler(Method.Defined.NotificationsRootsListChanged) { + + val serverSessionResult = CompletableDeferred() + + listOf( + launch { + client.connect(clientTransport) + println("Client connected") + }, + launch { + serverSessionResult.complete(server.connect(serverTransport)) + println("Server connected") + }, + ).joinAll() + + val serverSession = serverSessionResult.await() + serverSession.setNotificationHandler( + Method.Defined.NotificationsRootsListChanged, + ) { rootListChangedNotificationReceived = true CompletableDeferred(Unit) } - listOf( - launch { client.connect(clientTransport) }, - launch { server.connect(serverTransport) }, - ).joinAll() - client.sendRootsListChanged() assertTrue( @@ -809,14 +851,24 @@ class ClientTest { ), ) + val serverSessionResult = CompletableDeferred() + listOf( - launch { client.connect(clientTransport) }, - launch { server.connect(serverTransport) }, + launch { + client.connect(clientTransport) + println("Client connected") + }, + launch { + serverSessionResult.complete(server.connect(serverTransport)) + println("Server connected") + }, ).joinAll() + val serverSession = serverSessionResult.await() + // Verify that creating an elicitation throws an exception val exception = assertFailsWith { - server.createElicitation( + serverSession.createElicitation( message = "Please provide your GitHub username", requestedSchema = CreateElicitationRequest.RequestedSchema( properties = buildJsonObject { @@ -879,12 +931,22 @@ class ClientTest { ), ) + val serverSessionResult = CompletableDeferred() + listOf( - launch { client.connect(clientTransport) }, - launch { server.connect(serverTransport) }, + launch { + client.connect(clientTransport) + println("Client connected") + }, + launch { + serverSessionResult.complete(server.connect(serverTransport)) + println("Server connected") + }, ).joinAll() - val result = server.createElicitation( + val serverSession = serverSessionResult.await() + + val result = serverSession.createElicitation( message = elicitationMessage, requestedSchema = requestedSchema, ) diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt index 4b49e1e..597a3cb 100644 --- a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt +++ b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt @@ -70,19 +70,6 @@ class SseTransportTest : BaseTransportTest() { install(ServerSSE) routing { mcp { mcpServer } -// sse { -// mcpSseTransport("", transports).apply { -// onMessage { -// send(it) -// } -// -// start() -// } -// } -// -// post { -// mcpPostEndpoint(transports) -// } } }.startSuspend(wait = false) @@ -110,22 +97,7 @@ class SseTransportTest : BaseTransportTest() { val server = embeddedServer(CIO, port = 0) { install(ServerSSE) routing { - mcp("/sse") { mcpServer } -// route("/sse") { -// sse { -// mcpSseTransport("", transports).apply { -// onMessage { -// send(it) -// } -// -// start() -// } -// } -// -// post { -// mcpPostEndpoint(transports) -// } -// } + mcp { mcpServer } } }.startSuspend(wait = false) diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/SseIntegrationTest.kt b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/SseIntegrationTest.kt index 2d1f25c..31b0fd0 100644 --- a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/SseIntegrationTest.kt +++ b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/SseIntegrationTest.kt @@ -1,15 +1,22 @@ package io.modelcontextprotocol.kotlin.sdk.integration import io.ktor.client.HttpClient +import io.ktor.client.plugins.sse.SSE import io.ktor.server.application.install import io.ktor.server.cio.CIOApplicationEngine import io.ktor.server.engine.EmbeddedServer import io.ktor.server.engine.embeddedServer import io.ktor.server.routing.routing +import io.modelcontextprotocol.kotlin.sdk.GetPromptRequest +import io.modelcontextprotocol.kotlin.sdk.GetPromptResult import io.modelcontextprotocol.kotlin.sdk.Implementation +import io.modelcontextprotocol.kotlin.sdk.PromptArgument +import io.modelcontextprotocol.kotlin.sdk.PromptMessage +import io.modelcontextprotocol.kotlin.sdk.Role import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.TextContent import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.client.mcpSse +import io.modelcontextprotocol.kotlin.sdk.client.mcpSseTransport import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions import io.modelcontextprotocol.kotlin.sdk.server.mcp @@ -17,47 +24,172 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.test.runTest import kotlinx.coroutines.withContext import kotlin.test.Test -import kotlin.test.fail +import kotlin.test.assertTrue +import kotlin.time.Duration.Companion.seconds import io.ktor.client.engine.cio.CIO as ClientCIO -import io.ktor.client.plugins.sse.SSE as ClientSSE import io.ktor.server.cio.CIO as ServerCIO import io.ktor.server.sse.SSE as ServerSSE -private const val URL = "127.0.0.1" - class SseIntegrationTest { @Test - fun `client should be able to connect to sse server`() = runTest { - val serverEngine = initServer() + fun `client should be able to connect to sse server`() = runTest(timeout = 5.seconds) { + var server: EmbeddedServer? = null + var client: Client? = null + try { withContext(Dispatchers.Default) { - val port = serverEngine.engine.resolvedConnectors().first().port - val client = initClient(port) - client.close() + server = initServer() + val port = server.engine.resolvedConnectors().first().port + client = initClient(serverPort = port) } - } catch (e: Exception) { - fail("Failed to connect client: $e") } finally { - // Make sure to stop the server - serverEngine.stopSuspend(1000, 2000) + client?.close() + server?.stopSuspend(1000, 2000) } } - private suspend fun initClient(port: Int): Client = HttpClient(ClientCIO) { - install(ClientSSE) - }.mcpSse("http://$URL:$port") + /** + * Test Case #1: One opened connection, a client gets a prompt + * + * 1. Open SSE from Client A. + * 2. Send a POST request from Client A to POST /prompts/get. + * 3. Observe that Client A receives a response related to it. + */ + @Test + fun `single sse connection`() = runTest(timeout = 5.seconds) { + var server: EmbeddedServer? = null + var client: Client? = null + try { + withContext(Dispatchers.Default) { + server = initServer() + val port = server.engine.resolvedConnectors().first().port + client = initClient("Client A", port) + + val promptA = getPrompt(client, "Client A") + assertTrue { "Client A" in promptA } + } + } finally { + client?.close() + server?.stopSuspend(1000, 2000) + } + } + + /** + * Test Case #1: Two open connections, each client gets a client-specific prompt + * + * 1. Open SSE connection #1 from Client A and note the sessionId= value. + * 2. Open SSE connection #2 from Client B and note the sessionId= value. + * 3. Send a POST request to POST /message with the corresponding sessionId#1. + * 4. Observe that Client B (connection #2) receives a response related to sessionId#1. + */ + @Test + fun `multiple sse connections`() = runTest(timeout = 5.seconds) { + var server: EmbeddedServer? = null + var clientA: Client? = null + var clientB: Client? = null + + try { + withContext(Dispatchers.Default) { + server = initServer() + val port = server.engine.resolvedConnectors().first().port + + clientA = initClient("Client A", port) + clientB = initClient("Client B", port) + + // Step 3: Send a prompt request from Client A + val promptA = getPrompt(clientA, "Client A") + // Step 4: Send a prompt request from Client B + val promptB = getPrompt(clientB, "Client B") + + assertTrue { "Client A" in promptA } + assertTrue { "Client B" in promptB } + } + } finally { + clientA?.close() + clientB?.close() + server?.stopSuspend(1000, 2000) + } + } + + private suspend fun initClient(name: String = "", serverPort: Int): Client { + val client = Client( + Implementation(name = name, version = "1.0.0"), + ) + + val httpClient = HttpClient(ClientCIO) { + install(SSE) + } + + // Create a transport wrapper that captures the session ID and received messages + val transport = httpClient.mcpSseTransport { + url { + host = URL + port = serverPort + } + } + + client.connect(transport) + + return client + } private suspend fun initServer(): EmbeddedServer { val server = Server( - Implementation(name = "sse-e2e-test", version = "1.0.0"), - ServerOptions(capabilities = ServerCapabilities()), + Implementation(name = "sse-server", version = "1.0.0"), + ServerOptions( + capabilities = ServerCapabilities(prompts = ServerCapabilities.Prompts(listChanged = true)), + ), ) - return embeddedServer(ServerCIO, host = URL, port = 0) { + server.addPrompt( + name = "prompt", + description = "Prompt description", + arguments = listOf( + PromptArgument( + name = "client", + description = "Client name who requested a prompt", + required = true, + ), + ), + ) { request -> + GetPromptResult( + "Prompt for ${request.name}", + messages = listOf( + PromptMessage( + role = Role.user, + content = TextContent("Prompt for client ${request.arguments?.get("client")}"), + ), + ), + ) + } + + val ktorServer = embeddedServer(ServerCIO, host = URL, port = PORT) { install(ServerSSE) routing { mcp { server } } - }.startSuspend(wait = false) + } + + return ktorServer.startSuspend(wait = false) + } + + /** + * Retrieves a prompt result using the provided client and client name. + */ + private suspend fun getPrompt(client: Client, clientName: String): String { + val response = client.getPrompt( + GetPromptRequest( + "prompt", + arguments = mapOf("client" to clientName), + ), + ) + + return (response.messages.first().content as? TextContent)?.text + ?: error("Failed to receive prompt for Client $clientName") + } + + companion object { + private const val URL = "127.0.0.1" + private const val PORT = 0 } } diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/WebSocketIntegrationTest.kt b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/WebSocketIntegrationTest.kt new file mode 100644 index 0000000..46a57ca --- /dev/null +++ b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/WebSocketIntegrationTest.kt @@ -0,0 +1,196 @@ +package io.modelcontextprotocol.kotlin.sdk.integration + +import io.ktor.client.HttpClient +import io.ktor.server.application.install +import io.ktor.server.cio.CIOApplicationEngine +import io.ktor.server.engine.EmbeddedServer +import io.ktor.server.engine.embeddedServer +import io.ktor.server.routing.routing +import io.modelcontextprotocol.kotlin.sdk.GetPromptRequest +import io.modelcontextprotocol.kotlin.sdk.GetPromptResult +import io.modelcontextprotocol.kotlin.sdk.Implementation +import io.modelcontextprotocol.kotlin.sdk.PromptArgument +import io.modelcontextprotocol.kotlin.sdk.PromptMessage +import io.modelcontextprotocol.kotlin.sdk.Role +import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.TextContent +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.client.mcpWebSocketTransport +import io.modelcontextprotocol.kotlin.sdk.server.Server +import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions +import io.modelcontextprotocol.kotlin.sdk.server.mcpWebSocket +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.withContext +import kotlin.test.Test +import kotlin.test.assertTrue +import kotlin.time.Duration.Companion.seconds +import io.ktor.client.engine.cio.CIO as ClientCIO +import io.ktor.client.plugins.websocket.WebSockets as ClientWebSocket +import io.ktor.server.cio.CIO as ServerCIO +import io.ktor.server.websocket.WebSockets as ServerWebSockets + +class WebSocketIntegrationTest { + + @Test + fun `client should be able to connect to websocket server 2`() = runTest(timeout = 5.seconds) { + var server: EmbeddedServer? = null + var client: Client? = null + + try { + withContext(Dispatchers.Default) { + server = initServer() + val port = server.engine.resolvedConnectors().first().port + client = initClient(serverPort = port) + } + } finally { + client?.close() + server?.stopSuspend(1000, 2000) + } + } + + /** + * Test Case #1: One opened connection, a client gets a prompt + * + * 1. Open WebSocket from Client A. + * 2. Send a POST request from Client A to POST /prompts/get. + * 3. Observe that Client A receives a response related to it. + */ + @Test + fun `single websocket connection`() = runTest(timeout = 5.seconds) { + var server: EmbeddedServer? = null + var client: Client? = null + + try { + withContext(Dispatchers.Default) { + server = initServer() + val port = server.engine.resolvedConnectors().first().port + client = initClient("Client A", port) + + val promptA = getPrompt(client, "Client A") + assertTrue { "Client A" in promptA } + } + } finally { + client?.close() + server?.stopSuspend(1000, 2000) + } + } + + /** + * Test Case #1: Two open connections, each client gets a client-specific prompt + * + * 1. Open WebSocket connection #1 from Client A and note the sessionId= value. + * 2. Open WebSocket connection #2 from Client B and note the sessionId= value. + * 3. Send a POST request to POST /message with the corresponding sessionId#1. + * 4. Observe that Client B (connection #2) receives a response related to sessionId#1. + */ + @Test + fun `multiple websocket connections`() = runTest(timeout = 5.seconds) { + var server: EmbeddedServer? = null + var clientA: Client? = null + var clientB: Client? = null + + try { + withContext(Dispatchers.Default) { + server = initServer() + val port = server.engine.resolvedConnectors().first().port + clientA = initClient("Client A", port) + clientB = initClient("Client B", port) + + // Step 3: Send a prompt request from Client A + val promptA = getPrompt(clientA, "Client A") + // Step 4: Send a prompt request from Client B + val promptB = getPrompt(clientB, "Client B") + + assertTrue { "Client A" in promptA } + assertTrue { "Client B" in promptB } + } + } finally { + clientA?.close() + clientB?.close() + server?.stopSuspend(1000, 2000) + } + } + + private suspend fun initClient(name: String = "", serverPort: Int): Client { + val client = Client( + Implementation(name = name, version = "1.0.0"), + ) + + val httpClient = HttpClient(ClientCIO) { + install(ClientWebSocket) + } + + // Create a transport wrapper that captures the session ID and received messages + val transport = httpClient.mcpWebSocketTransport { + url { + host = URL + port = serverPort + } + } + + client.connect(transport) + + return client + } + + private suspend fun initServer(): EmbeddedServer { + val server = Server( + Implementation(name = "websocket-server", version = "1.0.0"), + ServerOptions( + capabilities = ServerCapabilities(prompts = ServerCapabilities.Prompts(listChanged = true)), + ), + ) + + server.addPrompt( + name = "prompt", + description = "Prompt description", + arguments = listOf( + PromptArgument( + name = "client", + description = "Client name who requested a prompt", + required = true, + ), + ), + ) { request -> + GetPromptResult( + "Prompt for ${request.name}", + messages = listOf( + PromptMessage( + role = Role.user, + content = TextContent("Prompt for client ${request.arguments?.get("client")}"), + ), + ), + ) + } + + val ktorServer = embeddedServer(ServerCIO, host = URL, port = PORT) { + install(ServerWebSockets) + routing { + mcpWebSocket(block = { server }) + } + } + + return ktorServer.startSuspend(wait = false) + } + + /** + * Retrieves a prompt result using the provided client and client name. + */ + private suspend fun getPrompt(client: Client, clientName: String): String { + val response = client.getPrompt( + GetPromptRequest( + "prompt", + arguments = mapOf("client" to clientName), + ), + ) + + return (response.messages.first().content as? TextContent)?.text + ?: error("Failed to receive prompt for Client $clientName") + } + + companion object { + private const val PORT = 0 + private const val URL = "127.0.0.1" + } +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt index 95c643a..ad80567 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt @@ -1,8 +1,6 @@ package io.modelcontextprotocol.kotlin.sdk.integration.kotlin import io.modelcontextprotocol.kotlin.sdk.BlobResourceContents -import io.modelcontextprotocol.kotlin.sdk.EmptyRequestResult -import io.modelcontextprotocol.kotlin.sdk.Method import io.modelcontextprotocol.kotlin.sdk.ReadResourceRequest import io.modelcontextprotocol.kotlin.sdk.ReadResourceResult import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities @@ -16,6 +14,7 @@ import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import java.util.concurrent.atomic.AtomicBoolean +import kotlin.test.Ignore import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue @@ -67,14 +66,6 @@ abstract class AbstractResourceIntegrationTest : KotlinTestBase() { ) } - server.setRequestHandler(Method.Defined.ResourcesSubscribe) { _, _ -> - EmptyRequestResult() - } - - server.setRequestHandler(Method.Defined.ResourcesUnsubscribe) { _, _ -> - EmptyRequestResult() - } - server.addResource( uri = testResourceUri, name = testResourceName, @@ -169,6 +160,7 @@ abstract class AbstractResourceIntegrationTest : KotlinTestBase() { assertEquals(testResourceContent, content.text, "Resource content should match") } + @Ignore("Blocked by https://github.com/modelcontextprotocol/kotlin-sdk/issues/249") @Test fun testSubscribeAndUnsubscribe() { runBlocking(Dispatchers.IO) { diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt index 56abf31..5cb0f61 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt @@ -26,8 +26,6 @@ import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage import io.modelcontextprotocol.kotlin.sdk.JSONRPCNotification import io.modelcontextprotocol.kotlin.sdk.JSONRPCRequest import io.modelcontextprotocol.kotlin.sdk.JSONRPCResponse -import io.modelcontextprotocol.kotlin.sdk.LoggingLevel -import io.modelcontextprotocol.kotlin.sdk.LoggingMessageNotification import io.modelcontextprotocol.kotlin.sdk.PromptArgument import io.modelcontextprotocol.kotlin.sdk.PromptMessage import io.modelcontextprotocol.kotlin.sdk.ReadResourceResult @@ -254,32 +252,6 @@ class KotlinServerForTsClient { ) { request -> val name = (request.arguments["name"] as? JsonPrimitive)?.content ?: "World" - server.sendToolListChanged() - server.sendLoggingMessage( - LoggingMessageNotification( - LoggingMessageNotification.Params( - level = LoggingLevel.info, - data = JsonPrimitive("Preparing greeting for $name"), - ), - ), - ) - server.sendLoggingMessage( - LoggingMessageNotification( - LoggingMessageNotification.Params( - level = LoggingLevel.info, - data = JsonPrimitive("Halfway there for $name"), - ), - ), - ) - server.sendLoggingMessage( - LoggingMessageNotification( - LoggingMessageNotification.Params( - level = LoggingLevel.info, - data = JsonPrimitive("Done sending greetings to $name"), - ), - ), - ) - CallToolResult( content = listOf(TextContent("Multiple greetings sent to $name!")), structuredContent = buildJsonObject { diff --git a/samples/kotlin-mcp-server/src/commonMain/kotlin/server.kt b/samples/kotlin-mcp-server/src/commonMain/kotlin/server.kt index 15e8cb0..f48e5ab 100644 --- a/samples/kotlin-mcp-server/src/commonMain/kotlin/server.kt +++ b/samples/kotlin-mcp-server/src/commonMain/kotlin/server.kt @@ -23,6 +23,7 @@ import io.modelcontextprotocol.kotlin.sdk.TextResourceContents import io.modelcontextprotocol.kotlin.sdk.Tool import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions +import io.modelcontextprotocol.kotlin.sdk.server.ServerSession import io.modelcontextprotocol.kotlin.sdk.server.SseServerTransport import io.modelcontextprotocol.kotlin.sdk.server.mcp @@ -94,33 +95,33 @@ fun configureServer(): Server { } suspend fun runSseMcpServerWithPlainConfiguration(port: Int) { - val servers = ConcurrentMap() + val serverSessions = ConcurrentMap() println("Starting sse server on port $port. ") println("Use inspector to connect to the http://localhost:$port/sse") + val server = configureServer() + embeddedServer(CIO, host = "0.0.0.0", port = port) { install(SSE) routing { sse("/sse") { val transport = SseServerTransport("/message", this) - val server = configureServer() // For SSE, you can also add prompts/tools/resources if needed: // server.addTool(...), server.addPrompt(...), server.addResource(...) - servers[transport.sessionId] = server + val serverSession = server.connect(transport) + serverSessions[transport.sessionId] = server.connect(transport) - server.onClose { + serverSession.onClose { println("Server closed") - servers.remove(transport.sessionId) + serverSessions.remove(transport.sessionId) } - - server.connect(transport) } post("/message") { println("Received Message") val sessionId: String = call.request.queryParameters["sessionId"]!! - val transport = servers[sessionId]?.transport as? SseServerTransport + val transport = serverSessions[sessionId]?.transport as? SseServerTransport if (transport == null) { call.respond(HttpStatusCode.NotFound, "Session not found") return@post diff --git a/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt b/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt index 6bdb84d..02f90d9 100644 --- a/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt +++ b/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt @@ -114,9 +114,9 @@ fun `run mcp server`() { ) runBlocking { - server.connect(transport) + val session = server.connect(transport) val done = Job() - server.onClose { + session.onClose { done.complete() } done.join() From 8958f4c731e1005d5d113efddef23f23645ddd91 Mon Sep 17 00:00:00 2001 From: Jeff Clyne Date: Mon, 29 Sep 2025 02:00:25 -0700 Subject: [PATCH 005/449] Capture version at configuration time to avoid configuration cache issue (#291) Moves capture of the project version to execution time to avoid a gradle configuration cache issue --- kotlin-sdk-core/build.gradle.kts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/kotlin-sdk-core/build.gradle.kts b/kotlin-sdk-core/build.gradle.kts index 849c841..1cbaf6e 100644 --- a/kotlin-sdk-core/build.gradle.kts +++ b/kotlin-sdk-core/build.gradle.kts @@ -14,6 +14,9 @@ val generateLibVersion by tasks.registering { val outputDir = layout.buildDirectory.dir("generated-sources/libVersion") outputs.dir(outputDir) + // Capture version at configuration time to avoid configuration cache issues + val projectVersion = project.version.toString() + doLast { val sourceFile = outputDir.get().file("io/modelcontextprotocol/kotlin/sdk/LibVersion.kt").asFile sourceFile.parentFile.mkdirs() @@ -21,7 +24,7 @@ val generateLibVersion by tasks.registering { """ package io.modelcontextprotocol.kotlin.sdk - public const val LIB_VERSION: String = "${project.version}" + public const val LIB_VERSION: String = "$projectVersion" """.trimIndent(), ) From d962e28ff7bfb13cdfd03f8f2095d3ff64733e15 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Mon, 29 Sep 2025 12:34:32 +0300 Subject: [PATCH 006/449] Generate MCP classes with OpenAPI Generator (#294) - Add OpenAPI Generator to build system to automatically generate MCP protocol data classes from the official schema. - Configure code quality tools to exclude generated files - Add deserialization test ## Motivation and Context Writing classes by hand is error-prone, when json schema exists. See #185, Also, autogenerating model classes ensures the SDK is in sync with the protocol and can quickly catch up with its changes (see #218) ## How Has This Been Tested? Regression tests ## Breaking Changes No ## 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) - [ ] My code follows the repository's style guidelines - [ ] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed ## Additional context --- build.gradle.kts | 8 + gradle/libs.versions.toml | 1 + kotlin-sdk-core/.openapi-generator-ignore | 29 + kotlin-sdk-core/api/kotlin-sdk-core.api | 2440 +++++++++++++++++ kotlin-sdk-core/build.gradle.kts | 57 + .../sdk/models/ProgressNotificationsTest.kt | 71 + 6 files changed, 2606 insertions(+) create mode 100644 kotlin-sdk-core/.openapi-generator-ignore create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationsTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index f3497c3..c876738 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -33,10 +33,18 @@ dokka { } } +ktlint { + filter { + exclude("**/generated*/**") + } +} + kover { reports { filters { includes.classes("io.modelcontextprotocol.kotlin.sdk.*") + excludes.classes("io.modelcontextprotocol.kotlin.sdk.models.*") // temporary + excludes.classes("io.modelcontextprotocol.kotlin.sdk.models.infrastructure.*") // generated } total { log { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a39d09d..5bb80c8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -48,6 +48,7 @@ ktor-server-core = { group = "io.ktor", name = "ktor-server-core", version.ref = # Testing awaitility = { group = "org.awaitility", name = "awaitility-kotlin", version.ref = "awaitility" } +kotest-assertions-core = { group = "io.kotest", name = "kotest-assertions-core", version.ref = "kotest" } kotest-assertions-json = { group = "io.kotest", name = "kotest-assertions-json", version.ref = "kotest" } kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines" } ktor-client-mock = { group = "io.ktor", name = "ktor-client-mock", version.ref = "ktor" } diff --git a/kotlin-sdk-core/.openapi-generator-ignore b/kotlin-sdk-core/.openapi-generator-ignore new file mode 100644 index 0000000..dbdf930 --- /dev/null +++ b/kotlin-sdk-core/.openapi-generator-ignore @@ -0,0 +1,29 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +**/auth/** +**/infrastructure/** +!**/Base64ByteArray.kt +!**/Bytes.kt + + +## Exclude Models which are causing problems +**/JSONRPCResponse.kt +**/Result.kt +**/ListToolsResult.kt +**/InitializeRequest.kt +**/InitializeResult.kt +**/ServerCapabilities.kt +**/InitializeRequestParams.kt +**/CreateMessageRequest.kt +**/CreateMessageRequestParams.kt +**/ClientCapabilities.kt +**/Tool.kt +**/ToolInputSchema.kt +**/ToolOutputSchema.kt + diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index ed9a268..0ae1ac4 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -3258,6 +3258,2446 @@ public final class io/modelcontextprotocol/kotlin/sdk/internal/Utils_jvmKt { public static final fun getIODispatcher ()Lkotlinx/coroutines/CoroutineDispatcher; } +public final class io/modelcontextprotocol/kotlin/sdk/models/Annotations { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/Annotations$Companion; + public fun ()V + public fun (Ljava/util/List;Ljava/lang/Double;)V + public synthetic fun (Ljava/util/List;Ljava/lang/Double;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/lang/Double; + public final fun copy (Ljava/util/List;Ljava/lang/Double;)Lio/modelcontextprotocol/kotlin/sdk/models/Annotations; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;Ljava/util/List;Ljava/lang/Double;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/Annotations; + public fun equals (Ljava/lang/Object;)Z + public final fun getAudience ()Ljava/util/List; + public final fun getPriority ()Ljava/lang/Double; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/Annotations$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/Annotations$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/Annotations; + 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/models/Annotations;)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/models/Annotations$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/AudioContent { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/AudioContent$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Lio/modelcontextprotocol/kotlin/sdk/models/Annotations; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;)Lio/modelcontextprotocol/kotlin/sdk/models/AudioContent; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/AudioContent;Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/AudioContent; + public fun equals (Ljava/lang/Object;)Z + public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/models/Annotations; + public final fun getData ()Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray; + public final fun getMimeType ()Ljava/lang/String; + public final fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/AudioContent$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/AudioContent$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/AudioContent; + 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/models/AudioContent;)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/models/AudioContent$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/BlobResourceContents { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/BlobResourceContents$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray;Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/BlobResourceContents; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/BlobResourceContents;Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/BlobResourceContents; + public fun equals (Ljava/lang/Object;)Z + public final fun getBlob ()Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray; + public final fun getMimeType ()Ljava/lang/String; + public final fun getUri ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/BlobResourceContents$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/BlobResourceContents$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/BlobResourceContents; + 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/models/BlobResourceContents;)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/models/BlobResourceContents$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/CallToolRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequest$Companion; + public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequestParams;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequestParams; + public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequest;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequest; + public fun equals (Ljava/lang/Object;)Z + public final fun getMethod ()Ljava/lang/String; + public final fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequestParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/CallToolRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequest; + 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/models/CallToolRequest;)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/models/CallToolRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/CallToolRequestParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequestParams$Companion; + public fun (Ljava/lang/String;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequestParams; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequestParams;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequestParams; + public fun equals (Ljava/lang/Object;)Z + public final fun getArguments ()Ljava/util/Map; + public final fun getName ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/CallToolRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequestParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequestParams; + 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/models/CallToolRequestParams;)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/models/CallToolRequestParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/CallToolResult { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/CallToolResult$Companion; + public fun (Ljava/util/List;Ljava/util/Map;Ljava/lang/Boolean;)V + public synthetic fun (Ljava/util/List;Ljava/util/Map;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/util/Map; + public final fun component3 ()Ljava/lang/Boolean; + public final fun copy (Ljava/util/List;Ljava/util/Map;Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/models/CallToolResult; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/CallToolResult;Ljava/util/List;Ljava/util/Map;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/CallToolResult; + public fun equals (Ljava/lang/Object;)Z + public final fun getContent ()Ljava/util/List; + public final fun getMeta ()Ljava/util/Map; + public fun hashCode ()I + public final fun isError ()Ljava/lang/Boolean; + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/CallToolResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/CallToolResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/CallToolResult; + 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/models/CallToolResult;)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/models/CallToolResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/CancelledNotification { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotification$Companion; + public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotificationParams;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotificationParams; + public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotification; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotification;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotificationParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotification; + public fun equals (Ljava/lang/Object;)Z + public final fun getMethod ()Ljava/lang/String; + public final fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotificationParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/CancelledNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotification$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotification; + 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/models/CancelledNotification;)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/models/CancelledNotification$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/CancelledNotificationParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotificationParams$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotificationParams; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotificationParams;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotificationParams; + public fun equals (Ljava/lang/Object;)Z + public final fun getReason ()Ljava/lang/String; + public final fun getRequestId ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/CancelledNotificationParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotificationParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotificationParams; + 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/models/CancelledNotificationParams;)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/models/CancelledNotificationParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/ClientCapabilitiesRoots { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ClientCapabilitiesRoots$Companion; + public fun ()V + public fun (Ljava/lang/Boolean;)V + public synthetic fun (Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Boolean; + public final fun copy (Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/models/ClientCapabilitiesRoots; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ClientCapabilitiesRoots;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ClientCapabilitiesRoots; + public fun equals (Ljava/lang/Object;)Z + public final fun getListChanged ()Ljava/lang/Boolean; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ClientCapabilitiesRoots$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ClientCapabilitiesRoots$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ClientCapabilitiesRoots; + 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/models/ClientCapabilitiesRoots;)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/models/ClientCapabilitiesRoots$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/CompleteRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequest$Companion; + public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParams;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParams; + public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequest;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequest; + public fun equals (Ljava/lang/Object;)Z + public final fun getMethod ()Ljava/lang/String; + public final fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/CompleteRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequest; + 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/models/CompleteRequest;)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/models/CompleteRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParams$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParamsArgument;Ljava/lang/String;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParamsArgument; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParamsArgument;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParams; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParams;Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParamsArgument;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParams; + public fun equals (Ljava/lang/Object;)Z + public final fun getArgument ()Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParamsArgument; + public final fun getRef ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParams; + 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/models/CompleteRequestParams;)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/models/CompleteRequestParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParamsArgument { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParamsArgument$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParamsArgument; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParamsArgument;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParamsArgument; + public fun equals (Ljava/lang/Object;)Z + public final fun getName ()Ljava/lang/String; + public final fun getValue ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParamsArgument$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParamsArgument$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParamsArgument; + 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/models/CompleteRequestParamsArgument;)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/models/CompleteRequestParamsArgument$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/CompleteResult { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResult$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResultCompletion;Ljava/util/Map;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResultCompletion;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResultCompletion; + public final fun component2 ()Ljava/util/Map; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResultCompletion;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResult; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResult;Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResultCompletion;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResult; + public fun equals (Ljava/lang/Object;)Z + public final fun getCompletion ()Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResultCompletion; + public final fun getMeta ()Ljava/util/Map; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/CompleteResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResult; + 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/models/CompleteResult;)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/models/CompleteResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/CompleteResultCompletion { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResultCompletion$Companion; + public fun (Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Integer;)V + public synthetic fun (Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/lang/Boolean; + public final fun component3 ()Ljava/lang/Integer; + public final fun copy (Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Integer;)Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResultCompletion; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResultCompletion;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Integer;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResultCompletion; + public fun equals (Ljava/lang/Object;)Z + public final fun getHasMore ()Ljava/lang/Boolean; + public final fun getPropertyValues ()Ljava/util/List; + public final fun getTotal ()Ljava/lang/Integer; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/CompleteResultCompletion$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResultCompletion$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResultCompletion; + 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/models/CompleteResultCompletion;)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/models/CompleteResultCompletion$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/CreateMessageResult { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/CreateMessageResult$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Role;Ljava/util/Map;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Role;Ljava/util/Map;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/models/Role; + public final fun component4 ()Ljava/util/Map; + public final fun component5 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Role;Ljava/util/Map;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/CreateMessageResult; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/CreateMessageResult;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Role;Ljava/util/Map;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/CreateMessageResult; + public fun equals (Ljava/lang/Object;)Z + public final fun getContent ()Ljava/lang/String; + public final fun getMeta ()Ljava/util/Map; + public final fun getModel ()Ljava/lang/String; + public final fun getRole ()Lio/modelcontextprotocol/kotlin/sdk/models/Role; + public final fun getStopReason ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/CreateMessageResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/CreateMessageResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/CreateMessageResult; + 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/models/CreateMessageResult;)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/models/CreateMessageResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/EmbeddedResource { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/EmbeddedResource$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/models/Annotations; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;)Lio/modelcontextprotocol/kotlin/sdk/models/EmbeddedResource; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/EmbeddedResource;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/EmbeddedResource; + public fun equals (Ljava/lang/Object;)Z + public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/models/Annotations; + public final fun getResource ()Ljava/lang/String; + public final fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/EmbeddedResource$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/EmbeddedResource$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/EmbeddedResource; + 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/models/EmbeddedResource;)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/models/EmbeddedResource$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/GetPromptRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequest$Companion; + public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequestParams;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequestParams; + public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequest;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequest; + public fun equals (Ljava/lang/Object;)Z + public final fun getMethod ()Ljava/lang/String; + public final fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequestParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/GetPromptRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequest; + 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/models/GetPromptRequest;)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/models/GetPromptRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/GetPromptRequestParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequestParams$Companion; + public fun (Ljava/lang/String;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequestParams; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequestParams;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequestParams; + public fun equals (Ljava/lang/Object;)Z + public final fun getArguments ()Ljava/util/Map; + public final fun getName ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/GetPromptRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequestParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequestParams; + 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/models/GetPromptRequestParams;)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/models/GetPromptRequestParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/GetPromptResult { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptResult$Companion; + public fun (Ljava/util/List;Ljava/util/Map;Ljava/lang/String;)V + public synthetic fun (Ljava/util/List;Ljava/util/Map;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/util/Map; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/util/List;Ljava/util/Map;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptResult; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptResult;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptResult; + public fun equals (Ljava/lang/Object;)Z + public final fun getDescription ()Ljava/lang/String; + public final fun getMessages ()Ljava/util/List; + public final fun getMeta ()Ljava/util/Map; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/GetPromptResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptResult; + 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/models/GetPromptResult;)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/models/GetPromptResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/ImageContent { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ImageContent$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Lio/modelcontextprotocol/kotlin/sdk/models/Annotations; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;)Lio/modelcontextprotocol/kotlin/sdk/models/ImageContent; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ImageContent;Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ImageContent; + public fun equals (Ljava/lang/Object;)Z + public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/models/Annotations; + public final fun getData ()Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray; + public final fun getMimeType ()Ljava/lang/String; + public final fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ImageContent$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ImageContent$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ImageContent; + 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/models/ImageContent;)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/models/ImageContent$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/Implementation { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/Implementation$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/Implementation; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/Implementation;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/Implementation; + public fun equals (Ljava/lang/Object;)Z + public final fun getName ()Ljava/lang/String; + public final fun getVersion ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/Implementation$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/Implementation$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/Implementation; + 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/models/Implementation;)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/models/Implementation$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/InitializedNotification { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/InitializedNotification$Companion; + public fun (Ljava/lang/String;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/models/InitializedNotification; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/InitializedNotification;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/InitializedNotification; + public fun equals (Ljava/lang/Object;)Z + public final fun getMethod ()Ljava/lang/String; + public final fun getParams ()Ljava/util/Map; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/InitializedNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/InitializedNotification$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/InitializedNotification; + 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/models/InitializedNotification;)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/models/InitializedNotification$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/JSONRPCError { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCError$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCErrorError;Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCErrorError; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCErrorError;Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCError; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCError;Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCErrorError;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCError; + public fun equals (Ljava/lang/Object;)Z + public final fun getError ()Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCErrorError; + public final fun getId ()Ljava/lang/String; + public final fun getJsonrpc ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/JSONRPCError$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCError$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCError; + 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/models/JSONRPCError;)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/models/JSONRPCError$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/JSONRPCErrorError { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCErrorError$Companion; + public fun (ILjava/lang/String;Ljava/lang/String;)V + public synthetic fun (ILjava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()I + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (ILjava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCErrorError; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCErrorError;ILjava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCErrorError; + public fun equals (Ljava/lang/Object;)Z + public final fun getCode ()I + public final fun getData ()Ljava/lang/String; + public final fun getMessage ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/JSONRPCErrorError$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCErrorError$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCErrorError; + 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/models/JSONRPCErrorError;)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/models/JSONRPCErrorError$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/JSONRPCNotification { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCNotification$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCNotification; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCNotification;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCNotification; + public fun equals (Ljava/lang/Object;)Z + public final fun getJsonrpc ()Ljava/lang/String; + public final fun getMethod ()Ljava/lang/String; + public final fun getParams ()Ljava/util/Map; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/JSONRPCNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCNotification$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCNotification; + 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/models/JSONRPCNotification;)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/models/JSONRPCNotification$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/JSONRPCRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCRequest$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCRequest;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCRequest; + public fun equals (Ljava/lang/Object;)Z + public final fun getId ()Ljava/lang/String; + public final fun getJsonrpc ()Ljava/lang/String; + public final fun getMethod ()Ljava/lang/String; + public final fun getParams ()Ljava/util/Map; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/JSONRPCRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCRequest; + 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/models/JSONRPCRequest;)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/models/JSONRPCRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequest$Companion; + public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;)V + public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams; + public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequest;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequest; + public fun equals (Ljava/lang/Object;)Z + public final fun getMethod ()Ljava/lang/String; + public final fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequest; + 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/models/ListPromptsRequest;)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/models/ListPromptsRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams$Companion; + public fun ()V + public fun (Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams; + public fun equals (Ljava/lang/Object;)Z + public final fun getCursor ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams; + 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/models/ListPromptsRequestParams;)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/models/ListPromptsRequestParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/ListPromptsResult { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsResult$Companion; + public fun (Ljava/util/List;Ljava/util/Map;Ljava/lang/String;)V + public synthetic fun (Ljava/util/List;Ljava/util/Map;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/util/Map; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/util/List;Ljava/util/Map;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsResult; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsResult;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsResult; + public fun equals (Ljava/lang/Object;)Z + public final fun getMeta ()Ljava/util/Map; + public final fun getNextCursor ()Ljava/lang/String; + public final fun getPrompts ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ListPromptsResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsResult; + 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/models/ListPromptsResult;)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/models/ListPromptsResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/ListResourceTemplatesRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ListResourceTemplatesRequest$Companion; + public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;)V + public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams; + public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/models/ListResourceTemplatesRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ListResourceTemplatesRequest;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ListResourceTemplatesRequest; + public fun equals (Ljava/lang/Object;)Z + public final fun getMethod ()Ljava/lang/String; + public final fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ListResourceTemplatesRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ListResourceTemplatesRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ListResourceTemplatesRequest; + 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/models/ListResourceTemplatesRequest;)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/models/ListResourceTemplatesRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/ListResourceTemplatesResult { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ListResourceTemplatesResult$Companion; + public fun (Ljava/util/List;Ljava/util/Map;Ljava/lang/String;)V + public synthetic fun (Ljava/util/List;Ljava/util/Map;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/util/Map; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/util/List;Ljava/util/Map;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/ListResourceTemplatesResult; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ListResourceTemplatesResult;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ListResourceTemplatesResult; + public fun equals (Ljava/lang/Object;)Z + public final fun getMeta ()Ljava/util/Map; + public final fun getNextCursor ()Ljava/lang/String; + public final fun getResourceTemplates ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ListResourceTemplatesResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ListResourceTemplatesResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ListResourceTemplatesResult; + 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/models/ListResourceTemplatesResult;)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/models/ListResourceTemplatesResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/ListResourcesRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ListResourcesRequest$Companion; + public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;)V + public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams; + public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/models/ListResourcesRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ListResourcesRequest;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ListResourcesRequest; + public fun equals (Ljava/lang/Object;)Z + public final fun getMethod ()Ljava/lang/String; + public final fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ListResourcesRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ListResourcesRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ListResourcesRequest; + 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/models/ListResourcesRequest;)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/models/ListResourcesRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/ListResourcesResult { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ListResourcesResult$Companion; + public fun (Ljava/util/List;Ljava/util/Map;Ljava/lang/String;)V + public synthetic fun (Ljava/util/List;Ljava/util/Map;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/util/Map; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/util/List;Ljava/util/Map;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/ListResourcesResult; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ListResourcesResult;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ListResourcesResult; + public fun equals (Ljava/lang/Object;)Z + public final fun getMeta ()Ljava/util/Map; + public final fun getNextCursor ()Ljava/lang/String; + public final fun getResources ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ListResourcesResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ListResourcesResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ListResourcesResult; + 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/models/ListResourcesResult;)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/models/ListResourcesResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/ListRootsRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ListRootsRequest$Companion; + public fun (Ljava/lang/String;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/models/ListRootsRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ListRootsRequest;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ListRootsRequest; + public fun equals (Ljava/lang/Object;)Z + public final fun getMethod ()Ljava/lang/String; + public final fun getParams ()Ljava/util/Map; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ListRootsRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ListRootsRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ListRootsRequest; + 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/models/ListRootsRequest;)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/models/ListRootsRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/ListRootsResult { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ListRootsResult$Companion; + public fun (Ljava/util/List;Ljava/util/Map;)V + public synthetic fun (Ljava/util/List;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/util/Map; + public final fun copy (Ljava/util/List;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/models/ListRootsResult; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ListRootsResult;Ljava/util/List;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ListRootsResult; + public fun equals (Ljava/lang/Object;)Z + public final fun getMeta ()Ljava/util/Map; + public final fun getRoots ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ListRootsResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ListRootsResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ListRootsResult; + 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/models/ListRootsResult;)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/models/ListRootsResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/ListToolsRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ListToolsRequest$Companion; + public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;)V + public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams; + public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/models/ListToolsRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ListToolsRequest;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ListToolsRequest; + public fun equals (Ljava/lang/Object;)Z + public final fun getMethod ()Ljava/lang/String; + public final fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ListToolsRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ListToolsRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ListToolsRequest; + 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/models/ListToolsRequest;)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/models/ListToolsRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/LoggingLevel : java/lang/Enum { + public static final field ALERT Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel; + public static final field CRITICAL Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel; + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel$Companion; + public static final field DEBUG Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel; + public static final field EMERGENCY Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel; + public static final field ERROR Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel; + public static final field INFO Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel; + public static final field NOTICE Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel; + public static final field WARNING Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public final fun getValue ()Ljava/lang/String; + public fun toString ()Ljava/lang/String; + public static fun valueOf (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel; + public static fun values ()[Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/LoggingLevel$Companion { + public final fun decode (Ljava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel; + public final fun encode (Ljava/lang/Object;)Ljava/lang/String; + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotification { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotification$Companion; + public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotificationParams;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotificationParams; + public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotification; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotification;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotificationParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotification; + public fun equals (Ljava/lang/Object;)Z + public final fun getMethod ()Ljava/lang/String; + public final fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotificationParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotification$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotification; + 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/models/LoggingMessageNotification;)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/models/LoggingMessageNotification$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotificationParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotificationParams$Companion; + public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotificationParams; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotificationParams;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotificationParams; + public fun equals (Ljava/lang/Object;)Z + public final fun getData ()Ljava/lang/String; + public final fun getLevel ()Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel; + public final fun getLogger ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotificationParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotificationParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotificationParams; + 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/models/LoggingMessageNotificationParams;)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/models/LoggingMessageNotificationParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/ModelHint { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ModelHint$Companion; + public fun ()V + public fun (Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/ModelHint; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ModelHint;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ModelHint; + public fun equals (Ljava/lang/Object;)Z + public final fun getName ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ModelHint$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ModelHint$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ModelHint; + 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/models/ModelHint;)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/models/ModelHint$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/ModelPreferences { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ModelPreferences$Companion; + public fun ()V + public fun (Ljava/lang/Double;Ljava/util/List;Ljava/lang/Double;Ljava/lang/Double;)V + public synthetic fun (Ljava/lang/Double;Ljava/util/List;Ljava/lang/Double;Ljava/lang/Double;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Double; + public final fun component2 ()Ljava/util/List; + public final fun component3 ()Ljava/lang/Double; + public final fun component4 ()Ljava/lang/Double; + public final fun copy (Ljava/lang/Double;Ljava/util/List;Ljava/lang/Double;Ljava/lang/Double;)Lio/modelcontextprotocol/kotlin/sdk/models/ModelPreferences; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ModelPreferences;Ljava/lang/Double;Ljava/util/List;Ljava/lang/Double;Ljava/lang/Double;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ModelPreferences; + public fun equals (Ljava/lang/Object;)Z + public final fun getCostPriority ()Ljava/lang/Double; + public final fun getHints ()Ljava/util/List; + public final fun getIntelligencePriority ()Ljava/lang/Double; + public final fun getSpeedPriority ()Ljava/lang/Double; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ModelPreferences$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ModelPreferences$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ModelPreferences; + 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/models/ModelPreferences;)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/models/ModelPreferences$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/Notification { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/Notification$Companion; + public fun (Ljava/lang/String;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/models/Notification; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/Notification;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/Notification; + public fun equals (Ljava/lang/Object;)Z + public final fun getMethod ()Ljava/lang/String; + public final fun getParams ()Ljava/util/Map; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/Notification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/Notification$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/Notification; + 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/models/Notification;)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/models/Notification$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/PaginatedRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/PaginatedRequest$Companion; + public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;)V + public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams; + public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/models/PaginatedRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/PaginatedRequest;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/PaginatedRequest; + public fun equals (Ljava/lang/Object;)Z + public final fun getMethod ()Ljava/lang/String; + public final fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/PaginatedRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/PaginatedRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/PaginatedRequest; + 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/models/PaginatedRequest;)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/models/PaginatedRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/PaginatedResult { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/PaginatedResult$Companion; + public fun ()V + public fun (Ljava/util/Map;Ljava/lang/String;)V + public synthetic fun (Ljava/util/Map;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/Map; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/util/Map;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/PaginatedResult; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/PaginatedResult;Ljava/util/Map;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/PaginatedResult; + public fun equals (Ljava/lang/Object;)Z + public final fun getMeta ()Ljava/util/Map; + public final fun getNextCursor ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/PaginatedResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/PaginatedResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/PaginatedResult; + 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/models/PaginatedResult;)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/models/PaginatedResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/PingRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/PingRequest$Companion; + public fun (Ljava/lang/String;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/models/PingRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/PingRequest;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/PingRequest; + public fun equals (Ljava/lang/Object;)Z + public final fun getMethod ()Ljava/lang/String; + public final fun getParams ()Ljava/util/Map; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/PingRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/PingRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/PingRequest; + 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/models/PingRequest;)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/models/PingRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/ProgressNotification { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotification$Companion; + public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationParams;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationParams; + public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotification; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotification;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotification; + public fun equals (Ljava/lang/Object;)Z + public final fun getMethod ()Ljava/lang/String; + public final fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ProgressNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotification$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotification; + 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/models/ProgressNotification;)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/models/ProgressNotification$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationParams$Companion; + public fun (DLjava/lang/String;Ljava/lang/String;Ljava/lang/Double;)V + public synthetic fun (DLjava/lang/String;Ljava/lang/String;Ljava/lang/Double;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()D + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/Double; + public final fun copy (DLjava/lang/String;Ljava/lang/String;Ljava/lang/Double;)Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationParams; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationParams;DLjava/lang/String;Ljava/lang/String;Ljava/lang/Double;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationParams; + public fun equals (Ljava/lang/Object;)Z + public final fun getMessage ()Ljava/lang/String; + public final fun getProgress ()D + public final fun getProgressToken ()Ljava/lang/String; + public final fun getTotal ()Ljava/lang/Double; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationParams; + 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/models/ProgressNotificationParams;)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/models/ProgressNotificationParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/Prompt { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/Prompt$Companion; + public fun (Ljava/lang/String;Ljava/util/List;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/util/List;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/List; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/util/List;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/Prompt; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/Prompt;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/Prompt; + public fun equals (Ljava/lang/Object;)Z + public final fun getArguments ()Ljava/util/List; + public final fun getDescription ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/Prompt$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/Prompt$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/Prompt; + 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/models/Prompt;)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/models/Prompt$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/PromptArgument { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/PromptArgument$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/Boolean; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/models/PromptArgument; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/PromptArgument;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/PromptArgument; + public fun equals (Ljava/lang/Object;)Z + public final fun getDescription ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getRequired ()Ljava/lang/Boolean; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/PromptArgument$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/PromptArgument$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/PromptArgument; + 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/models/PromptArgument;)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/models/PromptArgument$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/PromptListChangedNotification { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/PromptListChangedNotification$Companion; + public fun (Ljava/lang/String;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/models/PromptListChangedNotification; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/PromptListChangedNotification;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/PromptListChangedNotification; + public fun equals (Ljava/lang/Object;)Z + public final fun getMethod ()Ljava/lang/String; + public final fun getParams ()Ljava/util/Map; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/PromptListChangedNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/PromptListChangedNotification$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/PromptListChangedNotification; + 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/models/PromptListChangedNotification;)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/models/PromptListChangedNotification$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/PromptMessage { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/PromptMessage$Companion; + public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Role;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/Role; + public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Role;)Lio/modelcontextprotocol/kotlin/sdk/models/PromptMessage; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/PromptMessage;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Role;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/PromptMessage; + public fun equals (Ljava/lang/Object;)Z + public final fun getContent ()Ljava/lang/String; + public final fun getRole ()Lio/modelcontextprotocol/kotlin/sdk/models/Role; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/PromptMessage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/PromptMessage$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/PromptMessage; + 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/models/PromptMessage;)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/models/PromptMessage$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/PromptReference { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/PromptReference$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/PromptReference; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/PromptReference;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/PromptReference; + public fun equals (Ljava/lang/Object;)Z + public final fun getName ()Ljava/lang/String; + public final fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/PromptReference$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/PromptReference$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/PromptReference; + 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/models/PromptReference;)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/models/PromptReference$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequest$Companion; + public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequestParams;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequestParams; + public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequest;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequest; + public fun equals (Ljava/lang/Object;)Z + public final fun getMethod ()Ljava/lang/String; + public final fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequestParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequest; + 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/models/ReadResourceRequest;)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/models/ReadResourceRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequestParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequestParams$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequestParams; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequestParams;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequestParams; + public fun equals (Ljava/lang/Object;)Z + public final fun getUri ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequestParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequestParams; + 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/models/ReadResourceRequestParams;)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/models/ReadResourceRequestParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/ReadResourceResult { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceResult$Companion; + public fun (Ljava/util/List;Ljava/util/Map;)V + public synthetic fun (Ljava/util/List;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/util/Map; + public final fun copy (Ljava/util/List;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceResult; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceResult;Ljava/util/List;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceResult; + public fun equals (Ljava/lang/Object;)Z + public final fun getContents ()Ljava/util/List; + public final fun getMeta ()Ljava/util/Map; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ReadResourceResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceResult; + 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/models/ReadResourceResult;)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/models/ReadResourceResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/Request { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/Request$Companion; + public fun (Ljava/lang/String;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/models/Request; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/Request;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/Request; + public fun equals (Ljava/lang/Object;)Z + public final fun getMethod ()Ljava/lang/String; + public final fun getParams ()Ljava/util/Map; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/Request$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/Request$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/Request; + 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/models/Request;)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/models/Request$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/Resource { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/Resource$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/models/Annotations; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/Resource; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/Resource;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/Resource; + public fun equals (Ljava/lang/Object;)Z + public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/models/Annotations; + public final fun getDescription ()Ljava/lang/String; + public final fun getMimeType ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getUri ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/Resource$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/Resource$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/Resource; + 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/models/Resource;)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/models/Resource$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/ResourceContents { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ResourceContents$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceContents; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ResourceContents;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceContents; + public fun equals (Ljava/lang/Object;)Z + public final fun getMimeType ()Ljava/lang/String; + public final fun getUri ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ResourceContents$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ResourceContents$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceContents; + 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/models/ResourceContents;)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/models/ResourceContents$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/ResourceListChangedNotification { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ResourceListChangedNotification$Companion; + public fun (Ljava/lang/String;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceListChangedNotification; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ResourceListChangedNotification;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceListChangedNotification; + public fun equals (Ljava/lang/Object;)Z + public final fun getMethod ()Ljava/lang/String; + public final fun getParams ()Ljava/util/Map; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ResourceListChangedNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ResourceListChangedNotification$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceListChangedNotification; + 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/models/ResourceListChangedNotification;)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/models/ResourceListChangedNotification$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/ResourceReference { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ResourceReference$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceReference; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ResourceReference;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceReference; + public fun equals (Ljava/lang/Object;)Z + public final fun getType ()Ljava/lang/String; + public final fun getUri ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ResourceReference$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ResourceReference$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceReference; + 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/models/ResourceReference;)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/models/ResourceReference$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/ResourceTemplate { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ResourceTemplate$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/models/Annotations; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceTemplate; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ResourceTemplate;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceTemplate; + public fun equals (Ljava/lang/Object;)Z + public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/models/Annotations; + public final fun getDescription ()Ljava/lang/String; + public final fun getMimeType ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getUriTemplate ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ResourceTemplate$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ResourceTemplate$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceTemplate; + 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/models/ResourceTemplate;)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/models/ResourceTemplate$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotification { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotification$Companion; + public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotificationParams;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotificationParams; + public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotification; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotification;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotificationParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotification; + public fun equals (Ljava/lang/Object;)Z + public final fun getMethod ()Ljava/lang/String; + public final fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotificationParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotification$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotification; + 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/models/ResourceUpdatedNotification;)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/models/ResourceUpdatedNotification$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotificationParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotificationParams$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotificationParams; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotificationParams;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotificationParams; + public fun equals (Ljava/lang/Object;)Z + public final fun getUri ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotificationParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotificationParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotificationParams; + 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/models/ResourceUpdatedNotificationParams;)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/models/ResourceUpdatedNotificationParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/Role : java/lang/Enum { + public static final field ASSISTANT Lio/modelcontextprotocol/kotlin/sdk/models/Role; + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/Role$Companion; + public static final field USER Lio/modelcontextprotocol/kotlin/sdk/models/Role; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public final fun getValue ()Ljava/lang/String; + public fun toString ()Ljava/lang/String; + public static fun valueOf (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/Role; + public static fun values ()[Lio/modelcontextprotocol/kotlin/sdk/models/Role; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/Role$Companion { + public final fun decode (Ljava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/Role; + public final fun encode (Ljava/lang/Object;)Ljava/lang/String; + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/Root { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/Root$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/Root; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/Root;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/Root; + public fun equals (Ljava/lang/Object;)Z + public final fun getName ()Ljava/lang/String; + public final fun getUri ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/Root$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/Root$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/Root; + 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/models/Root;)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/models/Root$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/RootsListChangedNotification { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/RootsListChangedNotification$Companion; + public fun (Ljava/lang/String;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/models/RootsListChangedNotification; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/RootsListChangedNotification;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/RootsListChangedNotification; + public fun equals (Ljava/lang/Object;)Z + public final fun getMethod ()Ljava/lang/String; + public final fun getParams ()Ljava/util/Map; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/RootsListChangedNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/RootsListChangedNotification$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/RootsListChangedNotification; + 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/models/RootsListChangedNotification;)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/models/RootsListChangedNotification$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/SamplingMessage { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/SamplingMessage$Companion; + public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Role;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/Role; + public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Role;)Lio/modelcontextprotocol/kotlin/sdk/models/SamplingMessage; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/SamplingMessage;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Role;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/SamplingMessage; + public fun equals (Ljava/lang/Object;)Z + public final fun getContent ()Ljava/lang/String; + public final fun getRole ()Lio/modelcontextprotocol/kotlin/sdk/models/Role; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/SamplingMessage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/SamplingMessage$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/SamplingMessage; + 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/models/SamplingMessage;)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/models/SamplingMessage$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesPrompts { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesPrompts$Companion; + public fun ()V + public fun (Ljava/lang/Boolean;)V + public synthetic fun (Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Boolean; + public final fun copy (Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesPrompts; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesPrompts;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesPrompts; + public fun equals (Ljava/lang/Object;)Z + public final fun getListChanged ()Ljava/lang/Boolean; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesPrompts$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesPrompts$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesPrompts; + 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/models/ServerCapabilitiesPrompts;)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/models/ServerCapabilitiesPrompts$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesResources { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesResources$Companion; + public fun ()V + public fun (Ljava/lang/Boolean;Ljava/lang/Boolean;)V + public synthetic fun (Ljava/lang/Boolean;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Boolean; + public final fun component2 ()Ljava/lang/Boolean; + public final fun copy (Ljava/lang/Boolean;Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesResources; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesResources;Ljava/lang/Boolean;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesResources; + public fun equals (Ljava/lang/Object;)Z + public final fun getListChanged ()Ljava/lang/Boolean; + public final fun getSubscribe ()Ljava/lang/Boolean; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesResources$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesResources$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesResources; + 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/models/ServerCapabilitiesResources;)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/models/ServerCapabilitiesResources$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesTools { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesTools$Companion; + public fun ()V + public fun (Ljava/lang/Boolean;)V + public synthetic fun (Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Boolean; + public final fun copy (Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesTools; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesTools;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesTools; + public fun equals (Ljava/lang/Object;)Z + public final fun getListChanged ()Ljava/lang/Boolean; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesTools$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesTools$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesTools; + 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/models/ServerCapabilitiesTools;)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/models/ServerCapabilitiesTools$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/SetLevelRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequest$Companion; + public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequestParams;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequestParams; + public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequest;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequest; + public fun equals (Ljava/lang/Object;)Z + public final fun getMethod ()Ljava/lang/String; + public final fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequestParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/SetLevelRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequest; + 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/models/SetLevelRequest;)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/models/SetLevelRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/SetLevelRequestParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequestParams$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel;)Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequestParams; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequestParams;Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequestParams; + public fun equals (Ljava/lang/Object;)Z + public final fun getLevel ()Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/SetLevelRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequestParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequestParams; + 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/models/SetLevelRequestParams;)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/models/SetLevelRequestParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/SubscribeRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequest$Companion; + public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequestParams;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequestParams; + public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequest;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequest; + public fun equals (Ljava/lang/Object;)Z + public final fun getMethod ()Ljava/lang/String; + public final fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequestParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/SubscribeRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequest; + 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/models/SubscribeRequest;)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/models/SubscribeRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/SubscribeRequestParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequestParams$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequestParams; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequestParams;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequestParams; + public fun equals (Ljava/lang/Object;)Z + public final fun getUri ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/SubscribeRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequestParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequestParams; + 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/models/SubscribeRequestParams;)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/models/SubscribeRequestParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/TextContent { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/TextContent$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/models/Annotations; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;)Lio/modelcontextprotocol/kotlin/sdk/models/TextContent; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/TextContent;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/TextContent; + public fun equals (Ljava/lang/Object;)Z + public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/models/Annotations; + public final fun getText ()Ljava/lang/String; + public final fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/TextContent$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/TextContent$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/TextContent; + 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/models/TextContent;)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/models/TextContent$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/TextResourceContents { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/TextResourceContents$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/TextResourceContents; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/TextResourceContents;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/TextResourceContents; + public fun equals (Ljava/lang/Object;)Z + public final fun getMimeType ()Ljava/lang/String; + public final fun getText ()Ljava/lang/String; + public final fun getUri ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/TextResourceContents$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/TextResourceContents$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/TextResourceContents; + 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/models/TextResourceContents;)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/models/TextResourceContents$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/ToolAnnotations { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ToolAnnotations$Companion; + public fun ()V + public fun (Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Boolean; + public final fun component2 ()Ljava/lang/Boolean; + public final fun component3 ()Ljava/lang/Boolean; + public final fun component4 ()Ljava/lang/Boolean; + public final fun component5 ()Ljava/lang/String; + public final fun copy (Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/ToolAnnotations; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ToolAnnotations;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ToolAnnotations; + public fun equals (Ljava/lang/Object;)Z + public final fun getDestructiveHint ()Ljava/lang/Boolean; + public final fun getIdempotentHint ()Ljava/lang/Boolean; + public final fun getOpenWorldHint ()Ljava/lang/Boolean; + public final fun getReadOnlyHint ()Ljava/lang/Boolean; + public final fun getTitle ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ToolAnnotations$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ToolAnnotations$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ToolAnnotations; + 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/models/ToolAnnotations;)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/models/ToolAnnotations$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/ToolListChangedNotification { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ToolListChangedNotification$Companion; + public fun (Ljava/lang/String;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/models/ToolListChangedNotification; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ToolListChangedNotification;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ToolListChangedNotification; + public fun equals (Ljava/lang/Object;)Z + public final fun getMethod ()Ljava/lang/String; + public final fun getParams ()Ljava/util/Map; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ToolListChangedNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ToolListChangedNotification$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ToolListChangedNotification; + 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/models/ToolListChangedNotification;)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/models/ToolListChangedNotification$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequest$Companion; + public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequestParams;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequestParams; + public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequest;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequest; + public fun equals (Ljava/lang/Object;)Z + public final fun getMethod ()Ljava/lang/String; + public final fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequestParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequest; + 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/models/UnsubscribeRequest;)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/models/UnsubscribeRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequestParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequestParams$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequestParams; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequestParams;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequestParams; + public fun equals (Ljava/lang/Object;)Z + public final fun getUri ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequestParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequestParams; + 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/models/UnsubscribeRequestParams;)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/models/UnsubscribeRequestParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray$Companion; + public fun ([B)V + public fun equals (Ljava/lang/Object;)Z + public final fun getValue ()[B + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray$Companion : kotlinx/serialization/KSerializer { + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public abstract class io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport : io/modelcontextprotocol/kotlin/sdk/shared/Transport { public fun ()V protected final fun get_onClose ()Lkotlin/jvm/functions/Function0; diff --git a/kotlin-sdk-core/build.gradle.kts b/kotlin-sdk-core/build.gradle.kts index 1cbaf6e..df7f242 100644 --- a/kotlin-sdk-core/build.gradle.kts +++ b/kotlin-sdk-core/build.gradle.kts @@ -1,12 +1,67 @@ @file:OptIn(ExperimentalWasmDsl::class) +import org.jetbrains.dokka.gradle.tasks.DokkaGenerateTask import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl +import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask plugins { id("mcp.multiplatform") id("mcp.publishing") id("mcp.dokka") alias(libs.plugins.kotlinx.binary.compatibility.validator) + id("org.openapi.generator") version "7.15.0" +} + +tasks.withType>().configureEach { + dependsOn(tasks.openApiGenerate) +} + +tasks.named("runKtlintCheckOverCommonMainSourceSet") { + dependsOn(tasks.openApiGenerate) +} + +// Also ensure it runs before other relevant tasks +tasks.withType().configureEach { + dependsOn(tasks.openApiGenerate) +} + +tasks.withType().configureEach { + if (name.lowercase().contains("sourcesjar")) { + dependsOn(tasks.openApiGenerate) + } +} + +openApiGenerate { + val schemaVersion = "2025-03-26" // or "2025-06-18" or "draft" + val schemaUrl = +// "https://raw.githubusercontent.com/modelcontextprotocol/modelcontextprotocol/refs/heads/main/schema/$schemaVersion/schema.json" + "https://raw.githubusercontent.com/modelcontextprotocol/modelcontextprotocol/refs/tags/$schemaVersion/schema/$schemaVersion/schema.json" + generatorName = "kotlin" + remoteInputSpec = schemaUrl + packageName = "io.modelcontextprotocol.kotlin.sdk.models" + modelPackage = "io.modelcontextprotocol.kotlin.sdk.models" + apiPackage = "io.modelcontextprotocol.kotlin.sdk.api" + generateModelTests = false + generateModelDocumentation = false + cleanupOutput = false + skipValidateSpec = true // do not validate spec + library = "multiplatform" + ignoreFileOverride = "${layout.projectDirectory}/.openapi-generator-ignore" + globalProperties.set( + mapOf( + "supportingFiles" to "", + "models" to "", // or generate all models + ), + ) + configOptions.set( + mapOf( + "omitGradleWrapper" to "true", + "enumPropertyNaming" to "UPPERCASE", + "dateLibrary" to "kotlinx-datetime", + "explicitApi" to "true", + "modelMutable" to "false", + ), + ) } // Generation library versions @@ -53,6 +108,7 @@ kotlin { sourceSets { commonMain { kotlin.srcDir(generateLibVersion) + kotlin.srcDir("build/generate-resources/main/src/commonMain/kotlin") dependencies { api(libs.kotlinx.serialization.json) api(libs.kotlinx.coroutines.core) @@ -66,6 +122,7 @@ kotlin { commonTest { dependencies { implementation(kotlin("test")) + implementation(libs.kotest.assertions.core) implementation(libs.kotest.assertions.json) } } diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationsTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationsTest.kt new file mode 100644 index 0000000..33927ec --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationsTest.kt @@ -0,0 +1,71 @@ +package io.modelcontextprotocol.kotlin.sdk.models + +import io.kotest.matchers.shouldBe +import io.modelcontextprotocol.kotlin.sdk.ProgressNotification +import io.modelcontextprotocol.kotlin.sdk.RequestId +import io.modelcontextprotocol.kotlin.sdk.shared.McpJson +import kotlin.test.Test + +class ProgressNotificationsTest { + + /** + * https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/progress#progress-flow + */ + @Test + fun `Read ProgressNotifications with string token`() { + //language=json + val json = """ + { + "jsonrpc": "2.0", + "method": "notifications/progress", + "params": { + "progressToken": "abc123", + "progress": 50, + "total": 100, + "message": "Reticulating splines..." + } + } + """.trimIndent() + + val result = McpJson.decodeFromString(json) + + result shouldBe ProgressNotification( + params = ProgressNotification.Params( + progressToken = RequestId.StringId("abc123"), + progress = 50.0, + message = "Reticulating splines...", + total = 100.0, + ), + ) + } + + /** + * https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/progress#progress-flow + */ + @Test + fun `Read ProgressNotifications with integer token`() { + //language=json + val json = """ + { + "jsonrpc": "2.0", + "method": "notifications/progress", + "params": { + "progressToken": 100500, + "progress": 50, + "total": 100, + "message": "Reticulating splines..." + } + } + """.trimIndent() + + val result = McpJson.decodeFromString(json) + result shouldBe ProgressNotification( + params = ProgressNotification.Params( + progressToken = RequestId.NumberId(100500), + progress = 50.0, + message = "Reticulating splines...", + total = 100.0, + ), + ) + } +} From 73c0af891b647ea5fc0e054195cf54ec2ef3a695 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Wed, 1 Oct 2025 19:28:22 +0300 Subject: [PATCH 007/449] Disable OpenAPI Generator (#301) One may run it manually to generate classes from JSON Schema ## Motivation and Context Most of the generated classes could not be used in the current API today. On the other hand, it is useful to be able to generate such classes to simplify manual API synchronization. So, Gradle plugin configuration is preserved, but detached from the build pipeline. --- kotlin-sdk-core/api/kotlin-sdk-core.api | 2440 ----------------------- kotlin-sdk-core/build.gradle.kts | 6 +- 2 files changed, 3 insertions(+), 2443 deletions(-) diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index 0ae1ac4..ed9a268 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -3258,2446 +3258,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/internal/Utils_jvmKt { public static final fun getIODispatcher ()Lkotlinx/coroutines/CoroutineDispatcher; } -public final class io/modelcontextprotocol/kotlin/sdk/models/Annotations { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/Annotations$Companion; - public fun ()V - public fun (Ljava/util/List;Ljava/lang/Double;)V - public synthetic fun (Ljava/util/List;Ljava/lang/Double;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/util/List; - public final fun component2 ()Ljava/lang/Double; - public final fun copy (Ljava/util/List;Ljava/lang/Double;)Lio/modelcontextprotocol/kotlin/sdk/models/Annotations; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;Ljava/util/List;Ljava/lang/Double;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/Annotations; - public fun equals (Ljava/lang/Object;)Z - public final fun getAudience ()Ljava/util/List; - public final fun getPriority ()Ljava/lang/Double; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/Annotations$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/Annotations$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/Annotations; - 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/models/Annotations;)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/models/Annotations$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/AudioContent { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/AudioContent$Companion; - public fun (Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Lio/modelcontextprotocol/kotlin/sdk/models/Annotations; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;)Lio/modelcontextprotocol/kotlin/sdk/models/AudioContent; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/AudioContent;Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/AudioContent; - public fun equals (Ljava/lang/Object;)Z - public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/models/Annotations; - public final fun getData ()Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray; - public final fun getMimeType ()Ljava/lang/String; - public final fun getType ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/AudioContent$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/AudioContent$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/AudioContent; - 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/models/AudioContent;)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/models/AudioContent$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/BlobResourceContents { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/BlobResourceContents$Companion; - public fun (Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray;Ljava/lang/String;Ljava/lang/String;)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray;Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/BlobResourceContents; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/BlobResourceContents;Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/BlobResourceContents; - public fun equals (Ljava/lang/Object;)Z - public final fun getBlob ()Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray; - public final fun getMimeType ()Ljava/lang/String; - public final fun getUri ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/BlobResourceContents$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/BlobResourceContents$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/BlobResourceContents; - 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/models/BlobResourceContents;)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/models/BlobResourceContents$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/CallToolRequest { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequest$Companion; - public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequestParams;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequestParams; - public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequest; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequest;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequest; - public fun equals (Ljava/lang/Object;)Z - public final fun getMethod ()Ljava/lang/String; - public final fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequestParams; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/CallToolRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequest; - 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/models/CallToolRequest;)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/models/CallToolRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/CallToolRequestParams { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequestParams$Companion; - public fun (Ljava/lang/String;Ljava/util/Map;)V - public synthetic fun (Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/util/Map; - public final fun copy (Ljava/lang/String;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequestParams; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequestParams;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequestParams; - public fun equals (Ljava/lang/Object;)Z - public final fun getArguments ()Ljava/util/Map; - public final fun getName ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/CallToolRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequestParams$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/CallToolRequestParams; - 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/models/CallToolRequestParams;)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/models/CallToolRequestParams$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/CallToolResult { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/CallToolResult$Companion; - public fun (Ljava/util/List;Ljava/util/Map;Ljava/lang/Boolean;)V - public synthetic fun (Ljava/util/List;Ljava/util/Map;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/util/List; - public final fun component2 ()Ljava/util/Map; - public final fun component3 ()Ljava/lang/Boolean; - public final fun copy (Ljava/util/List;Ljava/util/Map;Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/models/CallToolResult; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/CallToolResult;Ljava/util/List;Ljava/util/Map;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/CallToolResult; - public fun equals (Ljava/lang/Object;)Z - public final fun getContent ()Ljava/util/List; - public final fun getMeta ()Ljava/util/Map; - public fun hashCode ()I - public final fun isError ()Ljava/lang/Boolean; - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/CallToolResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/CallToolResult$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/CallToolResult; - 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/models/CallToolResult;)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/models/CallToolResult$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/CancelledNotification { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotification$Companion; - public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotificationParams;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotificationParams; - public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotification; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotification;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotificationParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotification; - public fun equals (Ljava/lang/Object;)Z - public final fun getMethod ()Ljava/lang/String; - public final fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotificationParams; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/CancelledNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotification$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotification; - 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/models/CancelledNotification;)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/models/CancelledNotification$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/CancelledNotificationParams { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotificationParams$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotificationParams; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotificationParams;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotificationParams; - public fun equals (Ljava/lang/Object;)Z - public final fun getReason ()Ljava/lang/String; - public final fun getRequestId ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/CancelledNotificationParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotificationParams$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/CancelledNotificationParams; - 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/models/CancelledNotificationParams;)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/models/CancelledNotificationParams$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/ClientCapabilitiesRoots { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ClientCapabilitiesRoots$Companion; - public fun ()V - public fun (Ljava/lang/Boolean;)V - public synthetic fun (Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/Boolean; - public final fun copy (Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/models/ClientCapabilitiesRoots; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ClientCapabilitiesRoots;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ClientCapabilitiesRoots; - public fun equals (Ljava/lang/Object;)Z - public final fun getListChanged ()Ljava/lang/Boolean; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ClientCapabilitiesRoots$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ClientCapabilitiesRoots$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ClientCapabilitiesRoots; - 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/models/ClientCapabilitiesRoots;)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/models/ClientCapabilitiesRoots$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/CompleteRequest { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequest$Companion; - public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParams;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParams; - public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequest; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequest;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequest; - public fun equals (Ljava/lang/Object;)Z - public final fun getMethod ()Ljava/lang/String; - public final fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParams; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/CompleteRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequest; - 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/models/CompleteRequest;)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/models/CompleteRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParams { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParams$Companion; - public fun (Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParamsArgument;Ljava/lang/String;)V - public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParamsArgument; - public final fun component2 ()Ljava/lang/String; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParamsArgument;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParams; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParams;Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParamsArgument;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParams; - public fun equals (Ljava/lang/Object;)Z - public final fun getArgument ()Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParamsArgument; - public final fun getRef ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParams$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParams; - 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/models/CompleteRequestParams;)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/models/CompleteRequestParams$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParamsArgument { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParamsArgument$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParamsArgument; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParamsArgument;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParamsArgument; - public fun equals (Ljava/lang/Object;)Z - public final fun getName ()Ljava/lang/String; - public final fun getValue ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParamsArgument$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParamsArgument$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/CompleteRequestParamsArgument; - 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/models/CompleteRequestParamsArgument;)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/models/CompleteRequestParamsArgument$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/CompleteResult { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResult$Companion; - public fun (Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResultCompletion;Ljava/util/Map;)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResultCompletion;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResultCompletion; - public final fun component2 ()Ljava/util/Map; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResultCompletion;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResult; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResult;Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResultCompletion;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResult; - public fun equals (Ljava/lang/Object;)Z - public final fun getCompletion ()Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResultCompletion; - public final fun getMeta ()Ljava/util/Map; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/CompleteResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResult$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResult; - 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/models/CompleteResult;)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/models/CompleteResult$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/CompleteResultCompletion { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResultCompletion$Companion; - public fun (Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Integer;)V - public synthetic fun (Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/util/List; - public final fun component2 ()Ljava/lang/Boolean; - public final fun component3 ()Ljava/lang/Integer; - public final fun copy (Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Integer;)Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResultCompletion; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResultCompletion;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Integer;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResultCompletion; - public fun equals (Ljava/lang/Object;)Z - public final fun getHasMore ()Ljava/lang/Boolean; - public final fun getPropertyValues ()Ljava/util/List; - public final fun getTotal ()Ljava/lang/Integer; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/CompleteResultCompletion$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResultCompletion$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/CompleteResultCompletion; - 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/models/CompleteResultCompletion;)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/models/CompleteResultCompletion$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/CreateMessageResult { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/CreateMessageResult$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Role;Ljava/util/Map;Ljava/lang/String;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Role;Ljava/util/Map;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/models/Role; - public final fun component4 ()Ljava/util/Map; - public final fun component5 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Role;Ljava/util/Map;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/CreateMessageResult; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/CreateMessageResult;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Role;Ljava/util/Map;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/CreateMessageResult; - public fun equals (Ljava/lang/Object;)Z - public final fun getContent ()Ljava/lang/String; - public final fun getMeta ()Ljava/util/Map; - public final fun getModel ()Ljava/lang/String; - public final fun getRole ()Lio/modelcontextprotocol/kotlin/sdk/models/Role; - public final fun getStopReason ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/CreateMessageResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/CreateMessageResult$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/CreateMessageResult; - 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/models/CreateMessageResult;)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/models/CreateMessageResult$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/EmbeddedResource { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/EmbeddedResource$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/models/Annotations; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;)Lio/modelcontextprotocol/kotlin/sdk/models/EmbeddedResource; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/EmbeddedResource;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/EmbeddedResource; - public fun equals (Ljava/lang/Object;)Z - public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/models/Annotations; - public final fun getResource ()Ljava/lang/String; - public final fun getType ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/EmbeddedResource$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/EmbeddedResource$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/EmbeddedResource; - 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/models/EmbeddedResource;)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/models/EmbeddedResource$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/GetPromptRequest { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequest$Companion; - public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequestParams;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequestParams; - public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequest; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequest;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequest; - public fun equals (Ljava/lang/Object;)Z - public final fun getMethod ()Ljava/lang/String; - public final fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequestParams; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/GetPromptRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequest; - 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/models/GetPromptRequest;)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/models/GetPromptRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/GetPromptRequestParams { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequestParams$Companion; - public fun (Ljava/lang/String;Ljava/util/Map;)V - public synthetic fun (Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/util/Map; - public final fun copy (Ljava/lang/String;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequestParams; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequestParams;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequestParams; - public fun equals (Ljava/lang/Object;)Z - public final fun getArguments ()Ljava/util/Map; - public final fun getName ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/GetPromptRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequestParams$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptRequestParams; - 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/models/GetPromptRequestParams;)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/models/GetPromptRequestParams$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/GetPromptResult { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptResult$Companion; - public fun (Ljava/util/List;Ljava/util/Map;Ljava/lang/String;)V - public synthetic fun (Ljava/util/List;Ljava/util/Map;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/util/List; - public final fun component2 ()Ljava/util/Map; - public final fun component3 ()Ljava/lang/String; - public final fun copy (Ljava/util/List;Ljava/util/Map;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptResult; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptResult;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptResult; - public fun equals (Ljava/lang/Object;)Z - public final fun getDescription ()Ljava/lang/String; - public final fun getMessages ()Ljava/util/List; - public final fun getMeta ()Ljava/util/Map; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/GetPromptResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptResult$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/GetPromptResult; - 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/models/GetPromptResult;)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/models/GetPromptResult$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/ImageContent { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ImageContent$Companion; - public fun (Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Lio/modelcontextprotocol/kotlin/sdk/models/Annotations; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;)Lio/modelcontextprotocol/kotlin/sdk/models/ImageContent; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ImageContent;Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ImageContent; - public fun equals (Ljava/lang/Object;)Z - public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/models/Annotations; - public final fun getData ()Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray; - public final fun getMimeType ()Ljava/lang/String; - public final fun getType ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ImageContent$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ImageContent$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ImageContent; - 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/models/ImageContent;)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/models/ImageContent$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/Implementation { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/Implementation$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/Implementation; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/Implementation;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/Implementation; - public fun equals (Ljava/lang/Object;)Z - public final fun getName ()Ljava/lang/String; - public final fun getVersion ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/Implementation$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/Implementation$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/Implementation; - 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/models/Implementation;)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/models/Implementation$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/InitializedNotification { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/InitializedNotification$Companion; - public fun (Ljava/lang/String;Ljava/util/Map;)V - public synthetic fun (Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/util/Map; - public final fun copy (Ljava/lang/String;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/models/InitializedNotification; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/InitializedNotification;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/InitializedNotification; - public fun equals (Ljava/lang/Object;)Z - public final fun getMethod ()Ljava/lang/String; - public final fun getParams ()Ljava/util/Map; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/InitializedNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/InitializedNotification$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/InitializedNotification; - 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/models/InitializedNotification;)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/models/InitializedNotification$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/JSONRPCError { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCError$Companion; - public fun (Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCErrorError;Ljava/lang/String;Ljava/lang/String;)V - public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCErrorError; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCErrorError;Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCError; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCError;Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCErrorError;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCError; - public fun equals (Ljava/lang/Object;)Z - public final fun getError ()Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCErrorError; - public final fun getId ()Ljava/lang/String; - public final fun getJsonrpc ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/JSONRPCError$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCError$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCError; - 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/models/JSONRPCError;)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/models/JSONRPCError$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/JSONRPCErrorError { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCErrorError$Companion; - public fun (ILjava/lang/String;Ljava/lang/String;)V - public synthetic fun (ILjava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()I - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun copy (ILjava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCErrorError; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCErrorError;ILjava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCErrorError; - public fun equals (Ljava/lang/Object;)Z - public final fun getCode ()I - public final fun getData ()Ljava/lang/String; - public final fun getMessage ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/JSONRPCErrorError$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCErrorError$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCErrorError; - 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/models/JSONRPCErrorError;)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/models/JSONRPCErrorError$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/JSONRPCNotification { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCNotification$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/util/Map; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCNotification; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCNotification;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCNotification; - public fun equals (Ljava/lang/Object;)Z - public final fun getJsonrpc ()Ljava/lang/String; - public final fun getMethod ()Ljava/lang/String; - public final fun getParams ()Ljava/util/Map; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/JSONRPCNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCNotification$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCNotification; - 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/models/JSONRPCNotification;)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/models/JSONRPCNotification$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/JSONRPCRequest { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCRequest$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Ljava/util/Map; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCRequest; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCRequest;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCRequest; - public fun equals (Ljava/lang/Object;)Z - public final fun getId ()Ljava/lang/String; - public final fun getJsonrpc ()Ljava/lang/String; - public final fun getMethod ()Ljava/lang/String; - public final fun getParams ()Ljava/util/Map; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/JSONRPCRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/JSONRPCRequest; - 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/models/JSONRPCRequest;)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/models/JSONRPCRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequest { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequest$Companion; - public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;)V - public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams; - public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequest; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequest;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequest; - public fun equals (Ljava/lang/Object;)Z - public final fun getMethod ()Ljava/lang/String; - public final fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequest; - 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/models/ListPromptsRequest;)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/models/ListPromptsRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams$Companion; - public fun ()V - public fun (Ljava/lang/String;)V - public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams; - public fun equals (Ljava/lang/Object;)Z - public final fun getCursor ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams; - 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/models/ListPromptsRequestParams;)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/models/ListPromptsRequestParams$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/ListPromptsResult { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsResult$Companion; - public fun (Ljava/util/List;Ljava/util/Map;Ljava/lang/String;)V - public synthetic fun (Ljava/util/List;Ljava/util/Map;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/util/List; - public final fun component2 ()Ljava/util/Map; - public final fun component3 ()Ljava/lang/String; - public final fun copy (Ljava/util/List;Ljava/util/Map;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsResult; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsResult;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsResult; - public fun equals (Ljava/lang/Object;)Z - public final fun getMeta ()Ljava/util/Map; - public final fun getNextCursor ()Ljava/lang/String; - public final fun getPrompts ()Ljava/util/List; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ListPromptsResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsResult$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsResult; - 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/models/ListPromptsResult;)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/models/ListPromptsResult$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/ListResourceTemplatesRequest { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ListResourceTemplatesRequest$Companion; - public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;)V - public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams; - public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/models/ListResourceTemplatesRequest; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ListResourceTemplatesRequest;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ListResourceTemplatesRequest; - public fun equals (Ljava/lang/Object;)Z - public final fun getMethod ()Ljava/lang/String; - public final fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ListResourceTemplatesRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ListResourceTemplatesRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ListResourceTemplatesRequest; - 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/models/ListResourceTemplatesRequest;)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/models/ListResourceTemplatesRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/ListResourceTemplatesResult { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ListResourceTemplatesResult$Companion; - public fun (Ljava/util/List;Ljava/util/Map;Ljava/lang/String;)V - public synthetic fun (Ljava/util/List;Ljava/util/Map;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/util/List; - public final fun component2 ()Ljava/util/Map; - public final fun component3 ()Ljava/lang/String; - public final fun copy (Ljava/util/List;Ljava/util/Map;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/ListResourceTemplatesResult; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ListResourceTemplatesResult;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ListResourceTemplatesResult; - public fun equals (Ljava/lang/Object;)Z - public final fun getMeta ()Ljava/util/Map; - public final fun getNextCursor ()Ljava/lang/String; - public final fun getResourceTemplates ()Ljava/util/List; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ListResourceTemplatesResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ListResourceTemplatesResult$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ListResourceTemplatesResult; - 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/models/ListResourceTemplatesResult;)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/models/ListResourceTemplatesResult$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/ListResourcesRequest { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ListResourcesRequest$Companion; - public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;)V - public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams; - public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/models/ListResourcesRequest; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ListResourcesRequest;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ListResourcesRequest; - public fun equals (Ljava/lang/Object;)Z - public final fun getMethod ()Ljava/lang/String; - public final fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ListResourcesRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ListResourcesRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ListResourcesRequest; - 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/models/ListResourcesRequest;)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/models/ListResourcesRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/ListResourcesResult { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ListResourcesResult$Companion; - public fun (Ljava/util/List;Ljava/util/Map;Ljava/lang/String;)V - public synthetic fun (Ljava/util/List;Ljava/util/Map;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/util/List; - public final fun component2 ()Ljava/util/Map; - public final fun component3 ()Ljava/lang/String; - public final fun copy (Ljava/util/List;Ljava/util/Map;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/ListResourcesResult; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ListResourcesResult;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ListResourcesResult; - public fun equals (Ljava/lang/Object;)Z - public final fun getMeta ()Ljava/util/Map; - public final fun getNextCursor ()Ljava/lang/String; - public final fun getResources ()Ljava/util/List; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ListResourcesResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ListResourcesResult$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ListResourcesResult; - 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/models/ListResourcesResult;)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/models/ListResourcesResult$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/ListRootsRequest { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ListRootsRequest$Companion; - public fun (Ljava/lang/String;Ljava/util/Map;)V - public synthetic fun (Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/util/Map; - public final fun copy (Ljava/lang/String;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/models/ListRootsRequest; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ListRootsRequest;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ListRootsRequest; - public fun equals (Ljava/lang/Object;)Z - public final fun getMethod ()Ljava/lang/String; - public final fun getParams ()Ljava/util/Map; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ListRootsRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ListRootsRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ListRootsRequest; - 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/models/ListRootsRequest;)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/models/ListRootsRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/ListRootsResult { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ListRootsResult$Companion; - public fun (Ljava/util/List;Ljava/util/Map;)V - public synthetic fun (Ljava/util/List;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/util/List; - public final fun component2 ()Ljava/util/Map; - public final fun copy (Ljava/util/List;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/models/ListRootsResult; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ListRootsResult;Ljava/util/List;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ListRootsResult; - public fun equals (Ljava/lang/Object;)Z - public final fun getMeta ()Ljava/util/Map; - public final fun getRoots ()Ljava/util/List; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ListRootsResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ListRootsResult$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ListRootsResult; - 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/models/ListRootsResult;)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/models/ListRootsResult$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/ListToolsRequest { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ListToolsRequest$Companion; - public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;)V - public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams; - public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/models/ListToolsRequest; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ListToolsRequest;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ListToolsRequest; - public fun equals (Ljava/lang/Object;)Z - public final fun getMethod ()Ljava/lang/String; - public final fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ListToolsRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ListToolsRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ListToolsRequest; - 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/models/ListToolsRequest;)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/models/ListToolsRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/LoggingLevel : java/lang/Enum { - public static final field ALERT Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel; - public static final field CRITICAL Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel; - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel$Companion; - public static final field DEBUG Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel; - public static final field EMERGENCY Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel; - public static final field ERROR Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel; - public static final field INFO Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel; - public static final field NOTICE Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel; - public static final field WARNING Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public final fun getValue ()Ljava/lang/String; - public fun toString ()Ljava/lang/String; - public static fun valueOf (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel; - public static fun values ()[Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/LoggingLevel$Companion { - public final fun decode (Ljava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel; - public final fun encode (Ljava/lang/Object;)Ljava/lang/String; - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotification { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotification$Companion; - public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotificationParams;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotificationParams; - public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotification; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotification;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotificationParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotification; - public fun equals (Ljava/lang/Object;)Z - public final fun getMethod ()Ljava/lang/String; - public final fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotificationParams; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotification$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotification; - 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/models/LoggingMessageNotification;)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/models/LoggingMessageNotification$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotificationParams { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotificationParams$Companion; - public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel;Ljava/lang/String;)V - public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel; - public final fun component3 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotificationParams; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotificationParams;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotificationParams; - public fun equals (Ljava/lang/Object;)Z - public final fun getData ()Ljava/lang/String; - public final fun getLevel ()Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel; - public final fun getLogger ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotificationParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotificationParams$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/LoggingMessageNotificationParams; - 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/models/LoggingMessageNotificationParams;)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/models/LoggingMessageNotificationParams$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/ModelHint { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ModelHint$Companion; - public fun ()V - public fun (Ljava/lang/String;)V - public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/ModelHint; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ModelHint;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ModelHint; - public fun equals (Ljava/lang/Object;)Z - public final fun getName ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ModelHint$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ModelHint$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ModelHint; - 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/models/ModelHint;)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/models/ModelHint$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/ModelPreferences { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ModelPreferences$Companion; - public fun ()V - public fun (Ljava/lang/Double;Ljava/util/List;Ljava/lang/Double;Ljava/lang/Double;)V - public synthetic fun (Ljava/lang/Double;Ljava/util/List;Ljava/lang/Double;Ljava/lang/Double;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/Double; - public final fun component2 ()Ljava/util/List; - public final fun component3 ()Ljava/lang/Double; - public final fun component4 ()Ljava/lang/Double; - public final fun copy (Ljava/lang/Double;Ljava/util/List;Ljava/lang/Double;Ljava/lang/Double;)Lio/modelcontextprotocol/kotlin/sdk/models/ModelPreferences; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ModelPreferences;Ljava/lang/Double;Ljava/util/List;Ljava/lang/Double;Ljava/lang/Double;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ModelPreferences; - public fun equals (Ljava/lang/Object;)Z - public final fun getCostPriority ()Ljava/lang/Double; - public final fun getHints ()Ljava/util/List; - public final fun getIntelligencePriority ()Ljava/lang/Double; - public final fun getSpeedPriority ()Ljava/lang/Double; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ModelPreferences$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ModelPreferences$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ModelPreferences; - 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/models/ModelPreferences;)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/models/ModelPreferences$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/Notification { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/Notification$Companion; - public fun (Ljava/lang/String;Ljava/util/Map;)V - public synthetic fun (Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/util/Map; - public final fun copy (Ljava/lang/String;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/models/Notification; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/Notification;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/Notification; - public fun equals (Ljava/lang/Object;)Z - public final fun getMethod ()Ljava/lang/String; - public final fun getParams ()Ljava/util/Map; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/Notification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/Notification$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/Notification; - 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/models/Notification;)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/models/Notification$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/PaginatedRequest { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/PaginatedRequest$Companion; - public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;)V - public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams; - public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/models/PaginatedRequest; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/PaginatedRequest;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/PaginatedRequest; - public fun equals (Ljava/lang/Object;)Z - public final fun getMethod ()Ljava/lang/String; - public final fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/models/ListPromptsRequestParams; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/PaginatedRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/PaginatedRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/PaginatedRequest; - 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/models/PaginatedRequest;)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/models/PaginatedRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/PaginatedResult { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/PaginatedResult$Companion; - public fun ()V - public fun (Ljava/util/Map;Ljava/lang/String;)V - public synthetic fun (Ljava/util/Map;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/util/Map; - public final fun component2 ()Ljava/lang/String; - public final fun copy (Ljava/util/Map;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/PaginatedResult; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/PaginatedResult;Ljava/util/Map;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/PaginatedResult; - public fun equals (Ljava/lang/Object;)Z - public final fun getMeta ()Ljava/util/Map; - public final fun getNextCursor ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/PaginatedResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/PaginatedResult$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/PaginatedResult; - 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/models/PaginatedResult;)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/models/PaginatedResult$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/PingRequest { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/PingRequest$Companion; - public fun (Ljava/lang/String;Ljava/util/Map;)V - public synthetic fun (Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/util/Map; - public final fun copy (Ljava/lang/String;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/models/PingRequest; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/PingRequest;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/PingRequest; - public fun equals (Ljava/lang/Object;)Z - public final fun getMethod ()Ljava/lang/String; - public final fun getParams ()Ljava/util/Map; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/PingRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/PingRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/PingRequest; - 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/models/PingRequest;)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/models/PingRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/ProgressNotification { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotification$Companion; - public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationParams;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationParams; - public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotification; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotification;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotification; - public fun equals (Ljava/lang/Object;)Z - public final fun getMethod ()Ljava/lang/String; - public final fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationParams; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ProgressNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotification$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotification; - 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/models/ProgressNotification;)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/models/ProgressNotification$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationParams { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationParams$Companion; - public fun (DLjava/lang/String;Ljava/lang/String;Ljava/lang/Double;)V - public synthetic fun (DLjava/lang/String;Ljava/lang/String;Ljava/lang/Double;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()D - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Ljava/lang/Double; - public final fun copy (DLjava/lang/String;Ljava/lang/String;Ljava/lang/Double;)Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationParams; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationParams;DLjava/lang/String;Ljava/lang/String;Ljava/lang/Double;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationParams; - public fun equals (Ljava/lang/Object;)Z - public final fun getMessage ()Ljava/lang/String; - public final fun getProgress ()D - public final fun getProgressToken ()Ljava/lang/String; - public final fun getTotal ()Ljava/lang/Double; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationParams$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationParams; - 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/models/ProgressNotificationParams;)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/models/ProgressNotificationParams$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/Prompt { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/Prompt$Companion; - public fun (Ljava/lang/String;Ljava/util/List;Ljava/lang/String;)V - public synthetic fun (Ljava/lang/String;Ljava/util/List;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/util/List; - public final fun component3 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/util/List;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/Prompt; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/Prompt;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/Prompt; - public fun equals (Ljava/lang/Object;)Z - public final fun getArguments ()Ljava/util/List; - public final fun getDescription ()Ljava/lang/String; - public final fun getName ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/Prompt$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/Prompt$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/Prompt; - 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/models/Prompt;)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/models/Prompt$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/PromptArgument { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/PromptArgument$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/Boolean; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/models/PromptArgument; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/PromptArgument;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/PromptArgument; - public fun equals (Ljava/lang/Object;)Z - public final fun getDescription ()Ljava/lang/String; - public final fun getName ()Ljava/lang/String; - public final fun getRequired ()Ljava/lang/Boolean; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/PromptArgument$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/PromptArgument$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/PromptArgument; - 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/models/PromptArgument;)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/models/PromptArgument$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/PromptListChangedNotification { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/PromptListChangedNotification$Companion; - public fun (Ljava/lang/String;Ljava/util/Map;)V - public synthetic fun (Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/util/Map; - public final fun copy (Ljava/lang/String;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/models/PromptListChangedNotification; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/PromptListChangedNotification;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/PromptListChangedNotification; - public fun equals (Ljava/lang/Object;)Z - public final fun getMethod ()Ljava/lang/String; - public final fun getParams ()Ljava/util/Map; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/PromptListChangedNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/PromptListChangedNotification$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/PromptListChangedNotification; - 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/models/PromptListChangedNotification;)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/models/PromptListChangedNotification$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/PromptMessage { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/PromptMessage$Companion; - public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Role;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/Role; - public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Role;)Lio/modelcontextprotocol/kotlin/sdk/models/PromptMessage; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/PromptMessage;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Role;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/PromptMessage; - public fun equals (Ljava/lang/Object;)Z - public final fun getContent ()Ljava/lang/String; - public final fun getRole ()Lio/modelcontextprotocol/kotlin/sdk/models/Role; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/PromptMessage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/PromptMessage$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/PromptMessage; - 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/models/PromptMessage;)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/models/PromptMessage$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/PromptReference { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/PromptReference$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/PromptReference; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/PromptReference;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/PromptReference; - public fun equals (Ljava/lang/Object;)Z - public final fun getName ()Ljava/lang/String; - public final fun getType ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/PromptReference$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/PromptReference$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/PromptReference; - 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/models/PromptReference;)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/models/PromptReference$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequest { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequest$Companion; - public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequestParams;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequestParams; - public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequest; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequest;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequest; - public fun equals (Ljava/lang/Object;)Z - public final fun getMethod ()Ljava/lang/String; - public final fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequestParams; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequest; - 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/models/ReadResourceRequest;)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/models/ReadResourceRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequestParams { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequestParams$Companion; - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequestParams; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequestParams;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequestParams; - public fun equals (Ljava/lang/Object;)Z - public final fun getUri ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequestParams$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceRequestParams; - 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/models/ReadResourceRequestParams;)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/models/ReadResourceRequestParams$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/ReadResourceResult { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceResult$Companion; - public fun (Ljava/util/List;Ljava/util/Map;)V - public synthetic fun (Ljava/util/List;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/util/List; - public final fun component2 ()Ljava/util/Map; - public final fun copy (Ljava/util/List;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceResult; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceResult;Ljava/util/List;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceResult; - public fun equals (Ljava/lang/Object;)Z - public final fun getContents ()Ljava/util/List; - public final fun getMeta ()Ljava/util/Map; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ReadResourceResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceResult$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ReadResourceResult; - 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/models/ReadResourceResult;)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/models/ReadResourceResult$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/Request { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/Request$Companion; - public fun (Ljava/lang/String;Ljava/util/Map;)V - public synthetic fun (Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/util/Map; - public final fun copy (Ljava/lang/String;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/models/Request; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/Request;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/Request; - public fun equals (Ljava/lang/Object;)Z - public final fun getMethod ()Ljava/lang/String; - public final fun getParams ()Ljava/util/Map; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/Request$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/Request$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/Request; - 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/models/Request;)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/models/Request$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/Resource { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/Resource$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;Ljava/lang/String;Ljava/lang/String;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/models/Annotations; - public final fun component4 ()Ljava/lang/String; - public final fun component5 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/Resource; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/Resource;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/Resource; - public fun equals (Ljava/lang/Object;)Z - public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/models/Annotations; - public final fun getDescription ()Ljava/lang/String; - public final fun getMimeType ()Ljava/lang/String; - public final fun getName ()Ljava/lang/String; - public final fun getUri ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/Resource$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/Resource$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/Resource; - 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/models/Resource;)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/models/Resource$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/ResourceContents { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ResourceContents$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceContents; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ResourceContents;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceContents; - public fun equals (Ljava/lang/Object;)Z - public final fun getMimeType ()Ljava/lang/String; - public final fun getUri ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ResourceContents$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ResourceContents$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceContents; - 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/models/ResourceContents;)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/models/ResourceContents$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/ResourceListChangedNotification { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ResourceListChangedNotification$Companion; - public fun (Ljava/lang/String;Ljava/util/Map;)V - public synthetic fun (Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/util/Map; - public final fun copy (Ljava/lang/String;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceListChangedNotification; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ResourceListChangedNotification;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceListChangedNotification; - public fun equals (Ljava/lang/Object;)Z - public final fun getMethod ()Ljava/lang/String; - public final fun getParams ()Ljava/util/Map; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ResourceListChangedNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ResourceListChangedNotification$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceListChangedNotification; - 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/models/ResourceListChangedNotification;)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/models/ResourceListChangedNotification$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/ResourceReference { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ResourceReference$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceReference; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ResourceReference;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceReference; - public fun equals (Ljava/lang/Object;)Z - public final fun getType ()Ljava/lang/String; - public final fun getUri ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ResourceReference$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ResourceReference$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceReference; - 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/models/ResourceReference;)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/models/ResourceReference$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/ResourceTemplate { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ResourceTemplate$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;Ljava/lang/String;Ljava/lang/String;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/models/Annotations; - public final fun component4 ()Ljava/lang/String; - public final fun component5 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceTemplate; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ResourceTemplate;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceTemplate; - public fun equals (Ljava/lang/Object;)Z - public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/models/Annotations; - public final fun getDescription ()Ljava/lang/String; - public final fun getMimeType ()Ljava/lang/String; - public final fun getName ()Ljava/lang/String; - public final fun getUriTemplate ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ResourceTemplate$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ResourceTemplate$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceTemplate; - 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/models/ResourceTemplate;)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/models/ResourceTemplate$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotification { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotification$Companion; - public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotificationParams;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotificationParams; - public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotification; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotification;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotificationParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotification; - public fun equals (Ljava/lang/Object;)Z - public final fun getMethod ()Ljava/lang/String; - public final fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotificationParams; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotification$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotification; - 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/models/ResourceUpdatedNotification;)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/models/ResourceUpdatedNotification$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotificationParams { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotificationParams$Companion; - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotificationParams; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotificationParams;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotificationParams; - public fun equals (Ljava/lang/Object;)Z - public final fun getUri ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotificationParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotificationParams$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ResourceUpdatedNotificationParams; - 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/models/ResourceUpdatedNotificationParams;)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/models/ResourceUpdatedNotificationParams$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/Role : java/lang/Enum { - public static final field ASSISTANT Lio/modelcontextprotocol/kotlin/sdk/models/Role; - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/Role$Companion; - public static final field USER Lio/modelcontextprotocol/kotlin/sdk/models/Role; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public final fun getValue ()Ljava/lang/String; - public fun toString ()Ljava/lang/String; - public static fun valueOf (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/Role; - public static fun values ()[Lio/modelcontextprotocol/kotlin/sdk/models/Role; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/Role$Companion { - public final fun decode (Ljava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/Role; - public final fun encode (Ljava/lang/Object;)Ljava/lang/String; - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/Root { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/Root$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/Root; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/Root;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/Root; - public fun equals (Ljava/lang/Object;)Z - public final fun getName ()Ljava/lang/String; - public final fun getUri ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/Root$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/Root$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/Root; - 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/models/Root;)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/models/Root$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/RootsListChangedNotification { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/RootsListChangedNotification$Companion; - public fun (Ljava/lang/String;Ljava/util/Map;)V - public synthetic fun (Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/util/Map; - public final fun copy (Ljava/lang/String;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/models/RootsListChangedNotification; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/RootsListChangedNotification;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/RootsListChangedNotification; - public fun equals (Ljava/lang/Object;)Z - public final fun getMethod ()Ljava/lang/String; - public final fun getParams ()Ljava/util/Map; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/RootsListChangedNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/RootsListChangedNotification$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/RootsListChangedNotification; - 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/models/RootsListChangedNotification;)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/models/RootsListChangedNotification$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/SamplingMessage { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/SamplingMessage$Companion; - public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Role;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/Role; - public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Role;)Lio/modelcontextprotocol/kotlin/sdk/models/SamplingMessage; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/SamplingMessage;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Role;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/SamplingMessage; - public fun equals (Ljava/lang/Object;)Z - public final fun getContent ()Ljava/lang/String; - public final fun getRole ()Lio/modelcontextprotocol/kotlin/sdk/models/Role; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/SamplingMessage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/SamplingMessage$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/SamplingMessage; - 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/models/SamplingMessage;)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/models/SamplingMessage$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesPrompts { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesPrompts$Companion; - public fun ()V - public fun (Ljava/lang/Boolean;)V - public synthetic fun (Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/Boolean; - public final fun copy (Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesPrompts; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesPrompts;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesPrompts; - public fun equals (Ljava/lang/Object;)Z - public final fun getListChanged ()Ljava/lang/Boolean; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesPrompts$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesPrompts$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesPrompts; - 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/models/ServerCapabilitiesPrompts;)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/models/ServerCapabilitiesPrompts$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesResources { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesResources$Companion; - public fun ()V - public fun (Ljava/lang/Boolean;Ljava/lang/Boolean;)V - public synthetic fun (Ljava/lang/Boolean;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/Boolean; - public final fun component2 ()Ljava/lang/Boolean; - public final fun copy (Ljava/lang/Boolean;Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesResources; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesResources;Ljava/lang/Boolean;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesResources; - public fun equals (Ljava/lang/Object;)Z - public final fun getListChanged ()Ljava/lang/Boolean; - public final fun getSubscribe ()Ljava/lang/Boolean; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesResources$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesResources$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesResources; - 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/models/ServerCapabilitiesResources;)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/models/ServerCapabilitiesResources$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesTools { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesTools$Companion; - public fun ()V - public fun (Ljava/lang/Boolean;)V - public synthetic fun (Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/Boolean; - public final fun copy (Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesTools; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesTools;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesTools; - public fun equals (Ljava/lang/Object;)Z - public final fun getListChanged ()Ljava/lang/Boolean; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesTools$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesTools$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ServerCapabilitiesTools; - 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/models/ServerCapabilitiesTools;)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/models/ServerCapabilitiesTools$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/SetLevelRequest { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequest$Companion; - public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequestParams;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequestParams; - public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequest; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequest;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequest; - public fun equals (Ljava/lang/Object;)Z - public final fun getMethod ()Ljava/lang/String; - public final fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequestParams; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/SetLevelRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequest; - 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/models/SetLevelRequest;)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/models/SetLevelRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/SetLevelRequestParams { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequestParams$Companion; - public fun (Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel;)V - public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel;)Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequestParams; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequestParams;Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequestParams; - public fun equals (Ljava/lang/Object;)Z - public final fun getLevel ()Lio/modelcontextprotocol/kotlin/sdk/models/LoggingLevel; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/SetLevelRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequestParams$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/SetLevelRequestParams; - 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/models/SetLevelRequestParams;)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/models/SetLevelRequestParams$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/SubscribeRequest { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequest$Companion; - public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequestParams;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequestParams; - public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequest; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequest;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequest; - public fun equals (Ljava/lang/Object;)Z - public final fun getMethod ()Ljava/lang/String; - public final fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequestParams; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/SubscribeRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequest; - 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/models/SubscribeRequest;)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/models/SubscribeRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/SubscribeRequestParams { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequestParams$Companion; - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequestParams; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequestParams;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequestParams; - public fun equals (Ljava/lang/Object;)Z - public final fun getUri ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/SubscribeRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequestParams$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/SubscribeRequestParams; - 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/models/SubscribeRequestParams;)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/models/SubscribeRequestParams$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/TextContent { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/TextContent$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/models/Annotations; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;)Lio/modelcontextprotocol/kotlin/sdk/models/TextContent; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/TextContent;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/Annotations;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/TextContent; - public fun equals (Ljava/lang/Object;)Z - public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/models/Annotations; - public final fun getText ()Ljava/lang/String; - public final fun getType ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/TextContent$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/TextContent$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/TextContent; - 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/models/TextContent;)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/models/TextContent$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/TextResourceContents { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/TextResourceContents$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/TextResourceContents; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/TextResourceContents;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/TextResourceContents; - public fun equals (Ljava/lang/Object;)Z - public final fun getMimeType ()Ljava/lang/String; - public final fun getText ()Ljava/lang/String; - public final fun getUri ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/TextResourceContents$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/TextResourceContents$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/TextResourceContents; - 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/models/TextResourceContents;)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/models/TextResourceContents$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/ToolAnnotations { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ToolAnnotations$Companion; - public fun ()V - public fun (Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;)V - public synthetic fun (Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/Boolean; - public final fun component2 ()Ljava/lang/Boolean; - public final fun component3 ()Ljava/lang/Boolean; - public final fun component4 ()Ljava/lang/Boolean; - public final fun component5 ()Ljava/lang/String; - public final fun copy (Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/ToolAnnotations; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ToolAnnotations;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ToolAnnotations; - public fun equals (Ljava/lang/Object;)Z - public final fun getDestructiveHint ()Ljava/lang/Boolean; - public final fun getIdempotentHint ()Ljava/lang/Boolean; - public final fun getOpenWorldHint ()Ljava/lang/Boolean; - public final fun getReadOnlyHint ()Ljava/lang/Boolean; - public final fun getTitle ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ToolAnnotations$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ToolAnnotations$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ToolAnnotations; - 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/models/ToolAnnotations;)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/models/ToolAnnotations$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/ToolListChangedNotification { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/ToolListChangedNotification$Companion; - public fun (Ljava/lang/String;Ljava/util/Map;)V - public synthetic fun (Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/util/Map; - public final fun copy (Ljava/lang/String;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/models/ToolListChangedNotification; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/ToolListChangedNotification;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/ToolListChangedNotification; - public fun equals (Ljava/lang/Object;)Z - public final fun getMethod ()Ljava/lang/String; - public final fun getParams ()Ljava/util/Map; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/ToolListChangedNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/ToolListChangedNotification$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/ToolListChangedNotification; - 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/models/ToolListChangedNotification;)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/models/ToolListChangedNotification$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequest { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequest$Companion; - public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequestParams;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequestParams; - public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequest; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequest;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequest; - public fun equals (Ljava/lang/Object;)Z - public final fun getMethod ()Ljava/lang/String; - public final fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequestParams; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequest; - 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/models/UnsubscribeRequest;)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/models/UnsubscribeRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequestParams { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequestParams$Companion; - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequestParams; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequestParams;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequestParams; - public fun equals (Ljava/lang/Object;)Z - public final fun getUri ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequestParams$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/UnsubscribeRequestParams; - 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/models/UnsubscribeRequestParams;)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/models/UnsubscribeRequestParams$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray$Companion; - public fun ([B)V - public fun equals (Ljava/lang/Object;)Z - public final fun getValue ()[B - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray$Companion : kotlinx/serialization/KSerializer { - public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/models/infrastructure/Base64ByteArray;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - public abstract class io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport : io/modelcontextprotocol/kotlin/sdk/shared/Transport { public fun ()V protected final fun get_onClose ()Lkotlin/jvm/functions/Function0; diff --git a/kotlin-sdk-core/build.gradle.kts b/kotlin-sdk-core/build.gradle.kts index df7f242..d337724 100644 --- a/kotlin-sdk-core/build.gradle.kts +++ b/kotlin-sdk-core/build.gradle.kts @@ -1,8 +1,6 @@ @file:OptIn(ExperimentalWasmDsl::class) -import org.jetbrains.dokka.gradle.tasks.DokkaGenerateTask import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl -import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask plugins { id("mcp.multiplatform") @@ -12,6 +10,7 @@ plugins { id("org.openapi.generator") version "7.15.0" } +/* tasks.withType>().configureEach { dependsOn(tasks.openApiGenerate) } @@ -30,6 +29,7 @@ tasks.withType().configureEach { dependsOn(tasks.openApiGenerate) } } + */ openApiGenerate { val schemaVersion = "2025-03-26" // or "2025-06-18" or "draft" @@ -38,6 +38,7 @@ openApiGenerate { "https://raw.githubusercontent.com/modelcontextprotocol/modelcontextprotocol/refs/tags/$schemaVersion/schema/$schemaVersion/schema.json" generatorName = "kotlin" remoteInputSpec = schemaUrl + outputDir = layout.buildDirectory.dir("generated-sources/openapi").get().asFile.absolutePath packageName = "io.modelcontextprotocol.kotlin.sdk.models" modelPackage = "io.modelcontextprotocol.kotlin.sdk.models" apiPackage = "io.modelcontextprotocol.kotlin.sdk.api" @@ -108,7 +109,6 @@ kotlin { sourceSets { commonMain { kotlin.srcDir(generateLibVersion) - kotlin.srcDir("build/generate-resources/main/src/commonMain/kotlin") dependencies { api(libs.kotlinx.serialization.json) api(libs.kotlinx.coroutines.core) From 2f1b659efd3ab10578cfecb9ffbfe31d46bba217 Mon Sep 17 00:00:00 2001 From: Jeff Clyne Date: Fri, 3 Oct 2025 11:12:37 -0500 Subject: [PATCH 008/449] Adds 'instructions' string to the Server and propagates to the InitializeResult (#290) ## Motivation and Context The [schema](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/cca15204f62143bc48429ece62b6e35b06d9785b/schema/2025-03-26/schema.ts#L192) for the InitializeResult contains an optional 'instructions' string ## Additional context The instructions string is included in the schema for for version "2025-03-26" The instructions in the Server class are given as a provider function with the result of the provider propagated to the underlying ServerSession. As the ServerSession change is meant to allow for a 'Singleton scoped' server class, this provides the ability to dynamically modify the instructions at the server level but have them be immutable per session. This also refactors the `serverInfo` and `instructions` properties to be protected. This `Server` class is open and meant to be overridden, there is no harm in having these immutable properties available to subclasses. This adds the serverInstructions to the client as returned from the InitializeResult. This is used to verify the response in the Server tests. --- README.md | 16 +++-- kotlin-sdk-client/api/kotlin-sdk-client.api | 1 + .../kotlin/sdk/client/Client.kt | 9 +++ kotlin-sdk-core/api/kotlin-sdk-core.api | 12 ++-- .../modelcontextprotocol/kotlin/sdk/types.kt | 4 ++ .../kotlin/sdk/TypesTest.kt | 59 +++++++++++++++++++ kotlin-sdk-server/api/kotlin-sdk-server.api | 11 +++- .../kotlin/sdk/server/Server.kt | 24 +++++++- .../kotlin/sdk/server/ServerSession.kt | 7 ++- .../kotlin/sdk/server/ServerTest.kt | 56 ++++++++++++++++++ 10 files changed, 184 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 4fd0609..34cd83f 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,9 @@ val server = Server( ) ) ) -) +){ + "This server provides example resources and demonstrates MCP capabilities." +} // Add a resource server.addResource( @@ -156,8 +158,10 @@ fun Application.module() { prompts = ServerCapabilities.Prompts(listChanged = null), resources = ServerCapabilities.Resources(subscribe = null, listChanged = null) ) - ) - ) + ), + ) { + "This SSE server provides prompts and resources via Server-Sent Events." + } } } ``` @@ -184,8 +188,10 @@ fun Application.module() { prompts = ServerCapabilities.Prompts(listChanged = null), resources = ServerCapabilities.Resources(subscribe = null, listChanged = null) ) - ) - ) + ), + ) { + "Connect via SSE to interact with this MCP server." + } } } } diff --git a/kotlin-sdk-client/api/kotlin-sdk-client.api b/kotlin-sdk-client/api/kotlin-sdk-client.api index f0782da..a785a91 100644 --- a/kotlin-sdk-client/api/kotlin-sdk-client.api +++ b/kotlin-sdk-client/api/kotlin-sdk-client.api @@ -17,6 +17,7 @@ public class io/modelcontextprotocol/kotlin/sdk/client/Client : io/modelcontextp public final fun getPrompt (Lio/modelcontextprotocol/kotlin/sdk/GetPromptRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun getPrompt$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/GetPromptRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun getServerCapabilities ()Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities; + public final fun getServerInstructions ()Ljava/lang/String; public final fun getServerVersion ()Lio/modelcontextprotocol/kotlin/sdk/Implementation; public final fun listPrompts (Lio/modelcontextprotocol/kotlin/sdk/ListPromptsRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun listPrompts$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/ListPromptsRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt index e21696b..95a5bc5 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt @@ -91,6 +91,14 @@ public open class Client(private val clientInfo: Implementation, options: Client public var serverCapabilities: ServerCapabilities? = null private set + /** + * Optional human-readable instructions or description from the server. + * + * @return Instructions provided by the server, or `null` if none were given or initialization is not yet complete. + */ + public var serverInstructions: String? = null + private set + /** * Retrieves the server's reported version information after initialization. * @@ -154,6 +162,7 @@ public open class Client(private val clientInfo: Implementation, options: Client serverCapabilities = result.capabilities serverVersion = result.serverInfo + serverInstructions = result.instructions notification(InitializedNotification()) } catch (error: Throwable) { diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index ed9a268..d4d4b9d 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -975,16 +975,18 @@ public final class io/modelcontextprotocol/kotlin/sdk/InitializeRequest$Companio public final class io/modelcontextprotocol/kotlin/sdk/InitializeResult : io/modelcontextprotocol/kotlin/sdk/ServerResult { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/InitializeResult$Companion; - public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities;Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities;Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities;Lio/modelcontextprotocol/kotlin/sdk/Implementation;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities;Lio/modelcontextprotocol/kotlin/sdk/Implementation;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities; public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/Implementation; - public final fun component4 ()Lkotlinx/serialization/json/JsonObject; - public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities;Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/InitializeResult; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/InitializeResult;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities;Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/InitializeResult; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities;Lio/modelcontextprotocol/kotlin/sdk/Implementation;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/InitializeResult; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/InitializeResult;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities;Lio/modelcontextprotocol/kotlin/sdk/Implementation;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/InitializeResult; public fun equals (Ljava/lang/Object;)Z public final fun getCapabilities ()Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities; + public final fun getInstructions ()Ljava/lang/String; public final fun getProtocolVersion ()Ljava/lang/String; public final fun getServerInfo ()Lio/modelcontextprotocol/kotlin/sdk/Implementation; public fun get_meta ()Lkotlinx/serialization/json/JsonObject; diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt index b687924..927967a 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt @@ -512,6 +512,10 @@ public data class InitializeResult( val protocolVersion: String = LATEST_PROTOCOL_VERSION, val capabilities: ServerCapabilities = ServerCapabilities(), val serverInfo: Implementation, + /** + * Optional instructions from the server to the client about how to use this server. + */ + val instructions: String? = null, override val _meta: JsonObject = EmptyJsonObject, ) : ServerResult diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/TypesTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/TypesTest.kt index 23e2ffb..7a4938a 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/TypesTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/TypesTest.kt @@ -394,4 +394,63 @@ class TypesTest { assertEquals("id", request.argument.name) assertEquals("123", request.argument.value) } + + // InitializeResult Tests + @Test + fun `should create InitializeResult with default instructions`() { + val serverInfo = Implementation(name = "test-server", version = "1.0.0") + val result = InitializeResult( + serverInfo = serverInfo, + ) + + assertEquals(LATEST_PROTOCOL_VERSION, result.protocolVersion) + assertEquals(serverInfo, result.serverInfo) + assertEquals(null, result.instructions) + } + + @Test + fun `should create InitializeResult with custom instructions`() { + val serverInfo = Implementation(name = "test-server", version = "1.0.0") + val instructions = "Use this server to perform calculations. Call the 'add' tool to add numbers." + val result = InitializeResult( + serverInfo = serverInfo, + instructions = instructions, + ) + + assertEquals(LATEST_PROTOCOL_VERSION, result.protocolVersion) + assertEquals(serverInfo, result.serverInfo) + assertEquals(instructions, result.instructions) + } + + @Test + fun `should serialize and deserialize InitializeResult with instructions`() { + val serverInfo = Implementation(name = "test-server", version = "1.0.0") + val instructions = "This server provides file system access. Use the 'read' tool to read files." + val result = InitializeResult( + serverInfo = serverInfo, + instructions = instructions, + ) + + val json = McpJson.encodeToString(result) + val decoded = McpJson.decodeFromString(json) + + assertEquals(LATEST_PROTOCOL_VERSION, decoded.protocolVersion) + assertEquals(serverInfo, decoded.serverInfo) + assertEquals(instructions, decoded.instructions) + } + + @Test + fun `should serialize and deserialize InitializeResult without instructions`() { + val serverInfo = Implementation(name = "test-server", version = "1.0.0") + val result = InitializeResult( + serverInfo = serverInfo, + ) + + val json = McpJson.encodeToString(result) + val decoded = McpJson.decodeFromString(json) + + assertEquals(LATEST_PROTOCOL_VERSION, decoded.protocolVersion) + assertEquals(serverInfo, decoded.serverInfo) + assertEquals(null, decoded.instructions) + } } diff --git a/kotlin-sdk-server/api/kotlin-sdk-server.api b/kotlin-sdk-server/api/kotlin-sdk-server.api index 56e524a..8ee5af2 100644 --- a/kotlin-sdk-server/api/kotlin-sdk-server.api +++ b/kotlin-sdk-server/api/kotlin-sdk-server.api @@ -45,7 +45,9 @@ public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredTool { } public class io/modelcontextprotocol/kotlin/sdk/server/Server { - public fun (Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;)V + public fun (Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Ljava/lang/String;)V + public fun (Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Lkotlin/jvm/functions/Function0;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Lkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun addPrompt (Lio/modelcontextprotocol/kotlin/sdk/Prompt;Lkotlin/jvm/functions/Function2;)V public final fun addPrompt (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lkotlin/jvm/functions/Function2;)V public static synthetic fun addPrompt$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V @@ -59,8 +61,11 @@ public class io/modelcontextprotocol/kotlin/sdk/server/Server { public final fun addTools (Ljava/util/List;)V public final fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun connect (Lio/modelcontextprotocol/kotlin/sdk/shared/Transport;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + protected final fun getInstructionsProvider ()Lkotlin/jvm/functions/Function0; + protected final fun getOptions ()Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions; public final fun getPrompts ()Ljava/util/Map; public final fun getResources ()Ljava/util/Map; + protected final fun getServerInfo ()Lio/modelcontextprotocol/kotlin/sdk/Implementation; public final fun getTools ()Ljava/util/Map; public final fun onClose (Lkotlin/jvm/functions/Function0;)V public final fun onConnect (Lkotlin/jvm/functions/Function0;)V @@ -80,7 +85,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/server/ServerOptions : io/ } public class io/modelcontextprotocol/kotlin/sdk/server/ServerSession : io/modelcontextprotocol/kotlin/sdk/shared/Protocol { - public fun (Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;)V + public fun (Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Ljava/lang/String;)V protected fun assertCapabilityForMethod (Lio/modelcontextprotocol/kotlin/sdk/Method;)V protected fun assertNotificationCapability (Lio/modelcontextprotocol/kotlin/sdk/Method;)V public fun assertRequestHandlerCapability (Lio/modelcontextprotocol/kotlin/sdk/Method;)V @@ -90,6 +95,8 @@ public class io/modelcontextprotocol/kotlin/sdk/server/ServerSession : io/modelc public static synthetic fun createMessage$default (Lio/modelcontextprotocol/kotlin/sdk/server/ServerSession;Lio/modelcontextprotocol/kotlin/sdk/CreateMessageRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun getClientCapabilities ()Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities; public final fun getClientVersion ()Lio/modelcontextprotocol/kotlin/sdk/Implementation; + protected final fun getInstructions ()Ljava/lang/String; + protected final fun getServerInfo ()Lio/modelcontextprotocol/kotlin/sdk/Implementation; public final fun listRoots (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun listRoots$default (Lio/modelcontextprotocol/kotlin/sdk/server/ServerSession;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public fun onClose ()V diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt index 3e5aad8..18283e4 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt @@ -53,8 +53,28 @@ public class ServerOptions(public val capabilities: ServerCapabilities, enforceS * * @param serverInfo Information about this server implementation (name, version). * @param options Configuration options for the server. + * @param instructionsProvider Optional provider for instructions from the server to the client about how to use + * this server. The provider is called each time a new session is started to support dynamic instructions. */ -public open class Server(private val serverInfo: Implementation, private val options: ServerOptions) { + +public open class Server( + protected val serverInfo: Implementation, + protected val options: ServerOptions, + protected val instructionsProvider: (() -> String)? = null, +) { + /** + * Alternative constructor that provides the instructions directly as a string. + * + * @param serverInfo Information about this server implementation (name, version). + * @param options Configuration options for the server. + * @param instructions Instructions from the server to the client about how to use this server. + */ + public constructor( + serverInfo: Implementation, + options: ServerOptions, + instructions: String, + ) : this(serverInfo, options, { instructions }) + private val sessions = atomic(persistentListOf()) @Suppress("ktlint:standard:backing-property-naming") @@ -90,7 +110,7 @@ public open class Server(private val serverInfo: Implementation, private val opt * @return The initialized and connected server session. */ public suspend fun connect(transport: Transport): ServerSession { - val session = ServerSession(serverInfo, options) + val session = ServerSession(serverInfo, options, instructionsProvider?.invoke()) // Internal handlers for tools if (options.capabilities.tools != null) { diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt index 471ebf0..18cbf08 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt @@ -32,7 +32,11 @@ import kotlinx.serialization.json.JsonObject private val logger = KotlinLogging.logger {} -public open class ServerSession(private val serverInfo: Implementation, options: ServerOptions) : Protocol(options) { +public open class ServerSession( + protected val serverInfo: Implementation, + options: ServerOptions, + protected val instructions: String?, +) : Protocol(options) { @Suppress("ktlint:standard:backing-property-naming") private var _onInitialized: (() -> Unit) = {} @@ -366,6 +370,7 @@ public open class ServerSession(private val serverInfo: Implementation, options: protocolVersion = protocolVersion, capabilities = serverCapabilities, serverInfo = serverInfo, + instructions = instructions, ) } } diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerTest.kt index 77175b4..088444d 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerTest.kt @@ -19,6 +19,7 @@ import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertNull import org.junit.jupiter.api.assertThrows import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -471,4 +472,59 @@ class ServerTest { } assertEquals("Server does not support resources capability.", exception.message) } + + @Test + fun `Server constructor should accept instructions provider parameter`() = runTest { + val serverInfo = Implementation(name = "test server", version = "1.0") + val serverOptions = ServerOptions(capabilities = ServerCapabilities()) + val instructions = "This is a test server. Use it for testing purposes only." + + val server = Server(serverInfo, serverOptions, { instructions }) + + // 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.connect(serverTransport) + client.connect(clientTransport) + + assertEquals(instructions, client.serverInstructions) + } + + @Test + fun `Server constructor should accept instructions parameter`() = runTest { + val serverInfo = Implementation(name = "test server", version = "1.0") + val serverOptions = ServerOptions(capabilities = ServerCapabilities()) + val instructions = "This is a test server. Use it for testing purposes only." + + val server = Server(serverInfo, serverOptions, instructions) + + // 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.connect(serverTransport) + client.connect(clientTransport) + + assertEquals(instructions, client.serverInstructions) + } + + @Test + fun `Server constructor should work without instructions parameter`() = runTest { + val serverInfo = Implementation(name = "test server", version = "1.0") + val serverOptions = ServerOptions(capabilities = ServerCapabilities()) + + // Test that server works when instructions parameter is omitted (defaults to null) + val server = Server(serverInfo, serverOptions) + + val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() + val client = Client(clientInfo = Implementation(name = "test client", version = "1.0")) + + server.connect(serverTransport) + client.connect(clientTransport) + + assertNull(client.serverInstructions) + } } From 14e86b2a11fc718fef2afc1bf7cf8b628e61d3e5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 13:17:24 +0300 Subject: [PATCH 009/449] Bump gradle/actions from 4 to 5 (#306) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [gradle/actions](https://github.com/gradle/actions) from 4 to 5.
Release notes

Sourced from gradle/actions's releases.

v5.0.0

What's Changed

Breaking Changes

Make sure your runner is updated to this version or newer to use this release. v2.327.1 Release Notes

Dependency upgrades

Full Changelog: https://github.com/gradle/actions/compare/v4...v5.0.0

v4.4.4

What's Changed

Full Changelog: https://github.com/gradle/actions/compare/v4...v4.4.4

v4.4.3

What's Changed

... (truncated)

Commits
  • 4d9f0ba Bump the github-actions group across 1 directory with 2 updates (#748)
  • 4b530e3 Bump the github-actions group across 1 directory with 2 updates
  • e60655a Upgrade to node 24 (#721)
  • See full diff in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=gradle/actions&package-manager=github_actions&previous-version=4&new-version=5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/apidocs.yaml | 2 +- .github/workflows/build.yml | 2 +- .github/workflows/gradle-publish.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/apidocs.yaml b/.github/workflows/apidocs.yaml index 60a6e29..c2b86e8 100644 --- a/.github/workflows/apidocs.yaml +++ b/.github/workflows/apidocs.yaml @@ -33,7 +33,7 @@ jobs: cache: gradle - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 + uses: gradle/actions/setup-gradle@v5 - name: Generate Dokka Site run: |- diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 20b9a7f..e2d2869 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,7 +28,7 @@ jobs: distribution: 'temurin' - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 + uses: gradle/actions/setup-gradle@v5 with: add-job-summary: 'always' cache-read-only: true diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml index 53dde06..bdc64fa 100644 --- a/.github/workflows/gradle-publish.yml +++ b/.github/workflows/gradle-publish.yml @@ -31,7 +31,7 @@ jobs: distribution: 'temurin' - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 + uses: gradle/actions/setup-gradle@v5 - name: Clean Build with Gradle run: ./gradlew clean build From dcb4f21e9acd62e85d27a2b44467eff4f1600ce3 Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Wed, 8 Oct 2025 01:59:04 -0700 Subject: [PATCH 010/449] Change sample server host from 0.0.0.0 to 127.0.0.1 (#307) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Don't bind the sample server to `0.0.0.0` by default as that exposes the server to anyone else on the local network. If anyone were to use these samples as staring points to build larger services, those servers would thus expose developers to attack immediately upon launch. I'd prefer if we avoided that 😆 ## Motivation and Context 🔒 Security ## How Has This Been Tested? I haven't checked out the code locally to verify it works. All the editing to the code was done on GitHub.com ## Breaking Changes No. ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update ## Checklist - [ ] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [ ] My code follows the repository's style guidelines - [ ] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed ## Additional context --- samples/kotlin-mcp-server/src/commonMain/kotlin/server.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/kotlin-mcp-server/src/commonMain/kotlin/server.kt b/samples/kotlin-mcp-server/src/commonMain/kotlin/server.kt index f48e5ab..227c83d 100644 --- a/samples/kotlin-mcp-server/src/commonMain/kotlin/server.kt +++ b/samples/kotlin-mcp-server/src/commonMain/kotlin/server.kt @@ -101,7 +101,7 @@ suspend fun runSseMcpServerWithPlainConfiguration(port: Int) { val server = configureServer() - embeddedServer(CIO, host = "0.0.0.0", port = port) { + embeddedServer(CIO, host = "127.0.0.1", port = port) { install(SSE) routing { sse("/sse") { @@ -145,7 +145,7 @@ suspend fun runSseMcpServerUsingKtorPlugin(port: Int) { println("Starting sse server on port $port") println("Use inspector to connect to the http://localhost:$port/sse") - embeddedServer(CIO, host = "0.0.0.0", port = port) { + embeddedServer(CIO, host = "127.0.0.1", port = port) { mcp { return@mcp configureServer() } From 3d82883375bf9ffec2e2b76c626b21bdf91f089a Mon Sep 17 00:00:00 2001 From: Jeff Clyne Date: Sat, 11 Oct 2025 01:25:01 -0500 Subject: [PATCH 011/449] Compatibility updates for gradle 9.x.x (#313) Changes to kotlin-sdk-core/build.gradle.kts to be compatible with gradle 9.x.x ## Motivation and Context If including this project into a composite build that uses gradle 9.x.x, recent changes to the build.gradle.kts are not compatible. ## How Has This Been Tested? The build still works with gradle 8.x ## Breaking Changes N/A ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed ## Additional context --- kotlin-sdk-core/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kotlin-sdk-core/build.gradle.kts b/kotlin-sdk-core/build.gradle.kts index d337724..4ee21c1 100644 --- a/kotlin-sdk-core/build.gradle.kts +++ b/kotlin-sdk-core/build.gradle.kts @@ -46,8 +46,8 @@ openApiGenerate { generateModelDocumentation = false cleanupOutput = false skipValidateSpec = true // do not validate spec - library = "multiplatform" - ignoreFileOverride = "${layout.projectDirectory}/.openapi-generator-ignore" + library.set("multiplatform") + ignoreFileOverride.set("${layout.projectDirectory}/.openapi-generator-ignore") globalProperties.set( mapOf( "supportingFiles" to "", From 2f1cd523695e59b9a432ba84b395ea0478de4b91 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 22:34:18 +0300 Subject: [PATCH 012/449] Bump github/codeql-action from 3 to 4 (#317) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4.
Release notes

Sourced from github/codeql-action's releases.

v3.30.8

CodeQL Action Changelog

See the releases page for the relevant changes to the CodeQL CLI and language packs.

3.30.8 - 10 Oct 2025

No user facing changes.

See the full CHANGELOG.md for more information.

v3.30.7

CodeQL Action Changelog

See the releases page for the relevant changes to the CodeQL CLI and language packs.

3.30.7 - 06 Oct 2025

No user facing changes.

See the full CHANGELOG.md for more information.

v3.30.6

CodeQL Action Changelog

See the releases page for the relevant changes to the CodeQL CLI and language packs.

3.30.6 - 02 Oct 2025

  • Update default CodeQL bundle version to 2.23.2. #3168

See the full CHANGELOG.md for more information.

v3.30.5

CodeQL Action Changelog

See the releases page for the relevant changes to the CodeQL CLI and language packs.

3.30.5 - 26 Sep 2025

  • We fixed a bug that was introduced in 3.30.4 with upload-sarif which resulted in files without a .sarif extension not getting uploaded. #3160

See the full CHANGELOG.md for more information.

v3.30.4

CodeQL Action Changelog

See the releases page for the relevant changes to the CodeQL CLI and language packs.

3.30.4 - 25 Sep 2025

... (truncated)

Changelog

Sourced from github/codeql-action's changelog.

3.29.4 - 23 Jul 2025

No user facing changes.

3.29.3 - 21 Jul 2025

No user facing changes.

3.29.2 - 30 Jun 2025

  • Experimental: When the quality-queries input for the init action is provided with an argument, separate .quality.sarif files are produced and uploaded for each language with the results of the specified queries. Do not use this in production as it is part of an internal experiment and subject to change at any time. #2935

3.29.1 - 27 Jun 2025

  • Fix bug in PR analysis where user-provided include query filter fails to exclude non-included queries. #2938
  • Update default CodeQL bundle version to 2.22.1. #2950

3.29.0 - 11 Jun 2025

  • Update default CodeQL bundle version to 2.22.0. #2925
  • Bump minimum CodeQL bundle version to 2.16.6. #2912

3.28.21 - 28 July 2025

No user facing changes.

3.28.20 - 21 July 2025

3.28.19 - 03 Jun 2025

  • The CodeQL Action no longer includes its own copy of the extractor for the actions language, which is currently in public preview. The actions extractor has been included in the CodeQL CLI since v2.20.6. If your workflow has enabled the actions language and you have pinned your tools: property to a specific version of the CodeQL CLI earlier than v2.20.6, you will need to update to at least CodeQL v2.20.6 or disable actions analysis.
  • Update default CodeQL bundle version to 2.21.4. #2910

3.28.18 - 16 May 2025

  • Update default CodeQL bundle version to 2.21.3. #2893
  • Skip validating SARIF produced by CodeQL for improved performance. #2894
  • The number of threads and amount of RAM used by CodeQL can now be set via the CODEQL_THREADS and CODEQL_RAM runner environment variables. If set, these environment variables override the threads and ram inputs respectively. #2891

3.28.17 - 02 May 2025

  • Update default CodeQL bundle version to 2.21.2. #2872

3.28.16 - 23 Apr 2025

... (truncated)

Commits
  • a841c54 Scratch uploadSpecifiedFiles tests, make uploadPayload tests instead
  • aeb12f6 Merge branch 'main' into redsun82/skip-sarif-upload-tests
  • 6fd4ceb Merge pull request #3189 from github/henrymercer/download-codeql-rate-limit
  • 196a3e5 Merge pull request #3188 from github/mbg/telemetry/partial-config
  • 98abb87 Add configuration error for rate limited CodeQL download
  • bdd2cdf Also include language in error status report for start-proxy, if available
  • fb14878 Include languages in start-proxy telemetry
  • 2ff418f Parse language before calling getCredentials
  • See full diff in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github/codeql-action&package-manager=github_actions&previous-version=3&new-version=4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 63c798f..329aa59 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -40,7 +40,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} build-mode: manual @@ -64,6 +64,6 @@ jobs: --no-daemon --stacktrace --rerun-tasks - name: Analyze - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 with: category: '/language:${{ matrix.language }}' From 3b56429126c8796d2ab5fd8ebdc8402b9c29c112 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 22:47:35 +0300 Subject: [PATCH 013/449] Bump org.openapi.generator from 7.15.0 to 7.16.0 (#305) Bumps org.openapi.generator from 7.15.0 to 7.16.0. [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.openapi.generator&package-manager=gradle&previous-version=7.15.0&new-version=7.16.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> --- kotlin-sdk-core/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlin-sdk-core/build.gradle.kts b/kotlin-sdk-core/build.gradle.kts index 4ee21c1..68e5618 100644 --- a/kotlin-sdk-core/build.gradle.kts +++ b/kotlin-sdk-core/build.gradle.kts @@ -7,7 +7,7 @@ plugins { id("mcp.publishing") id("mcp.dokka") alias(libs.plugins.kotlinx.binary.compatibility.validator) - id("org.openapi.generator") version "7.15.0" + id("org.openapi.generator") version "7.16.0" } /* From d60e8b6fb24a57945df5f9508b12a7ab0d497ee5 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Fri, 17 Oct 2025 16:03:52 +0300 Subject: [PATCH 014/449] Add `StreamableHttpClientTest` with test infrastructure setup and dependencies (#316) Add `StreamableHttpClientTest` with test infrastructure setup and dependencies ## Motivation and Context To test Streaming HTTP Transport ## How Has This Been Tested? Integration test added ## Breaking Changes No, only tests ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [x] Tests ## 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 ## Additional context --- .github/workflows/build.yml | 11 +- .../main/kotlin/mcp.multiplatform.gradle.kts | 49 ++++- .../main/kotlin/netty-convention.gradle.kts | 61 ++++++ gradle/libs.versions.toml | 9 + kotlin-sdk-client/build.gradle.kts | 12 ++ .../client/StreamableHttpClientTransport.kt | 3 +- .../kotlin/sdk/client/MockMcp.kt | 89 ++++++++ .../sdk/client/StreamableHttpClientTest.kt | 202 ++++++++++++++++++ .../resources/junit-platform.properties | 6 + .../jvmTest/resources/simplelogger.properties | 14 ++ kotlin-sdk-test/build.gradle.kts | 2 +- 11 files changed, 447 insertions(+), 11 deletions(-) create mode 100644 buildSrc/src/main/kotlin/netty-convention.gradle.kts create mode 100644 kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt create mode 100644 kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt create mode 100644 kotlin-sdk-client/src/jvmTest/resources/junit-platform.properties create mode 100644 kotlin-sdk-client/src/jvmTest/resources/simplelogger.properties diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e2d2869..5c722e1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,6 +16,7 @@ jobs: validate-pr: runs-on: macos-latest-xlarge name: Validate PR + timeout-minutes: 20 env: JAVA_OPTS: "-Xmx8g -Dfile.encoding=UTF-8 -Djava.awt.headless=true -Dkotlin.daemon.jvm.options=-Xmx6g" steps: @@ -35,20 +36,20 @@ jobs: - name: Build with Gradle run: |- - ./gradlew clean ktlintCheck build koverLog koverHtmlReport - ./gradlew :kotlin-sdk-core:publishToMavenLocal :kotlin-sdk-client:publishToMavenLocal :kotlin-sdk-server:publishToMavenLocal + ./gradlew --no-daemon clean ktlintCheck build koverLog koverHtmlReport + ./gradlew --no-daemon :kotlin-sdk-core:publishToMavenLocal :kotlin-sdk-client:publishToMavenLocal :kotlin-sdk-server:publishToMavenLocal - name: Build Kotlin-MCP-Client Sample working-directory: ./samples/kotlin-mcp-client - run: ./../../gradlew clean build + run: ./../../gradlew --no-daemon clean build - name: Build Kotlin-MCP-Server Sample working-directory: ./samples/kotlin-mcp-server - run: ./../../gradlew clean build + run: ./../../gradlew --no-daemon clean build - name: Build Weather-Stdio-Server Sample working-directory: ./samples/weather-stdio-server - run: ./../../gradlew clean build + run: ./../../gradlew --no-daemon clean build - name: Upload Reports if: ${{ !cancelled() }} diff --git a/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts b/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts index e115716..0a57e98 100644 --- a/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts +++ b/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts @@ -1,7 +1,9 @@ @file:OptIn(ExperimentalWasmDsl::class) +import org.gradle.api.tasks.testing.logging.TestExceptionFormat import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode +import org.jetbrains.kotlin.gradle.dsl.JvmDefaultMode import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { @@ -11,11 +13,50 @@ plugins { } kotlin { - jvm { - compilerOptions.jvmTarget = JvmTarget.JVM_1_8 + + compilerOptions { + freeCompilerArgs = + listOf( + "-Wextra", + "-Xmulti-dollar-interpolation", + ) } - macosX64(); macosArm64() - linuxX64(); linuxArm64() + + // coreLibrariesVersion = "2.0.10" + + jvm { + compilerOptions { + jvmTarget = JvmTarget.JVM_1_8 + javaParameters = true + jvmDefault = JvmDefaultMode.ENABLE + freeCompilerArgs.addAll( + "-Xdebug", + ) + } + + tasks.withType().configureEach { + + useJUnitPlatform() + + maxParallelForks = Runtime.getRuntime().availableProcessors() + forkEvery = 100 + testLogging { + exceptionFormat = TestExceptionFormat.SHORT + showStandardStreams = true + events("failed") + } + systemProperty("kotest.output.ansi", "true") + reports { + junitXml.required.set(true) + junitXml.includeSystemOutLog.set(true) + junitXml.includeSystemErrLog.set(true) + } + } + } + macosX64() + macosArm64() + linuxX64() + linuxArm64() mingwX64() js { nodejs() } wasmJs { nodejs() } diff --git a/buildSrc/src/main/kotlin/netty-convention.gradle.kts b/buildSrc/src/main/kotlin/netty-convention.gradle.kts new file mode 100644 index 0000000..920baf0 --- /dev/null +++ b/buildSrc/src/main/kotlin/netty-convention.gradle.kts @@ -0,0 +1,61 @@ +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension + +/** + * Netty convention plugin that adds platform-specific Netty native transport libraries + * to the jvmTest source set. This is similar to how Maven handles OS-specific dependencies + * with profile activation. + * + * This plugin should be applied to any module that uses Netty for testing. + */ +val nettyVersion = "4.2.7.Final" +plugins { + id("org.gradle.base") +} + +// This plugin is applied to projects that already have the kotlin multiplatform plugin applied +// It adds the Netty native transport libraries to the jvmTest source set + +afterEvaluate { + + extensions.findByType()?.apply { + sourceSets.findByName("jvmTest")?.apply { + dependencies { + // Netty native transport libraries for different platforms + val osName = System.getProperty("os.name").lowercase() + val osArch = System.getProperty("os.arch").lowercase() + + // Add the base Netty platform + implementation(project.dependencies.platform("io.netty:netty-bom:$nettyVersion")) + + when { + osName.contains("linux") -> { + val archClassifier = + if (osArch.contains("aarch64")) { + "linux-aarch_64" + } else { + "linux-x86_64" + } + runtimeOnly( + "io.netty:netty-transport-native-epoll:$nettyVersion:$archClassifier", + ) + } + + osName.contains("mac") -> { + val archClassifier = + if (osArch.contains("aarch64")) { + "osx-aarch_64" + } else { + "osx-x86_64" + } + runtimeOnly( + "io.netty:netty-transport-native-kqueue:$nettyVersion:$archClassifier", + ) + runtimeOnly( + "io.netty:netty-resolver-dns-native-macos:$nettyVersion:$archClassifier", + ) + } + } + } + } + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5bb80c8..86886f7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,6 +5,7 @@ dokka = "2.0.0" atomicfu = "0.29.0" ktlint = "13.1.0" kover = "0.9.2" +netty = "4.2.7.Final" mavenPublish = "0.34.0" binaryCompatibilityValidatorPlugin = "0.18.1" @@ -18,6 +19,7 @@ logging = "7.0.13" slf4j = "2.0.17" kotest = "6.0.3" awaitility = "4.3.0" +mokksy = "0.6.1" # Samples mcp-kotlin = "0.7.2" @@ -41,8 +43,13 @@ kotlinx-collections-immutable = { group = "org.jetbrains.kotlinx", name = "kotli kotlin-logging = { group = "io.github.oshai", name = "kotlin-logging", version.ref = "logging" } # Ktor +ktor-bom = { group = "io.ktor", name = "ktor-bom", version.ref = "ktor" } ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" } +ktor-client-logging = { group = "io.ktor", name = "ktor-client-logging"} +ktor-client-apache5 = { group = "io.ktor", name = "ktor-client-apache5" } +ktor-client-okhttp = { group = "io.ktor", name = "ktor-client-okhttp" } ktor-server-sse = { group = "io.ktor", name = "ktor-server-sse", version.ref = "ktor" } +ktor-server-netty = { group = "io.ktor", name = "ktor-server-netty" } ktor-server-websockets = { group = "io.ktor", name = "ktor-server-websockets", version.ref = "ktor" } ktor-server-core = { group = "io.ktor", name = "ktor-server-core", version.ref = "ktor" } @@ -53,7 +60,9 @@ kotest-assertions-json = { group = "io.kotest", name = "kotest-assertions-json", kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines" } ktor-client-mock = { group = "io.ktor", name = "ktor-client-mock", version.ref = "ktor" } ktor-server-test-host = { group = "io.ktor", name = "ktor-server-test-host", version.ref = "ktor" } +mokksy = { group = "dev.mokksy", name = "mokksy", version.ref = "mokksy" } slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" } +netty-bom = { group = "io.netty", name = "netty-bom", version.ref = "netty" } # Samples ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio", version.ref = "ktor" } diff --git a/kotlin-sdk-client/build.gradle.kts b/kotlin-sdk-client/build.gradle.kts index 7ba3de2..7db177c 100644 --- a/kotlin-sdk-client/build.gradle.kts +++ b/kotlin-sdk-client/build.gradle.kts @@ -7,6 +7,7 @@ plugins { id("mcp.publishing") id("mcp.dokka") alias(libs.plugins.kotlinx.binary.compatibility.validator) + `netty-convention` } kotlin { @@ -40,8 +41,19 @@ kotlin { commonTest { dependencies { implementation(kotlin("test")) + implementation(dependencies.platform(libs.ktor.bom)) implementation(libs.ktor.client.mock) implementation(libs.kotlinx.coroutines.test) + implementation(libs.ktor.client.logging) + } + } + + jvmTest { + dependencies { + implementation(libs.mokksy) + implementation(libs.awaitility) + implementation(libs.ktor.client.apache5) + implementation(dependencies.platform(libs.netty.bom)) runtimeOnly(libs.slf4j.simple) } } diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt index 50f3a51..71a89f6 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt @@ -228,7 +228,8 @@ public class StreamableHttpClientTransport( ) { method = HttpMethod.Get applyCommonHeaders(this) - accept(ContentType.Text.EventStream) + // sseSession will add ContentType.Text.EventStream automatically + accept(ContentType.Application.Json) (resumptionToken ?: lastEventId)?.let { headers.append(MCP_RESUMPTION_TOKEN_HEADER, it) } requestBuilder() } diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt new file mode 100644 index 0000000..d6fcaed --- /dev/null +++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt @@ -0,0 +1,89 @@ +package io.modelcontextprotocol.kotlin.sdk.client + +import dev.mokksy.mokksy.Mokksy +import dev.mokksy.mokksy.StubConfiguration +import io.ktor.http.ContentType +import io.ktor.http.HttpMethod +import io.ktor.http.HttpStatusCode +import io.ktor.sse.ServerSentEvent +import io.modelcontextprotocol.kotlin.sdk.JSONRPCRequest +import kotlinx.coroutines.flow.Flow + +/** + * High-level helper for simulating an MCP server over Streaming HTTP transport with Server-Sent Events (SSE), + * built on top of an HTTP server using the [Mokksy](https://mokksy.dev) library. + * + * Provides test utilities to mock server behavior based on specific request conditions. + * + * @param verbose Whether to print detailed logs. Defaults to `false`. + * @author Konstantin Pavlov + */ +internal class MockMcp(verbose: Boolean = false) { + + private val mokksy: Mokksy = Mokksy(verbose = verbose) + + fun checkForUnmatchedRequests() { + mokksy.checkForUnmatchedRequests() + } + + val url = mokksy.baseUrl() + "/mcp" + + @Suppress("LongParameterList") + fun onJSONRPCRequest( + httpMethod: HttpMethod = HttpMethod.Post, + jsonRpcMethod: String, + expectedSessionId: String? = null, + sessionId: String, + contentType: ContentType = ContentType.Application.Json, + statusCode: HttpStatusCode = HttpStatusCode.OK, + bodyBuilder: () -> String, + ) { + mokksy.method( + configuration = StubConfiguration(removeAfterMatch = true), + httpMethod = httpMethod, + requestType = JSONRPCRequest::class, + ) { + path("/mcp") + expectedSessionId?.let { + containsHeader("Mcp-Session-Id", it) + } + bodyMatchesPredicates( + { + it!!.method == jsonRpcMethod + }, + { + it!!.jsonrpc == "2.0" + }, + ) + } respondsWith { + body = bodyBuilder.invoke() + this.contentType = contentType + headers += "Mcp-Session-Id" to sessionId + httpStatus = statusCode + } + } + + fun onSubscribeWithGet(sessionId: String, block: () -> Flow) { + mokksy.get(name = "MCP GETs", requestType = Any::class) { + path("/mcp") + containsHeader("Mcp-Session-Id", sessionId) + containsHeader("Accept", "application/json,text/event-stream") + containsHeader("Cache-Control", "no-store") + } respondsWithSseStream { + headers += "Mcp-Session-Id" to sessionId + this.flow = block.invoke() + } + } + + fun mockUnsubscribeRequest(sessionId: String) { + mokksy.delete( + configuration = StubConfiguration(removeAfterMatch = true), + requestType = JSONRPCRequest::class, + ) { + path("/mcp") + containsHeader("Mcp-Session-Id", sessionId) + } respondsWith { + body = null + } + } +} diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt new file mode 100644 index 0000000..6e730c3 --- /dev/null +++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt @@ -0,0 +1,202 @@ +package io.modelcontextprotocol.kotlin.sdk.client + +import io.kotest.matchers.collections.shouldContain +import io.ktor.client.HttpClient +import io.ktor.client.engine.apache5.Apache5 +import io.ktor.client.plugins.logging.LogLevel +import io.ktor.client.plugins.logging.Logging +import io.ktor.client.plugins.sse.SSE +import io.ktor.http.HttpStatusCode +import io.ktor.sse.ServerSentEvent +import io.modelcontextprotocol.kotlin.sdk.ClientCapabilities +import io.modelcontextprotocol.kotlin.sdk.Implementation +import io.modelcontextprotocol.kotlin.sdk.Tool +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import kotlinx.serialization.json.putJsonObject +import org.junit.jupiter.api.TestInstance +import java.util.UUID +import kotlin.test.AfterTest +import kotlin.test.Test +import kotlin.time.Duration.Companion.milliseconds + +/** + * Integration tests for the `StreamableHttpClientTransport` implementation + * using the [Mokksy](https://mokksy.dev) library + * to simulate Streaming HTTP with server-sent events (SSE). + * @author Konstantin Pavlov + */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class StreamableHttpClientTest { + + // start mokksy on random port + private val mockMcp: MockMcp = MockMcp(verbose = true) + + @AfterTest + fun afterEach() { + mockMcp.checkForUnmatchedRequests() + } + + @Test + @Suppress("LongMethod") + fun `test streamableHttpClient`(): Unit = runBlocking { + val client = Client( + clientInfo = Implementation(name = "sample-client", version = "1.0.0"), + options = ClientOptions( + capabilities = ClientCapabilities(), + ), + ) + + val sessionId = UUID.randomUUID().toString() + + mockMcp.onJSONRPCRequest( + jsonRpcMethod = "initialize", + sessionId = sessionId, + ) { + // language=json + """ + { + "jsonrpc": "2.0", + "id": 1, + "result": { + "capabilities": { + "tools": { + "listChanged": false + } + }, + "protocolVersion": "2025-03-26", + "serverInfo": { + "name": "Mock MCP Server", + "version": "1.0.0" + }, + "_meta": { + "foo": "bar" + } + } + } + """.trimIndent() + } + + mockMcp.onJSONRPCRequest( + jsonRpcMethod = "notifications/initialized", + expectedSessionId = sessionId, + sessionId = sessionId, + statusCode = HttpStatusCode.Accepted, + ) { + "" + } + + mockMcp.onSubscribeWithGet(sessionId) { + flow { + delay(500.milliseconds) + emit( + ServerSentEvent( + event = "message", + id = "1", + data = @Suppress("MaxLineLength") + //language=json + """{"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"upload-123","progress":50,"total":100}}""", + ), + ) + delay(200.milliseconds) + emit( + ServerSentEvent( + data = @Suppress("MaxLineLength") + //language=json + """{"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"upload-123","progress":50,"total":100}}""", + ), + ) + } + } + + client.connect( + StreamableHttpClientTransport( + url = mockMcp.url, + client = HttpClient(Apache5) { + install(SSE) + install(Logging) { + level = LogLevel.ALL + } + }, + ), + ) + + // TODO: how to get notifications via Client API? + + mockMcp.onJSONRPCRequest( + jsonRpcMethod = "tools/list", + sessionId = sessionId, + ) { + // language=json + """ + { + "jsonrpc": "2.0", + "id": 3, + "result": { + "tools": [ + { + "name": "get_weather", + "title": "Weather Information Provider", + "description": "Get current weather information for a location", + "inputSchema": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "City name or zip code" + } + }, + "required": ["location"] + }, + "outputSchema": { + "type": "object", + "properties": { + "temperature": { + "type": "number", + "description": "Temperature, Celsius" + } + }, + "required": ["temperature"] + } + } + ] + } + } + """.trimIndent() + } + + val listToolsResult = client.listTools() + + listToolsResult.tools shouldContain Tool( + name = "get_weather", + title = "Weather Information Provider", + description = "Get current weather information for a location", + inputSchema = Tool.Input( + properties = buildJsonObject { + putJsonObject("location") { + put("type", "string") + put("description", "City name or zip code") + } + }, + required = listOf("location"), + ), + outputSchema = Tool.Output( + properties = buildJsonObject { + putJsonObject("temperature") { + put("type", "number") + put("description", "Temperature, Celsius") + } + }, + required = listOf("temperature"), + ), + annotations = null, + ) + + mockMcp.mockUnsubscribeRequest(sessionId = sessionId) + + client.close() + } +} diff --git a/kotlin-sdk-client/src/jvmTest/resources/junit-platform.properties b/kotlin-sdk-client/src/jvmTest/resources/junit-platform.properties new file mode 100644 index 0000000..9af7987 --- /dev/null +++ b/kotlin-sdk-client/src/jvmTest/resources/junit-platform.properties @@ -0,0 +1,6 @@ +## https://docs.junit.org/5.3.0-M1/user-guide/index.html#writing-tests-parallel-execution +junit.jupiter.execution.parallel.enabled=true +junit.jupiter.execution.parallel.config.strategy=dynamic +junit.jupiter.execution.parallel.mode.default=concurrent +junit.jupiter.execution.parallel.mode.classes.default=concurrent +junit.jupiter.execution.timeout.default=2m diff --git a/kotlin-sdk-client/src/jvmTest/resources/simplelogger.properties b/kotlin-sdk-client/src/jvmTest/resources/simplelogger.properties new file mode 100644 index 0000000..506c436 --- /dev/null +++ b/kotlin-sdk-client/src/jvmTest/resources/simplelogger.properties @@ -0,0 +1,14 @@ +# Level of logging for the ROOT logger: ERROR, WARN, INFO, DEBUG, TRACE (default is INFO) +org.slf4j.simpleLogger.defaultLogLevel=INFO + + +org.slf4j.simpleLogger.showThreadName=true +org.slf4j.simpleLogger.showDateTime=false + +# Whether to enable stack traces for exceptions (true/false, default is true) +org.slf4j.simpleLogger.showShortLogName=false + +# Log level for specific packages or classes +org.slf4j.simpleLogger.log.io.ktor.server=DEBUG +org.slf4j.simpleLogger.log.io.modelcontextprotocol=TRACE +org.slf4j.simpleLogger.log.dev.mokksy=DEBUG diff --git a/kotlin-sdk-test/build.gradle.kts b/kotlin-sdk-test/build.gradle.kts index 012619b..b51398b 100644 --- a/kotlin-sdk-test/build.gradle.kts +++ b/kotlin-sdk-test/build.gradle.kts @@ -4,7 +4,7 @@ plugins { kotlin { jvm { - testRuns["test"].executionTask.configure { + tasks.withType { useJUnitPlatform() } } From bc28e6c03f10c33904e2d5920485df8d4eb8ddb1 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Tue, 21 Oct 2025 14:30:57 +0300 Subject: [PATCH 015/449] Refactor streaming http tests To provide better DSL for integration tests --- .../AbstractStreamableHttpClientTest.kt | 35 +++ .../kotlin/sdk/client/MockMcp.kt | 207 +++++++++++++++--- .../sdk/client/StreamableHttpClientTest.kt | 91 ++------ .../jvmTest/resources/simplelogger.properties | 7 +- 4 files changed, 234 insertions(+), 106 deletions(-) create mode 100644 kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractStreamableHttpClientTest.kt diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractStreamableHttpClientTest.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractStreamableHttpClientTest.kt new file mode 100644 index 0000000..77ba553 --- /dev/null +++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractStreamableHttpClientTest.kt @@ -0,0 +1,35 @@ +package io.modelcontextprotocol.kotlin.sdk.client + +import io.ktor.client.HttpClient +import io.ktor.client.engine.apache5.Apache5 +import io.ktor.client.plugins.logging.LogLevel +import io.ktor.client.plugins.logging.Logging +import io.ktor.client.plugins.sse.SSE +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.TestInstance + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +internal abstract class AbstractStreamableHttpClientTest { + + // start mokksy on random port + protected val mockMcp: MockMcp = MockMcp(verbose = true) + + @AfterEach + fun afterEach() { + mockMcp.checkForUnmatchedRequests() + } + + protected suspend fun connect(client: Client) { + client.connect( + StreamableHttpClientTransport( + url = mockMcp.url, + client = HttpClient(Apache5) { + install(SSE) + install(Logging) { + level = LogLevel.ALL + } + }, + ), + ) + } +} diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt index d6fcaed..eef77f1 100644 --- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt +++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt @@ -1,5 +1,6 @@ package io.modelcontextprotocol.kotlin.sdk.client +import dev.mokksy.mokksy.BuildingStep import dev.mokksy.mokksy.Mokksy import dev.mokksy.mokksy.StubConfiguration import io.ktor.http.ContentType @@ -7,7 +8,18 @@ import io.ktor.http.HttpMethod import io.ktor.http.HttpStatusCode import io.ktor.sse.ServerSentEvent import io.modelcontextprotocol.kotlin.sdk.JSONRPCRequest +import io.modelcontextprotocol.kotlin.sdk.RequestId import kotlinx.coroutines.flow.Flow +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.contentOrNull +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.putJsonObject + +const val MCP_SESSION_ID_HEADER = "Mcp-Session-Id" /** * High-level helper for simulating an MCP server over Streaming HTTP transport with Server-Sent Events (SSE), @@ -26,51 +38,188 @@ internal class MockMcp(verbose: Boolean = false) { mokksy.checkForUnmatchedRequests() } - val url = mokksy.baseUrl() + "/mcp" + val url = "${mokksy.baseUrl()}/mcp" @Suppress("LongParameterList") + fun onInitialize( + clientName: String? = null, + sessionId: String, + protocolVersion: String = "2025-03-26", + serverName: String = "Mock MCP Server", + serverVersion: String = "1.0.0", + capabilities: JsonObject = buildJsonObject { + putJsonObject("tools") { + put("listChanged", JsonPrimitive(false)) + } + }, + ) { + val predicates = if (clientName != null) { + arrayOf<(JSONRPCRequest?) -> Boolean>({ + it?.params?.jsonObject + ?.get("clientInfo")?.jsonObject + ?.get("name")?.jsonPrimitive + ?.contentOrNull == clientName + }) + } else { + emptyArray() + } + + handleWithResult( + jsonRpcMethod = "initialize", + sessionId = sessionId, + bodyPredicates = predicates, + // language=json + result = """ + { + "capabilities": $capabilities, + "protocolVersion": "$protocolVersion", + "serverInfo": { + "name": "$serverName", + "version": "$serverVersion" + }, + "_meta": { + "foo": "bar" + } + } + """.trimIndent(), + ) + } + fun onJSONRPCRequest( + httpMethod: HttpMethod = HttpMethod.Post, + jsonRpcMethod: String, + expectedSessionId: String? = null, + vararg bodyPredicates: (JSONRPCRequest) -> Boolean, + ): BuildingStep = mokksy.method( + configuration = StubConfiguration(removeAfterMatch = true), + httpMethod = httpMethod, + requestType = JSONRPCRequest::class, + ) { + path("/mcp") + expectedSessionId?.let { + containsHeader(MCP_SESSION_ID_HEADER, it) + } + bodyMatchesPredicate( + description = "JSON-RPC version is '2.0'", + predicate = + { + it!!.jsonrpc == "2.0" + }, + ) + bodyMatchesPredicate( + description = "JSON-RPC Method should be '$jsonRpcMethod'", + predicate = + { + it!!.method == jsonRpcMethod + }, + ) + bodyPredicates.forEach { predicate -> + bodyMatchesPredicate(predicate = { predicate.invoke(it!!) }) + } + } + + @Suppress("LongParameterList") + fun handleWithResult( httpMethod: HttpMethod = HttpMethod.Post, jsonRpcMethod: String, expectedSessionId: String? = null, sessionId: String, contentType: ContentType = ContentType.Application.Json, statusCode: HttpStatusCode = HttpStatusCode.OK, - bodyBuilder: () -> String, + vararg bodyPredicates: (JSONRPCRequest) -> Boolean, + result: () -> JsonObject, ) { - mokksy.method( - configuration = StubConfiguration(removeAfterMatch = true), + onJSONRPCRequest( httpMethod = httpMethod, - requestType = JSONRPCRequest::class, - ) { - path("/mcp") - expectedSessionId?.let { - containsHeader("Mcp-Session-Id", it) + jsonRpcMethod = jsonRpcMethod, + expectedSessionId = expectedSessionId, + bodyPredicates = bodyPredicates, + ) respondsWith { + val requestId = when (request.body.id) { + is RequestId.NumberId -> (request.body.id as RequestId.NumberId).value.toString() + is RequestId.StringId -> "\"${(request.body.id as RequestId.StringId).value}\"" } - bodyMatchesPredicates( - { - it!!.method == jsonRpcMethod - }, - { - it!!.jsonrpc == "2.0" - }, - ) - } respondsWith { - body = bodyBuilder.invoke() + val resultObject = result!!.invoke() + // language=json + body = """ + { + "jsonrpc": "2.0", + "id": $requestId, + "result": $resultObject + } + """.trimIndent() this.contentType = contentType - headers += "Mcp-Session-Id" to sessionId + headers += MCP_SESSION_ID_HEADER to sessionId httpStatus = statusCode } } - fun onSubscribeWithGet(sessionId: String, block: () -> Flow) { - mokksy.get(name = "MCP GETs", requestType = Any::class) { - path("/mcp") - containsHeader("Mcp-Session-Id", sessionId) - containsHeader("Accept", "application/json,text/event-stream") - containsHeader("Cache-Control", "no-store") - } respondsWithSseStream { - headers += "Mcp-Session-Id" to sessionId + @Suppress("LongParameterList") + fun handleWithResult( + httpMethod: HttpMethod = HttpMethod.Post, + jsonRpcMethod: String, + expectedSessionId: String? = null, + sessionId: String, + contentType: ContentType = ContentType.Application.Json, + statusCode: HttpStatusCode = HttpStatusCode.OK, + vararg bodyPredicates: (JSONRPCRequest) -> Boolean, + result: String, + ) { + handleWithResult( + httpMethod = httpMethod, + jsonRpcMethod = jsonRpcMethod, + expectedSessionId = expectedSessionId, + sessionId = sessionId, + contentType = contentType, + statusCode = statusCode, + bodyPredicates = bodyPredicates, + result = { + Json.parseToJsonElement(result).jsonObject + }, + ) + } + + @Suppress("LongParameterList") + fun handleJSONRPCRequest( + httpMethod: HttpMethod = HttpMethod.Post, + jsonRpcMethod: String, + expectedSessionId: String? = null, + sessionId: String, + contentType: ContentType = ContentType.Application.Json, + statusCode: HttpStatusCode = HttpStatusCode.OK, + vararg bodyPredicates: (JSONRPCRequest?) -> Boolean, + bodyBuilder: () -> String = { "" }, + ) { + onJSONRPCRequest( + httpMethod = httpMethod, + jsonRpcMethod = jsonRpcMethod, + expectedSessionId = expectedSessionId, + bodyPredicates = bodyPredicates, + ) respondsWith { + body = bodyBuilder.invoke() + this.contentType = contentType + headers += MCP_SESSION_ID_HEADER to sessionId + httpStatus = statusCode + } + } + + fun onSubscribe(httpMethod: HttpMethod = HttpMethod.Post, sessionId: String): BuildingStep = mokksy.method( + httpMethod = httpMethod, + name = "MCP GETs", + requestType = Any::class, + ) { + path("/mcp") + containsHeader(MCP_SESSION_ID_HEADER, sessionId) + containsHeader("Accept", "application/json,text/event-stream") + containsHeader("Cache-Control", "no-store") + } + + fun handleSubscribeWithGet(sessionId: String, block: () -> Flow) { + onSubscribe( + httpMethod = HttpMethod.Get, + sessionId = sessionId, + ) respondsWithSseStream { + headers += MCP_SESSION_ID_HEADER to sessionId this.flow = block.invoke() } } @@ -81,7 +230,7 @@ internal class MockMcp(verbose: Boolean = false) { requestType = JSONRPCRequest::class, ) { path("/mcp") - containsHeader("Mcp-Session-Id", sessionId) + containsHeader(MCP_SESSION_ID_HEADER, sessionId) } respondsWith { body = null } diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt index 6e730c3..7cedfe7 100644 --- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt +++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt @@ -1,11 +1,6 @@ package io.modelcontextprotocol.kotlin.sdk.client import io.kotest.matchers.collections.shouldContain -import io.ktor.client.HttpClient -import io.ktor.client.engine.apache5.Apache5 -import io.ktor.client.plugins.logging.LogLevel -import io.ktor.client.plugins.logging.Logging -import io.ktor.client.plugins.sse.SSE import io.ktor.http.HttpStatusCode import io.ktor.sse.ServerSentEvent import io.modelcontextprotocol.kotlin.sdk.ClientCapabilities @@ -19,7 +14,6 @@ import kotlinx.serialization.json.put import kotlinx.serialization.json.putJsonObject import org.junit.jupiter.api.TestInstance import java.util.UUID -import kotlin.test.AfterTest import kotlin.test.Test import kotlin.time.Duration.Companion.milliseconds @@ -30,21 +24,16 @@ import kotlin.time.Duration.Companion.milliseconds * @author Konstantin Pavlov */ @TestInstance(TestInstance.Lifecycle.PER_CLASS) -class StreamableHttpClientTest { - - // start mokksy on random port - private val mockMcp: MockMcp = MockMcp(verbose = true) - - @AfterTest - fun afterEach() { - mockMcp.checkForUnmatchedRequests() - } +@Suppress("LongMethod") +internal class StreamableHttpClientTest : AbstractStreamableHttpClientTest() { @Test - @Suppress("LongMethod") - fun `test streamableHttpClient`(): Unit = runBlocking { + fun `test streamableHttpClient`() = runBlocking { val client = Client( - clientInfo = Implementation(name = "sample-client", version = "1.0.0"), + clientInfo = Implementation( + name = "client1", + version = "1.0.0", + ), options = ClientOptions( capabilities = ClientCapabilities(), ), @@ -52,44 +41,19 @@ class StreamableHttpClientTest { val sessionId = UUID.randomUUID().toString() - mockMcp.onJSONRPCRequest( - jsonRpcMethod = "initialize", + mockMcp.onInitialize( + clientName = "client1", sessionId = sessionId, - ) { - // language=json - """ - { - "jsonrpc": "2.0", - "id": 1, - "result": { - "capabilities": { - "tools": { - "listChanged": false - } - }, - "protocolVersion": "2025-03-26", - "serverInfo": { - "name": "Mock MCP Server", - "version": "1.0.0" - }, - "_meta": { - "foo": "bar" - } - } - } - """.trimIndent() - } + ) - mockMcp.onJSONRPCRequest( + mockMcp.handleJSONRPCRequest( jsonRpcMethod = "notifications/initialized", expectedSessionId = sessionId, sessionId = sessionId, statusCode = HttpStatusCode.Accepted, - ) { - "" - } + ) - mockMcp.onSubscribeWithGet(sessionId) { + mockMcp.handleSubscribeWithGet(sessionId) { flow { delay(500.milliseconds) emit( @@ -112,30 +76,14 @@ class StreamableHttpClientTest { } } - client.connect( - StreamableHttpClientTransport( - url = mockMcp.url, - client = HttpClient(Apache5) { - install(SSE) - install(Logging) { - level = LogLevel.ALL - } - }, - ), - ) - // TODO: how to get notifications via Client API? - mockMcp.onJSONRPCRequest( + mockMcp.handleWithResult( jsonRpcMethod = "tools/list", sessionId = sessionId, - ) { // language=json - """ - { - "jsonrpc": "2.0", - "id": 3, - "result": { + result = """ + { "tools": [ { "name": "get_weather", @@ -164,9 +112,10 @@ class StreamableHttpClientTest { } ] } - } - """.trimIndent() - } + """.trimIndent(), + ) + + connect(client) val listToolsResult = client.listTools() diff --git a/kotlin-sdk-client/src/jvmTest/resources/simplelogger.properties b/kotlin-sdk-client/src/jvmTest/resources/simplelogger.properties index 506c436..2336c13 100644 --- a/kotlin-sdk-client/src/jvmTest/resources/simplelogger.properties +++ b/kotlin-sdk-client/src/jvmTest/resources/simplelogger.properties @@ -1,14 +1,9 @@ # Level of logging for the ROOT logger: ERROR, WARN, INFO, DEBUG, TRACE (default is INFO) org.slf4j.simpleLogger.defaultLogLevel=INFO - - org.slf4j.simpleLogger.showThreadName=true org.slf4j.simpleLogger.showDateTime=false -# Whether to enable stack traces for exceptions (true/false, default is true) -org.slf4j.simpleLogger.showShortLogName=false - # Log level for specific packages or classes org.slf4j.simpleLogger.log.io.ktor.server=DEBUG -org.slf4j.simpleLogger.log.io.modelcontextprotocol=TRACE +org.slf4j.simpleLogger.log.io.modelcontextprotocol=DEBUG org.slf4j.simpleLogger.log.dev.mokksy=DEBUG From bfaf54e72636261ad3875e1e473747d4bfcc337a Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Tue, 21 Oct 2025 14:05:39 +0200 Subject: [PATCH 016/449] Fix Streamable handling to support JSON-only responses (#309) quick fix #308 --- .../client/StreamableHttpClientTransport.kt | 14 +++- .../sdk/client/StreamableHttpClientTest.kt | 69 ++++++++++++++++++- 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt index 71a89f6..f0939a3 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt @@ -235,10 +235,22 @@ public class StreamableHttpClientTransport( } logger.debug { "Client SSE session started successfully." } } catch (e: SSEClientException) { - if (e.response?.status == HttpStatusCode.MethodNotAllowed) { + val responseStatus = e.response?.status + val responseContentType = e.response?.contentType() + + // 405 means server doesn't support SSE at GET endpoint - this is expected and valid + if (responseStatus == HttpStatusCode.MethodNotAllowed) { logger.info { "Server returned 405 for GET/SSE, stream disabled." } return } + + // If server returns application/json, it means it doesn't support SSE for this session + // This is valid per spec - server can choose to only use JSON responses + if (responseContentType?.match(ContentType.Application.Json) == true) { + logger.info { "Server returned application/json for GET/SSE, using JSON-only mode." } + return + } + _onError(e) throw e } diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt index 7cedfe7..1e5ca7e 100644 --- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt +++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt @@ -1,6 +1,8 @@ package io.modelcontextprotocol.kotlin.sdk.client import io.kotest.matchers.collections.shouldContain +import io.ktor.http.ContentType +import io.ktor.http.HttpMethod import io.ktor.http.HttpStatusCode import io.ktor.sse.ServerSentEvent import io.modelcontextprotocol.kotlin.sdk.ClientCapabilities @@ -13,9 +15,11 @@ import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.put import kotlinx.serialization.json.putJsonObject import org.junit.jupiter.api.TestInstance -import java.util.UUID import kotlin.test.Test import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds +import kotlin.uuid.ExperimentalUuidApi +import kotlin.uuid.Uuid /** * Integration tests for the `StreamableHttpClientTransport` implementation @@ -23,6 +27,7 @@ import kotlin.time.Duration.Companion.milliseconds * to simulate Streaming HTTP with server-sent events (SSE). * @author Konstantin Pavlov */ +@OptIn(ExperimentalUuidApi::class) @TestInstance(TestInstance.Lifecycle.PER_CLASS) @Suppress("LongMethod") internal class StreamableHttpClientTest : AbstractStreamableHttpClientTest() { @@ -39,7 +44,7 @@ internal class StreamableHttpClientTest : AbstractStreamableHttpClientTest() { ), ) - val sessionId = UUID.randomUUID().toString() + val sessionId = Uuid.random().toString() mockMcp.onInitialize( clientName = "client1", @@ -148,4 +153,64 @@ internal class StreamableHttpClientTest : AbstractStreamableHttpClientTest() { client.close() } + + @Test + fun `handle MethodNotAllowed`() = runBlocking { + checkSupportNonStreamingResponse( + ContentType.Text.EventStream, + HttpStatusCode.MethodNotAllowed, + ) + } + + @Test + fun `handle non-streaming response`() = runBlocking { + checkSupportNonStreamingResponse( + ContentType.Application.Json, + HttpStatusCode.OK, + ) + } + + private suspend fun checkSupportNonStreamingResponse(contentType: ContentType, statusCode: HttpStatusCode) { + val sessionId = "SID_${Uuid.random().toHexString()}" + val clientName = "client-${Uuid.random().toHexString()}" + val client = Client( + clientInfo = Implementation(name = clientName, version = "1.0.0"), + options = ClientOptions( + capabilities = ClientCapabilities(), + ), + ) + + mockMcp.onInitialize(clientName = clientName, sessionId = sessionId) + + mockMcp.handleJSONRPCRequest( + jsonRpcMethod = "notifications/initialized", + expectedSessionId = sessionId, + sessionId = sessionId, + statusCode = HttpStatusCode.Accepted, + ) + + mockMcp.onSubscribe( + httpMethod = HttpMethod.Get, + sessionId = sessionId, + ) respondsWith { + headers += MCP_SESSION_ID_HEADER to sessionId + body = null + httpStatus = statusCode + this.contentType = contentType + } + + mockMcp.handleWithResult(jsonRpcMethod = "ping", sessionId = sessionId) { + buildJsonObject {} + } + + mockMcp.mockUnsubscribeRequest(sessionId = sessionId) + + connect(client) + + delay(1.seconds) + + client.ping() // connection is still alive + + client.close() + } } From eb2a2b53da8180518245ef31e3ae224d9bbe5e7c Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Tue, 21 Oct 2025 18:57:18 +0300 Subject: [PATCH 017/449] Release 0.7.3 (#325) Release 0.7.3 ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] 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 - [ ] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed ## Additional context --- build.gradle.kts | 2 +- gradle/libs.versions.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index c876738..49bd9c8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { allprojects { group = "io.modelcontextprotocol" - version = "0.7.2" + version = "0.7.3" } dependencies { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 86886f7..7ff378d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,7 +22,7 @@ awaitility = "4.3.0" mokksy = "0.6.1" # Samples -mcp-kotlin = "0.7.2" +mcp-kotlin = "0.7.3" anthropic = "2.7.0" shadow = "8.1.1" From 79bfed07dc55ea8f2da908425ff14f88510efc84 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Wed, 22 Oct 2025 13:59:17 +0300 Subject: [PATCH 018/449] Update badges in README and set version to 0.7.4-SNAPSHOT (#326) Update badges in README and set version to 0.7.4-SNAPSHOT Not updating the version of sample projects because of #302 ## Motivation and Context ## How Has This Been Tested? ## Breaking Changes ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] 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 ## Additional context --- .github/workflows/build.yml | 8 ++++++-- README.md | 9 ++++++--- build.gradle.kts | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5c722e1..21bf62e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -36,8 +36,12 @@ jobs: - name: Build with Gradle run: |- - ./gradlew --no-daemon clean ktlintCheck build koverLog koverHtmlReport - ./gradlew --no-daemon :kotlin-sdk-core:publishToMavenLocal :kotlin-sdk-client:publishToMavenLocal :kotlin-sdk-server:publishToMavenLocal + ./gradlew --no-daemon --rerun-tasks \ + clean \ + ktlintCheck \ + build \ + koverLog koverHtmlReport \ + publishToMavenLocal - name: Build Kotlin-MCP-Client Sample working-directory: ./samples/kotlin-mcp-client diff --git a/README.md b/README.md index 34cd83f..2a756e5 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,11 @@ # MCP Kotlin SDK -[![Kotlin Multiplatform](https://img.shields.io/badge/Kotlin-Multiplatform-blueviolet?logo=kotlin)](https://kotlinlang.org/docs/multiplatform.html) -[![Platforms](https://img.shields.io/badge/Platforms-JVM%20%7C%20Wasm%2FJS%20%7C%20Native%20(iOS%2FiOS%20Simulator)-blue)](https://kotlinlang.org/docs/multiplatform.html) -[![Maven Central](https://img.shields.io/maven-central/v/io.modelcontextprotocol/kotlin-sdk.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:io.modelcontextprotocol%20a:kotlin-sdk) + +[![Maven Central](https://img.shields.io/maven-central/v/io.modelcontextprotocol/kotlin-sdk.svg?label=Maven%20Central)](https://central.sonatype.com/artifact/io.modelcontextprotocol/kotlin-sdk) +[![Build](https://github.com/modelcontextprotocol/kotlin-sdk/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/modelcontextprotocol/kotlin-sdk/actions/workflows/build.yml) +[![Kotlin](https://img.shields.io/badge/kotlin-2.2+-blueviolet.svg?logo=kotlin)](http://kotlinlang.org) +[![Kotlin Multiplatform](https://img.shields.io/badge/Platforms-%20JVM%20%7C%20Wasm%2FJS%20%7C%20Native%20-blueviolet?logo=kotlin)](https://kotlinlang.org/docs/multiplatform.html) +[![JVM](https://img.shields.io/badge/JVM-1.8+-red.svg?logo=jvm)](http://java.com) [![License](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) Kotlin Multiplatform implementation of the [Model Context Protocol](https://modelcontextprotocol.io) (MCP), diff --git a/build.gradle.kts b/build.gradle.kts index 49bd9c8..dbea927 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { allprojects { group = "io.modelcontextprotocol" - version = "0.7.3" + version = "0.7.4-SNAPSHOT" } dependencies { From 468877d5cb90b0ec6952dab9b118f3f30ce78954 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Wed, 22 Oct 2025 14:20:52 +0300 Subject: [PATCH 019/449] #302 [part 1] Add GitHub workflow to validate samples (#327) #302 Add GitHub workflow to validate samples ## Motivation and Context ## How Has This Been Tested? ## Breaking Changes ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 - [ ] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed ## Additional context --- .github/workflows/samples.yml | 58 +++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 .github/workflows/samples.yml diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml new file mode 100644 index 0000000..b3ec95d --- /dev/null +++ b/.github/workflows/samples.yml @@ -0,0 +1,58 @@ +name: Build Samples + +on: + workflow_dispatch: + pull_request: + branches: [ main ] + paths: + - 'samples/**' + push: + branches: [ main ] + paths: + - 'samples/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + # Cancel only when the run is NOT on `main` branch + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + sample: + - kotlin-mcp-client + - kotlin-mcp-server + - weather-stdio-server + + name: Build Sample + timeout-minutes: 10 + env: + JAVA_OPTS: "-Xmx8g -Dfile.encoding=UTF-8 -Djava.awt.headless=true -Dkotlin.daemon.jvm.options=-Xmx6g" + steps: + - uses: actions/checkout@v5 + + - name: Set up JDK 21 + uses: actions/setup-java@v5 + with: + java-version: 21 + distribution: 'temurin' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + with: + add-job-summary: 'always' + cache-read-only: true + + - name: "Build Sample: ${{ matrix.sample }}" + working-directory: ./samples/${{ matrix.sample }} + run: ./../../gradlew --no-daemon clean build + + - name: Upload Reports + if: ${{ !cancelled() }} + uses: actions/upload-artifact@v4 + with: + name: reports-${{ matrix.sample }} + path: | + **/build/reports/ From 232582db19fa1c4b93ddd94869ffaaa16f50aa7e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Oct 2025 14:37:23 +0300 Subject: [PATCH 020/449] Bump kotest from 6.0.3 to 6.0.4 (#324) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `kotest` from 6.0.3 to 6.0.4. Updates `io.kotest:kotest-assertions-core` from 6.0.3 to 6.0.4
Release notes

Sourced from io.kotest:kotest-assertions-core's releases.

v6.0.4

What's Changed

New Contributors

Full Changelog: https://github.com/kotest/kotest/compare/v6.0.3...v6.0.4

Commits
  • 7b2ed08 Add tests to validate tag useage
  • f69f07c Add timeout handling to eventually (#5134)
  • c199226 Enable to call afterSpec hooks on InstancePerLeaf correctly (#5114)
  • a903c8f Restrict @​ApplyExtension to the target class (#5133)
  • 7c28f43 Add tests for nested classes and anonymous types in equality matchers; handle...
  • c1c73bb Correct spelling of DISABLE_NAN_NEQUALITY constant and add deprecated alias...
  • 01ae08d Alex/fail softly (#5128) (#5129)
  • d80b955 PackageConfig checked bug (#5131)
  • 77f1515 Refactor TestEngineLauncher usage in system property timeout tests to use `...
  • bcc1cd0 Fix kotest plugin in project with Kotlin 2.2.20 (#5106)
  • Additional commits viewable in compare view

Updates `io.kotest:kotest-assertions-json` from 6.0.3 to 6.0.4
Release notes

Sourced from io.kotest:kotest-assertions-json's releases.

v6.0.4

What's Changed

New Contributors

Full Changelog: https://github.com/kotest/kotest/compare/v6.0.3...v6.0.4

Commits
  • 7b2ed08 Add tests to validate tag useage
  • f69f07c Add timeout handling to eventually (#5134)
  • c199226 Enable to call afterSpec hooks on InstancePerLeaf correctly (#5114)
  • a903c8f Restrict @​ApplyExtension to the target class (#5133)
  • 7c28f43 Add tests for nested classes and anonymous types in equality matchers; handle...
  • c1c73bb Correct spelling of DISABLE_NAN_NEQUALITY constant and add deprecated alias...
  • 01ae08d Alex/fail softly (#5128) (#5129)
  • d80b955 PackageConfig checked bug (#5131)
  • 77f1515 Refactor TestEngineLauncher usage in system property timeout tests to use `...
  • bcc1cd0 Fix kotest plugin in project with Kotlin 2.2.20 (#5106)
  • Additional commits viewable in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7ff378d..9c9d47d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,7 +17,7 @@ kotlinx-io = "0.8.0" ktor = "3.3.0" logging = "7.0.13" slf4j = "2.0.17" -kotest = "6.0.3" +kotest = "6.0.4" awaitility = "4.3.0" mokksy = "0.6.1" From e026069d2f24e9d57dd82de2d60a3e9df65b7815 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Oct 2025 15:04:32 +0300 Subject: [PATCH 021/449] Bump com.anthropic:anthropic-java from 2.7.0 to 2.9.0 (#323) Bumps [com.anthropic:anthropic-java](https://github.com/anthropics/anthropic-sdk-java) from 2.7.0 to 2.9.0.
Release notes

Sourced from com.anthropic:anthropic-java's releases.

v2.9.0

2.9.0 (2025-10-15)

Full Changelog: v2.8.1...v2.9.0

Features

  • add more helper methods to instantiate union objects (3e3a4bb)
  • api: manual updates (d2170e9)

v2.8.1

2.8.1 (2025-09-29)

Full Changelog: v2.8.0...v2.8.1

Chores

  • client: add context-management-2025-06-27 beta header (b6c5683)
  • client: add model-context-window-exceeded-2025-08-26 beta header (d9d0493)

v2.8.0

2.8.0 (2025-09-29)

Full Changelog: v2.7.0...v2.8.0

Features

  • api: adds support for Claude Sonnet 4.5 and context management features (c286ab8)
  • client: expose sleeper option (ca9ce4f)

Bug Fixes

  • client: deserialization of empty objects (af93581)
  • client: ensure single timer is created per client (ca9ce4f)
  • client: incorrect getPackageVersion impl (a8b170b)

Chores

  • improve formatter performance (c57630b)
  • improve formatter performance (100e6ad)
  • internal: change some comment formatting (f725d58)
  • internal: codegen related update (14a3c9c)
  • internal: codegen related update (e6fddb4)
  • internal: fix tests (cc05673)
Changelog

Sourced from com.anthropic:anthropic-java's changelog.

2.9.0 (2025-10-15)

Full Changelog: v2.8.1...v2.9.0

Features

  • add more helper methods to instantiate union objects (3e3a4bb)
  • api: manual updates (d2170e9)

2.8.1 (2025-09-29)

Full Changelog: v2.8.0...v2.8.1

Chores

  • client: add context-management-2025-06-27 beta header (b6c5683)
  • client: add model-context-window-exceeded-2025-08-26 beta header (d9d0493)

2.8.0 (2025-09-29)

Full Changelog: v2.7.0...v2.8.0

Features

  • api: adds support for Claude Sonnet 4.5 and context management features (c286ab8)
  • client: expose sleeper option (ca9ce4f)

Bug Fixes

  • client: deserialization of empty objects (af93581)
  • client: ensure single timer is created per client (ca9ce4f)
  • client: incorrect getPackageVersion impl (a8b170b)

Chores

  • improve formatter performance (c57630b)
  • improve formatter performance (100e6ad)
  • internal: change some comment formatting (f725d58)
  • internal: codegen related update (14a3c9c)
  • internal: codegen related update (e6fddb4)
  • internal: fix tests (cc05673)
Commits
  • f93cdaa release: 2.9.0
  • 3a37cfc feat(api): manual updates
  • d581f2a feat: add more helper methods to instantiate union objects
  • 5a0a815 codegen metadata
  • 4f816b2 release: 2.8.1
  • dec4738 chore(client): add model-context-window-exceeded-2025-08-26 beta header
  • 1479b2c chore(client): add context-management-2025-06-27 beta header
  • ef6a706 release: 2.8.0
  • 618bb97 feat(api): adds support for Claude Sonnet 4.5 and context management features
  • 1c6be51 fix(client): deserialization of empty objects
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=com.anthropic:anthropic-java&package-manager=gradle&previous-version=2.7.0&new-version=2.9.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9c9d47d..872f7cf 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,7 +23,7 @@ mokksy = "0.6.1" # Samples mcp-kotlin = "0.7.3" -anthropic = "2.7.0" +anthropic = "2.9.0" shadow = "8.1.1" [libraries] From ba18b2b95ee520e36c35920c2097acc54092b655 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Oct 2025 15:22:18 +0300 Subject: [PATCH 022/449] Bump org.jetbrains.kotlinx.kover from 0.9.2 to 0.9.3 (#322) Bumps [org.jetbrains.kotlinx.kover](https://github.com/Kotlin/kotlinx-kover) from 0.9.2 to 0.9.3.
Release notes

Sourced from org.jetbrains.kotlinx.kover's releases.

0.9.3

Kover Gradle Plugin

Bugfixes

Build features

  • Upgraded Gradle version to 9.1.0
Changelog

Sourced from org.jetbrains.kotlinx.kover's changelog.

0.9.3 / 2025-10-16

Kover Gradle Plugin

Bugfixes

Build features

  • Upgraded Gradle version to 9.1.0
Commits
  • f182e70 Release 0.9.3
  • fd9521e Fixed support of KSP plugin
  • 90c305a Fixed the locating of host tests for the multiplatform android library
  • 5c7951b Upgraded Gradle to 9.1.0
  • See full diff in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.jetbrains.kotlinx.kover&package-manager=gradle&previous-version=0.9.2&new-version=0.9.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 872f7cf..0a0d919 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ kotlin = "2.2.10" dokka = "2.0.0" atomicfu = "0.29.0" ktlint = "13.1.0" -kover = "0.9.2" +kover = "0.9.3" netty = "4.2.7.Final" mavenPublish = "0.34.0" binaryCompatibilityValidatorPlugin = "0.18.1" From 2da14b0f2e68ea578990d931325e51b3cd7c653e Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Thu, 23 Oct 2025 13:02:22 +0300 Subject: [PATCH 023/449] #302 Update samples build workflow (#330) # #302 Update samples build workflow - Introduce workflow to build samples independently from the main project - Keep building samples against the current snapshot version of the SDK. Update composite build configuration. - Temporarily disable "samples/kotlin-mcp-server": the multiplatform project is broken. **It doesn't compile for me even on the main branch**. Not working with [MCP Instepcor](https://modelcontextprotocol.io/docs/tools/inspector), because CORS is not configured. Deserves a separate PR to fix. ## Motivation and Context Samples should be independently buildable ## How Has This Been Tested? ## Breaking Changes ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [x] Build improvement - [x] Tests ## Checklist - [x] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [x] My code follows the repository's style guidelines - [ ] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed ## Additional context --- .github/workflows/build.yml | 16 +- .github/workflows/samples.yml | 4 +- build.gradle.kts | 5 - gradle.properties | 3 + gradle/libs.versions.toml | 4 +- samples/kotlin-mcp-client/README.md | 2 +- samples/kotlin-mcp-client/gradle.properties | 6 + .../gradle/libs.versions.toml | 17 ++ .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 45457 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 + samples/kotlin-mcp-client/gradlew | 248 ++++++++++++++++++ samples/kotlin-mcp-client/gradlew.bat | 93 +++++++ samples/kotlin-mcp-client/settings.gradle.kts | 17 +- .../sample/client/main.kt | 6 +- .../main/resources/simplelogger.properties | 7 + samples/kotlin-mcp-server/gradle.properties | 5 + .../gradle/libs.versions.toml | 15 ++ .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 45457 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 + samples/kotlin-mcp-server/gradlew | 248 ++++++++++++++++++ samples/kotlin-mcp-server/gradlew.bat | 93 +++++++ samples/kotlin-mcp-server/settings.gradle.kts | 17 +- .../jvmMain/resources/simplelogger.properties | 8 + samples/weather-stdio-server/README.md | 4 +- samples/weather-stdio-server/build.gradle.kts | 3 + .../weather-stdio-server/gradle.properties | 5 + .../gradle/libs.versions.toml | 26 ++ .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 45457 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 + samples/weather-stdio-server/gradlew | 248 ++++++++++++++++++ samples/weather-stdio-server/gradlew.bat | 93 +++++++ .../weather-stdio-server/settings.gradle.kts | 9 +- .../sample/server/McpWeatherServer.kt | 65 +++-- .../sample/server/main.kt | 2 +- .../sample/client/ClientStdio.kt | 22 +- .../sample/client/FakeTest.kt | 11 + settings.gradle.kts | 21 +- 37 files changed, 1271 insertions(+), 73 deletions(-) create mode 100644 samples/kotlin-mcp-client/gradle.properties create mode 100644 samples/kotlin-mcp-client/gradle/libs.versions.toml create mode 100644 samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.jar create mode 100644 samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties create mode 100755 samples/kotlin-mcp-client/gradlew create mode 100644 samples/kotlin-mcp-client/gradlew.bat create mode 100644 samples/kotlin-mcp-client/src/main/resources/simplelogger.properties create mode 100644 samples/kotlin-mcp-server/gradle.properties create mode 100644 samples/kotlin-mcp-server/gradle/libs.versions.toml create mode 100644 samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.jar create mode 100644 samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties create mode 100755 samples/kotlin-mcp-server/gradlew create mode 100644 samples/kotlin-mcp-server/gradlew.bat create mode 100644 samples/kotlin-mcp-server/src/jvmMain/resources/simplelogger.properties create mode 100644 samples/weather-stdio-server/gradle.properties create mode 100644 samples/weather-stdio-server/gradle/libs.versions.toml create mode 100644 samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.jar create mode 100644 samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties create mode 100755 samples/weather-stdio-server/gradlew create mode 100644 samples/weather-stdio-server/gradlew.bat create mode 100644 samples/weather-stdio-server/src/test/kotlin/io/modelcontextprotocol/sample/client/FakeTest.kt diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 21bf62e..3d4c225 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -36,24 +36,26 @@ jobs: - name: Build with Gradle run: |- - ./gradlew --no-daemon --rerun-tasks \ + ./gradlew --no-daemon \ + --rerun-tasks \ clean \ ktlintCheck \ build \ koverLog koverHtmlReport \ - publishToMavenLocal + publishToMavenLocal \ + -Pversion=0.0.1-SNAPSHOT - name: Build Kotlin-MCP-Client Sample working-directory: ./samples/kotlin-mcp-client - run: ./../../gradlew --no-daemon clean build + run: ./gradlew --no-daemon clean build -Pmcp.kotlin.overrideVersion=0.0.1-SNAPSHOT - - name: Build Kotlin-MCP-Server Sample - working-directory: ./samples/kotlin-mcp-server - run: ./../../gradlew --no-daemon clean build +# - name: Build Kotlin-MCP-Server Sample +# working-directory: ./samples/kotlin-mcp-server +# run: ./gradlew --no-daemon clean build -Pmcp.kotlin.overrideVersion=0.0.1-SNAPSHOT - name: Build Weather-Stdio-Server Sample working-directory: ./samples/weather-stdio-server - run: ./../../gradlew --no-daemon clean build + run: ./gradlew --no-daemon clean build -Pmcp.kotlin.overrideVersion=0.0.1-SNAPSHOT - name: Upload Reports if: ${{ !cancelled() }} diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index b3ec95d..6ed5179 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -23,7 +23,7 @@ jobs: matrix: sample: - kotlin-mcp-client - - kotlin-mcp-server +# - kotlin-mcp-server - weather-stdio-server name: Build Sample @@ -47,7 +47,7 @@ jobs: - name: "Build Sample: ${{ matrix.sample }}" working-directory: ./samples/${{ matrix.sample }} - run: ./../../gradlew --no-daemon clean build + run: ./gradlew --no-daemon clean build - name: Upload Reports if: ${{ !cancelled() }} diff --git a/build.gradle.kts b/build.gradle.kts index dbea927..616b514 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,11 +4,6 @@ plugins { alias(libs.plugins.kover) } -allprojects { - group = "io.modelcontextprotocol" - version = "0.7.4-SNAPSHOT" -} - dependencies { dokka(project(":kotlin-sdk-core")) dokka(project(":kotlin-sdk-client")) diff --git a/gradle.properties b/gradle.properties index 7e63634..2dadb66 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,3 +12,6 @@ kotlin.js.yarn=false # MPP kotlin.mpp.enableCInteropCommonization=true kotlin.native.ignoreDisabledTargets=true + +group=io.modelcontextprotocol +version=0.7.4-SNAPSHOT diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0a0d919..5002dec 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -24,7 +24,7 @@ mokksy = "0.6.1" # Samples mcp-kotlin = "0.7.3" anthropic = "2.9.0" -shadow = "8.1.1" +shadow = "9.2.2" [libraries] # Plugins @@ -82,4 +82,4 @@ kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } -shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" } +shadow = { id = "com.gradleup.shadow", version.ref = "shadow" } diff --git a/samples/kotlin-mcp-client/README.md b/samples/kotlin-mcp-client/README.md index 3842ea0..fd9e143 100644 --- a/samples/kotlin-mcp-client/README.md +++ b/samples/kotlin-mcp-client/README.md @@ -34,7 +34,7 @@ The client application performs the following tasks: Use the Gradle wrapper to build the application. In a terminal, run: ```shell -./gradlew clean build -x test +./gradlew clean build ``` To run the client, execute the jar file and provide the path to your MCP server script. diff --git a/samples/kotlin-mcp-client/gradle.properties b/samples/kotlin-mcp-client/gradle.properties new file mode 100644 index 0000000..fdc17e4 --- /dev/null +++ b/samples/kotlin-mcp-client/gradle.properties @@ -0,0 +1,6 @@ +org.gradle.configuration-cache=true +org.gradle.parallel=true +org.gradle.caching=true + +#mcp.kotlin.overrideVersion=0.0.1-SNAPSHOT + diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml new file mode 100644 index 0000000..8652aca --- /dev/null +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -0,0 +1,17 @@ +[versions] +anthropic = "2.9.0" +kotlin = "2.2.20" +ktor = "3.3.0" +mcp-kotlin = "0.7.3" +shadow = "9.2.2" +slf4j = "2.0.17" + +[libraries] +anthropic-java = { group = "com.anthropic", name = "anthropic-java", version.ref = "anthropic" } +ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio", version.ref = "ktor" } +mcp-kotlin-client = { group = "io.modelcontextprotocol", name = "kotlin-sdk-client", version.ref = "mcp-kotlin" } +slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" } + +[plugins] +kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +shadow = { id = "com.gradleup.shadow", version.ref = "shadow" } diff --git a/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.jar b/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..8bdaf60c75ab801e22807dde59e12a8735a34077 GIT binary patch literal 45457 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 c82=K%QXLKFx%@O{wJjEi6Y56o#$)Bpeg literal 0 HcmV?d00001 diff --git a/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties b/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..2e11132 --- /dev/null +++ b/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/samples/kotlin-mcp-client/gradlew b/samples/kotlin-mcp-client/gradlew new file mode 100755 index 0000000..adff685 --- /dev/null +++ b/samples/kotlin-mcp-client/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/samples/kotlin-mcp-client/gradlew.bat b/samples/kotlin-mcp-client/gradlew.bat new file mode 100644 index 0000000..c4bdd3a --- /dev/null +++ b/samples/kotlin-mcp-client/gradlew.bat @@ -0,0 +1,93 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/samples/kotlin-mcp-client/settings.gradle.kts b/samples/kotlin-mcp-client/settings.gradle.kts index c5f225b..7b54e1d 100644 --- a/samples/kotlin-mcp-client/settings.gradle.kts +++ b/samples/kotlin-mcp-client/settings.gradle.kts @@ -1,10 +1,8 @@ rootProject.name = "kotlin-mcp-client" -pluginManagement { - repositories { - mavenCentral() - gradlePluginPortal() - } +plugins { + // Apply the foojay-resolver plugin to allow automatic download of JDKs + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } dependencyResolutionManagement { @@ -12,9 +10,16 @@ dependencyResolutionManagement { mavenLocal() mavenCentral() } + versionCatalogs { create("libs") { - from(files("../../gradle/libs.versions.toml")) + val mcpKotlinVersion = providers.gradleProperty( + "mcp.kotlin.overrideVersion", + ).orNull + if (mcpKotlinVersion != null) { + logger.lifecycle("Using the override version $mcpKotlinVersion of MCP Kotlin SDK") + version("mcp-kotlin", mcpKotlinVersion) + } } } } diff --git a/samples/kotlin-mcp-client/src/main/kotlin/io/modelcontextprotocol/sample/client/main.kt b/samples/kotlin-mcp-client/src/main/kotlin/io/modelcontextprotocol/sample/client/main.kt index 6d76180..19a3102 100644 --- a/samples/kotlin-mcp-client/src/main/kotlin/io/modelcontextprotocol/sample/client/main.kt +++ b/samples/kotlin-mcp-client/src/main/kotlin/io/modelcontextprotocol/sample/client/main.kt @@ -3,11 +3,13 @@ package io.modelcontextprotocol.sample.client import kotlinx.coroutines.runBlocking fun main(args: Array) = runBlocking { - if (args.isEmpty()) throw IllegalArgumentException("Usage: java -jar /build/libs/kotlin-mcp-client-0.1.0-all.jar ") + require(args.isNotEmpty()) { + "Usage: java -jar /build/libs/kotlin-mcp-client-0.1.0-all.jar " + } val serverPath = args.first() val client = MCPClient() client.use { client.connectToServer(serverPath) client.chatLoop() } -} \ No newline at end of file +} diff --git a/samples/kotlin-mcp-client/src/main/resources/simplelogger.properties b/samples/kotlin-mcp-client/src/main/resources/simplelogger.properties new file mode 100644 index 0000000..0a188c8 --- /dev/null +++ b/samples/kotlin-mcp-client/src/main/resources/simplelogger.properties @@ -0,0 +1,7 @@ +# Level of logging for the ROOT logger: ERROR, WARN, INFO, DEBUG, TRACE (default is INFO) +org.slf4j.simpleLogger.defaultLogLevel=INFO +org.slf4j.simpleLogger.showThreadName=true +org.slf4j.simpleLogger.showDateTime=false + +# Log level for specific packages or classes +org.slf4j.simpleLogger.log.io.modelcontextprotocol=INFO diff --git a/samples/kotlin-mcp-server/gradle.properties b/samples/kotlin-mcp-server/gradle.properties new file mode 100644 index 0000000..32cead6 --- /dev/null +++ b/samples/kotlin-mcp-server/gradle.properties @@ -0,0 +1,5 @@ +org.gradle.configuration-cache=true +org.gradle.parallel=true +org.gradle.caching=true + +#mcp.kotlin.overrideVersion=0.0.1-SNAPSHOT diff --git a/samples/kotlin-mcp-server/gradle/libs.versions.toml b/samples/kotlin-mcp-server/gradle/libs.versions.toml new file mode 100644 index 0000000..caccfdf --- /dev/null +++ b/samples/kotlin-mcp-server/gradle/libs.versions.toml @@ -0,0 +1,15 @@ +[versions] +kotlin = "2.2.20" +ktor = "3.3.1" +mcp-kotlin = "0.7.3" +slf4j = "2.0.17" +serialization = "1.9.0" + +[libraries] +ktor-server-cio = { group = "io.ktor", name = "ktor-server-cio", version.ref = "ktor" } +mcp-kotlin-server = { group = "io.modelcontextprotocol", name = "kotlin-sdk-server", version.ref = "mcp-kotlin" } +slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" } + +[plugins] +kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } +kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } diff --git a/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.jar b/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..8bdaf60c75ab801e22807dde59e12a8735a34077 GIT binary patch literal 45457 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 c82=K%QXLKFx%@O{wJjEi6Y56o#$)Bpeg literal 0 HcmV?d00001 diff --git a/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties b/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..2e11132 --- /dev/null +++ b/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/samples/kotlin-mcp-server/gradlew b/samples/kotlin-mcp-server/gradlew new file mode 100755 index 0000000..adff685 --- /dev/null +++ b/samples/kotlin-mcp-server/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/samples/kotlin-mcp-server/gradlew.bat b/samples/kotlin-mcp-server/gradlew.bat new file mode 100644 index 0000000..c4bdd3a --- /dev/null +++ b/samples/kotlin-mcp-server/gradlew.bat @@ -0,0 +1,93 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/samples/kotlin-mcp-server/settings.gradle.kts b/samples/kotlin-mcp-server/settings.gradle.kts index cac44ab..5dc44ba 100644 --- a/samples/kotlin-mcp-server/settings.gradle.kts +++ b/samples/kotlin-mcp-server/settings.gradle.kts @@ -1,10 +1,8 @@ rootProject.name = "kotlin-mcp-server" -pluginManagement { - repositories { - mavenCentral() - gradlePluginPortal() - } +plugins { + // Apply the foojay-resolver plugin to allow automatic download of JDKs + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } dependencyResolutionManagement { @@ -12,9 +10,16 @@ dependencyResolutionManagement { mavenLocal() mavenCentral() } + versionCatalogs { create("libs") { - from(files("../../gradle/libs.versions.toml")) + val mcpKotlinVersion = providers.gradleProperty( + "mcp.kotlin.overrideVersion", + ).orNull + if (mcpKotlinVersion != null) { + logger.lifecycle("Using the override version $mcpKotlinVersion of MCP Kotlin SDK") + version("mcp-kotlin", mcpKotlinVersion) + } } } } diff --git a/samples/kotlin-mcp-server/src/jvmMain/resources/simplelogger.properties b/samples/kotlin-mcp-server/src/jvmMain/resources/simplelogger.properties new file mode 100644 index 0000000..2012bec --- /dev/null +++ b/samples/kotlin-mcp-server/src/jvmMain/resources/simplelogger.properties @@ -0,0 +1,8 @@ +# Level of logging for the ROOT logger: ERROR, WARN, INFO, DEBUG, TRACE (default is INFO) +org.slf4j.simpleLogger.defaultLogLevel=INFO +org.slf4j.simpleLogger.showThreadName=true +org.slf4j.simpleLogger.showDateTime=false + +# Log level for specific packages or classes +org.slf4j.simpleLogger.log.io.modelcontextprotocol=TRACE +org.slf4j.simpleLogger.log.io.ktor=TRACE diff --git a/samples/weather-stdio-server/README.md b/samples/weather-stdio-server/README.md index d1edd7f..0e8efc0 100644 --- a/samples/weather-stdio-server/README.md +++ b/samples/weather-stdio-server/README.md @@ -30,13 +30,13 @@ The project provides: Use the Gradle wrapper to build the application. In a terminal run: ```shell -./gradlew clean build -x test +./gradlew clean build ``` To run the server: ```shell -java -jar build/libs/.jar +java -jar build/libs/weather-stdio-server-0.1.0-all.jar ``` > [!NOTE] diff --git a/samples/weather-stdio-server/build.gradle.kts b/samples/weather-stdio-server/build.gradle.kts index f39057f..2b087d9 100644 --- a/samples/weather-stdio-server/build.gradle.kts +++ b/samples/weather-stdio-server/build.gradle.kts @@ -19,8 +19,11 @@ dependencies { implementation(libs.ktor.server.cio) implementation(libs.ktor.client.cio) implementation(libs.slf4j.simple) + runtimeOnly(libs.kotlin.logging) + runtimeOnly(libs.kotlinx.collections.immutable) testImplementation(kotlin("test")) + testImplementation(libs.mcp.kotlin.client) testImplementation(libs.kotlinx.coroutines.test) } diff --git a/samples/weather-stdio-server/gradle.properties b/samples/weather-stdio-server/gradle.properties new file mode 100644 index 0000000..32cead6 --- /dev/null +++ b/samples/weather-stdio-server/gradle.properties @@ -0,0 +1,5 @@ +org.gradle.configuration-cache=true +org.gradle.parallel=true +org.gradle.caching=true + +#mcp.kotlin.overrideVersion=0.0.1-SNAPSHOT diff --git a/samples/weather-stdio-server/gradle/libs.versions.toml b/samples/weather-stdio-server/gradle/libs.versions.toml new file mode 100644 index 0000000..8921724 --- /dev/null +++ b/samples/weather-stdio-server/gradle/libs.versions.toml @@ -0,0 +1,26 @@ +[versions] +collections-immutable = "0.4.0" +coroutines = "1.10.2" +kotlin = "2.2.20" +ktor = "3.3.0" +logging = "7.0.13" +mcp-kotlin = "0.7.3" +shadow = "9.2.2" +slf4j = "2.0.17" + +[libraries] +kotlin-logging = { group = "io.github.oshai", name = "kotlin-logging-jvm", version.ref = "logging" } +kotlinx-collections-immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable-jvm", version.ref = "collections-immutable" } +kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines" } +ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio", version.ref = "ktor" } +ktor-client-content-negotiation = { group = "io.ktor", name = "ktor-client-content-negotiation", version.ref = "ktor" } +ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json-jvm", version.ref = "ktor" } +ktor-server-cio = { group = "io.ktor", name = "ktor-server-cio", version.ref = "ktor" } +mcp-kotlin-client = { group = "io.modelcontextprotocol", name = "kotlin-sdk-client", version.ref = "mcp-kotlin" } +mcp-kotlin-server = { group = "io.modelcontextprotocol", name = "kotlin-sdk-server", version.ref = "mcp-kotlin" } +slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" } + +[plugins] +kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } +shadow = { id = "com.gradleup.shadow", version.ref = "shadow" } diff --git a/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.jar b/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..8bdaf60c75ab801e22807dde59e12a8735a34077 GIT binary patch literal 45457 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 c82=K%QXLKFx%@O{wJjEi6Y56o#$)Bpeg literal 0 HcmV?d00001 diff --git a/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties b/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..2e11132 --- /dev/null +++ b/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/samples/weather-stdio-server/gradlew b/samples/weather-stdio-server/gradlew new file mode 100755 index 0000000..adff685 --- /dev/null +++ b/samples/weather-stdio-server/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/samples/weather-stdio-server/gradlew.bat b/samples/weather-stdio-server/gradlew.bat new file mode 100644 index 0000000..c4bdd3a --- /dev/null +++ b/samples/weather-stdio-server/gradlew.bat @@ -0,0 +1,93 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/samples/weather-stdio-server/settings.gradle.kts b/samples/weather-stdio-server/settings.gradle.kts index 67e786b..559a770 100644 --- a/samples/weather-stdio-server/settings.gradle.kts +++ b/samples/weather-stdio-server/settings.gradle.kts @@ -12,9 +12,16 @@ dependencyResolutionManagement { mavenLocal() mavenCentral() } + versionCatalogs { create("libs") { - from(files("../../gradle/libs.versions.toml")) + val mcpKotlinVersion = providers.gradleProperty( + "mcp.kotlin.overrideVersion", + ).orNull + if (mcpKotlinVersion != null) { + logger.lifecycle("Using the override version $mcpKotlinVersion of MCP Kotlin SDK") + version("mcp-kotlin", mcpKotlinVersion) + } } } } diff --git a/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt b/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt index 02f90d9..c92ebec 100644 --- a/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt +++ b/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt @@ -1,23 +1,38 @@ package io.modelcontextprotocol.sample.server -import io.ktor.client.* -import io.ktor.client.plugins.* -import io.ktor.client.plugins.contentnegotiation.* -import io.ktor.http.* -import io.ktor.serialization.kotlinx.json.* -import io.ktor.utils.io.streams.* -import io.modelcontextprotocol.kotlin.sdk.* +import io.ktor.client.HttpClient +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.plugins.defaultRequest +import io.ktor.http.ContentType +import io.ktor.http.contentType +import io.ktor.http.headers +import io.ktor.serialization.kotlinx.json.json +import io.ktor.utils.io.core.Input +import io.ktor.utils.io.streams.asInput +import io.modelcontextprotocol.kotlin.sdk.CallToolResult +import io.modelcontextprotocol.kotlin.sdk.Implementation +import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.TextContent +import io.modelcontextprotocol.kotlin.sdk.Tool import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport import kotlinx.coroutines.Job import kotlinx.coroutines.runBlocking +import kotlinx.io.Sink import kotlinx.io.asSink import kotlinx.io.buffered -import kotlinx.serialization.json.* +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.doubleOrNull +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import kotlinx.serialization.json.putJsonObject -// Main function to run the MCP server -fun `run mcp server`() { +/** + * Starts an MCP server that provides weather-related tools for fetching active weather alerts by state and weather forecasts by latitude/longitude. + */ +fun runMcpServer() { // Base URL for the Weather API val baseUrl = "https://api.weather.gov" @@ -33,10 +48,12 @@ fun `run mcp server`() { } // Install content negotiation plugin for JSON serialization/deserialization install(ContentNegotiation) { - json(Json { - ignoreUnknownKeys = true - prettyPrint = true - }) + json( + Json { + ignoreUnknownKeys = true + prettyPrint = true + }, + ) } } @@ -44,11 +61,11 @@ fun `run mcp server`() { val server = Server( Implementation( name = "weather", // Tool name is "weather" - version = "1.0.0" // Version of the implementation + version = "1.0.0", // Version of the implementation ), ServerOptions( - capabilities = ServerCapabilities(tools = ServerCapabilities.Tools(listChanged = true)) - ) + capabilities = ServerCapabilities(tools = ServerCapabilities.Tools(listChanged = true)), + ), ) // Register a tool to fetch weather alerts by state @@ -64,11 +81,11 @@ fun `run mcp server`() { put("description", "Two-letter US state code (e.g. CA, NY)") } }, - required = listOf("state") - ) + required = listOf("state"), + ), ) { request -> val state = request.arguments["state"]?.jsonPrimitive?.content ?: return@addTool CallToolResult( - content = listOf(TextContent("The 'state' parameter is required.")) + content = listOf(TextContent("The 'state' parameter is required.")), ) val alerts = httpClient.getAlerts(state) @@ -91,14 +108,14 @@ fun `run mcp server`() { put("type", "number") } }, - required = listOf("latitude", "longitude") - ) + required = listOf("latitude", "longitude"), + ), ) { request -> val latitude = request.arguments["latitude"]?.jsonPrimitive?.doubleOrNull val longitude = request.arguments["longitude"]?.jsonPrimitive?.doubleOrNull if (latitude == null || longitude == null) { return@addTool CallToolResult( - content = listOf(TextContent("The 'latitude' and 'longitude' parameters are required.")) + content = listOf(TextContent("The 'latitude' and 'longitude' parameters are required.")), ) } @@ -110,7 +127,7 @@ fun `run mcp server`() { // Create a transport using standard IO for server communication val transport = StdioServerTransport( System.`in`.asInput(), - System.out.asSink().buffered() + System.out.asSink().buffered(), ) runBlocking { diff --git a/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/main.kt b/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/main.kt index 21100dd..3d225cd 100644 --- a/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/main.kt +++ b/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/main.kt @@ -1,3 +1,3 @@ package io.modelcontextprotocol.sample.server -fun main() = `run mcp server`() \ No newline at end of file +fun main() = runMcpServer() diff --git a/samples/weather-stdio-server/src/test/kotlin/io/modelcontextprotocol/sample/client/ClientStdio.kt b/samples/weather-stdio-server/src/test/kotlin/io/modelcontextprotocol/sample/client/ClientStdio.kt index 20c2a79..c08d156 100644 --- a/samples/weather-stdio-server/src/test/kotlin/io/modelcontextprotocol/sample/client/ClientStdio.kt +++ b/samples/weather-stdio-server/src/test/kotlin/io/modelcontextprotocol/sample/client/ClientStdio.kt @@ -11,15 +11,15 @@ import kotlinx.io.asSource import kotlinx.io.buffered import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive - +import kotlinx.serialization.json.buildJsonObject fun main(): Unit = runBlocking { - val process = ProcessBuilder("java", "-jar", "build/libs/weather-stdio-server-0.1.0-all.jar") + val process = ProcessBuilder("java", "-jar", "./build/libs/weather-stdio-server-0.1.0-all.jar") .start() val transport = StdioClientTransport( input = process.inputStream.asSource().buffered(), - output = process.outputStream.asSink().buffered() + output = process.outputStream.asSink().buffered(), ) // Initialize the MCP client with client information @@ -29,15 +29,17 @@ fun main(): Unit = runBlocking { client.connect(transport) - - val toolsList = client.listTools()?.tools?.map { it.name } + val toolsList = client.listTools().tools.map { it.name } println("Available Tools = $toolsList") val weatherForecastResult = client.callTool( CallToolRequest( name = "get_forecast", - arguments = JsonObject(mapOf("latitude" to JsonPrimitive(38.5816), "longitude" to JsonPrimitive(-121.4944))) - ) + arguments = buildJsonObject { + put("latitude", JsonPrimitive(38.5816)) + put("longitude", JsonPrimitive(-121.4944)) + }, + ), )?.content?.map { if (it is TextContent) it.text else it.toString() } println("Weather Forcast: ${weatherForecastResult?.joinToString(separator = "\n", prefix = "\n", postfix = "\n")}") @@ -46,11 +48,11 @@ fun main(): Unit = runBlocking { client.callTool( CallToolRequest( name = "get_alerts", - arguments = JsonObject(mapOf("state" to JsonPrimitive("TX"))) - ) + arguments = JsonObject(mapOf("state" to JsonPrimitive("TX"))), + ), )?.content?.map { if (it is TextContent) it.text else it.toString() } println("Alert Response = $alertResult") client.close() -} \ No newline at end of file +} diff --git a/samples/weather-stdio-server/src/test/kotlin/io/modelcontextprotocol/sample/client/FakeTest.kt b/samples/weather-stdio-server/src/test/kotlin/io/modelcontextprotocol/sample/client/FakeTest.kt new file mode 100644 index 0000000..40b4013 --- /dev/null +++ b/samples/weather-stdio-server/src/test/kotlin/io/modelcontextprotocol/sample/client/FakeTest.kt @@ -0,0 +1,11 @@ +package io.modelcontextprotocol.sample.client + +import kotlin.test.Test + +class FakeTest { + + @Test + fun testSomething() { + assert(true) { "Bingo!" } + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 6346267..bead87b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -26,8 +26,21 @@ include( ) // Include sample projects as composite builds if this is the root project -if (gradle.parent == null) { - includeBuild("samples/kotlin-mcp-client") - includeBuild("samples/kotlin-mcp-server") - includeBuild("samples/weather-stdio-server") +// if (gradle.parent == null) { +includeBuild("samples/kotlin-mcp-client") { + dependencySubstitution { + substitute(module("io.modelcontextprotocol:kotlin-sdk-client")) + .using(project(":")) + } } +// includeBuild("samples/kotlin-mcp-server") + +includeBuild("samples/weather-stdio-server") { + dependencySubstitution { + substitute(module("io.modelcontextprotocol:kotlin-sdk-client")) + .using(project(":")) + substitute(module("io.modelcontextprotocol:kotlin-sdk-server")) + .using(project(":")) + } +} +// } From 407df7a3a4298622c39eef2cd0c3ff6841f7f7ee Mon Sep 17 00:00:00 2001 From: M A E R Y O Date: Thu, 23 Oct 2025 22:17:09 +0900 Subject: [PATCH 024/449] Add metadata support to callTool method (#289) According to https://modelcontextprotocol.io/specification/2025-06-18/basic/index#meta, the _meta parameter should be passable when calling tools. While the CallToolRequest class (in types.kt) currently includes _meta in its definition, SDK users cannot actually input _meta when invoking the callTool method. This Pull Request adds a metadata parameter to the CallTool method and includes functionality to convert various data formats without loss. Additionally, to encourage the use of valid Key name formats as defined by the protocol, validation for prefixes has been implemented. --------- Co-authored-by: MAERYO Co-authored-by: devcrocod --- kotlin-sdk-client/api/kotlin-sdk-client.api | 4 +- .../kotlin/sdk/client/Client.kt | 146 ++++++++-- .../sdk/client/ClientMetaParameterTest.kt | 274 ++++++++++++++++++ .../kotlin/sdk/client/MockTransport.kt | 94 ++++++ .../kotlin/AbstractToolIntegrationTest.kt | 2 +- 5 files changed, 500 insertions(+), 20 deletions(-) create mode 100644 kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientMetaParameterTest.kt create mode 100644 kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockTransport.kt diff --git a/kotlin-sdk-client/api/kotlin-sdk-client.api b/kotlin-sdk-client/api/kotlin-sdk-client.api index a785a91..46a7b60 100644 --- a/kotlin-sdk-client/api/kotlin-sdk-client.api +++ b/kotlin-sdk-client/api/kotlin-sdk-client.api @@ -8,9 +8,9 @@ public class io/modelcontextprotocol/kotlin/sdk/client/Client : io/modelcontextp protected fun assertNotificationCapability (Lio/modelcontextprotocol/kotlin/sdk/Method;)V public fun assertRequestHandlerCapability (Lio/modelcontextprotocol/kotlin/sdk/Method;)V public final fun callTool (Lio/modelcontextprotocol/kotlin/sdk/CallToolRequest;ZLio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun callTool (Ljava/lang/String;Ljava/util/Map;ZLio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun callTool (Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;ZLio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun callTool$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/CallToolRequest;ZLio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public static synthetic fun callTool$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Ljava/lang/String;Ljava/util/Map;ZLio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun callTool$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;ZLio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun complete (Lio/modelcontextprotocol/kotlin/sdk/CompleteRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun complete$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/CompleteRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public fun connect (Lio/modelcontextprotocol/kotlin/sdk/shared/Transport;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt index 95a5bc5..56fd1ca 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt @@ -50,10 +50,14 @@ import kotlinx.atomicfu.update import kotlinx.collections.immutable.minus import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.toPersistentSet +import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonNull import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.add +import kotlinx.serialization.json.buildJsonArray +import kotlinx.serialization.json.buildJsonObject import kotlin.coroutines.cancellation.CancellationException private val logger = KotlinLogging.logger {} @@ -210,17 +214,13 @@ public open class Client(private val clientInfo: Implementation, options: Client } } - Method.Defined.ToolsCall, - Method.Defined.ToolsList, - -> { + Method.Defined.ToolsCall, Method.Defined.ToolsList -> { if (serverCapabilities?.tools == null) { throw IllegalStateException("Server does not support tools (required for $method)") } } - Method.Defined.Initialize, - Method.Defined.Ping, - -> { + Method.Defined.Initialize, Method.Defined.Ping -> { // No specific capability required } @@ -405,10 +405,14 @@ public open class Client(private val clientInfo: Implementation, options: Client ): EmptyRequestResult = request(request, options) /** - * Calls a tool on the server by name, passing the specified arguments. + * Calls a tool on the server by name, passing the specified arguments and metadata. * * @param name The name of the tool to call. * @param arguments A map of argument names to values for the tool. + * @param meta A map of metadata key-value pairs. Keys must follow MCP specification format. + * - Optional prefix: dot-separated labels followed by slash (e.g., "api.example.com/") + * - Name: alphanumeric start/end, may contain hyphens, underscores, dots, alphanumerics + * - Reserved prefixes starting with "mcp" or "modelcontextprotocol" are forbidden * @param compatibility Whether to use compatibility mode for older protocol versions. * @param options Optional request options. * @return The result of the tool call, or `null` if none. @@ -417,23 +421,19 @@ public open class Client(private val clientInfo: Implementation, options: Client public suspend fun callTool( name: String, arguments: Map, + meta: Map = emptyMap(), compatibility: Boolean = false, options: RequestOptions? = null, ): CallToolResultBase? { - val jsonArguments = arguments.mapValues { (_, value) -> - when (value) { - is String -> JsonPrimitive(value) - is Number -> JsonPrimitive(value) - is Boolean -> JsonPrimitive(value) - is JsonElement -> value - null -> JsonNull - else -> JsonPrimitive(value.toString()) - } - } + validateMetaKeys(meta.keys) + + val jsonArguments = convertToJsonMap(arguments) + val jsonMeta = convertToJsonMap(meta) val request = CallToolRequest( name = name, arguments = JsonObject(jsonArguments), + _meta = JsonObject(jsonMeta), ) return callTool(request, compatibility, options) } @@ -588,4 +588,116 @@ public open class Client(private val clientInfo: Implementation, options: Client val rootList = roots.value.values.toList() return ListRootsResult(rootList) } + + /** + * Validates meta keys according to MCP specification. + * + * Key format: [prefix/]name + * - Prefix (optional): dot-separated labels + slash + * - Reserved prefixes contain "modelcontextprotocol" or "mcp" as complete labels + * - Name: alphanumeric start/end, may contain hyphens, underscores, dots (empty allowed) + */ + private fun validateMetaKeys(keys: Set) { + val labelPattern = Regex("[a-zA-Z]([a-zA-Z0-9-]*[a-zA-Z0-9])?") + val namePattern = Regex("[a-zA-Z0-9]([a-zA-Z0-9._-]*[a-zA-Z0-9])?") + + keys.forEach { key -> + require(key.isNotEmpty()) { "Meta key cannot be empty" } + + val (prefix, name) = key.split('/', limit = 2).let { parts -> + when (parts.size) { + 1 -> null to parts[0] + 2 -> parts[0] to parts[1] + else -> throw IllegalArgumentException("Unexpected split result for key: $key") + } + } + + // Validate prefix if present + prefix?.let { + require(it.isNotEmpty()) { "Invalid _meta key '$key': prefix cannot be empty" } + + val labels = it.split('.') + require(labels.all { label -> label.matches(labelPattern) }) { + "Invalid _meta key '$key': prefix labels must start with a letter, end with letter/digit, and contain only letters, digits, or hyphens" + } + + require( + labels.none { label -> + label.equals("modelcontextprotocol", ignoreCase = true) || + label.equals("mcp", ignoreCase = true) + }, + ) { + "Invalid _meta key '$key': prefix cannot contain reserved labels 'modelcontextprotocol' or 'mcp'" + } + } + + // Validate name (empty allowed) + require(name.isEmpty() || name.matches(namePattern)) { + "Invalid _meta key '$key': name must start and end with alphanumeric characters, and contain only alphanumerics, hyphens, underscores, or dots" + } + } + } + + private fun convertToJsonMap(map: Map): Map = map.mapValues { (key, value) -> + try { + convertToJsonElement(value) + } catch (e: Exception) { + logger.warn { "Failed to convert value for key '$key': ${e.message}. Using string representation." } + JsonPrimitive(value.toString()) + } + } + + @OptIn(ExperimentalUnsignedTypes::class, ExperimentalSerializationApi::class) + private fun convertToJsonElement(value: Any?): JsonElement = when (value) { + null -> JsonNull + + is JsonElement -> value + + is String -> JsonPrimitive(value) + + is Number -> JsonPrimitive(value) + + is Boolean -> JsonPrimitive(value) + + is Char -> JsonPrimitive(value.toString()) + + is Enum<*> -> JsonPrimitive(value.name) + + is Map<*, *> -> buildJsonObject { value.forEach { (k, v) -> put(k.toString(), convertToJsonElement(v)) } } + + is Collection<*> -> buildJsonArray { value.forEach { add(convertToJsonElement(it)) } } + + is Array<*> -> buildJsonArray { value.forEach { add(convertToJsonElement(it)) } } + + // Primitive arrays + is IntArray -> buildJsonArray { value.forEach { add(it) } } + + is LongArray -> buildJsonArray { value.forEach { add(it) } } + + is FloatArray -> buildJsonArray { value.forEach { add(it) } } + + is DoubleArray -> buildJsonArray { value.forEach { add(it) } } + + is BooleanArray -> buildJsonArray { value.forEach { add(it) } } + + is ShortArray -> buildJsonArray { value.forEach { add(it) } } + + is ByteArray -> buildJsonArray { value.forEach { add(it) } } + + is CharArray -> buildJsonArray { value.forEach { add(it.toString()) } } + + // Unsigned arrays + is UIntArray -> buildJsonArray { value.forEach { add(JsonPrimitive(it)) } } + + is ULongArray -> buildJsonArray { value.forEach { add(JsonPrimitive(it)) } } + + is UShortArray -> buildJsonArray { value.forEach { add(JsonPrimitive(it)) } } + + is UByteArray -> buildJsonArray { value.forEach { add(JsonPrimitive(it)) } } + + else -> { + logger.debug { "Converting unknown type ${value::class} to string: $value" } + JsonPrimitive(value.toString()) + } + } } diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientMetaParameterTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientMetaParameterTest.kt new file mode 100644 index 0000000..e706107 --- /dev/null +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientMetaParameterTest.kt @@ -0,0 +1,274 @@ +package io.modelcontextprotocol.kotlin.sdk.client + +import io.modelcontextprotocol.kotlin.sdk.Implementation +import io.modelcontextprotocol.kotlin.sdk.JSONRPCRequest +import kotlinx.coroutines.test.runTest +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.boolean +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonPrimitive +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertContains +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertTrue + +/** + * Comprehensive test suite for MCP Client meta parameter functionality + * + * Tests cover: + * - Meta key validation according to MCP specification + * - JSON type conversion for various data types + * - Error handling for invalid meta keys + * - Integration with callTool method + */ +class ClientMetaParameterTest { + + private lateinit var client: Client + private lateinit var mockTransport: MockTransport + private val clientInfo = Implementation("test-client", "1.0.0") + + @BeforeTest + fun setup() = runTest { + mockTransport = MockTransport() + client = Client(clientInfo = clientInfo) + mockTransport.setupInitializationResponse() + client.connect(mockTransport) + } + + @Test + fun `should accept valid meta keys without throwing exception`() = runTest { + val validMeta = buildMap { + put("simple-key", "value1") + put("api.example.com/version", "1.0") + put("com.company.app/setting", "enabled") + put("retry_count", 3) + put("user.preference", true) + put("valid123", "alphanumeric") + put("multi.dot.name", "multiple-dots") + put("under_score", "underscore") + put("hyphen-dash", "hyphen") + put("org.apache.kafka/consumer-config", "complex-valid-prefix") + } + + val result = runCatching { + client.callTool("test-tool", mapOf("arg" to "value"), validMeta) + } + + assertTrue(result.isSuccess, "Valid meta keys should not cause exceptions") + mockTransport.lastJsonRpcRequest()?.let { request -> + val params = request.params as JsonObject + assertTrue(params.containsKey("_meta"), "Request should contain _meta field") + val metaField = params["_meta"] as JsonObject + + // Verify all meta keys are present + assertEquals(validMeta.size, metaField.size, "All meta keys should be included") + + // Verify specific key-value pairs + assertEquals("value1", metaField["simple-key"]?.jsonPrimitive?.content) + assertEquals("1.0", metaField["api.example.com/version"]?.jsonPrimitive?.content) + assertEquals("enabled", metaField["com.company.app/setting"]?.jsonPrimitive?.content) + assertEquals(3, metaField["retry_count"]?.jsonPrimitive?.int) + assertEquals(true, metaField["user.preference"]?.jsonPrimitive?.boolean) + assertEquals("alphanumeric", metaField["valid123"]?.jsonPrimitive?.content) + assertEquals("multiple-dots", metaField["multi.dot.name"]?.jsonPrimitive?.content) + assertEquals("underscore", metaField["under_score"]?.jsonPrimitive?.content) + assertEquals("hyphen", metaField["hyphen-dash"]?.jsonPrimitive?.content) + assertEquals("complex-valid-prefix", metaField["org.apache.kafka/consumer-config"]?.jsonPrimitive?.content) + } + } + + @Test + fun `should accept edge case valid prefixes and names`() = runTest { + val edgeCaseValidMeta = buildMap { + put("a/", "single-char-prefix-empty-name") // empty name is allowed + put("a1-b2/test", "alphanumeric-hyphen-prefix") + put("long.domain.name.here/config", "long-prefix") + put("x/a", "minimal-valid-key") + put("test123", "alphanumeric-name-only") + } + + val result = runCatching { + client.callTool("test-tool", emptyMap(), edgeCaseValidMeta) + } + + assertTrue(result.isSuccess, "Edge case valid meta keys should be accepted") + } + + @Test + fun `should reject mcp reserved prefix`() = runTest { + val invalidMeta = mapOf("mcp/internal" to "value") + + val exception = assertFailsWith { + client.callTool("test-tool", emptyMap(), invalidMeta) + } + + assertContains( + charSequence = exception.message ?: "", + other = "Invalid _meta key", + ) + } + + @Test + fun `should reject modelcontextprotocol reserved prefix`() = runTest { + val invalidMeta = mapOf("modelcontextprotocol/config" to "value") + + val exception = assertFailsWith { + client.callTool("test-tool", emptyMap(), invalidMeta) + } + + assertContains( + charSequence = exception.message ?: "", + other = "Invalid _meta key", + ) + } + + @Test + fun `should reject nested reserved prefixes`() = runTest { + val invalidKeys = listOf( + "api.mcp.io/setting", + "com.modelcontextprotocol.test/value", + "example.mcp/data", + "subdomain.mcp.com/config", + "app.modelcontextprotocol.dev/setting", + "test.mcp/value", + "service.modelcontextprotocol/data", + ) + + invalidKeys.forEach { key -> + val exception = assertFailsWith( + message = "Should reject nested reserved key: $key", + ) { + client.callTool("test-tool", emptyMap(), mapOf(key to "value")) + } + assertContains( + charSequence = exception.message ?: "", + other = "Invalid _meta key", + ) + } + } + + @Test + fun `should reject case-insensitive reserved prefixes`() = runTest { + val invalidKeys = listOf( + "MCP/internal", + "Mcp/config", + "mCp/setting", + "MODELCONTEXTPROTOCOL/data", + "ModelContextProtocol/value", + "modelContextProtocol/test", + ) + + invalidKeys.forEach { key -> + val exception = assertFailsWith( + message = "Should reject case-insensitive reserved key: $key", + ) { + client.callTool("test-tool", emptyMap(), mapOf(key to "value")) + } + assertContains( + charSequence = exception.message ?: "", + other = "Invalid _meta key", + ) + } + } + + @Test + fun `should reject invalid key formats`() = runTest { + val invalidKeys = listOf( + "", // empty key - not allowed at key level + "/invalid", // starts with slash + "-invalid", // starts with hyphen + ".invalid", // starts with dot + "in valid", // contains space + "api../test", // consecutive dots + "api./test", // label ends with dot + ) + + invalidKeys.forEach { key -> + assertFailsWith( + message = "Should reject invalid key format: '$key'", + ) { + client.callTool("test-tool", emptyMap(), mapOf(key to "value")) + } + } + } + + @Test + fun `should convert various data types to JSON correctly`() = runTest { + val complexMeta = createComplexMetaData() + + val result = runCatching { + client.callTool( + "test-tool", + emptyMap(), + complexMeta, + ) + } + + assertTrue(result.isSuccess, "Complex data type conversion should not throw exceptions") + + mockTransport.lastJsonRpcRequest()?.let { request -> + assertEquals("tools/call", request.method) + val params = request.params as JsonObject + assertTrue(params.containsKey("_meta"), "Request should contain _meta field") + } + } + + @Test + fun `should handle nested map structures correctly`() = runTest { + val nestedMeta = buildNestedConfiguration() + + val result = runCatching { + client.callTool("test-tool", emptyMap(), nestedMeta) + } + + assertTrue(result.isSuccess) + + mockTransport.lastJsonRpcRequest()?.let { request -> + val params = request.params as JsonObject + val metaField = params["_meta"] as JsonObject + assertTrue(metaField.containsKey("config")) + } + } + + @Test + fun `should include empty meta object when meta parameter not provided`() = runTest { + client.callTool("test-tool", mapOf("arg" to "value")) + + mockTransport.lastJsonRpcRequest()?.let { request -> + val params = request.params as JsonObject + val metaField = params["_meta"] as JsonObject + assertTrue(metaField.isEmpty(), "Meta field should be empty when not provided") + } + } + + private fun createComplexMetaData(): Map = buildMap { + put("string", "text") + put("number", 42) + put("boolean", true) + put("null_value", null) + put("list", listOf(1, 2, 3)) + put("map", mapOf("nested" to "value")) + put("enum", "STRING") + put("int_array", intArrayOf(1, 2, 3)) + } + + private fun buildNestedConfiguration(): Map = buildMap { + put( + "config", + buildMap { + put( + "database", + buildMap { + put("host", "localhost") + put("port", 5432) + }, + ) + put("features", listOf("feature1", "feature2")) + }, + ) + } +} + +suspend fun MockTransport.lastJsonRpcRequest(): JSONRPCRequest? = getSentMessages().lastOrNull() as? JSONRPCRequest diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockTransport.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockTransport.kt new file mode 100644 index 0000000..c987619 --- /dev/null +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockTransport.kt @@ -0,0 +1,94 @@ +package io.modelcontextprotocol.kotlin.sdk.client + +import io.modelcontextprotocol.kotlin.sdk.CallToolResult +import io.modelcontextprotocol.kotlin.sdk.Implementation +import io.modelcontextprotocol.kotlin.sdk.InitializeResult +import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage +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 kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock + +class MockTransport : Transport { + private val _sentMessages = mutableListOf() + private val _receivedMessages = mutableListOf() + private val mutex = Mutex() + + suspend fun getSentMessages() = mutex.withLock { _sentMessages.toList() } + suspend fun getReceivedMessages() = mutex.withLock { _receivedMessages.toList() } + + private var onMessageBlock: (suspend (JSONRPCMessage) -> Unit)? = null + private var onCloseBlock: (() -> Unit)? = null + private var onErrorBlock: ((Throwable) -> Unit)? = null + + override suspend fun start() = Unit + + override suspend fun send(message: JSONRPCMessage) { + mutex.withLock { + _sentMessages += message + } + + // Auto-respond to initialization and tool calls + when (message) { + is JSONRPCRequest -> { + when (message.method) { + "initialize" -> { + val initResponse = JSONRPCResponse( + id = message.id, + result = InitializeResult( + protocolVersion = "2024-11-05", + capabilities = ServerCapabilities( + tools = ServerCapabilities.Tools(listChanged = null), + ), + serverInfo = Implementation("mock-server", "1.0.0"), + ), + ) + onMessageBlock?.invoke(initResponse) + } + + "tools/call" -> { + val toolResponse = JSONRPCResponse( + id = message.id, + result = CallToolResult( + content = listOf(), + isError = false, + ), + ) + onMessageBlock?.invoke(toolResponse) + } + } + } + + else -> { + // Handle other message types if needed + } + } + } + + override suspend fun close() { + onCloseBlock?.invoke() + } + + override fun onMessage(block: suspend (JSONRPCMessage) -> Unit) { + onMessageBlock = { message -> + mutex.withLock { + _receivedMessages += message + } + block(message) + } + } + + override fun onClose(block: () -> Unit) { + onCloseBlock = block + } + + override fun onError(block: (Throwable) -> Unit) { + onErrorBlock = block + } + + fun setupInitializationResponse() { + // This method helps set up the mock for proper initialization + } +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt index 3b0de29..a9a8f27 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt @@ -484,7 +484,7 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() { "result" : 11.0, "formattedResult" : "11,000", "precision" : 3, - "tags" : [ ] + "tags" : ["test", "calculator", "integration"] } """.trimIndent() From 8f9f484e33c080076d435dfc0eae7f9538695a36 Mon Sep 17 00:00:00 2001 From: M A E R Y O Date: Thu, 23 Oct 2025 22:44:29 +0900 Subject: [PATCH 025/449] Fix decimal separator format in calculator tool tests (#315) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Changes - Updated `DecimalFormatSymbols` to use period as decimal separator (Locale.US) - Fixed test expectations in `testComplexInputSchemaTool`: - `formattedResult`: `"11,000"` → `"11.000"` - `tags`: Added actual test values `["test", "calculator", "integration"]` - Fixed test expectations in `testComplexNestedSchema`: - `formattedResult`: `"0,00"` → `"0.00"` ## Motivation 1. **Standard Decimal Notation**: Using a comma as a decimal separator could be confusing as it's commonly used for thousands separators in many locales. Standardizing on the period (`.`) follows international conventions and makes the output clearer. 2. **Test Data Correction**: During the Validate PR workflow for #289, discovered that the `tags` array in the `expectedContent` variable of `testComplexInputSchemaTool()` was incorrectly set to an empty array, which didn't match the actual input values being tested. --------- Co-authored-by: MAERYO --- .../sdk/integration/kotlin/AbstractToolIntegrationTest.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt index a9a8f27..41a951b 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt @@ -289,7 +289,7 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() { } val pattern = if (precision > 0) "0." + "0".repeat(precision) else "0" - val symbols = DecimalFormatSymbols(Locale.US).apply { decimalSeparator = ',' } + val symbols = DecimalFormatSymbols(Locale.US).apply { decimalSeparator = '.' } val df = DecimalFormat(pattern, symbols).apply { isGroupingUsed = false } val formattedResult = df.format(result) @@ -482,7 +482,7 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() { "a" : 5.5, "b" : 2.0, "result" : 11.0, - "formattedResult" : "11,000", + "formattedResult" : "11.000", "precision" : 3, "tags" : ["test", "calculator", "integration"] } @@ -633,7 +633,7 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() { "a": 0.0, "b": 0.0, "result": 0.0, - "formattedResult": "0,00", + "formattedResult": "0.00", "precision": 2, "tags": [] } From 93c23412104cd3ab13aa82f80ba74c60edd6633c Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Thu, 23 Oct 2025 19:41:57 +0300 Subject: [PATCH 026/449] Exclude samples from composite gradle build (#337) Exclude samples from composite gradle build ## Motivation and Context Circular dependency in composite build. Let's make samples more independent ## How Has This Been Tested? CI ## Breaking Changes No ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [x] Build configuration update ## Checklist - [x] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [x] My code follows the repository's style guidelines - [ ] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed ## Additional context --- settings.gradle.kts | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index bead87b..368bff3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -24,23 +24,3 @@ include( ":kotlin-sdk", ":kotlin-sdk-test", ) - -// Include sample projects as composite builds if this is the root project -// if (gradle.parent == null) { -includeBuild("samples/kotlin-mcp-client") { - dependencySubstitution { - substitute(module("io.modelcontextprotocol:kotlin-sdk-client")) - .using(project(":")) - } -} -// includeBuild("samples/kotlin-mcp-server") - -includeBuild("samples/weather-stdio-server") { - dependencySubstitution { - substitute(module("io.modelcontextprotocol:kotlin-sdk-client")) - .using(project(":")) - substitute(module("io.modelcontextprotocol:kotlin-sdk-server")) - .using(project(":")) - } -} -// } From 2163b23d2c5d3ac4a8064acda37523eab5610e8f Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Thu, 23 Oct 2025 20:06:40 +0200 Subject: [PATCH 027/449] Refactor testClientConnectWithInvalidJson (#338) simplification ## Breaking Changes ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [x] Refactoring ## 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 - [x] I have added appropriate error handling - [ ] I have added or updated documentation as needed --- .../StreamableHttpClientTransportTest.kt | 29 +++++++------------ 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransportTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransportTest.kt index 4c97272..a72bd85 100644 --- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransportTest.kt +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransportTest.kt @@ -19,7 +19,6 @@ import io.modelcontextprotocol.kotlin.sdk.JSONRPCRequest import io.modelcontextprotocol.kotlin.sdk.RequestId import io.modelcontextprotocol.kotlin.sdk.shared.McpJson import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.delay import kotlinx.coroutines.test.runTest import kotlinx.coroutines.withContext @@ -30,9 +29,9 @@ import kotlinx.serialization.json.buildJsonObject import kotlin.test.Ignore import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFailsWith import kotlin.test.assertNull import kotlin.test.assertTrue -import kotlin.test.fail import kotlin.time.Duration.Companion.seconds class StreamableHttpClientTransportTest { @@ -405,26 +404,18 @@ class StreamableHttpClientTransportTest { ), ) - runCatching { + try { // Real time-keeping is needed; otherwise Protocol will always throw TimeoutCancellationException in tests - withContext(Dispatchers.Default.limitedParallelism(1)) { - withTimeout(5.seconds) { - client.connect(transport) + assertFailsWith( + message = "Expected client.connect to fail on invalid JSON response", + ) { + withContext(Dispatchers.Default.limitedParallelism(1)) { + withTimeout(5.seconds) { + client.connect(transport) + } } } - }.onSuccess { - fail("Expected client.connect to fail on invalid JSON response") - }.onFailure { e -> - when (e) { - is TimeoutCancellationException -> fail("Client connect caused a hang", e) - - is IllegalStateException -> { - // Expected behavior: connect finishes and fails with an exception. - } - - else -> fail("Unexpected exception during client.connect", e) - } - }.also { + } finally { transport.close() } } From ff3e4e045e1ddfd549200807256f611d137cd9d1 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Thu, 23 Oct 2025 20:48:42 +0200 Subject: [PATCH 028/449] Update kotlin, ktor, dokka (#334) kotlin: 2.2.10 -> 2.2.21 ktor: 3.3.0 -> 3.3.1 dokka: 2.0.0 -> 2.1.0 ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] 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 --- gradle/libs.versions.toml | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5002dec..dd0edc4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] # plugins version -kotlin = "2.2.10" -dokka = "2.0.0" +kotlin = "2.2.21" +dokka = "2.1.0" atomicfu = "0.29.0" ktlint = "13.1.0" kover = "0.9.3" @@ -14,18 +14,13 @@ serialization = "1.9.0" collections-immutable = "0.4.0" coroutines = "1.10.2" kotlinx-io = "0.8.0" -ktor = "3.3.0" +ktor = "3.3.1" logging = "7.0.13" slf4j = "2.0.17" kotest = "6.0.4" awaitility = "4.3.0" mokksy = "0.6.1" -# Samples -mcp-kotlin = "0.7.3" -anthropic = "2.9.0" -shadow = "9.2.2" - [libraries] # Plugins kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } @@ -47,9 +42,7 @@ ktor-bom = { group = "io.ktor", name = "ktor-bom", version.ref = "ktor" } ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" } ktor-client-logging = { group = "io.ktor", name = "ktor-client-logging"} ktor-client-apache5 = { group = "io.ktor", name = "ktor-client-apache5" } -ktor-client-okhttp = { group = "io.ktor", name = "ktor-client-okhttp" } ktor-server-sse = { group = "io.ktor", name = "ktor-server-sse", version.ref = "ktor" } -ktor-server-netty = { group = "io.ktor", name = "ktor-server-netty" } ktor-server-websockets = { group = "io.ktor", name = "ktor-server-websockets", version.ref = "ktor" } ktor-server-core = { group = "io.ktor", name = "ktor-server-core", version.ref = "ktor" } @@ -67,19 +60,8 @@ netty-bom = { group = "io.netty", name = "netty-bom", version.ref = "netty" } # Samples ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio", version.ref = "ktor" } ktor-server-cio = { group = "io.ktor", name = "ktor-server-cio", version.ref = "ktor" } -mcp-kotlin-client = { group = "io.modelcontextprotocol", name = "kotlin-sdk-client", version.ref = "mcp-kotlin" } -mcp-kotlin-server = { group = "io.modelcontextprotocol", name = "kotlin-sdk-server", version.ref = "mcp-kotlin" } -anthropic-java = { group = "com.anthropic", name = "anthropic-java", version.ref = "anthropic" } -ktor-client-content-negotiation = { group = "io.ktor", name = "ktor-client-content-negotiation", version.ref = "ktor" } -ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" } [plugins] kotlinx-binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binaryCompatibilityValidatorPlugin" } ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" } kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } - -# Samples -kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } -kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } -kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } -shadow = { id = "com.gradleup.shadow", version.ref = "shadow" } From ebd5a27739fea85c4b0542aa301e366dca225f6f Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Fri, 24 Oct 2025 14:40:41 +0200 Subject: [PATCH 029/449] downgrade Ktor to 3.2.3 (#342) - ktor 3.2.3 - Tests are ignored because Ktor 3.2.3 has issues with wasm in the test environment. However, it works outside of tests. All other targets pass these tests successfully. ## Motivation and Context compatibility with older versions of Ktor --- gradle/libs.versions.toml | 2 +- .../kotlin/sdk/client/SseTransportTest.kt | 1 + .../kotlin/sdk/integration/SseIntegrationTest.kt | 4 ++++ .../kotlin/sdk/integration/WebSocketIntegrationTest.kt | 4 ++++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dd0edc4..d937c39 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,7 +14,7 @@ serialization = "1.9.0" collections-immutable = "0.4.0" coroutines = "1.10.2" kotlinx-io = "0.8.0" -ktor = "3.3.1" +ktor = "3.2.3" logging = "7.0.13" slf4j = "2.0.17" kotest = "6.0.4" diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt index 597a3cb..fa4ae02 100644 --- a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt +++ b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt @@ -37,6 +37,7 @@ class SseTransportTest : BaseTransportTest() { } @Test + @Ignore // Ignored because it doesn’t work with wasm/js in Ktor 3.2.3 fun `should start then close cleanly`() = runTest { val server = embeddedServer(CIO, port = 0) { install(ServerSSE) diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/SseIntegrationTest.kt b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/SseIntegrationTest.kt index 31b0fd0..2971812 100644 --- a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/SseIntegrationTest.kt +++ b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/SseIntegrationTest.kt @@ -23,6 +23,7 @@ import io.modelcontextprotocol.kotlin.sdk.server.mcp import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.test.runTest import kotlinx.coroutines.withContext +import kotlin.test.Ignore import kotlin.test.Test import kotlin.test.assertTrue import kotlin.time.Duration.Companion.seconds @@ -32,6 +33,7 @@ import io.ktor.server.sse.SSE as ServerSSE class SseIntegrationTest { @Test + @Ignore // Ignored because it doesn’t work with wasm/js in Ktor 3.2.3 fun `client should be able to connect to sse server`() = runTest(timeout = 5.seconds) { var server: EmbeddedServer? = null var client: Client? = null @@ -56,6 +58,7 @@ class SseIntegrationTest { * 3. Observe that Client A receives a response related to it. */ @Test + @Ignore // Ignored because it doesn’t work with wasm/js in Ktor 3.2.3 fun `single sse connection`() = runTest(timeout = 5.seconds) { var server: EmbeddedServer? = null var client: Client? = null @@ -83,6 +86,7 @@ class SseIntegrationTest { * 4. Observe that Client B (connection #2) receives a response related to sessionId#1. */ @Test + @Ignore // Ignored because it doesn’t work with wasm/js in Ktor 3.2.3 fun `multiple sse connections`() = runTest(timeout = 5.seconds) { var server: EmbeddedServer? = null var clientA: Client? = null diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/WebSocketIntegrationTest.kt b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/WebSocketIntegrationTest.kt index 46a57ca..2a815dd 100644 --- a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/WebSocketIntegrationTest.kt +++ b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/WebSocketIntegrationTest.kt @@ -22,6 +22,7 @@ import io.modelcontextprotocol.kotlin.sdk.server.mcpWebSocket import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.test.runTest import kotlinx.coroutines.withContext +import kotlin.test.Ignore import kotlin.test.Test import kotlin.test.assertTrue import kotlin.time.Duration.Companion.seconds @@ -33,6 +34,7 @@ import io.ktor.server.websocket.WebSockets as ServerWebSockets class WebSocketIntegrationTest { @Test + @Ignore // Ignored because it doesn’t work with wasm/js in Ktor 3.2.3 fun `client should be able to connect to websocket server 2`() = runTest(timeout = 5.seconds) { var server: EmbeddedServer? = null var client: Client? = null @@ -57,6 +59,7 @@ class WebSocketIntegrationTest { * 3. Observe that Client A receives a response related to it. */ @Test + @Ignore // Ignored because it doesn’t work with wasm/js in Ktor 3.2.3 fun `single websocket connection`() = runTest(timeout = 5.seconds) { var server: EmbeddedServer? = null var client: Client? = null @@ -85,6 +88,7 @@ class WebSocketIntegrationTest { * 4. Observe that Client B (connection #2) receives a response related to sessionId#1. */ @Test + @Ignore // Ignored because it doesn’t work with wasm/js in Ktor 3.2.3 fun `multiple websocket connections`() = runTest(timeout = 5.seconds) { var server: EmbeddedServer? = null var clientA: Client? = null From 64a8262d9dc213a0784c002cfab2df147fb8208d Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Fri, 24 Oct 2025 14:51:32 +0200 Subject: [PATCH 030/449] Prepare release 0.7.4 (#341) Bump version sdk to 0.7.4 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 2dadb66..0db4499 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,4 +14,4 @@ kotlin.mpp.enableCInteropCommonization=true kotlin.native.ignoreDisabledTargets=true group=io.modelcontextprotocol -version=0.7.4-SNAPSHOT +version=0.7.4 From 115066ab5d8ac29d2e5ccbcdb804789bc7c92d19 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 13:39:06 +0100 Subject: [PATCH 031/449] Bump actions/upload-artifact from 4 to 5 (#348) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.

Release notes

Sourced from actions/upload-artifact's releases.

v5.0.0

What's Changed

BREAKING CHANGE: this update supports Node v24.x. This is not a breaking change per-se but we're treating it as such.

New Contributors

Full Changelog: https://github.com/actions/upload-artifact/compare/v4...v5.0.0

v4.6.2

What's Changed

New Contributors

Full Changelog: https://github.com/actions/upload-artifact/compare/v4...v4.6.2

v4.6.1

What's Changed

Full Changelog: https://github.com/actions/upload-artifact/compare/v4...v4.6.1

v4.6.0

What's Changed

Full Changelog: https://github.com/actions/upload-artifact/compare/v4...v4.6.0

v4.5.0

What's Changed

New Contributors

... (truncated)

Commits
  • 330a01c Merge pull request #734 from actions/danwkennedy/prepare-5.0.0
  • 03f2824 Update github.dep.yml
  • 905a1ec Prepare v5.0.0
  • 2d9f9cd Merge pull request #725 from patrikpolyak/patch-1
  • 9687587 Merge branch 'main' into patch-1
  • 2848b2c Merge pull request #727 from danwkennedy/patch-1
  • 9b51177 Spell out the first use of GHES
  • cd231ca Update GHES guidance to include reference to Node 20 version
  • de65e23 Merge pull request #712 from actions/nebuk89-patch-1
  • 8747d8c Update README.md
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/upload-artifact&package-manager=github_actions&previous-version=4&new-version=5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- .github/workflows/samples.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3d4c225..979d54f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -59,7 +59,7 @@ jobs: - name: Upload Reports if: ${{ !cancelled() }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: reports path: | diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index 6ed5179..f023e4f 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -51,7 +51,7 @@ jobs: - name: Upload Reports if: ${{ !cancelled() }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: reports-${{ matrix.sample }} path: | From 7b7cbebe1d08a3cf512f401864d7cd1432b492e5 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Mon, 27 Oct 2025 15:01:14 +0100 Subject: [PATCH 032/449] Add AGENTS.md (#349) Introduce `AGENTS.md` documenting repository layout, build/test commands, API compatibility checks, code conventions, logging/error handling guidance, and the PR/review checklist for the Kotlin MCP SDK. --- AGENTS.md | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..5b49a6d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,95 @@ +# kotlin-mcp-sdk + +MCP Kotlin SDK — Kotlin Multiplatform implementation of the Model Context Protocol. + +## Repository Layout +- `kotlin-sdk-core`: Core protocol types, transport abstractions, WebSocket implementation +- `kotlin-sdk-client`: Client transports (STDIO, SSE, StreamableHttp, WebSocket) +- `kotlin-sdk-server`: Server transports + Ktor integration (STDIO, SSE, WebSocket) +- `kotlin-sdk`: Umbrella module that aggregates all three above +- `kotlin-sdk-test`: Shared test utilities +- `samples/`: Three sample projects (composite builds) + +## General Guidance +- Avoid changing public API signatures. Run `./gradlew apiCheck` before every commit. +- **Explicit API mode** is strict: all public APIs must have explicit visibility modifiers and return types. +- Anything under an `internal` directory is not part of the public API and may change freely. +- Package structure follows: `io.modelcontextprotocol.kotlin.sdk.*` +- The SDK targets Kotlin 2.2+ and JVM 1.8+ as minimum. + +## Building and Testing +1. Build the project: + ```bash + ./gradlew build + ``` +2. Run tests. To save time, run only the module you changed: + ```bash + ./gradlew :kotlin-sdk-core:test + ./gradlew :kotlin-sdk-client:test + ./gradlew :kotlin-sdk-server:test + ``` +3. For platform-specific tests: + ```bash + ./gradlew jsTest + ./gradlew wasmJsTest + ``` +4. Check binary compatibility (required before commit): + ```bash + ./gradlew apiCheck + ``` +5. If you intentionally changed public APIs, update the API dump: + ```bash + ./gradlew apiDump + ``` + +## Tests +- All tests for each module are located in `src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/` +- Platform-specific tests go in `src/jvmTest/`, `src/jsTest/`, etc. +- Use Kotest assertions (`shouldBe`, `shouldContain`, etc.) for readable test failures. +- Use `shouldMatchJson` from Kotest for JSON validation. +- Mock Ktor HTTP clients using `MockEngine` from `io.ktor:ktor-client-mock`. +- Always add tests for new features or bug fixes, even if not explicitly requested. + +## Code Conventions + +### Multiplatform Patterns +- Use `expect`/`actual` pattern for platform-specific implementations in `utils.*` files. +- Test changes on JVM first, then verify platform-specific behavior if needed. +- Supported targets: JVM (1.8+), JS/Wasm, iOS, watchOS, tvOS. + +### Serialization +- Use Kotlinx Serialization with explicit `@Serializable` annotations. +- Custom serializers should be registered in the companion object. +- JSON config is defined in `McpJson.kt` — use it consistently. + +### Concurrency and State +- Use `kotlinx.atomicfu` for thread-safe operations across platforms. +- Prefer coroutines over callbacks where possible. +- Transport implementations extend `AbstractTransport` for consistent callback management. + +### Error Handling +- Use sealed classes for representing different result states. +- Map errors to JSON-RPC error codes in protocol handlers. +- Log errors using `io.github.oshai.kotlinlogging.KotlinLogging`. + +### Logging +- Use `KotlinLogging.logger {}` for structured logging. +- Never log sensitive data (credentials, tokens). + +## Pull Requests +- Base all PRs on the `main` branch. +- PR title format: Brief description of the change +- Include in PR description: + - **What changed?** + - **Why? Motivation and context** + - **Breaking changes?** (if any) +- Run `./gradlew apiCheck` before submitting. +- Link PR to related issue (except for minor docs/typo fixes). +- Follow [Kotlin Coding Conventions](https://kotlinlang.org/docs/reference/coding-conventions.html). + +## Review Checklist +- `./gradlew apiCheck` must pass. +- `./gradlew test` must succeed for affected modules. +- New tests added for any new feature or bug fix. +- Documentation updated for user-facing changes. +- No breaking changes to public APIs without discussion. From c5a254b83f77540baba6550365218aca07be3377 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Tue, 28 Oct 2025 11:52:07 +0100 Subject: [PATCH 033/449] Refactor WebSocket dependency (#350) Extracted WebSocket as an optional dependency of the core module --- kotlin-sdk-client/build.gradle.kts | 1 + kotlin-sdk-core/build.gradle.kts | 2 +- kotlin-sdk-server/build.gradle.kts | 1 + kotlin-sdk-test/build.gradle.kts | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/kotlin-sdk-client/build.gradle.kts b/kotlin-sdk-client/build.gradle.kts index 7db177c..2466f8e 100644 --- a/kotlin-sdk-client/build.gradle.kts +++ b/kotlin-sdk-client/build.gradle.kts @@ -43,6 +43,7 @@ kotlin { implementation(kotlin("test")) implementation(dependencies.platform(libs.ktor.bom)) implementation(libs.ktor.client.mock) + implementation(libs.ktor.server.websockets) implementation(libs.kotlinx.coroutines.test) implementation(libs.ktor.client.logging) } diff --git a/kotlin-sdk-core/build.gradle.kts b/kotlin-sdk-core/build.gradle.kts index 68e5618..a3e1b58 100644 --- a/kotlin-sdk-core/build.gradle.kts +++ b/kotlin-sdk-core/build.gradle.kts @@ -113,8 +113,8 @@ kotlin { api(libs.kotlinx.serialization.json) api(libs.kotlinx.coroutines.core) api(libs.kotlinx.io.core) - api(libs.ktor.server.websockets) api(libs.kotlinx.collections.immutable) + implementation(libs.ktor.server.websockets) implementation(libs.kotlin.logging) } } diff --git a/kotlin-sdk-server/build.gradle.kts b/kotlin-sdk-server/build.gradle.kts index c5feae0..64a2749 100644 --- a/kotlin-sdk-server/build.gradle.kts +++ b/kotlin-sdk-server/build.gradle.kts @@ -12,6 +12,7 @@ kotlin { api(project(":kotlin-sdk-core")) api(libs.ktor.server.core) api(libs.ktor.server.sse) + implementation(libs.ktor.server.websockets) implementation(libs.kotlin.logging) } } diff --git a/kotlin-sdk-test/build.gradle.kts b/kotlin-sdk-test/build.gradle.kts index b51398b..c7f8b60 100644 --- a/kotlin-sdk-test/build.gradle.kts +++ b/kotlin-sdk-test/build.gradle.kts @@ -18,6 +18,7 @@ kotlin { implementation(libs.kotlinx.coroutines.test) implementation(libs.ktor.client.cio) implementation(libs.ktor.server.cio) + implementation(libs.ktor.server.websockets) implementation(libs.ktor.server.test.host) } } From 20e4e445098c2ed3c74a1dee71d2f08eb9a0cd4f Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Thu, 30 Oct 2025 12:19:23 +0100 Subject: [PATCH 034/449] Fix: Return CallToolResult with isError for tool errors to conform with MCP spec (#354) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update server-side tool error handling to return `CallToolResult` with `isError: true` instead of throwing protocol-level exceptions, conforming with MCP specification ## Motivation and Context [The MCP specification states](https://modelcontextprotocol.io/specification/2025-06-18/schema#calltoolresult): > Any errors that originate from the tool SHOULD be reported inside the result object, with isError set to true, not as an MCP protocol-level error response. Otherwise, the LLM would not be able to see that an error occurred and self-correct. This PR updates the kotlin sdk to match this behavior ## How Has This Been Tested? All tests pass successfully with the new behavior ## Breaking Changes None ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed ## Additional context The TypeScript SDK was recently updated [#1044](https://github.com/modelcontextprotocol/typescript-sdk/pull/1044) to conform with the MCP specification regarding tool error handling. That’s why we got exceptions in the integration tests --- .../kotlin/sdk/server/Server.kt | 25 +++++++-- .../kotlin/AbstractToolIntegrationTest.kt | 39 ++++++++------ .../KotlinClientTsServerEdgeCasesTestSse.kt | 52 +++++++++---------- .../KotlinClientTsServerEdgeCasesTestStdio.kt | 52 +++++++++---------- 4 files changed, 93 insertions(+), 75 deletions(-) diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt index 18283e4..8c01056 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt @@ -21,6 +21,7 @@ import io.modelcontextprotocol.kotlin.sdk.ReadResourceRequest import io.modelcontextprotocol.kotlin.sdk.ReadResourceResult import io.modelcontextprotocol.kotlin.sdk.Resource import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.TextContent import io.modelcontextprotocol.kotlin.sdk.Tool import io.modelcontextprotocol.kotlin.sdk.ToolAnnotations import io.modelcontextprotocol.kotlin.sdk.shared.ProtocolOptions @@ -32,6 +33,7 @@ import kotlinx.collections.immutable.minus import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.toPersistentSet +import kotlinx.coroutines.CancellationException private val logger = KotlinLogging.logger {} @@ -515,13 +517,30 @@ public open class Server( private suspend fun handleCallTool(request: CallToolRequest): CallToolResult { logger.debug { "Handling tool call request for tool: ${request.name}" } + + // Check if tool exists val tool = _tools.value[request.name] ?: run { logger.error { "Tool not found: ${request.name}" } - throw IllegalArgumentException("Tool not found: ${request.name}") + return CallToolResult( + content = listOf(TextContent(text = "Tool ${request.name} not found")), + isError = true, + ) } - logger.trace { "Executing tool ${request.name} with input: ${request.arguments}" } - return tool.handler(request) + + // Execute tool handler and catch any errors + return try { + logger.trace { "Executing tool ${request.name} with input: ${request.arguments}" } + tool.handler(request) + } catch (e: CancellationException) { + throw e + } catch (e: Exception) { + logger.error(e) { "Error executing tool ${request.name}" } + CallToolResult( + content = listOf(TextContent(text = "Error executing tool ${request.name}: ${e.message}")), + isError = true, + ) + } } private suspend fun handleListPrompts(): ListPromptsResult { diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt index 41a951b..ca88723 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt @@ -21,7 +21,6 @@ import kotlinx.serialization.json.buildJsonArray import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.put import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows import java.text.DecimalFormat import java.text.DecimalFormatSymbols import java.util.Locale @@ -527,16 +526,18 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() { "message" to "My exception message", ) - val exception = assertThrows { - runBlocking { - client.callTool(errorToolName, exceptionArgs) - } - } + val exceptionResult = client.callTool(errorToolName, exceptionArgs) as CallToolResultBase - val msg = exception.message ?: "" - val expectedMessage = "JSONRPCError(code=InternalError, message=My exception message, data={})" + assertTrue(exceptionResult.isError ?: false, "isError should be true for exception") - assertEquals(expectedMessage, msg, "Unexpected error message for exception") + val exceptionContent = exceptionResult.content.firstOrNull { it is TextContent } as? TextContent + assertNotNull(exceptionContent, "Error content should be present in the result") + + val exceptionText = exceptionContent.text ?: "" + assertTrue( + exceptionText.contains("Error executing tool") && exceptionText.contains("My exception message"), + "Error message should contain the exception details", + ) } @Test @@ -770,15 +771,21 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() { val nonExistentToolName = "non-existent-tool" val arguments = mapOf("text" to "Test") - val exception = assertThrows { - runBlocking { - client.callTool(nonExistentToolName, arguments) - } + val result = runBlocking { + client.callTool(nonExistentToolName, arguments) } - val msg = exception.message ?: "" - val expectedMessage = "JSONRPCError(code=InternalError, message=Tool not found: non-existent-tool, data={})" + assertNotNull(result, "Tool call result should not be null") + val callResult = result as CallToolResult + assertTrue(callResult.isError ?: false, "isError should be true for non-existent tool") - assertEquals(expectedMessage, msg, "Unexpected error message for non-existent tool") + val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent + assertNotNull(textContent, "Error content should be present in the result") + + val errorText = textContent.text ?: "" + assertTrue( + errorText.contains("non-existent-tool") && errorText.contains("not found"), + "Error message should indicate the tool was not found", + ) } } diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerEdgeCasesTestSse.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerEdgeCasesTestSse.kt index 1585aa5..de27566 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerEdgeCasesTestSse.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerEdgeCasesTestSse.kt @@ -17,7 +17,6 @@ import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.Timeout -import org.junit.jupiter.api.assertThrows import java.util.concurrent.TimeUnit import kotlin.test.assertEquals import kotlin.test.assertNotNull @@ -74,16 +73,19 @@ class KotlinClientTsServerEdgeCasesTestSse : TsTestBase() { val nonExistentToolName = "non-existent-tool" val arguments = mapOf("name" to "TestUser") - val exception = assertThrows { - client.callTool(nonExistentToolName, arguments) - } + val result = client.callTool(nonExistentToolName, arguments) + assertNotNull(result, "Tool call result should not be null") - val expectedMessage = - "JSONRPCError(code=InvalidParams, message=MCP error -32602: Tool non-existent-tool not found, data={})" - assertEquals( - expectedMessage, - exception.message, - "Unexpected error message for non-existent tool", + val callResult = result as CallToolResult + assertTrue(callResult.isError ?: false, "isError should be true for non-existent tool") + + val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent + assertNotNull(textContent, "Error content should be present in the result") + + val errorText = textContent.text ?: "" + assertTrue( + errorText.contains("non-existent-tool") && errorText.contains("not found"), + "Error message should indicate the tool was not found", ) } } @@ -176,26 +178,20 @@ class KotlinClientTsServerEdgeCasesTestSse : TsTestBase() { "name" to JsonObject(mapOf("nested" to JsonPrimitive("value"))), ) - val exception = assertThrows { - client.callTool("greet", invalidArguments) - } + val result = client.callTool("greet", invalidArguments) + assertNotNull(result, "Tool call result should not be null") - val msg = exception.message ?: "" - val expectedMessage = """ - JSONRPCError(code=InvalidParams, message=MCP error -32602: Invalid arguments for tool greet: [ - { - "code": "invalid_type", - "expected": "string", - "received": "object", - "path": [ - "name" - ], - "message": "Expected string, received object" - } - ], data={}) - """.trimIndent() + val callResult = result as CallToolResult + assertTrue(callResult.isError ?: false, "isError should be true for invalid arguments") - assertEquals(expectedMessage, msg, "Unexpected error message for invalid arguments") + val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent + assertNotNull(textContent, "Error content should be present in the result") + + val errorText = textContent.text ?: "" + assertTrue( + errorText.contains("Invalid arguments") && errorText.contains("greet"), + "Error message should indicate invalid arguments for tool greet", + ) } } diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt index 88a4685..b6e385f 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt @@ -14,7 +14,6 @@ import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive import org.junit.jupiter.api.Test import org.junit.jupiter.api.Timeout -import org.junit.jupiter.api.assertThrows import java.util.concurrent.TimeUnit import kotlin.test.assertEquals import kotlin.test.assertNotNull @@ -31,16 +30,19 @@ class KotlinClientTsServerEdgeCasesTestStdio : TsTestBase() { val nonExistentToolName = "non-existent-tool" val arguments = mapOf("name" to "TestUser") - val exception = assertThrows { - client.callTool(nonExistentToolName, arguments) - } + val result = client.callTool(nonExistentToolName, arguments) + assertNotNull(result, "Tool call result should not be null") - val expectedMessage = - "JSONRPCError(code=InvalidParams, message=MCP error -32602: Tool non-existent-tool not found, data={})" - assertEquals( - expectedMessage, - exception.message, - "Unexpected error message for non-existent tool", + val callResult = result as CallToolResult + assertTrue(callResult.isError ?: false, "isError should be true for non-existent tool") + + val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent + assertNotNull(textContent, "Error content should be present in the result") + + val errorText = textContent.text ?: "" + assertTrue( + errorText.contains("non-existent-tool") && errorText.contains("not found"), + "Error message should indicate the tool was not found", ) } } @@ -133,26 +135,20 @@ class KotlinClientTsServerEdgeCasesTestStdio : TsTestBase() { "name" to JsonObject(mapOf("nested" to JsonPrimitive("value"))), ) - val exception = assertThrows { - client.callTool("greet", invalidArguments) - } + val result = client.callTool("greet", invalidArguments) + assertNotNull(result, "Tool call result should not be null") - val msg = exception.message ?: "" - val expectedMessage = """ - JSONRPCError(code=InvalidParams, message=MCP error -32602: Invalid arguments for tool greet: [ - { - "code": "invalid_type", - "expected": "string", - "received": "object", - "path": [ - "name" - ], - "message": "Expected string, received object" - } - ], data={}) - """.trimIndent() + val callResult = result as CallToolResult + assertTrue(callResult.isError ?: false, "isError should be true for invalid arguments") - assertEquals(expectedMessage, msg, "Unexpected error message for invalid arguments") + val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent + assertNotNull(textContent, "Error content should be present in the result") + + val errorText = textContent.text ?: "" + assertTrue( + errorText.contains("Invalid arguments") && errorText.contains("greet"), + "Error message should indicate invalid arguments for tool greet", + ) } } From 33b2f628bce10c270f1af796c063a972f86895b8 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Thu, 30 Oct 2025 13:19:06 +0100 Subject: [PATCH 035/449] Update .gitignore to exclude kotlin-js-store directory (#343) improve git repository ## Breaking Changes no ## Checklist - [x] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [ ] My code follows the repository's style guidelines - [ ] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c0631fa..ea62a95 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ out/ ### Kotlin ### .kotlin +kotlin-js-store yarn.lock ### Eclipse ### From cae9130e373294ce8956d1d5546bd3471f488149 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Thu, 30 Oct 2025 15:12:03 +0100 Subject: [PATCH 036/449] Relocate `WebSocketIntegrationTest` from `commonTest` to `jvmTest` (#352) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With Ktor 3.2.3, tests don’t work correctly with wasm, so the websocket tests were moved to the jvm target as the most important one ## How Has This Been Tested? local ## Breaking Changes None ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [x] Refactoring ## 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- .../websocket}/WebSocketIntegrationTest.kt | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) rename kotlin-sdk-test/src/{commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration => jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket}/WebSocketIntegrationTest.kt (91%) diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/WebSocketIntegrationTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/WebSocketIntegrationTest.kt similarity index 91% rename from kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/WebSocketIntegrationTest.kt rename to kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/WebSocketIntegrationTest.kt index 2a815dd..6200e08 100644 --- a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/WebSocketIntegrationTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/WebSocketIntegrationTest.kt @@ -1,4 +1,4 @@ -package io.modelcontextprotocol.kotlin.sdk.integration +package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.websocket import io.ktor.client.HttpClient import io.ktor.server.application.install @@ -22,7 +22,6 @@ import io.modelcontextprotocol.kotlin.sdk.server.mcpWebSocket import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.test.runTest import kotlinx.coroutines.withContext -import kotlin.test.Ignore import kotlin.test.Test import kotlin.test.assertTrue import kotlin.time.Duration.Companion.seconds @@ -34,7 +33,6 @@ import io.ktor.server.websocket.WebSockets as ServerWebSockets class WebSocketIntegrationTest { @Test - @Ignore // Ignored because it doesn’t work with wasm/js in Ktor 3.2.3 fun `client should be able to connect to websocket server 2`() = runTest(timeout = 5.seconds) { var server: EmbeddedServer? = null var client: Client? = null @@ -47,7 +45,7 @@ class WebSocketIntegrationTest { } } finally { client?.close() - server?.stopSuspend(1000, 2000) + server?.stop(1000, 2000) } } @@ -59,7 +57,6 @@ class WebSocketIntegrationTest { * 3. Observe that Client A receives a response related to it. */ @Test - @Ignore // Ignored because it doesn’t work with wasm/js in Ktor 3.2.3 fun `single websocket connection`() = runTest(timeout = 5.seconds) { var server: EmbeddedServer? = null var client: Client? = null @@ -75,7 +72,7 @@ class WebSocketIntegrationTest { } } finally { client?.close() - server?.stopSuspend(1000, 2000) + server?.stop(1000, 2000) } } @@ -88,7 +85,6 @@ class WebSocketIntegrationTest { * 4. Observe that Client B (connection #2) receives a response related to sessionId#1. */ @Test - @Ignore // Ignored because it doesn’t work with wasm/js in Ktor 3.2.3 fun `multiple websocket connections`() = runTest(timeout = 5.seconds) { var server: EmbeddedServer? = null var clientA: Client? = null @@ -112,7 +108,7 @@ class WebSocketIntegrationTest { } finally { clientA?.close() clientB?.close() - server?.stopSuspend(1000, 2000) + server?.stop(1000, 2000) } } @@ -138,7 +134,7 @@ class WebSocketIntegrationTest { return client } - private suspend fun initServer(): EmbeddedServer { + private fun initServer(): EmbeddedServer { val server = Server( Implementation(name = "websocket-server", version = "1.0.0"), ServerOptions( @@ -175,7 +171,7 @@ class WebSocketIntegrationTest { } } - return ktorServer.startSuspend(wait = false) + return ktorServer.start(wait = false) } /** From 458a49a5fba7bc060a2b31fd19bda7edf6aed1e2 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Fri, 31 Oct 2025 12:21:46 +0100 Subject: [PATCH 037/449] Fix kotlin-mcp-server example and update samples (#345) - updated the sdk version in all examples - fixed `kotlin-mcp-server` example (#331) ## Motivation and Context Removed the WASM example, as it duplicates the JVM example and only adds unnecessary complexity ## How Has This Been Tested? Locally ## Breaking Changes No ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 - [x] I have added or updated documentation as needed --- .../gradle/libs.versions.toml | 6 +- samples/kotlin-mcp-server/README.md | 57 +++++++++------ samples/kotlin-mcp-server/build.gradle.kts | 54 ++++---------- .../gradle/libs.versions.toml | 8 +-- .../src/jvmMain/kotlin/main.jvm.kt | 50 ------------- .../sample/server/main.kt} | 11 +-- .../sample/server}/server.kt | 71 +++++++++++++------ .../resources/simplelogger.properties | 0 .../gradle/libs.versions.toml | 6 +- .../sample/server/McpWeatherServer.kt | 2 - 10 files changed, 118 insertions(+), 147 deletions(-) delete mode 100644 samples/kotlin-mcp-server/src/jvmMain/kotlin/main.jvm.kt rename samples/kotlin-mcp-server/src/{wasmJsMain/kotlin/main.wasmJs.kt => main/kotlin/io/modelcontextprotocol/sample/server/main.kt} (71%) rename samples/kotlin-mcp-server/src/{commonMain/kotlin => main/kotlin/io/modelcontextprotocol/sample/server}/server.kt (69%) rename samples/kotlin-mcp-server/src/{jvmMain => main}/resources/simplelogger.properties (100%) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index 8652aca..7490ed6 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -1,8 +1,8 @@ [versions] anthropic = "2.9.0" -kotlin = "2.2.20" -ktor = "3.3.0" -mcp-kotlin = "0.7.3" +kotlin = "2.2.21" +ktor = "3.2.3" +mcp-kotlin = "0.7.4" shadow = "9.2.2" slf4j = "2.0.17" diff --git a/samples/kotlin-mcp-server/README.md b/samples/kotlin-mcp-server/README.md index ee87ab7..13a2976 100644 --- a/samples/kotlin-mcp-server/README.md +++ b/samples/kotlin-mcp-server/README.md @@ -1,15 +1,14 @@ # MCP Kotlin Server Sample -A sample implementation of an MCP (Model Communication Protocol) server in Kotlin that demonstrates different server -configurations and transport methods for both JVM and WASM targets. +A sample implementation of an MCP (Model Context Protocol) server in Kotlin that demonstrates different server +configurations and transport methods. ## Features - Multiple server operation modes: - - Standard I/O server (JVM only) - - SSE (Server-Sent Events) server with plain configuration (JVM, WASM) - - SSE server using Ktor plugin (JVM, WASM) -- Multiplatform support + - Standard I/O server + - SSE (Server-Sent Events) server with plain configuration + - SSE server using Ktor plugin - Built-in capabilities for: - Prompts management - Resources handling @@ -19,31 +18,41 @@ configurations and transport methods for both JVM and WASM targets. ### Running the Server -You can run the server on the JVM or using Kotlin/WASM on Node.js. +The server defaults to SSE mode with Ktor plugin on port 3001. You can customize the behavior using command-line arguments. - -#### JVM: - -To run the server on the JVM (defaults to SSE mode with Ktor plugin on port 3001): +#### Default (SSE with Ktor plugin): ```bash -./gradlew runJvm +./gradlew run ``` -#### WASM: - -To run the server using Kotlin/WASM on Node.js (defaults to SSE mode with Ktor plugin on port 3001): +#### Standard I/O mode: ```bash -./gradlew wasmJsNodeDevelopmentRun +./gradlew run --args="--stdio" +``` + +#### SSE with plain configuration: + +```bash +./gradlew run --args="--sse-server 3001" +``` + +#### SSE with Ktor plugin (custom port): + +```bash +./gradlew run --args="--sse-server-ktor 3002" ``` ### Connecting to the Server -For servers on JVM or WASM: +For SSE servers: 1. Start the server 2. Use the [MCP inspector](https://modelcontextprotocol.io/docs/tools/inspector) to connect to `http://localhost:/sse` +For STDIO servers: +- Connect using an MCP client that supports STDIO transport + ## Server Capabilities - **Prompts**: Supports prompt management with list change notifications @@ -53,8 +62,14 @@ For servers on JVM or WASM: ## Implementation Details The server is implemented using: -- Ktor for HTTP server functionality +- Ktor for HTTP server functionality (SSE modes) - Kotlin coroutines for asynchronous operations -- SSE for real-time communication -- Standard I/O for command-line interface -- Common Kotlin code shared between JVM and WASM targets +- SSE for real-time communication in web contexts +- Standard I/O for command-line interface and process-based communication + +## Example Capabilities + +The sample server demonstrates: +- **Prompt**: "Kotlin Developer" - helps develop small Kotlin applications with a configurable project name +- **Tool**: "kotlin-sdk-tool" - a simple test tool that returns a greeting +- **Resource**: "Web Search" - a placeholder resource demonstrating resource handling diff --git a/samples/kotlin-mcp-server/build.gradle.kts b/samples/kotlin-mcp-server/build.gradle.kts index e85100d..0bf45d8 100644 --- a/samples/kotlin-mcp-server/build.gradle.kts +++ b/samples/kotlin-mcp-server/build.gradle.kts @@ -1,50 +1,26 @@ -@file:OptIn(ExperimentalWasmDsl::class, ExperimentalKotlinGradlePluginApi::class) - -import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi -import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl - plugins { - alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.kotlin.jvm) alias(libs.plugins.kotlin.serialization) + application } group = "org.example" version = "0.1.0" -val jvmMainClass = "Main_jvmKt" +application { + mainClass.set("io.modelcontextprotocol.sample.server.MainKt") +} + +dependencies { + implementation(libs.mcp.kotlin.server) + implementation(libs.ktor.server.cio) + implementation(libs.slf4j.simple) +} + +tasks.test { + useJUnitPlatform() +} kotlin { jvmToolchain(17) - jvm { - binaries { - executable { - mainClass.set(jvmMainClass) - } - } - val jvmJar by tasks.getting(org.gradle.jvm.tasks.Jar::class) { - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - doFirst { - manifest { - attributes["Main-Class"] = jvmMainClass - } - - from(configurations["jvmRuntimeClasspath"].map { if (it.isDirectory) it else zipTree(it) }) - } - } - } - wasmJs { - nodejs() - binaries.executable() - } - - sourceSets { - commonMain.dependencies { - implementation(libs.mcp.kotlin.server) - implementation(libs.ktor.server.cio) - } - jvmMain.dependencies { - implementation(libs.slf4j.simple) - } - wasmJsMain.dependencies {} - } } diff --git a/samples/kotlin-mcp-server/gradle/libs.versions.toml b/samples/kotlin-mcp-server/gradle/libs.versions.toml index caccfdf..37eb160 100644 --- a/samples/kotlin-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-server/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] -kotlin = "2.2.20" -ktor = "3.3.1" -mcp-kotlin = "0.7.3" +kotlin = "2.2.21" +ktor = "3.2.3" +mcp-kotlin = "0.7.4" slf4j = "2.0.17" serialization = "1.9.0" @@ -11,5 +11,5 @@ mcp-kotlin-server = { group = "io.modelcontextprotocol", name = "kotlin-sdk-serv slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" } [plugins] -kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } +kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } diff --git a/samples/kotlin-mcp-server/src/jvmMain/kotlin/main.jvm.kt b/samples/kotlin-mcp-server/src/jvmMain/kotlin/main.jvm.kt deleted file mode 100644 index 509aa56..0000000 --- a/samples/kotlin-mcp-server/src/jvmMain/kotlin/main.jvm.kt +++ /dev/null @@ -1,50 +0,0 @@ -import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport -import kotlinx.coroutines.Job -import kotlinx.coroutines.runBlocking -import kotlinx.io.asSink -import kotlinx.io.asSource -import kotlinx.io.buffered -import shared.configureServer -import shared.runSseMcpServerUsingKtorPlugin -import shared.runSseMcpServerWithPlainConfiguration - -/** - * Start sse-server mcp on port 3001. - * - * @param args - * - "--stdio": Runs an MCP server using standard input/output. - * - "--sse-server-ktor ": Runs an SSE MCP server using Ktor plugin (default if no argument is provided). - * - "--sse-server ": Runs an SSE MCP server with a plain configuration. - */ -fun main(args: Array): Unit = runBlocking { - val command = args.firstOrNull() ?: "--sse-server-ktor" - val port = args.getOrNull(1)?.toIntOrNull() ?: 3001 - when (command) { - "--stdio" -> runMcpServerUsingStdio() - "--sse-server-ktor" -> runSseMcpServerUsingKtorPlugin(port) - "--sse-server" -> runSseMcpServerWithPlainConfiguration(port) - else -> { - System.err.println("Unknown command: $command") - } - } -} - -fun runMcpServerUsingStdio() { - // Note: The server will handle listing prompts, tools, and resources automatically. - // The handleListResourceTemplates will return empty as defined in the Server code. - val server = configureServer() - val transport = StdioServerTransport( - inputStream = System.`in`.asSource().buffered(), - outputStream = System.out.asSink().buffered() - ) - - runBlocking { - server.connect(transport) - val done = Job() - server.onClose { - done.complete() - } - done.join() - println("Server closed") - } -} \ No newline at end of file diff --git a/samples/kotlin-mcp-server/src/wasmJsMain/kotlin/main.wasmJs.kt b/samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/main.kt similarity index 71% rename from samples/kotlin-mcp-server/src/wasmJsMain/kotlin/main.wasmJs.kt rename to samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/main.kt index 7aa1219..d01def5 100644 --- a/samples/kotlin-mcp-server/src/wasmJsMain/kotlin/main.wasmJs.kt +++ b/samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/main.kt @@ -1,21 +1,24 @@ -import shared.runSseMcpServerUsingKtorPlugin -import shared.runSseMcpServerWithPlainConfiguration +package io.modelcontextprotocol.sample.server + +import kotlinx.coroutines.runBlocking /** * Start sse-server mcp on port 3001. * * @param args + * - "--stdio": Runs an MCP server using standard input/output. * - "--sse-server-ktor ": Runs an SSE MCP server using Ktor plugin (default if no argument is provided). * - "--sse-server ": Runs an SSE MCP server with a plain configuration. */ -suspend fun main(args: Array) { +fun main(args: Array): Unit = runBlocking { val command = args.firstOrNull() ?: "--sse-server-ktor" val port = args.getOrNull(1)?.toIntOrNull() ?: 3001 when (command) { + "--stdio" -> runMcpServerUsingStdio() "--sse-server-ktor" -> runSseMcpServerUsingKtorPlugin(port) "--sse-server" -> runSseMcpServerWithPlainConfiguration(port) else -> { error("Unknown command: $command") } } -} \ No newline at end of file +} diff --git a/samples/kotlin-mcp-server/src/commonMain/kotlin/server.kt b/samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/server.kt similarity index 69% rename from samples/kotlin-mcp-server/src/commonMain/kotlin/server.kt rename to samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/server.kt index 227c83d..45a7005 100644 --- a/samples/kotlin-mcp-server/src/commonMain/kotlin/server.kt +++ b/samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/server.kt @@ -1,4 +1,4 @@ -package shared +package io.modelcontextprotocol.sample.server import io.ktor.http.HttpStatusCode import io.ktor.server.application.install @@ -25,7 +25,13 @@ import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions import io.modelcontextprotocol.kotlin.sdk.server.ServerSession import io.modelcontextprotocol.kotlin.sdk.server.SseServerTransport +import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport import io.modelcontextprotocol.kotlin.sdk.server.mcp +import kotlinx.coroutines.Job +import kotlinx.coroutines.runBlocking +import kotlinx.io.asSink +import kotlinx.io.asSource +import kotlinx.io.buffered fun configureServer(): Server { val server = Server( @@ -71,7 +77,7 @@ fun configureServer(): Server { name = "kotlin-sdk-tool", description = "A test tool", inputSchema = Tool.Input(), - ) { request -> + ) { _ -> CallToolResult( content = listOf(TextContent("Hello, world!")), ) @@ -94,10 +100,10 @@ fun configureServer(): Server { return server } -suspend fun runSseMcpServerWithPlainConfiguration(port: Int) { +fun runSseMcpServerWithPlainConfiguration(port: Int) { val serverSessions = ConcurrentMap() - println("Starting sse server on port $port. ") - println("Use inspector to connect to the http://localhost:$port/sse") + println("Starting SSE server on port $port") + println("Use inspector to connect to http://localhost:$port/sse") val server = configureServer() @@ -106,21 +112,21 @@ suspend fun runSseMcpServerWithPlainConfiguration(port: Int) { routing { sse("/sse") { val transport = SseServerTransport("/message", this) - - // For SSE, you can also add prompts/tools/resources if needed: - // server.addTool(...), server.addPrompt(...), server.addResource(...) - val serverSession = server.connect(transport) - serverSessions[transport.sessionId] = server.connect(transport) + serverSessions[transport.sessionId] = serverSession serverSession.onClose { - println("Server closed") + println("Server session closed for: ${transport.sessionId}") serverSessions.remove(transport.sessionId) } } post("/message") { - println("Received Message") - val sessionId: String = call.request.queryParameters["sessionId"]!! + val sessionId: String? = call.request.queryParameters["sessionId"] + if (sessionId == null) { + call.respond(HttpStatusCode.BadRequest, "Missing sessionId parameter") + return@post + } + val transport = serverSessions[sessionId]?.transport as? SseServerTransport if (transport == null) { call.respond(HttpStatusCode.NotFound, "Session not found") @@ -130,24 +136,47 @@ suspend fun runSseMcpServerWithPlainConfiguration(port: Int) { transport.handlePostMessage(call) } } - }.startSuspend(wait = true) + }.start(wait = true) } /** - * Starts an SSE (Server Sent Events) MCP server using the Ktor framework and the specified port. + * Starts an SSE (Server-Sent Events) MCP server using the Ktor plugin. * - * The url can be accessed in the MCP inspector at [http://localhost:$port] + * This is the recommended approach for SSE servers as it simplifies configuration. + * The URL can be accessed in the MCP inspector at http://localhost:[port]/sse * * @param port The port number on which the SSE MCP server will listen for client connections. - * @return Unit This method does not return a value. */ -suspend fun runSseMcpServerUsingKtorPlugin(port: Int) { - println("Starting sse server on port $port") - println("Use inspector to connect to the http://localhost:$port/sse") +fun runSseMcpServerUsingKtorPlugin(port: Int) { + println("Starting SSE server on port $port") + println("Use inspector to connect to http://localhost:$port/sse") embeddedServer(CIO, host = "127.0.0.1", port = port) { mcp { return@mcp configureServer() } - }.startSuspend(wait = true) + }.start(wait = true) +} + +/** + * Starts an MCP server using Standard I/O transport. + * + * This mode is useful for process-based communication where the server + * communicates via stdin/stdout with a parent process or client. + */ +fun runMcpServerUsingStdio() { + val server = configureServer() + val transport = StdioServerTransport( + inputStream = System.`in`.asSource().buffered(), + outputStream = System.out.asSink().buffered() + ) + + runBlocking { + server.connect(transport) + val done = Job() + server.onClose { + done.complete() + } + done.join() + } } diff --git a/samples/kotlin-mcp-server/src/jvmMain/resources/simplelogger.properties b/samples/kotlin-mcp-server/src/main/resources/simplelogger.properties similarity index 100% rename from samples/kotlin-mcp-server/src/jvmMain/resources/simplelogger.properties rename to samples/kotlin-mcp-server/src/main/resources/simplelogger.properties diff --git a/samples/weather-stdio-server/gradle/libs.versions.toml b/samples/weather-stdio-server/gradle/libs.versions.toml index 8921724..87efc88 100644 --- a/samples/weather-stdio-server/gradle/libs.versions.toml +++ b/samples/weather-stdio-server/gradle/libs.versions.toml @@ -1,10 +1,10 @@ [versions] collections-immutable = "0.4.0" coroutines = "1.10.2" -kotlin = "2.2.20" -ktor = "3.3.0" +kotlin = "2.2.21" +ktor = "3.2.3" logging = "7.0.13" -mcp-kotlin = "0.7.3" +mcp-kotlin = "0.7.4" shadow = "9.2.2" slf4j = "2.0.17" diff --git a/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt b/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt index c92ebec..eb921a9 100644 --- a/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt +++ b/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt @@ -7,7 +7,6 @@ import io.ktor.http.ContentType import io.ktor.http.contentType import io.ktor.http.headers import io.ktor.serialization.kotlinx.json.json -import io.ktor.utils.io.core.Input import io.ktor.utils.io.streams.asInput import io.modelcontextprotocol.kotlin.sdk.CallToolResult import io.modelcontextprotocol.kotlin.sdk.Implementation @@ -19,7 +18,6 @@ import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport import kotlinx.coroutines.Job import kotlinx.coroutines.runBlocking -import kotlinx.io.Sink import kotlinx.io.asSink import kotlinx.io.buffered import kotlinx.serialization.json.Json From afc32807cc990d8fbd276396ba6e878f49051e54 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 12:51:33 +0200 Subject: [PATCH 038/449] Bump mikepenz/action-junit-report from 5 to 6 (#363) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [mikepenz/action-junit-report](https://github.com/mikepenz/action-junit-report) from 5 to 6.
Release notes

Sourced from mikepenz/action-junit-report's releases.

v6

🚀 Features

  • Add report_url output for test report URLs

📦 Dependencies

  • Bump github/codeql-action from 3 to 4
  • Bump actions/setup-node from 5 to 6
  • Upgrade dev dependencies

Contributors:

v5.6.2

🚀 Features

  • Add pr_id parameter to support PR comments from workflow_run contexts

📦 Dependencies

  • Upgrade dev dependencies

Contributors:

v5.6.1

🐛 Fixes

  • User server url from github.context

📦 Dependencies

  • Upgrade Dev Dependencies

Contributors:

v5.6.0

... (truncated)

Commits
  • e08919a Merge pull request #1458 from mikepenz/feature/dependency_updates
  • be56403 Update dependencies: bump @​typescript-eslint packages and webpack
  • f210c12 Merge pull request #1456 from mikepenz/copilot/add-test-report-url-output
  • 43c5101 Merge pull request #1450 from mikepenz/dependabot/github_actions/actions/setu...
  • 33b2908 recompile dist
  • 2f43138 Update README with report_url output documentation
  • ba3f83d Add report_url output with test report URLs
  • 4e74f6f Initial plan
  • a2801ff Bump actions/setup-node from 5 to 6
  • 71ca73c Merge pull request #1443 from mikepenz/dependabot/github_actions/github/codeq...
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=mikepenz/action-junit-report&package-manager=github_actions&previous-version=5&new-version=6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 979d54f..3371dcc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -66,7 +66,7 @@ jobs: **/build/reports/ - name: Publish Test Report - uses: mikepenz/action-junit-report@v5 + uses: mikepenz/action-junit-report@v6 if: ${{ !cancelled() }} # always run even if the previous step fails with: report_paths: '**/test-results/**/TEST-*.xml' From 35e51a50c1142666504aede78b6a69164531f443 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Mon, 3 Nov 2025 12:17:39 +0100 Subject: [PATCH 039/449] Fix security for logging in server (#360) * Fix debug logging in the `createMessage` method to include message count, `maxTokens`, `temperature`, and presence of a system prompt; * Fix debug logging in the `createElicitation` method to show message length and schema property count * Added trace logging ## Motivation and Context The full message was printed in the debug logs, which could contain confidential information ## Breaking Changes None ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- .../kotlin/sdk/server/ServerSession.kt | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt index 18cbf08..20524ad 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt @@ -107,7 +107,7 @@ public open class ServerSession( * @return The result of the ping request. * @throws IllegalStateException If for some reason the method is not supported or the connection is closed. */ - public suspend fun ping(): EmptyRequestResult = request(PingRequest()) + public suspend fun ping(): EmptyRequestResult = request(PingRequest()) /** * Creates a message using the server's sampling capability. @@ -121,8 +121,12 @@ public open class ServerSession( params: CreateMessageRequest, options: RequestOptions? = null, ): CreateMessageResult { - logger.debug { "Creating message with params: $params" } - return request(params, options) + logger.debug { + "Creating message with ${params.messages.size} messages, maxTokens=${params.maxTokens}, " + + "temperature=${params.temperature}, systemPrompt=${if (params.systemPrompt != null) "present" else "absent"}" + } + logger.trace { "Full createMessage params: $params" } + return request(params, options) } /** @@ -138,7 +142,7 @@ public open class ServerSession( options: RequestOptions? = null, ): ListRootsResult { logger.debug { "Listing roots with params: $params" } - return request(ListRootsRequest(params), options) + return request(ListRootsRequest(params), options) } public suspend fun createElicitation( @@ -146,7 +150,11 @@ public open class ServerSession( requestedSchema: RequestedSchema, options: RequestOptions? = null, ): CreateElicitationResult { - logger.debug { "Creating elicitation with message: $message" } + logger.debug { + "Creating elicitation with message length=${message.length}, " + + "schema properties count=${requestedSchema.properties.size}" + } + logger.trace { "Full elicitation message: $message, requestedSchema: $requestedSchema" } return request(CreateElicitationRequest(message, requestedSchema), options) } From f27fe9a41d1e7f358ac0b4e520304bb501fef935 Mon Sep 17 00:00:00 2001 From: Maria Tigina <31625351+tiginamaria@users.noreply.github.com> Date: Mon, 3 Nov 2025 15:32:25 +0100 Subject: [PATCH 040/449] Refactor mcp server, rename connect to (#364) - Rename `connect` to `createSession` in `Server.kt `to make naming more clear - Add block to configure build to `Server.kt` - Move sse test to jvm to be able to run, small test refactoring - Introduce base integration test for sse server / client --- kotlin-sdk-server/api/kotlin-sdk-server.api | 8 +- .../kotlin/sdk/server/KtorServer.kt | 2 +- .../kotlin/sdk/server/Server.kt | 26 ++- .../WebSocketMcpKtorServerExtensions.kt | 6 +- kotlin-sdk-test/build.gradle.kts | 1 - .../kotlin/sdk/client/ClientTest.kt | 20 +- .../kotlin/sdk/client/SseTransportTest.kt | 12 +- .../sdk/client/WebSocketTransportTest.kt | 6 +- .../sdk/integration/SseIntegrationTest.kt | 199 ------------------ .../kotlin/sdk/shared/BaseTransportTest.kt | 23 +- .../sdk/client/StdioClientTransportTest.kt | 6 +- .../sdk/integration/kotlin/KotlinTestBase.kt | 2 +- .../sse/AbstractSseIntegrationTest.kt | 105 +++++++++ .../sdk/integration/sse/SseIntegrationTest.kt | 111 ++++++++++ .../sdk/integration/typescript/TsTestBase.kt | 2 +- .../sse/KotlinServerForTsClientSse.kt | 3 +- .../kotlin/sdk/server/ServerTest.kt | 24 +-- 17 files changed, 298 insertions(+), 258 deletions(-) delete mode 100644 kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/SseIntegrationTest.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/AbstractSseIntegrationTest.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/SseIntegrationTest.kt diff --git a/kotlin-sdk-server/api/kotlin-sdk-server.api b/kotlin-sdk-server/api/kotlin-sdk-server.api index 8ee5af2..9c4fded 100644 --- a/kotlin-sdk-server/api/kotlin-sdk-server.api +++ b/kotlin-sdk-server/api/kotlin-sdk-server.api @@ -45,9 +45,10 @@ public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredTool { } public class io/modelcontextprotocol/kotlin/sdk/server/Server { - public fun (Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Ljava/lang/String;)V - public fun (Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Lkotlin/jvm/functions/Function0;)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Lkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun addPrompt (Lio/modelcontextprotocol/kotlin/sdk/Prompt;Lkotlin/jvm/functions/Function2;)V public final fun addPrompt (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lkotlin/jvm/functions/Function2;)V public static synthetic fun addPrompt$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V @@ -61,6 +62,7 @@ public class io/modelcontextprotocol/kotlin/sdk/server/Server { public final fun addTools (Ljava/util/List;)V public final fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun connect (Lio/modelcontextprotocol/kotlin/sdk/shared/Transport;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun createSession (Lio/modelcontextprotocol/kotlin/sdk/shared/Transport;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; protected final fun getInstructionsProvider ()Lkotlin/jvm/functions/Function0; protected final fun getOptions ()Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions; public final fun getPrompts ()Ljava/util/Map; diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt index 934ba04..06e6528 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt @@ -88,7 +88,7 @@ internal suspend fun ServerSSESession.mcpSseEndpoint( sseTransportManager.removeTransport(transport.sessionId) } - server.connect(transport) + server.createSession(transport) logger.debug { "Server connected to transport for sessionId: ${transport.sessionId}" } } diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt index 8c01056..15d61c1 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt @@ -57,12 +57,14 @@ public class ServerOptions(public val capabilities: ServerCapabilities, enforceS * @param options Configuration options for the server. * @param instructionsProvider Optional provider for instructions from the server to the client about how to use * this server. The provider is called each time a new session is started to support dynamic instructions. + * @param block A block to configure the mcp server. */ public open class Server( protected val serverInfo: Implementation, protected val options: ServerOptions, protected val instructionsProvider: (() -> String)? = null, + block: Server.() -> Unit = {}, ) { /** * Alternative constructor that provides the instructions directly as a string. @@ -70,12 +72,14 @@ public open class Server( * @param serverInfo Information about this server implementation (name, version). * @param options Configuration options for the server. * @param instructions Instructions from the server to the client about how to use this server. + * @param block A block to configure the mcp server. */ public constructor( serverInfo: Implementation, options: ServerOptions, instructions: String, - ) : this(serverInfo, options, { instructions }) + block: Server.() -> Unit = {}, + ) : this(serverInfo, options, { instructions }, block) private val sessions = atomic(persistentListOf()) @@ -98,6 +102,10 @@ public open class Server( public val resources: Map get() = _resources.value + init { + block(this) + } + public suspend fun close() { logger.debug { "Closing MCP server" } sessions.value.forEach { it.close() } @@ -111,7 +119,21 @@ public open class Server( * @param transport The transport layer to connect the session with. * @return The initialized and connected server session. */ - public suspend fun connect(transport: Transport): ServerSession { + @Deprecated( + "Use createSession(transport) instead.", + ReplaceWith("createSession(transport)"), + DeprecationLevel.WARNING, + ) + public suspend fun connect(transport: Transport): ServerSession = createSession(transport) + + /** + * Starts a new server session with the given transport and initializes + * internal request handlers based on the server's capabilities. + * + * @param transport The transport layer to connect the session with. + * @return The initialized and connected server session. + */ + public suspend fun createSession(transport: Transport): ServerSession { val session = ServerSession(serverInfo, options, instructionsProvider?.invoke()) // Internal handlers for tools diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt index 1ceb764..a5a2492 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt @@ -69,7 +69,7 @@ internal suspend fun WebSocketServerSession.mcpWebSocketEndpoint(block: () -> Se val server = block() var session: ServerSession? = null try { - session = server.connect(transport) + session = server.createSession(transport) awaitCancellation() } catch (e: CancellationException) { session?.close() @@ -103,7 +103,7 @@ public fun Route.mcpWebSocket(options: ServerOptions? = null, handler: suspend S ) public fun Route.mcpWebSocket(block: () -> Server) { webSocket { - block().connect(createMcpTransport(this)) + block().createSession(createMcpTransport(this)) } } @@ -190,7 +190,7 @@ private suspend fun Route.createMcpServer( ), ) - server.connect(transport) + server.createSession(transport) handler(server) server.close() } diff --git a/kotlin-sdk-test/build.gradle.kts b/kotlin-sdk-test/build.gradle.kts index c7f8b60..41bf545 100644 --- a/kotlin-sdk-test/build.gradle.kts +++ b/kotlin-sdk-test/build.gradle.kts @@ -24,7 +24,6 @@ kotlin { } jvmTest { dependencies { - implementation(kotlin("test-junit5")) implementation(libs.awaitility) runtimeOnly(libs.slf4j.simple) } diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt index d132ae5..b563ba0 100644 --- a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt +++ b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt @@ -258,7 +258,7 @@ class ClientTest { client.connect(clientTransport) }, launch { - serverSessionResult.complete(server.connect(serverTransport)) + serverSessionResult.complete(server.createSession(serverTransport)) }, ).joinAll() @@ -322,7 +322,7 @@ class ClientTest { println("Client connected") }, launch { - server.connect(serverTransport) + server.createSession(serverTransport) println("Server connected") }, ).joinAll() @@ -379,7 +379,7 @@ class ClientTest { println("Client connected") }, launch { - serverSessionResult.complete(server.connect(serverTransport)) + serverSessionResult.complete(server.createSession(serverTransport)) println("Server connected") }, ).joinAll() @@ -439,7 +439,7 @@ class ClientTest { println("Client connected") }, launch { - serverSessionResult.complete(server.connect(serverTransport)) + serverSessionResult.complete(server.createSession(serverTransport)) println("Server connected") }, ).joinAll() @@ -502,7 +502,7 @@ class ClientTest { println("Client connected") }, launch { - serverSessionResult.complete(server.connect(serverTransport)) + serverSessionResult.complete(server.createSession(serverTransport)) println("Server connected") }, ).joinAll() @@ -594,7 +594,7 @@ class ClientTest { println("Client connected") }, launch { - serverSessionResult.complete(server.connect(serverTransport)) + serverSessionResult.complete(server.createSession(serverTransport)) println("Server connected") }, ).joinAll() @@ -680,7 +680,7 @@ class ClientTest { println("Client connected") }, launch { - serverSessionResult.complete(server.connect(serverTransport)) + serverSessionResult.complete(server.createSession(serverTransport)) println("Server connected") }, ).joinAll() @@ -812,7 +812,7 @@ class ClientTest { println("Client connected") }, launch { - serverSessionResult.complete(server.connect(serverTransport)) + serverSessionResult.complete(server.createSession(serverTransport)) println("Server connected") }, ).joinAll() @@ -859,7 +859,7 @@ class ClientTest { println("Client connected") }, launch { - serverSessionResult.complete(server.connect(serverTransport)) + serverSessionResult.complete(server.createSession(serverTransport)) println("Server connected") }, ).joinAll() @@ -939,7 +939,7 @@ class ClientTest { println("Client connected") }, launch { - serverSessionResult.complete(server.connect(serverTransport)) + serverSessionResult.complete(server.createSession(serverTransport)) println("Server connected") }, ).joinAll() diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt index fa4ae02..e81332e 100644 --- a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt +++ b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt @@ -48,7 +48,7 @@ class SseTransportTest : BaseTransportTest() { val actualPort = server.actualPort() - val client = HttpClient { + val transport = HttpClient { install(ClientSSE) }.mcpSseTransport { url { @@ -58,7 +58,7 @@ class SseTransportTest : BaseTransportTest() { } try { - testClientOpenClose(client) + testTransportRead(transport) } finally { server.stopSuspend() } @@ -76,7 +76,7 @@ class SseTransportTest : BaseTransportTest() { val actualPort = server.actualPort() - val client = HttpClient { + val transport = HttpClient { install(ClientSSE) }.mcpSseTransport { url { @@ -86,7 +86,7 @@ class SseTransportTest : BaseTransportTest() { } try { - testClientRead(client) + testTransportRead(transport) } finally { server.stopSuspend() } @@ -104,7 +104,7 @@ class SseTransportTest : BaseTransportTest() { val actualPort = server.actualPort() - val client = HttpClient { + val transport = HttpClient { install(ClientSSE) }.mcpSseTransport { url { @@ -115,7 +115,7 @@ class SseTransportTest : BaseTransportTest() { } try { - testClientRead(client) + testTransportRead(transport) } finally { server.stopSuspend() } diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketTransportTest.kt b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketTransportTest.kt index a4bf7a3..9733071 100644 --- a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketTransportTest.kt +++ b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketTransportTest.kt @@ -22,7 +22,7 @@ class WebSocketTransportTest : BaseTransportTest() { install(io.ktor.client.plugins.websocket.WebSockets) }.mcpWebSocketTransport() - testClientOpenClose(client) + testTransportOpenClose(client) } @Test @@ -41,11 +41,11 @@ class WebSocketTransportTest : BaseTransportTest() { } } - val client = createClient { + val transport = createClient { install(io.ktor.client.plugins.websocket.WebSockets) }.mcpWebSocketTransport() - testClientRead(client) + testTransportRead(transport) clientFinished.complete(Unit) } diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/SseIntegrationTest.kt b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/SseIntegrationTest.kt deleted file mode 100644 index 2971812..0000000 --- a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/SseIntegrationTest.kt +++ /dev/null @@ -1,199 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration - -import io.ktor.client.HttpClient -import io.ktor.client.plugins.sse.SSE -import io.ktor.server.application.install -import io.ktor.server.cio.CIOApplicationEngine -import io.ktor.server.engine.EmbeddedServer -import io.ktor.server.engine.embeddedServer -import io.ktor.server.routing.routing -import io.modelcontextprotocol.kotlin.sdk.GetPromptRequest -import io.modelcontextprotocol.kotlin.sdk.GetPromptResult -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.PromptArgument -import io.modelcontextprotocol.kotlin.sdk.PromptMessage -import io.modelcontextprotocol.kotlin.sdk.Role -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities -import io.modelcontextprotocol.kotlin.sdk.TextContent -import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.client.mcpSseTransport -import io.modelcontextprotocol.kotlin.sdk.server.Server -import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions -import io.modelcontextprotocol.kotlin.sdk.server.mcp -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.test.runTest -import kotlinx.coroutines.withContext -import kotlin.test.Ignore -import kotlin.test.Test -import kotlin.test.assertTrue -import kotlin.time.Duration.Companion.seconds -import io.ktor.client.engine.cio.CIO as ClientCIO -import io.ktor.server.cio.CIO as ServerCIO -import io.ktor.server.sse.SSE as ServerSSE - -class SseIntegrationTest { - @Test - @Ignore // Ignored because it doesn’t work with wasm/js in Ktor 3.2.3 - fun `client should be able to connect to sse server`() = runTest(timeout = 5.seconds) { - var server: EmbeddedServer? = null - var client: Client? = null - - try { - withContext(Dispatchers.Default) { - server = initServer() - val port = server.engine.resolvedConnectors().first().port - client = initClient(serverPort = port) - } - } finally { - client?.close() - server?.stopSuspend(1000, 2000) - } - } - - /** - * Test Case #1: One opened connection, a client gets a prompt - * - * 1. Open SSE from Client A. - * 2. Send a POST request from Client A to POST /prompts/get. - * 3. Observe that Client A receives a response related to it. - */ - @Test - @Ignore // Ignored because it doesn’t work with wasm/js in Ktor 3.2.3 - fun `single sse connection`() = runTest(timeout = 5.seconds) { - var server: EmbeddedServer? = null - var client: Client? = null - try { - withContext(Dispatchers.Default) { - server = initServer() - val port = server.engine.resolvedConnectors().first().port - client = initClient("Client A", port) - - val promptA = getPrompt(client, "Client A") - assertTrue { "Client A" in promptA } - } - } finally { - client?.close() - server?.stopSuspend(1000, 2000) - } - } - - /** - * Test Case #1: Two open connections, each client gets a client-specific prompt - * - * 1. Open SSE connection #1 from Client A and note the sessionId= value. - * 2. Open SSE connection #2 from Client B and note the sessionId= value. - * 3. Send a POST request to POST /message with the corresponding sessionId#1. - * 4. Observe that Client B (connection #2) receives a response related to sessionId#1. - */ - @Test - @Ignore // Ignored because it doesn’t work with wasm/js in Ktor 3.2.3 - fun `multiple sse connections`() = runTest(timeout = 5.seconds) { - var server: EmbeddedServer? = null - var clientA: Client? = null - var clientB: Client? = null - - try { - withContext(Dispatchers.Default) { - server = initServer() - val port = server.engine.resolvedConnectors().first().port - - clientA = initClient("Client A", port) - clientB = initClient("Client B", port) - - // Step 3: Send a prompt request from Client A - val promptA = getPrompt(clientA, "Client A") - // Step 4: Send a prompt request from Client B - val promptB = getPrompt(clientB, "Client B") - - assertTrue { "Client A" in promptA } - assertTrue { "Client B" in promptB } - } - } finally { - clientA?.close() - clientB?.close() - server?.stopSuspend(1000, 2000) - } - } - - private suspend fun initClient(name: String = "", serverPort: Int): Client { - val client = Client( - Implementation(name = name, version = "1.0.0"), - ) - - val httpClient = HttpClient(ClientCIO) { - install(SSE) - } - - // Create a transport wrapper that captures the session ID and received messages - val transport = httpClient.mcpSseTransport { - url { - host = URL - port = serverPort - } - } - - client.connect(transport) - - return client - } - - private suspend fun initServer(): EmbeddedServer { - val server = Server( - Implementation(name = "sse-server", version = "1.0.0"), - ServerOptions( - capabilities = ServerCapabilities(prompts = ServerCapabilities.Prompts(listChanged = true)), - ), - ) - - server.addPrompt( - name = "prompt", - description = "Prompt description", - arguments = listOf( - PromptArgument( - name = "client", - description = "Client name who requested a prompt", - required = true, - ), - ), - ) { request -> - GetPromptResult( - "Prompt for ${request.name}", - messages = listOf( - PromptMessage( - role = Role.user, - content = TextContent("Prompt for client ${request.arguments?.get("client")}"), - ), - ), - ) - } - - val ktorServer = embeddedServer(ServerCIO, host = URL, port = PORT) { - install(ServerSSE) - routing { - mcp { server } - } - } - - return ktorServer.startSuspend(wait = false) - } - - /** - * Retrieves a prompt result using the provided client and client name. - */ - private suspend fun getPrompt(client: Client, clientName: String): String { - val response = client.getPrompt( - GetPromptRequest( - "prompt", - arguments = mapOf("client" to clientName), - ), - ) - - return (response.messages.first().content as? TextContent)?.text - ?: error("Failed to receive prompt for Client $clientName") - } - - companion object { - private const val URL = "127.0.0.1" - private const val PORT = 0 - } -} diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/BaseTransportTest.kt b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/BaseTransportTest.kt index acb2f27..26404b6 100644 --- a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/BaseTransportTest.kt +++ b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/BaseTransportTest.kt @@ -11,23 +11,24 @@ import kotlin.test.assertTrue import kotlin.test.fail abstract class BaseTransportTest { - protected suspend fun testClientOpenClose(client: Transport) { - client.onError { error -> + + protected suspend fun testTransportOpenClose(transport: Transport) { + transport.onError { error -> fail("Unexpected error: $error") } var didClose = false - client.onClose { didClose = true } + transport.onClose { didClose = true } - client.start() + transport.start() assertFalse(didClose, "Transport should not be closed immediately after start") - client.close() + transport.close() assertTrue(didClose, "Transport should be closed after close() call") } - protected suspend fun testClientRead(client: Transport) { - client.onError { error -> + protected suspend fun testTransportRead(transport: Transport) { + transport.onError { error -> error.printStackTrace() fail("Unexpected error: $error") } @@ -40,23 +41,23 @@ abstract class BaseTransportTest { val readMessages = mutableListOf() val finished = CompletableDeferred() - client.onMessage { message -> + transport.onMessage { message -> readMessages.add(message) if (message == messages.last()) { finished.complete(Unit) } } - client.start() + transport.start() for (message in messages) { - client.send(message) + transport.send(message) } finished.await() assertEquals(messages, readMessages, "Assert messages received") - client.close() + transport.close() } } diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt index 6d0da32..7d0fa36 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt @@ -22,7 +22,7 @@ class StdioClientTransportTest : BaseTransportTest() { output = output, ) - testClientOpenClose(client) + testTransportOpenClose(client) process.destroy() } @@ -40,7 +40,7 @@ class StdioClientTransportTest : BaseTransportTest() { output = output, ) - testClientRead(client) + testTransportRead(client) process.waitFor() process.destroy() @@ -60,7 +60,7 @@ class StdioClientTransportTest : BaseTransportTest() { output = output, ) - testClientRead(client) + testTransportRead(client) process.waitFor() process.destroy() diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/KotlinTestBase.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/KotlinTestBase.kt index 563fa85..66b166c 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/KotlinTestBase.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/KotlinTestBase.kt @@ -139,7 +139,7 @@ abstract class KotlinTestBase { // Start server transport by connecting the server runBlocking { - server.connect(serverTransport) + server.createSession(serverTransport) } } } diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/AbstractSseIntegrationTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/AbstractSseIntegrationTest.kt new file mode 100644 index 0000000..2628925 --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/AbstractSseIntegrationTest.kt @@ -0,0 +1,105 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.sse + +import io.ktor.client.HttpClient +import io.ktor.client.plugins.sse.SSE +import io.ktor.server.application.install +import io.ktor.server.cio.CIOApplicationEngine +import io.ktor.server.engine.EmbeddedServer +import io.ktor.server.engine.embeddedServer +import io.ktor.server.routing.routing +import io.modelcontextprotocol.kotlin.sdk.GetPromptResult +import io.modelcontextprotocol.kotlin.sdk.Implementation +import io.modelcontextprotocol.kotlin.sdk.PromptArgument +import io.modelcontextprotocol.kotlin.sdk.PromptMessage +import io.modelcontextprotocol.kotlin.sdk.Role +import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.TextContent +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.client.mcpSseTransport +import io.modelcontextprotocol.kotlin.sdk.server.Server +import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions +import io.modelcontextprotocol.kotlin.sdk.server.mcp +import io.ktor.client.engine.cio.CIO as ClientCIO +import io.ktor.server.cio.CIO as ServerCIO +import io.ktor.server.sse.SSE as ServerSSE + +open class AbstractSseIntegrationTest { + + suspend fun EmbeddedServer<*, *>.actualPort() = engine.resolvedConnectors().single().port + + suspend fun initTestClient(serverPort: Int, name: String? = null): Client { + val client = Client( + Implementation(name = name ?: DEFAULT_CLIENT_NAME, version = VERSION), + ) + + val httpClient = HttpClient(ClientCIO) { + install(SSE) + } + + // Create a transport wrapper that captures the session ID and received messages + val transport = httpClient.mcpSseTransport { + url { + host = URL + port = serverPort + } + } + + client.connect(transport) + + return client + } + + suspend fun initTestServer( + name: String? = null, + ): EmbeddedServer { + val server = Server( + Implementation(name = name ?: DEFAULT_SERVER_NAME, version = VERSION), + ServerOptions( + capabilities = ServerCapabilities(prompts = ServerCapabilities.Prompts(listChanged = true)), + ), + ) { + addPrompt( + name = "prompt", + description = "Prompt description", + arguments = listOf( + PromptArgument( + name = "client", + description = "Client name who requested a prompt", + required = true, + ), + ), + ) { request -> + GetPromptResult( + "Prompt for ${request.name}", + messages = listOf( + PromptMessage( + role = Role.user, + content = TextContent("Prompt for client ${request.arguments?.get("client")}"), + ), + ), + ) + } + } + + val ktorServer = embeddedServer( + ServerCIO, + host = URL, + port = PORT, + ) { + install(ServerSSE) + routing { + mcp { server } + } + } + + return ktorServer.startSuspend(wait = false) + } + + companion object { + private const val DEFAULT_CLIENT_NAME = "sse-test-client" + private const val DEFAULT_SERVER_NAME = "sse-test-server" + private const val VERSION = "1.0.0" + private const val URL = "127.0.0.1" + private const val PORT = 0 + } +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/SseIntegrationTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/SseIntegrationTest.kt new file mode 100644 index 0000000..33e8070 --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/SseIntegrationTest.kt @@ -0,0 +1,111 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.sse + +import io.ktor.server.cio.CIOApplicationEngine +import io.ktor.server.engine.EmbeddedServer +import io.modelcontextprotocol.kotlin.sdk.GetPromptRequest +import io.modelcontextprotocol.kotlin.sdk.TextContent +import io.modelcontextprotocol.kotlin.sdk.client.Client +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.withContext +import kotlin.test.Test +import kotlin.test.assertTrue +import kotlin.time.Duration.Companion.seconds + +class SSeIntegrationTest : AbstractSseIntegrationTest() { + + @Test + fun `client should be able to connect to sse server`() = runTest(timeout = 5.seconds) { + var server: EmbeddedServer? = null + var client: Client? = null + + try { + withContext(Dispatchers.Default) { + server = initTestServer() + val port = server.engine.resolvedConnectors().single().port + client = initTestClient(serverPort = port) + } + } finally { + client?.close() + server?.stopSuspend(1000, 2000) + } + } + + /** + * Test Case #1: One opened connection, a client gets a prompt + * + * 1. Open SSE from Client A. + * 2. Send a POST request from Client A to POST /prompts/get. + * 3. Observe that Client A receives a response related to it. + */ + @Test + fun `single sse connection`() = runTest(timeout = 5.seconds) { + var server: EmbeddedServer? = null + var client: Client? = null + try { + withContext(Dispatchers.Default) { + server = initTestServer() + val port = server.engine.resolvedConnectors().single().port + client = initTestClient(port, "Client A") + + val promptA = getPrompt(client, "Client A") + assertTrue { "Client A" in promptA } + } + } finally { + client?.close() + server?.stopSuspend(1000, 2000) + } + } + + /** + * Test Case #1: Two open connections, each client gets a client-specific prompt + * + * 1. Open SSE connection #1 from Client A and note the sessionId= value. + * 2. Open SSE connection #2 from Client B and note the sessionId= value. + * 3. Send a POST request to POST /message with the corresponding sessionId#1. + * 4. Observe that Client B (connection #2) receives a response related to sessionId#1. + */ + @Test + fun `multiple sse connections`() = runTest(timeout = 5.seconds) { + var server: EmbeddedServer? = null + var clientA: Client? = null + var clientB: Client? = null + + try { + withContext(Dispatchers.Default) { + server = initTestServer() + val port = server.engine.resolvedConnectors().first().port + + clientA = initTestClient(port, "Client A") + clientB = initTestClient(port, "Client B") + + // Step 3: Send a prompt request from Client A + val promptA = getPrompt(clientA, "Client A") + // Step 4: Send a prompt request from Client B + val promptB = getPrompt(clientB, "Client B") + + assertTrue { "Client A" in promptA } + assertTrue { "Client B" in promptB } + } + } finally { + clientA?.close() + clientB?.close() + server?.stopSuspend(1000, 2000) + } + } + + /** + * Retrieves a prompt result using the provided client and client name. + */ + private suspend fun getPrompt(client: Client, clientName: String): String { + val response = client.getPrompt( + GetPromptRequest( + "prompt", + arguments = mapOf("client" to clientName), + ), + ) + + return (response.messages.first().content as? TextContent)?.text + ?: error("Failed to receive prompt for Client $clientName") + } +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TsTestBase.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TsTestBase.kt index 801f582..21593fe 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TsTestBase.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TsTestBase.kt @@ -412,7 +412,7 @@ abstract class TsTestBase { // Connect server in a background thread to avoid blocking val serverThread = Thread { try { - kotlinx.coroutines.runBlocking { server.connect(transport) } + kotlinx.coroutines.runBlocking { server.createSession(transport) } } catch (e: Exception) { println("[STDIO-SERVER] Error connecting: ${e.message}") } diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt index 5cb0f61..c51454d 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt @@ -53,7 +53,6 @@ import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.contentOrNull import kotlinx.serialization.json.decodeFromJsonElement import kotlinx.serialization.json.jsonPrimitive -import org.awaitility.Awaitility.await import java.util.UUID import java.util.concurrent.ConcurrentHashMap @@ -134,7 +133,7 @@ class KotlinServerForTsClient { val serverThread = Thread { runBlocking { - mcpServer.connect(transport) + mcpServer.createSession(transport) } } serverThread.start() diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerTest.kt index 088444d..e0bf1c6 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerTest.kt @@ -53,7 +53,7 @@ class ServerTest { // Connect client and server launch { client.connect(clientTransport) } - launch { server.connect(serverTransport) } + launch { server.createSession(serverTransport) } // Remove the tool val result = server.removeTool("test-tool") @@ -90,7 +90,7 @@ class ServerTest { // Connect client and server launch { client.connect(clientTransport) } - launch { server.connect(serverTransport) } + launch { server.createSession(serverTransport) } // Try to remove a non-existent tool val result = server.removeTool("non-existent-tool") @@ -147,7 +147,7 @@ class ServerTest { // Connect client and server launch { client.connect(clientTransport) } - launch { server.connect(serverTransport) } + launch { server.createSession(serverTransport) } // Remove the tools val result = server.removeTools(listOf("test-tool-1", "test-tool-2")) @@ -186,7 +186,7 @@ class ServerTest { // Connect client and server launch { client.connect(clientTransport) } - launch { server.connect(serverTransport) } + launch { server.createSession(serverTransport) } // Remove the prompt val result = server.removePrompt(testPrompt.name) @@ -232,7 +232,7 @@ class ServerTest { // Connect client and server launch { client.connect(clientTransport) } - launch { server.connect(serverTransport) } + launch { server.createSession(serverTransport) } // Remove the prompts val result = server.removePrompts(listOf(testPrompt1.name, testPrompt2.name)) @@ -281,7 +281,7 @@ class ServerTest { // Connect client and server launch { client.connect(clientTransport) } - launch { server.connect(serverTransport) } + launch { server.createSession(serverTransport) } // Remove the resource val result = server.removeResource(testResourceUri) @@ -347,7 +347,7 @@ class ServerTest { // Connect client and server launch { client.connect(clientTransport) } - launch { server.connect(serverTransport) } + launch { server.createSession(serverTransport) } // Remove the resources val result = server.removeResources(listOf(testResourceUri1, testResourceUri2)) @@ -384,7 +384,7 @@ class ServerTest { // Connect client and server launch { client.connect(clientTransport) } - launch { server.connect(serverTransport) } + launch { server.createSession(serverTransport) } // Try to remove a non-existent prompt val result = server.removePrompt("non-existent-prompt") @@ -442,7 +442,7 @@ class ServerTest { // Connect client and server launch { client.connect(clientTransport) } - launch { server.connect(serverTransport) } + launch { server.createSession(serverTransport) } // Try to remove a non-existent resource val result = server.removeResource("non-existent-resource") @@ -486,7 +486,7 @@ class ServerTest { val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() val client = Client(clientInfo = Implementation(name = "test client", version = "1.0")) - server.connect(serverTransport) + server.createSession(serverTransport) client.connect(clientTransport) assertEquals(instructions, client.serverInstructions) @@ -505,7 +505,7 @@ class ServerTest { val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() val client = Client(clientInfo = Implementation(name = "test client", version = "1.0")) - server.connect(serverTransport) + server.createSession(serverTransport) client.connect(clientTransport) assertEquals(instructions, client.serverInstructions) @@ -522,7 +522,7 @@ class ServerTest { val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() val client = Client(clientInfo = Implementation(name = "test client", version = "1.0")) - server.connect(serverTransport) + server.createSession(serverTransport) client.connect(clientTransport) assertNull(client.serverInstructions) From ee341df11e4413f8a7dbc68725612c3f48bfd1dc Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Mon, 3 Nov 2025 16:36:53 +0100 Subject: [PATCH 041/449] Fix: Memory leak from unclosed sessions in Server (#359) ## Motivation and Context Sessions were added to the `sessions` list on connect but never removed when closed, causing a memory leak in long-running server processes. The closed sessions and their closures would accumulate indefinitely, preventing garbage collection. ## Breaking Changes None ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- .../io/modelcontextprotocol/kotlin/sdk/server/Server.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt index 15d61c1..04f18f8 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt @@ -169,6 +169,11 @@ public open class Server( } } + // Register cleanup handler to remove session from list when it closes + session.onClose { + logger.debug { "Removing closed session from active sessions list" } + sessions.update { list -> list - session } + } logger.debug { "Server session connecting to transport" } session.connect(transport) logger.debug { "Server session successfully connected to transport" } From 913c0d111e3515461e630a63702bcf11900fe468 Mon Sep 17 00:00:00 2001 From: Maria Tigina <31625351+tiginamaria@users.noreply.github.com> Date: Mon, 3 Nov 2025 18:27:03 +0100 Subject: [PATCH 042/449] Move features(tools, prompts, resources) management to the FeatureRegistry (#365) Move features(tools, prompts, resources) management to the `FeatureRegistry` to unload `Server` As for 'feature' naming, I refer to https://modelcontextprotocol.io/docs/learn/server-concepts --- kotlin-sdk-server/api/kotlin-sdk-server.api | 9 +- .../kotlin/sdk/server/FeatureRegistry.kt | 120 +++++++++ .../kotlin/sdk/server/Features.kt | 56 +++++ .../kotlin/sdk/server/Server.kt | 235 +++++------------- .../sdk/integration/sse/SseIntegrationTest.kt | 2 +- 5 files changed, 245 insertions(+), 177 deletions(-) create mode 100644 kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/FeatureRegistry.kt create mode 100644 kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Features.kt diff --git a/kotlin-sdk-server/api/kotlin-sdk-server.api b/kotlin-sdk-server/api/kotlin-sdk-server.api index 9c4fded..763dc1b 100644 --- a/kotlin-sdk-server/api/kotlin-sdk-server.api +++ b/kotlin-sdk-server/api/kotlin-sdk-server.api @@ -5,33 +5,35 @@ public final class io/modelcontextprotocol/kotlin/sdk/server/KtorServerKt { public static final fun mcp (Lio/ktor/server/routing/Routing;Lkotlin/jvm/functions/Function1;)V } -public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredPrompt { +public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredPrompt : io/modelcontextprotocol/kotlin/sdk/server/Feature { public fun (Lio/modelcontextprotocol/kotlin/sdk/Prompt;Lkotlin/jvm/functions/Function2;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/Prompt; public final fun component2 ()Lkotlin/jvm/functions/Function2; public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/Prompt;Lkotlin/jvm/functions/Function2;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredPrompt; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredPrompt;Lio/modelcontextprotocol/kotlin/sdk/Prompt;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredPrompt; public fun equals (Ljava/lang/Object;)Z + public fun getKey ()Ljava/lang/String; public final fun getMessageProvider ()Lkotlin/jvm/functions/Function2; public final fun getPrompt ()Lio/modelcontextprotocol/kotlin/sdk/Prompt; public fun hashCode ()I public fun toString ()Ljava/lang/String; } -public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredResource { +public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredResource : io/modelcontextprotocol/kotlin/sdk/server/Feature { public fun (Lio/modelcontextprotocol/kotlin/sdk/Resource;Lkotlin/jvm/functions/Function2;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/Resource; public final fun component2 ()Lkotlin/jvm/functions/Function2; public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/Resource;Lkotlin/jvm/functions/Function2;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredResource; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredResource;Lio/modelcontextprotocol/kotlin/sdk/Resource;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredResource; public fun equals (Ljava/lang/Object;)Z + public fun getKey ()Ljava/lang/String; public final fun getReadHandler ()Lkotlin/jvm/functions/Function2; public final fun getResource ()Lio/modelcontextprotocol/kotlin/sdk/Resource; public fun hashCode ()I public fun toString ()Ljava/lang/String; } -public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredTool { +public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredTool : io/modelcontextprotocol/kotlin/sdk/server/Feature { public fun (Lio/modelcontextprotocol/kotlin/sdk/Tool;Lkotlin/jvm/functions/Function2;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/Tool; public final fun component2 ()Lkotlin/jvm/functions/Function2; @@ -39,6 +41,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredTool { public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredTool;Lio/modelcontextprotocol/kotlin/sdk/Tool;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredTool; public fun equals (Ljava/lang/Object;)Z public final fun getHandler ()Lkotlin/jvm/functions/Function2; + public fun getKey ()Ljava/lang/String; public final fun getTool ()Lio/modelcontextprotocol/kotlin/sdk/Tool; public fun hashCode ()I public fun toString ()Ljava/lang/String; diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/FeatureRegistry.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/FeatureRegistry.kt new file mode 100644 index 0000000..1749ef6 --- /dev/null +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/FeatureRegistry.kt @@ -0,0 +1,120 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.github.oshai.kotlinlogging.KotlinLogging +import kotlinx.atomicfu.atomic +import kotlinx.atomicfu.getAndUpdate +import kotlinx.atomicfu.update +import kotlinx.collections.immutable.minus +import kotlinx.collections.immutable.persistentMapOf +import kotlinx.collections.immutable.toPersistentSet + +/** + * A generic registry for managing features of a specified type. This class provides thread-safe + * operations for adding, removing, and retrieving features from the registry. + * + * @param T The type of the feature, constrained to implement the [Feature] interface. + * @param featureType A string description of the type of feature being managed. + * Used primarily for logging purposes. + */ +internal class FeatureRegistry(private val featureType: String) { + + private val logger = KotlinLogging.logger(name = "FeatureRegistry[$featureType]") + + /** + * Atomic variable used to maintain a thread-safe registry of features. + * Stores a persistent map where each feature is identified by a unique key. + */ + private val registry = atomic(persistentMapOf()) + + /** + * Provides a snapshot of all features currently registered in the registry. + * Keys represent unique identifiers for each feature, and values represent the associated features. + */ + internal val values: Map + get() = registry.value + + /** + * Adds the specified feature to the registry. + * + * @param feature The feature to be added to the registry. + */ + internal fun add(feature: T) { + logger.info { "Adding $featureType: \"${feature.key}\"" } + registry.update { current -> current.put(feature.key, feature) } + logger.info { "Added $featureType: \"${feature.key}\"" } + } + + /** + * Adds the given list of features to the registry. Each feature is mapped by its key + * and added to the current registry. + * + * @param features The list of features to add to the registry. + */ + internal fun addAll(features: List) { + logger.info { "Adding ${featureType}s: ${features.size}" } + registry.update { current -> current.putAll(features.associateBy { it.key }) } + logger.info { "Added ${featureType}s: ${features.size}" } + } + + /** + * Removes the feature associated with the given key from the registry. + * + * @param key The key of the feature to be removed. + * @return `true` if the feature was successfully removed, or `false` if the feature was not found. + */ + internal fun remove(key: FeatureKey): Boolean { + logger.info { "Removing $featureType: \"$key\"" } + val oldMap = registry.getAndUpdate { current -> current.remove(key) } + + val removed = key in oldMap + logger.info { + if (removed) { + "Removed $featureType: \"$key\"" + } else { + "$featureType not found: \"$key\"" + } + } + return key in oldMap + } + + /** + * Removes the features associated with the given keys from the registry. + * + * @param keys The list of keys whose associated features are to be removed. + * @return The number of features that were successfully removed. + */ + internal fun removeAll(keys: List): Int { + logger.info { "Removing ${featureType}s: ${keys.size}" } + val oldMap = registry.getAndUpdate { current -> current - keys.toPersistentSet() } + + val removedCount = keys.count { it in oldMap } + logger.info { + if (removedCount > 0) { + "Removed ${featureType}s: $removedCount" + } else { + "No $featureType were removed" + } + } + + return removedCount + } + + /** + * Retrieves the feature associated with the given key from the registry. + * + * @param key The key of the feature to retrieve. + * @return The feature associated with the given key, or `null` if no such feature exists in the registry. + */ + internal fun get(key: FeatureKey): T? { + logger.info { "Getting $featureType: \"$key\"" } + val feature = registry.value[key] + logger.info { + if (feature != null) { + "Got $featureType: \"$key\"" + } else { + "$featureType not found: \"$key\"" + } + } + return feature + } +} diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Features.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Features.kt new file mode 100644 index 0000000..a956f1b --- /dev/null +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Features.kt @@ -0,0 +1,56 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.modelcontextprotocol.kotlin.sdk.CallToolRequest +import io.modelcontextprotocol.kotlin.sdk.CallToolResult +import io.modelcontextprotocol.kotlin.sdk.GetPromptRequest +import io.modelcontextprotocol.kotlin.sdk.GetPromptResult +import io.modelcontextprotocol.kotlin.sdk.Prompt +import io.modelcontextprotocol.kotlin.sdk.ReadResourceRequest +import io.modelcontextprotocol.kotlin.sdk.ReadResourceResult +import io.modelcontextprotocol.kotlin.sdk.Resource +import io.modelcontextprotocol.kotlin.sdk.Tool + +internal typealias FeatureKey = String + +/** + * Represents a feature with an associated unique key. + */ +internal interface Feature { + val key: FeatureKey +} + +/** + * A wrapper class representing a registered tool on the server. + * + * @property tool The tool definition. + * @property handler A suspend function to handle the tool call requests. + */ +public data class RegisteredTool(val tool: Tool, val handler: suspend (CallToolRequest) -> CallToolResult) : Feature { + override val key: String = tool.name +} + +/** + * A wrapper class representing a registered prompt on the server. + * + * @property prompt The prompt definition. + * @property messageProvider A suspend function that returns the prompt content when requested by the client. + */ +public data class RegisteredPrompt( + val prompt: Prompt, + val messageProvider: suspend (GetPromptRequest) -> GetPromptResult, +) : Feature { + override val key: String = prompt.name +} + +/** + * A wrapper class representing a registered resource on the server. + * + * @property resource The resource definition. + * @property readHandler A suspend function to handle read requests for this resource. + */ +public data class RegisteredResource( + val resource: Resource, + val readHandler: suspend (ReadResourceRequest) -> ReadResourceResult, +) : Feature { + override val key: String = resource.uri +} diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt index 04f18f8..6a3ee8c 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt @@ -27,12 +27,8 @@ import io.modelcontextprotocol.kotlin.sdk.ToolAnnotations import io.modelcontextprotocol.kotlin.sdk.shared.ProtocolOptions import io.modelcontextprotocol.kotlin.sdk.shared.Transport import kotlinx.atomicfu.atomic -import kotlinx.atomicfu.getAndUpdate import kotlinx.atomicfu.update -import kotlinx.collections.immutable.minus import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.persistentMapOf -import kotlinx.collections.immutable.toPersistentSet import kotlinx.coroutines.CancellationException private val logger = KotlinLogging.logger {} @@ -59,7 +55,7 @@ public class ServerOptions(public val capabilities: ServerCapabilities, enforceS * this server. The provider is called each time a new session is started to support dynamic instructions. * @param block A block to configure the mcp server. */ - +@Suppress("TooManyFunctions") public open class Server( protected val serverInfo: Implementation, protected val options: ServerOptions, @@ -92,15 +88,16 @@ public open class Server( @Suppress("ktlint:standard:backing-property-naming") private var _onClose: () -> Unit = {} - private val _tools = atomic(persistentMapOf()) - private val _prompts = atomic(persistentMapOf()) - private val _resources = atomic(persistentMapOf()) + private val toolRegistry = FeatureRegistry("Tool") + private val promptRegistry = FeatureRegistry("Prompt") + private val resourceRegistry = FeatureRegistry("Resource") + public val tools: Map - get() = _tools.value + get() = toolRegistry.values public val prompts: Map - get() = _prompts.value + get() = promptRegistry.values public val resources: Map - get() = _resources.value + get() = resourceRegistry.values init { block(this) @@ -108,7 +105,7 @@ public open class Server( public suspend fun close() { logger.debug { "Closing MCP server" } - sessions.value.forEach { it.close() } + sessions.value.forEach { session -> session.close() } _onClose() } @@ -172,12 +169,12 @@ public open class Server( // Register cleanup handler to remove session from list when it closes session.onClose { logger.debug { "Removing closed session from active sessions list" } - sessions.update { list -> list - session } + sessions.update { list -> list.remove(session) } } logger.debug { "Server session connecting to transport" } session.connect(transport) logger.debug { "Server session successfully connected to transport" } - sessions.update { it.add(session) } + sessions.update { sessions -> sessions.add(session) } _onConnect() return session @@ -229,12 +226,12 @@ public open class Server( * @throws IllegalStateException If the server does not support tools. */ public fun addTool(tool: Tool, handler: suspend (CallToolRequest) -> CallToolResult) { - if (options.capabilities.tools == null) { + check(options.capabilities.tools != null) { logger.error { "Failed to add tool '${tool.name}': Server does not support tools capability" } - throw IllegalStateException("Server does not support tools capability. Enable it in ServerOptions.") + "Server does not support tools capability. Enable it in ServerOptions." } - logger.info { "Registering tool: ${tool.name}" } - _tools.update { current -> current.put(tool.name, RegisteredTool(tool, handler)) } + + toolRegistry.add(RegisteredTool(tool, handler)) } /** @@ -249,6 +246,7 @@ public open class Server( * @param handler A suspend function that handles executing the tool when called by the client. * @throws IllegalStateException If the server does not support tools. */ + @Suppress("LongParameterList") public fun addTool( name: String, description: String, @@ -269,12 +267,11 @@ public open class Server( * @throws IllegalStateException If the server does not support tools. */ public fun addTools(toolsToAdd: List) { - if (options.capabilities.tools == null) { + check(options.capabilities.tools != null) { logger.error { "Failed to add tools: Server does not support tools capability" } - throw IllegalStateException("Server does not support tools capability.") + "Server does not support tools capability." } - logger.info { "Registering ${toolsToAdd.size} tools" } - _tools.update { current -> current.putAll(toolsToAdd.associateBy { it.tool.name }) } + toolRegistry.addAll(toolsToAdd) } /** @@ -285,23 +282,11 @@ public open class Server( * @throws IllegalStateException If the server does not support tools. */ public fun removeTool(name: String): Boolean { - if (options.capabilities.tools == null) { + check(options.capabilities.tools != null) { logger.error { "Failed to remove tool '$name': Server does not support tools capability" } - throw IllegalStateException("Server does not support tools capability.") + "Server does not support tools capability." } - logger.info { "Removing tool: $name" } - - val oldMap = _tools.getAndUpdate { current -> current.remove(name) } - - val removed = name in oldMap - logger.debug { - if (removed) { - "Tool removed: $name" - } else { - "Tool not found: $name" - } - } - return removed + return toolRegistry.remove(name) } /** @@ -312,22 +297,12 @@ public open class Server( * @throws IllegalStateException If the server does not support tools. */ public fun removeTools(toolNames: List): Int { - if (options.capabilities.tools == null) { + check(options.capabilities.tools != null) { logger.error { "Failed to remove tools: Server does not support tools capability" } - throw IllegalStateException("Server does not support tools capability.") + "Server does not support tools capability." } - logger.info { "Removing ${toolNames.size} tools" } - val oldMap = _tools.getAndUpdate { current -> current - toolNames.toPersistentSet() } - - val removedCount = toolNames.count { it in oldMap } - logger.info { - if (removedCount > 0) { - "Removed $removedCount tools" - } else { - "No tools were removed" - } - } + val removedCount = toolRegistry.removeAll(toolNames) return removedCount } @@ -339,12 +314,11 @@ public open class Server( * @throws IllegalStateException If the server does not support prompts. */ public fun addPrompt(prompt: Prompt, promptProvider: suspend (GetPromptRequest) -> GetPromptResult) { - if (options.capabilities.prompts == null) { + check(options.capabilities.prompts != null) { logger.error { "Failed to add prompt '${prompt.name}': Server does not support prompts capability" } - throw IllegalStateException("Server does not support prompts capability.") + "Server does not support prompts capability." } - logger.info { "Registering prompt: ${prompt.name}" } - _prompts.update { current -> current.put(prompt.name, RegisteredPrompt(prompt, promptProvider)) } + promptRegistry.add(RegisteredPrompt(prompt, promptProvider)) } /** @@ -373,12 +347,11 @@ public open class Server( * @throws IllegalStateException If the server does not support prompts. */ public fun addPrompts(promptsToAdd: List) { - if (options.capabilities.prompts == null) { + check(options.capabilities.prompts != null) { logger.error { "Failed to add prompts: Server does not support prompts capability" } - throw IllegalStateException("Server does not support prompts capability.") + "Server does not support prompts capability." } - logger.info { "Registering ${promptsToAdd.size} prompts" } - _prompts.update { current -> current.putAll(promptsToAdd.associateBy { it.prompt.name }) } + promptRegistry.addAll(promptsToAdd) } /** @@ -389,23 +362,12 @@ public open class Server( * @throws IllegalStateException If the server does not support prompts. */ public fun removePrompt(name: String): Boolean { - if (options.capabilities.prompts == null) { + check(options.capabilities.prompts != null) { logger.error { "Failed to remove prompt '$name': Server does not support prompts capability" } - throw IllegalStateException("Server does not support prompts capability.") + "Server does not support prompts capability." } - logger.info { "Removing prompt: $name" } - val oldMap = _prompts.getAndUpdate { current -> current.remove(name) } - - val removed = name in oldMap - logger.debug { - if (removed) { - "Prompt removed: $name" - } else { - "Prompt not found: $name" - } - } - return removed + return promptRegistry.remove(name) } /** @@ -416,24 +378,12 @@ public open class Server( * @throws IllegalStateException If the server does not support prompts. */ public fun removePrompts(promptNames: List): Int { - if (options.capabilities.prompts == null) { + check(options.capabilities.prompts != null) { logger.error { "Failed to remove prompts: Server does not support prompts capability" } - throw IllegalStateException("Server does not support prompts capability.") + "Server does not support prompts capability." } - logger.info { "Removing ${promptNames.size} prompts" } - val oldMap = _prompts.getAndUpdate { current -> current - promptNames.toPersistentSet() } - - val removedCount = promptNames.count { it in oldMap } - - logger.info { - if (removedCount > 0) { - "Removed $removedCount prompts" - } else { - "No prompts were removed" - } - } - return removedCount + return promptRegistry.removeAll(promptNames) } /** @@ -453,17 +403,12 @@ public open class Server( mimeType: String = "text/html", readHandler: suspend (ReadResourceRequest) -> ReadResourceResult, ) { - if (options.capabilities.resources == null) { + check(options.capabilities.resources != null) { logger.error { "Failed to add resource '$name': Server does not support resources capability" } - throw IllegalStateException("Server does not support resources capability.") - } - logger.info { "Registering resource: $name ($uri)" } - _resources.update { current -> - current.put( - uri, - RegisteredResource(Resource(uri, name, description, mimeType), readHandler), - ) + "Server does not support resources capability." } + val resource = Resource(uri, name, description, mimeType) + resourceRegistry.add(RegisteredResource(resource, readHandler)) } /** @@ -473,12 +418,11 @@ public open class Server( * @throws IllegalStateException If the server does not support resources. */ public fun addResources(resourcesToAdd: List) { - if (options.capabilities.resources == null) { + check(options.capabilities.resources != null) { logger.error { "Failed to add resources: Server does not support resources capability" } - throw IllegalStateException("Server does not support resources capability.") + "Server does not support resources capability." } - logger.info { "Registering ${resourcesToAdd.size} resources" } - _resources.update { current -> current.putAll(resourcesToAdd.associateBy { it.resource.uri }) } + resourceRegistry.addAll(resourcesToAdd) } /** @@ -489,23 +433,11 @@ public open class Server( * @throws IllegalStateException If the server does not support resources. */ public fun removeResource(uri: String): Boolean { - if (options.capabilities.resources == null) { + check(options.capabilities.resources != null) { logger.error { "Failed to remove resource '$uri': Server does not support resources capability" } - throw IllegalStateException("Server does not support resources capability.") + "Server does not support resources capability." } - logger.info { "Removing resource: $uri" } - - val oldMap = _resources.getAndUpdate { current -> current.remove(uri) } - - val removed = uri in oldMap - logger.debug { - if (removed) { - "Resource removed: $uri" - } else { - "Resource not found: $uri" - } - } - return removed + return resourceRegistry.remove(uri) } /** @@ -516,24 +448,11 @@ public open class Server( * @throws IllegalStateException If the server does not support resources. */ public fun removeResources(uris: List): Int { - if (options.capabilities.resources == null) { + check(options.capabilities.resources != null) { logger.error { "Failed to remove resources: Server does not support resources capability" } - throw IllegalStateException("Server does not support resources capability.") + "Server does not support resources capability." } - logger.info { "Removing ${uris.size} resources" } - - val oldMap = _resources.getAndUpdate { current -> current - uris.toPersistentSet() } - - val removedCount = uris.count { it in oldMap } - - logger.info { - if (removedCount > 0) { - "Removed $removedCount resources" - } else { - "No resources were removed" - } - } - return removedCount + return resourceRegistry.removeAll(uris) } // --- Internal Handlers --- @@ -545,17 +464,17 @@ public open class Server( private suspend fun handleCallTool(request: CallToolRequest): CallToolResult { logger.debug { "Handling tool call request for tool: ${request.name}" } - // Check if tool exists - val tool = _tools.value[request.name] - ?: run { - logger.error { "Tool not found: ${request.name}" } - return CallToolResult( - content = listOf(TextContent(text = "Tool ${request.name} not found")), - isError = true, - ) - } + // Check if the tool exists + val tool = toolRegistry.get(request.name) ?: run { + logger.error { "Tool not found: ${request.name}" } + return CallToolResult( + content = listOf(TextContent(text = "Tool ${request.name} not found")), + isError = true, + ) + } - // Execute tool handler and catch any errors + @Suppress("TooGenericExceptionCaught") + // Execute the tool handler and catch any errors return try { logger.trace { "Executing tool ${request.name} with input: ${request.arguments}" } tool.handler(request) @@ -577,7 +496,7 @@ public open class Server( private suspend fun handleGetPrompt(request: GetPromptRequest): GetPromptResult { logger.debug { "Handling get prompt request for: ${request.name}" } - val prompt = prompts[request.name] + val prompt = promptRegistry.get(request.name) ?: run { logger.error { "Prompt not found: ${request.name}" } throw IllegalArgumentException("Prompt not found: ${request.name}") @@ -592,7 +511,7 @@ public open class Server( private suspend fun handleReadResource(request: ReadResourceRequest): ReadResourceResult { logger.debug { "Handling read resource request for: ${request.uri}" } - val resource = resources[request.uri] + val resource = resourceRegistry.get(request.uri) ?: run { logger.error { "Resource not found: ${request.uri}" } throw IllegalArgumentException("Resource not found: ${request.uri}") @@ -605,33 +524,3 @@ public open class Server( return ListResourceTemplatesResult(listOf()) } } - -/** - * A wrapper class representing a registered tool on the server. - * - * @property tool The tool definition. - * @property handler A suspend function to handle the tool call requests. - */ -public data class RegisteredTool(val tool: Tool, val handler: suspend (CallToolRequest) -> CallToolResult) - -/** - * A wrapper class representing a registered prompt on the server. - * - * @property prompt The prompt definition. - * @property messageProvider A suspend function that returns the prompt content when requested by the client. - */ -public data class RegisteredPrompt( - val prompt: Prompt, - val messageProvider: suspend (GetPromptRequest) -> GetPromptResult, -) - -/** - * A wrapper class representing a registered resource on the server. - * - * @property resource The resource definition. - * @property readHandler A suspend function to handle read requests for this resource. - */ -public data class RegisteredResource( - val resource: Resource, - val readHandler: suspend (ReadResourceRequest) -> ReadResourceResult, -) diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/SseIntegrationTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/SseIntegrationTest.kt index 33e8070..55e833b 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/SseIntegrationTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/SseIntegrationTest.kt @@ -12,7 +12,7 @@ import kotlin.test.Test import kotlin.test.assertTrue import kotlin.time.Duration.Companion.seconds -class SSeIntegrationTest : AbstractSseIntegrationTest() { +class SseIntegrationTest : AbstractSseIntegrationTest() { @Test fun `client should be able to connect to sse server`() = runTest(timeout = 5.seconds) { From 2fc0b40906c139d3f05bad06de030d9bdd869e60 Mon Sep 17 00:00:00 2001 From: Maria Tigina <31625351+tiginamaria@users.noreply.github.com> Date: Mon, 3 Nov 2025 19:21:43 +0100 Subject: [PATCH 043/449] Refactor ServerTest test structure (#366) Split ServerTest per feature test classes ## Motivation and Context ## How Has This Been Tested? ## Breaking Changes ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update ## Checklist - [ ] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [ ] My code follows the repository's style guidelines - [ ] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed ## Additional context --- .../sdk/server/AbstractServerFeaturesTest.kt | 44 ++ .../sdk/server/ServerInstructionsTest.kt | 68 +++ .../kotlin/sdk/server/ServerPromptsTest.kt | 100 ++++ .../kotlin/sdk/server/ServerResourcesTest.kt | 135 +++++ .../kotlin/sdk/server/ServerTest.kt | 530 ------------------ .../kotlin/sdk/server/ServerToolsTest.kt | 89 +++ 6 files changed, 436 insertions(+), 530 deletions(-) create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/AbstractServerFeaturesTest.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerInstructionsTest.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsTest.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesTest.kt delete mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerTest.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsTest.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/AbstractServerFeaturesTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/AbstractServerFeaturesTest.kt new file mode 100644 index 0000000..3feea02 --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/AbstractServerFeaturesTest.kt @@ -0,0 +1,44 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.modelcontextprotocol.kotlin.sdk.Implementation +import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.BeforeEach + +abstract class AbstractServerFeaturesTest { + + protected lateinit var server: Server + protected lateinit var client: Client + + abstract fun getServerCapabilities(): ServerCapabilities + + protected open fun getServerInstructionsProvider(): (() -> String)? = null + + @BeforeEach + fun setUp() { + val serverOptions = ServerOptions( + capabilities = getServerCapabilities(), + ) + + server = Server( + serverInfo = Implementation(name = "test server", version = "1.0"), + options = serverOptions, + instructionsProvider = getServerInstructionsProvider(), + ) + + val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() + + client = Client( + clientInfo = Implementation(name = "test client", version = "1.0"), + ) + + runBlocking { + // Connect client and server + launch { client.connect(clientTransport) } + launch { server.createSession(serverTransport) } + } + } +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerInstructionsTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerInstructionsTest.kt new file mode 100644 index 0000000..89eeaef --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerInstructionsTest.kt @@ -0,0 +1,68 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.modelcontextprotocol.kotlin.sdk.Implementation +import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertNull +import kotlin.test.assertEquals + +class ServerInstructionsTest { + + @Test + fun `Server constructor should accept instructions provider parameter`() = runTest { + val serverInfo = Implementation(name = "test server", version = "1.0") + val serverOptions = ServerOptions(capabilities = ServerCapabilities()) + val instructions = "This is a test server. Use it for testing purposes only." + + val server = Server(serverInfo, serverOptions, { instructions }) + + // 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) + + assertEquals(instructions, client.serverInstructions) + } + + @Test + fun `Server constructor should accept instructions parameter`() = runTest { + val serverInfo = Implementation(name = "test server", version = "1.0") + val serverOptions = ServerOptions(capabilities = ServerCapabilities()) + val instructions = "This is a test server. Use it for testing purposes only." + + val server = Server(serverInfo, serverOptions, instructions) + + // 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) + + assertEquals(instructions, client.serverInstructions) + } + + @Test + fun `Server constructor should work without instructions parameter`() = runTest { + val serverInfo = Implementation(name = "test server", version = "1.0") + val serverOptions = ServerOptions(capabilities = ServerCapabilities()) + + // Test that server works when instructions parameter is omitted (defaults to null) + 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) + + assertNull(client.serverInstructions) + } +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsTest.kt new file mode 100644 index 0000000..ffaff6b --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsTest.kt @@ -0,0 +1,100 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.modelcontextprotocol.kotlin.sdk.GetPromptResult +import io.modelcontextprotocol.kotlin.sdk.Implementation +import io.modelcontextprotocol.kotlin.sdk.Method +import io.modelcontextprotocol.kotlin.sdk.Prompt +import io.modelcontextprotocol.kotlin.sdk.PromptListChangedNotification +import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class ServerPromptsTest : AbstractServerFeaturesTest() { + + override fun getServerCapabilities(): ServerCapabilities = ServerCapabilities( + prompts = ServerCapabilities.Prompts(false), + ) + + @Test + fun `removePrompt should remove a prompt`() = runTest { + // Add a prompt + val testPrompt = Prompt("test-prompt", "Test Prompt", null) + server.addPrompt(testPrompt) { + GetPromptResult( + description = "Test prompt description", + messages = listOf(), + ) + } + + // Remove the prompt + val result = server.removePrompt(testPrompt.name) + + // Verify the prompt was removed + assertTrue(result, "Prompt should be removed successfully") + } + + @Test + fun `removePrompts should remove multiple prompts and send notification`() = runTest { + // Add prompts + val testPrompt1 = Prompt("test-prompt-1", "Test Prompt 1", null) + val testPrompt2 = Prompt("test-prompt-2", "Test Prompt 2", null) + server.addPrompt(testPrompt1) { + GetPromptResult( + description = "Test prompt description 1", + messages = listOf(), + ) + } + server.addPrompt(testPrompt2) { + GetPromptResult( + description = "Test prompt description 2", + messages = listOf(), + ) + } + + // Remove the prompts + val result = server.removePrompts(listOf(testPrompt1.name, testPrompt2.name)) + + // Verify the prompts were removed + assertEquals(2, result, "Both prompts should be removed") + } + + @Test + fun `removePrompt should return false when prompt does not exist`() = runTest { + // Track notifications + var promptListChangedNotificationReceived = false + client.setNotificationHandler(Method.Defined.NotificationsPromptsListChanged) { + promptListChangedNotificationReceived = true + CompletableDeferred(Unit) + } + + // Try to remove a non-existent prompt + val result = server.removePrompt("non-existent-prompt") + + // Verify the result + assertFalse(result, "Removing non-existent prompt should return false") + assertFalse(promptListChangedNotificationReceived, "No notification should be sent when prompt doesn't exist") + } + + @Test + fun `removePrompt should throw when prompts capability is not supported`() = runTest { + // Create server without prompts capability + val serverOptions = ServerOptions( + capabilities = ServerCapabilities(), + ) + val server = Server( + Implementation(name = "test server", version = "1.0"), + serverOptions, + ) + + // Verify that removing a prompt throws an exception + val exception = assertThrows { + server.removePrompt("test-prompt") + } + assertEquals("Server does not support prompts capability.", exception.message) + } +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesTest.kt new file mode 100644 index 0000000..ac9e979 --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesTest.kt @@ -0,0 +1,135 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.modelcontextprotocol.kotlin.sdk.Implementation +import io.modelcontextprotocol.kotlin.sdk.Method +import io.modelcontextprotocol.kotlin.sdk.ReadResourceResult +import io.modelcontextprotocol.kotlin.sdk.ResourceListChangedNotification +import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.TextResourceContents +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class ServerResourcesTest : AbstractServerFeaturesTest() { + + override fun getServerCapabilities(): ServerCapabilities = ServerCapabilities( + resources = ServerCapabilities.Resources(null, null), + ) + + @Test + fun `removeResource should remove a resource and send notification`() = runTest { + // Add a resource + val testResourceUri = "test://resource" + server.addResource( + uri = testResourceUri, + name = "Test Resource", + description = "A test resource", + mimeType = "text/plain", + ) { + ReadResourceResult( + contents = listOf( + TextResourceContents( + text = "Test resource content", + uri = testResourceUri, + mimeType = "text/plain", + ), + ), + ) + } + + // Remove the resource + val result = server.removeResource(testResourceUri) + + // Verify the resource was removed + assertTrue(result, "Resource should be removed successfully") + } + + @Test + fun `removeResources should remove multiple resources and send notification`() = runTest { + // Add resources + val testResourceUri1 = "test://resource1" + val testResourceUri2 = "test://resource2" + server.addResource( + uri = testResourceUri1, + name = "Test Resource 1", + description = "A test resource 1", + mimeType = "text/plain", + ) { + ReadResourceResult( + contents = listOf( + TextResourceContents( + text = "Test resource content 1", + uri = testResourceUri1, + mimeType = "text/plain", + ), + ), + ) + } + server.addResource( + uri = testResourceUri2, + name = "Test Resource 2", + description = "A test resource 2", + mimeType = "text/plain", + ) { + ReadResourceResult( + contents = listOf( + TextResourceContents( + text = "Test resource content 2", + uri = testResourceUri2, + mimeType = "text/plain", + ), + ), + ) + } + + // Remove the resources + val result = server.removeResources(listOf(testResourceUri1, testResourceUri2)) + + // Verify the resources were removed + assertEquals(2, result, "Both resources should be removed") + } + + @Test + fun `removeResource should return false when resource does not exist`() = runTest { + // Track notifications + var resourceListChangedNotificationReceived = false + client.setNotificationHandler( + Method.Defined.NotificationsResourcesListChanged, + ) { + resourceListChangedNotificationReceived = true + CompletableDeferred(Unit) + } + + // Try to remove a non-existent resource + val result = server.removeResource("non-existent-resource") + + // Verify the result + assertFalse(result, "Removing non-existent resource should return false") + assertFalse( + resourceListChangedNotificationReceived, + "No notification should be sent when resource doesn't exist", + ) + } + + @Test + fun `removeResource should throw when resources capability is not supported`() = runTest { + // Create server without resources capability + val serverOptions = ServerOptions( + capabilities = ServerCapabilities(), + ) + val server = Server( + Implementation(name = "test server", version = "1.0"), + serverOptions, + ) + + // Verify that removing a resource throws an exception + val exception = assertThrows { + server.removeResource("test://resource") + } + assertEquals("Server does not support resources capability.", exception.message) + } +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerTest.kt deleted file mode 100644 index e0bf1c6..0000000 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerTest.kt +++ /dev/null @@ -1,530 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.server - -import io.modelcontextprotocol.kotlin.sdk.CallToolResult -import io.modelcontextprotocol.kotlin.sdk.GetPromptResult -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.Method -import io.modelcontextprotocol.kotlin.sdk.Prompt -import io.modelcontextprotocol.kotlin.sdk.PromptListChangedNotification -import io.modelcontextprotocol.kotlin.sdk.ReadResourceResult -import io.modelcontextprotocol.kotlin.sdk.ResourceListChangedNotification -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities -import io.modelcontextprotocol.kotlin.sdk.TextContent -import io.modelcontextprotocol.kotlin.sdk.TextResourceContents -import io.modelcontextprotocol.kotlin.sdk.Tool -import io.modelcontextprotocol.kotlin.sdk.ToolListChangedNotification -import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.launch -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertNull -import org.junit.jupiter.api.assertThrows -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -class ServerTest { - - @Test - fun `removeTool should remove a tool`() = runTest { - // Create server with tools capability - val serverOptions = ServerOptions( - capabilities = ServerCapabilities( - tools = ServerCapabilities.Tools(null), - ), - ) - val server = Server( - Implementation(name = "test server", version = "1.0"), - serverOptions, - ) - - // Add a tool - server.addTool("test-tool", "Test Tool", Tool.Input()) { - CallToolResult(listOf(TextContent("Test result"))) - } - - // Setup client - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - val client = Client( - clientInfo = Implementation(name = "test client", version = "1.0"), - ) - - // Connect client and server - launch { client.connect(clientTransport) } - launch { server.createSession(serverTransport) } - - // Remove the tool - val result = server.removeTool("test-tool") - - // Verify the tool was removed - assertTrue(result, "Tool should be removed successfully") - } - - @Test - fun `removeTool should return false when tool does not exist`() = runTest { - // Create server with tools capability - val serverOptions = ServerOptions( - capabilities = ServerCapabilities( - tools = ServerCapabilities.Tools(null), - ), - ) - val server = Server( - Implementation(name = "test server", version = "1.0"), - serverOptions, - ) - - // Setup client - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - val client = Client( - clientInfo = Implementation(name = "test client", version = "1.0"), - ) - - // Track notifications - var toolListChangedNotificationReceived = false - client.setNotificationHandler(Method.Defined.NotificationsToolsListChanged) { - toolListChangedNotificationReceived = true - CompletableDeferred(Unit) - } - - // Connect client and server - launch { client.connect(clientTransport) } - launch { server.createSession(serverTransport) } - - // Try to remove a non-existent tool - val result = server.removeTool("non-existent-tool") - - // Verify the result - assertFalse(result, "Removing non-existent tool should return false") - assertFalse(toolListChangedNotificationReceived, "No notification should be sent when tool doesn't exist") - } - - @Test - fun `removeTool should throw when tools capability is not supported`() = runTest { - // Create server without tools capability - val serverOptions = ServerOptions( - capabilities = ServerCapabilities(), - ) - val server = Server( - Implementation(name = "test server", version = "1.0"), - serverOptions, - ) - - // Verify that removing a tool throws an exception - val exception = assertThrows { - server.removeTool("test-tool") - } - assertEquals("Server does not support tools capability.", exception.message) - } - - @Test - fun `removeTools should remove multiple tools`() = runTest { - // Create server with tools capability - val serverOptions = ServerOptions( - capabilities = ServerCapabilities( - tools = ServerCapabilities.Tools(null), - ), - ) - val server = Server( - Implementation(name = "test server", version = "1.0"), - serverOptions, - ) - - // Add tools - server.addTool("test-tool-1", "Test Tool 1") { - CallToolResult(listOf(TextContent("Test result 1"))) - } - server.addTool("test-tool-2", "Test Tool 2") { - CallToolResult(listOf(TextContent("Test result 2"))) - } - - // Setup client - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - val client = Client( - clientInfo = Implementation(name = "test client", version = "1.0"), - ) - - // Connect client and server - launch { client.connect(clientTransport) } - launch { server.createSession(serverTransport) } - - // Remove the tools - val result = server.removeTools(listOf("test-tool-1", "test-tool-2")) - - // Verify the tools were removed - assertEquals(2, result, "Both tools should be removed") - } - - @Test - fun `removePrompt should remove a prompt`() = runTest { - // Create server with prompts capability - val serverOptions = ServerOptions( - capabilities = ServerCapabilities( - prompts = ServerCapabilities.Prompts(listChanged = false), - ), - ) - val server = Server( - Implementation(name = "test server", version = "1.0"), - serverOptions, - ) - - // Add a prompt - val testPrompt = Prompt("test-prompt", "Test Prompt", null) - server.addPrompt(testPrompt) { - GetPromptResult( - description = "Test prompt description", - messages = listOf(), - ) - } - - // Setup client - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - val client = Client( - clientInfo = Implementation(name = "test client", version = "1.0"), - ) - - // Connect client and server - launch { client.connect(clientTransport) } - launch { server.createSession(serverTransport) } - - // Remove the prompt - val result = server.removePrompt(testPrompt.name) - - // Verify the prompt was removed - assertTrue(result, "Prompt should be removed successfully") - } - - @Test - fun `removePrompts should remove multiple prompts and send notification`() = runTest { - // Create server with prompts capability - val serverOptions = ServerOptions( - capabilities = ServerCapabilities( - prompts = ServerCapabilities.Prompts(listChanged = false), - ), - ) - val server = Server( - Implementation(name = "test server", version = "1.0"), - serverOptions, - ) - - // Add prompts - val testPrompt1 = Prompt("test-prompt-1", "Test Prompt 1", null) - val testPrompt2 = Prompt("test-prompt-2", "Test Prompt 2", null) - server.addPrompt(testPrompt1) { - GetPromptResult( - description = "Test prompt description 1", - messages = listOf(), - ) - } - server.addPrompt(testPrompt2) { - GetPromptResult( - description = "Test prompt description 2", - messages = listOf(), - ) - } - - // Setup client - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - val client = Client( - clientInfo = Implementation(name = "test client", version = "1.0"), - ) - - // Connect client and server - launch { client.connect(clientTransport) } - launch { server.createSession(serverTransport) } - - // Remove the prompts - val result = server.removePrompts(listOf(testPrompt1.name, testPrompt2.name)) - - // Verify the prompts were removed - assertEquals(2, result, "Both prompts should be removed") - } - - @Test - fun `removeResource should remove a resource and send notification`() = runTest { - // Create server with resources capability - val serverOptions = ServerOptions( - capabilities = ServerCapabilities( - resources = ServerCapabilities.Resources(null, null), - ), - ) - val server = Server( - Implementation(name = "test server", version = "1.0"), - serverOptions, - ) - - // Add a resource - val testResourceUri = "test://resource" - server.addResource( - uri = testResourceUri, - name = "Test Resource", - description = "A test resource", - mimeType = "text/plain", - ) { - ReadResourceResult( - contents = listOf( - TextResourceContents( - text = "Test resource content", - uri = testResourceUri, - mimeType = "text/plain", - ), - ), - ) - } - - // Setup client - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - val client = Client( - clientInfo = Implementation(name = "test client", version = "1.0"), - ) - - // Connect client and server - launch { client.connect(clientTransport) } - launch { server.createSession(serverTransport) } - - // Remove the resource - val result = server.removeResource(testResourceUri) - - // Verify the resource was removed - assertTrue(result, "Resource should be removed successfully") - } - - @Test - fun `removeResources should remove multiple resources and send notification`() = runTest { - // Create server with resources capability - val serverOptions = ServerOptions( - capabilities = ServerCapabilities( - resources = ServerCapabilities.Resources(null, null), - ), - ) - val server = Server( - Implementation(name = "test server", version = "1.0"), - serverOptions, - ) - - // Add resources - val testResourceUri1 = "test://resource1" - val testResourceUri2 = "test://resource2" - server.addResource( - uri = testResourceUri1, - name = "Test Resource 1", - description = "A test resource 1", - mimeType = "text/plain", - ) { - ReadResourceResult( - contents = listOf( - TextResourceContents( - text = "Test resource content 1", - uri = testResourceUri1, - mimeType = "text/plain", - ), - ), - ) - } - server.addResource( - uri = testResourceUri2, - name = "Test Resource 2", - description = "A test resource 2", - mimeType = "text/plain", - ) { - ReadResourceResult( - contents = listOf( - TextResourceContents( - text = "Test resource content 2", - uri = testResourceUri2, - mimeType = "text/plain", - ), - ), - ) - } - - // Setup client - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - val client = Client( - clientInfo = Implementation(name = "test client", version = "1.0"), - ) - - // Connect client and server - launch { client.connect(clientTransport) } - launch { server.createSession(serverTransport) } - - // Remove the resources - val result = server.removeResources(listOf(testResourceUri1, testResourceUri2)) - - // Verify the resources were removed - assertEquals(2, result, "Both resources should be removed") - } - - @Test - fun `removePrompt should return false when prompt does not exist`() = runTest { - // Create server with prompts capability - val serverOptions = ServerOptions( - capabilities = ServerCapabilities( - prompts = ServerCapabilities.Prompts(listChanged = false), - ), - ) - val server = Server( - Implementation(name = "test server", version = "1.0"), - serverOptions, - ) - - // Setup client - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - val client = Client( - clientInfo = Implementation(name = "test client", version = "1.0"), - ) - - // Track notifications - var promptListChangedNotificationReceived = false - client.setNotificationHandler(Method.Defined.NotificationsPromptsListChanged) { - promptListChangedNotificationReceived = true - CompletableDeferred(Unit) - } - - // Connect client and server - launch { client.connect(clientTransport) } - launch { server.createSession(serverTransport) } - - // Try to remove a non-existent prompt - val result = server.removePrompt("non-existent-prompt") - - // Verify the result - assertFalse(result, "Removing non-existent prompt should return false") - assertFalse(promptListChangedNotificationReceived, "No notification should be sent when prompt doesn't exist") - } - - @Test - fun `removePrompt should throw when prompts capability is not supported`() = runTest { - // Create server without prompts capability - val serverOptions = ServerOptions( - capabilities = ServerCapabilities(), - ) - val server = Server( - Implementation(name = "test server", version = "1.0"), - serverOptions, - ) - - // Verify that removing a prompt throws an exception - val exception = assertThrows { - server.removePrompt("test-prompt") - } - assertEquals("Server does not support prompts capability.", exception.message) - } - - @Test - fun `removeResource should return false when resource does not exist`() = runTest { - // Create server with resources capability - val serverOptions = ServerOptions( - capabilities = ServerCapabilities( - resources = ServerCapabilities.Resources(null, null), - ), - ) - val server = Server( - Implementation(name = "test server", version = "1.0"), - serverOptions, - ) - - // Setup client - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - val client = Client( - clientInfo = Implementation(name = "test client", version = "1.0"), - ) - - // Track notifications - var resourceListChangedNotificationReceived = false - client.setNotificationHandler( - Method.Defined.NotificationsResourcesListChanged, - ) { - resourceListChangedNotificationReceived = true - CompletableDeferred(Unit) - } - - // Connect client and server - launch { client.connect(clientTransport) } - launch { server.createSession(serverTransport) } - - // Try to remove a non-existent resource - val result = server.removeResource("non-existent-resource") - - // Verify the result - assertFalse(result, "Removing non-existent resource should return false") - assertFalse( - resourceListChangedNotificationReceived, - "No notification should be sent when resource doesn't exist", - ) - } - - @Test - fun `removeResource should throw when resources capability is not supported`() = runTest { - // Create server without resources capability - val serverOptions = ServerOptions( - capabilities = ServerCapabilities(), - ) - val server = Server( - Implementation(name = "test server", version = "1.0"), - serverOptions, - ) - - // Verify that removing a resource throws an exception - val exception = assertThrows { - server.removeResource("test://resource") - } - assertEquals("Server does not support resources capability.", exception.message) - } - - @Test - fun `Server constructor should accept instructions provider parameter`() = runTest { - val serverInfo = Implementation(name = "test server", version = "1.0") - val serverOptions = ServerOptions(capabilities = ServerCapabilities()) - val instructions = "This is a test server. Use it for testing purposes only." - - val server = Server(serverInfo, serverOptions, { instructions }) - - // 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) - - assertEquals(instructions, client.serverInstructions) - } - - @Test - fun `Server constructor should accept instructions parameter`() = runTest { - val serverInfo = Implementation(name = "test server", version = "1.0") - val serverOptions = ServerOptions(capabilities = ServerCapabilities()) - val instructions = "This is a test server. Use it for testing purposes only." - - val server = Server(serverInfo, serverOptions, instructions) - - // 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) - - assertEquals(instructions, client.serverInstructions) - } - - @Test - fun `Server constructor should work without instructions parameter`() = runTest { - val serverInfo = Implementation(name = "test server", version = "1.0") - val serverOptions = ServerOptions(capabilities = ServerCapabilities()) - - // Test that server works when instructions parameter is omitted (defaults to null) - 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) - - assertNull(client.serverInstructions) - } -} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsTest.kt new file mode 100644 index 0000000..2ccb460 --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsTest.kt @@ -0,0 +1,89 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.modelcontextprotocol.kotlin.sdk.CallToolResult +import io.modelcontextprotocol.kotlin.sdk.Implementation +import io.modelcontextprotocol.kotlin.sdk.Method +import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.TextContent +import io.modelcontextprotocol.kotlin.sdk.Tool +import io.modelcontextprotocol.kotlin.sdk.ToolListChangedNotification +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class ServerToolsTest : AbstractServerFeaturesTest() { + + override fun getServerCapabilities(): ServerCapabilities = ServerCapabilities( + tools = ServerCapabilities.Tools(null), + ) + + @Test + fun `removeTool should remove a tool`() = runTest { + // Add a tool + server.addTool("test-tool", "Test Tool", Tool.Input()) { + CallToolResult(listOf(TextContent("Test result"))) + } + + // Remove the tool + val result = server.removeTool("test-tool") + + // Verify the tool was removed + assertTrue(result, "Tool should be removed successfully") + } + + @Test + fun `removeTool should return false when tool does not exist`() = runTest { + // Track notifications + var toolListChangedNotificationReceived = false + client.setNotificationHandler(Method.Defined.NotificationsToolsListChanged) { + toolListChangedNotificationReceived = true + CompletableDeferred(Unit) + } + + // Try to remove a non-existent tool + val result = server.removeTool("non-existent-tool") + + // Verify the result + assertFalse(result, "Removing non-existent tool should return false") + assertFalse(toolListChangedNotificationReceived, "No notification should be sent when tool doesn't exist") + } + + @Test + fun `removeTool should throw when tools capability is not supported`() = runTest { + // Create server without tools capability + val serverOptions = ServerOptions( + capabilities = ServerCapabilities(), + ) + val server = Server( + Implementation(name = "test server", version = "1.0"), + serverOptions, + ) + + // Verify that removing a tool throws an exception + val exception = assertThrows { + server.removeTool("test-tool") + } + assertEquals("Server does not support tools capability.", exception.message) + } + + @Test + fun `removeTools should remove multiple tools`() = runTest { + // Add tools + server.addTool("test-tool-1", "Test Tool 1") { + CallToolResult(listOf(TextContent("Test result 1"))) + } + server.addTool("test-tool-2", "Test Tool 2") { + CallToolResult(listOf(TextContent("Test result 2"))) + } + + // Remove the tools + val result = server.removeTools(listOf("test-tool-1", "test-tool-2")) + + // Verify the tools were removed + assertEquals(2, result, "Both tools should be removed") + } +} From a75e7a81976ceaf6d4ac10f593b8f87f68a7efc2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 20:33:29 +0200 Subject: [PATCH 044/449] Bump org.openapi.generator from 7.16.0 to 7.17.0 (#362) Bumps org.openapi.generator from 7.16.0 to 7.17.0. [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.openapi.generator&package-manager=gradle&previous-version=7.16.0&new-version=7.17.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- kotlin-sdk-core/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlin-sdk-core/build.gradle.kts b/kotlin-sdk-core/build.gradle.kts index a3e1b58..186e168 100644 --- a/kotlin-sdk-core/build.gradle.kts +++ b/kotlin-sdk-core/build.gradle.kts @@ -7,7 +7,7 @@ plugins { id("mcp.publishing") id("mcp.dokka") alias(libs.plugins.kotlinx.binary.compatibility.validator) - id("org.openapi.generator") version "7.16.0" + id("org.openapi.generator") version "7.17.0" } /* From ab9ba54b33b708262c6e12049a2bfef3ca7b6fe5 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Mon, 3 Nov 2025 19:45:09 +0100 Subject: [PATCH 045/449] Fix: Handle logging/setLevel request for server (#358) Fix #293 Also fixed a bug with initialization: properties must be declared before the init block; otherwise, the initialization order is violated and an NPE will occur in the init block. ## Motivation and Context After initialization, MCP clients send a `logging/setLevel` request if the logging capability is enabled. This request was previously not handled, leading to an error that blocked the use of the MCP server in the inspector and other clients. ## How Has This Been Tested? Added integration test ## Breaking Changes None ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- .../kotlin/sdk/client/Client.kt | 2 +- .../kotlin/sdk/server/ServerSession.kt | 83 +++++++++++++++---- .../kotlin/sdk/client/ClientTest.kt | 80 ++++++++++++++++++ 3 files changed, 146 insertions(+), 19 deletions(-) diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt index 56fd1ca..8eb8957 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt @@ -315,7 +315,7 @@ public open class Client(private val clientInfo: Implementation, options: Client * @throws IllegalStateException If the server does not support logging. */ public suspend fun setLoggingLevel(level: LoggingLevel, options: RequestOptions? = null): EmptyRequestResult = - request(SetLevelRequest(level), options) + request(SetLevelRequest(level), options) /** * Retrieves a prompt by name from the server. diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt index 20524ad..887b57b 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt @@ -16,6 +16,7 @@ import io.modelcontextprotocol.kotlin.sdk.InitializedNotification import io.modelcontextprotocol.kotlin.sdk.LATEST_PROTOCOL_VERSION import io.modelcontextprotocol.kotlin.sdk.ListRootsRequest import io.modelcontextprotocol.kotlin.sdk.ListRootsResult +import io.modelcontextprotocol.kotlin.sdk.LoggingLevel import io.modelcontextprotocol.kotlin.sdk.LoggingMessageNotification import io.modelcontextprotocol.kotlin.sdk.Method import io.modelcontextprotocol.kotlin.sdk.Method.Defined @@ -27,6 +28,8 @@ import io.modelcontextprotocol.kotlin.sdk.SUPPORTED_PROTOCOL_VERSIONS import io.modelcontextprotocol.kotlin.sdk.ToolListChangedNotification import io.modelcontextprotocol.kotlin.sdk.shared.Protocol import io.modelcontextprotocol.kotlin.sdk.shared.RequestOptions +import kotlinx.atomicfu.AtomicRef +import kotlinx.atomicfu.atomic import kotlinx.coroutines.CompletableDeferred import kotlinx.serialization.json.JsonObject @@ -43,22 +46,6 @@ public open class ServerSession( @Suppress("ktlint:standard:backing-property-naming") private var _onClose: () -> Unit = {} - init { - // Core protocol handlers - setRequestHandler(Method.Defined.Initialize) { request, _ -> - handleInitialize(request) - } - setNotificationHandler(Method.Defined.NotificationsInitialized) { - _onInitialized() - CompletableDeferred(Unit) - } - } - - /** - * The capabilities supported by the server, related to the session. - */ - private val serverCapabilities = options.capabilities - /** * The client's reported capabilities after initialization. */ @@ -71,6 +58,37 @@ public open class ServerSession( public var clientVersion: Implementation? = null private set + /** + * The capabilities supported by the server, related to the session. + */ + private val serverCapabilities = options.capabilities + + /** + * The current logging level set by the client. + * When null, all messages are sent (no filtering). + */ + private val currentLoggingLevel: AtomicRef = atomic(null) + + init { + // Core protocol handlers + setRequestHandler(Defined.Initialize) { request, _ -> + handleInitialize(request) + } + setNotificationHandler(Defined.NotificationsInitialized) { + _onInitialized() + CompletableDeferred(Unit) + } + + // Logging level handler + if (options.capabilities.logging != null) { + setRequestHandler(Defined.LoggingSetLevel) { request, _ -> + currentLoggingLevel.value = request.level + logger.debug { "Logging level set to: ${request.level}" } + EmptyRequestResult() + } + } + } + /** * Registers a callback to be invoked when the server has completed initialization. */ @@ -160,12 +178,20 @@ public open class ServerSession( /** * Sends a logging message notification to the client. + * Messages are filtered based on the current logging level set by the client. + * If no logging level is set, all messages are sent. * * @param notification The logging message notification. */ public suspend fun sendLoggingMessage(notification: LoggingMessageNotification) { - logger.trace { "Sending logging message: ${notification.params.data}" } - notification(notification) + if (serverCapabilities.logging != null) { + if (isMessageAccepted(notification.params.level)) { + logger.trace { "Sending logging message: ${notification.params.data}" } + notification(notification) + } else { + logger.trace { "Filtering out logging message with level ${notification.params.level}" } + } + } } /** @@ -318,6 +344,7 @@ public open class ServerSession( Defined.LoggingSetLevel -> { if (serverCapabilities.logging == null) { + logger.error { "Server does not support logging (required for $method)" } throw IllegalStateException("Server does not support logging (required for $method)") } } @@ -381,4 +408,24 @@ public open class ServerSession( instructions = instructions, ) } + + /** + * Checks if a message with the given level should be ignored based on the current logging level. + * + * @param level The level of the message to check. + * @return true if the message should be ignored (filtered out), false otherwise. + */ + private fun isMessageIgnored(level: LoggingLevel): Boolean { + val current = currentLoggingLevel.value ?: return false // If no level is set, don't filter + + return level.ordinal < current.ordinal + } + + /** + * Checks if a message with the given level should be accepted based on the current logging level. + * + * @param level The level of the message to check. + * @return true if the message should be accepted (not filtered out), false otherwise. + */ + private fun isMessageAccepted(level: LoggingLevel): Boolean = !isMessageIgnored(level) } diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt index b563ba0..ad2a958 100644 --- a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt +++ b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt @@ -886,6 +886,86 @@ class ClientTest { ) } + @Test + fun `should handle logging setLevel request`() = runTest { + val server = Server( + Implementation(name = "test server", version = "1.0"), + ServerOptions( + capabilities = ServerCapabilities( + logging = EmptyJsonObject, + ), + ), + ) + + val client = Client( + clientInfo = Implementation(name = "test client", version = "1.0"), + options = ClientOptions( + capabilities = ClientCapabilities(), + ), + ) + + val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() + + val receivedMessages = mutableListOf() + client.setNotificationHandler(Method.Defined.NotificationsMessage) { notification -> + receivedMessages.add(notification) + CompletableDeferred(Unit) + } + + val serverSessionResult = CompletableDeferred() + + listOf( + launch { + client.connect(clientTransport) + println("Client connected") + }, + launch { + serverSessionResult.complete(server.connect(serverTransport)) + println("Server connected") + }, + ).joinAll() + + val serverSession = serverSessionResult.await() + + // Set logging level to warning + val minLevel = LoggingLevel.warning + val result = client.setLoggingLevel(minLevel) + assertEquals(EmptyJsonObject, result._meta) + + // Send messages of different levels + val testMessages = listOf( + LoggingLevel.debug to "Debug - should be filtered", + LoggingLevel.info to "Info - should be filtered", + LoggingLevel.warning to "Warning - should pass", + LoggingLevel.error to "Error - should pass", + ) + + testMessages.forEach { (level, message) -> + serverSession.sendLoggingMessage( + LoggingMessageNotification( + params = LoggingMessageNotification.Params( + level = level, + data = buildJsonObject { put("message", message) }, + ), + ), + ) + } + + delay(100) + + // Only warning and error should be received + assertEquals(2, receivedMessages.size, "Should receive only 2 messages (warning and error)") + + // Verify all received messages have severity >= minLevel + receivedMessages.forEach { message -> + val messageSeverity = message.params.level.ordinal + assertTrue( + messageSeverity >= minLevel.ordinal, + "Received message with level ${message.params.level} should have severity >= $minLevel", + ) + } + } + @Test fun `should handle server elicitation`() = runTest { val client = Client( From 935a1eddbc00c667535fad912dfb72630787eb5e Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Tue, 4 Nov 2025 11:44:39 +0200 Subject: [PATCH 046/449] Add Ktor BOM for dependency management across all modules, run Kotlin-MCP-Server Sample (#367) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Motivation and Context - 📦 Unifies Ktor library versioning by introducing the Ktor BOM dependency. - ✅ Re-enable build of MCP Server Sample ## How Has This Been Tested? Regression tests ## Breaking Changes No ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [x] Build configuration 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed ## Additional context --- .github/workflows/build.yml | 6 ++-- .github/workflows/samples.yml | 2 +- gradle/libs.versions.toml | 32 ++++++++++--------- kotlin-sdk-client/build.gradle.kts | 2 +- kotlin-sdk-core/build.gradle.kts | 3 +- kotlin-sdk-server/build.gradle.kts | 1 + kotlin-sdk-test/build.gradle.kts | 1 + kotlin-sdk/build.gradle.kts | 1 + samples/kotlin-mcp-client/build.gradle.kts | 1 + .../gradle/libs.versions.toml | 3 +- samples/kotlin-mcp-server/build.gradle.kts | 1 + .../gradle/libs.versions.toml | 3 +- samples/weather-stdio-server/build.gradle.kts | 1 + .../gradle/libs.versions.toml | 7 ++-- 14 files changed, 38 insertions(+), 26 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3371dcc..fd848b4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -49,9 +49,9 @@ jobs: working-directory: ./samples/kotlin-mcp-client run: ./gradlew --no-daemon clean build -Pmcp.kotlin.overrideVersion=0.0.1-SNAPSHOT -# - name: Build Kotlin-MCP-Server Sample -# working-directory: ./samples/kotlin-mcp-server -# run: ./gradlew --no-daemon clean build -Pmcp.kotlin.overrideVersion=0.0.1-SNAPSHOT + - name: Build Kotlin-MCP-Server Sample + working-directory: ./samples/kotlin-mcp-server + run: ./gradlew --no-daemon clean build -Pmcp.kotlin.overrideVersion=0.0.1-SNAPSHOT - name: Build Weather-Stdio-Server Sample working-directory: ./samples/weather-stdio-server diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index f023e4f..7ef265d 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -23,7 +23,7 @@ jobs: matrix: sample: - kotlin-mcp-client -# - kotlin-mcp-server + - kotlin-mcp-server - weather-stdio-server name: Build Sample diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d937c39..0283a64 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,6 +8,7 @@ kover = "0.9.3" netty = "4.2.7.Final" mavenPublish = "0.34.0" binaryCompatibilityValidatorPlugin = "0.18.1" +openapi-generator = "7.17.0" # libraries version serialization = "1.9.0" @@ -23,45 +24,46 @@ mokksy = "0.6.1" [libraries] # Plugins +dokka-gradle = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" } kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } kotlin-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" } kotlinx-atomicfu-gradle = { module = "org.jetbrains.kotlinx:atomicfu-gradle-plugin", version.ref = "atomicfu" } -dokka-gradle = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" } maven-publish = { module = "com.vanniktech:gradle-maven-publish-plugin", version.ref = "mavenPublish" } # Kotlinx libraries -kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "serialization" } +kotlin-logging = { group = "io.github.oshai", name = "kotlin-logging", version.ref = "logging" } +kotlinx-collections-immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable", version.ref = "collections-immutable" } kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutines" } kotlinx-coroutines-core-wasm = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core-wasm-js", version.ref = "coroutines" } kotlinx-io-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-io-core", version.ref = "kotlinx-io" } -kotlinx-collections-immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable", version.ref = "collections-immutable" } -kotlin-logging = { group = "io.github.oshai", name = "kotlin-logging", version.ref = "logging" } +kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "serialization" } # Ktor ktor-bom = { group = "io.ktor", name = "ktor-bom", version.ref = "ktor" } -ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" } -ktor-client-logging = { group = "io.ktor", name = "ktor-client-logging"} ktor-client-apache5 = { group = "io.ktor", name = "ktor-client-apache5" } -ktor-server-sse = { group = "io.ktor", name = "ktor-server-sse", version.ref = "ktor" } -ktor-server-websockets = { group = "io.ktor", name = "ktor-server-websockets", version.ref = "ktor" } -ktor-server-core = { group = "io.ktor", name = "ktor-server-core", version.ref = "ktor" } +ktor-client-core = { group = "io.ktor", name = "ktor-client-core" } +ktor-client-logging = { group = "io.ktor", name = "ktor-client-logging" } +ktor-server-core = { group = "io.ktor", name = "ktor-server-core" } +ktor-server-sse = { group = "io.ktor", name = "ktor-server-sse" } +ktor-server-websockets = { group = "io.ktor", name = "ktor-server-websockets" } # Testing awaitility = { group = "org.awaitility", name = "awaitility-kotlin", version.ref = "awaitility" } kotest-assertions-core = { group = "io.kotest", name = "kotest-assertions-core", version.ref = "kotest" } kotest-assertions-json = { group = "io.kotest", name = "kotest-assertions-json", version.ref = "kotest" } kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines" } -ktor-client-mock = { group = "io.ktor", name = "ktor-client-mock", version.ref = "ktor" } -ktor-server-test-host = { group = "io.ktor", name = "ktor-server-test-host", version.ref = "ktor" } +ktor-client-mock = { group = "io.ktor", name = "ktor-client-mock" } +ktor-server-test-host = { group = "io.ktor", name = "ktor-server-test-host" } mokksy = { group = "dev.mokksy", name = "mokksy", version.ref = "mokksy" } -slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" } netty-bom = { group = "io.netty", name = "netty-bom", version.ref = "netty" } +slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" } # Samples -ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio", version.ref = "ktor" } -ktor-server-cio = { group = "io.ktor", name = "ktor-server-cio", version.ref = "ktor" } +ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio" } +ktor-server-cio = { group = "io.ktor", name = "ktor-server-cio" } [plugins] kotlinx-binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binaryCompatibilityValidatorPlugin" } -ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" } kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } +ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" } +openapi-generator = { id = "org.openapi.generator", version.ref = "openapi-generator" } diff --git a/kotlin-sdk-client/build.gradle.kts b/kotlin-sdk-client/build.gradle.kts index 2466f8e..838afce 100644 --- a/kotlin-sdk-client/build.gradle.kts +++ b/kotlin-sdk-client/build.gradle.kts @@ -32,6 +32,7 @@ kotlin { sourceSets { commonMain { dependencies { + implementation(dependencies.platform(libs.ktor.bom)) api(project(":kotlin-sdk-core")) api(libs.ktor.client.core) implementation(libs.kotlin.logging) @@ -41,7 +42,6 @@ kotlin { commonTest { dependencies { implementation(kotlin("test")) - implementation(dependencies.platform(libs.ktor.bom)) implementation(libs.ktor.client.mock) implementation(libs.ktor.server.websockets) implementation(libs.kotlinx.coroutines.test) diff --git a/kotlin-sdk-core/build.gradle.kts b/kotlin-sdk-core/build.gradle.kts index 186e168..9e07904 100644 --- a/kotlin-sdk-core/build.gradle.kts +++ b/kotlin-sdk-core/build.gradle.kts @@ -7,7 +7,7 @@ plugins { id("mcp.publishing") id("mcp.dokka") alias(libs.plugins.kotlinx.binary.compatibility.validator) - id("org.openapi.generator") version "7.17.0" + alias(libs.plugins.openapi.generator) } /* @@ -110,6 +110,7 @@ kotlin { commonMain { kotlin.srcDir(generateLibVersion) dependencies { + implementation(dependencies.platform(libs.ktor.bom)) api(libs.kotlinx.serialization.json) api(libs.kotlinx.coroutines.core) api(libs.kotlinx.io.core) diff --git a/kotlin-sdk-server/build.gradle.kts b/kotlin-sdk-server/build.gradle.kts index 64a2749..f00fc40 100644 --- a/kotlin-sdk-server/build.gradle.kts +++ b/kotlin-sdk-server/build.gradle.kts @@ -9,6 +9,7 @@ kotlin { sourceSets { commonMain { dependencies { + implementation(dependencies.platform(libs.ktor.bom)) api(project(":kotlin-sdk-core")) api(libs.ktor.server.core) api(libs.ktor.server.sse) diff --git a/kotlin-sdk-test/build.gradle.kts b/kotlin-sdk-test/build.gradle.kts index 41bf545..b8532e1 100644 --- a/kotlin-sdk-test/build.gradle.kts +++ b/kotlin-sdk-test/build.gradle.kts @@ -11,6 +11,7 @@ kotlin { sourceSets { commonTest { dependencies { + implementation(dependencies.platform(libs.ktor.bom)) implementation(project(":kotlin-sdk")) implementation(kotlin("test")) implementation(libs.kotest.assertions.json) diff --git a/kotlin-sdk/build.gradle.kts b/kotlin-sdk/build.gradle.kts index e59df60..c59ba69 100644 --- a/kotlin-sdk/build.gradle.kts +++ b/kotlin-sdk/build.gradle.kts @@ -7,6 +7,7 @@ kotlin { sourceSets { commonMain { dependencies { + implementation(dependencies.platform(libs.ktor.bom)) api(project(":kotlin-sdk-core")) api(project(":kotlin-sdk-client")) api(project(":kotlin-sdk-server")) diff --git a/samples/kotlin-mcp-client/build.gradle.kts b/samples/kotlin-mcp-client/build.gradle.kts index 3e3ad85..61774cb 100644 --- a/samples/kotlin-mcp-client/build.gradle.kts +++ b/samples/kotlin-mcp-client/build.gradle.kts @@ -12,6 +12,7 @@ group = "org.example" version = "0.1.0" dependencies { + implementation(dependencies.platform(libs.ktor.bom)) implementation(libs.mcp.kotlin.client) implementation(libs.ktor.client.cio) implementation(libs.anthropic.java) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index 7490ed6..99cde11 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -8,7 +8,8 @@ slf4j = "2.0.17" [libraries] anthropic-java = { group = "com.anthropic", name = "anthropic-java", version.ref = "anthropic" } -ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio", version.ref = "ktor" } +ktor-bom = { group = "io.ktor", name = "ktor-bom", version.ref = "ktor" } +ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio" } mcp-kotlin-client = { group = "io.modelcontextprotocol", name = "kotlin-sdk-client", version.ref = "mcp-kotlin" } slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" } diff --git a/samples/kotlin-mcp-server/build.gradle.kts b/samples/kotlin-mcp-server/build.gradle.kts index 0bf45d8..48789db 100644 --- a/samples/kotlin-mcp-server/build.gradle.kts +++ b/samples/kotlin-mcp-server/build.gradle.kts @@ -12,6 +12,7 @@ application { } dependencies { + implementation(dependencies.platform(libs.ktor.bom)) implementation(libs.mcp.kotlin.server) implementation(libs.ktor.server.cio) implementation(libs.slf4j.simple) diff --git a/samples/kotlin-mcp-server/gradle/libs.versions.toml b/samples/kotlin-mcp-server/gradle/libs.versions.toml index 37eb160..c4c437a 100644 --- a/samples/kotlin-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-server/gradle/libs.versions.toml @@ -6,7 +6,8 @@ slf4j = "2.0.17" serialization = "1.9.0" [libraries] -ktor-server-cio = { group = "io.ktor", name = "ktor-server-cio", version.ref = "ktor" } +ktor-bom = { group = "io.ktor", name = "ktor-bom", version.ref = "ktor" } +ktor-server-cio = { group = "io.ktor", name = "ktor-server-cio" } mcp-kotlin-server = { group = "io.modelcontextprotocol", name = "kotlin-sdk-server", version.ref = "mcp-kotlin" } slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" } diff --git a/samples/weather-stdio-server/build.gradle.kts b/samples/weather-stdio-server/build.gradle.kts index 2b087d9..0420cc0 100644 --- a/samples/weather-stdio-server/build.gradle.kts +++ b/samples/weather-stdio-server/build.gradle.kts @@ -13,6 +13,7 @@ group = "org.example" version = "0.1.0" dependencies { + implementation(dependencies.platform(libs.ktor.bom)) implementation(libs.ktor.client.content.negotiation) implementation(libs.ktor.serialization.kotlinx.json) implementation(libs.mcp.kotlin.server) diff --git a/samples/weather-stdio-server/gradle/libs.versions.toml b/samples/weather-stdio-server/gradle/libs.versions.toml index 87efc88..3ac5426 100644 --- a/samples/weather-stdio-server/gradle/libs.versions.toml +++ b/samples/weather-stdio-server/gradle/libs.versions.toml @@ -12,9 +12,10 @@ slf4j = "2.0.17" kotlin-logging = { group = "io.github.oshai", name = "kotlin-logging-jvm", version.ref = "logging" } kotlinx-collections-immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable-jvm", version.ref = "collections-immutable" } kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines" } -ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio", version.ref = "ktor" } -ktor-client-content-negotiation = { group = "io.ktor", name = "ktor-client-content-negotiation", version.ref = "ktor" } -ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json-jvm", version.ref = "ktor" } +ktor-bom = { group = "io.ktor", name = "ktor-bom", version.ref = "ktor" } +ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio" } +ktor-client-content-negotiation = { group = "io.ktor", name = "ktor-client-content-negotiation" } +ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json-jvm" } ktor-server-cio = { group = "io.ktor", name = "ktor-server-cio", version.ref = "ktor" } mcp-kotlin-client = { group = "io.modelcontextprotocol", name = "kotlin-sdk-client", version.ref = "mcp-kotlin" } mcp-kotlin-server = { group = "io.modelcontextprotocol", name = "kotlin-sdk-server", version.ref = "mcp-kotlin" } From f462019c43ea75cdd24114ac3f11a723a2b8383a Mon Sep 17 00:00:00 2001 From: Maria Tigina <31625351+tiginamaria@users.noreply.github.com> Date: Tue, 4 Nov 2025 14:07:19 +0100 Subject: [PATCH 047/449] Add tests to server samples (#369) Add test for sse server TODO: Add tests for the other protocols ## Motivation and Context ## How Has This Been Tested? ## Breaking Changes ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [x] Tests ## 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 ## Additional context --- samples/kotlin-mcp-server/build.gradle.kts | 4 + .../gradle/libs.versions.toml | 3 +- .../sample/server/server.kt | 8 +- .../test/kotlin/SseServerIntegrationTest.kt | 83 +++++++++++++++++++ 4 files changed, 93 insertions(+), 5 deletions(-) create mode 100644 samples/kotlin-mcp-server/src/test/kotlin/SseServerIntegrationTest.kt diff --git a/samples/kotlin-mcp-server/build.gradle.kts b/samples/kotlin-mcp-server/build.gradle.kts index 48789db..11cfbc1 100644 --- a/samples/kotlin-mcp-server/build.gradle.kts +++ b/samples/kotlin-mcp-server/build.gradle.kts @@ -16,6 +16,10 @@ dependencies { implementation(libs.mcp.kotlin.server) implementation(libs.ktor.server.cio) implementation(libs.slf4j.simple) + + testImplementation(libs.mcp.kotlin.client) + testImplementation(libs.ktor.client.cio) + testImplementation(kotlin("test")) } tasks.test { diff --git a/samples/kotlin-mcp-server/gradle/libs.versions.toml b/samples/kotlin-mcp-server/gradle/libs.versions.toml index c4c437a..016241d 100644 --- a/samples/kotlin-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-server/gradle/libs.versions.toml @@ -3,12 +3,13 @@ kotlin = "2.2.21" ktor = "3.2.3" mcp-kotlin = "0.7.4" slf4j = "2.0.17" -serialization = "1.9.0" [libraries] ktor-bom = { group = "io.ktor", name = "ktor-bom", version.ref = "ktor" } ktor-server-cio = { group = "io.ktor", name = "ktor-server-cio" } +ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio" } mcp-kotlin-server = { group = "io.modelcontextprotocol", name = "kotlin-sdk-server", version.ref = "mcp-kotlin" } +mcp-kotlin-client = { group = "io.modelcontextprotocol", name = "kotlin-sdk-client", version.ref = "mcp-kotlin" } slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" } [plugins] diff --git a/samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/server.kt b/samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/server.kt index 45a7005..4cca92c 100644 --- a/samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/server.kt +++ b/samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/server.kt @@ -100,7 +100,7 @@ fun configureServer(): Server { return server } -fun runSseMcpServerWithPlainConfiguration(port: Int) { +fun runSseMcpServerWithPlainConfiguration(port: Int, wait: Boolean = true) { val serverSessions = ConcurrentMap() println("Starting SSE server on port $port") println("Use inspector to connect to http://localhost:$port/sse") @@ -136,7 +136,7 @@ fun runSseMcpServerWithPlainConfiguration(port: Int) { transport.handlePostMessage(call) } } - }.start(wait = true) + }.start(wait = wait) } /** @@ -147,7 +147,7 @@ fun runSseMcpServerWithPlainConfiguration(port: Int) { * * @param port The port number on which the SSE MCP server will listen for client connections. */ -fun runSseMcpServerUsingKtorPlugin(port: Int) { +fun runSseMcpServerUsingKtorPlugin(port: Int, wait: Boolean = true) { println("Starting SSE server on port $port") println("Use inspector to connect to http://localhost:$port/sse") @@ -155,7 +155,7 @@ fun runSseMcpServerUsingKtorPlugin(port: Int) { mcp { return@mcp configureServer() } - }.start(wait = true) + }.start(wait = wait) } /** diff --git a/samples/kotlin-mcp-server/src/test/kotlin/SseServerIntegrationTest.kt b/samples/kotlin-mcp-server/src/test/kotlin/SseServerIntegrationTest.kt new file mode 100644 index 0000000..2c0532c --- /dev/null +++ b/samples/kotlin-mcp-server/src/test/kotlin/SseServerIntegrationTest.kt @@ -0,0 +1,83 @@ +import io.ktor.client.HttpClient +import io.ktor.client.engine.cio.CIO +import io.ktor.client.plugins.sse.SSE +import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject +import io.modelcontextprotocol.kotlin.sdk.Implementation +import io.modelcontextprotocol.kotlin.sdk.TextContent +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.client.mcpSseTransport +import io.modelcontextprotocol.sample.server.runSseMcpServerUsingKtorPlugin +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.TestInstance +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertIs + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class SseServerIntegrationTest { + + companion object { + private const val PORT = 3002 + } + + private lateinit var client: Client + + private fun initClient(port: Int) { + client = Client( + Implementation(name = "test-client", version = "0.1.0"), + ) + + val httpClient = HttpClient(CIO) { + install(SSE) + } + + // Create a transport wrapper that captures the session ID and received messages + val transport = httpClient.mcpSseTransport { + url { + this.host = "127.0.0.1" + this.port = port + } + } + runBlocking { + client.connect(transport) + } + } + + @BeforeAll + fun setUp() { + runSseMcpServerUsingKtorPlugin(PORT, wait = false) + initClient(PORT) + } + + @Test + fun `should get tools`(): Unit = runBlocking { + val tools = client.listTools().tools + + assertEquals(expected = listOf("kotlin-sdk-tool"), actual = tools.map { it.name }) + } + + @Test + fun `should get prompts`(): Unit = runBlocking { + val prompts = client.listPrompts().prompts + + assertEquals(expected = listOf("Kotlin Developer"), actual = prompts.map { it.name }) + } + + @Test + fun `should get resources`(): Unit = runBlocking { + val resources = client.listResources().resources + + assertEquals(expected = listOf("Web Search"), actual = resources.map { it.name }) + } + + @Test + fun `should call tool`(): Unit = runBlocking { + val toolResult = client.callTool("kotlin-sdk-tool", EmptyJsonObject) + val content = toolResult?.content?.single() + assertIs(content, "Tool result should be a text content") + + assertEquals(expected = "Hello, world!", actual = content.text) + assertEquals(expected = "text", actual = content.type) + } +} From e4bd866fddbac7e5e7dc03590ac467ac278f36ea Mon Sep 17 00:00:00 2001 From: Jeff Clyne Date: Tue, 4 Nov 2025 11:39:22 -0600 Subject: [PATCH 048/449] Adds optional _meta field to Tool (#339) ## Motivation and Context The recently released openAI [Apps SDK ](https://developers.openai.com/apps-sdk/reference/#_meta-fields-the-client-provides)adds a '__meta_' field on the Tool Descriptor. This would be in addition to it being available on Messages. It remains optional and defaults to an empty json object, following existing conventions. ## How Has This Been Tested? Unit tests for serialization added ## Breaking Changes N/A ## 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 - [X] I have added appropriate error handling - [X] I have added or updated documentation as needed ## Additional context https://developers.openai.com/apps-sdk https://developers.openai.com/apps-sdk/reference/#_meta-fields-the-client-provides --------- Co-authored-by: Pavel Gorgulov --- .../sdk/client/StreamableHttpClientTest.kt | 3 ++- kotlin-sdk-core/api/kotlin-sdk-core.api | 11 +++++++---- .../modelcontextprotocol/kotlin/sdk/types.kt | 7 ++++++- .../kotlin/sdk/RequestSerializerTest.kt | 3 ++- .../kotlin/sdk/ToolSerializationTest.kt | 18 ++++++++++++++++++ kotlin-sdk-server/api/kotlin-sdk-server.api | 4 ++-- .../kotlin/sdk/server/Server.kt | 14 +++++++++++++- 7 files changed, 50 insertions(+), 10 deletions(-) diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt index 1e5ca7e..010b79a 100644 --- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt +++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt @@ -113,7 +113,8 @@ internal class StreamableHttpClientTest : AbstractStreamableHttpClientTest() { } }, "required": ["temperature"] - } + }, + "_meta": {} } ] } diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index d4d4b9d..4f230ab 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -2889,17 +2889,19 @@ public final class io/modelcontextprotocol/kotlin/sdk/TextResourceContents$Compa public final fun serializer ()Lkotlinx/serialization/KSerializer; } -public final class io/modelcontextprotocol/kotlin/sdk/Tool { +public final class io/modelcontextprotocol/kotlin/sdk/Tool : io/modelcontextprotocol/kotlin/sdk/WithMeta { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/Tool$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;)V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Ljava/lang/String; public final fun component4 ()Lio/modelcontextprotocol/kotlin/sdk/Tool$Input; public final fun component5 ()Lio/modelcontextprotocol/kotlin/sdk/Tool$Output; public final fun component6 ()Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;)Lio/modelcontextprotocol/kotlin/sdk/Tool; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/Tool;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/Tool; + public final fun component7 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/Tool; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/Tool;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/Tool; public fun equals (Ljava/lang/Object;)Z public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations; public final fun getDescription ()Ljava/lang/String; @@ -2907,6 +2909,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/Tool { public final fun getName ()Ljava/lang/String; public final fun getOutputSchema ()Lio/modelcontextprotocol/kotlin/sdk/Tool$Output; public final fun getTitle ()Ljava/lang/String; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt index 927967a..60adcca 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt @@ -1242,7 +1242,12 @@ public data class Tool( * Optional additional tool information. */ val annotations: ToolAnnotations?, -) { + + /** + * Optional metadata for the tool. + */ + override val _meta: JsonObject = EmptyJsonObject, +) : WithMeta { @Serializable public data class Input(val properties: JsonObject = EmptyJsonObject, val required: List? = null) { @OptIn(ExperimentalSerializationApi::class) diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/RequestSerializerTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/RequestSerializerTest.kt index 14f6779..96a74dc 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/RequestSerializerTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/RequestSerializerTest.kt @@ -75,7 +75,8 @@ class RequestSerializerTest { "inputSchema": { "type": "object", "properties": {} - } + }, + "_meta": {} } ] }""" diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/ToolSerializationTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/ToolSerializationTest.kt index 0664351..969a1f3 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/ToolSerializationTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/ToolSerializationTest.kt @@ -3,6 +3,7 @@ package io.modelcontextprotocol.kotlin.sdk import io.kotest.assertions.json.shouldEqualJson import io.modelcontextprotocol.kotlin.sdk.shared.McpJson import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.buildJsonObject import kotlin.test.Test @@ -44,6 +45,9 @@ class ToolSerializationTest { } }, "required": ["temperature", "conditions", "humidity"] + }, + "_meta": { + "_for_test_only": true } } """.trimIndent() @@ -91,6 +95,9 @@ class ToolSerializationTest { }, required = listOf("temperature", "conditions", "humidity"), ), + _meta = buildJsonObject { + put("_for_test_only", JsonPrimitive(true)) + }, ) //region Serialize @@ -411,6 +418,7 @@ class ToolSerializationTest { name: String = "get_weather", title: String? = null, outputSchema: String? = null, + @Suppress("LocalVariableName") _meta: String? = null, ): String { val stringBuilder = StringBuilder() @@ -453,6 +461,14 @@ class ToolSerializationTest { ) } + stringBuilder + .appendLine(",") + .append( + """ + "_meta": ${_meta ?: "{}"} + """.trimIndent(), + ) + stringBuilder .appendLine() .appendLine("}") @@ -464,6 +480,7 @@ class ToolSerializationTest { name: String = "get_weather", title: String? = null, outputSchema: Tool.Output? = null, + @Suppress("LocalVariableName") _meta: JsonObject? = null, ): Tool = Tool( name = name, title = title, @@ -482,6 +499,7 @@ class ToolSerializationTest { required = listOf("location"), ), outputSchema = outputSchema, + _meta = _meta ?: EmptyJsonObject, ) //endregion Private Methods diff --git a/kotlin-sdk-server/api/kotlin-sdk-server.api b/kotlin-sdk-server/api/kotlin-sdk-server.api index 763dc1b..f859446 100644 --- a/kotlin-sdk-server/api/kotlin-sdk-server.api +++ b/kotlin-sdk-server/api/kotlin-sdk-server.api @@ -60,8 +60,8 @@ public class io/modelcontextprotocol/kotlin/sdk/server/Server { public static synthetic fun addResource$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V public final fun addResources (Ljava/util/List;)V public final fun addTool (Lio/modelcontextprotocol/kotlin/sdk/Tool;Lkotlin/jvm/functions/Function2;)V - public final fun addTool (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;Lkotlin/jvm/functions/Function2;)V - public static synthetic fun addTool$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V + public final fun addTool (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/functions/Function2;)V + public static synthetic fun addTool$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V public final fun addTools (Ljava/util/List;)V public final fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun connect (Lio/modelcontextprotocol/kotlin/sdk/shared/Transport;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt index 6a3ee8c..99f7aa8 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt @@ -3,6 +3,7 @@ package io.modelcontextprotocol.kotlin.sdk.server import io.github.oshai.kotlinlogging.KotlinLogging import io.modelcontextprotocol.kotlin.sdk.CallToolRequest import io.modelcontextprotocol.kotlin.sdk.CallToolResult +import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject import io.modelcontextprotocol.kotlin.sdk.GetPromptRequest import io.modelcontextprotocol.kotlin.sdk.GetPromptResult import io.modelcontextprotocol.kotlin.sdk.Implementation @@ -30,6 +31,7 @@ import kotlinx.atomicfu.atomic import kotlinx.atomicfu.update import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.CancellationException +import kotlinx.serialization.json.JsonObject private val logger = KotlinLogging.logger {} @@ -243,6 +245,7 @@ public open class Server( * @param inputSchema The expected input schema for the tool. * @param outputSchema The optional expected output schema for the tool. * @param toolAnnotations Optional additional tool information. + * @param _meta Optional metadata as a [JsonObject]. * @param handler A suspend function that handles executing the tool when called by the client. * @throws IllegalStateException If the server does not support tools. */ @@ -254,9 +257,18 @@ public open class Server( title: String? = null, outputSchema: Tool.Output? = null, toolAnnotations: ToolAnnotations? = null, + @Suppress("LocalVariableName") _meta: JsonObject? = null, handler: suspend (CallToolRequest) -> CallToolResult, ) { - val tool = Tool(name, title, description, inputSchema, outputSchema, toolAnnotations) + val tool = Tool( + name = name, + title = title, + description = description, + inputSchema = inputSchema, + outputSchema = outputSchema, + annotations = toolAnnotations, + _meta = _meta ?: EmptyJsonObject, + ) addTool(tool, handler) } From dc4386f0f16c0fdb7dc9f0f78fa130408c925615 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Thu, 6 Nov 2025 18:18:04 +0200 Subject: [PATCH 049/449] Update Kotlin-MCP-Server with STDIO support, logging & CORS (#371) Update Kotlin-MCP-Server with STDIO support, logging & CORS ### Summary - Added support for STDIO transport mode (default behavior). - Configured CORS and migrated SSE server to Ktor's Netty engine. - Redirected logs to `./build/stdout.log` and updated log levels. - New `TestEnvironment` setup for integration testing. - Enhanced documentation with examples for server execution and MCP Inspector usage. - Introduced shadow JAR plugin for building runnable artifacts. ### Motivation and Context Improves server versatility and developer experience by enabling STDIO and refining setup. ## How Has This Been Tested? Unit tests, tested locally (see README.md in sample) **NB! sse plain configuration is not working at the moment** ## Breaking Changes No ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update ## Checklist - [ ] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [ ] My code follows the repository's style guidelines - [ ] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed ## Additional context --- samples/kotlin-mcp-server/README.md | 50 +++++++++++++------ samples/kotlin-mcp-server/build.gradle.kts | 8 +++ .../gradle/libs.versions.toml | 7 ++- .../mcp-inspector-config.json | 25 ++++++++++ .../sample/server/main.kt | 7 ++- .../sample/server/server.kt | 39 ++++++++++++--- .../main/resources/simplelogger.properties | 6 ++- .../test/kotlin/SseServerIntegrationTest.kt | 41 +-------------- .../src/test/kotlin/TestEnvironment.kt | 50 +++++++++++++++++++ .../test/resources/simplelogger.properties | 8 +++ 10 files changed, 172 insertions(+), 69 deletions(-) create mode 100644 samples/kotlin-mcp-server/mcp-inspector-config.json create mode 100644 samples/kotlin-mcp-server/src/test/kotlin/TestEnvironment.kt create mode 100644 samples/kotlin-mcp-server/src/test/resources/simplelogger.properties diff --git a/samples/kotlin-mcp-server/README.md b/samples/kotlin-mcp-server/README.md index 13a2976..fa56b66 100644 --- a/samples/kotlin-mcp-server/README.md +++ b/samples/kotlin-mcp-server/README.md @@ -18,40 +18,58 @@ configurations and transport methods. ### Running the Server -The server defaults to SSE mode with Ktor plugin on port 3001. You can customize the behavior using command-line arguments. +The server defaults [STDIO transport](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#stdio). -#### Default (SSE with Ktor plugin): +You can customize the behavior using command-line arguments. +Logs are printed to [./build/stdout.log](./build/stdout.log) + +#### Standard I/O mode (STDIO): ```bash -./gradlew run +./gradlew clean build ``` +Use the [MCP inspector](https://modelcontextprotocol.io/docs/tools/inspector) +to connect to MCP via STDIO (Click the "▶️ Connect" button): -#### Standard I/O mode: - -```bash -./gradlew run --args="--stdio" +```shell +npx @modelcontextprotocol/inspector --config mcp-inspector-config.json --server stdio-server ``` #### SSE with plain configuration: +**NB!: 🐞 This configuration may not work ATM** + ```bash ./gradlew run --args="--sse-server 3001" ``` +or +```shell +./gradlew clean build +java -jar ./build/libs/kotlin-mcp-server-0.1.0-all.jar --sse-server 3001 +``` -#### SSE with Ktor plugin (custom port): +Use the [MCP inspector](https://modelcontextprotocol.io/docs/tools/inspector) +to connect to `http://localhost:3002/` via SSE Transport (Click the "▶️ Connect" button): +```shell +npx @modelcontextprotocol/inspector --config mcp-inspector-config.json --server sse-server +``` + +#### SSE with Ktor plugin: ```bash ./gradlew run --args="--sse-server-ktor 3002" ``` +or +```shell +./gradlew clean build +java -jar ./build/libs/kotlin-mcp-server-0.1.0-all.jar --sse-server-ktor 3002 +``` -### Connecting to the Server - -For SSE servers: -1. Start the server -2. Use the [MCP inspector](https://modelcontextprotocol.io/docs/tools/inspector) to connect to `http://localhost:/sse` - -For STDIO servers: -- Connect using an MCP client that supports STDIO transport +Use the [MCP inspector](https://modelcontextprotocol.io/docs/tools/inspector) +to connect to `http://localhost:3002/` via SSE transport (Click the "▶️ Connect" button): +```shell +npx @modelcontextprotocol/inspector --config mcp-inspector-config.json --server sse-ktor-server +``` ## Server Capabilities diff --git a/samples/kotlin-mcp-server/build.gradle.kts b/samples/kotlin-mcp-server/build.gradle.kts index 11cfbc1..90bb9c9 100644 --- a/samples/kotlin-mcp-server/build.gradle.kts +++ b/samples/kotlin-mcp-server/build.gradle.kts @@ -1,6 +1,7 @@ plugins { alias(libs.plugins.kotlin.jvm) alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.shadow) application } @@ -15,6 +16,7 @@ dependencies { implementation(dependencies.platform(libs.ktor.bom)) implementation(libs.mcp.kotlin.server) implementation(libs.ktor.server.cio) + implementation(libs.ktor.server.cors) implementation(libs.slf4j.simple) testImplementation(libs.mcp.kotlin.client) @@ -28,4 +30,10 @@ tasks.test { kotlin { jvmToolchain(17) + compilerOptions { + javaParameters = true + freeCompilerArgs.addAll( + "-Xdebug", + ) + } } diff --git a/samples/kotlin-mcp-server/gradle/libs.versions.toml b/samples/kotlin-mcp-server/gradle/libs.versions.toml index 016241d..0f6ab78 100644 --- a/samples/kotlin-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-server/gradle/libs.versions.toml @@ -3,15 +3,18 @@ kotlin = "2.2.21" ktor = "3.2.3" mcp-kotlin = "0.7.4" slf4j = "2.0.17" +shadow = "9.2.2" [libraries] ktor-bom = { group = "io.ktor", name = "ktor-bom", version.ref = "ktor" } -ktor-server-cio = { group = "io.ktor", name = "ktor-server-cio" } ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio" } -mcp-kotlin-server = { group = "io.modelcontextprotocol", name = "kotlin-sdk-server", version.ref = "mcp-kotlin" } +ktor-server-cio = { group = "io.ktor", name = "ktor-server-cio" } +ktor-server-cors = { group = "io.ktor", name = "ktor-server-cors" } mcp-kotlin-client = { group = "io.modelcontextprotocol", name = "kotlin-sdk-client", version.ref = "mcp-kotlin" } +mcp-kotlin-server = { group = "io.modelcontextprotocol", name = "kotlin-sdk-server", version.ref = "mcp-kotlin" } slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" } [plugins] kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } +shadow = { id = "com.gradleup.shadow", version.ref = "shadow" } diff --git a/samples/kotlin-mcp-server/mcp-inspector-config.json b/samples/kotlin-mcp-server/mcp-inspector-config.json new file mode 100644 index 0000000..e23ef69 --- /dev/null +++ b/samples/kotlin-mcp-server/mcp-inspector-config.json @@ -0,0 +1,25 @@ +{ + "mcpServers": { + "stdio-server": { + "command": "java", + "args": [ + "-Dorg.slf4j.simpleLogger.defaultLogLevel=off", + "-jar", + "./build/libs/kotlin-mcp-server-0.1.0-all.jar" + ], + "env": { + }, + "note": "For SSE connections, add this URL directly in your MCP Client" + }, + "sse-server": { + "type": "sse", + "url": "http://127.0.0.1:3001/sse", + "note": "SSE with plain configuration" + }, + "sse-ktor-server": { + "type": "sse", + "url": "http://127.0.0.1:3002/", + "note": "SSE with Ktor plugin" + } + } +} diff --git a/samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/main.kt b/samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/main.kt index d01def5..ebe7c0d 100644 --- a/samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/main.kt +++ b/samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/main.kt @@ -10,13 +10,16 @@ import kotlinx.coroutines.runBlocking * - "--sse-server-ktor ": Runs an SSE MCP server using Ktor plugin (default if no argument is provided). * - "--sse-server ": Runs an SSE MCP server with a plain configuration. */ -fun main(args: Array): Unit = runBlocking { - val command = args.firstOrNull() ?: "--sse-server-ktor" +fun main(vararg args: String): Unit = runBlocking { + val command = args.firstOrNull() ?: "--stdio" val port = args.getOrNull(1)?.toIntOrNull() ?: 3001 when (command) { "--stdio" -> runMcpServerUsingStdio() + "--sse-server-ktor" -> runSseMcpServerUsingKtorPlugin(port) + "--sse-server" -> runSseMcpServerWithPlainConfiguration(port) + else -> { error("Unknown command: $command") } diff --git a/samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/server.kt b/samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/server.kt index 4cca92c..fc82c5d 100644 --- a/samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/server.kt +++ b/samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/server.kt @@ -1,9 +1,13 @@ package io.modelcontextprotocol.sample.server +import io.ktor.http.HttpMethod import io.ktor.http.HttpStatusCode +import io.ktor.server.application.Application import io.ktor.server.application.install import io.ktor.server.cio.CIO +import io.ktor.server.engine.EmbeddedServer import io.ktor.server.engine.embeddedServer +import io.ktor.server.plugins.cors.routing.CORS import io.ktor.server.response.respond import io.ktor.server.routing.post import io.ktor.server.routing.routing @@ -101,13 +105,13 @@ fun configureServer(): Server { } fun runSseMcpServerWithPlainConfiguration(port: Int, wait: Boolean = true) { + printBanner(port = port, path = "/sse") val serverSessions = ConcurrentMap() - println("Starting SSE server on port $port") - println("Use inspector to connect to http://localhost:$port/sse") val server = configureServer() embeddedServer(CIO, host = "127.0.0.1", port = port) { + installCors() install(SSE) routing { sse("/sse") { @@ -147,15 +151,36 @@ fun runSseMcpServerWithPlainConfiguration(port: Int, wait: Boolean = true) { * * @param port The port number on which the SSE MCP server will listen for client connections. */ -fun runSseMcpServerUsingKtorPlugin(port: Int, wait: Boolean = true) { - println("Starting SSE server on port $port") - println("Use inspector to connect to http://localhost:$port/sse") +fun runSseMcpServerUsingKtorPlugin(port: Int, wait: Boolean = true): EmbeddedServer<*, *> { + printBanner(port) - embeddedServer(CIO, host = "127.0.0.1", port = port) { + val server = embeddedServer(CIO, host = "127.0.0.1", port = port) { + installCors() mcp { return@mcp configureServer() } }.start(wait = wait) + return server +} + +private fun printBanner(port: Int, path: String = "") { + if (port == 0) { + println("🎬 Starting SSE server on random port") + } else { + println("🎬 Starting SSE server on ${if (port > 0) "port $port" else "random port"}") + println("🔍 Use MCP inspector to connect to http://localhost:$port$path") + } +} + +private fun Application.installCors() { + install(CORS) { + allowMethod(HttpMethod.Options) + allowMethod(HttpMethod.Get) + allowMethod(HttpMethod.Post) + allowMethod(HttpMethod.Delete) + allowNonSimpleContentTypes = true + anyHost() // @TODO: Don't do this in production if possible. Try to limit it. + } } /** @@ -168,7 +193,7 @@ fun runMcpServerUsingStdio() { val server = configureServer() val transport = StdioServerTransport( inputStream = System.`in`.asSource().buffered(), - outputStream = System.out.asSink().buffered() + outputStream = System.out.asSink().buffered(), ) runBlocking { diff --git a/samples/kotlin-mcp-server/src/main/resources/simplelogger.properties b/samples/kotlin-mcp-server/src/main/resources/simplelogger.properties index 2012bec..ba9c7d8 100644 --- a/samples/kotlin-mcp-server/src/main/resources/simplelogger.properties +++ b/samples/kotlin-mcp-server/src/main/resources/simplelogger.properties @@ -3,6 +3,8 @@ org.slf4j.simpleLogger.defaultLogLevel=INFO org.slf4j.simpleLogger.showThreadName=true org.slf4j.simpleLogger.showDateTime=false +org.slf4j.simpleLogger.logFile=./build/stdout.log + # Log level for specific packages or classes -org.slf4j.simpleLogger.log.io.modelcontextprotocol=TRACE -org.slf4j.simpleLogger.log.io.ktor=TRACE +org.slf4j.simpleLogger.log.io.modelcontextprotocol=DEBUG +org.slf4j.simpleLogger.log.io.ktor=DEBUG diff --git a/samples/kotlin-mcp-server/src/test/kotlin/SseServerIntegrationTest.kt b/samples/kotlin-mcp-server/src/test/kotlin/SseServerIntegrationTest.kt index 2c0532c..8399173 100644 --- a/samples/kotlin-mcp-server/src/test/kotlin/SseServerIntegrationTest.kt +++ b/samples/kotlin-mcp-server/src/test/kotlin/SseServerIntegrationTest.kt @@ -1,54 +1,15 @@ -import io.ktor.client.HttpClient -import io.ktor.client.engine.cio.CIO -import io.ktor.client.plugins.sse.SSE import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject -import io.modelcontextprotocol.kotlin.sdk.Implementation import io.modelcontextprotocol.kotlin.sdk.TextContent import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.client.mcpSseTransport -import io.modelcontextprotocol.sample.server.runSseMcpServerUsingKtorPlugin import kotlinx.coroutines.runBlocking -import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.TestInstance import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs -@TestInstance(TestInstance.Lifecycle.PER_CLASS) class SseServerIntegrationTest { - companion object { - private const val PORT = 3002 - } - - private lateinit var client: Client - - private fun initClient(port: Int) { - client = Client( - Implementation(name = "test-client", version = "0.1.0"), - ) - - val httpClient = HttpClient(CIO) { - install(SSE) - } - - // Create a transport wrapper that captures the session ID and received messages - val transport = httpClient.mcpSseTransport { - url { - this.host = "127.0.0.1" - this.port = port - } - } - runBlocking { - client.connect(transport) - } - } - - @BeforeAll - fun setUp() { - runSseMcpServerUsingKtorPlugin(PORT, wait = false) - initClient(PORT) - } + private val client: Client = TestEnvironment.client @Test fun `should get tools`(): Unit = runBlocking { diff --git a/samples/kotlin-mcp-server/src/test/kotlin/TestEnvironment.kt b/samples/kotlin-mcp-server/src/test/kotlin/TestEnvironment.kt new file mode 100644 index 0000000..6d457dd --- /dev/null +++ b/samples/kotlin-mcp-server/src/test/kotlin/TestEnvironment.kt @@ -0,0 +1,50 @@ +import io.ktor.client.HttpClient +import io.ktor.client.engine.cio.CIO +import io.ktor.client.plugins.sse.SSE +import io.modelcontextprotocol.kotlin.sdk.Implementation +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.client.mcpSseTransport +import io.modelcontextprotocol.sample.server.runSseMcpServerUsingKtorPlugin +import kotlinx.coroutines.runBlocking +import java.util.concurrent.TimeUnit + +object TestEnvironment { + + val server = runSseMcpServerUsingKtorPlugin(0, wait = false) + val client: Client + + init { + client = runBlocking { + val port = server.engine.resolvedConnectors().single().port + initClient(port) + } + + Runtime.getRuntime().addShutdownHook( + Thread { + println("🏁 Shutting down server") + server.stop(500, 700, TimeUnit.MILLISECONDS) + println("☑️ Shutdown complete") + }, + ) + } + + private suspend fun initClient(port: Int): Client { + val client = Client( + Implementation(name = "test-client", version = "0.1.0"), + ) + + val httpClient = HttpClient(CIO) { + install(SSE) + } + + // Create a transport wrapper that captures the session ID and received messages + val transport = httpClient.mcpSseTransport { + url { + this.host = "127.0.0.1" + this.port = port + } + } + client.connect(transport) + return client + } +} diff --git a/samples/kotlin-mcp-server/src/test/resources/simplelogger.properties b/samples/kotlin-mcp-server/src/test/resources/simplelogger.properties new file mode 100644 index 0000000..2012bec --- /dev/null +++ b/samples/kotlin-mcp-server/src/test/resources/simplelogger.properties @@ -0,0 +1,8 @@ +# Level of logging for the ROOT logger: ERROR, WARN, INFO, DEBUG, TRACE (default is INFO) +org.slf4j.simpleLogger.defaultLogLevel=INFO +org.slf4j.simpleLogger.showThreadName=true +org.slf4j.simpleLogger.showDateTime=false + +# Log level for specific packages or classes +org.slf4j.simpleLogger.log.io.modelcontextprotocol=TRACE +org.slf4j.simpleLogger.log.io.ktor=TRACE From 7681de1df8888eb59f9ac491204c7eb1193069c9 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Thu, 6 Nov 2025 19:19:35 +0200 Subject: [PATCH 050/449] Release 0.7.5 (#373) --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 0db4499..85f8d8e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,4 +14,4 @@ kotlin.mpp.enableCInteropCommonization=true kotlin.native.ignoreDisabledTargets=true group=io.modelcontextprotocol -version=0.7.4 +version=0.7.5 From 4b88c7cb8297a31841a8ffb2c0c5d20e44a4ed35 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Thu, 6 Nov 2025 21:58:19 +0200 Subject: [PATCH 051/449] Bump version to 0.7.6-SNAPSHOT and update version to 0.7.5 in samples (#374) --- gradle.properties | 2 +- samples/kotlin-mcp-client/gradle/libs.versions.toml | 2 +- samples/kotlin-mcp-server/gradle/libs.versions.toml | 2 +- samples/weather-stdio-server/gradle/libs.versions.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gradle.properties b/gradle.properties index 85f8d8e..d401ea2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,4 +14,4 @@ kotlin.mpp.enableCInteropCommonization=true kotlin.native.ignoreDisabledTargets=true group=io.modelcontextprotocol -version=0.7.5 +version=0.7.6-SNAPSHOT diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index 99cde11..988d14d 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -2,7 +2,7 @@ anthropic = "2.9.0" kotlin = "2.2.21" ktor = "3.2.3" -mcp-kotlin = "0.7.4" +mcp-kotlin = "0.7.5" shadow = "9.2.2" slf4j = "2.0.17" diff --git a/samples/kotlin-mcp-server/gradle/libs.versions.toml b/samples/kotlin-mcp-server/gradle/libs.versions.toml index 0f6ab78..56ed570 100644 --- a/samples/kotlin-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-server/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] kotlin = "2.2.21" ktor = "3.2.3" -mcp-kotlin = "0.7.4" +mcp-kotlin = "0.7.5" slf4j = "2.0.17" shadow = "9.2.2" diff --git a/samples/weather-stdio-server/gradle/libs.versions.toml b/samples/weather-stdio-server/gradle/libs.versions.toml index 3ac5426..1ac03e2 100644 --- a/samples/weather-stdio-server/gradle/libs.versions.toml +++ b/samples/weather-stdio-server/gradle/libs.versions.toml @@ -4,7 +4,7 @@ coroutines = "1.10.2" kotlin = "2.2.21" ktor = "3.2.3" logging = "7.0.13" -mcp-kotlin = "0.7.4" +mcp-kotlin = "0.7.5" shadow = "9.2.2" slf4j = "2.0.17" From 07f661a94cb54339507cbe7564f1bcc60fe19e35 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Fri, 7 Nov 2025 12:04:56 +0200 Subject: [PATCH 052/449] #355 Handle coroutine cancellation in Server Transports (#375) --- .../kotlin/sdk/server/KtorServer.kt | 3 +++ .../kotlin/sdk/server/SSEServerTransport.kt | 13 +++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt index 06e6528..8ad4c2f 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt @@ -19,6 +19,7 @@ import kotlinx.atomicfu.atomic import kotlinx.atomicfu.update import kotlinx.collections.immutable.PersistentMap import kotlinx.collections.immutable.toPersistentMap +import kotlinx.coroutines.awaitCancellation private val logger = KotlinLogging.logger {} @@ -91,6 +92,8 @@ internal suspend fun ServerSSESession.mcpSseEndpoint( server.createSession(transport) logger.debug { "Server connected to transport for sessionId: ${transport.sessionId}" } + + awaitCancellation() } internal fun ServerSSESession.mcpSseTransport( diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt index 8b5c6be..64e32fe 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt @@ -11,9 +11,11 @@ import io.ktor.server.sse.ServerSSESession import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport import io.modelcontextprotocol.kotlin.sdk.shared.McpJson +import kotlinx.coroutines.InternalCoroutinesApi import kotlinx.coroutines.job import kotlin.concurrent.atomics.AtomicBoolean import kotlin.concurrent.atomics.ExperimentalAtomicApi +import kotlin.coroutines.cancellation.CancellationException import kotlin.uuid.ExperimentalUuidApi import kotlin.uuid.Uuid @@ -53,10 +55,13 @@ public class SseServerTransport(private val endpoint: String, private val sessio data = "${endpoint.encodeURLPath()}?$SESSION_ID_PARAM=$sessionId", ) - try { - session.coroutineContext.job.join() - } finally { - _onClose.invoke() + @OptIn(InternalCoroutinesApi::class) + session.coroutineContext.job.invokeOnCompletion { + if (it != null && it !is CancellationException) { + _onError.invoke(it) + } else { + _onClose.invoke() + } } } From 23ce45027962d47eae4f2f3e27aeed66a3fd7f7e Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Fri, 7 Nov 2025 17:07:20 +0200 Subject: [PATCH 053/449] Update Gradle to version 9.2.0 across all projects (#377) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Upgrade Gradle ## Motivation and Context KTLO (🪔 To keep the lights on) ## How Has This Been Tested? ## Breaking Changes ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [x] Build tools 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 ## Additional context --- gradle/wrapper/gradle-wrapper.jar | Bin 43764 -> 45633 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 5 +---- gradlew.bat | 3 +-- .../gradle/wrapper/gradle-wrapper.jar | Bin 45457 -> 45633 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 45457 -> 45633 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 45457 -> 45633 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- 10 files changed, 6 insertions(+), 10 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 1b33c55baabb587c669f562ae36f953de2481846..f8e1ee3125fe0768e9a76ee977ac089eb657005e 100644 GIT binary patch delta 37442 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

WNp5~7YD4``2J+2^JLXSDAn1#GQa=Wm~d2R({ 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 delta 35599 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 ORzXA2f%7X@nSTIfr%7r6 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d4081da..bad7c24 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 23d15a9..adff685 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -212,7 +210,6 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" diff --git a/gradlew.bat b/gradlew.bat index 5eed7ee..e509b2d 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,10 @@ goto fail :execute @rem Setup the command line -set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.jar b/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.jar index 8bdaf60c75ab801e22807dde59e12a8735a34077..f8e1ee3125fe0768e9a76ee977ac089eb657005e 100644 GIT binary patch delta 36466 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 delta 36287 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 TbTtHgKIb=}#OPPBcmMwnM9LS( diff --git a/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties b/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties index 2e11132..bad7c24 100644 --- a/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties +++ b/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.jar b/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.jar index 8bdaf60c75ab801e22807dde59e12a8735a34077..f8e1ee3125fe0768e9a76ee977ac089eb657005e 100644 GIT binary patch delta 36466 zcmXVXQ)4CEwrnG|ZQHhO+qRvo*j}-1c5K_WZL@>Z-@f-{{(%~(8dWpl;8)+_uR3dZ zfZQ)egq&Z$i11kM_Up+}Q({I9MU9QZb2tR|yWGN*EITsYQsXf^5qA245#ac}VN7sh zh0J3lJ2M}?KHXivdw;lL^3<*X4-B$3ia;m37Kqtw&i zS44bg*-wcSL! z3SdD4tBX;ArwHhPVinq?S0M$-NDOHL41GA}u(Mv5lJs<>n1t_H{9Dx+iP^!|OZQjg z?r?7~D`^kTzf9h>O*8A}exIMuzXc$pU~b^ya#D9FHgvX-Q%Yzk&SMjmRXXNaWUeur zlkHbC@;ZrS`g8^HFE*ztUGuNoszWPjE*%z7Ig86rushQM?7yOw$~9=Fm8+HQUTzv? zfJk=PC+3tWDRvq{9HGU^Zu%T_j*8tp%>TiVuoXOEUm?=ke1bF@YIBp42=^no_&WYdKw$ss1@Y-se*rkpi&K6u}~U$255Np z?7GN0p@Jp>xLr#KDJ@^^k}45a;HG1THPWPxvaJ=yARI7bHIW%7`oU5K?Zzz`nW5_D zr@AaO1KtwBXLQ3AdtS+tV8RsHYw?WXA}~magC2%dki?8d@kw)G7AS~1Mi{5@cB`Re zhFOH2-A^`}!>EIWtoSO%}~`k7X@ z@Qd#Q?2$C1GC6$WnOnF6&3iLUKO?&*=wT=QJA;RTu^aiHh9T1=UUCc}c#wz@jeYns zvk<0}xrpSWOn(w+ngAey!wL-gr}+QXrU%HlghdPxfNTZ$C0<*Mf_SUTBlzydqQ-|} zB@;h5SoD)PGJxL9yM-6-`Z=S^IvL}Q@C%IJA~4ZnAO2Pxvy>hrm@LM9Z2EHYVRCYM z`s?fA0Nvm85Q7gFw~GeAV71AGFwdq2TWN;^Lw$=~GeXQBUxL3Hcq!!(7#5i2&BId7 zYls^x%g+K(yV!&l?X%!0nZYjEh^X(>p-NVRs2nf-@LG5nMYwi+AhZo}(V+}>Di&T& zQ6M}b^7cHNQ(9{jop|K{8_u%_l{JkpuOY{n6yOvUcicc0op6r22)J>!RHvMWt`W*V zUO7)t$c%B@%MUdS?H7=G8Z8DQ@60bBMWS zjK>B9@LWwT`0;-9+`>2qK`lE(5t43;N7ho7oqWH$L4in9EWoHQ03@#V=2&?;3QW|*ThDm+^W_8PLz=_P?p z7PHSz5MvfwXE@_RYBaN8kHQF*AamlbM=mnH---W3jWF;?Q)W zT?E3Ih|JH}0OS_q`et(vywK<3Y-o&4lySM=>7Bx(T$#uizd|^`c)8!He?5*a1iA-5 za!4*i@&~W#|5sz8{LcJZzkd8^1_2^s;Q&tvNr6#lWB^>XPN7WTZK@5 z8ojIux!<+(H17=O3EubL9sSy$G6!G-3*_?GXnu;+Iy)$Cs@cjafZTlN@%m>du}^ zD4bg?P7|@FjvehnIoA#=$k(Tds}4Fw9Xa||Y5=VZBqW$rV_S9XfbAjM>*n~x8DGNu zp!|Va?Cn!gFjvxP*@V@HP}^5%n3ds!GkhkY*7i8{EDhz^h#EQ{FwuZ$J4_@mOfQ^F zB9~7JyU?e* zHb(HP_@|SQp?06V-{H?Zo3c<$qi%}emH?RjGN>W1aQnQ^YUCw$a$;C6{`9yFy7s0L zQk(B_`F4w9hMC!KLrfAG=IBPJM4R#z8K?N@cB1t5-C{CsM(5sFU467ukNNOAjwRn` zh&T9H`w%qgKAcDwHj7V2Io1eisR&TJ%U%5yxf}bCnQ{n*_(k!ywLHM-^;gfUTzL_f58`f7`3{e;7jHS zS-!BrUgGH%7EF#6z0gy0ZVW53^gTlCC)hWd=68=Cb6@Sy`8(GJlt#7Y{$TFwdg6(> z$6QM`b`8n27K;!=+cQQ7qex-G{?sY8j((iIh-z%iPgQ|Q?1M;9Lb1&$V{UAh!$G66 z5&rxAU&9UP-?1tIVX$lg7y39$n0{a>Ff>4sQP@NawoGiA4g(0)Uo5B$Rwe-~2&cm; zxNh(ahHmD}%+P|?M)vC!U2D|K%X$UnthR+miKDgEx$pTq8-TpeRsP;(D&Of#IvWFJ zp`OXChn$!D&K2jc`?0?Okl8>{&|2MYpOUAw-Eh2IgmRHOS`2&uY-o@pbvm2O7HgFX zi6tq>Su;b(H3VMlg&YYV-9EA$*%`=&nCk-Ko`Dxl;^G40z{1@8{N|=lR;Qb_o*#>G z1_{T3*C9m<5vy5Ia9zwIMmezG%_8b?dDu9nIw3d>hEE+%Gyu>$zUp`vZU_Ymul204 zkXB7DGl#++Q(_08PG3}xpkU2jwiRCm_f}d~soZHQzuRu}9@nNkJ>swCbr4tb=tkr~)d!U2G(jN>q^N6a<5W0DM zNl>jM`Mhz2Nix9+)n&<)*y6mrzC;J&oINA9*NahEal~uUSq;2&LB>lpV9!q3J}4H}MDBui-h@YN0*Dm+l?*gvBH5EC58CVimf z@T8=3Vm$AhDx_gS-GbucQvR7B)3FfX1MyqA9a#dfv0RjZ5Yby^3huOp&REVJZ@bx> zAUvHg?3869Gn%-Vesdst|0$B-Uv)HpOMwSW;Ax@?eXF<||1%OtCxVJ|(fvC|pdU$& zz_jhBdM?6G-lj|BglERPKgg~dSIDV4!x8~j9OT6}dVJ}nGyBn#3;u!N%Tv;|dmMu) zv_e*Z3D}W_tWkGfbUS<1vx7Q(;a8meSjZQ~EltZ3i`*xG2?c{`>p?)U!+!Ig;s%cs zXO2RhwQE`jQ)oIubL7n5I}$hNOEd_oGP;zO()@`UEJMuGGZuSk(1Ze80JyPAf`9z8 z7-w3fN)(ivcANE4ZN`z8MSZd7` zXQkbz+t(QLa&8&ulu+&;BnelNVH4VPLghCuaZ5-s5)}%_-IF2R?wvUkj|^T)?#Noy->*3IRu&vevo)O` zxYU4M`Q!Mplrev2^k&V!oj@_J$-n(MjIve23po1(>v=jb{D`e@5uML4;n+!bpX2tJ zsr(@+^>_kg&y+pQ;yKYhx;UmzjyUv*l&>=Ze?olQ-K4sxqzB?4Hj4|Q(vtv^?N69$ zUKMsLr1+3K`SBsQbLBy9X2?TE#vqKad)V8oOI$F&!-VS}PNdQ6oDo^;b3iX4r}nl( zIWM7S3;`x^;8woQ9FKInmR!-qoNuWVy8Yj6voy~$ME7_ZJ0j8pJX+=osF71jV?11; zCwH?kqAo+DO|>I0;5;}{DWL$c3Vh4fh!OPJzokUxpKr`o^nJ8`?D>Z~5Z8Twb<_vM zL86goRFdY!@g1{=BXDX6=TW}kQTNx!rn(+5vI3gJ^EE7f)?*Vw+OC0w{L1;tew5Zy z@T?0t?Ap9~De!?acVgr6AhWfhZp?qL=~MUe)hwhEROc=7(E21fZc+n`y@;qOOA=FN z{vcH=vvk0`Iv^Y-*YfRdkr6AE2dj82|EbTNj(%rSHPzx@YK9osl>CXA+|<4P$1@t1 zeXu`a^;&C(C-HZiM5vL>XaL2Kxnq6myf17+8q~}|8@$62S$DsE6}!m#3Q>c{8(Oz= zpdtLg3zylQ@7FWWRYxRX7-#exzdJ;BrLI=u+U&k7HYjK=}gBZa1LT_&?FEsD8b%+OIO{}KDC*c zQOqyArw=~kROVy%KV2m+Z9YpQXaV%T=Jm}v$;xrQp3&a{{3shRc&+RCF1VAkc63%B zZEh||$E_|%&)I0e#aqu|(W#{!y@3|L6__2NgNJFLp4`g20b7>}18@hFS@2Fcc+S)r z$9Q$ARc;Yi?O@z<`;HKb&mIlaazR zcH2jiHxpxAdvvQd`E0w1EV*2sx1Y4@G-nn)X8jGgMbBLm6iCeMqIeSTZbH3RpujLM z!x`gcfFz!Ze>Or51$aG3TnqLJLsbs;*h_Q!^g5{XM+i7(8tMSFHq{bBGC~3=QGuf- z3;~v*ofA9FAcCI`%wx`K_T78lo#zggsxAFx&6PvAV4?sFl$fJ?5;^1>{qR7|z*e53 z95AY5h0%SQX+{~(d-}I*#CzgjIaL}>2Ms7XMQ^txg5I$Z082i*^KZmY2OZSZ`uHJI z9Ysex^a3Jl7*2UR52x1+>%t1JP6o#emKb04QH3E_8c{}j1?K(Bu*s1>Q}Oc$!?g?5 zhH0^z*uE{KAE}*35R(cIsO=rmZYzDY2O$Zsur?9(|R^k;-51>qsU8@GwA!@I<)FwtTEAaH_&4uF=D;zY^* zQ_&IB)-u&ed0bS2)Iy>TfgtfcOr~YvPhAITgLd%06I^Z|zEvwp#_JEhq&pkv@nT7L z;9B;L&k1)b=fmHbIQ<`F^}V-mhbFwAic`+M`W&FHVoUCeMHT2*zZdp%G+kIaG!TNN z4I!_gn691F7u-X-Z&Ga3vjJ;+Db$JQ0r_hm0?ZRwK;Xf_!NOqj8TEH|qAB9;wUcdh zb3dbuID{n^SbmsLJ4)2;}VMx+)t4j!u4)3k=w9oE6(%Nk+ISL47B5St22MYts**7K3IbfVNfT_zt)+{Bz zZ-aHKZ;tAcd(Y=N>nv@XXCPC<*6Zr_&K)s$4n>Gp3nQ@J6hAzV76x<<0t@k878k~r zjei=NO2sQVl+k5K?fI2ZUUQRC2|EWs*Kqd&BW2SUV@(_(wn^NxfdbkkQr{;2uq)<( zzh(HCpR1+r@<;SU3h<%IdY6H!&kt|4EKvQ93d7JCPAq>P%_7z8z>^A98BFI{k~}Uk z7N;(=k{Kf~1Vq3yj?XWI66{kB{Xvgn=W52w7r5IntP-&ex zQMqrKl`3F9c@>X{P{SIiNp0)ZuF4`As|3QGA-m&=hU_$$wPNFL5LToHKco=9{l5{YH3W0}7Fot_Y6`aEj&W$nDbqSLm30heN<=az}u~ z0s@7td{-3*3F?HJ_l3F9v7cZ&p!+t3{%&&xi{@e0)nGt1l}g@t z-s;CZmU{l-_LE9NT=VbFdVH73-LkcF{~hA^KcC0i8NQD$Ti7A#(lhZbJ3-z$RaErF=hM9 z-~2_+o};ZfaRhMI)?3TppNYLJ?dVq~4RaOqpeQyZq1xH>Jw4FWg7W_Y)F89Ko z3V_>`R)FN<*41Ct7L{r(OTSDBiQreU@NM>$ZDUwjUyVXdHJZ0-C+U0{p8==;j@rN! zvW(Z?AByK&A;a(tK$+>RwwFzA^XnIU{qCO@1;e*{hlipU)ysRs2@W0WC1#I}6=oi> z_G>(iJnHV9H3WK4+W&T(X)bvC9Qv>#gTpV|2EfRbfC}r*BJTBtTGgBl8&*sh3bi)+ zTUUTBGU8*KUv6waE3G<&WgA)jh!B`c)T`Q+V0E$kQ2jF<>~Q?r3)nn@JgqjvX%5Rp z$)T?HBJ4(my?5-u)-@a@?+FuvlkH5L*ET${E+{k1L)b^!itTmAQ`dApgAgcXC`l@r zFu?ER<%q&GituO%2?S5G29?iDgI;!6mg(qsB5g9fijElDZ%}oWB1Y~g=`{5O%zPeU zu17IU80g~rMmVJQoZ+P&tsakARwfdNHkcrFwN-PCta+`srbtp6a|c{@jL7e8eoPHF zbqUaxFCbpet{WJ2t1y|vPH7v#MQMnf0>Jo=7PV3%isZUNqt_^S855(ca+0Az7P&8e z3*PcH$MKu=MkJRT-S^3(7 zEr$89!koefY#*h9AC84V7{WqTE@1*ato02~TEBk2rgIE^CO^A?VX%u(3SSXtfjXK0 zOy2Q~>PnMD4NxEuPI=fMM<6<1m>fNj*z=DJfkPZ~lwc73+g)bA|91wAq7ej4$sI5t zjEp2TD^MieAg5Zu(^`c$ZwLpVf+I*spc1t2_fAQcl=szBlUD~L@Icu2L0ZD|{D2R^ z2)id$B~s$S|Gu-oynC4VCjVj4j}`Q{qoZLt=HOu$O7+x+TX3bEJ0SM2(7lSq(J+_D zICjz~GD-aiGW)5aLo&?%;Mz67DcxY}Ox$krZ_Oyilv&~2YJt{**Uk>Egk(g~$kq-@ zL1eHHi;!p%Zh>SO`jK`ffKA&jT=>Kd3L{8K9A3shOo%9`oh<cCXmlAJttBvb%DEYlq*hNFxDFEJmN_%PuLU8Ft&3`mSETTBB{igdMa$X6 zE<=uX{m;khOxxco2l~4hqFCljh!p6j7|!&X~B!0N^^)&?HS9iMCTy zm%e6)9w-p39obfe)Nr&wrguNSd0~siw zr`<~2bP2ZOXzo7mpnI95V(i-e*Wu4iEjMpBT~A&lpCrwQ_X_ll*fGjdom0#T1391zQ{(bnp+_& z{?wh4x*Nn|n3cgW2hxlYA3DICpqWox%_1%$6zD?QxWdo{!11+e{Qyq6%^^SC&R9mc zIHe4AOkNrs#+bYqJOApGTIoJcl^Y@zb#mX0-);|XKq3s<@~{9f$P$sK`{89PkFn?E zr!Dx?8S_xF$h2yn+Xy34q>6PjBfi#S3UPFvV*W7$`EZVIbVAHKlGZlD2C4`{_>p$b zGIUzkeZJ`;4^cA241tgo0NJCxdKAK}ZP% zBwzolw?sVS5n5D*YQe*7Q=5@5mpe;*Ywu>u|tqpGWpERNwf48lIB zL0ZOwW|}Dl84wTENc4(ItOi9Qp@2O%Z|E+e#@6BJJ|JGL=i#;G+_^Zh?XjT--JG+o z_fo=N&{J4^bA7K0k#n4NIo?81qAHb2Q*%yKeX7M}twyz}FcNMVsNGyg0)h#E+c|*dsdg?*nx5H4jwCkZ z7k2hDvLeaN95WpbGbdwGRvrLyhOVZ8*KlU!G>b^m6qC0eKulm1;Fh+^X4ODEqfQ++ zv@2k)+J*|7Ej8%r(Q#bELUhEOV6{vfbe=Og=(3~RV?2bmSsZ@y!u*}F3_aE{fFR+r z7ju9J8;A$Y4$d}42@5%6h|i)3f`DVs`5VLJJG;2%?#?dDHgIuL6AnAbpf;O9vNr7b z^zF$_J;!$7_IX>t9&pHsAPoOZ{S%YV>Ugrh;$8vI)UH^`leLXgY<5k?E$oq-ZwjAI z;?qm;b|&;wigdVH=e`N^d{cQJV*Sn#qjVNg5F!Rp!<R+~wS>p60@z zmlis}I)H~d0VCeRePliuUnv5`ThnS8X-O5l@GXh+%>wD06wo|CJJMaud4#KjO^3KL z@=78D>KZy&<`7mfV05|>JC)L=itu9HuONaeIs7S0GNuXBo(Quv=YvbK_i@bV27X`+Z%<)bsWg?-6lAR&h0+b9MiI@lQo5FK^{7pc%|WP9fH;wf`Y`6u7Oy$unp=&D z+@<1kCGBbsJJmb#K@&qOgbDh75ThH0F6{(J?74iBg^ggY-*2#4HcGaDsHd8$PAck^ zgN!S)u1JbQuI#L*++S3nv;CQXHXr`;%A|S$n;5xaoFS{!ME;C6ym8nsk)r*|6P-zs z$RCki*-f!vD6#>m>3ae}#h{ZVNn5E!yeg3W z%g-ChR%t8jXqm$jVRGI*e2>6Y*fdA$B57l$JfvN5e2J=||DML8fR1Etg)uyBBgub& zu|+wtc+9r{(ad8c6Wl^%Dl>;QBl{6>EWo0vBl^f1iz2S>$nu7)<>&twdT&eC2K(7_ z_1IznN)8Oa#~&xAM`#~3k?$3F;1d=*qM5iNA!UG=d;h%%*rTQP>&E*ESOQBkPF8f6mca0dFJyP%yvEAREN0HWN1~W&=+0a&@rcqEwSyh6{@=>K-cG*w zXs4c#^E(y>Hn6_iAri-adBvu%w5#O#g8d|Qp=KbR5oC%^^PMrlA?(9Mp96!>w2}{l z3kUaNFKzFk>VWU~O?|t79{_R}uUeK^F8KkUCN=6u` z_w(gM%gopFG(Eugo7mqH1tW*Y&xicW)Iogg(wP@K3);-7PlCZ1C9VN2ShKJ4*MhRH+Me<`ix4Akmlv&jH|j(D7W{3z1K> zt0>Zfp_%BQs4;o_&td61RnKHADq(pbe`2PFh||9ZZ=r(4Svf1$!zvTtS^N7ml;pk` zVXTOhAc6rd+#!u&km}8XYDUYiyU`6ryby$v`l`9l8=h8SV+uzZJ4r9ev@&;QpRJHd zWEge+(nl`Z?xZjh(*e|{A6B}q?WyXvk?Hr-k}dU)Ge%X95`sZ1$~6j*_6w^~8?Dw; zJaKO#L%TyH1d*PB@(p z{$)}VUHV!76u6Bs6Hrqn?N9}wE`eg#AqHn)ab4bHOBSWZODvdKZK{KEPnrmheVETg2hAyQzeF5O)=$qkeG8yPlT0)52svXin;$dobPo1p zS`Al&rRoVOV2v8$d?5s*Yb6%fSohL;`{Xn+}Gk8I!8r50Vsm&JXAzi z`WMD)^8v&@kCz}5r~yOZsllqp{^bU;99@_cc(O<8Abuda_a*I z1W5c*<3MFkx=$y&}%5Bp@45bE*w^;_cu1MzpoBUS>r z*;5}foXw9f{(d40z*GOCKe$u9J=`9<4VWM0br=ZfxC+%wq^5;VF4jvF9c(9*okz+P zw(N3{*bAaXzQBViYHqwbu1_7^R9*nY>eX&sT{|y>08YS*muXh>AKlWGuqu;A%UH}@ zMTYO`mV>gpvU{T}4*ApxLC#k+Erqz0GeDM^k^j~wB#`khj`t)DE4gHlM#vQMz33kH zsJk&x&vvE~pIN65e+_N%hJ*xp2D~LKl4=GleJ#>M7k?rf9~$eg?Z^KXr&84(suwUd zekcjxLOXYuML7Kcy4R_UFk!)}C{`N6(FUw3`_XI3;CvbPg?CXR_1N-oPcADMf;6_+ zk!yVP-e0|eOjWZt>IYc1_dA0n|1Yss!0_^<@GnhR^}jS>9x^--qLLBds%-7NAcn$= zohiY1T%7WlAQVDCf>^JTsE`F-+n&!exzLD9q0e@&g?4J_wgQP6`VsdI|7aZs9~zMV zIUQ%@MH&hjr^1|~ezNU--PU2g)9L#L?+>bDjA3KDRm1PN>~X75WNSVK$u(VtY$8b? z*%te!F~!7Iuob0#S4sg;H=#;<8o8T)PCl-Aeyb6oS&F`lv|T9*I?~_2xw%PnH2(hg zU5-B%QbAXbTQZjjwonBEvzRddLWt^I)MG32<-Dm`!#yX%047;&IW?daV<<2`Wo#Cn z)PAHtPKnlJ+_*=W@SAL9i#zdzhsW-4KbS?qZ1vl})|vm<@^lBF;Z5+%)*var!bAL! zb;VPtbEGtL!;&^TN-O6L=RN!i*DSB!K*Wmm>`XRv{&BL4s$x~S&+gLHpgZD|C3s%P z>dE_`CrL%9x^oJDhNfawQa8Fz2 z5N0PK_OIuaVMQ@OnD$-Gu;+kZNO7hRPTX?u#1aT9Bvo4Wlo)}TL7}WCrER=FI3oNn z*M#(&HFPU~mfdt2S;<~)&$#LrJb&lR^##Rqre?3Pp+T(KThovMD}M~F`dCQmFhd-E zgrfPBjqoVgn$2GszRB%9nTj%F#$4+N6?+j&?hOSpHH<94)#8LBjS!xGydghgxt=dh zxSpNuZ*$Dfa!FQG@o_n3TW4)Z%CQYm8JXF%1RHY)m^H0X&#ME+*p#$a7(@mas#K^M0VZ4buZ z443qp7a%Ku5IQ)z2O8`?%n*j`6QgE!g}Gn+6fl8nwDTjt8W&@K_s5IQMWvDBL{m5B z{_KxJfZ=#kH-0~+!BA5y>TahsExbE2nS2 z5NG#^TEk7-l+6^^y+P|z}0Tv$#*eBo!=(Af8K=9olaRm-H<||zDH7V*=5(?#uN|Xg6qXrf@ZQGDd zvt2YbS&OTL`$6#cwvmH?$Kt$F7}%gX8o)Pj-*kC0XMDb0Y`Fh0=0SLM@%=QrC(Mv) zBH@mTC1#C-BT6<8Vf(Y#7SrG(7R}8>!pqne&!+~hB&~1C$CV=uI5;Dqn&$wDR$<58 zd9YydnpJ0hROkLn>FuI3@$DzsRL~|Yu$j7uPCL^NlZU~>lix+F&5gi91Z;ciaf;G6 z5(WS_izUwwClv~8C8wo;lj%}2H7Pd9Jb+r6qo)c=j}jD509m!n7^nrbjsAGDOti^S zd1g3uNG8Ri+!>Np!ac>_e~qKevj`vOdgp0?!nxg z-DlTI${yhmuKfUXhu#o0(66Ggkt*WTZ=P@%)-n00B}#y{`Hv?d?X-otP#_6d>%gn?5_p zCciPbXh6C-mD+QgK=}v!XE!wZgXpB8;I5HK^O*Bh!`#vR`i={FRBINnHcRz=EhEU3 z?pAtRhEhcr^;*JMw@QXZ9rXHpXSOx}LNv)i*kGN^Cg?;|?$P(JL3gxg z@koGLPQiudVyi*+#Lok>Ci)yia8>lG5V5kz7fsD6+%x>${Q{ zfZgg_uD$y=sd0+Ly0V|maeS&ED?&|fk^k$%`OEs**nc45{SPEwabrO6I$11XQQzRK zkZ=NyjPel0s1hmH8g4DS=1u$GJp$FEd2Dbwo!uWJISCmEO-dWe39sZIzvUWlXcXq5 z5IW{NZ+IRiJMm{G7tnjY;0*AjVP5RFw5q=hs+^sbJyPl|(rk@~89wc196b+T_1Z49 z18)Ud|3$>nxOr$YaSZ`>p^oeJFs5w_2LLjl`$m{5W67>+Z(>XcSDYSF!emEfieu4Pmr0xGXfC|mPUq$8KO`)dJ z{(18xqR@6|?^!cbZJa8SIb!$d^(DKz5dbj|iY(TT91zJO>=p!g@fYMDQW1@SHJ*tjqyG(1NQ4K$>O-E;uI>WJ4uUPT^cJ1BO_R`MpFB+r3b*&2jg!` z$A3J&f@3=GxeYrinT=Gj3?2LXWYGmhVeyty3MOn@vmp}mJXVB9>aU6645y0BEXvME zKeMQ*KnB!uv*`;xp{sC z8kJSE+Ta&zXk>4aE)d%w-v z7GYQjGhQc!!dq~zHUlERoML$au$3obvQT}vge92!Q zN07SGw7PubZR3lBjQys_?y`S% z$VJWB-bD*~oL41+JwX|-Kxv%vFzzHtH*2-8{PHpIo_w6s6#QX@a(4!!{T{obdfs^P zYgB%#D~wQb2awacVp_MH+!IfERO)s+L|oyFG$K0JABaL)Z|=ZZKcOLO0~E7-*&`z5 z4S3v%e@QFX@Cb;8BF)D0l@_@JMWk}hSj9eExRUise*d3QszvFXAPwA=vIB@h zQ9moHR(!d zN#=_>m`{98vTm~8%mANX@36fDWSHAFHj^cKXHlCvh;nN9%TuIX`s`ambAQ_G+vGZT z%|O?R>p)*XI3G5nGCd7zJc{ttm%TMyT*mt39n4GJv^G(@H9Dw%GK=JDAd?19_~Y z4l@pi3gX8)t}~1~cs96+w^A>RN%`qNX~A#f)_m4to*u?>jCGk!0c|L$yu{$T*0ZT6D`_Vx|VF!d2w?fjm_&lq_Ql zI50BZ9ai1Uy;Z%sscapmY4{`|$mv4$kXUG#?56FzL{oFn>`PKqFs;+WUm+Ztp{@1& zOJA2AcPb>I-2|fiWAutJCQZALm|}s1mmS;&u&KYvz-ky8RmOz+Q~`;gPeR63Roq>z zG%`IE*ENy@tUC53e?5ZC)F!0*5C#%(vAm@qgBAsqI;LhH@G&V{g@I37E;$p7ly6+Z zU5LIZ%M788r~?uUB=8tB4Ue%kH{}>ZzOc3y@Nn0JA*E+R$_vhXzRYDip4}GE_w4c5 z28{*W0+n1^l@=u@#(;I#+3whFE1HG~V&7cEH~-xgG?VS2PElT=$CP>|F24OmZkiz` z(=1vb9zWq9P~wFbALKcv{VPeYB*t6NaxR|{2eWhVP@|?iZPvVqTniig^woZgP^rx; z^39-^dRZQ?o++QeoUO9IK_?KDEE&6T{1!iz%Q}7_u3u9>qQm{SUj`wj^1*)p_#p`T zKQGu>P5}C+R&IWPOJX{uZ6B~(j)tx=5}K-vf{N<;Z(t;7#K|^0P%g3ldBN}PxZC-f z_aw)A8Zgb<64&MO9rQzu8Hgb-25_y?>vB38XKh@zqnv<8`=e>ZA?)>NL)4beC^Kz4 zFp1!vwgGn~`jN6)p&`vO6?Rdp*oW~8veKaRRg8E7Q?B7shR68gj3zE_)r)WD>eb77 za8k=lD#W43hpT@Owmp!SRiM*$CZK%t=I|*zPGVysTv!L#Qf3ePo<0mLM(jI-*d~7X5gy zk5NH#Z#ZfdB;nmY!mC~R!`qlJ#qAK1=4omv<{lR40Z4AqdO>io4A=n4Pa2V7o+LgN z)1EmzB@l%1^66LZc4W(n4O_VDiSX=ZA3W|TgHU@XVKi%hKMkEjnSo}f9%ah%Qt37$qc{g4bM4GONOpDYWv~5Jg>i2=zUC9JavY-HHm4qTZ<7bEYBS}?G=lGMU1KLoyOJL@1DTTfIdZiNxWufy z=ol$8y}qPNOLpmGZT@J9TukfSCRHvHGe6nm?*V5YdRvaCNK%y^+`4?s1Fba%CJRJU zJD9yO`x#}ZLS%wGTBOGfQKphP{Z7c+v?`l2Z`-SWmmxQZ(DtbOCm!V_Ha865)dbrY z#q)nLKA2jmt_d<8Ahyu)2eW@R?g_)J*lKqamlm1>mv3fE){ncp^<=%Y zG6C0CQw7`_3$jxTxH$#$Ma3nZ)VH~Ggkr>mwm_G%l#&b!?BAgEWF=4IHIm_+V7M~W zGPY^q<*yLJul&n0p!md#$~R00BB+|5j+>~xdOih6J_`hSyLM7!juaDYHHqd*ca-}}(Br64mu;i%tv|Dr@x1_B<*T)A0`_Gbzz<4Z9W@6^41RVm!Dw^L$ec?1=* zvT@kn+j|a`MgkD5z-YNkJD3%eK@U_Fn3qPrki|uA_4D}JvwNjMiTCEc(e}YhrFj3HLyq?J(pqM+ z3&jM`z-XmO+^jdovCf3c>w5daCS#!dr@;~jT^ugYdhF*TisPAIIKa6cG$QeFN(8b+ zha=#tXjWm+G`p%Y{Ax?|vNJC{KusU9b4hT{+*5KD{W;8fq~2Tq1frvVA(>qdaX`VyB!Y5PZjbS~JH_=Z#-R=ojNI1=}5ppvv`Wk1XBeK<0&>9iY z5;7**_D_6j1g=6O~;>eb|QAzP@Mx)Fer3!9)1 zPX?^+o)tmNh4-yL{)t1e?@-DAXetO85_kDS_Sgi%IN&F?AcZK8pe`w9J^d8ctvmRF z_U1nSh-^Y=+he7@ivFG-?^~jjk}LwCi${_k|K&}NZy(Dd%6kyy;nssbpX*JS9aZVb zyG1MyEVWBGsBRUGb>Fw+ye97nzf-K(aklVDtf;}r9cFg&L7N$yixS0O{67GzKvcgM zu;E1k=r+RpA^}?k_efMv;&RkTh~G#o0RqB)4g6Cq`SF05c=`z9lQLrkAtSq(l3V=O zP=7D&WRuwQ@G%mmDE|VhOE9_>0SVF@1jslVuDiO zp$5}h6Q7#P?sU6!zjAlCh8X!F{tIJ*MiYO4Kgu{=G)D2kmwRW<%$Yee_kR2S`73}1 zRs`N1A9tE>^aE*)D6sVn-5TiYx)u77>&e6o1H%t(VqB3&GA0fVWo%eOvPN<0$NI)V zdNHl*kz@EWGJo8xMx#-$*B^4OYX0I6QqN-`9!ldryA(KXK&0HjVRTF?=3b09YY%Eo z!=|zRR)Y{IcEeOzwBvdtuo^1IbdoJf;Qa59L^z zo(vpw|KO0UB#&8vrR~}Hl*#yOOVn8SI2Ldw!=L)NC@!XK)-M{z=a9v1~l z4kgc3$nzve0xQ+CO4iOoWn9ALA{Ma7jVH?pYysoHxIZ;>i7U7|5xUHinzhr_YPil` z5}u|CEL2qg+59GMO_;aK&`>B~26qIepYmh#?q`ta4<@)e$1+Pp5hcFwvA2pTwt(d? zn0xgR^OedcES=z5(NwV;+CHkS-D`c_ zm)B>l-R!2M|8wq4l1U~=e~(}E-h0mXedpVkGhe^($YVsbO1Ylt?i+91x%PDx?MCE? zndqnpRzx~0sw*N^yeEGeGtyBj5r|tICf1W?%rsIaHoJ{fAku9{j-)d2R7J4Uh^5Tx zik_}O+~^5JC5=_9R<2yR4zS6!dHl{yEQW;td)xlip@!k`Z%LG-fg_vXWi3T}h)OX4W1{8a;nKX0mp({M>7QwHVQa zN=~M!*BeKT+L)2(s@>jxy%|ZXF}fLc zX%98gCY^ukXtQmT9Y)ejq&?Iu2-MM5n`o;MH$4=x*IGdIK+W|=a*;;c;XO4yNehvc zj+!ZrUIV_8W>3rz5t&RurE80vTZEHh9?(X+gxPj8Ex9xzQk=bl$!qOsvBePz^3Xn7 zCwBHT`A21mNGytYh{ugYM+o@_2q_c#JqVAewCR5x(&;)n3}YFwn00J-;z+_eme}4S zq6IF;aIbcXlmMm!UtJn?!ss^fKnAn|-BujV+YPN*J$8t#DVknF6LV#s28~=#M+7&q zi^^a_tAvw0MzSjthYEJ}_P`mZg~sGBknEuwO30!ln7=_I7|eA;CTgZNI*OG>jgG^_ zsosB7+KdN;p*4Cf(_~2|la9t}L(vpc=j$OAbm(qU;I!0+!rME+QBNVn6scwj?(mPNRY^$c~ zdu>+XD4{~g7NZBpy_4Q0oaEi(2w_{L|AGKgA#kZ#m7}?BuwpU05`f4Z^j@7#(w$7c z%HlA#&)%ie?Q}{Mvd}oLb0F{6={CBDY4xiG!=ie#(@NUm+?Hw2?W^=brp=WXaaw;H zd;M65amvl{^I@GnL?6j#RB5~Bw3#&8MRNNnlNJ?UX)7tD8m*w_+&a|rar#6leT@2; zs$W$q`_Yy*(xCsT{J8}bRr)mYAG(=V^LSb&1O)qM=z&tYpB_Y6O33BH@|E*94}Fdv z5^$e~zs8N;b`vKh)7x_%UNjkl_!)o7>U1CVGbw?9Mh`OuUR98~POzKHM=A}90Jv10 zKdRFg=rPC`2<*RZ!@(+z9>+)ANTV{RJoF{OO%;6^r6HARPvzXZyt27yw*D%e6VOi} z^IC}lY4=@X33ydeZGxjLhhx7+Ul%I-Z>E+1IlfBYKsL*2pKPGL60sl|~aC=JD;Q3{EJo*+bu@?+IOfU-)wKMC$yZ0QnI* zTq{wROXE!S#XRRf<@U$)oZ$9hhSmF6DnWI{j9Z&eDjLNTZ8QZ8Lj4^gGdwouvO`nmJaK`b;#| zA?ok%QG5^pf6(a#(VDnL^PWI~4HOlB0y2>FPxNO&;xEV_Y0KX9&`Y9`d4c{0W1&Yt zTFk50L;s)$gl7IJwGK7*#LP5$hnH>hz5-5S7);XLG8@257_6{yD;psdh-*p!Ps60z&pUBnIi^-<) z9Js&+jpt2}EkzXZ0-=9=3>c&_h6+bdW^BWmOUDy71O+*p7cm^;BxB@ogH&A%eU^80 z1r?PXnUgDdsm^m5qsQc)q?v-}4EKX$i@zwi9P%nTo#F^e|=#v9M-b-W@J9D|wxOz&NpBROx?2L(pZ!WWYBakC-`Y zVYHZIv+74#^T#u>bQCV`w_A@IjW_DNK?K}_98`y8ru+#j4e+C;-$?Y@pn$AHS3cpi zy&HL83;N!WWgW@%sEk2mR%6qqy4|5&hc@r5YYa6V+TGT)^U#*` zt`UuMjclBGi#>m!<~*Ypu6ao*07~D=+eB=HKz8hz8n?p6AZsR$dU%Hoh}mUY?)*_B z4Yx5ZtSp@2CKx?zhKOnOUL-3hxhS* zox^+p&0vx9LRMFoIN&-_boSZ&9oG2}8)9HSjtGPcMnr$Qt79bAiXtR(cP358%}7SH zL);0k7infmQGJZfPWG8moxAvYoC(M8jH0?0?#(;m!N*>u2jhmK88r7~|Bzi!yu>V-(#i;3ap`TXepO--;HbBPv?U_Ke*uP9AfO z#Bbxb3$xq?Bh_krJJJCr*L1VpQF-`Xb`ii_#mIk3n4{E`(P{2ZMw#YMaGNts`VM}t zP%y@+NolLDt)aO&*COy;@&bi1LOEI*uhjVc@W8@Lv=%d!GP=yhXqPF9*$3=2C5@60 zmvbv7PE*BcfS(WXhjsoSV+wNZ-nA*PR%0>vfNXtlT6o`#Vl?;Hd-&u0384fOQ+KAP z137;m6e{=Ei(2(5zE9BnbiU4_yYH^8k9IXD(xyxhp8-;bD7lnYy zgb}lE=3-Xl2-H>EG$A_l{{%l-0;l{M)7*&zZqW*rF>NF-8cc`exw6qyobMM=Y7l{rBQ!$ z`AKvVRub*U5AAgf9kWL$?#v|8$djYmh#zA{YOpe~4%r$+k|sRP-y`qZ{2h5}{!T0D z7i#wl1Nb$@xG@D>*R`jtSSD@Smi?)`ROFxO{G8}Jw1kncL{^p!_Er!7l7A&?^RHbb z{!OXB#zFj@$MaJM)V&jx)Qa1dlsA8&jKN~w@U?8D+ejqLSW23u!TwPB_jv)wfrP-Y z%74HBo;;@UA9a3S6m(r^+XiF0ta8+~a54JO{$Kd70`(<0aXQ&6h>CPH5uVrw(%<HLa91@nK%u5OsM zLsb-{l2ptzdqj5F3UI}#xOBy#xbuB|9^5)6FKZMHkP*1HN73maxT{hME#!V3@+gxq z{P2s)WEeOWZP%2k=m?60K)q$9ku8iK@r#Wd&oRZWP!+GPl!>%c5<@Z(vLgVN7KUQN zhzqTxMWxw4=JH)BS7yN9l$n3|;WPJJhK!mJ$`|yf_{2T2Klj^4$fL{^zke)L#3V8% z&hi)wkX?tnxKTUeo^D)l+FRh|GNnRS{K`TPHQR5Cg)I9Ee}0(&TV?}giLT66Dp9^r zP(<|EMjLx4Wv}AEWlEK>Tr~tqiJy|-S;x}_?9Q&7_&lXXsTJtU;mUuoSWT{1jv1Lm zq#OOZ-TDiAY064rAzg!tS*2Vl^srjPKqr5XXfe~>76iJc2!Z|M2#h9~ zG&{weXst~FwAU#?ol2GU1veIjP^z*4Niv!^Y8^oWT32L{ocLO(TD)GbVm^Gvyt*tv!EUoBpkJJN?l_etC51^eVj72V|M%}I`t>^-j?M$_PxET2E z@EMSIRp-jCe`NZzOPA31$RsRhCme*q zYGVVEIF>pvNG9Xagz1k6KkPwelk(@1nSZO1^mit$xIbcbnC%ccZ^Fa*wldc#wDl&^ z#_>_|wu{@Ji|~KmA$W(cI>m3g$OFifks}x&b_p2NTX9v>nu)iYNx6a)?;=%}Qp6W& z_>Yw?qDhDGR!XFmdtF(YlBM!2&B@Yb;ZT;Ac$a3W_LTZ;)c{@jFd=?IedOIcNOj?s zsw_43)0XPcva0;|HvHG>-QnGtr9J%=&e8$Ggv()R`=#tda?34u@d&a0f62&o}o7l(Ax&-ws6onNVkUrr|CUe zdS5@?-A^AFqz{FIu7JoX;iRPcuCZ(m^##1E=`oKGpk( z5R;}2(ieY#D^Sn&4V}mO7qj%0Q{)4h3gtZMQx9e7e3qUGy84Fh$NqQZevR0lx*BTJCZlmTymH=_dl|7?6G@k^BP5IRffa-6LQ-9-?2~O|t;}YYFV`qtZe8 zZMdKQ_iO-YfDCGo+NWjd`BOAq&=T)?-lhg}w1a;o0;)tpy~*bqq!+@zl0)7X`{|Ea z`fHZ{F80nt=q&x~6qV%#wb{7vH6K1M1YNGxTDj&^v#i{E;f&8!t~n12Ny{}UYEYA8 zwF0s>GZ%sY@o7O2=o>l=N&*eNHgN+5k^(9i)@syq~9d zd$S|#J*YZ-rpp4hwhG}nrS zm8Yo92M4SRy1ZRj4+q_nhbx`82%oS&);d2*GlkpX%aP^vUpA^yw^rTV~;Ki-?m0x$u$lwMr7- z9md9XxHZc^xSLi4ggt2z9?!~@;veMa!UDJYP#OP3gdYDqkN1nS+{!`c0RLt>|2AI6 zzdK>9adA-fsZY@E-0BS19rw_dL4H0g1ObmxG=_?t#g zo)}PIPkozDZSCW7q^MTdcj&X*`WVr7j=o=&RXlJXXhE3*3~{`|k5gU97PYEOnI;4h zLM*M8LR3@_DrMo8er0-fR+$xUIY(2gA9H?~^lHb-mOgTEOZ7Qw8&qb84}<_QC#%eF zaXw1(!j6CHwgF{PR=K=iSvsHuaPo>W<%+Db2D_!q<*TruVqsYuyozC)GNnb@=66|TN13uy7`dcChXG0^0N5rV%9MS}`jvxcM{$2#4UP}xIUXa4kzbd{!0bFr)6pV4Qm%ClIM2!FdFPMB(>>ii$9%%`D}N0%jeDR6KWlvC*1Fr@F(4OilG)Psh#o}Rc{tg zQWSq+J4{+N1ojk_HmJ0aSf#6nXmaiwWQXV@q}m_y?>d!kQ|UgH?k9SgR;yICVAbVA z<|C@jDP_#!-qXj2iOr}98UXsb#lqQPRnf%J*??LPrDa{?g9Zv*%*#_13` zJNNK5*_$@jC|&Sexp7jGeT9|_2LDd;hiHF~P=-E4I=RL`6J0j%Wh%w@5cLgnz&EL! zg7zh(5b~d-zf99v=%P4`*)^7AUc;8gj={BAuuWdX}Ci2pqgzfwwu$X$T*qK$v^ zd>%(5mqZb?atMz6>G@?Eb;h0%FMU}!E_B*PN`7~y z^-5{@->}!{^6c z!SQ=w@_TUz8dv%hDBcg#K8SdHnx=oyGc=WTn#Nx8DxaY;7^ykN~&~L(^BUuTIO6sRn9u9c3w*X=RvA*MyS?#f|ff!Mk}0sw9@$?UEyo|5v_8* zL{~chMXOy*Yg~(Htt&w5T+1owYNqwB?R1qZP8(cDX``!`u6Er;*SH>}I`?#{cdwxa z_np+}zMGodKcr3SCfcmNgSMzoQ?vRkZB_paU9oUO=*sbLP)i30w>@zreii@#T`iLl z8XS|uaY=u%l@VYtItJBJVBIKTgL;?Tws$1CCAry#4^+hA3+3T!h!5~FR8+t&tyG4H z+XNLvK?PCqfiFZ6Q4x{)KVOnH?bfdHUDFnLQY&vFda1yq{LO0#l(wDa43A<%y+8P+J(=F|(jAtbh zi&uXbC{vid-P&QbB&<|l%lX^3+cD2Tg~BmW+e%4!zQTX&cQT8Y)A-2_6|5J!)0~v!tyM@jjMsZI ztf<~}EMK>dOD_sy4yr3rj@lpssFJUFK?L((p&({prhFY8#4OB~uVWPa4RWpq#|B`a zPN9shr~i&>SlTf$hO{nNYJ`Es3N!j_?*dB#nUB`&;=!V&L7adS16YCvg?VEvyF7o% zxwEoPB5tP*GzPFt#2aMSX(jE68sy2zER1Gh)n7=d^CclWalyDU(RF3Uvy%ok z(sOp2_qz75+80nukGk4ck{WEn=1O$oBHr;{%SmL@_IdP!)yI^no-AQ5MpS>KUBXH+ z2}1*0nRElPkYGiqoh+3YN-!x(1)_Grbnd>CU)9)__kVP!HDF)nHKB2}=_ zE4Qm}a;HspnGHBprnIVc^;;`fw{~TdL0m2tZAYSb9Sc>&Kx({4wfY7I=>A?;inr~> z`W23yG_8xJNUTjGBSH3Pt-XJXI%I;_k+*gywHhnpZEhmLbfr_|nsyG5I?GoiDoH#-9zGAB%x(1*?7!F|Zd!6BB_v4D|53x|xe zcg5_1{GIzozW?M)@kri|Z*r|Uqc5L{{Y26>3-%6HoBFBSPVs*-1@SH1FI>J&ZxppU zE8E*^r|tMg+tRJ?JB(U!uRS%8EmWrB_Q7J?EErTmV&J=zgiZIPhXQyI-%}{xylF#t zjfo#9G>#FL@R+izJrckn{7_2T;bVD~3j03_-~fKi3qvmP4i1q)JT5LffS*uoEa7Ij z@-vw$4&aI6jn#i*N<9AaaT}ZZ)9%jvDEJFHrOnMI;&mz4tLv4@RmVHkU&l(uoc|Rs z%_Ny#Of>_)W~4G!ue~|VLb|A7LXDdkrQhOr0sIEfOqeB#MPu4o;tz83{-~guP5hbe zpp!CgDncdyB7M*H3hgO~zlwW)iN8@bH`AQrlw;!W92{Z_imz9MUuhA2v^;-Lj6Xi07VGJJNoGmPR41@`^y*!O z(m@fz;P^hb&dE@p zI=(EOcpck`-fK41dIqYE&uuEvZkG~{zlLq6{S_()%aqxdY}K&+AHD+qrVHAc5?4VCS~+-3VPziA&9g!f@# zsC*w54dJ-EAb)dEkK^;_%%zk`?*8i=N3bN?(Kv|tIV>H)vS?$^5Slc)Jcm`)t^Ak6 zDML7I5DRiRa}QQ8%b{%#nt5g}e+!~HY#2sI^t?e_80|cWioO0>%kD-unQY0y$|2s} z7>$2!B{eDLcMf7RTR<%3uhjZ`${(BD0XWW?~dniZ{;Va?sFHTNLU6b_Z;kPVgR zuuXExU#k=jfTp5qW&5c>?*5KGrP)LD{^X1ZADMlkUA=- z<}+<_YSA8K#1Wn1hKLd3QhoKqJ@nb5A%l(>QHx18q?XW~KPACIG=wT@)QJ(Z>|Pkr zNTokybkW_FIkk+ze5!LVX7jf|7%k_=-0-k!%_$oHJTWsYFnkclh02O_ z;pu9lhDkM`p2kz^3iTch-=}}B)9`xr2@P*i!y0~5J*wfO>M`{UpnjsB(D*azS%JRm zd>`@8R4p0?RM>Zm?=u<~DEO_u&ud(u;J5qsXXx)1lNC%IDG0HmfdSn z_di@@V2Odwmoamcb6>zfxn4L||9AEO?FNS%1&p$aPf5TUjtavVWsRSb#=teCdKI$` z>{e>wD0@ZK)ci;!GX2x>GqhUfbR;)vl2fMF{1OBAjN;lUiChvj1~O8M^0r_$jKMZNjiJ3UQwV37GgxS7JF~=7} zwXY8~zN>JQzYDeg%P`j;$2`A-`Thh#{sAq=ug4Dm{{c`-2MAY7nUlqnzkXeR%Sr<= z6o&s;wOU7Oy=~l$g13%?wTpVejUWn&pdh%6)6;1*b0L}5h1du2EnF1Ag%99EiD#-J zA_sEL`SYDWA$k9JeFIR(g1}R+chPuoZ)9*sans#(gO*!$gCt6omYGUoFG`xkx*<){ z5^uJp^@279ceWE*cef?ArK+2MhF&C7PYpck^;)gA!>noi%(psPvtZlO+v1kuP{l+> z#UZO*GM3L`y|KBy+3=dwbsxtd1WK1l#{_hwGzqLECiFuio0|N4Bh!?Oe-hhFYQ6lO z5A%x^F)T2BE4$LyG7kOon_CL9B1YNoYy3Eg*l+4|z^KH}{r6aCNu@h~hR(=Z88R_* z`s;F;<+o*ObYI0PI}lh}{cG?aUb_+~tgc--a4=Ou5oHHs7$3(7Dh^7R$g5d_;X{5U zRJbYf&kS+J6jzm;;Vd{DO!7L<-69GcKzaT8`UTS)eauw*n0p2WXklPc_ykZ(2MBcE zwK8e}008F!002-+0|XS4nSe)s)p`F;^th6&JZ#5FL;|_7V`58|odgmGg_rFFaI8GA z^FSmeTuayXC6cbty^3RAgEi~{+d5jdHt7nCj+w2jYc`NWQwVGwP&V3i?{184quryW zYsc1Yuz~jf&b_i_$A)gpFF&38o%5aVeCPZA-{bhz_rLf&5nV3s5_I~1o_hwj-Pt^> zrH|?POmm_+J<{CKoHla>BdghlnUCkpjE?!Dp4Bx=$Kse~#nWSY`j}P9SDdY0XH*em21$c|ws{2Pu*(@fkF)h9cq@Eu&^15C$@}rnNt`{wwh52or zmvwH7XY}LEcLzua3JsZmrD9sY&dBP5E;5UwU86-UlhwP%i&~+e7rXlNmaS#83V8)B zyG=W;b!D~uXxHB1+w`=pkYA8LYmScUMM0~R^XyN`#qELu8FM_JHMNYOi|1q9;Vy&q zBK<@wLTDPp-T3(Z1F8a;gtg zm=*`gM=Qb6YN+ZVIe89hbWl7* zG3o?s7Bj2@&aH22KRnwSVcJNWSc}bqmd;sI5ZKf>Bf6)5Sk&a13T+KhH+#CyuyzV{TpgPJjQOU;|Mnl|&cX5>{ZKLIvJCquV7)tx5_AoPrCo9c*> zwdEp2CiC)7>Td=s4k+6n)Rn8ln1lU~twAxaL6s9u9c84fQdjxqc;|^USsXt8n=tefwAVPgXL%H^`UxLA4eht*th-d z7g{7t2k8*2aufKn#&CdcW<)^W_IZraYnnFH)C#+Qq1ceE6_F~|Z&K(ZA-aXp)jr!M zc`e8J!se_q4~%c+lQcoQJ{&82yjJ9^PNPPhXBY097PJC2#Tc2W=EOd?UeH3ys@WBuYWY)Fj zTk|ROSM&1ZvW13QLt`2u zWvG%_g0H?mDuW%DskaScvW-r!Lfl3~y1jB2;tZ7zUHa zx!cu|qM1V)u!|*&)1W20#ZJE1j@ru{C}tCCtA$xtj+!(b6FJkQtCS>ku&G)2j zIHYD;br%jAmSL-7wq39iU2cl(xMZe9kd>R>Jo^^%FVfq4{ z386PUi`-Gl-(eI4t(~`~&g8d$SuGZJblq$eoM!DyA$pFpo}%Y%u8)Euv%0LE`BF|V zO1DF~^YjAqzc@=?4U0m*i%`RBXSZ3@cV_h*q#`nZkK3Cs(@V%hdHsa#F3gSX>B{;F zeTCUyMMb(m>zz<@LZLr^#)#-WD3oFPDo65b^fhFh1^aQ1`Ta$WUrm~S(~NLt{5qOf zOB<0Hfn^E0^he5BQ1XxIPeSxZ^rw6>`apl~7`g}C^)H;1^9|(5suOxkBO{y$_Ll6L zO7d5NR@J&`_Ud1U=neKuWX7|#3@+yWCPc5(-=e#erE_{!P*+=We$!PiGrO+gK5x>u z*tOq=SLQoVp??6}#!+^EVeo(As`iic9sc?s3MjE-3-eEMYKhiw9V0G zHW?Q6q9G)fiN-kvcfr=Zrr?Sum|Uz7w0=l0A~w{bZkLLAB=sBi@81W?Dj>BY|C$*c z<`k={k3IKvMc{#d>a)!QgUK3`IEzWBwnkhW605}}v$rJ`fmh=a6fv%edFQ!@)H`SY z%o-#$iRnF4Y)f&flxB<0z&ONSmdZj_v^?mSsSoER`6g{Gko4I@` z6~Y-ecqqPq&5^x!`s5)K8MvzcOlA;xAg*j%Fh_~qd<8tRmW@gi*lIh&}!=(QA3DeJQ_HDovFFS$C(At4v2#xv0og*2E*bOx>zg~(B_0i3Yi@%H~WbH zEhEB7XdXrpB^JNw_51~Y%bFY7>v!lQS}AK+>3ws5w&VO;#8Gzht+W5Q^uHCPh+)*| zY6plHr(Lr8#&A(xzFc#BrcV zj05X`3`+P1*GeDTtrhk&4`7=#^7@`qZdfnM8LCGKQs62Nz5*1)S3Un-O^#t&j8TCP zm(WrYVgl3#@Ov??j;4-GQNuY}o*I~bc*&) z(kH3ODdI&HXNR!BT)7fwZ-K5>tg5V7FO^m=;2g< z@+Ddtd*1sP1!JDgljIdI*B+6%;E2VNhzby~FTg~L#2xs(lNMKCY7bkwou~p!dGOid zQg!4}XiIf%G5J6#)D0Sinj#hWgW0=X2Epu#!`^yng?gB7ap-ID^$xAAz&DF9lY~d^ zgaT1G+>ed>Fgpfe$KkDyLF^-#odn;1n*he!*i!*tkaQYxy~^thov(JhOx`mMV`nM9 z=NuJM32$tQtomj2r9i?L@v_VAOl|c)LJjeV&(q{D(~nKU-Smkj|Ds3P?{CB--ZGsm z(@$al1K2-B4|=xxntbu+AE(ZElkc*6s@!vy9!e#!`C%BmF5+Fbu}q&j1o4=E`t+fb zJ|N=j9W3Y3)SfauehMnZQe}DqXnQiLuvbz?5?)Vq$Qv8-bqx9ALk+(k@j5PgDg_7q z=6R{bd03!{E`rGk2yT&MJASuJJM7~W^1V+h6uM2Jf=UIQmq2qL{P+-#e-+MCC%tvPqfFmB!$0Sd%|f_JuQb|nIj$sCj?L66 z$2Qt3$1CV+Iqsxga!k^HUO5iZ5jh^AqcAv;hOppqj7&L}=s`L1Z?6Q+r{w5BD9bol z!lC(P>DK4irDkv68V7`k-lbI)+{!BjU|!02m;5`b z;0Qrp2HUXqYeLmHbp-~_#nU<{sTW9 z?)N+RL7bK8eU~+U6pc0W3eWV?c^pQ7gde%X!47iFbqc6@;T1mTc<;l~5hUK}h^A!L z9f*}h&^aDipe#ZV*eJq|Sc#M;4!Ngx;M65J^{iN47Ava?t=_}lMv)OgMpL7RB)lTw z6A8cMTboKKjI2a|fT0DDev?rpT2p`-FvR~;9l(`nFN=;w(Rs8iE`M5VD~l`T?=>%q zo>R0d;(I~#l*NwJRrU1BW1P&o&(Yf{)@sdBMk`EleWU1y+7ZQ3Zc6MrKM(maR$8vEb7kv0L6kbu+xuvJWYs|@3MsMy6)g) z0GAp);t~TL!AvgmW6j?bZ7=3Jafc zX(HUa0bIC zXdb@}`Xqf$(mXQ#c`8eqN2Xt-S0v3N(_g_a(>yZ$Lp*_*=8@_5#3D)a2wHfApJ_QV zy+kaRG>=TL7V9L=T*Bt|5E%_Gydi@PMvBhyccZ^(Pf+#n2E zR)Okh#e^c3Ib~p65$iarkpNm`lZF)z^>M1kdIev#3+@JpNXjLdFH&&=|zLTAHq2}0Wb+NLR7P%|3-%;G{PC|3<<3Za|I}N$+ zbR2>5dZ+wAd?Xq9Fu5~K?cIIP#aUJldq8@f0?!j(N=b{o7s2H^PV#~Gv9uF z`3j(fHHPD(qi5weYMmSV!fmJgPzS?c` z6$bqx;q+F6IZ&BlOn?0Rp{;YK|C`f~4>gpz)LWa>= ze?$lU5%-@LO?<$WV_JFbvb1kxi$2GC=bbM(L~3-!%GY0LjA3&lsQk zgo*c%PX?37c4C$~G3mlbOdKQm0hgCga8(8}thOzGV2qeWTo!i^4k+ z`%bB!NJFRTAW>454mUdvcaWd~Otu|HFQvo$!U=MJd3-18%%i{Mk?&o_+zD3xTLoPp za*@mZzX4E72M7foSXo;F003G9lMxymlh8H~lka{AlQ5JZe*-ZP#wYf#wTgWLC8t)p zU93Hd6xD)kEXPvKWM8U0E40B<=Qhaln3y{1DQ8{uH$Ph%!@#E)9J3{xXiMa@O_;98d47v|G&mVVK};+8+yo(@ z^^h-}qHAqPz+vu^FmV_g3LhO71-bAJ%k6}#AfVzPC zxwg8z5OC1F`3dMe+-yDj?|Ksfm2Pj+h9`;gjkB2LNB&R1sk9e zET&%YWO9-WOlHQpKv`;6eeg%L5Bk{GK83cdWgmR=-QVDE@Wr^#a7lo;G;2*}&c1y6 za=yKP|NG^y04A`(@Z{mcrObD+0@v1sS&U7_v{Eb?)2ynk8{E?^GgY;Ug5-`c1a}1~ zE8Ifje=>f*X#Q6C56|!|Q`~D^0Fi7B5cI8zp0Zp-yVwc*KdA)XDabt^4l zrbc#;zNZ`Md2UZJM4I&qREAF7-A$`KK5{>T2)a~+agw30;X7>^21TaYTm{_>AyKux zEka~C9X}c*f*zbvaT>i0y$w|(PX%Ww>{Z>=z3V~Pdl8(&fQo*cXXtOvaL%-SFXuUe zR~5W}A)_5BFmQcS#UL&)bQl&dGMsA|HTeg&7ZH8Jy;(-^z2j~jpePpOwlLXX%;(gR6iV)@`rlj3f%D}Uz1H{myw!2cY zy^b3y6nw}Kb`Ll;7Q(GV?AtY0@Db73^|<4?E4o)9p2Ux1N+#XL$0}wp%kY}(7u>xR z`r>mfbNB>zq;-~hZO~LnU}DBx1PfS{io0|!%NrPuA1}9M2bPeN*Ro8GL;{9P3&;O| z33U%Ek_ewOobpZD1bRv51w#Y_5RD(<|M7Xak1ymrd`V+_K+MIN2b!?uNGbSwUlBxI zt~l1Fgjetl!}*3{$u~W{Dw-S%`{UBo%Uix~6y@%qNf?@O9LtGrR)iUqvq9>peK3d7 z5{=Nc5K8}Yhrw)BR0!yFD{@bngqiw(kNzLZf{I^rj~JF{5h>fE_`UG93Ju>9 z*{5m$(tzbWTJhA?Wobj#k)`#!;8zR62_&>HPY{#kk7$PwJq#`##?VO`;Fm-Vr=CE0 zj=pC*=+Eb~+jxBkgZcTKN15Sqa^)|6gA{mp4NX$JjYkhvQH6868fth zX!#w8d}6hyyp6RQ%o?oGZ8)ze$zCGu_gKXMqQN_gAv~hghcFzRAK|Cq-3E`OH_;ic z(+X-=FruJep_#{DxKT$3m_my%oc>npXqWmhb!JcsxYV|hfOG`m({qwc$e2R3;Ya$W zp&@A-+W!GiO9u#Kvf8dOlMtF+f9p;YK@`W&K%u2BrCUIds>mYt0;N(4mWvl|DhY~d z1p^@=Lp$9rYi3vrX zu`5Dc&oU^cFqY9w>1Bc{yhq30tg@vc4e2Xkc3J?>A3+(QkN5csrmo zi{PbXd!IzpqXsXnY#Ukw-n*gipjzDG2*gtNI9Ttqu%V31ZX1=?H*5fFTSqf#3i7ot z3UJV+q+R-gcd-p@uIPJ#RWwQ9y zO7ab?x!PovZ(n-Xzy~(UPZu-2ZRm%y{4C@V^n4^GOws+##KRIqFlgl#5PgO?*=HX~VUXGPYG|^p4tWVft zj#GiPMQ5o6_qHo(>&kFKGQ927+q6%(GKFd}g zTQ{cO&~ea$)LDR)ZK^x04x&eowO8DOlcxu4m8sGQDi2-ydys2$UI}X#omgic^#&`tsS!|Ywo?&C!|o7@uZ8(ddXr{1eeXUHckc4ZKLrd zk{C2DLwR>Pg$Ot%{?G@+Y|;@*n#;>}qWLeO1 zV6Paiz&+^s)s0La~v zAOt%&sD#Q|bv#|hi&d(t+EG5u%{iOZ8rs8eA1y=UvRm-s64$|WSfLN`q$#hE3@e4%5jP|M$Iv24-qU zuE3|~0i&r{v`x)Jbk5B7|bIGkisJN<7f+KPrrBcxfg--!@xBuewf|aF`*I0>;j(& zVSvJ4sh+I6+1=K-y;`Fo;nh0fNB1_RI*j{p=oM)2aHv_`3~zLIES?Bch|CQjJP_&T zL-E+S^w>0WibmH&+{eqb_{MbSY;t(cV7UG$ta(Uzq)QkOfVqk^%~$I|GWUtLQ+B-` zB~E?D+~bluCV)kdngcO<_O?AdMv0z*dcY8VI_3O1vXp@2H+|N539B@|67dq3z(j$T z#99M#O7(uAjnbOpr?uajMP`dI(_|W7gStoS;Pa&n9SE-5SFR7UvyRR|PvL^%!>g0d zMpOBTUcOaS(hSnn_>nVYG$=6hY~t3J{+2)zO4zv-OpYx%sNZx-#-YH?^>x_ zd!$tH_n8c?o~F*j(SYA%S6=^6h}sL`AS)hp+z}PQ1-r#lpCtB=6<7r%W#T=A?s`AC z@HJ4dv2O2T`lHJNi+J(6jY5-pOnxBUh9v5}e#ysN&G1i|Q1I>ThihxBH(RKErw-Or zwA|fy8|r&jwZ}{c-LxbP5H9pV%bKXMvWpqE5{I3_yQDdq_&zkki<`I;_=@gg=H>|`Dwpz#Y}0uC^Np2TBu0*x!`@cI{CcMc)Hg96QmP`|PS}S5f$` z)ooqNs#DZW*t=7X&pp@25Nw@vs)|glv&Fl}p3w@aPmgROyDsp$C>;+>$ODihF~Sph z@z^94G2emUu#5fD2TRt*Vl9a|Vq6;ejpJ(CsDSHh`f3d|GsQT^m5Nz_bHrek9MrHf z`AL2{p?d6@sBwY=DXSpi@k|`{j1K)ofMFP>Pb%u#Q|GUZTvYbVCbI{GhejiJXkHhP zg=StSi@vt^x@!fla3Z3I2_g&Xqy zjR)ba%QaB$mnOb9`=t6k;!M=Txs$FvHO`S&nv?L4GRO)LA9{HxDP>|cJNdF-UOjfA zQ|7K(Qq>0_#YdSvBws((Z9A6wMG@COQuS{4#Gl0OwKPLE=RJ7AqVY;g7{+ez_`AD}>9VN!`hGFOoWSm*rCfO~; zPf|C^i`4&4F=!~G3S;aP3rm#a&VR2IwMe=O^Jw{B<<(z}4RNN|Ito#A_4f`=wPY=zhb1)7Nq5ilDbf zgOz52O~cHOgeAA)hr6Y$rl|vv-p21)CCg`w>k5J2?v_F~EhW9h0f8b9#FZ{%uVnDw zX9OgRL_t8lq@5oTxLKr&G|;}J?Ficc1a)wZG1+7H0ck}@;PF0jpx_=KwB-z4#R||V zhOrnQ_-WV!TrE~c{!_DAi>U=}p&j5tu?q5+_AUgX$pjn~+W~TkANk%L;=6w7FzA;l z6aryx^)^5&ae)5N&EzG+SUyTJD)UH5ey@o9rIPsXfWAuq1abq9$r4D08XvfC?E!mH zVsa;5cE=s6tPmr5hLjp`suaeOEw^J#Dpf=NUqfgB6joy%-zdeOK(_QU5yW>vj~H-_ zt^R}S05r;Up$t_DAgT<;LdF1?GI5}`Oaj3m-UlbH`ahH71bi*CK<==;gCXcJh42y! zY?dK_$Z{RT4qrG3I>7{*1>TlBB6g^%LAyMYT?05(VFvv{yjxfSW05lg`0v30Duo?z zHj-zI_Aee%2t=8wGTn3sENaHMgpmKF>=I)FyPy0CmP9i6p1^6j54v-d36y@C0jfKF zm@y@cuW$ftC5)xVkzw$yRDk|SXe$H@DS=?2Ffddpiuh-?6?2o_0N*_m*lFo2}kOpG_cg{q>K$x$J2N?{YLI#onWbA*y+4TfP z&*hj}JeJ7-W>A%pzgCd{9+FNX1LQ*6lTMR`{1-ocl`eV0~V3w delta 36287 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 TbTtHgKIb=}#OPPBcmMwnM9LS( diff --git a/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties b/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties index 2e11132..bad7c24 100644 --- a/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties +++ b/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.jar b/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.jar index 8bdaf60c75ab801e22807dde59e12a8735a34077..f8e1ee3125fe0768e9a76ee977ac089eb657005e 100644 GIT binary patch delta 36466 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 delta 36287 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 TbTtHgKIb=}#OPPBcmMwnM9LS( diff --git a/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties b/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties index 2e11132..bad7c24 100644 --- a/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties +++ b/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From 110efa985db0762589faec53fc8c9531f1cd57a4 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Fri, 7 Nov 2025 16:22:50 +0100 Subject: [PATCH 054/449] Prepare release 0.7.6 (#379) Bump kotlin-sdk version to 0.7.6 ## Breaking Changes None --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index d401ea2..a8eed2c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,4 +14,4 @@ kotlin.mpp.enableCInteropCommonization=true kotlin.native.ignoreDisabledTargets=true group=io.modelcontextprotocol -version=0.7.6-SNAPSHOT +version=0.7.6 From 33b5fc33dfe85dc02d11f185b6dff0ea2997069d Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Fri, 7 Nov 2025 18:09:27 +0200 Subject: [PATCH 055/449] Bump version to 0.8.0-SNAPSHOT, 0.7.6 in samples (#380) ## Motivation and Context Prepare for the subsequent development iteration ## How Has This Been Tested? ## Breaking Changes ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [x] Dependency 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 ## Additional context --- gradle.properties | 2 +- samples/kotlin-mcp-client/gradle/libs.versions.toml | 2 +- samples/kotlin-mcp-server/gradle/libs.versions.toml | 2 +- samples/weather-stdio-server/gradle/libs.versions.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gradle.properties b/gradle.properties index a8eed2c..18d5b8f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,4 +14,4 @@ kotlin.mpp.enableCInteropCommonization=true kotlin.native.ignoreDisabledTargets=true group=io.modelcontextprotocol -version=0.7.6 +version=0.8.0-SNAPSHOT diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index 988d14d..875b5b2 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -2,7 +2,7 @@ anthropic = "2.9.0" kotlin = "2.2.21" ktor = "3.2.3" -mcp-kotlin = "0.7.5" +mcp-kotlin = "0.7.6" shadow = "9.2.2" slf4j = "2.0.17" diff --git a/samples/kotlin-mcp-server/gradle/libs.versions.toml b/samples/kotlin-mcp-server/gradle/libs.versions.toml index 56ed570..ede736d 100644 --- a/samples/kotlin-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-server/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] kotlin = "2.2.21" ktor = "3.2.3" -mcp-kotlin = "0.7.5" +mcp-kotlin = "0.7.6" slf4j = "2.0.17" shadow = "9.2.2" diff --git a/samples/weather-stdio-server/gradle/libs.versions.toml b/samples/weather-stdio-server/gradle/libs.versions.toml index 1ac03e2..3b4b150 100644 --- a/samples/weather-stdio-server/gradle/libs.versions.toml +++ b/samples/weather-stdio-server/gradle/libs.versions.toml @@ -4,7 +4,7 @@ coroutines = "1.10.2" kotlin = "2.2.21" ktor = "3.2.3" logging = "7.0.13" -mcp-kotlin = "0.7.5" +mcp-kotlin = "0.7.6" shadow = "9.2.2" slf4j = "2.0.17" From e6432dbf26f9be5deb98cafb3498721c324dcf8c Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Mon, 10 Nov 2025 17:56:59 +0200 Subject: [PATCH 056/449] Replace `Instant` with `String` for `lastModified` in `Annotations` (#383) Replace `Instant` with `String` for `lastModified` in `Annotations` ## Motivation and Context To fix serialization/kotlin library version conflicts ## How Has This Been Tested? Unit test ## Breaking Changes Yes ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [x] 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 - [ ] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed ## Additional context --- kotlin-sdk-core/api/kotlin-sdk-core.api | 10 +++++----- .../io/modelcontextprotocol/kotlin/sdk/types.kt | 16 ++++++++++++---- .../modelcontextprotocol/kotlin/sdk/TypesTest.kt | 5 ++--- .../server/WebSocketMcpKtorServerExtensions.kt | 2 +- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index 4f230ab..0c56876 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -1,14 +1,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/Annotations { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/Annotations$Companion; - public fun (Ljava/util/List;Lkotlin/time/Instant;Ljava/lang/Double;)V + public fun (Ljava/util/List;Ljava/lang/String;Ljava/lang/Double;)V public final fun component1 ()Ljava/util/List; - public final fun component2 ()Lkotlin/time/Instant; + public final fun component2 ()Ljava/lang/String; public final fun component3 ()Ljava/lang/Double; - public final fun copy (Ljava/util/List;Lkotlin/time/Instant;Ljava/lang/Double;)Lio/modelcontextprotocol/kotlin/sdk/Annotations; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/Annotations;Ljava/util/List;Lkotlin/time/Instant;Ljava/lang/Double;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/Annotations; + public final fun copy (Ljava/util/List;Ljava/lang/String;Ljava/lang/Double;)Lio/modelcontextprotocol/kotlin/sdk/Annotations; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/Annotations;Ljava/util/List;Ljava/lang/String;Ljava/lang/Double;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/Annotations; public fun equals (Ljava/lang/Object;)Z public final fun getAudience ()Ljava/util/List; - public final fun getLastModified ()Lkotlin/time/Instant; + public final fun getLastModified ()Ljava/lang/String; public final fun getPriority ()Ljava/lang/Double; public fun hashCode ()I public fun toString ()Ljava/lang/String; diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt index 60adcca..a2383ac 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt @@ -17,8 +17,6 @@ import kotlin.concurrent.atomics.AtomicLong import kotlin.concurrent.atomics.ExperimentalAtomicApi import kotlin.concurrent.atomics.incrementAndFetch import kotlin.jvm.JvmInline -import kotlin.time.ExperimentalTime -import kotlin.time.Instant public const val LATEST_PROTOCOL_VERSION: String = "2025-03-26" @@ -302,6 +300,7 @@ public data class JSONRPCError(val code: ErrorCode, val message: String, val dat public sealed interface NotificationParams : WithMeta /* Cancellation */ + /** * This notification can be sent by either side to indicate that it is cancelling a previously issued request. * @@ -334,6 +333,7 @@ public data class CancelledNotification(override val params: Params) : } /* Initialization */ + /** * Describes the name and version of an MCP implementation. */ @@ -531,6 +531,7 @@ public data class InitializedNotification(override val params: Params = Params() } /* Ping */ + /** * A ping, issued by either the server or the client, to check that the other party is still alive. * The receiver must promptly respond, or else it may be disconnected. @@ -564,6 +565,7 @@ public sealed interface ProgressBase { } /* Progress notifications */ + /** * Represents a progress notification. * @@ -623,6 +625,7 @@ public data class ProgressNotification(override val params: Params) : } /* Pagination */ + /** * Represents a request supporting pagination. */ @@ -650,6 +653,7 @@ public sealed interface PaginatedResult : RequestResult { } /* Resources */ + /** * The contents of a specific resource or sub-resource. */ @@ -892,6 +896,7 @@ public data class ResourceUpdatedNotification(override val params: Params) : Ser } /* Prompts */ + /** * Describes an argument that a prompt can accept. */ @@ -1115,8 +1120,7 @@ public data class Annotations( /** * The moment the resource was last modified. */ - @OptIn(ExperimentalTime::class) - val lastModified: Instant?, + val lastModified: String?, /** * Describes how important this data is for operating the server. * @@ -1162,6 +1166,7 @@ public data class PromptListChangedNotification(override val params: Params = Pa } /* Tools */ + /** * Additional properties describing a Tool to clients. * @@ -1345,6 +1350,7 @@ public data class ToolListChangedNotification(override val params: Params = Para } /* Logging */ + /** * The severity of a log message. */ @@ -1404,6 +1410,7 @@ public data class LoggingMessageNotification(override val params: Params) : Serv } /* Sampling */ + /** * Hints to use for model selection. */ @@ -1648,6 +1655,7 @@ public data class CompleteResult(val completion: Completion, override val _meta: } /* Roots */ + /** * Represents a root directory or file that the server can operate on. */ diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/TypesTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/TypesTest.kt index 7a4938a..96d2534 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/TypesTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/TypesTest.kt @@ -7,7 +7,6 @@ import kotlin.test.assertIs import kotlin.test.assertNotEquals import kotlin.test.assertTrue import kotlin.time.ExperimentalTime -import kotlin.time.Instant class TypesTest { @@ -243,7 +242,7 @@ class TypesTest { fun `should serialize and deserialize annotations correctly`() { val annotations = Annotations( audience = listOf(Role.assistant), - lastModified = Instant.parse("2025-06-18T00:00:00Z"), + lastModified = "2025-06-18T00:00:00Z", priority = 0.5, ) @@ -251,7 +250,7 @@ class TypesTest { val decoded = McpJson.decodeFromString(json) assertEquals(listOf(Role.assistant), decoded.audience) - assertEquals(Instant.parse("2025-06-18T00:00:00Z"), decoded.lastModified) + assertEquals("2025-06-18T00:00:00Z", decoded.lastModified) assertEquals(0.5, decoded.priority) } diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt index a5a2492..cfa8ed8 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt @@ -71,7 +71,7 @@ internal suspend fun WebSocketServerSession.mcpWebSocketEndpoint(block: () -> Se try { session = server.createSession(transport) awaitCancellation() - } catch (e: CancellationException) { + } catch (_: CancellationException) { session?.close() } } From 65f2f3d931745155efa45fcd4c77606da9dc2e7c Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Mon, 10 Nov 2025 18:41:35 +0200 Subject: [PATCH 057/449] Prepare to 0.7.7 (#384) ## Motivation and Context Prepare to release 0.7.7 ## How Has This Been Tested? Regression ## Breaking Changes No ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] 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 ## Additional context --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 18d5b8f..e28d991 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,4 +14,4 @@ kotlin.mpp.enableCInteropCommonization=true kotlin.native.ignoreDisabledTargets=true group=io.modelcontextprotocol -version=0.8.0-SNAPSHOT +version=0.7.7 From 91d45b0f8db7f6a20a3aaf30128a406cd6a4745e Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Tue, 11 Nov 2025 10:56:45 +0200 Subject: [PATCH 058/449] Bump version to 0.8.0-SNAPSHOT and update `mcp-kotlin` dependency in samples (#385) Bump version to 0.8.0-SNAPSHOT and update `mcp-kotlin` dependency to 0.7.7 in samples ## How Has This Been Tested? Regression ## Breaking Changes No ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] 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 ## Additional context --- gradle.properties | 2 +- samples/kotlin-mcp-client/gradle/libs.versions.toml | 2 +- samples/kotlin-mcp-server/gradle/libs.versions.toml | 2 +- samples/weather-stdio-server/gradle/libs.versions.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gradle.properties b/gradle.properties index e28d991..18d5b8f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,4 +14,4 @@ kotlin.mpp.enableCInteropCommonization=true kotlin.native.ignoreDisabledTargets=true group=io.modelcontextprotocol -version=0.7.7 +version=0.8.0-SNAPSHOT diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index 875b5b2..ee688a2 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -2,7 +2,7 @@ anthropic = "2.9.0" kotlin = "2.2.21" ktor = "3.2.3" -mcp-kotlin = "0.7.6" +mcp-kotlin = "0.7.7" shadow = "9.2.2" slf4j = "2.0.17" diff --git a/samples/kotlin-mcp-server/gradle/libs.versions.toml b/samples/kotlin-mcp-server/gradle/libs.versions.toml index ede736d..af35f79 100644 --- a/samples/kotlin-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-server/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] kotlin = "2.2.21" ktor = "3.2.3" -mcp-kotlin = "0.7.6" +mcp-kotlin = "0.7.7" slf4j = "2.0.17" shadow = "9.2.2" diff --git a/samples/weather-stdio-server/gradle/libs.versions.toml b/samples/weather-stdio-server/gradle/libs.versions.toml index 3b4b150..7136d03 100644 --- a/samples/weather-stdio-server/gradle/libs.versions.toml +++ b/samples/weather-stdio-server/gradle/libs.versions.toml @@ -4,7 +4,7 @@ coroutines = "1.10.2" kotlin = "2.2.21" ktor = "3.2.3" logging = "7.0.13" -mcp-kotlin = "0.7.6" +mcp-kotlin = "0.7.7" shadow = "9.2.2" slf4j = "2.0.17" From 21393e7ce3beb4360760ce312d8658e2318fe12b Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Tue, 11 Nov 2025 12:25:23 +0100 Subject: [PATCH 059/449] New MCP Schema types (#386) Implement the last version of the mcp schema and complete restructuring of the mcp schema implementation from a monolithic `types.kt` file into separate files. - #158 - #185 - #353 - #298 - #248 ## Motivation and Context The previous implementation of the mcp schema was outdated and contained several issues. In addition, it stored all mcp types in a single large file, which made maintenance and further development more difficult ## How Has This Been Tested? All tests passed ## Breaking Changes Yes. The new types are now located in a separate package. Additionally, many types have new parameters and modified constructors ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [x] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed ## Additional context to adopt the new structure: **Before:** ```kotlin import io.modelcontextprotocol.kotlin.sdk.InitializeRequest import io.modelcontextprotocol.kotlin.sdk.Resource ``` **After:** ```kotlin import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest import io.modelcontextprotocol.kotlin.sdk.types.Resource ``` --- build.gradle.kts | 7 +- kotlin-sdk-client/api/kotlin-sdk-client.api | 78 +- .../kotlin/sdk/client/Client.kt | 212 +- .../kotlin/sdk/client/KtorClient.kt | 2 +- .../kotlin/sdk/client/SSEClientTransport.kt | 4 +- .../kotlin/sdk/client/StdioClientTransport.kt | 2 +- .../client/StreamableHttpClientTransport.kt | 12 +- .../StreamableHttpMcpKtorClientExtensions.kt | 2 +- .../WebSocketMcpKtorClientExtensions.kt | 2 +- .../{MockMcp.kt => OldSchemaMockMcp.kt} | 4 +- ...Test.kt => OldStreamableHttpClientTest.kt} | 4 +- kotlin-sdk-core/api/kotlin-sdk-core.api | 6920 +++++++++-------- .../kotlin/sdk/shared/Protocol.kt | 113 +- .../kotlin/sdk/shared/ReadBuffer.kt | 3 +- .../kotlin/sdk/shared/Transport.kt | 2 +- .../sdk/shared/WebSocketMcpTransport.kt | 5 +- .../modelcontextprotocol/kotlin/sdk/types.kt | 2856 ++++--- .../kotlin/sdk/types.util.kt | 331 +- .../kotlin/sdk/types/McpException.kt | 14 + .../kotlin/sdk/types/PingRequest.kt | 14 + .../kotlin/sdk/types/capabilities.kt | 145 + .../kotlin/sdk/types/common.kt | 164 + .../kotlin/sdk/types/completion.kt | 93 + .../kotlin/sdk/types/content.kt | 163 + .../kotlin/sdk/types/elicitation.kt | 111 + .../kotlin/sdk/types/initialize.kt | 74 + .../kotlin/sdk/types/jsonRpc.kt | 225 + .../kotlin/sdk/types/jsonUtils.kt | 83 + .../kotlin/sdk/types/logging.kt | 84 + .../kotlin/sdk/types/methods.kt | 49 + .../kotlin/sdk/types/notification.kt | 327 + .../kotlin/sdk/types/prompts.kt | 212 + .../kotlin/sdk/types/request.kt | 116 + .../kotlin/sdk/types/resources.kt | 388 + .../kotlin/sdk/types/roots.kt | 81 + .../kotlin/sdk/types/sampling.kt | 200 + .../kotlin/sdk/types/serializers.kt | 402 + .../kotlin/sdk/types/tools.kt | 250 + ...kt => OldSchemaCallToolResultUtilsTest.kt} | 18 +- ...t.kt => OldSchemaRequestSerializerTest.kt} | 18 +- ...t.kt => OldSchemaToolSerializationTest.kt} | 17 +- .../{TypesTest.kt => OldSchemaTypesTest.kt} | 127 +- .../kotlin/sdk/TypesUtilTest.kt | 252 - ... => OldSchemaProgressNotificationsTest.kt} | 6 +- .../kotlin/sdk/types/CapabilitiesTest.kt | 636 ++ .../kotlin/sdk/types/CommonTypeTest.kt | 184 + .../kotlin/sdk/types/CompletionTest.kt | 227 + .../kotlin/sdk/types/ContentTest.kt | 301 + .../kotlin/sdk/types/ElicitationTest.kt | 206 + .../kotlin/sdk/types/InitializeTest.kt | 219 + .../kotlin/sdk/types/JsonRpcTest.kt | 401 + .../kotlin/sdk/types/LoggingTest.kt | 166 + .../kotlin/sdk/types/NotificationTest.kt | 296 + .../kotlin/sdk/types/PingRequestTest.kt | 95 + .../kotlin/sdk/types/PromptsTest.kt | 453 ++ .../kotlin/sdk/types/RequestTest.kt | 182 + .../kotlin/sdk/types/ResourcesTest.kt | 539 ++ .../kotlin/sdk/types/RootsTest.kt | 169 + .../kotlin/sdk/types/SamplingTest.kt | 258 + .../kotlin/sdk/types/ToolsTest.kt | 523 ++ kotlin-sdk-server/api/kotlin-sdk-server.api | 84 +- .../kotlin/sdk/server/Features.kt | 18 +- .../kotlin/sdk/server/SSEServerTransport.kt | 4 +- .../kotlin/sdk/server/Server.kt | 95 +- .../kotlin/sdk/server/ServerSession.kt | 84 +- .../kotlin/sdk/server/StdioServerTransport.kt | 2 +- .../WebSocketMcpKtorServerExtensions.kt | 4 +- .../{ClientTest.kt => OldSchemaClientTest.kt} | 26 +- ...OldSchemaAbstractPromptIntegrationTest.kt} | 107 +- ...dSchemaAbstractResourceIntegrationTest.kt} | 59 +- ...> OldSchemaAbstractToolIntegrationTest.kt} | 98 +- ...TestBase.kt => OldSchemaKotlinTestBase.kt} | 5 +- .../sse/OldSchemaPromptIntegrationTestSse.kt | 7 + .../OldSchemaResourceIntegrationTestSse.kt | 7 + .../sse/OldSchemaToolIntegrationTestSse.kt | 7 + .../kotlin/sse/PromptIntegrationTestSse.kt | 7 - .../kotlin/sse/ResourceIntegrationTestSse.kt | 7 - .../kotlin/sse/ToolIntegrationTestSse.kt | 7 - .../OldSchemaPromptIntegrationTestStdio.kt | 7 + .../OldSchemaResourceIntegrationTestStdio.kt | 7 + .../OldSchemaToolIntegrationTestStdio.kt | 7 + .../stdio/PromptIntegrationTestStdio.kt | 7 - .../stdio/ResourceIntegrationTestStdio.kt | 7 - .../kotlin/stdio/ToolIntegrationTestStdio.kt | 7 - ...t => OldSchemaWebSocketIntegrationTest.kt} | 13 +- ...=> OldSchemaAbstractSseIntegrationTest.kt} | 6 +- ...Test.kt => OldSchemaSseIntegrationTest.kt} | 9 +- ...SchemaAbstractKotlinClientTsServerTest.kt} | 5 +- .../sse/KotlinClientTsServerTestSse.kt | 4 +- ...maKotlinClientTsServerEdgeCasesTestSse.kt} | 15 +- ...=> OldSchemaKotlinServerForTsClientSse.kt} | 23 +- ...KotlinClientTsServerEdgeCasesTestStdio.kt} | 15 +- ...OldSchemaKotlinClientTsServerTestStdio.kt} | 4 +- ...=> OldSchemaAbstractServerFeaturesTest.kt} | 5 +- ...sTest.kt => OldSchemaServerPromptsTest.kt} | 14 +- ...est.kt => OldSchemaServerResourcesTest.kt} | 12 +- ...olsTest.kt => OldSchemaServerToolsTest.kt} | 14 +- .../sample/client/MCPClient.kt | 11 +- .../sample/server/server.kt | 2 +- .../test/kotlin/SseServerIntegrationTest.kt | 3 +- .../sample/server/McpWeatherServer.kt | 6 +- .../sample/client/ClientStdio.kt | 20 +- 102 files changed, 14358 insertions(+), 5540 deletions(-) rename kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/{MockMcp.kt => OldSchemaMockMcp.kt} (98%) rename kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/{StreamableHttpClientTest.kt => OldStreamableHttpClientTest.kt} (97%) create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/McpException.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PingRequest.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/content.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/initialize.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonUtils.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/logging.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/methods.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/notification.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt rename kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/{CallToolResultUtilsTest.kt => OldSchemaCallToolResultUtilsTest.kt} (76%) rename kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/{RequestSerializerTest.kt => OldSchemaRequestSerializerTest.kt} (94%) rename kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/{ToolSerializationTest.kt => OldSchemaToolSerializationTest.kt} (95%) rename kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/{TypesTest.kt => OldSchemaTypesTest.kt} (78%) delete mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/TypesUtilTest.kt rename kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/models/{ProgressNotificationsTest.kt => OldSchemaProgressNotificationsTest.kt} (88%) create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CapabilitiesTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CommonTypeTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CompletionTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ContentTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ElicitationTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/InitializeTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/JsonRpcTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/LoggingTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/NotificationTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PingRequestTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PromptsTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/RequestTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ResourcesTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/RootsTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/SamplingTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ToolsTest.kt rename kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/{ClientTest.kt => OldSchemaClientTest.kt} (97%) rename kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/{AbstractPromptIntegrationTest.kt => OldSchemaAbstractPromptIntegrationTest.kt} (88%) rename kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/{AbstractResourceIntegrationTest.kt => OldSchemaAbstractResourceIntegrationTest.kt} (83%) rename kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/{AbstractToolIntegrationTest.kt => OldSchemaAbstractToolIntegrationTest.kt} (87%) rename kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/{KotlinTestBase.kt => OldSchemaKotlinTestBase.kt} (97%) create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaPromptIntegrationTestSse.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaResourceIntegrationTestSse.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaToolIntegrationTestSse.kt delete mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/PromptIntegrationTestSse.kt delete mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/ResourceIntegrationTestSse.kt delete mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/ToolIntegrationTestSse.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaPromptIntegrationTestStdio.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaResourceIntegrationTestStdio.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaToolIntegrationTestStdio.kt delete mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/PromptIntegrationTestStdio.kt delete mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/ResourceIntegrationTestStdio.kt delete mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/ToolIntegrationTestStdio.kt rename kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/{WebSocketIntegrationTest.kt => OldSchemaWebSocketIntegrationTest.kt} (94%) rename kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/{AbstractSseIntegrationTest.kt => OldSchemaAbstractSseIntegrationTest.kt} (95%) rename kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/{SseIntegrationTest.kt => OldSchemaSseIntegrationTest.kt} (93%) rename kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/{AbstractKotlinClientTsServerTest.kt => OldSchemaAbstractKotlinClientTsServerTest.kt} (95%) rename kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/{KotlinClientTsServerEdgeCasesTestSse.kt => OldSchemaKotlinClientTsServerEdgeCasesTestSse.kt} (92%) rename kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/{KotlinServerForTsClientSse.kt => OldSchemaKotlinServerForTsClientSse.kt} (96%) rename kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/{KotlinClientTsServerEdgeCasesTestStdio.kt => OldSchemaKotlinClientTsServerEdgeCasesTestStdio.kt} (91%) rename kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/{KotlinClientTsServerTestStdio.kt => OldSchemaKotlinClientTsServerTestStdio.kt} (84%) rename kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/{AbstractServerFeaturesTest.kt => OldSchemaAbstractServerFeaturesTest.kt} (88%) rename kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/{ServerPromptsTest.kt => OldSchemaServerPromptsTest.kt} (87%) rename kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/{ServerResourcesTest.kt => OldSchemaServerResourcesTest.kt} (91%) rename kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/{ServerToolsTest.kt => OldSchemaServerToolsTest.kt} (86%) diff --git a/build.gradle.kts b/build.gradle.kts index 616b514..f7f3ef4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -38,8 +38,11 @@ kover { reports { filters { includes.classes("io.modelcontextprotocol.kotlin.sdk.*") - excludes.classes("io.modelcontextprotocol.kotlin.sdk.models.*") // temporary - excludes.classes("io.modelcontextprotocol.kotlin.sdk.models.infrastructure.*") // generated + excludes { + annotatedBy("kotlin.Deprecated") + classes("io.modelcontextprotocol.kotlin.sdk.models.*") // temporary + classes("io.modelcontextprotocol.kotlin.sdk.models.infrastructure.*") // generated + } } total { log { diff --git a/kotlin-sdk-client/api/kotlin-sdk-client.api b/kotlin-sdk-client/api/kotlin-sdk-client.api index 46a7b60..7a1e0fa 100644 --- a/kotlin-sdk-client/api/kotlin-sdk-client.api +++ b/kotlin-sdk-client/api/kotlin-sdk-client.api @@ -1,53 +1,53 @@ public class io/modelcontextprotocol/kotlin/sdk/client/Client : io/modelcontextprotocol/kotlin/sdk/shared/Protocol { - public fun (Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lio/modelcontextprotocol/kotlin/sdk/client/ClientOptions;)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lio/modelcontextprotocol/kotlin/sdk/client/ClientOptions;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/Implementation;Lio/modelcontextprotocol/kotlin/sdk/client/ClientOptions;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/Implementation;Lio/modelcontextprotocol/kotlin/sdk/client/ClientOptions;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun addRoot (Ljava/lang/String;Ljava/lang/String;)V public final fun addRoots (Ljava/util/List;)V protected final fun assertCapability (Ljava/lang/String;Ljava/lang/String;)V - protected fun assertCapabilityForMethod (Lio/modelcontextprotocol/kotlin/sdk/Method;)V - protected fun assertNotificationCapability (Lio/modelcontextprotocol/kotlin/sdk/Method;)V - public fun assertRequestHandlerCapability (Lio/modelcontextprotocol/kotlin/sdk/Method;)V - public final fun callTool (Lio/modelcontextprotocol/kotlin/sdk/CallToolRequest;ZLio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun callTool (Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;ZLio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun callTool$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/CallToolRequest;ZLio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public static synthetic fun callTool$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;ZLio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public final fun complete (Lio/modelcontextprotocol/kotlin/sdk/CompleteRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun complete$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/CompleteRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + protected fun assertCapabilityForMethod (Lio/modelcontextprotocol/kotlin/sdk/types/Method;)V + protected fun assertNotificationCapability (Lio/modelcontextprotocol/kotlin/sdk/types/Method;)V + public fun assertRequestHandlerCapability (Lio/modelcontextprotocol/kotlin/sdk/types/Method;)V + public final fun callTool (Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun callTool (Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun callTool$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun callTool$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun complete (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun complete$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public fun connect (Lio/modelcontextprotocol/kotlin/sdk/shared/Transport;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun getPrompt (Lio/modelcontextprotocol/kotlin/sdk/GetPromptRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun getPrompt$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/GetPromptRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public final fun getServerCapabilities ()Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities; + public final fun getPrompt (Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun getPrompt$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun getServerCapabilities ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities; public final fun getServerInstructions ()Ljava/lang/String; - public final fun getServerVersion ()Lio/modelcontextprotocol/kotlin/sdk/Implementation; - public final fun listPrompts (Lio/modelcontextprotocol/kotlin/sdk/ListPromptsRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun listPrompts$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/ListPromptsRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public final fun listResourceTemplates (Lio/modelcontextprotocol/kotlin/sdk/ListResourceTemplatesRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun listResourceTemplates$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/ListResourceTemplatesRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public final fun listResources (Lio/modelcontextprotocol/kotlin/sdk/ListResourcesRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun listResources$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/ListResourcesRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public final fun listTools (Lio/modelcontextprotocol/kotlin/sdk/ListToolsRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun listTools$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/ListToolsRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun getServerVersion ()Lio/modelcontextprotocol/kotlin/sdk/types/Implementation; + public final fun listPrompts (Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun listPrompts$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun listResourceTemplates (Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun listResourceTemplates$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun listResources (Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun listResources$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun listTools (Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun listTools$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun ping (Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun ping$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public final fun readResource (Lio/modelcontextprotocol/kotlin/sdk/ReadResourceRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun readResource$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/ReadResourceRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun readResource (Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun readResource$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun removeRoot (Ljava/lang/String;)Z public final fun removeRoots (Ljava/util/List;)I public final fun sendRootsListChanged (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun setElicitationHandler (Lkotlin/jvm/functions/Function1;)V - public final fun setLoggingLevel (Lio/modelcontextprotocol/kotlin/sdk/LoggingLevel;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun setLoggingLevel$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/LoggingLevel;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public final fun subscribeResource (Lio/modelcontextprotocol/kotlin/sdk/SubscribeRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun subscribeResource$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/SubscribeRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public final fun unsubscribeResource (Lio/modelcontextprotocol/kotlin/sdk/UnsubscribeRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun unsubscribeResource$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/UnsubscribeRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun setLoggingLevel (Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun setLoggingLevel$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun subscribeResource (Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun subscribeResource$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun unsubscribeResource (Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + 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/ClientOptions : io/modelcontextprotocol/kotlin/sdk/shared/ProtocolOptions { public fun ()V - public fun (Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities;Z)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getCapabilities ()Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;Z)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getCapabilities ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities; } public final class io/modelcontextprotocol/kotlin/sdk/client/KtorClientKt { @@ -61,14 +61,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/client/SseClientTransport public synthetic fun (Lio/ktor/client/HttpClient;Ljava/lang/String;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun (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/JSONRPCMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun send (Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage;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 (Lkotlinx/io/Source;Lkotlinx/io/Sink;)V public fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun send (Lio/modelcontextprotocol/kotlin/sdk/JSONRPCMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun send (Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun start (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } @@ -78,9 +78,9 @@ 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 final fun send (Lio/modelcontextprotocol/kotlin/sdk/JSONRPCMessage;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun send (Lio/modelcontextprotocol/kotlin/sdk/JSONRPCMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun send$default (Lio/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport;Lio/modelcontextprotocol/kotlin/sdk/JSONRPCMessage;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)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; public final fun terminateSession (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt index 8eb8957..f0283c9 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt @@ -1,63 +1,59 @@ package io.modelcontextprotocol.kotlin.sdk.client import io.github.oshai.kotlinlogging.KotlinLogging -import io.modelcontextprotocol.kotlin.sdk.CallToolRequest -import io.modelcontextprotocol.kotlin.sdk.CallToolResult -import io.modelcontextprotocol.kotlin.sdk.CallToolResultBase -import io.modelcontextprotocol.kotlin.sdk.ClientCapabilities -import io.modelcontextprotocol.kotlin.sdk.CompatibilityCallToolResult -import io.modelcontextprotocol.kotlin.sdk.CompleteRequest -import io.modelcontextprotocol.kotlin.sdk.CompleteResult -import io.modelcontextprotocol.kotlin.sdk.CreateElicitationRequest -import io.modelcontextprotocol.kotlin.sdk.CreateElicitationResult -import io.modelcontextprotocol.kotlin.sdk.EmptyRequestResult -import io.modelcontextprotocol.kotlin.sdk.GetPromptRequest -import io.modelcontextprotocol.kotlin.sdk.GetPromptResult -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.InitializeRequest -import io.modelcontextprotocol.kotlin.sdk.InitializeResult -import io.modelcontextprotocol.kotlin.sdk.InitializedNotification -import io.modelcontextprotocol.kotlin.sdk.LATEST_PROTOCOL_VERSION -import io.modelcontextprotocol.kotlin.sdk.ListPromptsRequest -import io.modelcontextprotocol.kotlin.sdk.ListPromptsResult -import io.modelcontextprotocol.kotlin.sdk.ListResourceTemplatesRequest -import io.modelcontextprotocol.kotlin.sdk.ListResourceTemplatesResult -import io.modelcontextprotocol.kotlin.sdk.ListResourcesRequest -import io.modelcontextprotocol.kotlin.sdk.ListResourcesResult -import io.modelcontextprotocol.kotlin.sdk.ListRootsRequest -import io.modelcontextprotocol.kotlin.sdk.ListRootsResult -import io.modelcontextprotocol.kotlin.sdk.ListToolsRequest -import io.modelcontextprotocol.kotlin.sdk.ListToolsResult -import io.modelcontextprotocol.kotlin.sdk.LoggingLevel -import io.modelcontextprotocol.kotlin.sdk.LoggingMessageNotification.SetLevelRequest -import io.modelcontextprotocol.kotlin.sdk.Method -import io.modelcontextprotocol.kotlin.sdk.PingRequest -import io.modelcontextprotocol.kotlin.sdk.ReadResourceRequest -import io.modelcontextprotocol.kotlin.sdk.ReadResourceResult -import io.modelcontextprotocol.kotlin.sdk.Root -import io.modelcontextprotocol.kotlin.sdk.RootsListChangedNotification -import io.modelcontextprotocol.kotlin.sdk.SUPPORTED_PROTOCOL_VERSIONS -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities -import io.modelcontextprotocol.kotlin.sdk.SubscribeRequest -import io.modelcontextprotocol.kotlin.sdk.UnsubscribeRequest import io.modelcontextprotocol.kotlin.sdk.shared.Protocol import io.modelcontextprotocol.kotlin.sdk.shared.ProtocolOptions import io.modelcontextprotocol.kotlin.sdk.shared.RequestOptions import io.modelcontextprotocol.kotlin.sdk.shared.Transport +import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest +import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult +import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.CompleteRequest +import io.modelcontextprotocol.kotlin.sdk.types.CompleteResult +import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequest +import io.modelcontextprotocol.kotlin.sdk.types.ElicitResult +import io.modelcontextprotocol.kotlin.sdk.types.EmptyResult +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest +import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.InitializeResult +import io.modelcontextprotocol.kotlin.sdk.types.InitializedNotification +import io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION +import io.modelcontextprotocol.kotlin.sdk.types.ListPromptsRequest +import io.modelcontextprotocol.kotlin.sdk.types.ListPromptsResult +import io.modelcontextprotocol.kotlin.sdk.types.ListResourceTemplatesRequest +import io.modelcontextprotocol.kotlin.sdk.types.ListResourceTemplatesResult +import io.modelcontextprotocol.kotlin.sdk.types.ListResourcesRequest +import io.modelcontextprotocol.kotlin.sdk.types.ListResourcesResult +import io.modelcontextprotocol.kotlin.sdk.types.ListRootsRequest +import io.modelcontextprotocol.kotlin.sdk.types.ListRootsResult +import io.modelcontextprotocol.kotlin.sdk.types.ListToolsRequest +import io.modelcontextprotocol.kotlin.sdk.types.ListToolsResult +import io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel +import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.PingRequest +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult +import io.modelcontextprotocol.kotlin.sdk.types.RequestMeta +import io.modelcontextprotocol.kotlin.sdk.types.Root +import io.modelcontextprotocol.kotlin.sdk.types.RootsListChangedNotification +import io.modelcontextprotocol.kotlin.sdk.types.SUPPORTED_PROTOCOL_VERSIONS +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.SetLevelRequest +import io.modelcontextprotocol.kotlin.sdk.types.SetLevelRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.SubscribeRequest +import io.modelcontextprotocol.kotlin.sdk.types.UnsubscribeRequest +import io.modelcontextprotocol.kotlin.sdk.types.toJson import kotlinx.atomicfu.atomic import kotlinx.atomicfu.getAndUpdate import kotlinx.atomicfu.update import kotlinx.collections.immutable.minus import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.toPersistentSet -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonNull import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.add -import kotlinx.serialization.json.buildJsonArray -import kotlinx.serialization.json.buildJsonObject import kotlin.coroutines.cancellation.CancellationException private val logger = KotlinLogging.logger {} @@ -152,9 +148,11 @@ public open class Client(private val clientInfo: Implementation, options: Client try { val message = InitializeRequest( - protocolVersion = LATEST_PROTOCOL_VERSION, - capabilities = capabilities, - clientInfo = clientInfo, + InitializeRequestParams( + protocolVersion = LATEST_PROTOCOL_VERSION, + capabilities = capabilities, + clientInfo = clientInfo, + ), ) val result = request(message) @@ -293,8 +291,7 @@ public open class Client(private val clientInfo: Implementation, options: Client * @param options Optional request options. * @throws IllegalStateException If the server does not support the ping method (unlikely). */ - public suspend fun ping(options: RequestOptions? = null): EmptyRequestResult = - request(PingRequest(), options) + public suspend fun ping(options: RequestOptions? = null): EmptyResult = request(PingRequest(), options) /** * Sends a completion request to the server, typically to generate or complete some content. @@ -314,8 +311,8 @@ public open class Client(private val clientInfo: Implementation, options: Client * @param options Optional request options. * @throws IllegalStateException If the server does not support logging. */ - public suspend fun setLoggingLevel(level: LoggingLevel, options: RequestOptions? = null): EmptyRequestResult = - request(SetLevelRequest(level), options) + public suspend fun setLoggingLevel(level: LoggingLevel, options: RequestOptions? = null): EmptyResult = + request(SetLevelRequest(SetLevelRequestParams(level)), options) /** * Retrieves a prompt by name from the server. @@ -387,10 +384,8 @@ public open class Client(private val clientInfo: Implementation, options: Client * @param options Optional request options. * @throws IllegalStateException If the server does not support resource subscriptions. */ - public suspend fun subscribeResource( - request: SubscribeRequest, - options: RequestOptions? = null, - ): EmptyRequestResult = request(request, options) + public suspend fun subscribeResource(request: SubscribeRequest, options: RequestOptions? = null): EmptyResult = + request(request, options) /** * Unsubscribes from resource changes on the server. @@ -399,10 +394,8 @@ public open class Client(private val clientInfo: Implementation, options: Client * @param options Optional request options. * @throws IllegalStateException If the server does not support resource subscriptions. */ - public suspend fun unsubscribeResource( - request: UnsubscribeRequest, - options: RequestOptions? = null, - ): EmptyRequestResult = request(request, options) + public suspend fun unsubscribeResource(request: UnsubscribeRequest, options: RequestOptions? = null): EmptyResult = + request(request, options) /** * Calls a tool on the server by name, passing the specified arguments and metadata. @@ -413,7 +406,6 @@ public open class Client(private val clientInfo: Implementation, options: Client * - Optional prefix: dot-separated labels followed by slash (e.g., "api.example.com/") * - Name: alphanumeric start/end, may contain hyphens, underscores, dots, alphanumerics * - Reserved prefixes starting with "mcp" or "modelcontextprotocol" are forbidden - * @param compatibility Whether to use compatibility mode for older protocol versions. * @param options Optional request options. * @return The result of the tool call, or `null` if none. * @throws IllegalStateException If the server does not support tools. @@ -422,40 +414,33 @@ public open class Client(private val clientInfo: Implementation, options: Client name: String, arguments: Map, meta: Map = emptyMap(), - compatibility: Boolean = false, options: RequestOptions? = null, - ): CallToolResultBase? { + ): CallToolResult { validateMetaKeys(meta.keys) - val jsonArguments = convertToJsonMap(arguments) - val jsonMeta = convertToJsonMap(meta) + val jsonArguments = arguments.toJson() + val jsonMeta = meta.toJson() val request = CallToolRequest( - name = name, - arguments = JsonObject(jsonArguments), - _meta = JsonObject(jsonMeta), + CallToolRequestParams( + name = name, + arguments = JsonObject(jsonArguments), + meta = RequestMeta(JsonObject(jsonMeta)), + ), ) - return callTool(request, compatibility, options) + return callTool(request, options) } /** * Calls a tool on the server using a [CallToolRequest] object. * * @param request The request object containing the tool name and arguments. - * @param compatibility Whether to use compatibility mode for older protocol versions. * @param options Optional request options. * @return The result of the tool call, or `null` if none. * @throws IllegalStateException If the server does not support tools. */ - public suspend fun callTool( - request: CallToolRequest, - compatibility: Boolean = false, - options: RequestOptions? = null, - ): CallToolResultBase? = if (compatibility) { - request(request, options) - } else { - request(request, options) - } + public suspend fun callTool(request: CallToolRequest, options: RequestOptions? = null): CallToolResult = + request(request, options) /** * Lists all available tools on the server. @@ -570,14 +555,14 @@ public open class Client(private val clientInfo: Implementation, options: Client * @param handler The elicitation handler. * @throws IllegalStateException if the client does not support elicitation. */ - public fun setElicitationHandler(handler: (CreateElicitationRequest) -> CreateElicitationResult) { + public fun setElicitationHandler(handler: (ElicitRequest) -> ElicitResult) { if (capabilities.elicitation == null) { logger.error { "Failed to set elicitation handler: Client does not support elicitation" } throw IllegalStateException("Client does not support elicitation.") } logger.info { "Setting the elicitation handler" } - setRequestHandler(Method.Defined.ElicitationCreate) { request, _ -> + setRequestHandler(Method.Defined.ElicitationCreate) { request, _ -> handler(request) } } @@ -637,67 +622,4 @@ public open class Client(private val clientInfo: Implementation, options: Client } } } - - private fun convertToJsonMap(map: Map): Map = map.mapValues { (key, value) -> - try { - convertToJsonElement(value) - } catch (e: Exception) { - logger.warn { "Failed to convert value for key '$key': ${e.message}. Using string representation." } - JsonPrimitive(value.toString()) - } - } - - @OptIn(ExperimentalUnsignedTypes::class, ExperimentalSerializationApi::class) - private fun convertToJsonElement(value: Any?): JsonElement = when (value) { - null -> JsonNull - - is JsonElement -> value - - is String -> JsonPrimitive(value) - - is Number -> JsonPrimitive(value) - - is Boolean -> JsonPrimitive(value) - - is Char -> JsonPrimitive(value.toString()) - - is Enum<*> -> JsonPrimitive(value.name) - - is Map<*, *> -> buildJsonObject { value.forEach { (k, v) -> put(k.toString(), convertToJsonElement(v)) } } - - is Collection<*> -> buildJsonArray { value.forEach { add(convertToJsonElement(it)) } } - - is Array<*> -> buildJsonArray { value.forEach { add(convertToJsonElement(it)) } } - - // Primitive arrays - is IntArray -> buildJsonArray { value.forEach { add(it) } } - - is LongArray -> buildJsonArray { value.forEach { add(it) } } - - is FloatArray -> buildJsonArray { value.forEach { add(it) } } - - is DoubleArray -> buildJsonArray { value.forEach { add(it) } } - - is BooleanArray -> buildJsonArray { value.forEach { add(it) } } - - is ShortArray -> buildJsonArray { value.forEach { add(it) } } - - is ByteArray -> buildJsonArray { value.forEach { add(it) } } - - is CharArray -> buildJsonArray { value.forEach { add(it.toString()) } } - - // Unsigned arrays - is UIntArray -> buildJsonArray { value.forEach { add(JsonPrimitive(it)) } } - - is ULongArray -> buildJsonArray { value.forEach { add(JsonPrimitive(it)) } } - - is UShortArray -> buildJsonArray { value.forEach { add(JsonPrimitive(it)) } } - - is UByteArray -> buildJsonArray { value.forEach { add(JsonPrimitive(it)) } } - - else -> { - logger.debug { "Converting unknown type ${value::class} to string: $value" } - JsonPrimitive(value.toString()) - } - } } diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/KtorClient.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/KtorClient.kt index ccc1496..fe92e15 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/KtorClient.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/KtorClient.kt @@ -2,9 +2,9 @@ package io.modelcontextprotocol.kotlin.sdk.client import io.ktor.client.HttpClient import io.ktor.client.request.HttpRequestBuilder -import io.modelcontextprotocol.kotlin.sdk.Implementation import io.modelcontextprotocol.kotlin.sdk.LIB_VERSION import io.modelcontextprotocol.kotlin.sdk.shared.IMPLEMENTATION_NAME +import io.modelcontextprotocol.kotlin.sdk.types.Implementation import kotlin.time.Duration /** diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt index c0bcd54..f2b7d3d 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt @@ -14,9 +14,9 @@ import io.ktor.http.Url import io.ktor.http.append import io.ktor.http.isSuccess import io.ktor.http.protocolWithAuthority -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport -import io.modelcontextprotocol.kotlin.sdk.shared.McpJson +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.McpJson import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineName diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt index 583cec6..6e272eb 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt @@ -1,11 +1,11 @@ package io.modelcontextprotocol.kotlin.sdk.client import io.github.oshai.kotlinlogging.KotlinLogging -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage 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.serializeMessage +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt index f0939a3..a402874 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt @@ -21,13 +21,13 @@ import io.ktor.http.HttpStatusCode import io.ktor.http.contentType import io.ktor.http.isSuccess import io.ktor.utils.io.readUTF8Line -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage -import io.modelcontextprotocol.kotlin.sdk.JSONRPCNotification -import io.modelcontextprotocol.kotlin.sdk.JSONRPCRequest -import io.modelcontextprotocol.kotlin.sdk.JSONRPCResponse -import io.modelcontextprotocol.kotlin.sdk.RequestId import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport -import io.modelcontextprotocol.kotlin.sdk.shared.McpJson +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse +import io.modelcontextprotocol.kotlin.sdk.types.McpJson +import io.modelcontextprotocol.kotlin.sdk.types.RequestId import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpMcpKtorClientExtensions.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpMcpKtorClientExtensions.kt index 1a600a3..b64a220 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpMcpKtorClientExtensions.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpMcpKtorClientExtensions.kt @@ -2,9 +2,9 @@ package io.modelcontextprotocol.kotlin.sdk.client import io.ktor.client.HttpClient import io.ktor.client.request.HttpRequestBuilder -import io.modelcontextprotocol.kotlin.sdk.Implementation import io.modelcontextprotocol.kotlin.sdk.LIB_VERSION import io.modelcontextprotocol.kotlin.sdk.shared.IMPLEMENTATION_NAME +import io.modelcontextprotocol.kotlin.sdk.types.Implementation import kotlin.time.Duration /** diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketMcpKtorClientExtensions.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketMcpKtorClientExtensions.kt index 8647953..a0df47c 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketMcpKtorClientExtensions.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketMcpKtorClientExtensions.kt @@ -3,9 +3,9 @@ package io.modelcontextprotocol.kotlin.sdk.client import io.github.oshai.kotlinlogging.KotlinLogging import io.ktor.client.HttpClient import io.ktor.client.request.HttpRequestBuilder -import io.modelcontextprotocol.kotlin.sdk.Implementation import io.modelcontextprotocol.kotlin.sdk.LIB_VERSION import io.modelcontextprotocol.kotlin.sdk.shared.IMPLEMENTATION_NAME +import io.modelcontextprotocol.kotlin.sdk.types.Implementation private val logger = KotlinLogging.logger {} diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaMockMcp.kt similarity index 98% rename from kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt rename to kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaMockMcp.kt index eef77f1..eb2760b 100644 --- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt +++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaMockMcp.kt @@ -7,8 +7,8 @@ import io.ktor.http.ContentType import io.ktor.http.HttpMethod import io.ktor.http.HttpStatusCode import io.ktor.sse.ServerSentEvent -import io.modelcontextprotocol.kotlin.sdk.JSONRPCRequest -import io.modelcontextprotocol.kotlin.sdk.RequestId +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest +import io.modelcontextprotocol.kotlin.sdk.types.RequestId import kotlinx.coroutines.flow.Flow import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldStreamableHttpClientTest.kt similarity index 97% rename from kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt rename to kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldStreamableHttpClientTest.kt index 010b79a..33897e6 100644 --- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt +++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldStreamableHttpClientTest.kt @@ -6,6 +6,7 @@ import io.ktor.http.HttpMethod import io.ktor.http.HttpStatusCode import io.ktor.sse.ServerSentEvent import io.modelcontextprotocol.kotlin.sdk.ClientCapabilities +import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject import io.modelcontextprotocol.kotlin.sdk.Implementation import io.modelcontextprotocol.kotlin.sdk.Tool import kotlinx.coroutines.delay @@ -30,7 +31,7 @@ import kotlin.uuid.Uuid @OptIn(ExperimentalUuidApi::class) @TestInstance(TestInstance.Lifecycle.PER_CLASS) @Suppress("LongMethod") -internal class StreamableHttpClientTest : AbstractStreamableHttpClientTest() { +internal class OldStreamableHttpClientTest : AbstractStreamableHttpClientTest() { @Test fun `test streamableHttpClient`() = runBlocking { @@ -148,6 +149,7 @@ internal class StreamableHttpClientTest : AbstractStreamableHttpClientTest() { required = listOf("temperature"), ), annotations = null, + _meta = EmptyJsonObject, ) mockMcp.mockUnsubscribeRequest(sessionId = sessionId) diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index 0c56876..1b5c44c 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -1,158 +1,8 @@ -public final class io/modelcontextprotocol/kotlin/sdk/Annotations { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/Annotations$Companion; - public fun (Ljava/util/List;Ljava/lang/String;Ljava/lang/Double;)V - public final fun component1 ()Ljava/util/List; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/Double; - public final fun copy (Ljava/util/List;Ljava/lang/String;Ljava/lang/Double;)Lio/modelcontextprotocol/kotlin/sdk/Annotations; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/Annotations;Ljava/util/List;Ljava/lang/String;Ljava/lang/Double;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/Annotations; - public fun equals (Ljava/lang/Object;)Z - public final fun getAudience ()Ljava/util/List; - public final fun getLastModified ()Ljava/lang/String; - public final fun getPriority ()Ljava/lang/Double; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; +public final class io/modelcontextprotocol/kotlin/sdk/CallToolResult { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/CallToolResult; } -public final synthetic class io/modelcontextprotocol/kotlin/sdk/Annotations$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/Annotations$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/Annotations; - 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/Annotations;)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/Annotations$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/AudioContent : io/modelcontextprotocol/kotlin/sdk/PromptMessageContentMultimodal { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/AudioContent$Companion; - public static final field TYPE Ljava/lang/String; - public fun (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Annotations;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Annotations;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/Annotations; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Annotations;)Lio/modelcontextprotocol/kotlin/sdk/AudioContent; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/AudioContent;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Annotations;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/AudioContent; - public fun equals (Ljava/lang/Object;)Z - public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/Annotations; - public final fun getData ()Ljava/lang/String; - public final fun getMimeType ()Ljava/lang/String; - public fun getType ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/AudioContent$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/AudioContent$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/AudioContent; - 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/AudioContent;)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/AudioContent$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/BlobResourceContents : io/modelcontextprotocol/kotlin/sdk/ResourceContents { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/BlobResourceContents$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/BlobResourceContents; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/BlobResourceContents;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/BlobResourceContents; - public fun equals (Ljava/lang/Object;)Z - public final fun getBlob ()Ljava/lang/String; - public fun getMimeType ()Ljava/lang/String; - public fun getUri ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/BlobResourceContents$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/BlobResourceContents$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/BlobResourceContents; - 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/BlobResourceContents;)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/BlobResourceContents$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/CallToolRequest : io/modelcontextprotocol/kotlin/sdk/ClientRequest, io/modelcontextprotocol/kotlin/sdk/WithMeta { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/CallToolRequest$Companion; - public fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;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 component3 ()Lkotlinx/serialization/json/JsonObject; - public final fun copy (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/CallToolRequest; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/CallToolRequest;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/CallToolRequest; - public fun equals (Ljava/lang/Object;)Z - public final fun getArguments ()Lkotlinx/serialization/json/JsonObject; - public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/Method; - public final fun getName ()Ljava/lang/String; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/CallToolRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/CallToolRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/CallToolRequest; - 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/CallToolRequest;)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/CallToolRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/CallToolResult : io/modelcontextprotocol/kotlin/sdk/CallToolResultBase { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/CallToolResult$Companion; - public fun (Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun getContent ()Ljava/util/List; - public fun getStructuredContent ()Lkotlinx/serialization/json/JsonObject; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; - public fun isError ()Ljava/lang/Boolean; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/CallToolResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/CallToolResult$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/CallToolResult; - 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/CallToolResult;)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/CallToolResult$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public abstract interface class io/modelcontextprotocol/kotlin/sdk/CallToolResultBase : io/modelcontextprotocol/kotlin/sdk/ServerResult { +public abstract interface class io/modelcontextprotocol/kotlin/sdk/CallToolResultBase { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/CallToolResultBase$Companion; public abstract fun getContent ()Ljava/util/List; public abstract fun getStructuredContent ()Lkotlinx/serialization/json/JsonObject; @@ -167,157 +17,27 @@ public final class io/modelcontextprotocol/kotlin/sdk/CallToolResultBase$Default public static fun isError (Lio/modelcontextprotocol/kotlin/sdk/CallToolResultBase;)Ljava/lang/Boolean; } -public final class io/modelcontextprotocol/kotlin/sdk/CancelledNotification : io/modelcontextprotocol/kotlin/sdk/ClientNotification, io/modelcontextprotocol/kotlin/sdk/ServerNotification { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/CancelledNotification$Companion; - public fun (Lio/modelcontextprotocol/kotlin/sdk/CancelledNotification$Params;)V - public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/CancelledNotification$Params; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/CancelledNotification$Params;)Lio/modelcontextprotocol/kotlin/sdk/CancelledNotification; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/CancelledNotification;Lio/modelcontextprotocol/kotlin/sdk/CancelledNotification$Params;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/CancelledNotification; - public fun equals (Ljava/lang/Object;)Z - public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/Method; - public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/CancelledNotification$Params; - public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/NotificationParams; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/CancelledNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/CancelledNotification$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/CancelledNotification; - 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/CancelledNotification;)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/CancelledNotification$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/CancelledNotification$Params : io/modelcontextprotocol/kotlin/sdk/NotificationParams { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/CancelledNotification$Params$Companion; - public fun (Lio/modelcontextprotocol/kotlin/sdk/RequestId;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/RequestId;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/RequestId; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Lkotlinx/serialization/json/JsonObject; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/RequestId;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/CancelledNotification$Params; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/CancelledNotification$Params;Lio/modelcontextprotocol/kotlin/sdk/RequestId;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/CancelledNotification$Params; - public fun equals (Ljava/lang/Object;)Z - public final fun getReason ()Ljava/lang/String; - public final fun getRequestId ()Lio/modelcontextprotocol/kotlin/sdk/RequestId; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/CancelledNotification$Params$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/CancelledNotification$Params$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/CancelledNotification$Params; - 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/CancelledNotification$Params;)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/CancelledNotification$Params$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final class io/modelcontextprotocol/kotlin/sdk/CancelledNotification { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/CancelledNotification; + public final fun Params (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotificationParams; + public static synthetic fun Params$default (Lio/modelcontextprotocol/kotlin/sdk/CancelledNotification;Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotificationParams; } public final class io/modelcontextprotocol/kotlin/sdk/ClientCapabilities { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities$Companion; - public fun ()V - public fun (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lkotlinx/serialization/json/JsonObject; - public final fun component2 ()Lkotlinx/serialization/json/JsonObject; - public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities$Roots; - public final fun component4 ()Lkotlinx/serialization/json/JsonObject; - public final fun copy (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities; - public fun equals (Ljava/lang/Object;)Z - public final fun getElicitation ()Lkotlinx/serialization/json/JsonObject; - public final fun getExperimental ()Lkotlinx/serialization/json/JsonObject; - public final fun getRoots ()Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities$Roots; - public final fun getSampling ()Lkotlinx/serialization/json/JsonObject; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities; + public final fun Roots (Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots; + public static synthetic fun Roots$default (Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots; } -public final synthetic class io/modelcontextprotocol/kotlin/sdk/ClientCapabilities$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities; - 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/ClientCapabilities;)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/ClientCapabilities$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ClientCapabilities$Roots { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities$Roots$Companion; - public fun (Ljava/lang/Boolean;)V - public final fun component1 ()Ljava/lang/Boolean; - public final fun copy (Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities$Roots; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities$Roots;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities$Roots; - public fun equals (Ljava/lang/Object;)Z - public final fun getListChanged ()Ljava/lang/Boolean; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/ClientCapabilities$Roots$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities$Roots$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities$Roots; - 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/ClientCapabilities$Roots;)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/ClientCapabilities$Roots$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public abstract interface class io/modelcontextprotocol/kotlin/sdk/ClientNotification : io/modelcontextprotocol/kotlin/sdk/Notification { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ClientNotification$Companion; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ClientNotification$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public abstract interface class io/modelcontextprotocol/kotlin/sdk/ClientRequest : io/modelcontextprotocol/kotlin/sdk/Request { -} - -public abstract interface class io/modelcontextprotocol/kotlin/sdk/ClientResult : io/modelcontextprotocol/kotlin/sdk/RequestResult { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ClientResult$Companion; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ClientResult$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/CompatibilityCallToolResult : io/modelcontextprotocol/kotlin/sdk/CallToolResultBase { +public final class io/modelcontextprotocol/kotlin/sdk/CompatibilityCallToolResult { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/CompatibilityCallToolResult$Companion; public fun (Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)V public synthetic fun (Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun getContent ()Ljava/util/List; - public fun getStructuredContent ()Lkotlinx/serialization/json/JsonObject; + public final fun getContent ()Ljava/util/List; + public final fun getStructuredContent ()Lkotlinx/serialization/json/JsonObject; public final fun getToolResult ()Lkotlinx/serialization/json/JsonObject; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; - public fun isError ()Ljava/lang/Boolean; + public final fun get_meta ()Lkotlinx/serialization/json/JsonObject; + public final fun isError ()Ljava/lang/Boolean; } public final synthetic class io/modelcontextprotocol/kotlin/sdk/CompatibilityCallToolResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { @@ -335,460 +55,52 @@ public final class io/modelcontextprotocol/kotlin/sdk/CompatibilityCallToolResul public final fun serializer ()Lkotlinx/serialization/KSerializer; } -public final class io/modelcontextprotocol/kotlin/sdk/CompleteRequest : io/modelcontextprotocol/kotlin/sdk/ClientRequest, io/modelcontextprotocol/kotlin/sdk/WithMeta { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/CompleteRequest$Companion; - public fun (Lio/modelcontextprotocol/kotlin/sdk/Reference;Lio/modelcontextprotocol/kotlin/sdk/CompleteRequest$Argument;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/Reference;Lio/modelcontextprotocol/kotlin/sdk/CompleteRequest$Argument;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/Reference; - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/CompleteRequest$Argument; - public final fun component3 ()Lkotlinx/serialization/json/JsonObject; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/Reference;Lio/modelcontextprotocol/kotlin/sdk/CompleteRequest$Argument;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/CompleteRequest; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/CompleteRequest;Lio/modelcontextprotocol/kotlin/sdk/Reference;Lio/modelcontextprotocol/kotlin/sdk/CompleteRequest$Argument;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/CompleteRequest; - public fun equals (Ljava/lang/Object;)Z - public final fun getArgument ()Lio/modelcontextprotocol/kotlin/sdk/CompleteRequest$Argument; - public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/Method; - public final fun getRef ()Lio/modelcontextprotocol/kotlin/sdk/Reference; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; +public final class io/modelcontextprotocol/kotlin/sdk/CompleteRequest { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/CompleteRequest; + public final fun Argument (Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Argument; } -public final synthetic class io/modelcontextprotocol/kotlin/sdk/CompleteRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/CompleteRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/CompleteRequest; - 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/CompleteRequest;)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/CompleteResult { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/CompleteResult; + public final fun Completion (Ljava/util/List;Ljava/lang/Integer;Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult$Completion; + public static synthetic fun Completion$default (Lio/modelcontextprotocol/kotlin/sdk/CompleteResult;Ljava/util/List;Ljava/lang/Integer;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult$Completion; } -public final class io/modelcontextprotocol/kotlin/sdk/CompleteRequest$Argument { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/CompleteRequest$Argument$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/CompleteRequest$Argument; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/CompleteRequest$Argument;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/CompleteRequest$Argument; - public fun equals (Ljava/lang/Object;)Z - public final fun getName ()Ljava/lang/String; - public final fun getValue ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; +public final class io/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest; + public final fun RequestedSchema (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema; + public static synthetic fun RequestedSchema$default (Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema; } -public final synthetic class io/modelcontextprotocol/kotlin/sdk/CompleteRequest$Argument$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/CompleteRequest$Argument$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/CompleteRequest$Argument; - 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/CompleteRequest$Argument;)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/CreateElicitationResult { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationResult; } -public final class io/modelcontextprotocol/kotlin/sdk/CompleteRequest$Argument$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final class io/modelcontextprotocol/kotlin/sdk/CreateElicitationResult$Action { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationResult$Action; + public final fun getAccept ()Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Action; + public final fun getCancel ()Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Action; + public final fun getDecline ()Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Action; } -public final class io/modelcontextprotocol/kotlin/sdk/CompleteRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/CompleteResult : io/modelcontextprotocol/kotlin/sdk/ServerResult { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/CompleteResult$Companion; - public fun (Lio/modelcontextprotocol/kotlin/sdk/CompleteResult$Completion;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/CompleteResult$Completion;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/CompleteResult$Completion; - public final fun component2 ()Lkotlinx/serialization/json/JsonObject; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/CompleteResult$Completion;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/CompleteResult; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/CompleteResult;Lio/modelcontextprotocol/kotlin/sdk/CompleteResult$Completion;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/CompleteResult; - public fun equals (Ljava/lang/Object;)Z - public final fun getCompletion ()Lio/modelcontextprotocol/kotlin/sdk/CompleteResult$Completion; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/CompleteResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/CompleteResult$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/CompleteResult; - 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/CompleteResult;)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/CompleteResult$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/CompleteResult$Completion { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/CompleteResult$Completion$Companion; - public fun (Ljava/util/List;Ljava/lang/Integer;Ljava/lang/Boolean;)V - public final fun getHasMore ()Ljava/lang/Boolean; - public final fun getTotal ()Ljava/lang/Integer; - public final fun getValues ()Ljava/util/List; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/CompleteResult$Completion$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/CompleteResult$Completion$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/CompleteResult$Completion; - 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/CompleteResult$Completion;)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/CompleteResult$Completion$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest : io/modelcontextprotocol/kotlin/sdk/ServerRequest, io/modelcontextprotocol/kotlin/sdk/WithMeta { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest$Companion; - public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest$RequestedSchema;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest$RequestedSchema;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest$RequestedSchema; - public final fun component3 ()Lkotlinx/serialization/json/JsonObject; - public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest$RequestedSchema;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest$RequestedSchema;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest; - public fun equals (Ljava/lang/Object;)Z - public final fun getMessage ()Ljava/lang/String; - public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/Method; - public final fun getRequestedSchema ()Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest$RequestedSchema; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest; - 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/CreateElicitationRequest;)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/CreateElicitationRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest$RequestedSchema { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest$RequestedSchema$Companion; - public fun ()V - public fun (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;)V - public synthetic fun (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lkotlinx/serialization/json/JsonObject; - public final fun component2 ()Ljava/util/List; - public final fun copy (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;)Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest$RequestedSchema; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest$RequestedSchema;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest$RequestedSchema; - public fun equals (Ljava/lang/Object;)Z - public final fun getProperties ()Lkotlinx/serialization/json/JsonObject; - public final fun getRequired ()Ljava/util/List; - public final fun getType ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest$RequestedSchema$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest$RequestedSchema$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest$RequestedSchema; - 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/CreateElicitationRequest$RequestedSchema;)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/CreateElicitationRequest$RequestedSchema$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/CreateElicitationResult : io/modelcontextprotocol/kotlin/sdk/ClientResult { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationResult$Companion; - public fun (Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationResult$Action;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationResult$Action;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationResult$Action; - public final fun component2 ()Lkotlinx/serialization/json/JsonObject; - public final fun component3 ()Lkotlinx/serialization/json/JsonObject; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationResult$Action;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationResult; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationResult;Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationResult$Action;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationResult; - public fun equals (Ljava/lang/Object;)Z - public final fun getAction ()Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationResult$Action; - public final fun getContent ()Lkotlinx/serialization/json/JsonObject; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/CreateElicitationResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationResult$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationResult; - 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/CreateElicitationResult;)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/CreateElicitationResult$Action : java/lang/Enum { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationResult$Action$Companion; - public static final field accept Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationResult$Action; - public static final field cancel Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationResult$Action; - public static final field decline Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationResult$Action; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public static fun valueOf (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationResult$Action; - public static fun values ()[Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationResult$Action; -} - -public final class io/modelcontextprotocol/kotlin/sdk/CreateElicitationResult$Action$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/CreateElicitationResult$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/CreateMessageRequest : io/modelcontextprotocol/kotlin/sdk/ServerRequest, io/modelcontextprotocol/kotlin/sdk/WithMeta { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/CreateMessageRequest$Companion; - public fun (Ljava/util/List;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/CreateMessageRequest$IncludeContext;Ljava/lang/Double;ILjava/util/List;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/ModelPreferences;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Ljava/util/List;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/CreateMessageRequest$IncludeContext;Ljava/lang/Double;ILjava/util/List;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/ModelPreferences;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/util/List; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/CreateMessageRequest$IncludeContext; - public final fun component4 ()Ljava/lang/Double; - public final fun component5 ()I - public final fun component6 ()Ljava/util/List; - public final fun component7 ()Lkotlinx/serialization/json/JsonObject; - public final fun component8 ()Lio/modelcontextprotocol/kotlin/sdk/ModelPreferences; - public final fun component9 ()Lkotlinx/serialization/json/JsonObject; - public final fun copy (Ljava/util/List;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/CreateMessageRequest$IncludeContext;Ljava/lang/Double;ILjava/util/List;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/ModelPreferences;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/CreateMessageRequest; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/CreateMessageRequest;Ljava/util/List;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/CreateMessageRequest$IncludeContext;Ljava/lang/Double;ILjava/util/List;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/ModelPreferences;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/CreateMessageRequest; - public fun equals (Ljava/lang/Object;)Z - public final fun getIncludeContext ()Lio/modelcontextprotocol/kotlin/sdk/CreateMessageRequest$IncludeContext; - public final fun getMaxTokens ()I - public final fun getMessages ()Ljava/util/List; - public final fun getMetadata ()Lkotlinx/serialization/json/JsonObject; - public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/Method; - public final fun getModelPreferences ()Lio/modelcontextprotocol/kotlin/sdk/ModelPreferences; - public final fun getStopSequences ()Ljava/util/List; - public final fun getSystemPrompt ()Ljava/lang/String; - public final fun getTemperature ()Ljava/lang/Double; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/CreateMessageRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/CreateMessageRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/CreateMessageRequest; - 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/CreateMessageRequest;)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/CreateMessageRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/CreateMessageRequest$IncludeContext : java/lang/Enum { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/CreateMessageRequest$IncludeContext$Companion; - public static final field allServers Lio/modelcontextprotocol/kotlin/sdk/CreateMessageRequest$IncludeContext; - public static final field none Lio/modelcontextprotocol/kotlin/sdk/CreateMessageRequest$IncludeContext; - public static final field thisServer Lio/modelcontextprotocol/kotlin/sdk/CreateMessageRequest$IncludeContext; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public static fun valueOf (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/CreateMessageRequest$IncludeContext; - public static fun values ()[Lio/modelcontextprotocol/kotlin/sdk/CreateMessageRequest$IncludeContext; -} - -public final class io/modelcontextprotocol/kotlin/sdk/CreateMessageRequest$IncludeContext$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/CreateMessageResult : io/modelcontextprotocol/kotlin/sdk/ClientResult { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/CreateMessageResult$Companion; - public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/StopReason;Lio/modelcontextprotocol/kotlin/sdk/Role;Lio/modelcontextprotocol/kotlin/sdk/PromptMessageContentMultimodal;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/StopReason;Lio/modelcontextprotocol/kotlin/sdk/Role;Lio/modelcontextprotocol/kotlin/sdk/PromptMessageContentMultimodal;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/StopReason; - public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/Role; - public final fun component4 ()Lio/modelcontextprotocol/kotlin/sdk/PromptMessageContentMultimodal; - public final fun component5 ()Lkotlinx/serialization/json/JsonObject; - public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/StopReason;Lio/modelcontextprotocol/kotlin/sdk/Role;Lio/modelcontextprotocol/kotlin/sdk/PromptMessageContentMultimodal;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/CreateMessageResult; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/CreateMessageResult;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/StopReason;Lio/modelcontextprotocol/kotlin/sdk/Role;Lio/modelcontextprotocol/kotlin/sdk/PromptMessageContentMultimodal;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/CreateMessageResult; - public fun equals (Ljava/lang/Object;)Z - public final fun getContent ()Lio/modelcontextprotocol/kotlin/sdk/PromptMessageContentMultimodal; - public final fun getModel ()Ljava/lang/String; - public final fun getRole ()Lio/modelcontextprotocol/kotlin/sdk/Role; - public final fun getStopReason ()Lio/modelcontextprotocol/kotlin/sdk/StopReason; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/CreateMessageResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/CreateMessageResult$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/CreateMessageResult; - 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/CreateMessageResult;)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/CreateMessageResult$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/CustomMeta : io/modelcontextprotocol/kotlin/sdk/WithMeta { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/CustomMeta$Companion; - public fun ()V - public fun (Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/CustomMeta$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/CustomMeta$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/CustomMeta; - 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/CustomMeta;)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/CustomMeta$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public class io/modelcontextprotocol/kotlin/sdk/CustomRequest : io/modelcontextprotocol/kotlin/sdk/Request { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/CustomRequest$Companion; - public synthetic fun (ILio/modelcontextprotocol/kotlin/sdk/Method;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V - public fun (Lio/modelcontextprotocol/kotlin/sdk/Method;)V - public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/Method; - public static final synthetic fun write$Self (Lio/modelcontextprotocol/kotlin/sdk/CustomRequest;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/CustomRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/CustomRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/CustomRequest; - 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/CustomRequest;)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/CustomRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/EmbeddedResource : io/modelcontextprotocol/kotlin/sdk/PromptMessageContent { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/EmbeddedResource$Companion; - public static final field TYPE Ljava/lang/String; - public fun (Lio/modelcontextprotocol/kotlin/sdk/ResourceContents;Lio/modelcontextprotocol/kotlin/sdk/Annotations;)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/ResourceContents;Lio/modelcontextprotocol/kotlin/sdk/Annotations;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/ResourceContents; - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/Annotations; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/ResourceContents;Lio/modelcontextprotocol/kotlin/sdk/Annotations;)Lio/modelcontextprotocol/kotlin/sdk/EmbeddedResource; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/EmbeddedResource;Lio/modelcontextprotocol/kotlin/sdk/ResourceContents;Lio/modelcontextprotocol/kotlin/sdk/Annotations;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/EmbeddedResource; - public fun equals (Ljava/lang/Object;)Z - public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/Annotations; - public final fun getResource ()Lio/modelcontextprotocol/kotlin/sdk/ResourceContents; - public fun getType ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/EmbeddedResource$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/EmbeddedResource$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/EmbeddedResource; - 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/EmbeddedResource;)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/EmbeddedResource$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/EmptyRequestResult : io/modelcontextprotocol/kotlin/sdk/ClientResult, io/modelcontextprotocol/kotlin/sdk/ServerResult { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/EmptyRequestResult$Companion; - public fun ()V - public fun (Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lkotlinx/serialization/json/JsonObject; - public final fun copy (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/EmptyRequestResult; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/EmptyRequestResult;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/EmptyRequestResult; - public fun equals (Ljava/lang/Object;)Z - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/EmptyRequestResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/EmptyRequestResult$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/EmptyRequestResult; - 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/EmptyRequestResult;)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/EmptyRequestResult$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final class io/modelcontextprotocol/kotlin/sdk/CreateMessageRequest { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/CreateMessageRequest; + public final fun getIncludeContext ()Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext$Companion; } public abstract interface class io/modelcontextprotocol/kotlin/sdk/ErrorCode { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ErrorCode$Companion; public abstract fun getCode ()I } -public final class io/modelcontextprotocol/kotlin/sdk/ErrorCode$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ErrorCode$Defined : java/lang/Enum, io/modelcontextprotocol/kotlin/sdk/ErrorCode { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ErrorCode$Defined$Companion; - public static final field ConnectionClosed Lio/modelcontextprotocol/kotlin/sdk/ErrorCode$Defined; - public static final field InternalError Lio/modelcontextprotocol/kotlin/sdk/ErrorCode$Defined; - public static final field InvalidParams Lio/modelcontextprotocol/kotlin/sdk/ErrorCode$Defined; - public static final field InvalidRequest Lio/modelcontextprotocol/kotlin/sdk/ErrorCode$Defined; - public static final field MethodNotFound Lio/modelcontextprotocol/kotlin/sdk/ErrorCode$Defined; - public static final field ParseError Lio/modelcontextprotocol/kotlin/sdk/ErrorCode$Defined; - public static final field RequestTimeout Lio/modelcontextprotocol/kotlin/sdk/ErrorCode$Defined; - public fun getCode ()I - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public static fun valueOf (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/ErrorCode$Defined; - public static fun values ()[Lio/modelcontextprotocol/kotlin/sdk/ErrorCode$Defined; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ErrorCode$Defined$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final class io/modelcontextprotocol/kotlin/sdk/ErrorCode$Defined { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ErrorCode$Defined; + public final fun getConnectionClosed ()I + public final fun getInternalError ()I + public final fun getInvalidParams ()I + public final fun getInvalidRequest ()I + public final fun getMethodNotFound ()I + public final fun getParseError ()I + public final fun getRequestTimeout ()I } public final class io/modelcontextprotocol/kotlin/sdk/ErrorCode$Unknown : io/modelcontextprotocol/kotlin/sdk/ErrorCode { @@ -818,783 +130,33 @@ public final class io/modelcontextprotocol/kotlin/sdk/ErrorCode$Unknown$Companio public final fun serializer ()Lkotlinx/serialization/KSerializer; } -public final class io/modelcontextprotocol/kotlin/sdk/GetPromptRequest : io/modelcontextprotocol/kotlin/sdk/ClientRequest, io/modelcontextprotocol/kotlin/sdk/WithMeta { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/GetPromptRequest$Companion; - public fun (Ljava/lang/String;Ljava/util/Map;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Ljava/lang/String;Ljava/util/Map;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/util/Map; - public final fun component3 ()Lkotlinx/serialization/json/JsonObject; - public final fun copy (Ljava/lang/String;Ljava/util/Map;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/GetPromptRequest; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/GetPromptRequest;Ljava/lang/String;Ljava/util/Map;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/GetPromptRequest; - public fun equals (Ljava/lang/Object;)Z - public final fun getArguments ()Ljava/util/Map; - public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/Method; - public final fun getName ()Ljava/lang/String; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/GetPromptRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/GetPromptRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/GetPromptRequest; - 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/GetPromptRequest;)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/GetPromptRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/GetPromptResult : io/modelcontextprotocol/kotlin/sdk/ServerResult { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/GetPromptResult$Companion; - public fun (Ljava/lang/String;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Ljava/lang/String;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getDescription ()Ljava/lang/String; - public final fun getMessages ()Ljava/util/List; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/GetPromptResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/GetPromptResult$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/GetPromptResult; - 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/GetPromptResult;)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/GetPromptResult$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ImageContent : io/modelcontextprotocol/kotlin/sdk/PromptMessageContentMultimodal { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ImageContent$Companion; - public static final field TYPE Ljava/lang/String; - public fun (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Annotations;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Annotations;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/Annotations; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Annotations;)Lio/modelcontextprotocol/kotlin/sdk/ImageContent; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/ImageContent;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Annotations;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/ImageContent; - public fun equals (Ljava/lang/Object;)Z - public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/Annotations; - public final fun getData ()Ljava/lang/String; - public final fun getMimeType ()Ljava/lang/String; - public fun getType ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/ImageContent$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ImageContent$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/ImageContent; - 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/ImageContent;)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/ImageContent$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/Implementation { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/Implementation$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/Implementation; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/Implementation;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/Implementation; - public fun equals (Ljava/lang/Object;)Z - public final fun getName ()Ljava/lang/String; - public final fun getVersion ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/Implementation$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/Implementation$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/Implementation; - 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/Implementation;)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/Implementation$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/InitializeRequest : io/modelcontextprotocol/kotlin/sdk/ClientRequest, io/modelcontextprotocol/kotlin/sdk/WithMeta { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/InitializeRequest$Companion; - public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities;Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities;Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities; - public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/Implementation; - public final fun component4 ()Lkotlinx/serialization/json/JsonObject; - public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities;Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/InitializeRequest; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/InitializeRequest;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities;Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/InitializeRequest; - public fun equals (Ljava/lang/Object;)Z - public final fun getCapabilities ()Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities; - public final fun getClientInfo ()Lio/modelcontextprotocol/kotlin/sdk/Implementation; - public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/Method; - public final fun getProtocolVersion ()Ljava/lang/String; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/InitializeRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/InitializeRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/InitializeRequest; - 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/InitializeRequest;)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/InitializeRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/InitializeResult : io/modelcontextprotocol/kotlin/sdk/ServerResult { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/InitializeResult$Companion; - public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities;Lio/modelcontextprotocol/kotlin/sdk/Implementation;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities;Lio/modelcontextprotocol/kotlin/sdk/Implementation;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities; - public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/Implementation; - public final fun component4 ()Ljava/lang/String; - public final fun component5 ()Lkotlinx/serialization/json/JsonObject; - public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities;Lio/modelcontextprotocol/kotlin/sdk/Implementation;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/InitializeResult; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/InitializeResult;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities;Lio/modelcontextprotocol/kotlin/sdk/Implementation;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/InitializeResult; - public fun equals (Ljava/lang/Object;)Z - public final fun getCapabilities ()Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities; - public final fun getInstructions ()Ljava/lang/String; - public final fun getProtocolVersion ()Ljava/lang/String; - public final fun getServerInfo ()Lio/modelcontextprotocol/kotlin/sdk/Implementation; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/InitializeResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/InitializeResult$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/InitializeResult; - 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/InitializeResult;)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/InitializeResult$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/InitializedNotification : io/modelcontextprotocol/kotlin/sdk/ClientNotification { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/InitializedNotification$Companion; - public fun ()V - public fun (Lio/modelcontextprotocol/kotlin/sdk/InitializedNotification$Params;)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/InitializedNotification$Params;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/InitializedNotification$Params; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/InitializedNotification$Params;)Lio/modelcontextprotocol/kotlin/sdk/InitializedNotification; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/InitializedNotification;Lio/modelcontextprotocol/kotlin/sdk/InitializedNotification$Params;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/InitializedNotification; - public fun equals (Ljava/lang/Object;)Z - public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/Method; - public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/InitializedNotification$Params; - public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/NotificationParams; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/InitializedNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/InitializedNotification$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/InitializedNotification; - 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/InitializedNotification;)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/InitializedNotification$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/InitializedNotification$Params : io/modelcontextprotocol/kotlin/sdk/NotificationParams { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/InitializedNotification$Params$Companion; - public fun ()V - public fun (Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lkotlinx/serialization/json/JsonObject; - public final fun copy (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/InitializedNotification$Params; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/InitializedNotification$Params;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/InitializedNotification$Params; - public fun equals (Ljava/lang/Object;)Z - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/InitializedNotification$Params$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/InitializedNotification$Params$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/InitializedNotification$Params; - 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/InitializedNotification$Params;)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/InitializedNotification$Params$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/JSONRPCError : io/modelcontextprotocol/kotlin/sdk/JSONRPCMessage { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/JSONRPCError$Companion; - public fun (Lio/modelcontextprotocol/kotlin/sdk/ErrorCode;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/ErrorCode;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/ErrorCode; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Lkotlinx/serialization/json/JsonObject; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/ErrorCode;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/JSONRPCError; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/JSONRPCError;Lio/modelcontextprotocol/kotlin/sdk/ErrorCode;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/JSONRPCError; - public fun equals (Ljava/lang/Object;)Z - public final fun getCode ()Lio/modelcontextprotocol/kotlin/sdk/ErrorCode; - public final fun getData ()Lkotlinx/serialization/json/JsonObject; - public final fun getMessage ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/JSONRPCError$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/JSONRPCError$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/JSONRPCError; - 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/JSONRPCError;)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/JSONRPCError$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public abstract interface class io/modelcontextprotocol/kotlin/sdk/JSONRPCMessage { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/JSONRPCMessage$Companion; -} - -public final class io/modelcontextprotocol/kotlin/sdk/JSONRPCMessage$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/JSONRPCNotification : io/modelcontextprotocol/kotlin/sdk/JSONRPCMessage { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/JSONRPCNotification$Companion; - public fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;Ljava/lang/String;)V - public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lkotlinx/serialization/json/JsonElement; - public final fun component3 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/JSONRPCNotification; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/JSONRPCNotification;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/JSONRPCNotification; - public fun equals (Ljava/lang/Object;)Z - public final fun getJsonrpc ()Ljava/lang/String; - public final fun getMethod ()Ljava/lang/String; - public final fun getParams ()Lkotlinx/serialization/json/JsonElement; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/JSONRPCNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/JSONRPCNotification$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/JSONRPCNotification; - 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/JSONRPCNotification;)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/JSONRPCNotification$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/JSONRPCRequest : io/modelcontextprotocol/kotlin/sdk/JSONRPCMessage { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/JSONRPCRequest$Companion; - public fun (Lio/modelcontextprotocol/kotlin/sdk/RequestId;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;Ljava/lang/String;)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/RequestId;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/RequestId; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Lkotlinx/serialization/json/JsonElement; - public final fun component4 ()Ljava/lang/String; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/RequestId;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/JSONRPCRequest; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/JSONRPCRequest;Lio/modelcontextprotocol/kotlin/sdk/RequestId;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/JSONRPCRequest; - public fun equals (Ljava/lang/Object;)Z - public final fun getId ()Lio/modelcontextprotocol/kotlin/sdk/RequestId; - public final fun getJsonrpc ()Ljava/lang/String; - public final fun getMethod ()Ljava/lang/String; - public final fun getParams ()Lkotlinx/serialization/json/JsonElement; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/JSONRPCRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/JSONRPCRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/JSONRPCRequest; - 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/JSONRPCRequest;)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/JSONRPCRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/JSONRPCResponse : io/modelcontextprotocol/kotlin/sdk/JSONRPCMessage { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/JSONRPCResponse$Companion; - public fun (Lio/modelcontextprotocol/kotlin/sdk/RequestId;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/RequestResult;Lio/modelcontextprotocol/kotlin/sdk/JSONRPCError;)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/RequestId;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/RequestResult;Lio/modelcontextprotocol/kotlin/sdk/JSONRPCError;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/RequestId;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/RequestResult;Lio/modelcontextprotocol/kotlin/sdk/JSONRPCError;)Lio/modelcontextprotocol/kotlin/sdk/JSONRPCResponse; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/JSONRPCResponse;Lio/modelcontextprotocol/kotlin/sdk/RequestId;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/RequestResult;Lio/modelcontextprotocol/kotlin/sdk/JSONRPCError;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/JSONRPCResponse; - public final fun getError ()Lio/modelcontextprotocol/kotlin/sdk/JSONRPCError; - public final fun getId ()Lio/modelcontextprotocol/kotlin/sdk/RequestId; - public final fun getJsonrpc ()Ljava/lang/String; - public final fun getResult ()Lio/modelcontextprotocol/kotlin/sdk/RequestResult; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/JSONRPCResponse$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/JSONRPCResponse$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/JSONRPCResponse; - 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/JSONRPCResponse;)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/JSONRPCResponse$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +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; + public static synthetic fun Params$default (Lio/modelcontextprotocol/kotlin/sdk/InitializedNotification;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; } public final class io/modelcontextprotocol/kotlin/sdk/LibVersionKt { public static final field LIB_VERSION Ljava/lang/String; } -public final class io/modelcontextprotocol/kotlin/sdk/ListPromptsRequest : io/modelcontextprotocol/kotlin/sdk/ClientRequest, io/modelcontextprotocol/kotlin/sdk/PaginatedRequest { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ListPromptsRequest$Companion; - public fun ()V - public fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (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/ListPromptsRequest; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/ListPromptsRequest;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/ListPromptsRequest; - public fun equals (Ljava/lang/Object;)Z - public fun getCursor ()Ljava/lang/String; - public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/Method; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; +public final class io/modelcontextprotocol/kotlin/sdk/LoggingLevel { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/LoggingLevel; + public final fun getAlert ()Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; + public final fun getCritical ()Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; + public final fun getDebug ()Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; + public final fun getEmergency ()Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; + public final fun getError ()Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; + public final fun getInfo ()Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; + public final fun getNotice ()Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; + public final fun getWarning ()Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; } -public final synthetic class io/modelcontextprotocol/kotlin/sdk/ListPromptsRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ListPromptsRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/ListPromptsRequest; - 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/ListPromptsRequest;)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/ListPromptsRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ListPromptsResult : io/modelcontextprotocol/kotlin/sdk/PaginatedResult, io/modelcontextprotocol/kotlin/sdk/ServerResult { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ListPromptsResult$Companion; - public fun (Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun getNextCursor ()Ljava/lang/String; - public final fun getPrompts ()Ljava/util/List; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/ListPromptsResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ListPromptsResult$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/ListPromptsResult; - 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/ListPromptsResult;)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/ListPromptsResult$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ListResourceTemplatesRequest : io/modelcontextprotocol/kotlin/sdk/ClientRequest, io/modelcontextprotocol/kotlin/sdk/PaginatedRequest { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ListResourceTemplatesRequest$Companion; - public fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (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/ListResourceTemplatesRequest; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/ListResourceTemplatesRequest;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/ListResourceTemplatesRequest; - public fun equals (Ljava/lang/Object;)Z - public fun getCursor ()Ljava/lang/String; - public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/Method; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/ListResourceTemplatesRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ListResourceTemplatesRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/ListResourceTemplatesRequest; - 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/ListResourceTemplatesRequest;)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/ListResourceTemplatesRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ListResourceTemplatesResult : io/modelcontextprotocol/kotlin/sdk/PaginatedResult, io/modelcontextprotocol/kotlin/sdk/ServerResult { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ListResourceTemplatesResult$Companion; - public fun (Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun getNextCursor ()Ljava/lang/String; - public final fun getResourceTemplates ()Ljava/util/List; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/ListResourceTemplatesResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ListResourceTemplatesResult$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/ListResourceTemplatesResult; - 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/ListResourceTemplatesResult;)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/ListResourceTemplatesResult$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ListResourcesRequest : io/modelcontextprotocol/kotlin/sdk/ClientRequest, io/modelcontextprotocol/kotlin/sdk/PaginatedRequest { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ListResourcesRequest$Companion; - public fun ()V - public fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (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/ListResourcesRequest; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/ListResourcesRequest;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/ListResourcesRequest; - public fun equals (Ljava/lang/Object;)Z - public fun getCursor ()Ljava/lang/String; - public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/Method; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/ListResourcesRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ListResourcesRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/ListResourcesRequest; - 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/ListResourcesRequest;)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/ListResourcesRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ListResourcesResult : io/modelcontextprotocol/kotlin/sdk/PaginatedResult, io/modelcontextprotocol/kotlin/sdk/ServerResult { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ListResourcesResult$Companion; - public fun (Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun getNextCursor ()Ljava/lang/String; - public final fun getResources ()Ljava/util/List; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/ListResourcesResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ListResourcesResult$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/ListResourcesResult; - 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/ListResourcesResult;)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/ListResourcesResult$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ListRootsRequest : io/modelcontextprotocol/kotlin/sdk/ServerRequest, io/modelcontextprotocol/kotlin/sdk/WithMeta { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ListRootsRequest$Companion; - public fun ()V - public fun (Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/Method; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/ListRootsRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ListRootsRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/ListRootsRequest; - 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/ListRootsRequest;)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/ListRootsRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ListRootsResult : io/modelcontextprotocol/kotlin/sdk/ClientResult { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ListRootsResult$Companion; - public fun (Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Ljava/util/List;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getRoots ()Ljava/util/List; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/ListRootsResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ListRootsResult$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/ListRootsResult; - 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/ListRootsResult;)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/ListRootsResult$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ListToolsRequest : io/modelcontextprotocol/kotlin/sdk/ClientRequest, io/modelcontextprotocol/kotlin/sdk/PaginatedRequest { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ListToolsRequest$Companion; - public fun ()V - public fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (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/ListToolsRequest; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/ListToolsRequest;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/ListToolsRequest; - public fun equals (Ljava/lang/Object;)Z - public fun getCursor ()Ljava/lang/String; - public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/Method; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/ListToolsRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ListToolsRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/ListToolsRequest; - 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/ListToolsRequest;)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/ListToolsRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ListToolsResult : io/modelcontextprotocol/kotlin/sdk/PaginatedResult, io/modelcontextprotocol/kotlin/sdk/ServerResult { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ListToolsResult$Companion; - public fun (Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun getNextCursor ()Ljava/lang/String; - public final fun getTools ()Ljava/util/List; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/ListToolsResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ListToolsResult$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/ListToolsResult; - 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/ListToolsResult;)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/ListToolsResult$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/LoggingLevel : java/lang/Enum { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/LoggingLevel$Companion; - public static final field alert Lio/modelcontextprotocol/kotlin/sdk/LoggingLevel; - public static final field critical Lio/modelcontextprotocol/kotlin/sdk/LoggingLevel; - public static final field debug Lio/modelcontextprotocol/kotlin/sdk/LoggingLevel; - public static final field emergency Lio/modelcontextprotocol/kotlin/sdk/LoggingLevel; - public static final field error Lio/modelcontextprotocol/kotlin/sdk/LoggingLevel; - public static final field info Lio/modelcontextprotocol/kotlin/sdk/LoggingLevel; - public static final field notice Lio/modelcontextprotocol/kotlin/sdk/LoggingLevel; - public static final field warning Lio/modelcontextprotocol/kotlin/sdk/LoggingLevel; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public static fun valueOf (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/LoggingLevel; - public static fun values ()[Lio/modelcontextprotocol/kotlin/sdk/LoggingLevel; -} - -public final class io/modelcontextprotocol/kotlin/sdk/LoggingLevel$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification : io/modelcontextprotocol/kotlin/sdk/ServerNotification { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification$Companion; - public fun (Lio/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification$Params;)V - public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification$Params; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification$Params;)Lio/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification;Lio/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification$Params;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification; - public fun equals (Ljava/lang/Object;)Z - public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/Method; - public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification$Params; - public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/NotificationParams; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification; - 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/LoggingMessageNotification;)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/LoggingMessageNotification$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification$Params : io/modelcontextprotocol/kotlin/sdk/NotificationParams { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification$Params$Companion; - public fun (Lio/modelcontextprotocol/kotlin/sdk/LoggingLevel;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/LoggingLevel;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/LoggingLevel; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Lkotlinx/serialization/json/JsonElement; - public final fun component4 ()Lkotlinx/serialization/json/JsonObject; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/LoggingLevel;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification$Params; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification$Params;Lio/modelcontextprotocol/kotlin/sdk/LoggingLevel;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification$Params; - public fun equals (Ljava/lang/Object;)Z - public final fun getData ()Lkotlinx/serialization/json/JsonElement; - public final fun getLevel ()Lio/modelcontextprotocol/kotlin/sdk/LoggingLevel; - public final fun getLogger ()Ljava/lang/String; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification$Params$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification$Params$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification$Params; - 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/LoggingMessageNotification$Params;)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/LoggingMessageNotification$Params$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification$SetLevelRequest : io/modelcontextprotocol/kotlin/sdk/ClientRequest, io/modelcontextprotocol/kotlin/sdk/WithMeta { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification$SetLevelRequest$Companion; - public fun (Lio/modelcontextprotocol/kotlin/sdk/LoggingLevel;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/LoggingLevel;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/LoggingLevel; - public final fun component2 ()Lkotlinx/serialization/json/JsonObject; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/LoggingLevel;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification$SetLevelRequest; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification$SetLevelRequest;Lio/modelcontextprotocol/kotlin/sdk/LoggingLevel;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification$SetLevelRequest; - public fun equals (Ljava/lang/Object;)Z - public final fun getLevel ()Lio/modelcontextprotocol/kotlin/sdk/LoggingLevel; - public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/Method; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification$SetLevelRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification$SetLevelRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification$SetLevelRequest; - 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/LoggingMessageNotification$SetLevelRequest;)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/LoggingMessageNotification$SetLevelRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/McpError : java/lang/Exception { - public fun (ILjava/lang/String;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (ILjava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getCode ()I - public final fun getData ()Lkotlinx/serialization/json/JsonObject; - public fun getMessage ()Ljava/lang/String; +public final class io/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification; + public final fun Params (Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel;Lkotlinx/serialization/json/JsonElement;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotificationParams; + public static synthetic fun Params$default (Lio/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification;Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel;Lkotlinx/serialization/json/JsonElement;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotificationParams; + public final fun SetLevelRequest (Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequest; } public abstract interface class io/modelcontextprotocol/kotlin/sdk/Method { @@ -1603,205 +165,36 @@ public abstract interface class io/modelcontextprotocol/kotlin/sdk/Method { } public final class io/modelcontextprotocol/kotlin/sdk/Method$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; + public final fun Custom (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/Method$Custom; } -public final class io/modelcontextprotocol/kotlin/sdk/Method$Custom : io/modelcontextprotocol/kotlin/sdk/Method { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/Method$Custom$Companion; - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/Method$Custom; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/Method$Custom;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/Method$Custom; - public fun equals (Ljava/lang/Object;)Z - public fun getValue ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/Method$Custom$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/Method$Custom$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/Method$Custom; - 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/Method$Custom;)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/Method$Custom$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/Method$Defined : java/lang/Enum, io/modelcontextprotocol/kotlin/sdk/Method { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/Method$Defined$Companion; - public static final field CompletionComplete Lio/modelcontextprotocol/kotlin/sdk/Method$Defined; - public static final field ElicitationCreate Lio/modelcontextprotocol/kotlin/sdk/Method$Defined; - public static final field Initialize Lio/modelcontextprotocol/kotlin/sdk/Method$Defined; - public static final field LoggingSetLevel Lio/modelcontextprotocol/kotlin/sdk/Method$Defined; - public static final field NotificationsCancelled Lio/modelcontextprotocol/kotlin/sdk/Method$Defined; - public static final field NotificationsInitialized Lio/modelcontextprotocol/kotlin/sdk/Method$Defined; - public static final field NotificationsMessage Lio/modelcontextprotocol/kotlin/sdk/Method$Defined; - public static final field NotificationsProgress Lio/modelcontextprotocol/kotlin/sdk/Method$Defined; - public static final field NotificationsPromptsListChanged Lio/modelcontextprotocol/kotlin/sdk/Method$Defined; - public static final field NotificationsResourcesListChanged Lio/modelcontextprotocol/kotlin/sdk/Method$Defined; - public static final field NotificationsResourcesUpdated Lio/modelcontextprotocol/kotlin/sdk/Method$Defined; - public static final field NotificationsRootsListChanged Lio/modelcontextprotocol/kotlin/sdk/Method$Defined; - public static final field NotificationsToolsListChanged Lio/modelcontextprotocol/kotlin/sdk/Method$Defined; - public static final field Ping Lio/modelcontextprotocol/kotlin/sdk/Method$Defined; - public static final field PromptsGet Lio/modelcontextprotocol/kotlin/sdk/Method$Defined; - public static final field PromptsList Lio/modelcontextprotocol/kotlin/sdk/Method$Defined; - public static final field ResourcesList Lio/modelcontextprotocol/kotlin/sdk/Method$Defined; - public static final field ResourcesRead Lio/modelcontextprotocol/kotlin/sdk/Method$Defined; - public static final field ResourcesSubscribe Lio/modelcontextprotocol/kotlin/sdk/Method$Defined; - public static final field ResourcesTemplatesList Lio/modelcontextprotocol/kotlin/sdk/Method$Defined; - public static final field ResourcesUnsubscribe Lio/modelcontextprotocol/kotlin/sdk/Method$Defined; - public static final field RootsList Lio/modelcontextprotocol/kotlin/sdk/Method$Defined; - public static final field SamplingCreateMessage Lio/modelcontextprotocol/kotlin/sdk/Method$Defined; - public static final field ToolsCall Lio/modelcontextprotocol/kotlin/sdk/Method$Defined; - public static final field ToolsList Lio/modelcontextprotocol/kotlin/sdk/Method$Defined; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public fun getValue ()Ljava/lang/String; - public static fun valueOf (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/Method$Defined; - public static fun values ()[Lio/modelcontextprotocol/kotlin/sdk/Method$Defined; -} - -public final class io/modelcontextprotocol/kotlin/sdk/Method$Defined$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ModelHint { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ModelHint$Companion; - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/ModelHint; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/ModelHint;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/ModelHint; - public fun equals (Ljava/lang/Object;)Z - public final fun getName ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/ModelHint$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ModelHint$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/ModelHint; - 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/ModelHint;)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/ModelHint$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ModelPreferences { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ModelPreferences$Companion; - public fun (Ljava/util/List;Ljava/lang/Double;Ljava/lang/Double;Ljava/lang/Double;)V - public final fun getCostPriority ()Ljava/lang/Double; - public final fun getHints ()Ljava/util/List; - public final fun getIntelligencePriority ()Ljava/lang/Double; - public final fun getSpeedPriority ()Ljava/lang/Double; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/ModelPreferences$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ModelPreferences$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/ModelPreferences; - 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/ModelPreferences;)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/ModelPreferences$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public abstract interface class io/modelcontextprotocol/kotlin/sdk/Notification { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/Notification$Companion; - public abstract fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/Method; - public abstract fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/NotificationParams; -} - -public final class io/modelcontextprotocol/kotlin/sdk/Notification$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public abstract interface class io/modelcontextprotocol/kotlin/sdk/NotificationParams : io/modelcontextprotocol/kotlin/sdk/WithMeta { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/NotificationParams$Companion; -} - -public final class io/modelcontextprotocol/kotlin/sdk/NotificationParams$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public abstract interface class io/modelcontextprotocol/kotlin/sdk/PaginatedRequest : io/modelcontextprotocol/kotlin/sdk/Request, io/modelcontextprotocol/kotlin/sdk/WithMeta { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/PaginatedRequest$Companion; - public abstract fun getCursor ()Ljava/lang/String; - public abstract fun get_meta ()Lkotlinx/serialization/json/JsonObject; -} - -public final class io/modelcontextprotocol/kotlin/sdk/PaginatedRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public abstract interface class io/modelcontextprotocol/kotlin/sdk/PaginatedResult : io/modelcontextprotocol/kotlin/sdk/RequestResult { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/PaginatedResult$Companion; - public abstract fun getNextCursor ()Ljava/lang/String; -} - -public final class io/modelcontextprotocol/kotlin/sdk/PaginatedResult$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/PingRequest : io/modelcontextprotocol/kotlin/sdk/ClientRequest, io/modelcontextprotocol/kotlin/sdk/ServerRequest { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/PingRequest$Companion; - public fun ()V - public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/Method; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/PingRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/PingRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/PingRequest; - 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/PingRequest;)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/PingRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public class io/modelcontextprotocol/kotlin/sdk/Progress : io/modelcontextprotocol/kotlin/sdk/ProgressBase { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/Progress$Companion; - public fun (DLjava/lang/Double;Ljava/lang/String;)V - public synthetic fun (IDLjava/lang/Double;Ljava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V - public fun getMessage ()Ljava/lang/String; - public fun getProgress ()D - public fun getTotal ()Ljava/lang/Double; - public static final synthetic fun write$Self (Lio/modelcontextprotocol/kotlin/sdk/Progress;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/Progress$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/Progress$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/Progress; - 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/Progress;)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/Progress$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final class io/modelcontextprotocol/kotlin/sdk/Method$Defined { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/Method$Defined; + public final fun getCompletionComplete ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public final fun getElicitationCreate ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public final fun getInitialize ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public final fun getLoggingSetLevel ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public final fun getNotificationsCancelled ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public final fun getNotificationsInitialized ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public final fun getNotificationsMessage ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public final fun getNotificationsProgress ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public final fun getNotificationsPromptsListChanged ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public final fun getNotificationsResourcesListChanged ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public final fun getNotificationsResourcesUpdated ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public final fun getNotificationsRootsListChanged ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public final fun getNotificationsToolsListChanged ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public final fun getPing ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public final fun getPromptsGet ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public final fun getPromptsList ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public final fun getResourcesList ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public final fun getResourcesRead ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public final fun getResourcesSubscribe ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public final fun getResourcesTemplatesList ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public final fun getResourcesUnsubscribe ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public final fun getRootsList ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public final fun getSamplingCreateMessage ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public final fun getToolsCall ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public final fun getToolsList ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; } public abstract interface class io/modelcontextprotocol/kotlin/sdk/ProgressBase { @@ -1815,1304 +208,123 @@ public final class io/modelcontextprotocol/kotlin/sdk/ProgressBase$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } -public final class io/modelcontextprotocol/kotlin/sdk/ProgressNotification : io/modelcontextprotocol/kotlin/sdk/ClientNotification, io/modelcontextprotocol/kotlin/sdk/ServerNotification { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ProgressNotification$Companion; - public fun (Lio/modelcontextprotocol/kotlin/sdk/ProgressNotification$Params;)V - public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/ProgressNotification$Params; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/ProgressNotification$Params;)Lio/modelcontextprotocol/kotlin/sdk/ProgressNotification; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/ProgressNotification;Lio/modelcontextprotocol/kotlin/sdk/ProgressNotification$Params;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/ProgressNotification; - public fun equals (Ljava/lang/Object;)Z - public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/Method; - public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/NotificationParams; - public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/ProgressNotification$Params; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; +public final class io/modelcontextprotocol/kotlin/sdk/ProgressNotification { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ProgressNotification; + public final fun Params (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;DLjava/lang/Double;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ProgressNotificationParams; + public static synthetic fun Params$default (Lio/modelcontextprotocol/kotlin/sdk/ProgressNotification;Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;DLjava/lang/Double;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ProgressNotificationParams; } -public final synthetic class io/modelcontextprotocol/kotlin/sdk/ProgressNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ProgressNotification$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/ProgressNotification; - 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/ProgressNotification;)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/PromptListChangedNotification { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/PromptListChangedNotification; + public final fun Params (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; + public static synthetic fun Params$default (Lio/modelcontextprotocol/kotlin/sdk/PromptListChangedNotification;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; } -public final class io/modelcontextprotocol/kotlin/sdk/ProgressNotification$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final class io/modelcontextprotocol/kotlin/sdk/RequestId { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/RequestId; + public final fun NumberId-Nt6Vbyc (J)J + public final fun StringId-QNUQp7g (Ljava/lang/String;)Ljava/lang/String; } -public final class io/modelcontextprotocol/kotlin/sdk/ProgressNotification$Params : io/modelcontextprotocol/kotlin/sdk/NotificationParams, io/modelcontextprotocol/kotlin/sdk/ProgressBase { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ProgressNotification$Params$Companion; - public fun (DLio/modelcontextprotocol/kotlin/sdk/RequestId;Ljava/lang/Double;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (DLio/modelcontextprotocol/kotlin/sdk/RequestId;Ljava/lang/Double;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()D - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/RequestId; - public final fun component3 ()Ljava/lang/Double; - public final fun component4 ()Ljava/lang/String; - public final fun component5 ()Lkotlinx/serialization/json/JsonObject; - public final fun copy (DLio/modelcontextprotocol/kotlin/sdk/RequestId;Ljava/lang/Double;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/ProgressNotification$Params; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/ProgressNotification$Params;DLio/modelcontextprotocol/kotlin/sdk/RequestId;Ljava/lang/Double;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/ProgressNotification$Params; - public fun equals (Ljava/lang/Object;)Z - public fun getMessage ()Ljava/lang/String; - public fun getProgress ()D - public final fun getProgressToken ()Lio/modelcontextprotocol/kotlin/sdk/RequestId; - public fun getTotal ()Ljava/lang/Double; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; +public final class io/modelcontextprotocol/kotlin/sdk/ResourceListChangedNotification { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ResourceListChangedNotification; + public final fun Params (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; + public static synthetic fun Params$default (Lio/modelcontextprotocol/kotlin/sdk/ResourceListChangedNotification;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; } -public final synthetic class io/modelcontextprotocol/kotlin/sdk/ProgressNotification$Params$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ProgressNotification$Params$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/ProgressNotification$Params; - 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/ProgressNotification$Params;)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/ResourceUpdatedNotification { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ResourceUpdatedNotification; + public final fun Params (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotificationParams; + public static synthetic fun Params$default (Lio/modelcontextprotocol/kotlin/sdk/ResourceUpdatedNotification;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotificationParams; } -public final class io/modelcontextprotocol/kotlin/sdk/ProgressNotification$Params$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final class io/modelcontextprotocol/kotlin/sdk/Role { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/Role; + public final fun getAssistant ()Lio/modelcontextprotocol/kotlin/sdk/types/Role; + public final fun getUser ()Lio/modelcontextprotocol/kotlin/sdk/types/Role; } -public final class io/modelcontextprotocol/kotlin/sdk/Prompt { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/Prompt$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)V - public final fun getArguments ()Ljava/util/List; - public final fun getDescription ()Ljava/lang/String; - public final fun getName ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/Prompt$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/Prompt$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/Prompt; - 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/Prompt;)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/Prompt$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/PromptArgument { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/PromptArgument$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/Boolean; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/PromptArgument; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/PromptArgument;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/PromptArgument; - public fun equals (Ljava/lang/Object;)Z - public final fun getDescription ()Ljava/lang/String; - public final fun getName ()Ljava/lang/String; - public final fun getRequired ()Ljava/lang/Boolean; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/PromptArgument$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/PromptArgument$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/PromptArgument; - 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/PromptArgument;)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/PromptArgument$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/PromptListChangedNotification : io/modelcontextprotocol/kotlin/sdk/ServerNotification { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/PromptListChangedNotification$Companion; - public fun ()V - public fun (Lio/modelcontextprotocol/kotlin/sdk/PromptListChangedNotification$Params;)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/PromptListChangedNotification$Params;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/PromptListChangedNotification$Params; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/PromptListChangedNotification$Params;)Lio/modelcontextprotocol/kotlin/sdk/PromptListChangedNotification; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/PromptListChangedNotification;Lio/modelcontextprotocol/kotlin/sdk/PromptListChangedNotification$Params;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/PromptListChangedNotification; - public fun equals (Ljava/lang/Object;)Z - public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/Method; - public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/NotificationParams; - public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/PromptListChangedNotification$Params; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/PromptListChangedNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/PromptListChangedNotification$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/PromptListChangedNotification; - 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/PromptListChangedNotification;)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/PromptListChangedNotification$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/PromptListChangedNotification$Params : io/modelcontextprotocol/kotlin/sdk/NotificationParams { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/PromptListChangedNotification$Params$Companion; - public fun ()V - public fun (Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lkotlinx/serialization/json/JsonObject; - public final fun copy (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/PromptListChangedNotification$Params; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/PromptListChangedNotification$Params;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/PromptListChangedNotification$Params; - public fun equals (Ljava/lang/Object;)Z - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/PromptListChangedNotification$Params$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/PromptListChangedNotification$Params$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/PromptListChangedNotification$Params; - 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/PromptListChangedNotification$Params;)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/PromptListChangedNotification$Params$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/PromptMessage { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/PromptMessage$Companion; - public fun (Lio/modelcontextprotocol/kotlin/sdk/Role;Lio/modelcontextprotocol/kotlin/sdk/PromptMessageContent;)V - public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/Role; - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/PromptMessageContent; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/Role;Lio/modelcontextprotocol/kotlin/sdk/PromptMessageContent;)Lio/modelcontextprotocol/kotlin/sdk/PromptMessage; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/PromptMessage;Lio/modelcontextprotocol/kotlin/sdk/Role;Lio/modelcontextprotocol/kotlin/sdk/PromptMessageContent;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/PromptMessage; - public fun equals (Ljava/lang/Object;)Z - public final fun getContent ()Lio/modelcontextprotocol/kotlin/sdk/PromptMessageContent; - public final fun getRole ()Lio/modelcontextprotocol/kotlin/sdk/Role; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/PromptMessage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/PromptMessage$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/PromptMessage; - 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/PromptMessage;)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/PromptMessage$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public abstract interface class io/modelcontextprotocol/kotlin/sdk/PromptMessageContent { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/PromptMessageContent$Companion; - public abstract fun getType ()Ljava/lang/String; -} - -public final class io/modelcontextprotocol/kotlin/sdk/PromptMessageContent$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public abstract interface class io/modelcontextprotocol/kotlin/sdk/PromptMessageContentMultimodal : io/modelcontextprotocol/kotlin/sdk/PromptMessageContent { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/PromptMessageContentMultimodal$Companion; -} - -public final class io/modelcontextprotocol/kotlin/sdk/PromptMessageContentMultimodal$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/PromptReference : io/modelcontextprotocol/kotlin/sdk/Reference { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/PromptReference$Companion; - public static final field TYPE Ljava/lang/String; - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/PromptReference; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/PromptReference;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/PromptReference; - public fun equals (Ljava/lang/Object;)Z - public final fun getName ()Ljava/lang/String; - public fun getType ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/PromptReference$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/PromptReference$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/PromptReference; - 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/PromptReference;)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/PromptReference$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ReadResourceRequest : io/modelcontextprotocol/kotlin/sdk/ClientRequest, io/modelcontextprotocol/kotlin/sdk/WithMeta { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ReadResourceRequest$Companion; - public fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (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/ReadResourceRequest; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/ReadResourceRequest;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/ReadResourceRequest; - public fun equals (Ljava/lang/Object;)Z - public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/Method; - public final fun getUri ()Ljava/lang/String; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/ReadResourceRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ReadResourceRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/ReadResourceRequest; - 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/ReadResourceRequest;)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/ReadResourceRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ReadResourceResult : io/modelcontextprotocol/kotlin/sdk/ServerResult { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ReadResourceResult$Companion; - public fun (Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Ljava/util/List;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getContents ()Ljava/util/List; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/ReadResourceResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ReadResourceResult$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/ReadResourceResult; - 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/ReadResourceResult;)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/ReadResourceResult$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public abstract interface class io/modelcontextprotocol/kotlin/sdk/Reference { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/Reference$Companion; - public abstract fun getType ()Ljava/lang/String; -} - -public final class io/modelcontextprotocol/kotlin/sdk/Reference$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public abstract interface class io/modelcontextprotocol/kotlin/sdk/Request { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/Request$Companion; - public abstract fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/Method; -} - -public final class io/modelcontextprotocol/kotlin/sdk/Request$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public abstract interface class io/modelcontextprotocol/kotlin/sdk/RequestId { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/RequestId$Companion; -} - -public final class io/modelcontextprotocol/kotlin/sdk/RequestId$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/RequestId$NumberId : io/modelcontextprotocol/kotlin/sdk/RequestId { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/RequestId$NumberId$Companion; - public fun (J)V - public final fun component1 ()J - public final fun copy (J)Lio/modelcontextprotocol/kotlin/sdk/RequestId$NumberId; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/RequestId$NumberId;JILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/RequestId$NumberId; - public fun equals (Ljava/lang/Object;)Z - public final fun getValue ()J - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/RequestId$NumberId$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/RequestId$NumberId$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/RequestId$NumberId; - 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/RequestId$NumberId;)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/RequestId$NumberId$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/RequestId$StringId : io/modelcontextprotocol/kotlin/sdk/RequestId { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/RequestId$StringId$Companion; - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/RequestId$StringId; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/RequestId$StringId;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/RequestId$StringId; - public fun equals (Ljava/lang/Object;)Z - public final fun getValue ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/RequestId$StringId$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/RequestId$StringId$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/RequestId$StringId; - 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/RequestId$StringId;)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/RequestId$StringId$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/RequestIdSerializer : kotlinx/serialization/KSerializer { - public fun ()V - public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/RequestId; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/RequestId;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V -} - -public abstract interface class io/modelcontextprotocol/kotlin/sdk/RequestResult : io/modelcontextprotocol/kotlin/sdk/WithMeta { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/RequestResult$Companion; -} - -public final class io/modelcontextprotocol/kotlin/sdk/RequestResult$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/Resource { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/Resource$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Lio/modelcontextprotocol/kotlin/sdk/Annotations;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Lio/modelcontextprotocol/kotlin/sdk/Annotations;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Ljava/lang/String; - public final fun component5 ()Ljava/lang/String; - public final fun component6 ()Ljava/lang/Long; - public final fun component7 ()Lio/modelcontextprotocol/kotlin/sdk/Annotations; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Lio/modelcontextprotocol/kotlin/sdk/Annotations;)Lio/modelcontextprotocol/kotlin/sdk/Resource; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/Resource;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Lio/modelcontextprotocol/kotlin/sdk/Annotations;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/Resource; - public fun equals (Ljava/lang/Object;)Z - public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/Annotations; - public final fun getDescription ()Ljava/lang/String; - public final fun getMimeType ()Ljava/lang/String; - public final fun getName ()Ljava/lang/String; - public final fun getSize ()Ljava/lang/Long; - public final fun getTitle ()Ljava/lang/String; - public final fun getUri ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/Resource$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/Resource$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/Resource; - 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/Resource;)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/Resource$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public abstract interface class io/modelcontextprotocol/kotlin/sdk/ResourceContents { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ResourceContents$Companion; - public abstract fun getMimeType ()Ljava/lang/String; - public abstract fun getUri ()Ljava/lang/String; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ResourceContents$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ResourceListChangedNotification : io/modelcontextprotocol/kotlin/sdk/ServerNotification { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ResourceListChangedNotification$Companion; - public fun ()V - public fun (Lio/modelcontextprotocol/kotlin/sdk/ResourceListChangedNotification$Params;)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/ResourceListChangedNotification$Params;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/ResourceListChangedNotification$Params; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/ResourceListChangedNotification$Params;)Lio/modelcontextprotocol/kotlin/sdk/ResourceListChangedNotification; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/ResourceListChangedNotification;Lio/modelcontextprotocol/kotlin/sdk/ResourceListChangedNotification$Params;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/ResourceListChangedNotification; - public fun equals (Ljava/lang/Object;)Z - public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/Method; - public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/NotificationParams; - public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/ResourceListChangedNotification$Params; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/ResourceListChangedNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ResourceListChangedNotification$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/ResourceListChangedNotification; - 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/ResourceListChangedNotification;)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/ResourceListChangedNotification$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ResourceListChangedNotification$Params : io/modelcontextprotocol/kotlin/sdk/NotificationParams { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ResourceListChangedNotification$Params$Companion; - public fun ()V - public fun (Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lkotlinx/serialization/json/JsonObject; - public final fun copy (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/ResourceListChangedNotification$Params; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/ResourceListChangedNotification$Params;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/ResourceListChangedNotification$Params; - public fun equals (Ljava/lang/Object;)Z - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/ResourceListChangedNotification$Params$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ResourceListChangedNotification$Params$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/ResourceListChangedNotification$Params; - 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/ResourceListChangedNotification$Params;)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/ResourceListChangedNotification$Params$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ResourceTemplate { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ResourceTemplate$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Annotations;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Annotations;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Ljava/lang/String; - public final fun component5 ()Ljava/lang/String; - public final fun component6 ()Lio/modelcontextprotocol/kotlin/sdk/Annotations; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Annotations;)Lio/modelcontextprotocol/kotlin/sdk/ResourceTemplate; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/ResourceTemplate;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Annotations;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/ResourceTemplate; - public fun equals (Ljava/lang/Object;)Z - public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/Annotations; - public final fun getDescription ()Ljava/lang/String; - public final fun getMimeType ()Ljava/lang/String; - public final fun getName ()Ljava/lang/String; - public final fun getTitle ()Ljava/lang/String; - public final fun getUriTemplate ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/ResourceTemplate$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ResourceTemplate$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/ResourceTemplate; - 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/ResourceTemplate;)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/ResourceTemplate$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ResourceTemplateReference : io/modelcontextprotocol/kotlin/sdk/Reference { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ResourceTemplateReference$Companion; - public static final field TYPE Ljava/lang/String; - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/ResourceTemplateReference; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/ResourceTemplateReference;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/ResourceTemplateReference; - public fun equals (Ljava/lang/Object;)Z - public fun getType ()Ljava/lang/String; - public final fun getUri ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/ResourceTemplateReference$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ResourceTemplateReference$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/ResourceTemplateReference; - 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/ResourceTemplateReference;)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/ResourceTemplateReference$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ResourceUpdatedNotification : io/modelcontextprotocol/kotlin/sdk/ServerNotification { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ResourceUpdatedNotification$Companion; - public fun (Lio/modelcontextprotocol/kotlin/sdk/ResourceUpdatedNotification$Params;)V - public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/ResourceUpdatedNotification$Params; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/ResourceUpdatedNotification$Params;)Lio/modelcontextprotocol/kotlin/sdk/ResourceUpdatedNotification; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/ResourceUpdatedNotification;Lio/modelcontextprotocol/kotlin/sdk/ResourceUpdatedNotification$Params;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/ResourceUpdatedNotification; - public fun equals (Ljava/lang/Object;)Z - public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/Method; - public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/NotificationParams; - public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/ResourceUpdatedNotification$Params; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/ResourceUpdatedNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ResourceUpdatedNotification$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/ResourceUpdatedNotification; - 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/ResourceUpdatedNotification;)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/ResourceUpdatedNotification$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ResourceUpdatedNotification$Params : io/modelcontextprotocol/kotlin/sdk/NotificationParams { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ResourceUpdatedNotification$Params$Companion; - public fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (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/ResourceUpdatedNotification$Params; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/ResourceUpdatedNotification$Params;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/ResourceUpdatedNotification$Params; - public fun equals (Ljava/lang/Object;)Z - public final fun getUri ()Ljava/lang/String; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/ResourceUpdatedNotification$Params$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ResourceUpdatedNotification$Params$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/ResourceUpdatedNotification$Params; - 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/ResourceUpdatedNotification$Params;)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/ResourceUpdatedNotification$Params$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/Role : java/lang/Enum { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/Role$Companion; - public static final field assistant Lio/modelcontextprotocol/kotlin/sdk/Role; - public static final field user Lio/modelcontextprotocol/kotlin/sdk/Role; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public static fun valueOf (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/Role; - public static fun values ()[Lio/modelcontextprotocol/kotlin/sdk/Role; -} - -public final class io/modelcontextprotocol/kotlin/sdk/Role$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/Root { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/Root$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/Root; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/Root;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/Root; - public fun equals (Ljava/lang/Object;)Z - public final fun getName ()Ljava/lang/String; - public final fun getUri ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/Root$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/Root$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/Root; - 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/Root;)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/Root$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/RootsListChangedNotification : io/modelcontextprotocol/kotlin/sdk/ClientNotification { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/RootsListChangedNotification$Companion; - public fun ()V - public fun (Lio/modelcontextprotocol/kotlin/sdk/RootsListChangedNotification$Params;)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/RootsListChangedNotification$Params;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/RootsListChangedNotification$Params; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/RootsListChangedNotification$Params;)Lio/modelcontextprotocol/kotlin/sdk/RootsListChangedNotification; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/RootsListChangedNotification;Lio/modelcontextprotocol/kotlin/sdk/RootsListChangedNotification$Params;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/RootsListChangedNotification; - public fun equals (Ljava/lang/Object;)Z - public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/Method; - public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/NotificationParams; - public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/RootsListChangedNotification$Params; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/RootsListChangedNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/RootsListChangedNotification$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/RootsListChangedNotification; - 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/RootsListChangedNotification;)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/RootsListChangedNotification$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/RootsListChangedNotification$Params : io/modelcontextprotocol/kotlin/sdk/NotificationParams { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/RootsListChangedNotification$Params$Companion; - public fun ()V - public fun (Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lkotlinx/serialization/json/JsonObject; - public final fun copy (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/RootsListChangedNotification$Params; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/RootsListChangedNotification$Params;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/RootsListChangedNotification$Params; - public fun equals (Ljava/lang/Object;)Z - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/RootsListChangedNotification$Params$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/RootsListChangedNotification$Params$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/RootsListChangedNotification$Params; - 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/RootsListChangedNotification$Params;)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/RootsListChangedNotification$Params$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/SamplingMessage { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/SamplingMessage$Companion; - public fun (Lio/modelcontextprotocol/kotlin/sdk/Role;Lio/modelcontextprotocol/kotlin/sdk/PromptMessageContentMultimodal;)V - public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/Role; - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/PromptMessageContentMultimodal; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/Role;Lio/modelcontextprotocol/kotlin/sdk/PromptMessageContentMultimodal;)Lio/modelcontextprotocol/kotlin/sdk/SamplingMessage; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/SamplingMessage;Lio/modelcontextprotocol/kotlin/sdk/Role;Lio/modelcontextprotocol/kotlin/sdk/PromptMessageContentMultimodal;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/SamplingMessage; - public fun equals (Ljava/lang/Object;)Z - public final fun getContent ()Lio/modelcontextprotocol/kotlin/sdk/PromptMessageContentMultimodal; - public final fun getRole ()Lio/modelcontextprotocol/kotlin/sdk/Role; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/SamplingMessage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/SamplingMessage$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/SamplingMessage; - 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/SamplingMessage;)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/SamplingMessage$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final class io/modelcontextprotocol/kotlin/sdk/RootsListChangedNotification { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/RootsListChangedNotification; + public final fun Params (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; + public static synthetic fun Params$default (Lio/modelcontextprotocol/kotlin/sdk/RootsListChangedNotification;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; } public final class io/modelcontextprotocol/kotlin/sdk/ServerCapabilities { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Companion; - public fun ()V - public fun (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Prompts;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Tools;)V - public synthetic fun (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Prompts;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Tools;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lkotlinx/serialization/json/JsonObject; - public final fun component2 ()Lkotlinx/serialization/json/JsonObject; - public final fun component3 ()Lkotlinx/serialization/json/JsonObject; - public final fun component4 ()Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Prompts; - public final fun component5 ()Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Resources; - public final fun component6 ()Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Tools; - public final fun copy (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Prompts;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Tools;)Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Prompts;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Tools;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities; - public fun equals (Ljava/lang/Object;)Z - public final fun getExperimental ()Lkotlinx/serialization/json/JsonObject; - public final fun getLogging ()Lkotlinx/serialization/json/JsonObject; - public final fun getPrompts ()Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Prompts; - public final fun getResources ()Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Resources; - public final fun getSampling ()Lkotlinx/serialization/json/JsonObject; - public final fun getTools ()Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Tools; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities; + public final fun Prompts (Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts; + public static synthetic fun Prompts$default (Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts; + public final fun Resources (Ljava/lang/Boolean;Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources; + public static synthetic fun Resources$default (Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities;Ljava/lang/Boolean;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources; + public final fun Tools (Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools; + public static synthetic fun Tools$default (Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools; } -public final synthetic class io/modelcontextprotocol/kotlin/sdk/ServerCapabilities$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities; - 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/ServerCapabilities;)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/Tool { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/Tool; + public final fun Input (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema; + public static synthetic fun Input$default (Lio/modelcontextprotocol/kotlin/sdk/Tool;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema; + public final fun Output (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema; + public static synthetic fun Output$default (Lio/modelcontextprotocol/kotlin/sdk/Tool;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema; } -public final class io/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Prompts { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Prompts$Companion; - public fun (Ljava/lang/Boolean;)V - public final fun component1 ()Ljava/lang/Boolean; - public final fun copy (Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Prompts; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Prompts;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Prompts; - public fun equals (Ljava/lang/Object;)Z - public final fun getListChanged ()Ljava/lang/Boolean; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Prompts$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Prompts$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Prompts; - 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/ServerCapabilities$Prompts;)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/ServerCapabilities$Prompts$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Resources { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Resources$Companion; - public fun (Ljava/lang/Boolean;Ljava/lang/Boolean;)V - public final fun component1 ()Ljava/lang/Boolean; - public final fun component2 ()Ljava/lang/Boolean; - public final fun copy (Ljava/lang/Boolean;Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Resources; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Resources;Ljava/lang/Boolean;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Resources; - public fun equals (Ljava/lang/Object;)Z - public final fun getListChanged ()Ljava/lang/Boolean; - public final fun getSubscribe ()Ljava/lang/Boolean; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Resources$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Resources$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Resources; - 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/ServerCapabilities$Resources;)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/ServerCapabilities$Resources$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Tools { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Tools$Companion; - public fun (Ljava/lang/Boolean;)V - public final fun component1 ()Ljava/lang/Boolean; - public final fun copy (Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Tools; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Tools;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Tools; - public fun equals (Ljava/lang/Object;)Z - public final fun getListChanged ()Ljava/lang/Boolean; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Tools$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Tools$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Tools; - 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/ServerCapabilities$Tools;)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/ServerCapabilities$Tools$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public abstract interface class io/modelcontextprotocol/kotlin/sdk/ServerNotification : io/modelcontextprotocol/kotlin/sdk/Notification { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ServerNotification$Companion; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ServerNotification$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public abstract interface class io/modelcontextprotocol/kotlin/sdk/ServerRequest : io/modelcontextprotocol/kotlin/sdk/Request { -} - -public abstract interface class io/modelcontextprotocol/kotlin/sdk/ServerResult : io/modelcontextprotocol/kotlin/sdk/RequestResult { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ServerResult$Companion; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ServerResult$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public abstract interface class io/modelcontextprotocol/kotlin/sdk/StopReason { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/StopReason$Companion; - public abstract fun getValue ()Ljava/lang/String; -} - -public final class io/modelcontextprotocol/kotlin/sdk/StopReason$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/StopReason$EndTurn : io/modelcontextprotocol/kotlin/sdk/StopReason { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/StopReason$EndTurn; - public fun equals (Ljava/lang/Object;)Z - public fun getValue ()Ljava/lang/String; - public fun hashCode ()I - public final fun serializer ()Lkotlinx/serialization/KSerializer; - public fun toString ()Ljava/lang/String; -} - -public final class io/modelcontextprotocol/kotlin/sdk/StopReason$MaxTokens : io/modelcontextprotocol/kotlin/sdk/StopReason { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/StopReason$MaxTokens; - public fun equals (Ljava/lang/Object;)Z - public fun getValue ()Ljava/lang/String; - public fun hashCode ()I - public final fun serializer ()Lkotlinx/serialization/KSerializer; - public fun toString ()Ljava/lang/String; -} - -public final class io/modelcontextprotocol/kotlin/sdk/StopReason$Other : io/modelcontextprotocol/kotlin/sdk/StopReason { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/StopReason$Other$Companion; - public static final synthetic fun box-impl (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/StopReason$Other; - public static fun constructor-impl (Ljava/lang/String;)Ljava/lang/String; - public fun equals (Ljava/lang/Object;)Z - public static fun equals-impl (Ljava/lang/String;Ljava/lang/Object;)Z - public static final fun equals-impl0 (Ljava/lang/String;Ljava/lang/String;)Z - public fun getValue ()Ljava/lang/String; - public fun hashCode ()I - public static fun hashCode-impl (Ljava/lang/String;)I - public fun toString ()Ljava/lang/String; - public static fun toString-impl (Ljava/lang/String;)Ljava/lang/String; - public final synthetic fun unbox-impl ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/StopReason$Other$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/StopReason$Other$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public final fun deserialize-vvjsQ-s (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/String; - public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public final fun serialize-AuY3eX0 (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/String;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/StopReason$Other$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/StopReason$StopSequence : io/modelcontextprotocol/kotlin/sdk/StopReason { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/StopReason$StopSequence; - public fun equals (Ljava/lang/Object;)Z - public fun getValue ()Ljava/lang/String; - public fun hashCode ()I - public final fun serializer ()Lkotlinx/serialization/KSerializer; - public fun toString ()Ljava/lang/String; -} - -public final class io/modelcontextprotocol/kotlin/sdk/SubscribeRequest : io/modelcontextprotocol/kotlin/sdk/ClientRequest, io/modelcontextprotocol/kotlin/sdk/WithMeta { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/SubscribeRequest$Companion; - public fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (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/SubscribeRequest; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/SubscribeRequest;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/SubscribeRequest; - public fun equals (Ljava/lang/Object;)Z - public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/Method; - public final fun getUri ()Ljava/lang/String; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/SubscribeRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/SubscribeRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/SubscribeRequest; - 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/SubscribeRequest;)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/SubscribeRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/TextContent : io/modelcontextprotocol/kotlin/sdk/PromptMessageContentMultimodal { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/TextContent$Companion; - public static final field TYPE Ljava/lang/String; - public fun ()V - public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Annotations;)V - public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Annotations;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/Annotations; - public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Annotations;)Lio/modelcontextprotocol/kotlin/sdk/TextContent; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/TextContent;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Annotations;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/TextContent; - public fun equals (Ljava/lang/Object;)Z - public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/Annotations; - public final fun getText ()Ljava/lang/String; - public fun getType ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/TextContent$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/TextContent$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/TextContent; - 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/TextContent;)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/TextContent$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/TextResourceContents : io/modelcontextprotocol/kotlin/sdk/ResourceContents { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/TextResourceContents$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/TextResourceContents; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/TextResourceContents;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/TextResourceContents; - public fun equals (Ljava/lang/Object;)Z - public fun getMimeType ()Ljava/lang/String; - public final fun getText ()Ljava/lang/String; - public fun getUri ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/TextResourceContents$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/TextResourceContents$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/TextResourceContents; - 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/TextResourceContents;)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/TextResourceContents$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/Tool : io/modelcontextprotocol/kotlin/sdk/WithMeta { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/Tool$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Lio/modelcontextprotocol/kotlin/sdk/Tool$Input; - public final fun component5 ()Lio/modelcontextprotocol/kotlin/sdk/Tool$Output; - public final fun component6 ()Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations; - public final fun component7 ()Lkotlinx/serialization/json/JsonObject; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/Tool; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/Tool;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/Tool; - public fun equals (Ljava/lang/Object;)Z - public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations; - public final fun getDescription ()Ljava/lang/String; - public final fun getInputSchema ()Lio/modelcontextprotocol/kotlin/sdk/Tool$Input; - public final fun getName ()Ljava/lang/String; - public final fun getOutputSchema ()Lio/modelcontextprotocol/kotlin/sdk/Tool$Output; - public final fun getTitle ()Ljava/lang/String; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/Tool$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/Tool$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/Tool; - 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/Tool;)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/Tool$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/Tool$Input { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/Tool$Input$Companion; - public fun ()V - public fun (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;)V - public synthetic fun (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lkotlinx/serialization/json/JsonObject; - public final fun component2 ()Ljava/util/List; - public final fun copy (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;)Lio/modelcontextprotocol/kotlin/sdk/Tool$Input; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/Tool$Input; - public fun equals (Ljava/lang/Object;)Z - public final fun getProperties ()Lkotlinx/serialization/json/JsonObject; - public final fun getRequired ()Ljava/util/List; - public final fun getType ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/Tool$Input$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/Tool$Input$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/Tool$Input; - 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/Tool$Input;)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/Tool$Input$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/Tool$Output { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/Tool$Output$Companion; - public fun ()V - public fun (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;)V - public synthetic fun (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lkotlinx/serialization/json/JsonObject; - public final fun component2 ()Ljava/util/List; - public final fun copy (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;)Lio/modelcontextprotocol/kotlin/sdk/Tool$Output; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/Tool$Output; - public fun equals (Ljava/lang/Object;)Z - public final fun getProperties ()Lkotlinx/serialization/json/JsonObject; - public final fun getRequired ()Ljava/util/List; - public final fun getType ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/Tool$Output$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/Tool$Output$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/Tool$Output; - 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/Tool$Output;)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/Tool$Output$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ToolAnnotations { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations$Companion; - public fun (Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/Boolean; - public final fun component3 ()Ljava/lang/Boolean; - public final fun component4 ()Ljava/lang/Boolean; - public final fun component5 ()Ljava/lang/Boolean; - public final fun copy (Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations; - public fun equals (Ljava/lang/Object;)Z - public final fun getDestructiveHint ()Ljava/lang/Boolean; - public final fun getIdempotentHint ()Ljava/lang/Boolean; - public final fun getOpenWorldHint ()Ljava/lang/Boolean; - public final fun getReadOnlyHint ()Ljava/lang/Boolean; - public final fun getTitle ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/ToolAnnotations$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations; - 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/ToolAnnotations;)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/ToolAnnotations$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ToolListChangedNotification : io/modelcontextprotocol/kotlin/sdk/ServerNotification { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ToolListChangedNotification$Companion; - public fun ()V - public fun (Lio/modelcontextprotocol/kotlin/sdk/ToolListChangedNotification$Params;)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/ToolListChangedNotification$Params;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/ToolListChangedNotification$Params; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/ToolListChangedNotification$Params;)Lio/modelcontextprotocol/kotlin/sdk/ToolListChangedNotification; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/ToolListChangedNotification;Lio/modelcontextprotocol/kotlin/sdk/ToolListChangedNotification$Params;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/ToolListChangedNotification; - public fun equals (Ljava/lang/Object;)Z - public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/Method; - public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/NotificationParams; - public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/ToolListChangedNotification$Params; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/ToolListChangedNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ToolListChangedNotification$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/ToolListChangedNotification; - 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/ToolListChangedNotification;)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/ToolListChangedNotification$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ToolListChangedNotification$Params : io/modelcontextprotocol/kotlin/sdk/NotificationParams { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ToolListChangedNotification$Params$Companion; - public fun ()V - public fun (Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lkotlinx/serialization/json/JsonObject; - public final fun copy (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/ToolListChangedNotification$Params; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/ToolListChangedNotification$Params;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/ToolListChangedNotification$Params; - public fun equals (Ljava/lang/Object;)Z - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/ToolListChangedNotification$Params$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ToolListChangedNotification$Params$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/ToolListChangedNotification$Params; - 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/ToolListChangedNotification$Params;)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/ToolListChangedNotification$Params$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final class io/modelcontextprotocol/kotlin/sdk/ToolListChangedNotification { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ToolListChangedNotification; + public final fun Params (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; + public static synthetic fun Params$default (Lio/modelcontextprotocol/kotlin/sdk/ToolListChangedNotification;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; } public final class io/modelcontextprotocol/kotlin/sdk/TypesKt { public static final field JSONRPC_VERSION Ljava/lang/String; public static final field LATEST_PROTOCOL_VERSION Ljava/lang/String; - public static final fun getSUPPORTED_PROTOCOL_VERSIONS ()[Ljava/lang/String; - public static final fun toJSON (Lio/modelcontextprotocol/kotlin/sdk/Notification;)Lio/modelcontextprotocol/kotlin/sdk/JSONRPCNotification; - public static final fun toJSON (Lio/modelcontextprotocol/kotlin/sdk/Request;)Lio/modelcontextprotocol/kotlin/sdk/JSONRPCRequest; + public static final fun CallToolResult (Ljava/util/List;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult; + public static synthetic fun CallToolResult$default (Ljava/util/List;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult; + public static final fun CancelledNotification (Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotification; + public static final fun ClientCapabilities (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities; + public static synthetic fun ClientCapabilities$default (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities; + public static final fun CompleteRequest (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequest; + public static final fun CompleteResult (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult$Completion;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult; + public static synthetic fun CompleteResult$default (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult$Completion;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult; + public static final fun CreateElicitationRequest (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequest; + public static final fun CreateElicitationResult (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Action;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult; + public static synthetic fun CreateElicitationResult$default (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Action;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult; + public static final fun CreateMessageRequest (Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest; + public static final fun InitializedNotification (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/types/InitializedNotification; + public static synthetic fun InitializedNotification$default (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/InitializedNotification; + public static final fun LoggingMessageNotification (Lio/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotification; + public static final fun ProgressNotification (Lio/modelcontextprotocol/kotlin/sdk/types/ProgressNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ProgressNotification; + public static final fun PromptListChangedNotification (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/types/PromptListChangedNotification; + public static final fun ResourceListChangedNotification (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ResourceListChangedNotification; + public static synthetic fun ResourceListChangedNotification$default (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ResourceListChangedNotification; + public static final fun ResourceUpdatedNotification (Lio/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotification; + public static final fun RootsListChangedNotification (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/types/RootsListChangedNotification; + public static synthetic fun RootsListChangedNotification$default (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/RootsListChangedNotification; + public static final fun ServerCapabilities (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities; + public static synthetic fun ServerCapabilities$default (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities; + public static final fun Tool (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/Tool; + public static synthetic fun Tool$default (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/Tool; + public static final fun ToolListChangedNotification (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolListChangedNotification; + public static final fun getSUPPORTED_PROTOCOL_VERSIONS ()Ljava/util/List; + public static final fun toJSON (Lio/modelcontextprotocol/kotlin/sdk/types/Notification;)Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCNotification; + public static final fun toJSON (Lio/modelcontextprotocol/kotlin/sdk/types/Request;)Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCRequest; } public final class io/modelcontextprotocol/kotlin/sdk/Types_utilKt { - public static final fun error (Lio/modelcontextprotocol/kotlin/sdk/CallToolResult$Companion;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/CallToolResult; - public static synthetic fun error$default (Lio/modelcontextprotocol/kotlin/sdk/CallToolResult$Companion;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/CallToolResult; + public static final fun error (Lio/modelcontextprotocol/kotlin/sdk/CallToolResult;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult; + public static synthetic fun error$default (Lio/modelcontextprotocol/kotlin/sdk/CallToolResult;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult; public static final fun getEmptyJsonObject ()Lkotlinx/serialization/json/JsonObject; - public static final fun ok (Lio/modelcontextprotocol/kotlin/sdk/CallToolResult$Companion;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/CallToolResult; - public static synthetic fun ok$default (Lio/modelcontextprotocol/kotlin/sdk/CallToolResult$Companion;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/CallToolResult; + public static final fun ok (Lio/modelcontextprotocol/kotlin/sdk/CallToolResult;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult; + public static synthetic fun ok$default (Lio/modelcontextprotocol/kotlin/sdk/CallToolResult;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult; } -public final class io/modelcontextprotocol/kotlin/sdk/UnknownContent : io/modelcontextprotocol/kotlin/sdk/PromptMessageContentMultimodal { +public final class io/modelcontextprotocol/kotlin/sdk/UnknownContent { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/UnknownContent$Companion; public fun (Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; public final fun copy (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/UnknownContent; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/UnknownContent;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/UnknownContent; public fun equals (Ljava/lang/Object;)Z - public fun getType ()Ljava/lang/String; + public final fun getType ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -3132,17 +344,17 @@ public final class io/modelcontextprotocol/kotlin/sdk/UnknownContent$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } -public final class io/modelcontextprotocol/kotlin/sdk/UnknownMethodRequestOrNotification : io/modelcontextprotocol/kotlin/sdk/ClientNotification, io/modelcontextprotocol/kotlin/sdk/ClientRequest, io/modelcontextprotocol/kotlin/sdk/ServerNotification, io/modelcontextprotocol/kotlin/sdk/ServerRequest { +public final class io/modelcontextprotocol/kotlin/sdk/UnknownMethodRequestOrNotification { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/UnknownMethodRequestOrNotification$Companion; - public fun (Lio/modelcontextprotocol/kotlin/sdk/Method;Lio/modelcontextprotocol/kotlin/sdk/NotificationParams;)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/Method;Lio/modelcontextprotocol/kotlin/sdk/NotificationParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lio/modelcontextprotocol/kotlin/sdk/Method;Lio/modelcontextprotocol/kotlin/sdk/types/NotificationParams;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/Method;Lio/modelcontextprotocol/kotlin/sdk/types/NotificationParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/Method; - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/NotificationParams; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/Method;Lio/modelcontextprotocol/kotlin/sdk/NotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/UnknownMethodRequestOrNotification; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/UnknownMethodRequestOrNotification;Lio/modelcontextprotocol/kotlin/sdk/Method;Lio/modelcontextprotocol/kotlin/sdk/NotificationParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/UnknownMethodRequestOrNotification; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/NotificationParams; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/Method;Lio/modelcontextprotocol/kotlin/sdk/types/NotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/UnknownMethodRequestOrNotification; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/UnknownMethodRequestOrNotification;Lio/modelcontextprotocol/kotlin/sdk/Method;Lio/modelcontextprotocol/kotlin/sdk/types/NotificationParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/UnknownMethodRequestOrNotification; public fun equals (Ljava/lang/Object;)Z - public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/Method; - public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/NotificationParams; + public final fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/Method; + public final fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/NotificationParams; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -3162,14 +374,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/UnknownMethodRequestOrNoti public final fun serializer ()Lkotlinx/serialization/KSerializer; } -public final class io/modelcontextprotocol/kotlin/sdk/UnknownReference : io/modelcontextprotocol/kotlin/sdk/Reference { +public final class io/modelcontextprotocol/kotlin/sdk/UnknownReference { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/UnknownReference$Companion; public fun (Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; public final fun copy (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/UnknownReference; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/UnknownReference;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/UnknownReference; public fun equals (Ljava/lang/Object;)Z - public fun getType ()Ljava/lang/String; + public final fun getType ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -3189,76 +401,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/UnknownReference$Companion public final fun serializer ()Lkotlinx/serialization/KSerializer; } -public final class io/modelcontextprotocol/kotlin/sdk/UnknownResourceContents : io/modelcontextprotocol/kotlin/sdk/ResourceContents { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/UnknownResourceContents$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/UnknownResourceContents; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/UnknownResourceContents;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/UnknownResourceContents; - public fun equals (Ljava/lang/Object;)Z - public fun getMimeType ()Ljava/lang/String; - public fun getUri ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/UnknownResourceContents$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/UnknownResourceContents$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/UnknownResourceContents; - 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/UnknownResourceContents;)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/UnknownResourceContents$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/UnsubscribeRequest : io/modelcontextprotocol/kotlin/sdk/ClientRequest, io/modelcontextprotocol/kotlin/sdk/WithMeta { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/UnsubscribeRequest$Companion; - public fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (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/UnsubscribeRequest; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/UnsubscribeRequest;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/UnsubscribeRequest; - public fun equals (Ljava/lang/Object;)Z - public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/Method; - public final fun getUri ()Ljava/lang/String; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/UnsubscribeRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/UnsubscribeRequest$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/UnsubscribeRequest; - 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/UnsubscribeRequest;)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/UnsubscribeRequest$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public abstract interface class io/modelcontextprotocol/kotlin/sdk/WithMeta { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/WithMeta$Companion; - public abstract fun get_meta ()Lkotlinx/serialization/json/JsonObject; -} - -public final class io/modelcontextprotocol/kotlin/sdk/WithMeta$Companion { - public final fun getEmpty ()Lio/modelcontextprotocol/kotlin/sdk/CustomMeta; - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - public final class io/modelcontextprotocol/kotlin/sdk/internal/Utils_jvmKt { public static final fun getIODispatcher ()Lkotlinx/coroutines/CoroutineDispatcher; } @@ -3275,9 +417,9 @@ public abstract class io/modelcontextprotocol/kotlin/sdk/shared/AbstractTranspor public abstract class io/modelcontextprotocol/kotlin/sdk/shared/Protocol { public fun (Lio/modelcontextprotocol/kotlin/sdk/shared/ProtocolOptions;)V - protected abstract fun assertCapabilityForMethod (Lio/modelcontextprotocol/kotlin/sdk/Method;)V - protected abstract fun assertNotificationCapability (Lio/modelcontextprotocol/kotlin/sdk/Method;)V - public abstract fun assertRequestHandlerCapability (Lio/modelcontextprotocol/kotlin/sdk/Method;)V + protected abstract fun assertCapabilityForMethod (Lio/modelcontextprotocol/kotlin/sdk/types/Method;)V + protected abstract fun assertNotificationCapability (Lio/modelcontextprotocol/kotlin/sdk/types/Method;)V + public abstract fun assertRequestHandlerCapability (Lio/modelcontextprotocol/kotlin/sdk/types/Method;)V public final fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun connect (Lio/modelcontextprotocol/kotlin/sdk/shared/Transport;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun getFallbackNotificationHandler ()Lkotlin/jvm/functions/Function2; @@ -3288,17 +430,17 @@ 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/Notification;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun notification (Lio/modelcontextprotocol/kotlin/sdk/types/Notification;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun onClose ()V public fun onError (Ljava/lang/Throwable;)V - public final fun removeNotificationHandler (Lio/modelcontextprotocol/kotlin/sdk/Method;)V - public final fun removeRequestHandler (Lio/modelcontextprotocol/kotlin/sdk/Method;)V - public final fun request (Lio/modelcontextprotocol/kotlin/sdk/Request;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun request$default (Lio/modelcontextprotocol/kotlin/sdk/shared/Protocol;Lio/modelcontextprotocol/kotlin/sdk/Request;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun removeNotificationHandler (Lio/modelcontextprotocol/kotlin/sdk/types/Method;)V + public final fun removeRequestHandler (Lio/modelcontextprotocol/kotlin/sdk/types/Method;)V + public final fun request (Lio/modelcontextprotocol/kotlin/sdk/types/Request;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun request$default (Lio/modelcontextprotocol/kotlin/sdk/shared/Protocol;Lio/modelcontextprotocol/kotlin/sdk/types/Request;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun setFallbackNotificationHandler (Lkotlin/jvm/functions/Function2;)V public final fun setFallbackRequestHandler (Lkotlin/jvm/functions/Function3;)V - public final fun setNotificationHandler (Lio/modelcontextprotocol/kotlin/sdk/Method;Lkotlin/jvm/functions/Function1;)V - public final fun setRequestHandler (Lkotlin/reflect/KType;Lio/modelcontextprotocol/kotlin/sdk/Method;Lkotlin/jvm/functions/Function3;)V + public final fun setNotificationHandler (Lio/modelcontextprotocol/kotlin/sdk/types/Method;Lkotlin/jvm/functions/Function1;)V + public final fun setRequestHandlerInternal (Lio/modelcontextprotocol/kotlin/sdk/types/Method;Lkotlin/jvm/functions/Function3;)V } public final class io/modelcontextprotocol/kotlin/sdk/shared/ProtocolKt { @@ -3320,11 +462,11 @@ public final class io/modelcontextprotocol/kotlin/sdk/shared/ReadBuffer { public fun ()V public final fun append ([B)V public final fun clear ()V - public final fun readMessage ()Lio/modelcontextprotocol/kotlin/sdk/JSONRPCMessage; + public final fun readMessage ()Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage; } public final class io/modelcontextprotocol/kotlin/sdk/shared/ReadBufferKt { - public static final fun serializeMessage (Lio/modelcontextprotocol/kotlin/sdk/JSONRPCMessage;)Ljava/lang/String; + public static final fun serializeMessage (Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage;)Ljava/lang/String; } public final class io/modelcontextprotocol/kotlin/sdk/shared/RequestHandlerExtra { @@ -3350,7 +492,7 @@ 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/JSONRPCMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun send (Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun start (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } @@ -3359,7 +501,7 @@ public abstract class io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTran 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/JSONRPCMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun send (Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun start (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } @@ -3367,3 +509,3671 @@ public final class io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTranspo public static final field MCP_SUBPROTOCOL Ljava/lang/String; } +public final class io/modelcontextprotocol/kotlin/sdk/types/Annotations { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Annotations$Companion; + public fun ()V + public fun (Ljava/util/List;Ljava/lang/Double;Ljava/lang/String;)V + public synthetic fun (Ljava/util/List;Ljava/lang/Double;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/lang/Double; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/util/List;Ljava/lang/Double;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/Annotations; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Ljava/util/List;Ljava/lang/Double;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/Annotations; + public fun equals (Ljava/lang/Object;)Z + public final fun getAudience ()Ljava/util/List; + public final fun getLastModified ()Ljava/lang/String; + public final fun getPriority ()Ljava/lang/Double; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/Annotations$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/Annotations$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/Annotations; + 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/Annotations;)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/Annotations$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/AudioContent : io/modelcontextprotocol/kotlin/sdk/types/MediaContent { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/AudioContent$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/types/Annotations; + public final fun component4 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/AudioContent; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/AudioContent;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/AudioContent; + public fun equals (Ljava/lang/Object;)Z + public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/types/Annotations; + public final fun getData ()Ljava/lang/String; + public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public final fun getMimeType ()Ljava/lang/String; + public fun getType ()Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/AudioContent$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/AudioContent$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/AudioContent; + 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/AudioContent;)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/AudioContent$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams : io/modelcontextprotocol/kotlin/sdk/types/NotificationParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams$Companion; + public fun ()V + public fun (Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; + public fun equals (Ljava/lang/Object;)Z + 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/BaseNotificationParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; + 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/BaseNotificationParams;)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/BaseNotificationParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams$Companion; + public synthetic fun (Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun copy-mJxasco (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams; + public static synthetic fun copy-mJxasco$default (Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams; + public fun equals (Ljava/lang/Object;)Z + public fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams; + 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/BaseRequestParams;)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/BaseRequestParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/BlobResourceContents : io/modelcontextprotocol/kotlin/sdk/types/ResourceContents { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/BlobResourceContents$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/BlobResourceContents; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/BlobResourceContents;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/BlobResourceContents; + public fun equals (Ljava/lang/Object;)Z + public final fun getBlob ()Ljava/lang/String; + public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public fun getMimeType ()Ljava/lang/String; + public fun getUri ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/BlobResourceContents$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/BlobResourceContents$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/BlobResourceContents; + 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/BlobResourceContents;)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/BlobResourceContents$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/CallToolRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequest$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequest;Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequest; + public fun equals (Ljava/lang/Object;)Z + public final fun getArguments ()Lkotlinx/serialization/json/JsonObject; + public final fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public final fun getName ()Ljava/lang/String; + public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams; + public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/CallToolRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequest; + 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/CallToolRequest;)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/CallToolRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams$Companion; + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lkotlinx/serialization/json/JsonObject; + public final fun component3-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun copy-EDEVuBg (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams; + public static synthetic fun copy-EDEVuBg$default (Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams; + public fun equals (Ljava/lang/Object;)Z + public final fun getArguments ()Lkotlinx/serialization/json/JsonObject; + public fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun getName ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams; + 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/CallToolRequestParams;)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/CallToolRequestParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/CallToolResult : io/modelcontextprotocol/kotlin/sdk/types/ServerResult { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult$Companion; + public fun (Ljava/util/List;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/util/List;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/lang/Boolean; + public final fun component3 ()Lkotlinx/serialization/json/JsonObject; + public final fun component4 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Ljava/util/List;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult;Ljava/util/List;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult; + public fun equals (Ljava/lang/Object;)Z + public final fun getContent ()Ljava/util/List; + public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public final fun getStructuredContent ()Lkotlinx/serialization/json/JsonObject; + public fun hashCode ()I + public final fun isError ()Ljava/lang/Boolean; + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/CallToolResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult; + 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/CallToolResult;)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/CallToolResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/CancelledNotification : io/modelcontextprotocol/kotlin/sdk/types/ClientNotification, io/modelcontextprotocol/kotlin/sdk/types/ServerNotification { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotification$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotificationParams;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotificationParams; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotification; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotification;Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotificationParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotification; + public fun equals (Ljava/lang/Object;)Z + public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotificationParams; + 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/CancelledNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotification$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotification; + 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/CancelledNotification;)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/CancelledNotification$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/CancelledNotificationParams : io/modelcontextprotocol/kotlin/sdk/types/NotificationParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotificationParams$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;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 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotificationParams; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotificationParams;Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotificationParams; + public fun equals (Ljava/lang/Object;)Z + public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public final fun getReason ()Ljava/lang/String; + public final fun getRequestId ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestId; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/CancelledNotificationParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotificationParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotificationParams; + 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/CancelledNotificationParams;)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/CancelledNotificationParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Companion; + public fun ()V + public fun (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lkotlinx/serialization/json/JsonObject; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots; + public final fun component3 ()Lkotlinx/serialization/json/JsonObject; + public final fun component4 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities; + public fun equals (Ljava/lang/Object;)Z + public final fun getElicitation ()Lkotlinx/serialization/json/JsonObject; + public final fun getExperimental ()Lkotlinx/serialization/json/JsonObject; + public final fun getRoots ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots; + public final fun getSampling ()Lkotlinx/serialization/json/JsonObject; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities; + 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/ClientCapabilities;)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/ClientCapabilities$Companion { + public final fun getElicitation ()Lkotlinx/serialization/json/JsonObject; + public final fun getSampling ()Lkotlinx/serialization/json/JsonObject; + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots$Companion; + public fun ()V + public fun (Ljava/lang/Boolean;)V + public synthetic fun (Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Boolean; + public final fun copy (Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots; + public fun equals (Ljava/lang/Object;)Z + public final fun getListChanged ()Ljava/lang/Boolean; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots; + 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/ClientCapabilities$Roots;)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/ClientCapabilities$Roots$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/ClientNotification : io/modelcontextprotocol/kotlin/sdk/types/Notification { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ClientNotification$Companion; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ClientNotification$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/ClientRequest : io/modelcontextprotocol/kotlin/sdk/types/Request { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ClientRequest$Companion; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ClientRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/ClientResult : io/modelcontextprotocol/kotlin/sdk/types/RequestResult { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ClientResult$Companion; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ClientResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/CommonKt { + public static final field LATEST_PROTOCOL_VERSION Ljava/lang/String; + public static final fun ProgressToken (J)Lio/modelcontextprotocol/kotlin/sdk/types/RequestId; + public static final fun ProgressToken (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/RequestId; + public static final fun getSUPPORTED_PROTOCOL_VERSIONS ()Ljava/util/List; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/CompleteRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequest$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequest;Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequest; + public fun equals (Ljava/lang/Object;)Z + public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams; + public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/CompleteRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequest; + 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/CompleteRequest;)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/CompleteRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Companion; + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Argument;Lio/modelcontextprotocol/kotlin/sdk/types/Reference;Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Context;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Argument;Lio/modelcontextprotocol/kotlin/sdk/types/Reference;Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Context;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Argument; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/Reference; + public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Context; + public final fun component4-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun copy-6R815Ik (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Argument;Lio/modelcontextprotocol/kotlin/sdk/types/Reference;Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Context;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams; + public static synthetic fun copy-6R815Ik$default (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams;Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Argument;Lio/modelcontextprotocol/kotlin/sdk/types/Reference;Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Context;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams; + public fun equals (Ljava/lang/Object;)Z + public final fun getArgument ()Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Argument; + public final fun getContext ()Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Context; + public fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun getRef ()Lio/modelcontextprotocol/kotlin/sdk/types/Reference; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams; + 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/CompleteRequestParams;)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/CompleteRequestParams$Argument { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Argument$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Argument; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Argument;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Argument; + public fun equals (Ljava/lang/Object;)Z + public final fun getName ()Ljava/lang/String; + public final fun getValue ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Argument$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Argument$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Argument; + 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/CompleteRequestParams$Argument;)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/CompleteRequestParams$Argument$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Context { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Context$Companion; + public fun ()V + public fun (Ljava/util/Map;)V + public synthetic fun (Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/Map; + public final fun copy (Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Context; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Context;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Context; + public fun equals (Ljava/lang/Object;)Z + public final fun getArguments ()Ljava/util/Map; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Context$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Context$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Context; + 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/CompleteRequestParams$Context;)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/CompleteRequestParams$Context$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/CompleteResult : io/modelcontextprotocol/kotlin/sdk/types/ServerResult { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult$Completion;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult$Completion;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult$Completion; + public final fun component2 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult$Completion;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult;Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult$Completion;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult; + public fun equals (Ljava/lang/Object;)Z + public final fun getCompletion ()Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult$Completion; + 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/CompleteResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult; + 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/CompleteResult;)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/CompleteResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/CompleteResult$Completion { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult$Completion$Companion; + public fun (Ljava/util/List;Ljava/lang/Integer;Ljava/lang/Boolean;)V + public synthetic fun (Ljava/util/List;Ljava/lang/Integer;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/lang/Integer; + public final fun component3 ()Ljava/lang/Boolean; + public final fun copy (Ljava/util/List;Ljava/lang/Integer;Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult$Completion; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult$Completion;Ljava/util/List;Ljava/lang/Integer;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult$Completion; + public fun equals (Ljava/lang/Object;)Z + public final fun getHasMore ()Ljava/lang/Boolean; + public final fun getTotal ()Ljava/lang/Integer; + public final fun getValues ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/CompleteResult$Completion$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult$Completion$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult$Completion; + 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/CompleteResult$Completion;)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/CompleteResult$Completion$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/ContentBlock : io/modelcontextprotocol/kotlin/sdk/types/WithMeta { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ContentBlock$Companion; + public abstract fun getType ()Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ContentBlock$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ContentTypes : java/lang/Enum { + public static final field AUDIO Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes$Companion; + public static final field EMBEDDED_RESOURCE Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; + public static final field IMAGE Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; + public static final field RESOURCE_LINK Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; + public static final field TEXT Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public final fun getValue ()Ljava/lang/String; + public static fun valueOf (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; + public static fun values ()[Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ContentTypes$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest : io/modelcontextprotocol/kotlin/sdk/types/ServerRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest;Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest; + public fun equals (Ljava/lang/Object;)Z + public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams; + public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest; + 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/CreateMessageRequest;)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/CreateMessageRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams$Companion; + public synthetic fun (ILjava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext;Ljava/lang/Double;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (ILjava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext;Ljava/lang/Double;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()I + public final fun component2 ()Ljava/util/List; + public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext; + public final fun component6 ()Ljava/lang/Double; + public final fun component7 ()Ljava/util/List; + public final fun component8 ()Lkotlinx/serialization/json/JsonObject; + public final fun component9-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun copy-qtvSJzw (ILjava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext;Ljava/lang/Double;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams; + public static synthetic fun copy-qtvSJzw$default (Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams;ILjava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext;Ljava/lang/Double;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams; + public fun equals (Ljava/lang/Object;)Z + public final fun getIncludeContext ()Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext; + public final fun getMaxTokens ()I + public final fun getMessages ()Ljava/util/List; + public fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun getMetadata ()Lkotlinx/serialization/json/JsonObject; + public final fun getModelPreferences ()Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences; + public final fun getStopSequences ()Ljava/util/List; + public final fun getSystemPrompt ()Ljava/lang/String; + public final fun getTemperature ()Ljava/lang/Double; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams; + 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/CreateMessageRequestParams;)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/CreateMessageRequestParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageResult : io/modelcontextprotocol/kotlin/sdk/types/ClientResult { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageResult$Companion; + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/Role; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent; + public final fun component3 ()Ljava/lang/String; + public final fun component4-6olV-UY ()Ljava/lang/String; + public final fun component5 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy-ctVX1tw (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageResult; + public static synthetic fun copy-ctVX1tw$default (Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageResult;Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageResult; + public fun equals (Ljava/lang/Object;)Z + public final fun getContent ()Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent; + public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public final fun getModel ()Ljava/lang/String; + public final fun getRole ()Lio/modelcontextprotocol/kotlin/sdk/types/Role; + public final fun getStopReason-6olV-UY ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageResult; + 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/CreateMessageResult;)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/CreateMessageResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/CustomNotification : io/modelcontextprotocol/kotlin/sdk/types/ClientNotification, io/modelcontextprotocol/kotlin/sdk/types/ServerNotification { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CustomNotification$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/Method;Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/Method;Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/Method;Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/types/CustomNotification; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/CustomNotification;Lio/modelcontextprotocol/kotlin/sdk/types/Method;Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CustomNotification; + public fun equals (Ljava/lang/Object;)Z + public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; + 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/CustomNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/CustomNotification$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/CustomNotification; + 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/CustomNotification;)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/CustomNotification$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public class io/modelcontextprotocol/kotlin/sdk/types/CustomRequest : io/modelcontextprotocol/kotlin/sdk/types/Request { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CustomRequest$Companion; + public synthetic fun (ILio/modelcontextprotocol/kotlin/sdk/types/Method;Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/Method;Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams;)V + public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams; + public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; + public static final synthetic fun write$Self (Lio/modelcontextprotocol/kotlin/sdk/types/CustomRequest;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/CustomRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/CustomRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/CustomRequest; + 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/CustomRequest;)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/CustomRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequest : io/modelcontextprotocol/kotlin/sdk/types/ServerRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequest$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequest;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequest; + public fun equals (Ljava/lang/Object;)Z + public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams; + public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequest; + 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/ElicitRequest;)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/ElicitRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$Companion; + public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema; + public final fun component3-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun copy-EDEVuBg (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams; + public static synthetic fun copy-EDEVuBg$default (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams; + public fun equals (Ljava/lang/Object;)Z + public final fun getMessage ()Ljava/lang/String; + public fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun getRequestedSchema ()Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams; + 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/ElicitRequestParams;)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/ElicitRequestParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema$Companion; + public fun (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;)V + public synthetic fun (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lkotlinx/serialization/json/JsonObject; + public final fun component2 ()Ljava/util/List; + public final fun copy (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema; + public fun equals (Ljava/lang/Object;)Z + public final fun getProperties ()Lkotlinx/serialization/json/JsonObject; + public final fun getRequired ()Ljava/util/List; + public final fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema; + 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/ElicitRequestParams$RequestedSchema;)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/ElicitRequestParams$RequestedSchema$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitResult : io/modelcontextprotocol/kotlin/sdk/types/ClientResult { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Action;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Action;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Action; + public final fun component2 ()Lkotlinx/serialization/json/JsonObject; + public final fun component3 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Action;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Action;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult; + public fun equals (Ljava/lang/Object;)Z + public final fun getAction ()Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Action; + public final fun getContent ()Lkotlinx/serialization/json/JsonObject; + 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/ElicitResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult; + 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/ElicitResult;)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/ElicitResult$Action : java/lang/Enum { + public static final field Accept Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Action; + public static final field Cancel Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Action; + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Action$Companion; + public static final field Decline Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Action; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Action; + public static fun values ()[Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Action; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Action$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/EmbeddedResource : io/modelcontextprotocol/kotlin/sdk/types/ContentBlock { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/EmbeddedResource$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ResourceContents;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/ResourceContents;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/ResourceContents; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/Annotations; + public final fun component3 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/ResourceContents;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/EmbeddedResource; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/EmbeddedResource;Lio/modelcontextprotocol/kotlin/sdk/types/ResourceContents;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/EmbeddedResource; + public fun equals (Ljava/lang/Object;)Z + public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/types/Annotations; + public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public final fun getResource ()Lio/modelcontextprotocol/kotlin/sdk/types/ResourceContents; + public fun getType ()Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/EmbeddedResource$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/EmbeddedResource$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/EmbeddedResource; + 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/EmbeddedResource;)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/EmbeddedResource$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/EmptyResult : io/modelcontextprotocol/kotlin/sdk/types/ClientResult, io/modelcontextprotocol/kotlin/sdk/types/ServerResult { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/EmptyResult$Companion; + public fun ()V + public fun (Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/EmptyResult; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/EmptyResult;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/EmptyResult; + public fun equals (Ljava/lang/Object;)Z + 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/EmptyResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/EmptyResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/EmptyResult; + 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/EmptyResult;)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/EmptyResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequestParams;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequestParams; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest;Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest; + public fun equals (Ljava/lang/Object;)Z + public final fun getArguments ()Ljava/util/Map; + public final fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public final fun getName ()Ljava/lang/String; + public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequestParams; + public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest; + 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/GetPromptRequest;)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/GetPromptRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/GetPromptRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequestParams$Companion; + public synthetic fun (Ljava/lang/String;Ljava/util/Map;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Ljava/util/Map;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/Map; + public final fun component3-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun copy-EDEVuBg (Ljava/lang/String;Ljava/util/Map;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequestParams; + public static synthetic fun copy-EDEVuBg$default (Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequestParams;Ljava/lang/String;Ljava/util/Map;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequestParams; + public fun equals (Ljava/lang/Object;)Z + public final fun getArguments ()Ljava/util/Map; + public fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun getName ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/GetPromptRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequestParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequestParams; + 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/GetPromptRequestParams;)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/GetPromptRequestParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/GetPromptResult : io/modelcontextprotocol/kotlin/sdk/types/ServerResult { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptResult$Companion; + public fun (Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptResult; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptResult;Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptResult; + public fun equals (Ljava/lang/Object;)Z + public final fun getDescription ()Ljava/lang/String; + public final fun getMessages ()Ljava/util/List; + 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/GetPromptResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptResult; + 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/GetPromptResult;)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/GetPromptResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/Icon { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Icon$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/Icon$Theme;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/Icon$Theme;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/util/List; + public final fun component4 ()Lio/modelcontextprotocol/kotlin/sdk/types/Icon$Theme; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/Icon$Theme;)Lio/modelcontextprotocol/kotlin/sdk/types/Icon; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/Icon;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/Icon$Theme;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/Icon; + public fun equals (Ljava/lang/Object;)Z + public final fun getMimeType ()Ljava/lang/String; + public final fun getSizes ()Ljava/util/List; + public final fun getSrc ()Ljava/lang/String; + public final fun getTheme ()Lio/modelcontextprotocol/kotlin/sdk/types/Icon$Theme; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/Icon$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/Icon$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/Icon; + 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/Icon;)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/Icon$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/Icon$Theme : java/lang/Enum { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Icon$Theme$Companion; + public static final field Dark Lio/modelcontextprotocol/kotlin/sdk/types/Icon$Theme; + public static final field Light Lio/modelcontextprotocol/kotlin/sdk/types/Icon$Theme; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/Icon$Theme; + public static fun values ()[Lio/modelcontextprotocol/kotlin/sdk/types/Icon$Theme; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/Icon$Theme$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ImageContent : io/modelcontextprotocol/kotlin/sdk/types/MediaContent { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ImageContent$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/types/Annotations; + public final fun component4 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ImageContent; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ImageContent;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ImageContent; + public fun equals (Ljava/lang/Object;)Z + public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/types/Annotations; + public final fun getData ()Ljava/lang/String; + public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public final fun getMimeType ()Ljava/lang/String; + public fun getType ()Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ImageContent$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ImageContent$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ImageContent; + 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/ImageContent;)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/ImageContent$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/Implementation { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Implementation$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/util/List; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)Lio/modelcontextprotocol/kotlin/sdk/types/Implementation; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/Implementation;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/Implementation; + public fun equals (Ljava/lang/Object;)Z + public final fun getIcons ()Ljava/util/List; + public final fun getName ()Ljava/lang/String; + public final fun getTitle ()Ljava/lang/String; + public final fun getVersion ()Ljava/lang/String; + public final fun getWebsiteUrl ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/Implementation$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/Implementation$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/Implementation; + 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/Implementation;)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/Implementation$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/IncludeContext : java/lang/Enum { + public static final field AllServers Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext; + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext$Companion; + public static final field None Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext; + public static final field ThisServer Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext; + public static fun values ()[Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/IncludeContext$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/InitializeRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequest$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequestParams;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequestParams; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequest;Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequest; + public fun equals (Ljava/lang/Object;)Z + public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequestParams; + public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/InitializeRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequest; + 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/InitializeRequest;)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/InitializeRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/InitializeRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequestParams$Companion; + public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;Lio/modelcontextprotocol/kotlin/sdk/types/Implementation;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;Lio/modelcontextprotocol/kotlin/sdk/types/Implementation;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities; + public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/types/Implementation; + public final fun component4-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun copy-6R815Ik (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;Lio/modelcontextprotocol/kotlin/sdk/types/Implementation;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequestParams; + public static synthetic fun copy-6R815Ik$default (Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequestParams;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;Lio/modelcontextprotocol/kotlin/sdk/types/Implementation;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequestParams; + public fun equals (Ljava/lang/Object;)Z + public final fun getCapabilities ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities; + public final fun getClientInfo ()Lio/modelcontextprotocol/kotlin/sdk/types/Implementation; + public fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun getProtocolVersion ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/InitializeRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequestParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequestParams; + 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/InitializeRequestParams;)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/InitializeRequestParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/InitializeResult : io/modelcontextprotocol/kotlin/sdk/types/ServerResult { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/InitializeResult$Companion; + public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;Lio/modelcontextprotocol/kotlin/sdk/types/Implementation;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;Lio/modelcontextprotocol/kotlin/sdk/types/Implementation;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities; + public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/types/Implementation; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;Lio/modelcontextprotocol/kotlin/sdk/types/Implementation;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/InitializeResult; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/InitializeResult;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;Lio/modelcontextprotocol/kotlin/sdk/types/Implementation;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/InitializeResult; + public fun equals (Ljava/lang/Object;)Z + public final fun getCapabilities ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities; + public final fun getInstructions ()Ljava/lang/String; + public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public final fun getProtocolVersion ()Ljava/lang/String; + public final fun getServerInfo ()Lio/modelcontextprotocol/kotlin/sdk/types/Implementation; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/InitializeResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/InitializeResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/InitializeResult; + 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/InitializeResult;)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/InitializeResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/InitializedNotification : io/modelcontextprotocol/kotlin/sdk/types/ClientNotification { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/InitializedNotification$Companion; + public fun ()V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/types/InitializedNotification; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/InitializedNotification;Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/InitializedNotification; + public fun equals (Ljava/lang/Object;)Z + public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; + 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/InitializedNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/InitializedNotification$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/InitializedNotification; + 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/InitializedNotification;)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/InitializedNotification$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/JSONRPCError : io/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCError$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Lio/modelcontextprotocol/kotlin/sdk/types/RPCError;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestId; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/RPCError; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Lio/modelcontextprotocol/kotlin/sdk/types/RPCError;)Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCError; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCError;Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Lio/modelcontextprotocol/kotlin/sdk/types/RPCError;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCError; + public fun equals (Ljava/lang/Object;)Z + public final fun getError ()Lio/modelcontextprotocol/kotlin/sdk/types/RPCError; + public final fun getId ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestId; + public fun getJsonrpc ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/JSONRPCError$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCError$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCError; + 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/JSONRPCError;)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/JSONRPCError$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage$Companion; + public abstract fun getJsonrpc ()Ljava/lang/String; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/JSONRPCNotification : io/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCNotification$Companion; + public fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;)V + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lkotlinx/serialization/json/JsonElement; + public final fun copy (Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;)Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCNotification; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCNotification;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCNotification; + public fun equals (Ljava/lang/Object;)Z + public fun getJsonrpc ()Ljava/lang/String; + public final fun getMethod ()Ljava/lang/String; + public final fun getParams ()Lkotlinx/serialization/json/JsonElement; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/JSONRPCNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCNotification$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCNotification; + 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/JSONRPCNotification;)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/JSONRPCNotification$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/JSONRPCRequest : io/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCRequest$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;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 ()Lkotlinx/serialization/json/JsonElement; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;)Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCRequest;Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCRequest; + public fun equals (Ljava/lang/Object;)Z + public final fun getId ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestId; + public fun getJsonrpc ()Ljava/lang/String; + public final fun getMethod ()Ljava/lang/String; + public final fun getParams ()Lkotlinx/serialization/json/JsonElement; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/JSONRPCRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCRequest; + 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/JSONRPCRequest;)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/JSONRPCRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/JSONRPCResponse : io/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCResponse$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Lio/modelcontextprotocol/kotlin/sdk/types/RequestResult;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Lio/modelcontextprotocol/kotlin/sdk/types/RequestResult;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestId; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestResult; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Lio/modelcontextprotocol/kotlin/sdk/types/RequestResult;)Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCResponse; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCResponse;Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Lio/modelcontextprotocol/kotlin/sdk/types/RequestResult;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCResponse; + public fun equals (Ljava/lang/Object;)Z + public final fun getId ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestId; + public fun getJsonrpc ()Ljava/lang/String; + public final fun getResult ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestResult; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/JSONRPCResponse$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCResponse$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCResponse; + 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/JSONRPCResponse;)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/JSONRPCResponse$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/JsonRpcKt { + public static final field JSONRPC_VERSION Ljava/lang/String; + public static final fun RequestId (J)Lio/modelcontextprotocol/kotlin/sdk/types/RequestId; + public static final fun RequestId (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/RequestId; + public static final fun fromJSON (Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCRequest;)Lio/modelcontextprotocol/kotlin/sdk/types/Request; + public static final fun toJSON (Lio/modelcontextprotocol/kotlin/sdk/types/Notification;)Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCNotification; + public static final fun toJSON (Lio/modelcontextprotocol/kotlin/sdk/types/Request;)Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCRequest; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/JsonUtilsKt { + public static final fun getEmptyJsonObject ()Lkotlinx/serialization/json/JsonObject; + public static final fun getMcpJson ()Lkotlinx/serialization/json/Json; + public static final fun toJson (Ljava/util/Map;)Ljava/util/Map; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest, io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest$Companion; + public fun ()V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest;Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest; + public fun equals (Ljava/lang/Object;)Z + public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams; + public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest; + 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/ListPromptsRequest;)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/ListPromptsRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ListPromptsResult : io/modelcontextprotocol/kotlin/sdk/types/PaginatedResult, io/modelcontextprotocol/kotlin/sdk/types/ServerResult { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsResult$Companion; + public fun (Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsResult; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsResult;Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsResult; + public fun equals (Ljava/lang/Object;)Z + public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public fun getNextCursor ()Ljava/lang/String; + public final fun getPrompts ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ListPromptsResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsResult; + 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/ListPromptsResult;)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/ListPromptsResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest, io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesRequest$Companion; + public fun ()V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesRequest;Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesRequest; + public fun equals (Ljava/lang/Object;)Z + public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams; + public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesRequest; + 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/ListResourceTemplatesRequest;)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/ListResourceTemplatesRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesResult : io/modelcontextprotocol/kotlin/sdk/types/PaginatedResult, io/modelcontextprotocol/kotlin/sdk/types/ServerResult { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesResult$Companion; + public fun (Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesResult; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesResult;Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesResult; + public fun equals (Ljava/lang/Object;)Z + public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public fun getNextCursor ()Ljava/lang/String; + public final fun getResourceTemplates ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesResult; + 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/ListResourceTemplatesResult;)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/ListResourceTemplatesResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest, io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest$Companion; + public fun ()V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest;Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest; + public fun equals (Ljava/lang/Object;)Z + public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams; + public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest; + 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/ListResourcesRequest;)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/ListResourcesRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourcesResult : io/modelcontextprotocol/kotlin/sdk/types/PaginatedResult, io/modelcontextprotocol/kotlin/sdk/types/ServerResult { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesResult$Companion; + public fun (Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesResult; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesResult;Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesResult; + public fun equals (Ljava/lang/Object;)Z + public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public fun getNextCursor ()Ljava/lang/String; + public final fun getResources ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ListResourcesResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesResult; + 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/ListResourcesResult;)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/ListResourcesResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest : io/modelcontextprotocol/kotlin/sdk/types/ServerRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest$Companion; + public fun ()V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest;Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest; + public fun equals (Ljava/lang/Object;)Z + public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams; + public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest; + 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/ListRootsRequest;)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/ListRootsRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ListRootsResult : io/modelcontextprotocol/kotlin/sdk/types/ClientResult { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsResult$Companion; + public fun (Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/util/List;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsResult; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsResult;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsResult; + public fun equals (Ljava/lang/Object;)Z + public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public final fun getRoots ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ListRootsResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsResult; + 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/ListRootsResult;)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/ListRootsResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest, io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest$Companion; + public fun ()V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest;Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest; + public fun equals (Ljava/lang/Object;)Z + public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams; + public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest; + 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/ListToolsRequest;)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/ListToolsRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ListToolsResult : io/modelcontextprotocol/kotlin/sdk/types/PaginatedResult, io/modelcontextprotocol/kotlin/sdk/types/ServerResult { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsResult$Companion; + public fun (Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsResult; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsResult;Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsResult; + public fun equals (Ljava/lang/Object;)Z + public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public fun getNextCursor ()Ljava/lang/String; + public final fun getTools ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ListToolsResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsResult; + 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/ListToolsResult;)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/ListToolsResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/LoggingLevel : java/lang/Enum { + public static final field Alert Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel$Companion; + public static final field Critical Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; + public static final field Debug Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; + public static final field Emergency Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; + public static final field Error Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; + public static final field Info Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; + public static final field Notice Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; + public static final field Warning Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; + public static fun values ()[Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/LoggingLevel$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotification : io/modelcontextprotocol/kotlin/sdk/types/ServerNotification { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotification$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotificationParams;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotificationParams; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotification; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotification;Lio/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotificationParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotification; + public fun equals (Ljava/lang/Object;)Z + public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotificationParams; + 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/LoggingMessageNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotification$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotification; + 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/LoggingMessageNotification;)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/LoggingMessageNotification$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotificationParams : io/modelcontextprotocol/kotlin/sdk/types/NotificationParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotificationParams$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel;Lkotlinx/serialization/json/JsonElement;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel;Lkotlinx/serialization/json/JsonElement;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; + public final fun component2 ()Lkotlinx/serialization/json/JsonElement; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel;Lkotlinx/serialization/json/JsonElement;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotificationParams; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotificationParams;Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel;Lkotlinx/serialization/json/JsonElement;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotificationParams; + public fun equals (Ljava/lang/Object;)Z + public final fun getData ()Lkotlinx/serialization/json/JsonElement; + public final fun getLevel ()Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; + public final fun getLogger ()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/LoggingMessageNotificationParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotificationParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotificationParams; + 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/LoggingMessageNotificationParams;)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/LoggingMessageNotificationParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/McpException : java/lang/Exception { + public fun (ILjava/lang/String;Lkotlinx/serialization/json/JsonElement;)V + public synthetic fun (ILjava/lang/String;Lkotlinx/serialization/json/JsonElement;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getCode ()I + public final fun getData ()Lkotlinx/serialization/json/JsonElement; + public fun getMessage ()Ljava/lang/String; +} + +public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/MediaContent : io/modelcontextprotocol/kotlin/sdk/types/ContentBlock { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent$Companion; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/MediaContent$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/Method { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Method$Companion; + public abstract fun getValue ()Ljava/lang/String; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/Method$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/Method$Custom : io/modelcontextprotocol/kotlin/sdk/types/Method { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Method$Custom$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/Method$Custom; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/Method$Custom;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/Method$Custom; + public fun equals (Ljava/lang/Object;)Z + public fun getValue ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/Method$Custom$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/Method$Custom$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/Method$Custom; + 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/Method$Custom;)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/Method$Custom$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/Method$Defined : java/lang/Enum, io/modelcontextprotocol/kotlin/sdk/types/Method { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined$Companion; + public static final field CompletionComplete Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public static final field ElicitationCreate Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + 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 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; + public static final field NotificationsPromptsListChanged Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public static final field NotificationsResourcesListChanged Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public static final field NotificationsResourcesUpdated Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public static final field NotificationsRootsListChanged Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public static final field NotificationsToolsListChanged Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public static final field Ping Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public static final field PromptsGet Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public static final field PromptsList Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public static final field ResourcesList Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public static final field ResourcesRead Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public static final field ResourcesSubscribe Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public static final field ResourcesTemplatesList Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public static final field ResourcesUnsubscribe Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public static final field RootsList Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public static final field SamplingCreateMessage Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public static final field ToolsCall Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public static final field ToolsList Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public fun getValue ()Ljava/lang/String; + public static fun valueOf (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public static fun values ()[Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/Method$Defined$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ModelHint { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ModelHint$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/ModelHint; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ModelHint;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ModelHint; + public fun equals (Ljava/lang/Object;)Z + public final fun getName ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ModelHint$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ModelHint$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ModelHint; + 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/ModelHint;)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/ModelHint$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ModelPreferences { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences$Companion; + public fun ()V + public fun (Ljava/util/List;Ljava/lang/Double;Ljava/lang/Double;Ljava/lang/Double;)V + public synthetic fun (Ljava/util/List;Ljava/lang/Double;Ljava/lang/Double;Ljava/lang/Double;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/lang/Double; + public final fun component3 ()Ljava/lang/Double; + public final fun component4 ()Ljava/lang/Double; + public final fun copy (Ljava/util/List;Ljava/lang/Double;Ljava/lang/Double;Ljava/lang/Double;)Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences;Ljava/util/List;Ljava/lang/Double;Ljava/lang/Double;Ljava/lang/Double;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences; + public fun equals (Ljava/lang/Object;)Z + public final fun getCostPriority ()Ljava/lang/Double; + public final fun getHints ()Ljava/util/List; + public final fun getIntelligencePriority ()Ljava/lang/Double; + public final fun getSpeedPriority ()Ljava/lang/Double; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ModelPreferences$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences; + 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/ModelPreferences;)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/ModelPreferences$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/Notification { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Notification$Companion; + public abstract fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public abstract fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/NotificationParams; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/Notification$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/NotificationParams : io/modelcontextprotocol/kotlin/sdk/types/WithMeta { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/NotificationParams$Companion; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/NotificationParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequest : io/modelcontextprotocol/kotlin/sdk/types/Request { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequest$Companion; + public abstract fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams$Companion; + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun copy-D-90kyg (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams; + public static synthetic fun copy-D-90kyg$default (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams; + public fun equals (Ljava/lang/Object;)Z + public final fun getCursor ()Ljava/lang/String; + public fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams; + 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/PaginatedRequestParams;)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/PaginatedRequestParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/PaginatedResult : io/modelcontextprotocol/kotlin/sdk/types/RequestResult { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedResult$Companion; + public abstract fun getNextCursor ()Ljava/lang/String; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/PaginatedResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/PingRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest, io/modelcontextprotocol/kotlin/sdk/types/ServerRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/PingRequest$Companion; + public fun ()V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/PingRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/PingRequest;Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/PingRequest; + public fun equals (Ljava/lang/Object;)Z + public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams; + public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/PingRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/PingRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/PingRequest; + 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/PingRequest;)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/PingRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/Progress { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Progress$Companion; + public fun (DLjava/lang/Double;Ljava/lang/String;)V + public synthetic fun (DLjava/lang/Double;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getMessage ()Ljava/lang/String; + public final fun getProgress ()D + public final fun getTotal ()Ljava/lang/Double; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/Progress$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/Progress$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/Progress; + 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/Progress;)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/Progress$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ProgressNotification : io/modelcontextprotocol/kotlin/sdk/types/ClientNotification, io/modelcontextprotocol/kotlin/sdk/types/ServerNotification { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ProgressNotification$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ProgressNotificationParams;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/ProgressNotificationParams; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/ProgressNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ProgressNotification; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ProgressNotification;Lio/modelcontextprotocol/kotlin/sdk/types/ProgressNotificationParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ProgressNotification; + public fun equals (Ljava/lang/Object;)Z + public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/NotificationParams; + public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/ProgressNotificationParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ProgressNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ProgressNotification$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ProgressNotification; + 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/ProgressNotification;)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/ProgressNotification$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ProgressNotificationParams : io/modelcontextprotocol/kotlin/sdk/types/NotificationParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ProgressNotificationParams$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;DLjava/lang/Double;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;DLjava/lang/Double;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestId; + public final fun component2 ()D + public final fun component3 ()Ljava/lang/Double; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;DLjava/lang/Double;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ProgressNotificationParams; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ProgressNotificationParams;Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;DLjava/lang/Double;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ProgressNotificationParams; + public fun equals (Ljava/lang/Object;)Z + public final fun getMessage ()Ljava/lang/String; + public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public final fun getProgress ()D + public final fun getProgressToken ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestId; + public final fun getTotal ()Ljava/lang/Double; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ProgressNotificationParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ProgressNotificationParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ProgressNotificationParams; + 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/ProgressNotificationParams;)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/ProgressNotificationParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/Prompt : io/modelcontextprotocol/kotlin/sdk/types/WithMeta { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Prompt$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/util/List; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/util/List; + public final fun component6 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/Prompt; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/Prompt;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/Prompt; + public fun equals (Ljava/lang/Object;)Z + public final fun getArguments ()Ljava/util/List; + public final fun getDescription ()Ljava/lang/String; + public final fun getIcons ()Ljava/util/List; + public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public final fun getName ()Ljava/lang/String; + public final fun getTitle ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/Prompt$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/Prompt$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/Prompt; + 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/Prompt;)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/Prompt$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/PromptArgument { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/PromptArgument$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/Boolean; + public final fun component4 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/PromptArgument; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/PromptArgument;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/PromptArgument; + public fun equals (Ljava/lang/Object;)Z + public final fun getDescription ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getRequired ()Ljava/lang/Boolean; + public final fun getTitle ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/PromptArgument$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/PromptArgument$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/PromptArgument; + 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/PromptArgument;)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/PromptArgument$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/PromptListChangedNotification : io/modelcontextprotocol/kotlin/sdk/types/ServerNotification { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/PromptListChangedNotification$Companion; + public fun ()V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/types/PromptListChangedNotification; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/PromptListChangedNotification;Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/PromptListChangedNotification; + public fun equals (Ljava/lang/Object;)Z + public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; + 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/PromptListChangedNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/PromptListChangedNotification$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/PromptListChangedNotification; + 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/PromptListChangedNotification;)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/PromptListChangedNotification$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/PromptMessage { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/PromptMessage$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/ContentBlock;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/Role; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/ContentBlock; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/ContentBlock;)Lio/modelcontextprotocol/kotlin/sdk/types/PromptMessage; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/PromptMessage;Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/ContentBlock;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/PromptMessage; + public fun equals (Ljava/lang/Object;)Z + public final fun getContent ()Lio/modelcontextprotocol/kotlin/sdk/types/ContentBlock; + public final fun getRole ()Lio/modelcontextprotocol/kotlin/sdk/types/Role; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/PromptMessage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/PromptMessage$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/PromptMessage; + 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/PromptMessage;)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/PromptMessage$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/PromptReference : io/modelcontextprotocol/kotlin/sdk/types/Reference { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/PromptReference$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/PromptReference; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/PromptReference;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/PromptReference; + public fun equals (Ljava/lang/Object;)Z + public final fun getName ()Ljava/lang/String; + public final fun getTitle ()Ljava/lang/String; + public fun getType ()Lio/modelcontextprotocol/kotlin/sdk/types/ReferenceType; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/PromptReference$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/PromptReference$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/PromptReference; + 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/PromptReference;)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/PromptReference$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/RPCError { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/RPCError$Companion; + public fun (ILjava/lang/String;Lkotlinx/serialization/json/JsonElement;)V + public synthetic fun (ILjava/lang/String;Lkotlinx/serialization/json/JsonElement;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()I + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Lkotlinx/serialization/json/JsonElement; + public final fun copy (ILjava/lang/String;Lkotlinx/serialization/json/JsonElement;)Lio/modelcontextprotocol/kotlin/sdk/types/RPCError; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/RPCError;ILjava/lang/String;Lkotlinx/serialization/json/JsonElement;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/RPCError; + public fun equals (Ljava/lang/Object;)Z + public final fun getCode ()I + public final fun getData ()Lkotlinx/serialization/json/JsonElement; + public final fun getMessage ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/RPCError$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/RPCError$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/RPCError; + 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/RPCError;)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/RPCError$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/RPCError$ErrorCode { + public static final field CONNECTION_CLOSED I + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/RPCError$ErrorCode; + public static final field INTERNAL_ERROR I + public static final field INVALID_PARAMS I + public static final field INVALID_REQUEST I + public static final field METHOD_NOT_FOUND I + public static final field PARSE_ERROR I + public static final field REQUEST_TIMEOUT I +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequestParams;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequestParams; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest;Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest; + public fun equals (Ljava/lang/Object;)Z + public final fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequestParams; + public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; + public final fun getUri ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest; + 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/ReadResourceRequest;)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/ReadResourceRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequestParams$Companion; + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun copy-D-90kyg (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequestParams; + public static synthetic fun copy-D-90kyg$default (Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequestParams;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequestParams; + public fun equals (Ljava/lang/Object;)Z + public fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun getUri ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequestParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequestParams; + 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/ReadResourceRequestParams;)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/ReadResourceRequestParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ReadResourceResult : io/modelcontextprotocol/kotlin/sdk/types/ServerResult { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceResult$Companion; + public fun (Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/util/List;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceResult; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceResult;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceResult; + public fun equals (Ljava/lang/Object;)Z + public final fun getContents ()Ljava/util/List; + 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/ReadResourceResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceResult; + 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/ReadResourceResult;)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/ReadResourceResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/Reference { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Reference$Companion; + public abstract fun getType ()Lio/modelcontextprotocol/kotlin/sdk/types/ReferenceType; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/Reference$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ReferenceType : java/lang/Enum { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ReferenceType$Companion; + public static final field Prompt Lio/modelcontextprotocol/kotlin/sdk/types/ReferenceType; + public static final field ResourceTemplate Lio/modelcontextprotocol/kotlin/sdk/types/ReferenceType; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public final fun getValue ()Ljava/lang/String; + public static fun valueOf (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/ReferenceType; + public static fun values ()[Lio/modelcontextprotocol/kotlin/sdk/types/ReferenceType; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ReferenceType$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/Request { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Request$Companion; + public abstract fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public abstract fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/Request$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/RequestId { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/RequestId$Companion; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/RequestId$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/RequestId$NumberId : io/modelcontextprotocol/kotlin/sdk/types/RequestId { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/RequestId$NumberId$Companion; + public static final synthetic fun box-impl (J)Lio/modelcontextprotocol/kotlin/sdk/types/RequestId$NumberId; + public static fun constructor-impl (J)J + public fun equals (Ljava/lang/Object;)Z + public static fun equals-impl (JLjava/lang/Object;)Z + public static final fun equals-impl0 (JJ)Z + public final fun getValue ()J + public fun hashCode ()I + public static fun hashCode-impl (J)I + public fun toString ()Ljava/lang/String; + public static fun toString-impl (J)Ljava/lang/String; + public final synthetic fun unbox-impl ()J +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/RequestId$NumberId$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/RequestId$NumberId$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun deserialize-Nt6Vbyc (Lkotlinx/serialization/encoding/Decoder;)J + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public final fun serialize-T5IKJsA (Lkotlinx/serialization/encoding/Encoder;J)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/RequestId$NumberId$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/RequestId$StringId : io/modelcontextprotocol/kotlin/sdk/types/RequestId { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/RequestId$StringId$Companion; + public static final synthetic fun box-impl (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/RequestId$StringId; + public static fun constructor-impl (Ljava/lang/String;)Ljava/lang/String; + public fun equals (Ljava/lang/Object;)Z + public static fun equals-impl (Ljava/lang/String;Ljava/lang/Object;)Z + public static final fun equals-impl0 (Ljava/lang/String;Ljava/lang/String;)Z + public final fun getValue ()Ljava/lang/String; + public fun hashCode ()I + public static fun hashCode-impl (Ljava/lang/String;)I + public fun toString ()Ljava/lang/String; + public static fun toString-impl (Ljava/lang/String;)Ljava/lang/String; + public final synthetic fun unbox-impl ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/RequestId$StringId$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/RequestId$StringId$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun deserialize-QNUQp7g (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/String; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public final fun serialize-i2Jt6-s (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/String;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/RequestId$StringId$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/RequestMeta { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/RequestMeta$Companion; + public static final synthetic fun box-impl (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/RequestMeta; + public static fun constructor-impl (Lkotlinx/serialization/json/JsonObject;)Lkotlinx/serialization/json/JsonObject; + public fun equals (Ljava/lang/Object;)Z + public static fun equals-impl (Lkotlinx/serialization/json/JsonObject;Ljava/lang/Object;)Z + public static final fun equals-impl0 (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Z + public static final fun get-impl (Lkotlinx/serialization/json/JsonObject;Ljava/lang/String;)Lkotlinx/serialization/json/JsonElement; + public final fun getJson ()Lkotlinx/serialization/json/JsonObject; + public static final fun getProgressToken-impl (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/RequestId; + public fun hashCode ()I + public static fun hashCode-impl (Lkotlinx/serialization/json/JsonObject;)I + public fun toString ()Ljava/lang/String; + public static fun toString-impl (Lkotlinx/serialization/json/JsonObject;)Ljava/lang/String; + public final synthetic fun unbox-impl ()Lkotlinx/serialization/json/JsonObject; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/RequestMeta$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/RequestMeta$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun deserialize-6WR2xV8 (Lkotlinx/serialization/encoding/Decoder;)Lkotlinx/serialization/json/JsonObject; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public final fun serialize-qqtNtwg (Lkotlinx/serialization/encoding/Encoder;Lkotlinx/serialization/json/JsonObject;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/RequestMeta$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/RequestParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams$Companion; + public abstract fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/RequestParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/RequestResult : io/modelcontextprotocol/kotlin/sdk/types/WithMeta { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/RequestResult$Companion; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/RequestResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/Resource : io/modelcontextprotocol/kotlin/sdk/types/ResourceLike { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Resource$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/Long; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Lio/modelcontextprotocol/kotlin/sdk/types/Annotations; + public final fun component8 ()Ljava/util/List; + public final fun component9 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/Resource; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/Resource;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/Resource; + public fun equals (Ljava/lang/Object;)Z + public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/types/Annotations; + public final fun getDescription ()Ljava/lang/String; + public final fun getIcons ()Ljava/util/List; + public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public final fun getMimeType ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getSize ()Ljava/lang/Long; + public final fun getTitle ()Ljava/lang/String; + public final fun getUri ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/Resource$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/Resource$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/Resource; + 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/Resource;)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/Resource$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/ResourceContents : io/modelcontextprotocol/kotlin/sdk/types/WithMeta { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ResourceContents$Companion; + public abstract fun getMimeType ()Ljava/lang/String; + public abstract fun getUri ()Ljava/lang/String; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ResourceContents$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/ResourceLike : io/modelcontextprotocol/kotlin/sdk/types/WithMeta { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ResourceLike$Companion; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ResourceLike$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ResourceLink : io/modelcontextprotocol/kotlin/sdk/types/ContentBlock, io/modelcontextprotocol/kotlin/sdk/types/ResourceLike { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ResourceLink$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/Long; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/util/List; + public final fun component7 ()Ljava/lang/String; + public final fun component8 ()Lio/modelcontextprotocol/kotlin/sdk/types/Annotations; + public final fun component9 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ResourceLink; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ResourceLink;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ResourceLink; + public fun equals (Ljava/lang/Object;)Z + public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/types/Annotations; + public final fun getDescription ()Ljava/lang/String; + public final fun getIcons ()Ljava/util/List; + public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public final fun getMimeType ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getSize ()Ljava/lang/Long; + public final fun getTitle ()Ljava/lang/String; + public fun getType ()Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; + public final fun getUri ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ResourceLink$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ResourceLink$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ResourceLink; + 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/ResourceLink;)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/ResourceLink$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ResourceListChangedNotification : io/modelcontextprotocol/kotlin/sdk/types/ServerNotification { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ResourceListChangedNotification$Companion; + public fun ()V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ResourceListChangedNotification; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ResourceListChangedNotification;Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ResourceListChangedNotification; + public fun equals (Ljava/lang/Object;)Z + public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; + 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/ResourceListChangedNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ResourceListChangedNotification$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ResourceListChangedNotification; + 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/ResourceListChangedNotification;)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/ResourceListChangedNotification$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ResourceTemplate : io/modelcontextprotocol/kotlin/sdk/types/WithMeta { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ResourceTemplate$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Lio/modelcontextprotocol/kotlin/sdk/types/Annotations; + public final fun component7 ()Ljava/util/List; + public final fun component8 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ResourceTemplate; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ResourceTemplate;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ResourceTemplate; + public fun equals (Ljava/lang/Object;)Z + public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/types/Annotations; + public final fun getDescription ()Ljava/lang/String; + public final fun getIcons ()Ljava/util/List; + public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public final fun getMimeType ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getTitle ()Ljava/lang/String; + public final fun getUriTemplate ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ResourceTemplate$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ResourceTemplate$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ResourceTemplate; + 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/ResourceTemplate;)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/ResourceTemplate$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ResourceTemplateReference : io/modelcontextprotocol/kotlin/sdk/types/Reference { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ResourceTemplateReference$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/ResourceTemplateReference; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ResourceTemplateReference;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ResourceTemplateReference; + public fun equals (Ljava/lang/Object;)Z + public fun getType ()Lio/modelcontextprotocol/kotlin/sdk/types/ReferenceType; + public final fun getUri ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ResourceTemplateReference$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ResourceTemplateReference$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ResourceTemplateReference; + 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/ResourceTemplateReference;)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/ResourceTemplateReference$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotification : io/modelcontextprotocol/kotlin/sdk/types/ServerNotification { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotification$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotificationParams;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotificationParams; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotification; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotification;Lio/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotificationParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotification; + public fun equals (Ljava/lang/Object;)Z + public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/NotificationParams; + public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotificationParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotification$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotification; + 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/ResourceUpdatedNotification;)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/ResourceUpdatedNotification$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotificationParams : io/modelcontextprotocol/kotlin/sdk/types/NotificationParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotificationParams$Companion; + public fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (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/ResourceUpdatedNotificationParams; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotificationParams;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotificationParams; + public fun equals (Ljava/lang/Object;)Z + public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public final fun getUri ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotificationParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotificationParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotificationParams; + 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/ResourceUpdatedNotificationParams;)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/ResourceUpdatedNotificationParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/Role : java/lang/Enum { + public static final field Assistant Lio/modelcontextprotocol/kotlin/sdk/types/Role; + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Role$Companion; + public static final field User Lio/modelcontextprotocol/kotlin/sdk/types/Role; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/Role; + public static fun values ()[Lio/modelcontextprotocol/kotlin/sdk/types/Role; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/Role$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/Root : io/modelcontextprotocol/kotlin/sdk/types/WithMeta { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Root$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/Root; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/Root;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/Root; + public fun equals (Ljava/lang/Object;)Z + public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public final fun getName ()Ljava/lang/String; + public final fun getUri ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/Root$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/Root$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/Root; + 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/Root;)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/Root$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/RootsListChangedNotification : io/modelcontextprotocol/kotlin/sdk/types/ClientNotification { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/RootsListChangedNotification$Companion; + public fun ()V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/types/RootsListChangedNotification; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/RootsListChangedNotification;Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/RootsListChangedNotification; + public fun equals (Ljava/lang/Object;)Z + public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; + 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/RootsListChangedNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/RootsListChangedNotification$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/RootsListChangedNotification; + 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/RootsListChangedNotification;)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/RootsListChangedNotification$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/SamplingMessage { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessage$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/Role; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;)Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessage; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessage;Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessage; + public fun equals (Ljava/lang/Object;)Z + public final fun getContent ()Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent; + public final fun getRole ()Lio/modelcontextprotocol/kotlin/sdk/types/Role; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/SamplingMessage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessage$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessage; + 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/SamplingMessage;)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/SamplingMessage$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Companion; + public fun ()V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources; + public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts; + public final fun component4 ()Lkotlinx/serialization/json/JsonObject; + public final fun component5 ()Lkotlinx/serialization/json/JsonObject; + public final fun component6 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities; + public fun equals (Ljava/lang/Object;)Z + public final fun getCompletions ()Lkotlinx/serialization/json/JsonObject; + public final fun getExperimental ()Lkotlinx/serialization/json/JsonObject; + public final fun getLogging ()Lkotlinx/serialization/json/JsonObject; + public final fun getPrompts ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts; + public final fun getResources ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources; + public final fun getTools ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities; + 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/ServerCapabilities;)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/ServerCapabilities$Companion { + public final fun getCompletions ()Lkotlinx/serialization/json/JsonObject; + public final fun getLogging ()Lkotlinx/serialization/json/JsonObject; + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts$Companion; + public fun ()V + public fun (Ljava/lang/Boolean;)V + public synthetic fun (Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Boolean; + public final fun copy (Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts; + public fun equals (Ljava/lang/Object;)Z + public final fun getListChanged ()Ljava/lang/Boolean; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts; + 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/ServerCapabilities$Prompts;)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/ServerCapabilities$Prompts$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources$Companion; + public fun ()V + public fun (Ljava/lang/Boolean;Ljava/lang/Boolean;)V + public synthetic fun (Ljava/lang/Boolean;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Boolean; + public final fun component2 ()Ljava/lang/Boolean; + public final fun copy (Ljava/lang/Boolean;Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Ljava/lang/Boolean;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources; + public fun equals (Ljava/lang/Object;)Z + public final fun getListChanged ()Ljava/lang/Boolean; + public final fun getSubscribe ()Ljava/lang/Boolean; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources; + 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/ServerCapabilities$Resources;)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/ServerCapabilities$Resources$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools$Companion; + public fun ()V + public fun (Ljava/lang/Boolean;)V + public synthetic fun (Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Boolean; + public final fun copy (Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools; + public fun equals (Ljava/lang/Object;)Z + public final fun getListChanged ()Ljava/lang/Boolean; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools; + 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/ServerCapabilities$Tools;)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/ServerCapabilities$Tools$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/ServerNotification : io/modelcontextprotocol/kotlin/sdk/types/Notification { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ServerNotification$Companion; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ServerNotification$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/ServerRequest : io/modelcontextprotocol/kotlin/sdk/types/Request { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ServerRequest$Companion; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ServerRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/ServerResult : io/modelcontextprotocol/kotlin/sdk/types/RequestResult { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ServerResult$Companion; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ServerResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/SetLevelRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequest$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequestParams;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequestParams; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequest;Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequest; + public fun equals (Ljava/lang/Object;)Z + public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; + public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequestParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/SetLevelRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequest; + 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/SetLevelRequest;)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/SetLevelRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/SetLevelRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequestParams$Companion; + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; + public final fun component2-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun copy-D-90kyg (Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequestParams; + public static synthetic fun copy-D-90kyg$default (Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequestParams;Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequestParams; + public fun equals (Ljava/lang/Object;)Z + public final fun getLevel ()Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; + public fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/SetLevelRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequestParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequestParams; + 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/SetLevelRequestParams;)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/SetLevelRequestParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/StopReason { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/StopReason$Companion; + public static final synthetic fun box-impl (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/StopReason; + public static fun constructor-impl (Ljava/lang/String;)Ljava/lang/String; + public fun equals (Ljava/lang/Object;)Z + public static fun equals-impl (Ljava/lang/String;Ljava/lang/Object;)Z + public static final fun equals-impl0 (Ljava/lang/String;Ljava/lang/String;)Z + public final fun getValue ()Ljava/lang/String; + public fun hashCode ()I + public static fun hashCode-impl (Ljava/lang/String;)I + public fun toString ()Ljava/lang/String; + public static fun toString-impl (Ljava/lang/String;)Ljava/lang/String; + public final synthetic fun unbox-impl ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/StopReason$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/StopReason$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun deserialize-_6z6jHk (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/String; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public final fun serialize-OgWWgxw (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/String;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/StopReason$Companion { + public final fun getEndTurn--MeDoko ()Ljava/lang/String; + public final fun getMaxTokens--MeDoko ()Ljava/lang/String; + public final fun getStopSequence--MeDoko ()Ljava/lang/String; + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequestParams;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequestParams; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest;Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest; + public fun equals (Ljava/lang/Object;)Z + public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; + public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequestParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest; + 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/SubscribeRequest;)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/SubscribeRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/SubscribeRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequestParams$Companion; + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun copy-D-90kyg (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequestParams; + public static synthetic fun copy-D-90kyg$default (Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequestParams;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequestParams; + public fun equals (Ljava/lang/Object;)Z + public fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun getUri ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/SubscribeRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequestParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequestParams; + 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/SubscribeRequestParams;)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/SubscribeRequestParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/TextContent : io/modelcontextprotocol/kotlin/sdk/types/MediaContent { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/TextContent$Companion; + public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/Annotations; + public final fun component3 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/TextContent; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/TextContent;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/TextContent; + public fun equals (Ljava/lang/Object;)Z + public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/types/Annotations; + public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public final fun getText ()Ljava/lang/String; + public fun getType ()Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/TextContent$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/TextContent$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/TextContent; + 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/TextContent;)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/TextContent$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/TextResourceContents : io/modelcontextprotocol/kotlin/sdk/types/ResourceContents { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/TextResourceContents$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/TextResourceContents; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/TextResourceContents;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/TextResourceContents; + public fun equals (Ljava/lang/Object;)Z + public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public fun getMimeType ()Ljava/lang/String; + public final fun getText ()Ljava/lang/String; + public fun getUri ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/TextResourceContents$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/TextResourceContents$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/TextResourceContents; + 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/TextResourceContents;)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/TextResourceContents$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/Tool : io/modelcontextprotocol/kotlin/sdk/types/WithMeta { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Tool$Companion; + public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations; + public final fun component7 ()Ljava/util/List; + public final fun component8 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/Tool; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/Tool;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/Tool; + public fun equals (Ljava/lang/Object;)Z + public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations; + public final fun getDescription ()Ljava/lang/String; + public final fun getIcons ()Ljava/util/List; + public final fun getInputSchema ()Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema; + public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public final fun getName ()Ljava/lang/String; + public final fun getOutputSchema ()Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema; + public final fun getTitle ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/Tool$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/Tool$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/Tool; + 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/Tool;)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/Tool$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/Boolean; + public final fun component3 ()Ljava/lang/Boolean; + public final fun component4 ()Ljava/lang/Boolean; + public final fun component5 ()Ljava/lang/Boolean; + public final fun copy (Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations; + public fun equals (Ljava/lang/Object;)Z + public final fun getDestructiveHint ()Ljava/lang/Boolean; + public final fun getIdempotentHint ()Ljava/lang/Boolean; + public final fun getOpenWorldHint ()Ljava/lang/Boolean; + public final fun getReadOnlyHint ()Ljava/lang/Boolean; + public final fun getTitle ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations; + 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/ToolAnnotations;)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/ToolAnnotations$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ToolListChangedNotification : io/modelcontextprotocol/kotlin/sdk/types/ServerNotification { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ToolListChangedNotification$Companion; + public fun ()V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolListChangedNotification; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ToolListChangedNotification;Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolListChangedNotification; + public fun equals (Ljava/lang/Object;)Z + public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; + 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/ToolListChangedNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ToolListChangedNotification$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolListChangedNotification; + 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/ToolListChangedNotification;)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/ToolListChangedNotification$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ToolSchema { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema$Companion; + public fun ()V + public fun (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;)V + public synthetic fun (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lkotlinx/serialization/json/JsonObject; + public final fun component2 ()Ljava/util/List; + public final fun copy (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema; + public fun equals (Ljava/lang/Object;)Z + public final fun getProperties ()Lkotlinx/serialization/json/JsonObject; + public final fun getRequired ()Ljava/util/List; + public final fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ToolSchema$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema; + 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/ToolSchema;)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/ToolSchema$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ToolsKt { + public static final fun error (Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult$Companion;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult; + public static synthetic fun error$default (Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult$Companion;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult; + public static final fun success (Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult$Companion;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult; + public static synthetic fun success$default (Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult$Companion;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/UnknownResourceContents : io/modelcontextprotocol/kotlin/sdk/types/ResourceContents { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/UnknownResourceContents$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/UnknownResourceContents; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/UnknownResourceContents;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/UnknownResourceContents; + public fun equals (Ljava/lang/Object;)Z + public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public fun getMimeType ()Ljava/lang/String; + public fun getUri ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/UnknownResourceContents$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/UnknownResourceContents$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/UnknownResourceContents; + 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/UnknownResourceContents;)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/UnknownResourceContents$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequestParams;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequestParams; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest;Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest; + public fun equals (Ljava/lang/Object;)Z + public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; + public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequestParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest; + 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/UnsubscribeRequest;)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/UnsubscribeRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequestParams$Companion; + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun copy-D-90kyg (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequestParams; + public static synthetic fun copy-D-90kyg$default (Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequestParams;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequestParams; + public fun equals (Ljava/lang/Object;)Z + public fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun getUri ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequestParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequestParams; + 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/UnsubscribeRequestParams;)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/UnsubscribeRequestParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/WithMeta { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/WithMeta$Companion; + public abstract fun getMeta ()Lkotlinx/serialization/json/JsonObject; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/WithMeta$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt index 6eedfe6..7d55924 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt @@ -1,24 +1,27 @@ package io.modelcontextprotocol.kotlin.sdk.shared import io.github.oshai.kotlinlogging.KotlinLogging -import io.modelcontextprotocol.kotlin.sdk.CancelledNotification -import io.modelcontextprotocol.kotlin.sdk.EmptyRequestResult -import io.modelcontextprotocol.kotlin.sdk.ErrorCode -import io.modelcontextprotocol.kotlin.sdk.JSONRPCError -import io.modelcontextprotocol.kotlin.sdk.JSONRPCNotification -import io.modelcontextprotocol.kotlin.sdk.JSONRPCRequest -import io.modelcontextprotocol.kotlin.sdk.JSONRPCResponse -import io.modelcontextprotocol.kotlin.sdk.McpError -import io.modelcontextprotocol.kotlin.sdk.Method -import io.modelcontextprotocol.kotlin.sdk.Notification -import io.modelcontextprotocol.kotlin.sdk.PingRequest -import io.modelcontextprotocol.kotlin.sdk.Progress -import io.modelcontextprotocol.kotlin.sdk.ProgressNotification -import io.modelcontextprotocol.kotlin.sdk.Request -import io.modelcontextprotocol.kotlin.sdk.RequestId -import io.modelcontextprotocol.kotlin.sdk.RequestResult -import io.modelcontextprotocol.kotlin.sdk.fromJSON -import io.modelcontextprotocol.kotlin.sdk.toJSON +import io.modelcontextprotocol.kotlin.sdk.types.CancelledNotification +import io.modelcontextprotocol.kotlin.sdk.types.CancelledNotificationParams +import io.modelcontextprotocol.kotlin.sdk.types.EmptyResult +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCError +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse +import io.modelcontextprotocol.kotlin.sdk.types.McpException +import io.modelcontextprotocol.kotlin.sdk.types.McpJson +import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.Notification +import io.modelcontextprotocol.kotlin.sdk.types.PingRequest +import io.modelcontextprotocol.kotlin.sdk.types.Progress +import io.modelcontextprotocol.kotlin.sdk.types.ProgressNotification +import io.modelcontextprotocol.kotlin.sdk.types.ProgressToken +import io.modelcontextprotocol.kotlin.sdk.types.RPCError +import io.modelcontextprotocol.kotlin.sdk.types.Request +import io.modelcontextprotocol.kotlin.sdk.types.RequestId +import io.modelcontextprotocol.kotlin.sdk.types.RequestResult +import io.modelcontextprotocol.kotlin.sdk.types.fromJSON +import io.modelcontextprotocol.kotlin.sdk.types.toJSON import kotlinx.atomicfu.AtomicRef import kotlinx.atomicfu.atomic import kotlinx.atomicfu.getAndUpdate @@ -35,9 +38,6 @@ import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.encodeToJsonElement -import kotlinx.serialization.serializer -import kotlin.reflect.KType -import kotlin.reflect.typeOf import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds @@ -51,6 +51,11 @@ public const val IMPLEMENTATION_NAME: String = "mcp-ktor" public typealias ProgressCallback = (Progress) -> Unit @OptIn(ExperimentalSerializationApi::class) +@Deprecated( + message = "Use McpJson instead", + replaceWith = ReplaceWith("McpJson", "io.modelcontextprotocol.kotlin.sdk.types.McpJson"), + level = DeprecationLevel.WARNING, +) public val McpJson: Json by lazy { Json { ignoreUnknownKeys = true @@ -143,9 +148,9 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio public val responseHandlers: Map Unit> get() = _responseHandlers.value - private val _progressHandlers: AtomicRef> = + private val _progressHandlers: AtomicRef> = atomic(persistentMapOf()) - public val progressHandlers: Map + public val progressHandlers: Map get() = _progressHandlers.value /** @@ -183,7 +188,7 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio } setRequestHandler(Method.Defined.Ping) { _, _ -> - EmptyRequestResult() + EmptyResult() } } @@ -222,7 +227,7 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio transport = null onClose() - val error = McpError(ErrorCode.Defined.ConnectionClosed.code, "Connection closed") + val error = McpException(RPCError.ErrorCode.CONNECTION_CLOSED, "Connection closed") for (handler in handlersToNotify) { handler(null, error) } @@ -254,10 +259,10 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio logger.trace { "No handler found for request: ${request.method}" } try { transport?.send( - JSONRPCResponse( + JSONRPCError( id = request.id, - error = JSONRPCError( - ErrorCode.Defined.MethodNotFound, + error = RPCError( + code = RPCError.ErrorCode.METHOD_NOT_FOUND, message = "Server does not support ${request.method}", ), ), @@ -276,7 +281,7 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio transport?.send( JSONRPCResponse( id = request.id, - result = result, + result = result ?: EmptyResult(), ), ) } catch (cause: Throwable) { @@ -284,10 +289,10 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio try { transport?.send( - JSONRPCResponse( + JSONRPCError( id = request.id, - error = JSONRPCError( - code = ErrorCode.Defined.InternalError, + error = RPCError( + code = RPCError.ErrorCode.INTERNAL_ERROR, message = cause.message ?: "Internal error", ), ), @@ -324,7 +329,7 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio } private fun onResponse(response: JSONRPCResponse?, error: JSONRPCError?) { - val messageId = response?.id + val messageId = response?.id ?: error?.id val oldResponseHandlers = _responseHandlers.getAndUpdate { current -> if (messageId != null && messageId in current) { @@ -347,10 +352,10 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio handler(response, null) } else { check(error != null) - val error = McpError( - error.code.code, - error.message, - error.data, + val error = McpException( + code = error.error.code, + message = error.error.message, + data = error.error.data, ) handler(null, error) } @@ -415,11 +420,6 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio return@put } - if (response?.error != null) { - result.completeExceptionally(IllegalStateException(response.error.toString())) - return@put - } - try { @Suppress("UNCHECKED_CAST") result.complete(response!!.result as T) @@ -434,7 +434,7 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio _progressHandlers.update { current -> current.remove(messageId) } val notification = CancelledNotification( - params = CancelledNotification.Params( + params = CancelledNotificationParams( requestId = messageId, reason = reason.message ?: "Unknown", ), @@ -459,10 +459,10 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio } catch (cause: TimeoutCancellationException) { logger.error { "Request timed out after ${timeout.inWholeMilliseconds}ms: ${request.method}" } cancel( - McpError( - ErrorCode.Defined.RequestTimeout.code, - "Request timed out", - JsonObject(mutableMapOf("timeout" to JsonPrimitive(timeout.inWholeMilliseconds))), + McpException( + code = RPCError.ErrorCode.REQUEST_TIMEOUT, + message = "Request timed out", + data = JsonObject(mutableMapOf("timeout" to JsonPrimitive(timeout.inWholeMilliseconds))), ), ) result.cancel(cause) @@ -491,28 +491,21 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio method: Method, noinline block: suspend (T, RequestHandlerExtra) -> RequestResult?, ) { - setRequestHandler(typeOf(), method, block) + setRequestHandlerInternal(method, block) } @PublishedApi - internal fun setRequestHandler( - requestType: KType, + @Suppress("UNCHECKED_CAST") + internal fun setRequestHandlerInternal( method: Method, block: suspend (T, RequestHandlerExtra) -> RequestResult?, ) { assertRequestHandlerCapability(method) - val serializer = McpJson.serializersModule.serializer(requestType) - _requestHandlers.update { current -> - current.put(method.value) { request, extraHandler -> - val result = McpJson.decodeFromJsonElement(serializer, request.params) - val response = if (result != null) { - @Suppress("UNCHECKED_CAST") - block(result as T, extraHandler) - } else { - EmptyRequestResult() - } + current.put(method.value) { jSONRPCRequest, extraHandler -> + val request = jSONRPCRequest.fromJSON() + val response = block(request as T, extraHandler) response } } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBuffer.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBuffer.kt index 014e0ee..d991e7f 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBuffer.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBuffer.kt @@ -1,7 +1,8 @@ package io.modelcontextprotocol.kotlin.sdk.shared import io.github.oshai.kotlinlogging.KotlinLogging -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.McpJson import kotlinx.io.Buffer import kotlinx.io.indexOf import kotlinx.io.readString diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Transport.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Transport.kt index ba460f9..0ce13ec 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Transport.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Transport.kt @@ -1,6 +1,6 @@ package io.modelcontextprotocol.kotlin.sdk.shared -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage import kotlinx.coroutines.CompletableDeferred /** diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt index 85c60c0..4b20926 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt @@ -5,7 +5,8 @@ import io.ktor.websocket.Frame import io.ktor.websocket.WebSocketSession import io.ktor.websocket.close import io.ktor.websocket.readText -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.McpJson import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.InternalCoroutinesApi @@ -58,7 +59,7 @@ public abstract class WebSocketMcpTransport : AbstractTransport() { while (true) { val message = try { session.incoming.receive() - } catch (e: ClosedReceiveChannelException) { + } catch (_: ClosedReceiveChannelException) { logger.debug { "Closed receive channel, exiting" } return@launch } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt index a2383ac..23b0e45 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt @@ -1,287 +1,654 @@ -@file:Suppress("unused", "EnumEntryName") +@file:Suppress("FunctionName") package io.modelcontextprotocol.kotlin.sdk -import io.modelcontextprotocol.kotlin.sdk.shared.McpJson -import kotlinx.serialization.EncodeDefault -import kotlinx.serialization.ExperimentalSerializationApi +import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities.Roots +import io.modelcontextprotocol.kotlin.sdk.types.ContentBlock +import io.modelcontextprotocol.kotlin.sdk.types.fromJSON +import io.modelcontextprotocol.kotlin.sdk.types.toJSON import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.decodeFromJsonElement -import kotlinx.serialization.json.encodeToJsonElement -import kotlinx.serialization.json.jsonObject import kotlin.concurrent.atomics.AtomicLong import kotlin.concurrent.atomics.ExperimentalAtomicApi -import kotlin.concurrent.atomics.incrementAndFetch -import kotlin.jvm.JvmInline -public const val LATEST_PROTOCOL_VERSION: String = "2025-03-26" - -public val SUPPORTED_PROTOCOL_VERSIONS: Array = arrayOf( - LATEST_PROTOCOL_VERSION, - "2024-11-05", +@Deprecated( + message = "Use `LATEST_PROTOCOL_VERSION` instead", + replaceWith = ReplaceWith( + "LATEST_PROTOCOL_VERSION", + "io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION", + ), + level = DeprecationLevel.WARNING, ) +public const val LATEST_PROTOCOL_VERSION: String = io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION -public const val JSONRPC_VERSION: String = "2.0" +@Deprecated( + message = "Use `SUPPORTED_PROTOCOL_VERSIONS` instead", + replaceWith = ReplaceWith( + "SUPPORTED_PROTOCOL_VERSIONS", + "io.modelcontextprotocol.kotlin.sdk.types.SUPPORTED_PROTOCOL_VERSIONS", + ), + level = DeprecationLevel.WARNING, +) +public val SUPPORTED_PROTOCOL_VERSIONS: List = + io.modelcontextprotocol.kotlin.sdk.types.SUPPORTED_PROTOCOL_VERSIONS + +@Deprecated( + message = "Use `JSONRPC_VERSION` instead", + replaceWith = ReplaceWith("JSONRPC_VERSION", "io.modelcontextprotocol.kotlin.sdk.types.JSONRPC_VERSION"), + level = DeprecationLevel.WARNING, +) +public const val JSONRPC_VERSION: String = io.modelcontextprotocol.kotlin.sdk.types.JSONRPC_VERSION @OptIn(ExperimentalAtomicApi::class) +@Deprecated( + message = "Use `REQUEST_MESSAGE_ID` instead", + replaceWith = ReplaceWith("REQUEST_MESSAGE_ID", "io.modelcontextprotocol.kotlin.sdk.types.REQUEST_MESSAGE_ID"), + level = DeprecationLevel.WARNING, +) private val REQUEST_MESSAGE_ID: AtomicLong = AtomicLong(0L) /** * A progress token, used to associate progress notifications with the original request. * Stores message ID. */ -public typealias ProgressToken = RequestId +@Deprecated( + message = "Use `ProgressToken` instead", + replaceWith = ReplaceWith("ProgressToken", "io.modelcontextprotocol.kotlin.sdk.types.ProgressToken"), + level = DeprecationLevel.WARNING, +) +public typealias ProgressToken = io.modelcontextprotocol.kotlin.sdk.types.ProgressToken /** * An opaque token used to represent a cursor for pagination. */ +@Deprecated( + message = "This alias will be removed. Use String directly instead.", + replaceWith = ReplaceWith("String"), + level = DeprecationLevel.WARNING, +) public typealias Cursor = String /** * Represents an entity that includes additional metadata in its responses. */ -@Serializable -public sealed interface WithMeta { - /** - * The protocol reserves this result property - * to allow clients and servers to attach additional metadata to their responses. - */ - @Suppress("PropertyName") - public val _meta: JsonObject - - public companion object { - public val Empty: CustomMeta = CustomMeta() - } -} +@Deprecated( + message = "Use `WithMeta` instead", + replaceWith = ReplaceWith("WithMeta", "io.modelcontextprotocol.kotlin.sdk.types.WithMeta"), + level = DeprecationLevel.WARNING, +) +public typealias WithMeta = io.modelcontextprotocol.kotlin.sdk.types.WithMeta /** * An implementation of [WithMeta] containing custom metadata. * * @param _meta The JSON object holding metadata. Defaults to an empty JSON object. */ -@Serializable -public class CustomMeta(override val _meta: JsonObject = EmptyJsonObject) : WithMeta +@Deprecated( + message = "This class will be removed. Use `WithMeta` instead", + replaceWith = ReplaceWith("WithMeta", "io.modelcontextprotocol.kotlin.sdk.types.WithMeta"), + level = DeprecationLevel.WARNING, +) +public typealias CustomMeta = io.modelcontextprotocol.kotlin.sdk.types.WithMeta /** * Represents a method in the protocol, which can be predefined or custom. */ -@Serializable(with = RequestMethodSerializer::class) +@Deprecated( + message = "Use `Method` instead", + replaceWith = ReplaceWith("Method", "io.modelcontextprotocol.kotlin.sdk.types.Method"), + level = DeprecationLevel.WARNING, +) public sealed interface Method { public val value: String - /** - * Enum of predefined methods supported by the protocol. - */ - @Serializable - public enum class Defined(override val value: String) : Method { - Initialize("initialize"), - Ping("ping"), - ResourcesList("resources/list"), - ResourcesTemplatesList("resources/templates/list"), - ResourcesRead("resources/read"), - ResourcesSubscribe("resources/subscribe"), - ResourcesUnsubscribe("resources/unsubscribe"), - PromptsList("prompts/list"), - PromptsGet("prompts/get"), - NotificationsCancelled("notifications/cancelled"), - NotificationsInitialized("notifications/initialized"), - NotificationsProgress("notifications/progress"), - NotificationsMessage("notifications/message"), - NotificationsResourcesUpdated("notifications/resources/updated"), - NotificationsResourcesListChanged("notifications/resources/list_changed"), - NotificationsToolsListChanged("notifications/tools/list_changed"), - NotificationsRootsListChanged("notifications/roots/list_changed"), - NotificationsPromptsListChanged("notifications/prompts/list_changed"), - ToolsList("tools/list"), - ToolsCall("tools/call"), - LoggingSetLevel("logging/setLevel"), - SamplingCreateMessage("sampling/createMessage"), - CompletionComplete("completion/complete"), - RootsList("roots/list"), - ElicitationCreate("elicitation/create"), + public companion object { + /** + * Represents a custom method defined by the user. + */ + @Deprecated( + message = "Use `Method.Custom` instead", + replaceWith = ReplaceWith("Method.Custom", "io.modelcontextprotocol.kotlin.sdk.types.Method.Custom"), + level = DeprecationLevel.WARNING, + ) + public fun Custom(value: String): io.modelcontextprotocol.kotlin.sdk.types.Method.Custom = + io.modelcontextprotocol.kotlin.sdk.types.Method.Custom(value) } - /** - * Represents a custom method defined by the user. - */ - @Serializable - public data class Custom(override val value: String) : Method + public object Defined { + @Deprecated( + message = "Use `Method.Defined.Initialize` instead", + replaceWith = ReplaceWith( + "Method.Defined.Initialize", + "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.Initialize", + ), + level = DeprecationLevel.WARNING, + ) + public val Initialize: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = + io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.Initialize + + @Deprecated( + message = "Use `Method.Defined.Ping` instead", + replaceWith = ReplaceWith( + "Method.Defined.Ping", + "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.Ping", + ), + level = DeprecationLevel.WARNING, + ) + public val Ping: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = + io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.Ping + + @Deprecated( + message = "Use `Method.Defined.ResourcesList` instead", + replaceWith = ReplaceWith( + "Method.Defined.ResourcesList", + "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesList", + ), + level = DeprecationLevel.WARNING, + ) + public val ResourcesList: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = + io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesList + + @Deprecated( + message = "Use `Method.Defined.ResourcesTemplatesList` instead", + replaceWith = ReplaceWith( + "Method.Defined.ResourcesTemplatesList", + "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesTemplatesList", + ), + level = DeprecationLevel.WARNING, + ) + public val ResourcesTemplatesList: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = + io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesTemplatesList + + @Deprecated( + message = "Use `Method.Defined.ResourcesRead` instead", + replaceWith = ReplaceWith( + "Method.Defined.ResourcesRead", + "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesRead", + ), + level = DeprecationLevel.WARNING, + ) + public val ResourcesRead: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = + io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesRead + + @Deprecated( + message = "Use `Method.Defined.ResourcesSubscribe` instead", + replaceWith = ReplaceWith( + "Method.Defined.ResourcesSubscribe", + "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesSubscribe", + ), + level = DeprecationLevel.WARNING, + ) + public val ResourcesSubscribe: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = + io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesSubscribe + + @Deprecated( + message = "Use `Method.Defined.ResourcesUnsubscribe` instead", + replaceWith = ReplaceWith( + "Method.Defined.ResourcesUnsubscribe", + "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesUnsubscribe", + ), + level = DeprecationLevel.WARNING, + ) + public val ResourcesUnsubscribe: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = + io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesUnsubscribe + + @Deprecated( + message = "Use `Method.Defined.PromptsList` instead", + replaceWith = ReplaceWith( + "Method.Defined.PromptsList", + "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.PromptsList", + ), + level = DeprecationLevel.WARNING, + ) + public val PromptsList: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = + io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.PromptsList + + @Deprecated( + message = "Use `Method.Defined.PromptsGet` instead", + replaceWith = ReplaceWith( + "Method.Defined.PromptsGet", + "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.PromptsGet", + ), + level = DeprecationLevel.WARNING, + ) + public val PromptsGet: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = + io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.PromptsGet + + @Deprecated( + message = "Use `Method.Defined.NotificationsCancelled` instead", + replaceWith = ReplaceWith( + "Method.Defined.NotificationsCancelled", + "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsCancelled", + ), + level = DeprecationLevel.WARNING, + ) + public val NotificationsCancelled: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = + io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsCancelled + + @Deprecated( + message = "Use `Method.Defined.NotificationsInitialized` instead", + replaceWith = ReplaceWith( + "Method.Defined.NotificationsInitialized", + "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsInitialized", + ), + level = DeprecationLevel.WARNING, + ) + public val NotificationsInitialized: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = + io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsInitialized + + @Deprecated( + message = "Use `Method.Defined.NotificationsProgress` instead", + replaceWith = ReplaceWith( + "Method.Defined.NotificationsProgress", + "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsProgress", + ), + level = DeprecationLevel.WARNING, + ) + public val NotificationsProgress: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = + io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsProgress + + @Deprecated( + message = "Use `Method.Defined.NotificationsMessage` instead", + replaceWith = ReplaceWith( + "Method.Defined.NotificationsMessage", + "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsMessage", + ), + level = DeprecationLevel.WARNING, + ) + public val NotificationsMessage: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = + io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsMessage + + @Deprecated( + message = "Use `Method.Defined.NotificationsResourcesUpdated` instead", + replaceWith = ReplaceWith( + "Method.Defined.NotificationsResourcesUpdated", + "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsResourcesUpdated", + ), + level = DeprecationLevel.WARNING, + ) + public val NotificationsResourcesUpdated: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = + io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsResourcesUpdated + + @Deprecated( + message = "Use `Method.Defined.NotificationsResourcesListChanged` instead", + replaceWith = ReplaceWith( + "Method.Defined.NotificationsResourcesListChanged", + "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsResourcesListChanged", + ), + level = DeprecationLevel.WARNING, + ) + public val NotificationsResourcesListChanged: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = + io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsResourcesListChanged + + @Deprecated( + message = "Use `Method.Defined.NotificationsToolsListChanged` instead", + replaceWith = ReplaceWith( + "Method.Defined.NotificationsToolsListChanged", + "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsToolsListChanged", + ), + level = DeprecationLevel.WARNING, + ) + public val NotificationsToolsListChanged: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = + io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsToolsListChanged + + @Deprecated( + message = "Use `Method.Defined.NotificationsRootsListChanged` instead", + replaceWith = ReplaceWith( + "Method.Defined.NotificationsRootsListChanged", + "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsRootsListChanged", + ), + level = DeprecationLevel.WARNING, + ) + public val NotificationsRootsListChanged: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = + io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsRootsListChanged + + @Deprecated( + message = "Use `Method.Defined.NotificationsPromptsListChanged` instead", + replaceWith = ReplaceWith( + "Method.Defined.NotificationsPromptsListChanged", + "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsPromptsListChanged", + ), + level = DeprecationLevel.WARNING, + ) + public val NotificationsPromptsListChanged: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = + io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsPromptsListChanged + + @Deprecated( + message = "Use `Method.Defined.ToolsList` instead", + replaceWith = ReplaceWith( + "Method.Defined.ToolsList", + "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ToolsList", + ), + level = DeprecationLevel.WARNING, + ) + public val ToolsList: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = + io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ToolsList + + @Deprecated( + message = "Use `Method.Defined.ToolsCall` instead", + replaceWith = ReplaceWith( + "Method.Defined.ToolsCall", + "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ToolsCall", + ), + level = DeprecationLevel.WARNING, + ) + public val ToolsCall: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = + io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ToolsCall + + @Deprecated( + message = "Use `Method.Defined.LoggingSetLevel` instead", + replaceWith = ReplaceWith( + "Method.Defined.LoggingSetLevel", + "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.LoggingSetLevel", + ), + level = DeprecationLevel.WARNING, + ) + public val LoggingSetLevel: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = + io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.LoggingSetLevel + + @Deprecated( + message = "Use `Method.Defined.SamplingCreateMessage` instead", + replaceWith = ReplaceWith( + "Method.Defined.SamplingCreateMessage", + "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.SamplingCreateMessage", + ), + level = DeprecationLevel.WARNING, + ) + public val SamplingCreateMessage: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = + io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.SamplingCreateMessage + + @Deprecated( + message = "Use `Method.Defined.CompletionComplete` instead", + replaceWith = ReplaceWith( + "Method.Defined.CompletionComplete", + "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.CompletionComplete", + ), + level = DeprecationLevel.WARNING, + ) + public val CompletionComplete: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = + io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.CompletionComplete + + @Deprecated( + message = "Use `Method.Defined.RootsList` instead", + replaceWith = ReplaceWith( + "Method.Defined.RootsList", + "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.RootsList", + ), + level = DeprecationLevel.WARNING, + ) + public val RootsList: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = + io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.RootsList + + @Deprecated( + message = "Use `Method.Defined.ElicitationCreate` instead", + replaceWith = ReplaceWith( + "Method.Defined.ElicitationCreate", + "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ElicitationCreate", + ), + level = DeprecationLevel.WARNING, + ) + public val ElicitationCreate: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = + io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ElicitationCreate + } } /** * Represents a request in the protocol. */ -@Serializable(with = RequestPolymorphicSerializer::class) -public sealed interface Request { - public val method: Method -} +@Deprecated( + message = "Use `Request` instead", + replaceWith = ReplaceWith("Request", "io.modelcontextprotocol.kotlin.sdk.types.Request"), + level = DeprecationLevel.WARNING, +) +public typealias Request = io.modelcontextprotocol.kotlin.sdk.types.Request /** * Converts the request to a JSON-RPC request. * * @return The JSON-RPC request representation. */ -public fun Request.toJSON(): JSONRPCRequest { - val fullJson = McpJson.encodeToJsonElement(this).jsonObject - val params = JsonObject(fullJson.filterKeys { it != "method" }) - return JSONRPCRequest( - method = method.value, - params = params, - jsonrpc = JSONRPC_VERSION, - ) -} +@Deprecated( + message = "Use `toJSON` instead", + replaceWith = ReplaceWith("toJSON", "io.modelcontextprotocol.kotlin.sdk.types.toJSON"), + level = DeprecationLevel.WARNING, +) +public fun Request.toJSON(): JSONRPCRequest = this.toJSON() /** * Decodes a JSON-RPC request into a protocol-specific [Request]. * * @return The decoded [Request] or null */ -internal fun JSONRPCRequest.fromJSON(): Request { - val requestData = JsonObject(params.jsonObject + ("method" to JsonPrimitive(method))) - val deserializer = selectRequestDeserializer(method) - return McpJson.decodeFromJsonElement(deserializer, requestData) -} +@Deprecated( + message = "Use `fromJSON` instead", + replaceWith = ReplaceWith("fromJSON", "io.modelcontextprotocol.kotlin.sdk.types.fromJSON"), + level = DeprecationLevel.WARNING, +) +internal fun JSONRPCRequest.fromJSON(): Request = this.fromJSON() /** * A custom request with a specified method. * * @param method The method associated with the request. */ -@Serializable -public open class CustomRequest(override val method: Method) : Request +@Deprecated( + message = "Use `CustomRequest` instead", + replaceWith = ReplaceWith("CustomRequest", "io.modelcontextprotocol.kotlin.sdk.types.CustomRequest"), + level = DeprecationLevel.WARNING, +) +public typealias CustomRequest = io.modelcontextprotocol.kotlin.sdk.types.CustomRequest /** * Represents a notification in the protocol. */ -@Serializable(with = NotificationPolymorphicSerializer::class) -public sealed interface Notification { - public val method: Method - public val params: NotificationParams? -} +@Deprecated( + message = "Use `Notification` instead", + replaceWith = ReplaceWith("Notification", "io.modelcontextprotocol.kotlin.sdk.types.Notification"), + level = DeprecationLevel.WARNING, +) +public typealias Notification = io.modelcontextprotocol.kotlin.sdk.types.Notification /** * Converts the notification to a JSON-RPC notification. * * @return The JSON-RPC notification representation. */ -public fun Notification.toJSON(): JSONRPCNotification = JSONRPCNotification( - method = method.value, - params = McpJson.encodeToJsonElement(params), +@Deprecated( + message = "Use `toJSON` instead", + replaceWith = ReplaceWith("toJSON", "io.modelcontextprotocol.kotlin.sdk.types.toJSON"), + level = DeprecationLevel.WARNING, ) +public fun Notification.toJSON(): JSONRPCNotification = this.toJSON() /** * Decodes a JSON-RPC notification into a protocol-specific [Notification]. * * @return The decoded [Notification]. */ -internal fun JSONRPCNotification.fromJSON(): Notification { - val data = buildJsonObject { - put("method", JsonPrimitive(method)) - put("params", params) - } - return McpJson.decodeFromJsonElement(data) -} +@Deprecated( + message = "Use `fromJSON` instead", + replaceWith = ReplaceWith("toJSON", "io.modelcontextprotocol.kotlin.sdk.types.fromJSON"), + level = DeprecationLevel.WARNING, +) +internal fun JSONRPCNotification.fromJSON(): Notification = this.fromJSON() /** * Represents the result of a request, including additional metadata. */ -@Serializable(with = RequestResultPolymorphicSerializer::class) -public sealed interface RequestResult : WithMeta +@Deprecated( + message = "Use `RequestResult` instead", + replaceWith = ReplaceWith("RequestResult", "io.modelcontextprotocol.kotlin.sdk.types.RequestResult"), + level = DeprecationLevel.WARNING, +) +public typealias RequestResult = io.modelcontextprotocol.kotlin.sdk.types.RequestResult /** * An empty result for a request containing optional metadata. * * @param _meta Additional metadata for the response. Defaults to an empty JSON object. */ -@Serializable -public data class EmptyRequestResult(override val _meta: JsonObject = EmptyJsonObject) : - ServerResult, - ClientResult +@Deprecated( + message = "Use `EmptyResult` instead", + replaceWith = ReplaceWith("EmptyResult", "io.modelcontextprotocol.kotlin.sdk.types.EmptyResult"), + level = DeprecationLevel.WARNING, +) +public typealias EmptyRequestResult = io.modelcontextprotocol.kotlin.sdk.types.EmptyResult /** * A uniquely identifying ID for a request in JSON-RPC. */ -@Serializable(with = RequestIdSerializer::class) -public sealed interface RequestId { - @Serializable - public data class StringId(val value: String) : RequestId +@Deprecated( + message = "Use `RequestId` instead", + replaceWith = ReplaceWith("RequestId", "io.modelcontextprotocol.kotlin.sdk.types.RequestId"), + level = DeprecationLevel.WARNING, +) +public object RequestId { + @Deprecated( + message = "Use `RequestId.StringId` instead", + replaceWith = ReplaceWith("RequestId.StringId", "io.modelcontextprotocol.kotlin.sdk.types.RequestId.StringId"), + level = DeprecationLevel.WARNING, + ) + public fun StringId(value: String): io.modelcontextprotocol.kotlin.sdk.types.RequestId.StringId = + io.modelcontextprotocol.kotlin.sdk.types.RequestId.StringId(value) - @Serializable - public data class NumberId(val value: Long) : RequestId + @Deprecated( + message = "Use `RequestId.NumberId` instead", + replaceWith = ReplaceWith("RequestId.NumberId", "io.modelcontextprotocol.kotlin.sdk.types.RequestId.NumberId"), + level = DeprecationLevel.WARNING, + ) + public fun NumberId(value: Long): io.modelcontextprotocol.kotlin.sdk.types.RequestId.NumberId = + io.modelcontextprotocol.kotlin.sdk.types.RequestId.NumberId(value) } /** * Represents a JSON-RPC message in the protocol. */ -@Serializable(with = JSONRPCMessagePolymorphicSerializer::class) -public sealed interface JSONRPCMessage +@Deprecated( + message = "Use `JSONRPCMessage` instead", + replaceWith = ReplaceWith("JSONRPCMessage", "io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage"), + level = DeprecationLevel.WARNING, +) +public typealias JSONRPCMessage = io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage /** * A request that expects a response. */ -@OptIn(ExperimentalAtomicApi::class) -@Serializable -public data class JSONRPCRequest( - val id: RequestId = RequestId.NumberId(REQUEST_MESSAGE_ID.incrementAndFetch()), - val method: String, - val params: JsonElement = EmptyJsonObject, - val jsonrpc: String = JSONRPC_VERSION, -) : JSONRPCMessage +@Deprecated( + message = "Use `JSONRPCRequest` instead", + replaceWith = ReplaceWith("JSONRPCRequest", "io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest"), + level = DeprecationLevel.WARNING, +) +public typealias JSONRPCRequest = io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest /** * A notification which does not expect a response. */ -@Serializable -public data class JSONRPCNotification( - val method: String, - val params: JsonElement = EmptyJsonObject, - val jsonrpc: String = JSONRPC_VERSION, -) : JSONRPCMessage +@Deprecated( + message = "Use `JSONRPCNotification` instead", + replaceWith = ReplaceWith("JSONRPCNotification", "io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification"), + level = DeprecationLevel.WARNING, +) +public typealias JSONRPCNotification = io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification /** * A successful (non-error) response to a request. */ -@Serializable -public class JSONRPCResponse( - public val id: RequestId, - public val jsonrpc: String = JSONRPC_VERSION, - public val result: RequestResult? = null, - public val error: JSONRPCError? = null, -) : JSONRPCMessage { - - public fun copy( - id: RequestId = this.id, - jsonrpc: String = this.jsonrpc, - result: RequestResult? = this.result, - error: JSONRPCError? = this.error, - ): JSONRPCResponse = JSONRPCResponse(id, jsonrpc, result, error) -} +@Deprecated( + message = "Use `JSONRPCResponse` instead", + replaceWith = ReplaceWith("JSONRPCResponse", "io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse"), + level = DeprecationLevel.WARNING, +) +public typealias JSONRPCResponse = io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse /** * An incomplete set of error codes that may appear in JSON-RPC responses. */ -@Serializable(with = ErrorCodeSerializer::class) +@Deprecated( + message = "Use `RPCError` instead", + replaceWith = ReplaceWith("RPCError", "io.modelcontextprotocol.kotlin.sdk.types.RPCError"), + level = DeprecationLevel.WARNING, +) public sealed interface ErrorCode { public val code: Int - @Serializable - public enum class Defined(override val code: Int) : ErrorCode { - // SDK error codes - ConnectionClosed(-1), - RequestTimeout(-2), + public object Defined { + @Deprecated( + message = "Use `RPCError.ErrorCode.CONNECTION_CLOSED` instead", + replaceWith = ReplaceWith( + "RPCError.ErrorCode.CONNECTION_CLOSED", + "io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.CONNECTION_CLOSED", + ), + level = DeprecationLevel.WARNING, + ) + public val ConnectionClosed: Int = io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.CONNECTION_CLOSED - // Standard JSON-RPC error codes - ParseError(-32700), - InvalidRequest(-32600), - MethodNotFound(-32601), - InvalidParams(-32602), - InternalError(-32603), + @Deprecated( + message = "Use `RPCError.ErrorCode.REQUEST_TIMEOUT` instead", + replaceWith = ReplaceWith( + "RPCError.ErrorCode.REQUEST_TIMEOUT", + "io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.REQUEST_TIMEOUT", + ), + level = DeprecationLevel.WARNING, + ) + public val RequestTimeout: Int = io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.REQUEST_TIMEOUT + + @Deprecated( + message = "Use `RPCError.ErrorCode.PARSE_ERROR` instead", + replaceWith = ReplaceWith( + "RPCError.ErrorCode.PARSE_ERROR", + "io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.PARSE_ERROR", + ), + level = DeprecationLevel.WARNING, + ) + public val ParseError: Int = io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.PARSE_ERROR + + @Deprecated( + message = "Use `RPCError.ErrorCode.INVALID_REQUEST` instead", + replaceWith = ReplaceWith( + "RPCError.ErrorCode.INVALID_REQUEST", + "io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.INVALID_REQUEST", + ), + level = DeprecationLevel.WARNING, + ) + public val InvalidRequest: Int = io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.INVALID_REQUEST + + @Deprecated( + message = "Use `RPCError.ErrorCode.METHOD_NOT_FOUND` instead", + replaceWith = ReplaceWith( + "RPCError.ErrorCode.METHOD_NOT_FOUND", + "io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.METHOD_NOT_FOUND", + ), + level = DeprecationLevel.WARNING, + ) + public val MethodNotFound: Int = io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.METHOD_NOT_FOUND + + @Deprecated( + message = "Use `RPCError.ErrorCode.INVALID_PARAMS` instead", + replaceWith = ReplaceWith( + "RPCError.ErrorCode.INVALID_PARAMS", + "io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.INVALID_PARAMS", + ), + level = DeprecationLevel.WARNING, + ) + public val InvalidParams: Int = io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.INVALID_PARAMS + + @Deprecated( + message = "Use `RPCError.ErrorCode.INTERNAL_ERROR` instead", + replaceWith = ReplaceWith( + "RPCError.ErrorCode.INTERNAL_ERROR", + "io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.INTERNAL_ERROR", + ), + level = DeprecationLevel.WARNING, + ) + public val InternalError: Int = io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.INTERNAL_ERROR } + @Deprecated( + message = "Use `RPCError` instead", + replaceWith = ReplaceWith("RPCError", "io.modelcontextprotocol.kotlin.sdk.types.RPCError"), + level = DeprecationLevel.WARNING, + ) @Serializable public data class Unknown(override val code: Int) : ErrorCode } @@ -289,18 +656,50 @@ public sealed interface ErrorCode { /** * A response to a request that indicates an error occurred. */ -@Serializable -public data class JSONRPCError(val code: ErrorCode, val message: String, val data: JsonObject = EmptyJsonObject) : - JSONRPCMessage +@Deprecated( + message = "Use `JSONRPCError` instead", + replaceWith = ReplaceWith("JSONRPCError", "io.modelcontextprotocol.kotlin.sdk.types.JSONRPCError"), + level = DeprecationLevel.WARNING, +) +public typealias JSONRPCError = io.modelcontextprotocol.kotlin.sdk.types.JSONRPCError /** * Base interface for notification parameters with optional metadata. */ -@Serializable -public sealed interface NotificationParams : WithMeta +@Deprecated( + message = "Use `NotificationParams` instead", + replaceWith = ReplaceWith("NotificationParams", "io.modelcontextprotocol.kotlin.sdk.types.NotificationParams"), + level = DeprecationLevel.WARNING, +) +public typealias NotificationParams = io.modelcontextprotocol.kotlin.sdk.types.NotificationParams /* Cancellation */ +@Deprecated( + message = "Use `CancelledNotification` instead", + replaceWith = ReplaceWith( + "CancelledNotification", + "io.modelcontextprotocol.kotlin.sdk.types.CancelledNotification", + ), + level = DeprecationLevel.WARNING, +) +public object CancelledNotification { + @Deprecated( + message = "Use `CancelledNotificationParams` instead", + replaceWith = ReplaceWith( + "CancelledNotificationParams", + "io.modelcontextprotocol.kotlin.sdk.types.CancelledNotificationParams", + ), + level = DeprecationLevel.WARNING, + ) + public fun Params( + requestId: io.modelcontextprotocol.kotlin.sdk.types.RequestId, + reason: String? = null, + meta: JsonObject? = null, + ): io.modelcontextprotocol.kotlin.sdk.types.CancelledNotificationParams = + io.modelcontextprotocol.kotlin.sdk.types.CancelledNotificationParams(requestId, reason, meta) +} + /** * This notification can be sent by either side to indicate that it is cancelling a previously issued request. * @@ -310,104 +709,126 @@ public sealed interface NotificationParams : WithMeta * * A client MUST NOT attempt to cancel its `initialize` request. */ -@Serializable -public data class CancelledNotification(override val params: Params) : - ClientNotification, - ServerNotification { - override val method: Method = Method.Defined.NotificationsCancelled - - @Serializable - public data class Params( - /** - * The ID of the request to cancel. - * - * It MUST correspond to the ID of a request previously issued in the same direction. - */ - val requestId: RequestId, - /** - * An optional string describing the reason for the cancellation. This MAY be logged or presented to the user. - */ - val reason: String? = null, - override val _meta: JsonObject = EmptyJsonObject, - ) : NotificationParams -} +@Deprecated( + message = "Use `CancelledNotification` instead", + replaceWith = ReplaceWith( + "CancelledNotification", + "io.modelcontextprotocol.kotlin.sdk.types.CancelledNotification", + ), + level = DeprecationLevel.WARNING, +) +public fun CancelledNotification( + params: io.modelcontextprotocol.kotlin.sdk.types.CancelledNotificationParams, +): io.modelcontextprotocol.kotlin.sdk.types.CancelledNotification = + io.modelcontextprotocol.kotlin.sdk.types.CancelledNotification(params) /* Initialization */ /** * Describes the name and version of an MCP implementation. */ -@Serializable -public data class Implementation(val name: String, val version: String) +@Deprecated( + message = "Use `Implementation` instead", + replaceWith = ReplaceWith("Implementation", "io.modelcontextprotocol.kotlin.sdk.types.Implementation"), + level = DeprecationLevel.WARNING, +) +public typealias Implementation = io.modelcontextprotocol.kotlin.sdk.types.Implementation + +@Deprecated( + message = "Use `ClientCapabilities` instead", + replaceWith = ReplaceWith("ClientCapabilities", "io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities"), + level = DeprecationLevel.WARNING, +) +public object ClientCapabilities { + @Deprecated( + message = "Use `ClientCapabilities.Roots` instead", + replaceWith = ReplaceWith( + "ClientCapabilities.Roots", + "io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities.Roots", + ), + level = DeprecationLevel.WARNING, + ) + public fun Roots(listChanged: Boolean? = null): io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities.Roots = + io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities.Roots(listChanged) +} /** * Capabilities a client may support. * Known capabilities are defined here, in this, but this is not a closed set: * any client can define its own, additional capabilities. */ -@Serializable -public data class ClientCapabilities( - /** - * Experimental, non-standard capabilities that the client supports. - */ - val experimental: JsonObject? = EmptyJsonObject, - /** - * Present if the client supports sampling from an LLM. - */ - val sampling: JsonObject? = EmptyJsonObject, - /** - * Present if the client supports listing roots. - */ - val roots: Roots? = null, - /** - * Present if the client supports elicitation. - */ - val elicitation: JsonObject? = null, -) { - @Serializable - public data class Roots( - /** - * Whether the client supports issuing notifications for changes to the root list. - */ - val listChanged: Boolean?, - ) -} +@Deprecated( + message = "Use `ClientCapabilities` instead", + replaceWith = ReplaceWith("ClientCapabilities", "io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities"), + level = DeprecationLevel.WARNING, +) +public fun ClientCapabilities( + sampling: JsonObject? = null, + roots: Roots? = null, + elicitation: JsonObject? = null, + experimental: JsonObject? = null, +): io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities = + io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities(sampling, roots, elicitation, experimental) /** * Represents a request sent by the client. */ -// @Serializable(with = ClientRequestPolymorphicSerializer::class) -public interface ClientRequest : Request +@Deprecated( + message = "Use `ClientRequest` instead", + replaceWith = ReplaceWith("ClientRequest", "io.modelcontextprotocol.kotlin.sdk.types.ClientRequest"), + level = DeprecationLevel.WARNING, +) +public typealias ClientRequest = io.modelcontextprotocol.kotlin.sdk.types.ClientRequest /** * Represents a notification sent by the client. */ -@Serializable(with = ClientNotificationPolymorphicSerializer::class) -public sealed interface ClientNotification : Notification +@Deprecated( + message = "Use `ClientNotification` instead", + replaceWith = ReplaceWith("ClientNotification", "io.modelcontextprotocol.kotlin.sdk.types.ClientNotification"), + level = DeprecationLevel.WARNING, +) +public typealias ClientNotification = io.modelcontextprotocol.kotlin.sdk.types.ClientNotification /** * Represents a result returned to the client. */ -@Serializable(with = ClientResultPolymorphicSerializer::class) -public sealed interface ClientResult : RequestResult +@Deprecated( + message = "Use `ClientResult` instead", + replaceWith = ReplaceWith("ClientResult", "io.modelcontextprotocol.kotlin.sdk.types.ClientResult"), + level = DeprecationLevel.WARNING, +) +public typealias ClientResult = io.modelcontextprotocol.kotlin.sdk.types.ClientResult /** * Represents a request sent by the server. */ -// @Serializable(with = ServerRequestPolymorphicSerializer::class) -public sealed interface ServerRequest : Request +@Deprecated( + message = "Use `ServerRequest` instead", + replaceWith = ReplaceWith("ServerRequest", "io.modelcontextprotocol.kotlin.sdk.types.ServerRequest"), + level = DeprecationLevel.WARNING, +) +public typealias ServerRequest = io.modelcontextprotocol.kotlin.sdk.types.ServerRequest /** * Represents a notification sent by the server. */ -@Serializable(with = ServerNotificationPolymorphicSerializer::class) -public sealed interface ServerNotification : Notification +@Deprecated( + message = "Use `ServerNotification` instead", + replaceWith = ReplaceWith("ServerNotification", "io.modelcontextprotocol.kotlin.sdk.types.ServerNotification"), + level = DeprecationLevel.WARNING, +) +public typealias ServerNotification = io.modelcontextprotocol.kotlin.sdk.types.ServerNotification /** * Represents a result returned by the server. */ -@Serializable(with = ServerResultPolymorphicSerializer::class) -public sealed interface ServerResult : RequestResult +@Deprecated( + message = "Use `ServerResult` instead", + replaceWith = ReplaceWith("ServerResult", "io.modelcontextprotocol.kotlin.sdk.types.ServerResult"), + level = DeprecationLevel.WARNING, +) +public typealias ServerResult = io.modelcontextprotocol.kotlin.sdk.types.ServerResult /** * Represents a request or notification for an unknown method. @@ -415,120 +836,147 @@ public sealed interface ServerResult : RequestResult * @param method The method that is unknown. */ @Serializable -public data class UnknownMethodRequestOrNotification( - override val method: Method, - override val params: NotificationParams? = null, -) : ClientNotification, - ClientRequest, - ServerNotification, - ServerRequest +@Deprecated( + message = "This class will be removed", + level = DeprecationLevel.WARNING, +) +public data class UnknownMethodRequestOrNotification(val method: Method, val params: NotificationParams? = null) /** * This request is sent from the client to the server when it first connects, asking it to begin initialization. */ -@Serializable -public data class InitializeRequest( - val protocolVersion: String, - val capabilities: ClientCapabilities, - val clientInfo: Implementation, - override val _meta: JsonObject = EmptyJsonObject, -) : ClientRequest, - WithMeta { - override val method: Method = Method.Defined.Initialize +@Deprecated( + message = "Use `InitializeRequest` instead", + replaceWith = ReplaceWith("InitializeRequest", "io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest"), + level = DeprecationLevel.WARNING, +) +public typealias InitializeRequest = io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest + +@Deprecated( + message = "Use `ServerCapabilities` instead", + replaceWith = ReplaceWith("ServerCapabilities", "io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities"), + level = DeprecationLevel.WARNING, +) +public object ServerCapabilities { + + @Deprecated( + message = "Use `ServerCapabilities.Prompts` instead", + replaceWith = ReplaceWith( + "ServerCapabilities.Prompts", + "io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Prompts", + ), + level = DeprecationLevel.WARNING, + ) + public fun Prompts( + listChanged: Boolean? = null, + ): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Prompts = + io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Prompts(listChanged) + + @Deprecated( + message = "Use `ServerCapabilities.Resources` instead", + replaceWith = ReplaceWith( + "ServerCapabilities.Resources", + "io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Resources", + ), + level = DeprecationLevel.WARNING, + ) + public fun Resources( + listChanged: Boolean? = null, + subscribe: Boolean? = null, + ): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Resources = + io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Resources(listChanged, subscribe) + + @Deprecated( + message = "Use `ServerCapabilities.Tools` instead", + replaceWith = ReplaceWith( + "ServerCapabilities.Tools", + "io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Tools", + ), + level = DeprecationLevel.WARNING, + ) + public fun Tools(listChanged: Boolean? = null): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Tools = + io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Tools(listChanged) } /** * Represents the capabilities that a server can support. * * @property experimental Experimental, non-standard capabilities that the server supports. - * @property sampling Present if the client supports sampling from an LLM. * @property logging Present if the server supports sending log messages to the client. * @property prompts Capabilities related to prompt templates offered by the server. * @property resources Capabilities related to resources available on the server. * @property tools Capabilities related to tools that can be called on the server. */ -@Serializable -public data class ServerCapabilities( - val experimental: JsonObject? = EmptyJsonObject, - val sampling: JsonObject? = EmptyJsonObject, - val logging: JsonObject? = EmptyJsonObject, - val prompts: Prompts? = null, - val resources: Resources? = null, - val tools: Tools? = null, -) { - /** - * Capabilities related to prompt templates. - * - * @property listChanged Indicates if the server supports notifications when the prompt list changes. - */ - @Serializable - public data class Prompts( - /** - * Whether this server supports issuing notifications for changes to the prompt list. - */ - val listChanged: Boolean?, +@Deprecated( + message = "Use `ServerCapabilities` instead", + replaceWith = ReplaceWith("ServerCapabilities", "io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities"), + level = DeprecationLevel.WARNING, +) +public fun ServerCapabilities( + tools: io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Tools? = null, + resources: io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Resources? = null, + prompts: io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Prompts? = null, + logging: JsonObject? = null, + completions: JsonObject? = null, + experimental: JsonObject? = null, +): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities = + io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities( + tools, + resources, + prompts, + logging, + completions, + experimental, ) - /** - * Capabilities related to resources. - * - * @property subscribe Indicates if clients can subscribe to resource updates. - * @property listChanged Indicates if the server supports notifications when the resource list changes. - */ - @Serializable - public data class Resources( - /** - * Whether this server supports clients subscribing to resource updates. - */ - val subscribe: Boolean?, - /** - * Whether this server supports issuing notifications for changes to the resource list. - */ - val listChanged: Boolean?, - ) - - /** - * Capabilities related to tools. - * - * @property listChanged Indicates if the server supports notifications when the tool list changes. - */ - @Serializable - public data class Tools( - /** - * Whether this server supports issuing notifications for changes to the tool list. - */ - val listChanged: Boolean?, - ) -} - /** * After receiving an initialized request from the client, the server sends this response. */ -@Serializable -public data class InitializeResult( - /** - * The version of the Model Context Protocol that the server wants to use. This may not match the version that the client requested. If the client cannot support this version, it MUST disconnect. - */ - val protocolVersion: String = LATEST_PROTOCOL_VERSION, - val capabilities: ServerCapabilities = ServerCapabilities(), - val serverInfo: Implementation, - /** - * Optional instructions from the server to the client about how to use this server. - */ - val instructions: String? = null, - override val _meta: JsonObject = EmptyJsonObject, -) : ServerResult +@Deprecated( + message = "Use `InitializeResult` instead", + replaceWith = ReplaceWith("InitializeResult", "io.modelcontextprotocol.kotlin.sdk.types.InitializeResult"), + level = DeprecationLevel.WARNING, +) +public typealias InitializeResult = io.modelcontextprotocol.kotlin.sdk.types.InitializeResult + +@Deprecated( + message = "Use `InitializedNotification` instead", + replaceWith = ReplaceWith( + "InitializedNotification", + "io.modelcontextprotocol.kotlin.sdk.types.InitializedNotification", + ), + level = DeprecationLevel.WARNING, +) +public object InitializedNotification { + @Deprecated( + message = "Use `BaseNotificationParams` instead", + replaceWith = ReplaceWith( + "BaseNotificationParams", + "io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams", + ), + level = DeprecationLevel.WARNING, + ) + public fun Params( + meta: JsonObject = EmptyJsonObject, + ): io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams = + io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams(meta) +} /** * This notification is sent from the client to the server after initialization has finished. */ -@Serializable -public data class InitializedNotification(override val params: Params = Params()) : ClientNotification { - override val method: Method = Method.Defined.NotificationsInitialized - - @Serializable - public data class Params(override val _meta: JsonObject = EmptyJsonObject) : NotificationParams -} +@Deprecated( + message = "Use `InitializedNotification` instead", + replaceWith = ReplaceWith( + "InitializedNotification", + "io.modelcontextprotocol.kotlin.sdk.types.InitializedNotification", + ), + level = DeprecationLevel.WARNING, +) +public fun InitializedNotification( + params: io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams? = null, +): io.modelcontextprotocol.kotlin.sdk.types.InitializedNotification = + io.modelcontextprotocol.kotlin.sdk.types.InitializedNotification(params) /* Ping */ @@ -536,17 +984,21 @@ public data class InitializedNotification(override val params: Params = Params() * A ping, issued by either the server or the client, to check that the other party is still alive. * The receiver must promptly respond, or else it may be disconnected. */ -@Serializable -public class PingRequest : - ServerRequest, - ClientRequest { - override val method: Method = Method.Defined.Ping -} +@Deprecated( + message = "Use `PingRequest` instead", + replaceWith = ReplaceWith("PingRequest", "io.modelcontextprotocol.kotlin.sdk.types.PingRequest"), + level = DeprecationLevel.WARNING, +) +public typealias PingRequest = io.modelcontextprotocol.kotlin.sdk.types.PingRequest /** * Represents the base interface for progress tracking. */ @Serializable +@Deprecated( + message = "This interface will be removed", + level = DeprecationLevel.WARNING, +) public sealed interface ProgressBase { /** * The progress thus far. This should increase every time progress is made, even if the total is unknown. @@ -572,598 +1024,528 @@ public sealed interface ProgressBase { * @property progress The current progress value. * @property total The total progress required, if known. */ -@Serializable -public open class Progress( - /** - * The progress thus far. This should increase every time progress is made, even if the total is unknown. - */ - override val progress: Double, +@Deprecated( + message = "Use `Progress` instead", + replaceWith = ReplaceWith("Progress", "io.modelcontextprotocol.kotlin.sdk.types.Progress"), + level = DeprecationLevel.WARNING, +) +public typealias Progress = io.modelcontextprotocol.kotlin.sdk.types.Progress - /** - * Total number of items to a process (or total progress required), if known. - */ - override val total: Double?, - - /** - * An optional message describing the current progress. - */ - override val message: String?, -) : ProgressBase +@Deprecated( + message = "Use `ProgressNotification` instead", + replaceWith = ReplaceWith("ProgressNotification", "io.modelcontextprotocol.kotlin.sdk.types.ProgressNotification"), + level = DeprecationLevel.WARNING, +) +public object ProgressNotification { + @Deprecated( + message = "Use `ProgressNotificationParams` instead", + replaceWith = ReplaceWith( + "ProgressNotificationParams", + "io.modelcontextprotocol.kotlin.sdk.types.ProgressNotificationParams", + ), + level = DeprecationLevel.WARNING, + ) + public fun Params( + progressToken: io.modelcontextprotocol.kotlin.sdk.types.ProgressToken, + progress: Double, + total: Double? = null, + message: String? = null, + meta: JsonObject? = null, + ): io.modelcontextprotocol.kotlin.sdk.types.ProgressNotificationParams = + io.modelcontextprotocol.kotlin.sdk.types.ProgressNotificationParams( + progressToken, + progress, + total, + message, + meta, + ) +} /** * An out-of-band notification used to inform the receiver of a progress update for a long-running request. */ -@Serializable -public data class ProgressNotification(override val params: Params) : - ClientNotification, - ServerNotification { - override val method: Method = Method.Defined.NotificationsProgress - - @Serializable - public data class Params( - /** - * The progress thus far. This should increase every time progress is made, even if the total is unknown. - */ - override val progress: Double, - /** - * The progress token, - * which was given in the initial request, - * used to associate this notification with the request that is proceeding. - */ - val progressToken: ProgressToken, - /** - * Total number of items to process (or total progress required), if known. - */ - override val total: Double? = null, - /** - * An optional message describing the current progress. - */ - override val message: String? = null, - override val _meta: JsonObject = EmptyJsonObject, - ) : NotificationParams, - ProgressBase -} +@Deprecated( + message = "Use `ProgressNotification` instead", + replaceWith = ReplaceWith("ProgressNotification", "io.modelcontextprotocol.kotlin.sdk.types.ProgressNotification"), + level = DeprecationLevel.WARNING, +) +public fun ProgressNotification( + params: io.modelcontextprotocol.kotlin.sdk.types.ProgressNotificationParams, +): io.modelcontextprotocol.kotlin.sdk.types.ProgressNotification = + io.modelcontextprotocol.kotlin.sdk.types.ProgressNotification(params) /* Pagination */ /** * Represents a request supporting pagination. */ -@Serializable -public sealed interface PaginatedRequest : - Request, - WithMeta { - /** - * The cursor indicating the pagination position. - */ - public val cursor: Cursor? - override val _meta: JsonObject -} +@Deprecated( + message = "Use `PaginatedRequest` instead", + replaceWith = ReplaceWith("PaginatedRequest", "io.modelcontextprotocol.kotlin.sdk.types.PaginatedRequest"), + level = DeprecationLevel.WARNING, +) +public typealias PaginatedRequest = io.modelcontextprotocol.kotlin.sdk.types.PaginatedRequest /** * Represents a paginated result of a request. */ -@Serializable -public sealed interface PaginatedResult : RequestResult { - /** - * An opaque token representing the pagination position after the last returned result. - * If present, there may be more results available. - */ - public val nextCursor: Cursor? -} +@Deprecated( + message = "Use `PaginatedResult` instead", + replaceWith = ReplaceWith("PaginatedResult", "io.modelcontextprotocol.kotlin.sdk.types.PaginatedResult"), + level = DeprecationLevel.WARNING, +) +public typealias PaginatedResult = io.modelcontextprotocol.kotlin.sdk.types.PaginatedResult /* Resources */ /** * The contents of a specific resource or sub-resource. */ -@Serializable(with = ResourceContentsPolymorphicSerializer::class) -public sealed interface ResourceContents { - /** - * The URI of this resource. - */ - public val uri: String - - /** - * The MIME type of this resource, if known. - */ - public val mimeType: String? -} +@Deprecated( + message = "Use `ResourceContents` instead", + replaceWith = ReplaceWith("ResourceContents", "io.modelcontextprotocol.kotlin.sdk.types.ResourceContents"), + level = DeprecationLevel.WARNING, +) +public typealias ResourceContents = io.modelcontextprotocol.kotlin.sdk.types.ResourceContents /** * Represents the text contents of a resource. * * @property text The text of the item. This must only be set if the item can actually be represented as text (not binary data). */ -@Serializable -public data class TextResourceContents(val text: String, override val uri: String, override val mimeType: String?) : - ResourceContents +@Deprecated( + message = "Use `TextResourceContents` instead", + replaceWith = ReplaceWith("TextResourceContents", "io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents"), + level = DeprecationLevel.WARNING, +) +public typealias TextResourceContents = io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents /** * Represents the binary contents of a resource encoded as a base64 string. * * @property blob A base64-encoded string representing the binary data of the item. */ -@Serializable -public data class BlobResourceContents(val blob: String, override val uri: String, override val mimeType: String?) : - ResourceContents +@Deprecated( + message = "Use `BlobResourceContents` instead", + replaceWith = ReplaceWith("BlobResourceContents", "io.modelcontextprotocol.kotlin.sdk.types.BlobResourceContents"), + level = DeprecationLevel.WARNING, +) +public typealias BlobResourceContents = io.modelcontextprotocol.kotlin.sdk.types.BlobResourceContents /** * Represents resource contents with unknown or unspecified data. */ -@Serializable -public data class UnknownResourceContents(override val uri: String, override val mimeType: String?) : ResourceContents +@Deprecated( + message = "Use `UnknownResourceContents` instead", + replaceWith = ReplaceWith( + "UnknownResourceContents", + "io.modelcontextprotocol.kotlin.sdk.types.UnknownResourceContents", + ), + level = DeprecationLevel.WARNING, +) +public typealias UnknownResourceContents = io.modelcontextprotocol.kotlin.sdk.types.UnknownResourceContents /** * A known resource that the server is capable of reading. */ -@Serializable -public data class Resource( - /** - * The URI of this resource. - */ - val uri: String, - /** - * A human-readable name for this resource. - * - * Clients can use this to populate UI elements. - */ - val name: String, - /** - * A description of what this resource represents. - * - * Clients can use this to improve the LLM's understanding of available resources. - * It can be thought of as a "hint" to the model. - */ - val description: String?, - /** - * The MIME type of this resource, if known. - */ - val mimeType: String?, - /** - * The optional human-readable name of this resource for display purposes. - */ - val title: String? = null, - /** - * The optional size of this resource in bytes, if known. - */ - val size: Long? = null, - /** - * Optional annotations for the client. - */ - val annotations: Annotations? = null, +@Deprecated( + message = "Use `Resource` instead", + replaceWith = ReplaceWith("Resource", "io.modelcontextprotocol.kotlin.sdk.types.Resource"), + level = DeprecationLevel.WARNING, ) +public typealias Resource = io.modelcontextprotocol.kotlin.sdk.types.Resource /** * A template description for resources available on the server. */ -@Serializable -public data class ResourceTemplate( - /** - * A URI template (according to RFC 6570) that can be used to construct resource URIs. - */ - val uriTemplate: String, - /** - * A human-readable name for the type of resource this template refers to. - * - * Clients can use this to populate UI elements. - */ - val name: String, - /** - * A description of what this template is for. - * - * Clients can use this to improve the LLM's understanding of available resources. - * It can be thought of as a "hint" to the model. - */ - val description: String?, - /** - * The MIME type for all resources that match this template. This should only be included if all resources matching this template have the same type. - */ - val mimeType: String?, - /** - * The optional human-readable name of this resource for display purposes. - */ - val title: String? = null, - /** - * Optional annotations for the client. - */ - val annotations: Annotations? = null, +@Deprecated( + message = "Use `ResourceTemplate` instead", + replaceWith = ReplaceWith("ResourceTemplate", "io.modelcontextprotocol.kotlin.sdk.types.ResourceTemplate"), + level = DeprecationLevel.WARNING, ) +public typealias ResourceTemplate = io.modelcontextprotocol.kotlin.sdk.types.ResourceTemplate /** * Sent from the client to request a list of resources the server has. */ -@Serializable -public data class ListResourcesRequest( - override val cursor: Cursor? = null, - override val _meta: JsonObject = EmptyJsonObject, -) : ClientRequest, - PaginatedRequest { - override val method: Method = Method.Defined.ResourcesList -} +@Deprecated( + message = "Use `ListResourcesRequest` instead", + replaceWith = ReplaceWith("ListResourcesRequest", "io.modelcontextprotocol.kotlin.sdk.types.ListResourcesRequest"), + level = DeprecationLevel.WARNING, +) +public typealias ListResourcesRequest = io.modelcontextprotocol.kotlin.sdk.types.ListResourcesRequest /** * The server's response to a resources/list request from the client. */ -@Serializable -public class ListResourcesResult( - public val resources: List, - override val nextCursor: Cursor? = null, - override val _meta: JsonObject = EmptyJsonObject, -) : ServerResult, - PaginatedResult +@Deprecated( + message = "Use `ListResourcesResult` instead", + replaceWith = ReplaceWith("ListResourcesResult", "io.modelcontextprotocol.kotlin.sdk.types.ListResourcesResult"), + level = DeprecationLevel.WARNING, +) +public typealias ListResourcesResult = io.modelcontextprotocol.kotlin.sdk.types.ListResourcesResult /** * Sent from the client to request a list of resource templates the server has. */ -@Serializable -public data class ListResourceTemplatesRequest( - override val cursor: Cursor?, - override val _meta: JsonObject = EmptyJsonObject, -) : ClientRequest, - PaginatedRequest { - override val method: Method = Method.Defined.ResourcesTemplatesList -} +@Deprecated( + message = "Use `ListResourceTemplatesRequest` instead", + replaceWith = ReplaceWith( + "ListResourceTemplatesRequest", + "io.modelcontextprotocol.kotlin.sdk.types.ListResourceTemplatesRequest", + ), + level = DeprecationLevel.WARNING, +) +public typealias ListResourceTemplatesRequest = io.modelcontextprotocol.kotlin.sdk.types.ListResourceTemplatesRequest /** * The server's response to a resources/templates/list request from the client. */ -@Serializable -public class ListResourceTemplatesResult( - public val resourceTemplates: List, - override val nextCursor: Cursor? = null, - override val _meta: JsonObject = EmptyJsonObject, -) : ServerResult, - PaginatedResult +@Deprecated( + message = "Use `ListResourceTemplatesResult` instead", + replaceWith = ReplaceWith( + "ListResourceTemplatesResult", + "io.modelcontextprotocol.kotlin.sdk.types.ListResourceTemplatesResult", + ), + level = DeprecationLevel.WARNING, +) +public typealias ListResourceTemplatesResult = io.modelcontextprotocol.kotlin.sdk.types.ListResourceTemplatesResult /** * Sent from the client to the server to read a specific resource URI. */ -@Serializable -public data class ReadResourceRequest(val uri: String, override val _meta: JsonObject = EmptyJsonObject) : - ClientRequest, - WithMeta { - override val method: Method = Method.Defined.ResourcesRead -} +@Deprecated( + message = "Use `ReadResourceRequest` instead", + replaceWith = ReplaceWith("ReadResourceRequest", "io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest"), + level = DeprecationLevel.WARNING, +) +public typealias ReadResourceRequest = io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest /** * The server's response to a resources/read request from the client. */ -@Serializable -public class ReadResourceResult( - public val contents: List, - override val _meta: JsonObject = EmptyJsonObject, -) : ServerResult +@Deprecated( + message = "Use `ReadResourceResult` instead", + replaceWith = ReplaceWith("ReadResourceResult", "io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult"), + level = DeprecationLevel.WARNING, +) +public typealias ReadResourceResult = io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult + +@Deprecated( + message = "Use `ResourceListChangedNotification` instead", + replaceWith = ReplaceWith( + "ResourceListChangedNotification", + "io.modelcontextprotocol.kotlin.sdk.types.ResourceListChangedNotification", + ), + level = DeprecationLevel.WARNING, +) +public object ResourceListChangedNotification { + @Deprecated( + message = "Use `BaseNotificationParams` instead", + replaceWith = ReplaceWith( + "BaseNotificationParams", + "io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams", + ), + level = DeprecationLevel.WARNING, + ) + public fun Params(meta: JsonObject? = null): io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams = + io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams(meta) +} /** * An optional notification from the server to the client, * informing it that the list of resources it can read from has changed. * Servers may issue this without any previous subscription from the client. */ -@Serializable -public data class ResourceListChangedNotification(override val params: Params = Params()) : ServerNotification { - override val method: Method = Method.Defined.NotificationsResourcesListChanged - - @Serializable - public data class Params(override val _meta: JsonObject = EmptyJsonObject) : NotificationParams -} +@Deprecated( + message = "Use `ResourceListChangedNotification` instead", + replaceWith = ReplaceWith( + "ResourceListChangedNotification", + "io.modelcontextprotocol.kotlin.sdk.types.ResourceListChangedNotification", + ), + level = DeprecationLevel.WARNING, +) +public fun ResourceListChangedNotification( + params: io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams? = null, +): io.modelcontextprotocol.kotlin.sdk.types.ResourceListChangedNotification = + io.modelcontextprotocol.kotlin.sdk.types.ResourceListChangedNotification(params) /** * Sent from the client to request resources/updated notifications from the server whenever a particular resource changes. */ -@Serializable -public data class SubscribeRequest( - /** - * The URI of the resource to subscribe to. The URI can use any protocol; it is up to the server how to interpret it. - */ - val uri: String, - override val _meta: JsonObject = EmptyJsonObject, -) : ClientRequest, - WithMeta { - override val method: Method = Method.Defined.ResourcesSubscribe -} +@Deprecated( + message = "Use `SubscribeRequest` instead", + replaceWith = ReplaceWith("SubscribeRequest", "io.modelcontextprotocol.kotlin.sdk.types.SubscribeRequest"), + level = DeprecationLevel.WARNING, +) +public typealias SubscribeRequest = io.modelcontextprotocol.kotlin.sdk.types.SubscribeRequest /** * Sent from the client to request cancellation of resources/updated notifications from the server. This should follow a previous resources/subscribe request. */ -@Serializable -public data class UnsubscribeRequest( - /** - * The URI of the resource to unsubscribe from. - */ - val uri: String, - override val _meta: JsonObject = EmptyJsonObject, -) : ClientRequest, - WithMeta { - override val method: Method = Method.Defined.ResourcesUnsubscribe +@Deprecated( + message = "Use `UnsubscribeRequest` instead", + replaceWith = ReplaceWith("UnsubscribeRequest", "io.modelcontextprotocol.kotlin.sdk.types.UnsubscribeRequest"), + level = DeprecationLevel.WARNING, +) +public typealias UnsubscribeRequest = io.modelcontextprotocol.kotlin.sdk.types.UnsubscribeRequest + +@Deprecated( + message = "Use `ResourceUpdatedNotification` instead", + replaceWith = ReplaceWith( + "ResourceUpdatedNotification", + "io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotification", + ), + level = DeprecationLevel.WARNING, +) +public object ResourceUpdatedNotification { + @Deprecated( + message = "Use `ResourceUpdatedNotificationParams` instead", + replaceWith = ReplaceWith( + "ResourceUpdatedNotificationParams", + "io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotificationParams", + ), + level = DeprecationLevel.WARNING, + ) + public fun Params( + uri: String, + meta: JsonObject? = null, + ): io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotificationParams = + io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotificationParams(uri, meta) } /** * A notification from the server to the client, informing it that a resource has changed and may need to be read again. This should only be sent if the client previously sent a resources/subscribe request. */ -@Serializable -public data class ResourceUpdatedNotification(override val params: Params) : ServerNotification { - override val method: Method = Method.Defined.NotificationsResourcesUpdated - - @Serializable - public data class Params( - /** - * The URI of the resource that has been updated. This might be a sub-resource of the one that the client actually subscribed to. - */ - val uri: String, - override val _meta: JsonObject = EmptyJsonObject, - ) : NotificationParams -} +@Deprecated( + message = "Use `ResourceUpdatedNotification` instead", + replaceWith = ReplaceWith( + "ResourceUpdatedNotification", + "io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotification", + ), + level = DeprecationLevel.WARNING, +) +public fun ResourceUpdatedNotification( + params: io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotificationParams, +): io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotification = + io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotification(params) /* Prompts */ /** * Describes an argument that a prompt can accept. */ -@Serializable -public data class PromptArgument( - /** - * The name of the argument. - */ - val name: String, - /** - * A human-readable description of the argument. - */ - val description: String?, - /** - * Whether this argument must be provided. - */ - val required: Boolean?, +@Deprecated( + message = "Use `PromptArgument` instead", + replaceWith = ReplaceWith("PromptArgument", "io.modelcontextprotocol.kotlin.sdk.types.PromptArgument"), + level = DeprecationLevel.WARNING, ) +public typealias PromptArgument = io.modelcontextprotocol.kotlin.sdk.types.PromptArgument /** * A prompt or prompt template that the server offers. */ -@Serializable -public class Prompt( - /** - * The name of the prompt or prompt template. - */ - public val name: String, - /** - * An optional description of what this prompt provides - */ - public val description: String?, - /** - * A list of arguments to use for templating the prompt. - */ - public val arguments: List?, +@Deprecated( + message = "Use `Prompt` instead", + replaceWith = ReplaceWith("Prompt", "io.modelcontextprotocol.kotlin.sdk.types.Prompt"), + level = DeprecationLevel.WARNING, ) +public typealias Prompt = io.modelcontextprotocol.kotlin.sdk.types.Prompt /** * Sent from the client to request a list of prompts and prompt templates the server has. */ -@Serializable -public data class ListPromptsRequest( - override val cursor: Cursor? = null, - override val _meta: JsonObject = EmptyJsonObject, -) : ClientRequest, - PaginatedRequest { - override val method: Method = Method.Defined.PromptsList -} +@Deprecated( + message = "Use `ListPromptsRequest` instead", + replaceWith = ReplaceWith("ListPromptsRequest", "io.modelcontextprotocol.kotlin.sdk.types.ListPromptsRequest"), + level = DeprecationLevel.WARNING, +) +public typealias ListPromptsRequest = io.modelcontextprotocol.kotlin.sdk.types.ListPromptsRequest /** * The server's response to a prompts/list request from the client. */ -@Serializable -public class ListPromptsResult( - public val prompts: List, - override val nextCursor: Cursor? = null, - override val _meta: JsonObject = EmptyJsonObject, -) : ServerResult, - PaginatedResult +@Deprecated( + message = "Use `ListPromptsResult` instead", + replaceWith = ReplaceWith("ListPromptsResult", "io.modelcontextprotocol.kotlin.sdk.types.ListPromptsResult"), + level = DeprecationLevel.WARNING, +) +public typealias ListPromptsResult = io.modelcontextprotocol.kotlin.sdk.types.ListPromptsResult /** * Used by the client to get a prompt provided by the server. */ -@Serializable -public data class GetPromptRequest( - /** - * The name of the prompt or prompt template. - */ - val name: String, - - /** - * Arguments to use for templating the prompt. - */ - val arguments: Map?, - - override val _meta: JsonObject = EmptyJsonObject, -) : ClientRequest, - WithMeta { - override val method: Method = Method.Defined.PromptsGet -} +@Deprecated( + message = "Use `GetPromptRequest` instead", + replaceWith = ReplaceWith("GetPromptRequest", "io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest"), + level = DeprecationLevel.WARNING, +) +public typealias GetPromptRequest = io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest /** * Represents the content of a prompt message. */ -@Serializable(with = PromptMessageContentPolymorphicSerializer::class) -public sealed interface PromptMessageContent { - public val type: String -} +@Deprecated( + message = "Use `ContentBlock` instead", + replaceWith = ReplaceWith("ContentBlock"), + level = DeprecationLevel.WARNING, +) +public typealias PromptMessageContent = io.modelcontextprotocol.kotlin.sdk.types.ContentBlock /** * Represents prompt message content that is either text, image or audio. */ -@Serializable(with = PromptMessageContentMultimodalPolymorphicSerializer::class) -public sealed interface PromptMessageContentMultimodal : PromptMessageContent +@Deprecated( + message = "Use `MediaContent` instead", + replaceWith = ReplaceWith("MediaContent"), + level = DeprecationLevel.WARNING, +) +public typealias PromptMessageContentMultimodal = io.modelcontextprotocol.kotlin.sdk.types.MediaContent /** * Text provided to or from an LLM. */ -@Serializable -public data class TextContent( - /** - * The text content of the message. - */ - val text: String? = null, - - /** - * Optional annotations for the client. - */ - val annotations: Annotations? = null, -) : PromptMessageContentMultimodal { - override val type: String = TYPE - - public companion object { - public const val TYPE: String = "text" - } -} +@Deprecated( + message = "Use `TextContent` instead", + replaceWith = ReplaceWith("TextContent", "io.modelcontextprotocol.kotlin.sdk.types.TextContent"), + level = DeprecationLevel.WARNING, +) +public typealias TextContent = io.modelcontextprotocol.kotlin.sdk.types.TextContent /** * An image provided to or from an LLM. */ -@Serializable -public data class ImageContent( - /** - * The base64-encoded image data. - */ - val data: String, - - /** - * The MIME type of the image. Different providers may support different image types. - */ - val mimeType: String, - - /** - * Optional annotations for the client. - */ - val annotations: Annotations? = null, -) : PromptMessageContentMultimodal { - override val type: String = TYPE - - public companion object { - public const val TYPE: String = "image" - } -} +@Deprecated( + message = "Use `ImageContent` instead", + replaceWith = ReplaceWith("ImageContent", "io.modelcontextprotocol.kotlin.sdk.types.ImageContent"), + level = DeprecationLevel.WARNING, +) +public typealias ImageContent = io.modelcontextprotocol.kotlin.sdk.types.ImageContent /** * Audio provided to or from an LLM. */ -@Serializable -public data class AudioContent( - /** - * The base64-encoded audio data. - */ - val data: String, - - /** - * The MIME type of the audio. Different providers may support different audio types. - */ - val mimeType: String, - - /** - * Optional annotations for the client. - */ - val annotations: Annotations? = null, -) : PromptMessageContentMultimodal { - override val type: String = TYPE - - public companion object { - public const val TYPE: String = "audio" - } -} +@Deprecated( + message = "Use `AudioContent` instead", + replaceWith = ReplaceWith("AudioContent", "io.modelcontextprotocol.kotlin.sdk.types.AudioContent"), + level = DeprecationLevel.WARNING, +) +public typealias AudioContent = io.modelcontextprotocol.kotlin.sdk.types.AudioContent /** * Unknown content provided to or from an LLM. */ @Serializable -public data class UnknownContent(override val type: String) : PromptMessageContentMultimodal +@Deprecated( + message = "This class will be removed.", + replaceWith = ReplaceWith("MediaContent"), + level = DeprecationLevel.WARNING, +) +public data class UnknownContent(val type: String) /** * The contents of a resource, embedded into a prompt or tool call result. */ -@Serializable -public data class EmbeddedResource( - /** - * The contents of the embedded resource. - */ - val resource: ResourceContents, - - /** - * Optional annotations for the client. - */ - val annotations: Annotations? = null, -) : PromptMessageContent { - override val type: String = TYPE - - public companion object { - public const val TYPE: String = "resource" - } -} +@Deprecated( + message = "Use `EmbeddedResource` instead", + replaceWith = ReplaceWith("EmbeddedResource", "io.modelcontextprotocol.kotlin.sdk.types.EmbeddedResource"), + level = DeprecationLevel.WARNING, +) +public typealias EmbeddedResource = io.modelcontextprotocol.kotlin.sdk.types.EmbeddedResource /** * Enum representing the role of a participant. */ -@Suppress("EnumEntryName") -@Serializable -public enum class Role { - user, - assistant, +@Deprecated( + message = "Use `Role` instead", + replaceWith = ReplaceWith("Role", "io.modelcontextprotocol.kotlin.sdk.types.Role"), + level = DeprecationLevel.WARNING, +) +public object Role { + @Deprecated( + message = "Use `Role.User` instead", + replaceWith = ReplaceWith("Role.User", "io.modelcontextprotocol.kotlin.sdk.types.Role"), + level = DeprecationLevel.WARNING, + ) + public val user: io.modelcontextprotocol.kotlin.sdk.types.Role = io.modelcontextprotocol.kotlin.sdk.types.Role.User + + @Deprecated( + message = "Use `Role.Assistant` instead", + replaceWith = ReplaceWith("Role.Assistant", "io.modelcontextprotocol.kotlin.sdk.types.Role"), + level = DeprecationLevel.WARNING, + ) + public val assistant: io.modelcontextprotocol.kotlin.sdk.types.Role = + io.modelcontextprotocol.kotlin.sdk.types.Role.Assistant } /** * Optional annotations for the client. * The client can use annotations to inform how objects are used or displayed. */ -@Serializable -public data class Annotations( - /** - * Describes who the intended customer of this object or data is. - */ - val audience: List?, - /** - * The moment the resource was last modified. - */ - val lastModified: String?, - /** - * Describes how important this data is for operating the server. - * - * A value of 1.0 means "most important", and indicates that the data is effectively required, - * while 0.0 means "less important", and indicates that the data is entirely optional. - */ - val priority: Double?, -) { - init { - require(priority == null || priority in 0.0..1.0) { "Priority must be between 0.0 and 1.0" } - } -} +@Deprecated( + message = "Use `Annotations` instead", + replaceWith = ReplaceWith("Annotations", "io.modelcontextprotocol.kotlin.sdk.types.Annotations"), + level = DeprecationLevel.WARNING, +) +public typealias Annotations = io.modelcontextprotocol.kotlin.sdk.types.Annotations /** * Describes a message returned as part of a prompt. */ -@Serializable -public data class PromptMessage(val role: Role, val content: PromptMessageContent) +@Deprecated( + message = "Use `PromptMessage` instead", + replaceWith = ReplaceWith("PromptMessage", "io.modelcontextprotocol.kotlin.sdk.types.PromptMessage"), + level = DeprecationLevel.WARNING, +) +public typealias PromptMessage = io.modelcontextprotocol.kotlin.sdk.types.PromptMessage /** * The server's response to a prompts/get request from the client. */ -@Serializable -public class GetPromptResult( - /** - * An optional description for the prompt. - */ - public val description: String?, - public val messages: List, - override val _meta: JsonObject = EmptyJsonObject, -) : ServerResult +@Deprecated( + message = "Use `GetPromptResult` instead", + replaceWith = ReplaceWith("GetPromptResult", "io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult"), + level = DeprecationLevel.WARNING, +) +public typealias GetPromptResult = io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult + +@Deprecated( + message = "Use `PromptListChangedNotification` instead", + replaceWith = ReplaceWith( + "PromptListChangedNotification", + "io.modelcontextprotocol.kotlin.sdk.types.PromptListChangedNotification", + ), + level = DeprecationLevel.WARNING, +) +public object PromptListChangedNotification { + @Deprecated( + message = "Use `BaseNotificationParams` instead", + replaceWith = ReplaceWith( + "BaseNotificationParams", + "io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams", + ), + level = DeprecationLevel.WARNING, + ) + public fun Params(meta: JsonObject? = null): io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams = + io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams(meta) +} /** * An optional notification from the server to the client, informing it that the list of prompts it offers has changed. * Servers may issue this without any previous subscription from the client. */ -@Serializable -public data class PromptListChangedNotification(override val params: Params = Params()) : ServerNotification { - override val method: Method = Method.Defined.NotificationsPromptsListChanged - - @Serializable - public data class Params(override val _meta: JsonObject = EmptyJsonObject) : NotificationParams -} +@Deprecated( + message = "Use `PromptListChangedNotification` instead", + replaceWith = ReplaceWith( + "PromptListChangedNotification", + "io.modelcontextprotocol.kotlin.sdk.types.PromptListChangedNotification", + ), + level = DeprecationLevel.WARNING, +) +public fun PromptListChangedNotification( + params: io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams, +): io.modelcontextprotocol.kotlin.sdk.types.PromptListChangedNotification = + io.modelcontextprotocol.kotlin.sdk.types.PromptListChangedNotification(params) /* Tools */ @@ -1177,194 +1559,306 @@ public data class PromptListChangedNotification(override val params: Params = Pa * Clients should never make tool use decisions based on ToolAnnotations * received from untrusted servers. */ -@Serializable -public data class ToolAnnotations( - /** - * A human-readable title for the tool. - */ - val title: String?, - /** - * If true, the tool does not modify its environment. - * - * Default: false - */ - val readOnlyHint: Boolean? = false, - /** - * If true, the tool may perform destructive updates to its environment. - * If false, the tool performs only additive updates. - * - * (This property is meaningful only when `readOnlyHint == false`) - * - * Default: true - */ - val destructiveHint: Boolean? = true, - /** - * If true, calling the tool repeatedly with the same arguments - * will have no additional effect on the its environment. - * - * (This property is meaningful only when `readOnlyHint == false`) - * - * Default: false - */ - val idempotentHint: Boolean? = false, - /** - * If true, this tool may interact with an "open world" of external - * entities. If false, the tool's domain of interaction is closed. - * For example, the world of a web search tool is open, whereas that - * of a memory tool is not. - * - * Default: true - */ - val openWorldHint: Boolean? = true, +@Deprecated( + message = "Use `ToolAnnotations` instead", + replaceWith = ReplaceWith("ToolAnnotations", "io.modelcontextprotocol.kotlin.sdk.types.ToolAnnotations"), + level = DeprecationLevel.WARNING, ) +public typealias ToolAnnotations = io.modelcontextprotocol.kotlin.sdk.types.ToolAnnotations + +@Deprecated( + message = "Use `Tool` instead", + replaceWith = ReplaceWith("Tool", "io.modelcontextprotocol.kotlin.sdk.types.Tool"), + level = DeprecationLevel.WARNING, +) +public object Tool { + @Deprecated( + message = "Use `ToolSchema` instead", + replaceWith = ReplaceWith("ToolSchema", "io.modelcontextprotocol.kotlin.sdk.types.ToolSchema"), + level = DeprecationLevel.WARNING, + ) + public fun Input( + properties: JsonObject? = null, + required: List? = null, + ): io.modelcontextprotocol.kotlin.sdk.types.ToolSchema = + io.modelcontextprotocol.kotlin.sdk.types.ToolSchema(properties, required) + + @Deprecated( + message = "Use `ToolSchema` instead", + replaceWith = ReplaceWith("ToolSchema", "io.modelcontextprotocol.kotlin.sdk.types.ToolSchema"), + level = DeprecationLevel.WARNING, + ) + public fun Output( + properties: JsonObject? = null, + required: List? = null, + ): io.modelcontextprotocol.kotlin.sdk.types.ToolSchema = + io.modelcontextprotocol.kotlin.sdk.types.ToolSchema(properties, required) +} /** * Definition for a tool the client can call. */ -@Serializable -public data class Tool( - /** - * The name of the tool. - */ - val name: String, - /** - * The title of the tool. - */ - val title: String?, - /** - * A human-readable description of the tool. - */ - val description: String?, - /** - * A JSON object defining the expected parameters for the tool. - */ - val inputSchema: Input, - /** - * An optional JSON object defining the expected output schema for the tool. - */ - val outputSchema: Output?, - /** - * Optional additional tool information. - */ - val annotations: ToolAnnotations?, - - /** - * Optional metadata for the tool. - */ - override val _meta: JsonObject = EmptyJsonObject, -) : WithMeta { - @Serializable - public data class Input(val properties: JsonObject = EmptyJsonObject, val required: List? = null) { - @OptIn(ExperimentalSerializationApi::class) - @EncodeDefault - val type: String = "object" - } - - @Serializable - public data class Output(val properties: JsonObject = EmptyJsonObject, val required: List? = null) { - @OptIn(ExperimentalSerializationApi::class) - @EncodeDefault - val type: String = "object" - } -} +@Deprecated( + message = "Use `Tool` instead", + replaceWith = ReplaceWith("Tool", "io.modelcontextprotocol.kotlin.sdk.types.Tool"), + level = DeprecationLevel.WARNING, +) +public fun Tool( + name: String, + inputSchema: io.modelcontextprotocol.kotlin.sdk.types.ToolSchema, + description: String? = null, + outputSchema: io.modelcontextprotocol.kotlin.sdk.types.ToolSchema? = null, + title: String? = null, + annotations: io.modelcontextprotocol.kotlin.sdk.types.ToolAnnotations? = null, + icons: List? = null, + _meta: JsonObject? = null, +): io.modelcontextprotocol.kotlin.sdk.types.Tool = io.modelcontextprotocol.kotlin.sdk.types.Tool( + name, + inputSchema, + description, + outputSchema, + title, + annotations, + icons, + _meta, +) /** * Sent from the client to request a list of tools the server has. */ -@Serializable -public data class ListToolsRequest( - override val cursor: Cursor? = null, - override val _meta: JsonObject = EmptyJsonObject, -) : ClientRequest, - PaginatedRequest { - override val method: Method = Method.Defined.ToolsList -} +@Deprecated( + message = "Use `ListToolsRequest` instead", + replaceWith = ReplaceWith("ListToolsRequest", "io.modelcontextprotocol.kotlin.sdk.types.ListToolsRequest"), + level = DeprecationLevel.WARNING, +) +public typealias ListToolsRequest = io.modelcontextprotocol.kotlin.sdk.types.ListToolsRequest /** * The server's response to a tools/list request from the client. */ -@Serializable -public class ListToolsResult( - public val tools: List, - override val nextCursor: Cursor?, - override val _meta: JsonObject = EmptyJsonObject, -) : ServerResult, - PaginatedResult +@Deprecated( + message = "Use `ListToolsResult` instead", + replaceWith = ReplaceWith("ListToolsResult", "io.modelcontextprotocol.kotlin.sdk.types.ListToolsResult"), + level = DeprecationLevel.WARNING, +) +public typealias ListToolsResult = io.modelcontextprotocol.kotlin.sdk.types.ListToolsResult /** * The server's response to a tool call. */ @Serializable -public sealed interface CallToolResultBase : ServerResult { +@Deprecated( + message = "This interface will be removed.", + replaceWith = ReplaceWith("CallToolResult", "io.modelcontextprotocol.kotlin.sdk.types.CallToolResult"), + level = DeprecationLevel.WARNING, +) +public sealed interface CallToolResultBase { public val content: List public val structuredContent: JsonObject? public val isError: Boolean? get() = false } +@Deprecated( + message = "Use `CallToolResult` instead", + replaceWith = ReplaceWith("CallToolResult", "io.modelcontextprotocol.kotlin.sdk.types.CallToolResult"), + level = DeprecationLevel.WARNING, +) +public object CallToolResult + /** * The server's response to a tool call. */ -@Serializable -public class CallToolResult( - override val content: List, - override val structuredContent: JsonObject? = null, - override val isError: Boolean? = false, - override val _meta: JsonObject = EmptyJsonObject, -) : CallToolResultBase +@Deprecated( + message = "Use `CallToolResult` instead", + replaceWith = ReplaceWith("CallToolResult", "io.modelcontextprotocol.kotlin.sdk.types.CallToolResult"), + level = DeprecationLevel.WARNING, +) +public fun CallToolResult( + content: List, + isError: Boolean? = null, + structuredContent: JsonObject? = null, + meta: JsonObject? = null, +): io.modelcontextprotocol.kotlin.sdk.types.CallToolResult = + io.modelcontextprotocol.kotlin.sdk.types.CallToolResult(content, isError, structuredContent, meta) /** * [CallToolResult] extended with backwards compatibility to protocol version 2024-11-05. */ @Serializable +@Deprecated( + message = "This class will be removed.", + replaceWith = ReplaceWith("ToolCallResult"), + level = DeprecationLevel.WARNING, +) public class CompatibilityCallToolResult( - override val content: List, - override val structuredContent: JsonObject? = null, - override val isError: Boolean? = false, - override val _meta: JsonObject = EmptyJsonObject, + public val content: List, + public val structuredContent: JsonObject? = null, + public val isError: Boolean? = false, + public val _meta: JsonObject = EmptyJsonObject, public val toolResult: JsonObject = EmptyJsonObject, -) : CallToolResultBase +) /** * Used by the client to invoke a tool provided by the server. */ -@Serializable -public data class CallToolRequest( - val name: String, - val arguments: JsonObject = EmptyJsonObject, - override val _meta: JsonObject = EmptyJsonObject, -) : ClientRequest, - WithMeta { - override val method: Method = Method.Defined.ToolsCall +@Deprecated( + message = "Use `CallToolRequest` instead", + replaceWith = ReplaceWith("CallToolRequest", "io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest"), + level = DeprecationLevel.WARNING, +) +public typealias CallToolRequest = io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest + +@Deprecated( + message = "Use `ToolListChangedNotification` instead", + replaceWith = ReplaceWith( + "ToolListChangedNotification", + "io.modelcontextprotocol.kotlin.sdk.types.ToolListChangedNotification", + ), + level = DeprecationLevel.WARNING, +) +public object ToolListChangedNotification { + @Deprecated( + message = "Use `BaseNotificationParams` instead", + replaceWith = ReplaceWith( + "BaseNotificationParams", + "io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams", + ), + level = DeprecationLevel.WARNING, + ) + public fun Params(meta: JsonObject? = null): io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams = + io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams(meta) } /** * An optional notification from the server to the client, informing it that the list of tools it offers has changed. * Servers may issue this without any previous subscription from the client. */ -@Serializable -public data class ToolListChangedNotification(override val params: Params = Params()) : ServerNotification { - override val method: Method = Method.Defined.NotificationsToolsListChanged - - @Serializable - public data class Params(override val _meta: JsonObject = EmptyJsonObject) : NotificationParams -} +@Deprecated( + message = "Use `ToolListChangedNotification` instead", + replaceWith = ReplaceWith( + "ToolListChangedNotification", + "io.modelcontextprotocol.kotlin.sdk.types.ToolListChangedNotification", + ), + level = DeprecationLevel.WARNING, +) +public fun ToolListChangedNotification( + params: io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams, +): io.modelcontextprotocol.kotlin.sdk.types.ToolListChangedNotification = + io.modelcontextprotocol.kotlin.sdk.types.ToolListChangedNotification(params) /* Logging */ /** * The severity of a log message. */ -@Suppress("EnumEntryName") -@Serializable -public enum class LoggingLevel { - debug, - info, - notice, - warning, - error, - critical, - alert, - emergency, +@Deprecated( + message = "Use `LoggingLevel` instead", + replaceWith = ReplaceWith("LoggingLevel", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), + level = DeprecationLevel.WARNING, +) +public object LoggingLevel { + @Deprecated( + message = "Use `LoggingLevel.Debug` instead", + replaceWith = ReplaceWith("LoggingLevel.Debug", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), + level = DeprecationLevel.WARNING, + ) + public val debug: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel = + io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel.Debug + + @Deprecated( + message = "Use `LoggingLevel.Info` instead", + replaceWith = ReplaceWith("LoggingLevel.Info", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), + level = DeprecationLevel.WARNING, + ) + public val info: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel = + io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel.Info + + @Deprecated( + message = "Use `LoggingLevel.Notice` instead", + replaceWith = ReplaceWith("LoggingLevel.Notice", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), + level = DeprecationLevel.WARNING, + ) + public val notice: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel = + io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel.Notice + + @Deprecated( + message = "Use `LoggingLevel.Warning` instead", + replaceWith = ReplaceWith("LoggingLevel.Warning", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), + level = DeprecationLevel.WARNING, + ) + public val warning: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel = + io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel.Warning + + @Deprecated( + message = "Use `LoggingLevel.Error` instead", + replaceWith = ReplaceWith("LoggingLevel.Error", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), + level = DeprecationLevel.WARNING, + ) + public val error: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel = + io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel.Error + + @Deprecated( + message = "Use `LoggingLevel.Critical` instead", + replaceWith = ReplaceWith("LoggingLevel.Critical", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), + level = DeprecationLevel.WARNING, + ) + public val critical: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel = + io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel.Critical + + @Deprecated( + message = "Use `LoggingLevel.Alert` instead", + replaceWith = ReplaceWith("LoggingLevel.Alert", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), + level = DeprecationLevel.WARNING, + ) + public val alert: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel = + io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel.Alert + + @Deprecated( + message = "Use `LoggingLevel.Emergency` instead", + replaceWith = ReplaceWith("LoggingLevel.Emergency", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), + level = DeprecationLevel.WARNING, + ) + public val emergency: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel = + io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel.Emergency +} + +@Deprecated( + message = "Use `LoggingMessageNotification` instead", + replaceWith = ReplaceWith( + "LoggingMessageNotification", + "io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotification", + ), + level = DeprecationLevel.WARNING, +) +public object LoggingMessageNotification { + @Deprecated( + message = "Use `LoggingMessageNotificationParams` instead", + replaceWith = ReplaceWith( + "LoggingMessageNotificationParams", + "io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotificationParams", + ), + level = DeprecationLevel.WARNING, + ) + public fun Params( + level: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel, + data: JsonElement, + logger: String? = null, + meta: JsonObject? = null, + ): io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotificationParams = + io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotificationParams(level, data, logger, meta) + + @Deprecated( + message = "Use `SetLevelRequest` instead", + replaceWith = ReplaceWith( + "SetLevelRequest", + "io.modelcontextprotocol.kotlin.sdk.types.SetLevelRequest", + ), + level = DeprecationLevel.WARNING, + ) + public fun SetLevelRequest( + params: io.modelcontextprotocol.kotlin.sdk.types.SetLevelRequestParams, + ): io.modelcontextprotocol.kotlin.sdk.types.SetLevelRequest = + io.modelcontextprotocol.kotlin.sdk.types.SetLevelRequest(params) } /** @@ -1372,99 +1866,60 @@ public enum class LoggingLevel { * If no logging level request has been sent from the client, * the server MAY decide which messages to send automatically. */ -@Serializable -public data class LoggingMessageNotification(override val params: Params) : ServerNotification { - override val method: Method = Method.Defined.NotificationsMessage - - @Serializable - public data class Params( - /** - * The severity of this log message. - */ - val level: LoggingLevel, - /** - * An optional name of the logger issuing this message. - */ - val logger: String? = null, - /** - * The data to be logged, such as a string message or an object. Any JSON serializable type is allowed here. - */ - val data: JsonElement, - override val _meta: JsonObject = EmptyJsonObject, - ) : NotificationParams - - /** - * A request from the client to the server to enable or adjust logging. - */ - @Serializable - public data class SetLevelRequest( - /** - * The level of logging that the client wants to receive from the server. The server should send all logs at this level and higher (i.e., more severe) to the client as notifications/logging/message. - */ - val level: LoggingLevel, - override val _meta: JsonObject = EmptyJsonObject, - ) : ClientRequest, - WithMeta { - override val method: Method = Method.Defined.LoggingSetLevel - } -} +@Deprecated( + message = "Use `LoggingMessageNotification` instead", + replaceWith = ReplaceWith( + "LoggingMessageNotification", + "io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotification", + ), + level = DeprecationLevel.WARNING, +) +public fun LoggingMessageNotification( + params: io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotificationParams, +): io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotification = + io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotification(params) /* Sampling */ /** * Hints to use for model selection. */ -@Serializable -public data class ModelHint( - /** - * A hint for a model name. - */ - val name: String?, +@Deprecated( + message = "Use `ModelHint` instead", + replaceWith = ReplaceWith("ModelHint", "io.modelcontextprotocol.kotlin.sdk.types.ModelHint"), + level = DeprecationLevel.WARNING, ) +public typealias ModelHint = io.modelcontextprotocol.kotlin.sdk.types.ModelHint /** * The server's preferences for model selection, requested by the client during sampling. */ -@Suppress("CanBeParameter") -@Serializable -public class ModelPreferences( - /** - * Optional hints to use for model selection. - */ - public val hints: List?, - /** - * How much to prioritize cost when selecting a model. - */ - public val costPriority: Double?, - /** - * How much to prioritize sampling speed (latency) when selecting a model. - */ - public val speedPriority: Double?, - /** - * How much to prioritize intelligence and capabilities when selecting a model. - */ - public val intelligencePriority: Double?, -) { - init { - require(costPriority == null || costPriority in 0.0..1.0) { - "costPriority must be in 0.0 <= x <= 1.0 value range" - } - - require(speedPriority == null || speedPriority in 0.0..1.0) { - "costPriority must be in 0.0 <= x <= 1.0 value range" - } - - require(intelligencePriority == null || intelligencePriority in 0.0..1.0) { - "intelligencePriority must be in 0.0 <= x <= 1.0 value range" - } - } -} +@Deprecated( + message = "Use `ModelPreferences` instead", + replaceWith = ReplaceWith("ModelPreferences", "io.modelcontextprotocol.kotlin.sdk.types.ModelPreferences"), + level = DeprecationLevel.WARNING, +) +public typealias ModelPreferences = io.modelcontextprotocol.kotlin.sdk.types.ModelPreferences /** * Describes a message issued to or received from an LLM API. */ -@Serializable -public data class SamplingMessage(val role: Role, val content: PromptMessageContentMultimodal) +@Deprecated( + message = "Use `SamplingMessage` instead", + replaceWith = ReplaceWith("SamplingMessage", "io.modelcontextprotocol.kotlin.sdk.types.SamplingMessage"), + level = DeprecationLevel.WARNING, +) +public typealias SamplingMessage = io.modelcontextprotocol.kotlin.sdk.types.SamplingMessage + +@Deprecated( + message = "Use `CreateMessageRequest` instead", + replaceWith = ReplaceWith("CreateMessageRequest", "io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest"), + level = DeprecationLevel.WARNING, +) +public object CreateMessageRequest { + public val IncludeContext: io.modelcontextprotocol.kotlin.sdk.types.IncludeContext.Companion = + io.modelcontextprotocol.kotlin.sdk.types.IncludeContext +} /** * A request from the server to sample an LLM via the client. @@ -1472,282 +1927,317 @@ public data class SamplingMessage(val role: Role, val content: PromptMessageCont * The client should also inform the user before beginning sampling to allow them to inspect the request * (human in the loop) and decide whether to approve it. */ -@Serializable -public data class CreateMessageRequest( - val messages: List, - /** - * An optional system prompt the server wants to use it for sampling. The client MAY modify or omit this prompt. - */ - val systemPrompt: String?, - /** - * A request to include context from one or more MCP servers (including the caller), to be attached to the prompt. The client MAY ignore this request. - */ - val includeContext: IncludeContext?, - val temperature: Double?, - /** - * The maximum number of tokens to sample, as requested by the server. The client MAY choose to sample fewer tokens than requested. - */ - val maxTokens: Int, - val stopSequences: List?, - /** - * Optional metadata to pass through to the LLM provider. The format of this metadata is provider-specific. - */ - val metadata: JsonObject = EmptyJsonObject, - /** - * The server's preferences for which model to select. - */ - val modelPreferences: ModelPreferences?, - override val _meta: JsonObject = EmptyJsonObject, -) : ServerRequest, - WithMeta { - override val method: Method = Method.Defined.SamplingCreateMessage +@Deprecated( + message = "Use `CreateMessageRequest` instead", + replaceWith = ReplaceWith("CreateMessageRequest", "io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest"), + level = DeprecationLevel.WARNING, +) +public fun CreateMessageRequest( + params: io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequestParams, +): io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest = + io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest(params) - @Serializable - public enum class IncludeContext { none, thisServer, allServers } -} - -@Serializable(with = StopReasonSerializer::class) -public sealed interface StopReason { - public val value: String - - @Serializable - public data object EndTurn : StopReason { - override val value: String = "endTurn" - } - - @Serializable - public data object StopSequence : StopReason { - override val value: String = "stopSequence" - } - - @Serializable - public data object MaxTokens : StopReason { - override val value: String = "maxTokens" - } - - @Serializable - @JvmInline - public value class Other(override val value: String) : StopReason -} +@Deprecated( + message = "Use `StopReason` instead", + replaceWith = ReplaceWith("StopReason", "io.modelcontextprotocol.kotlin.sdk.types.StopReason"), + level = DeprecationLevel.WARNING, +) +public typealias StopReason = io.modelcontextprotocol.kotlin.sdk.types.StopReason /** * The client's response to a sampling/create_message request from the server. * The client should inform the user before returning the sampled message to allow them to inspect the response * (human in the loop) and decide whether to allow the server to see it. */ -@Serializable -public data class CreateMessageResult( - /** - * The name of the model that generated the message. - */ - val model: String, - /** - * The reason why sampling stopped. - */ - val stopReason: StopReason? = null, - val role: Role, - val content: PromptMessageContentMultimodal, - override val _meta: JsonObject = EmptyJsonObject, -) : ClientResult +@Deprecated( + message = "Use `CreateMessageResult` instead", + replaceWith = ReplaceWith("CreateMessageResult", "io.modelcontextprotocol.kotlin.sdk.types.CreateMessageResult"), + level = DeprecationLevel.WARNING, +) +public typealias CreateMessageResult = io.modelcontextprotocol.kotlin.sdk.types.CreateMessageResult /* Autocomplete */ -@Serializable(with = ReferencePolymorphicSerializer::class) -public sealed interface Reference { - public val type: String -} +@Deprecated( + message = "Use `Reference` instead", + replaceWith = ReplaceWith("Reference", "io.modelcontextprotocol.kotlin.sdk.types.Reference"), + level = DeprecationLevel.WARNING, +) +public typealias Reference = io.modelcontextprotocol.kotlin.sdk.types.Reference /** * A reference to a resource or resource template definition. */ -@Serializable -public data class ResourceTemplateReference( - /** - * The URI or URI template of the resource. - */ - val uri: String, -) : Reference { - override val type: String = TYPE +@Deprecated( + message = "Use `ResourceTemplateReference` instead", + replaceWith = ReplaceWith( + "ResourceTemplateReference", + "io.modelcontextprotocol.kotlin.sdk.types.ResourceTemplateReference", + ), + level = DeprecationLevel.WARNING, +) +public typealias ResourceTemplateReference = io.modelcontextprotocol.kotlin.sdk.types.ResourceTemplateReference - public companion object { - public const val TYPE: String = "ref/resource" - } -} +/** + * Identifies a prompt. + */ +@Deprecated( + message = "Use `PromptReference` instead", + replaceWith = ReplaceWith("PromptReference", "io.modelcontextprotocol.kotlin.sdk.types.PromptReference"), + level = DeprecationLevel.WARNING, +) +public typealias PromptReference = io.modelcontextprotocol.kotlin.sdk.types.PromptReference /** * Identifies a prompt. */ @Serializable -public data class PromptReference( - /** - * The name of the prompt or prompt template - */ - val name: String, -) : Reference { - override val type: String = TYPE +@Deprecated( + message = "This class will be removed.", + level = DeprecationLevel.WARNING, +) +public data class UnknownReference(val type: String) - public companion object { - public const val TYPE: String = "ref/prompt" - } +@Deprecated( + message = "Use `CompleteRequest` instead", + replaceWith = ReplaceWith("CompleteRequest", "io.modelcontextprotocol.kotlin.sdk.types.CompleteRequest"), + level = DeprecationLevel.WARNING, +) +public object CompleteRequest { + @Deprecated( + message = "Use `CompleteRequestParams.Argument` instead", + replaceWith = ReplaceWith( + "CompleteRequestParams.Argument", + "io.modelcontextprotocol.kotlin.sdk.types.CompleteRequestParams.Argument", + ), + level = DeprecationLevel.WARNING, + ) + public fun Argument( + name: String, + value: String, + ): io.modelcontextprotocol.kotlin.sdk.types.CompleteRequestParams.Argument = + io.modelcontextprotocol.kotlin.sdk.types.CompleteRequestParams.Argument(name, value) } -/** - * Identifies a prompt. - */ -@Serializable -public data class UnknownReference(override val type: String) : Reference - /** * A request from the client to the server to ask for completion options. */ -@Serializable -public data class CompleteRequest( - val ref: Reference, - /** - * The argument's information - */ - val argument: Argument, - override val _meta: JsonObject = EmptyJsonObject, -) : ClientRequest, - WithMeta { - override val method: Method = Method.Defined.CompletionComplete +@Deprecated( + message = "Use `CompleteRequest` instead", + replaceWith = ReplaceWith("CompleteRequest", "io.modelcontextprotocol.kotlin.sdk.types.CompleteRequest"), + level = DeprecationLevel.WARNING, +) +public fun CompleteRequest( + params: io.modelcontextprotocol.kotlin.sdk.types.CompleteRequestParams, +): io.modelcontextprotocol.kotlin.sdk.types.CompleteRequest = + io.modelcontextprotocol.kotlin.sdk.types.CompleteRequest(params) - @Serializable - public data class Argument( - /** - * The name of the argument - */ - val name: String, - /** - * The value of the argument to use for completion matching. - */ - val value: String, +@Deprecated( + message = "Use `CompleteResult` instead", + replaceWith = ReplaceWith("CompleteResult", "io.modelcontextprotocol.kotlin.sdk.types.CompleteResult"), + level = DeprecationLevel.WARNING, +) +public object CompleteResult { + @Deprecated( + message = "Use `CompleteResult.Completion` instead", + replaceWith = ReplaceWith( + "CompleteResult.Completion", + "io.modelcontextprotocol.kotlin.sdk.types.CompleteResult.Completion", + ), + level = DeprecationLevel.WARNING, ) + public fun Completion( + values: List, + total: Int? = null, + hasMore: Boolean? = null, + ): io.modelcontextprotocol.kotlin.sdk.types.CompleteResult.Completion = + io.modelcontextprotocol.kotlin.sdk.types.CompleteResult.Completion(values, total, hasMore) } /** * The server's response to a completion/complete request */ -@Serializable -public data class CompleteResult(val completion: Completion, override val _meta: JsonObject = EmptyJsonObject) : - ServerResult { - @Suppress("CanBeParameter") - @Serializable - public class Completion( - /** - * A list of completion values. Must not exceed 100 items. - */ - public val values: List, - /** - * The total number of completion options available. This can exceed the number of values actually sent in the response. - */ - public val total: Int?, - /** - * Indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown. - */ - public val hasMore: Boolean?, - ) { - init { - require(values.size <= 100) { - "'values' field must not exceed 100 items" - } - } - } -} +@Deprecated( + message = "Use `CompleteResult` instead", + replaceWith = ReplaceWith("CompleteResult", "io.modelcontextprotocol.kotlin.sdk.types.CompleteResult"), + level = DeprecationLevel.WARNING, +) +public fun CompleteResult( + completion: io.modelcontextprotocol.kotlin.sdk.types.CompleteResult.Completion, + meta: JsonObject? = null, +): io.modelcontextprotocol.kotlin.sdk.types.CompleteResult = + io.modelcontextprotocol.kotlin.sdk.types.CompleteResult(completion, meta) /* Roots */ /** * Represents a root directory or file that the server can operate on. */ -@Serializable -public data class Root( - /** - * The URI identifying the root. This *must* start with file:// for now. - */ - val uri: String, - - /** - * An optional name for the root. - */ - val name: String?, -) { - init { - require(uri.startsWith("file://")) { - "'uri' param must start with 'file://'" - } - } -} +@Deprecated( + message = "Use `Root` instead", + replaceWith = ReplaceWith("Root", "io.modelcontextprotocol.kotlin.sdk.types.Root"), + level = DeprecationLevel.WARNING, +) +public typealias Root = io.modelcontextprotocol.kotlin.sdk.types.Root /** * Sent from the server to request a list of root URIs from the client. */ -@Serializable -public class ListRootsRequest(override val _meta: JsonObject = EmptyJsonObject) : - ServerRequest, - WithMeta { - override val method: Method = Method.Defined.RootsList -} +@Deprecated( + message = "Use `ListRootsRequest` instead", + replaceWith = ReplaceWith("ListRootsRequest", "io.modelcontextprotocol.kotlin.sdk.types.ListRootsRequest"), + level = DeprecationLevel.WARNING, +) +public typealias ListRootsRequest = io.modelcontextprotocol.kotlin.sdk.types.ListRootsRequest /** * The client's response to a roots/list request from the server. */ -@Serializable -public class ListRootsResult(public val roots: List, override val _meta: JsonObject = EmptyJsonObject) : - ClientResult +@Deprecated( + message = "Use `ListRootsResult` instead", + replaceWith = ReplaceWith("ListRootsResult", "io.modelcontextprotocol.kotlin.sdk.types.ListRootsResult"), + level = DeprecationLevel.WARNING, +) +public typealias ListRootsResult = io.modelcontextprotocol.kotlin.sdk.types.ListRootsResult + +@Deprecated( + message = "Use `RootsListChangedNotification` instead", + replaceWith = ReplaceWith( + "RootsListChangedNotification", + "io.modelcontextprotocol.kotlin.sdk.types.RootsListChangedNotification", + ), + level = DeprecationLevel.WARNING, +) +public object RootsListChangedNotification { + @Deprecated( + message = "Use `BaseNotificationParams` instead", + replaceWith = ReplaceWith( + "BaseNotificationParams", + "io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams", + ), + level = DeprecationLevel.WARNING, + ) + public fun Params(meta: JsonObject? = null): io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams = + io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams(meta) +} /** * A notification from the client to the server, informing it that the list of roots has changed. */ -@Serializable -public data class RootsListChangedNotification(override val params: Params = Params()) : ClientNotification { - override val method: Method = Method.Defined.NotificationsRootsListChanged +@Deprecated( + message = "Use `RootsListChangedNotification` instead", + replaceWith = ReplaceWith( + "RootsListChangedNotification", + "io.modelcontextprotocol.kotlin.sdk.types.RootsListChangedNotification", + ), + level = DeprecationLevel.WARNING, +) +public fun RootsListChangedNotification( + params: io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams? = null, +): io.modelcontextprotocol.kotlin.sdk.types.RootsListChangedNotification = + io.modelcontextprotocol.kotlin.sdk.types.RootsListChangedNotification(params) - @Serializable - public data class Params(override val _meta: JsonObject = EmptyJsonObject) : NotificationParams +@Deprecated( + message = "Use `ElicitRequest` instead", + replaceWith = ReplaceWith( + "ElicitRequest", + "io.modelcontextprotocol.kotlin.sdk.types.ElicitRequest", + ), + level = DeprecationLevel.WARNING, +) +public object CreateElicitationRequest { + public fun RequestedSchema( + properties: JsonObject, + required: List? = null, + ): io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestParams.RequestedSchema = + io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestParams.RequestedSchema(properties, required) } /** * Sent from the server to create an elicitation from the client. */ -@Serializable -public data class CreateElicitationRequest( - public val message: String, - public val requestedSchema: RequestedSchema, - override val _meta: JsonObject = EmptyJsonObject, -) : ServerRequest, - WithMeta { - override val method: Method = Method.Defined.ElicitationCreate +@Deprecated( + message = "Use `ElicitRequest` instead", + replaceWith = ReplaceWith( + "ElicitRequest", + "io.modelcontextprotocol.kotlin.sdk.types.ElicitRequest", + ), + level = DeprecationLevel.WARNING, +) +public fun CreateElicitationRequest( + params: io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestParams, +): io.modelcontextprotocol.kotlin.sdk.types.ElicitRequest = + io.modelcontextprotocol.kotlin.sdk.types.ElicitRequest(params) - @Serializable - public data class RequestedSchema( - val properties: JsonObject = EmptyJsonObject, - val required: List? = null, - ) { - @OptIn(ExperimentalSerializationApi::class) - @EncodeDefault - val type: String = "object" +@Deprecated( + message = "Use `ElicitResult` instead", + replaceWith = ReplaceWith( + "ElicitResult", + "io.modelcontextprotocol.kotlin.sdk.types.ElicitResult", + ), + level = DeprecationLevel.WARNING, +) +public object CreateElicitationResult { + @Deprecated( + message = "Use `ElicitResult.Action` instead", + replaceWith = ReplaceWith( + "ElicitResult.Action", + "io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action", + ), + level = DeprecationLevel.WARNING, + ) + public object Action { + @Deprecated( + message = "Use `ElicitResult.Action.Accept` instead", + replaceWith = ReplaceWith( + "ElicitResult.Action.Accept", + "io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action", + ), + level = DeprecationLevel.WARNING, + ) + public val accept: io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action = + io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action.Accept + + @Deprecated( + message = "Use `ElicitResult.Action.Decline` instead", + replaceWith = ReplaceWith( + "ElicitResult.Action.Decline", + "io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action", + ), + level = DeprecationLevel.WARNING, + ) + public val decline: io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action = + io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action.Decline + + @Deprecated( + message = "Use `ElicitResult.Action.Cancel` instead", + replaceWith = ReplaceWith( + "ElicitResult.Action.Cancel", + "io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action", + ), + level = DeprecationLevel.WARNING, + ) + public val cancel: io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action = + io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action.Cancel } } /** * The client's response to an elicitation/create request from the server. */ -@Serializable -public data class CreateElicitationResult( - public val action: Action, - public val content: JsonObject? = null, - override val _meta: JsonObject = EmptyJsonObject, -) : ClientResult { - - init { - require(action == Action.accept || content == null) { - "Content can only be provided for an 'accept' action" - } - } - - @Serializable - public enum class Action { accept, decline, cancel } -} +@Deprecated( + message = "Use `ElicitResult` instead", + replaceWith = ReplaceWith( + "ElicitResult", + "io.modelcontextprotocol.kotlin.sdk.types.ElicitResult", + ), + level = DeprecationLevel.WARNING, +) +public fun CreateElicitationResult( + action: io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action, + content: JsonObject? = null, + meta: JsonObject? = null, +): io.modelcontextprotocol.kotlin.sdk.types.ElicitResult = + io.modelcontextprotocol.kotlin.sdk.types.ElicitResult(action, content, meta) /** * Represents an error specific to the MCP protocol. @@ -1756,7 +2246,9 @@ public data class CreateElicitationResult( * @property message The error message. * @property data Additional error data as a JSON object. */ -public class McpError(public val code: Int, message: String, public val data: JsonObject = EmptyJsonObject) : - Exception() { - override val message: String = "MCP error $code: $message" -} +@Deprecated( + message = "Use `McpException` instead", + replaceWith = ReplaceWith("McpException"), + level = DeprecationLevel.WARNING, +) +public typealias McpError = io.modelcontextprotocol.kotlin.sdk.types.McpException diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.util.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.util.kt index 07a0bf4..305efe7 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.util.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.util.kt @@ -1,315 +1,40 @@ package io.modelcontextprotocol.kotlin.sdk -import io.github.oshai.kotlinlogging.KotlinLogging -import io.modelcontextprotocol.kotlin.sdk.LoggingMessageNotification.SetLevelRequest -import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.KSerializer -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.descriptors.buildClassSerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import kotlinx.serialization.json.JsonContentPolymorphicSerializer -import kotlinx.serialization.json.JsonDecoder -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonEncoder +import io.modelcontextprotocol.kotlin.sdk.types.error +import io.modelcontextprotocol.kotlin.sdk.types.success import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive -import kotlinx.serialization.json.long -import kotlinx.serialization.json.longOrNull -private val logger = KotlinLogging.logger {} - -internal object ErrorCodeSerializer : KSerializer { - override val descriptor: SerialDescriptor = - PrimitiveSerialDescriptor("io.modelcontextprotocol.kotlin.sdk.ErrorCode", PrimitiveKind.INT) - - override fun serialize(encoder: Encoder, value: ErrorCode) { - encoder.encodeInt(value.code) - } - - override fun deserialize(decoder: Decoder): ErrorCode { - val decodedInt = decoder.decodeInt() - return ErrorCode.Defined.entries.firstOrNull { it.code == decodedInt } - ?: ErrorCode.Unknown(decodedInt) - } -} - -internal object RequestMethodSerializer : KSerializer { - override val descriptor: SerialDescriptor = - PrimitiveSerialDescriptor("io.modelcontextprotocol.kotlin.sdk.Method", PrimitiveKind.STRING) - - override fun serialize(encoder: Encoder, value: Method) { - encoder.encodeString(value.value) - } - - override fun deserialize(decoder: Decoder): Method { - val decodedString = decoder.decodeString() - return Method.Defined.entries.firstOrNull { it.value == decodedString } - ?: Method.Custom(decodedString) - } -} - -internal object StopReasonSerializer : KSerializer { - override val descriptor: SerialDescriptor = - PrimitiveSerialDescriptor("io.modelcontextprotocol.kotlin.sdk.StopReason", PrimitiveKind.STRING) - - override fun serialize(encoder: Encoder, value: StopReason) { - encoder.encodeString(value.value) - } - - override fun deserialize(decoder: Decoder): StopReason { - val decodedString = decoder.decodeString() - return when (decodedString) { - StopReason.StopSequence.value -> StopReason.StopSequence - StopReason.MaxTokens.value -> StopReason.MaxTokens - StopReason.EndTurn.value -> StopReason.EndTurn - else -> StopReason.Other(decodedString) - } - } -} - -internal object ReferencePolymorphicSerializer : JsonContentPolymorphicSerializer(Reference::class) { - override fun selectDeserializer(element: JsonElement): DeserializationStrategy = - when (element.jsonObject.getValue("type").jsonPrimitive.content) { - ResourceTemplateReference.TYPE -> ResourceTemplateReference.serializer() - PromptReference.TYPE -> PromptReference.serializer() - else -> UnknownReference.serializer() - } -} - -internal object PromptMessageContentPolymorphicSerializer : - JsonContentPolymorphicSerializer(PromptMessageContent::class) { - override fun selectDeserializer(element: JsonElement): DeserializationStrategy = - when (element.jsonObject.getValue("type").jsonPrimitive.content) { - ImageContent.TYPE -> ImageContent.serializer() - TextContent.TYPE -> TextContent.serializer() - EmbeddedResource.TYPE -> EmbeddedResource.serializer() - AudioContent.TYPE -> AudioContent.serializer() - else -> UnknownContent.serializer() - } -} - -internal object PromptMessageContentMultimodalPolymorphicSerializer : - JsonContentPolymorphicSerializer(PromptMessageContentMultimodal::class) { - override fun selectDeserializer(element: JsonElement): DeserializationStrategy = - when (element.jsonObject.getValue("type").jsonPrimitive.content) { - ImageContent.TYPE -> ImageContent.serializer() - TextContent.TYPE -> TextContent.serializer() - AudioContent.TYPE -> AudioContent.serializer() - else -> UnknownContent.serializer() - } -} - -internal object ResourceContentsPolymorphicSerializer : - JsonContentPolymorphicSerializer(ResourceContents::class) { - override fun selectDeserializer(element: JsonElement): DeserializationStrategy { - val jsonObject = element.jsonObject - return when { - jsonObject.contains("text") -> TextResourceContents.serializer() - jsonObject.contains("blob") -> BlobResourceContents.serializer() - else -> UnknownResourceContents.serializer() - } - } -} - -internal fun selectRequestDeserializer(method: String): DeserializationStrategy { - selectClientRequestDeserializer(method)?.let { return it } - selectServerRequestDeserializer(method)?.let { return it } - return CustomRequest.serializer() -} - -internal fun selectClientRequestDeserializer(method: String): DeserializationStrategy? = when (method) { - Method.Defined.Ping.value -> PingRequest.serializer() - Method.Defined.Initialize.value -> InitializeRequest.serializer() - Method.Defined.CompletionComplete.value -> CompleteRequest.serializer() - Method.Defined.LoggingSetLevel.value -> SetLevelRequest.serializer() - Method.Defined.PromptsGet.value -> GetPromptRequest.serializer() - Method.Defined.PromptsList.value -> ListPromptsRequest.serializer() - Method.Defined.ResourcesList.value -> ListResourcesRequest.serializer() - Method.Defined.ResourcesTemplatesList.value -> ListResourceTemplatesRequest.serializer() - Method.Defined.ResourcesRead.value -> ReadResourceRequest.serializer() - Method.Defined.ResourcesSubscribe.value -> SubscribeRequest.serializer() - Method.Defined.ResourcesUnsubscribe.value -> UnsubscribeRequest.serializer() - Method.Defined.ToolsCall.value -> CallToolRequest.serializer() - Method.Defined.ToolsList.value -> ListToolsRequest.serializer() - else -> null -} - -private fun selectClientNotificationDeserializer(element: JsonElement): DeserializationStrategy? = - when (element.jsonObject.getValue("method").jsonPrimitive.content) { - Method.Defined.NotificationsCancelled.value -> CancelledNotification.serializer() - Method.Defined.NotificationsProgress.value -> ProgressNotification.serializer() - Method.Defined.NotificationsInitialized.value -> InitializedNotification.serializer() - Method.Defined.NotificationsRootsListChanged.value -> RootsListChangedNotification.serializer() - else -> null - } - -internal object ClientNotificationPolymorphicSerializer : - JsonContentPolymorphicSerializer(ClientNotification::class) { - override fun selectDeserializer(element: JsonElement): DeserializationStrategy = - selectClientNotificationDeserializer(element) - ?: UnknownMethodRequestOrNotification.serializer() -} - -internal fun selectServerRequestDeserializer(method: String): DeserializationStrategy? = when (method) { - Method.Defined.Ping.value -> PingRequest.serializer() - Method.Defined.SamplingCreateMessage.value -> CreateMessageRequest.serializer() - Method.Defined.RootsList.value -> ListRootsRequest.serializer() - else -> null -} - -internal fun selectServerNotificationDeserializer(element: JsonElement): DeserializationStrategy? = - when (element.jsonObject.getValue("method").jsonPrimitive.content) { - Method.Defined.NotificationsCancelled.value -> CancelledNotification.serializer() - Method.Defined.NotificationsProgress.value -> ProgressNotification.serializer() - Method.Defined.NotificationsMessage.value -> LoggingMessageNotification.serializer() - Method.Defined.NotificationsResourcesUpdated.value -> ResourceUpdatedNotification.serializer() - Method.Defined.NotificationsResourcesListChanged.value -> ResourceListChangedNotification.serializer() - Method.Defined.NotificationsToolsListChanged.value -> ToolListChangedNotification.serializer() - Method.Defined.NotificationsPromptsListChanged.value -> PromptListChangedNotification.serializer() - else -> null - } - -internal object ServerNotificationPolymorphicSerializer : - JsonContentPolymorphicSerializer(ServerNotification::class) { - override fun selectDeserializer(element: JsonElement): DeserializationStrategy = - selectServerNotificationDeserializer(element) - ?: UnknownMethodRequestOrNotification.serializer() -} - -internal object NotificationPolymorphicSerializer : - JsonContentPolymorphicSerializer(Notification::class) { - override fun selectDeserializer(element: JsonElement): DeserializationStrategy = - selectClientNotificationDeserializer(element) - ?: selectServerNotificationDeserializer(element) - ?: UnknownMethodRequestOrNotification.serializer() -} - -internal object RequestPolymorphicSerializer : - JsonContentPolymorphicSerializer(Request::class) { - override fun selectDeserializer(element: JsonElement): DeserializationStrategy { - val method = element.jsonObject.getOrElse("method") { null }?.jsonPrimitive?.content ?: run { - logger.error { "No method in $element" } - error("No method in $element") - } - - return selectClientRequestDeserializer(method) - ?: selectServerRequestDeserializer(method) - ?: UnknownMethodRequestOrNotification.serializer() - } -} - -private fun selectServerResultDeserializer(element: JsonElement): DeserializationStrategy? { - val jsonObject = element.jsonObject - return when { - jsonObject.contains("tools") -> ListToolsResult.serializer() - jsonObject.contains("resources") -> ListResourcesResult.serializer() - jsonObject.contains("resourceTemplates") -> ListResourceTemplatesResult.serializer() - jsonObject.contains("prompts") -> ListPromptsResult.serializer() - jsonObject.contains("capabilities") -> InitializeResult.serializer() - jsonObject.contains("description") -> GetPromptResult.serializer() - jsonObject.contains("completion") -> CompleteResult.serializer() - jsonObject.contains("toolResult") -> CompatibilityCallToolResult.serializer() - jsonObject.contains("contents") -> ReadResourceResult.serializer() - jsonObject.contains("content") -> CallToolResult.serializer() - else -> null - } -} - -private fun selectClientResultDeserializer(element: JsonElement): DeserializationStrategy? { - val jsonObject = element.jsonObject - return when { - jsonObject.contains("model") -> CreateMessageResult.serializer() - jsonObject.contains("roots") -> ListRootsResult.serializer() - jsonObject.contains("action") -> CreateElicitationResult.serializer() - else -> null - } -} - -internal object ServerResultPolymorphicSerializer : - JsonContentPolymorphicSerializer(ServerResult::class) { - override fun selectDeserializer(element: JsonElement): DeserializationStrategy = - selectServerResultDeserializer(element) - ?: EmptyRequestResult.serializer() -} - -internal object ClientResultPolymorphicSerializer : - JsonContentPolymorphicSerializer(ClientResult::class) { - override fun selectDeserializer(element: JsonElement): DeserializationStrategy = - selectClientResultDeserializer(element) - ?: EmptyRequestResult.serializer() -} - -internal object RequestResultPolymorphicSerializer : - JsonContentPolymorphicSerializer(RequestResult::class) { - override fun selectDeserializer(element: JsonElement): DeserializationStrategy = - selectClientResultDeserializer(element) - ?: selectServerResultDeserializer(element) - ?: EmptyRequestResult.serializer() -} - -internal object JSONRPCMessagePolymorphicSerializer : - JsonContentPolymorphicSerializer(JSONRPCMessage::class) { - override fun selectDeserializer(element: JsonElement): DeserializationStrategy { - val jsonObject = element.jsonObject - return when { - jsonObject.contains("message") -> JSONRPCError.serializer() - !jsonObject.contains("method") -> JSONRPCResponse.serializer() - jsonObject.contains("id") -> JSONRPCRequest.serializer() - else -> JSONRPCNotification.serializer() - } - } -} - -public val EmptyJsonObject: JsonObject = JsonObject(emptyMap()) - -public class RequestIdSerializer : KSerializer { - override val descriptor: SerialDescriptor = buildClassSerialDescriptor("RequestId") - - override fun deserialize(decoder: Decoder): RequestId { - val jsonDecoder = decoder as? JsonDecoder ?: error("Can only deserialize JSON") - val element = jsonDecoder.decodeJsonElement() - - return when (element) { - is JsonPrimitive -> when { - element.isString -> RequestId.StringId(element.content) - element.longOrNull != null -> RequestId.NumberId(element.long) - else -> error("Invalid RequestId type") - } - - else -> error("Invalid RequestId format") - } - } - - override fun serialize(encoder: Encoder, value: RequestId) { - val jsonEncoder = encoder as? JsonEncoder ?: error("Can only serialize JSON") - when (value) { - is RequestId.StringId -> jsonEncoder.encodeString(value.value) - is RequestId.NumberId -> jsonEncoder.encodeLong(value.value) - } - } -} +@Deprecated( + message = "Use `io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject` instead", + replaceWith = ReplaceWith("EmptyJsonObject", "io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject"), + level = DeprecationLevel.WARNING, +) +public val EmptyJsonObject: JsonObject = io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject /** * Creates a [CallToolResult] with single [TextContent] and [meta]. */ -public fun CallToolResult.Companion.ok(content: String, meta: JsonObject = EmptyJsonObject): CallToolResult = - CallToolResult( - content = listOf(TextContent(content)), - isError = false, - _meta = meta, - ) +@Deprecated( + message = "Use `CallToolResult.success` instead", + replaceWith = ReplaceWith("CallToolResult.Companion.success"), + level = DeprecationLevel.WARNING, +) +public fun CallToolResult.ok( + content: String, + meta: JsonObject = EmptyJsonObject, +): io.modelcontextprotocol.kotlin.sdk.types.CallToolResult = + io.modelcontextprotocol.kotlin.sdk.types.CallToolResult.success(content, meta) /** * Creates a [CallToolResult] with single [TextContent] and [meta], with `isError` being true. */ -public fun CallToolResult.Companion.error(content: String, meta: JsonObject = EmptyJsonObject): CallToolResult = - CallToolResult( - content = listOf(TextContent(content)), - isError = true, - _meta = meta, - ) +@Deprecated( + message = "Use `CallToolResult.error` instead", + replaceWith = ReplaceWith("CallToolResult.Companion.error"), + level = DeprecationLevel.WARNING, +) +public fun CallToolResult.error( + content: String, + meta: JsonObject = EmptyJsonObject, +): io.modelcontextprotocol.kotlin.sdk.types.CallToolResult = + io.modelcontextprotocol.kotlin.sdk.types.CallToolResult.error(content, meta) diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/McpException.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/McpException.kt new file mode 100644 index 0000000..fb6e1ee --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/McpException.kt @@ -0,0 +1,14 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import kotlinx.serialization.json.JsonElement + +/** + * Represents an error specific to the MCP protocol. + * + * @property code The error code. + * @property message The error message. + * @property data Additional error data as a JSON object. + */ +public class McpException(public val code: Int, message: String, public val data: JsonElement? = null) : Exception() { + override val message: String = "MCP error $code: $message" +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PingRequest.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PingRequest.kt new file mode 100644 index 0000000..575d3db --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PingRequest.kt @@ -0,0 +1,14 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import kotlinx.serialization.EncodeDefault +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.Serializable + +@Serializable +public data class PingRequest(override val params: BaseRequestParams? = null) : + ClientRequest, + ServerRequest { + @OptIn(ExperimentalSerializationApi::class) + @EncodeDefault + override val method: Method = Method.Defined.Ping +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.kt new file mode 100644 index 0000000..cff3b89 --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.kt @@ -0,0 +1,145 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonObject + +/** + * Describes an MCP (Model Context Protocol) implementation. + * + * This metadata helps clients identify and display information about servers or clients, + * including their name, version, and optional branding elements like icons and website links. + * + * @property name The programmatic identifier for this implementation. + * Intended for logical use and API identification. If [title] is not provided, + * this should be used as a fallback display name. + * @property version The version string of this implementation (e.g., "1.0.0", "2.1.3-beta"). + * @property title Optional human-readable display name for this implementation. + * Intended for UI and end-user contexts, optimized to be easily understood + * even by those unfamiliar with domain-specific terminology. + * If not provided, [name] should be used for display purposes. + * @property websiteUrl Optional URL of the website for this implementation. + * Can be used to provide documentation, support, or additional information. + * @property icons Optional set of sized icons that clients can display in their user interface. + * See [Icon] for supported formats and requirements. + */ +@Serializable +public data class Implementation( + val name: String, + val version: String, + val title: String? = null, + val websiteUrl: String? = null, + val icons: List? = null, +) + +/** + * Capabilities that a client may support. + * + * Known capabilities are defined here, but this is not a closed set: any client + * can define its own additional capabilities through the [experimental] field. + * + * The presence of a capability object (non-null value) indicates that the client + * supports that capability. + * + * @property sampling Present if the client supports sampling from an LLM. + * @property roots Present if the client supports listing roots. + * @property elicitation Present if the client supports elicitation from the server. + * @property experimental Experimental, non-standard capabilities that the client supports. + * Keys are capability names, values are capability-specific configuration objects. + */ +@Serializable +public data class ClientCapabilities( + public val sampling: JsonObject? = null, + public val roots: Roots? = null, + public val elicitation: JsonObject? = null, + public val experimental: JsonObject? = null, +) { + + public companion object { + public val sampling: JsonObject = EmptyJsonObject + public val elicitation: JsonObject = EmptyJsonObject + } + + /** + * Indicates that the client supports listing roots. + * + * Roots are the top-level directories or locations that the server can access. + * When present (non-null), the server can query available roots and optionally + * receive notifications when the roots list changes. + * + * @property listChanged Whether the client supports notifications for changes to the roots list. + * If true, the client will send notifications when roots are added or removed. + */ + @Serializable + public data class Roots(val listChanged: Boolean? = null) +} + +/** + * Capabilities that a server may support. + * + * Known capabilities are defined here, but this is not a closed set: any server + * can define its own additional capabilities through the [experimental] field. + * + * The presence of a capability object (non-null value) indicates that the server + * supports that capability. + * + * @property tools Present if the server offers any tools to call. + * @property resources Present if the server offers any resources to read. + * @property prompts Present if the server offers any prompt templates. + * @property logging Present if the server supports sending log messages to the client. + * @property completions Present if the server supports argument autocompletion suggestions. + * Keys are capability names, values are capability-specific configuration objects. + * @property experimental Experimental, non-standard capabilities that the server supports. + */ +@Serializable +public data class ServerCapabilities( + val tools: Tools? = null, + val resources: Resources? = null, + val prompts: Prompts? = null, + val logging: JsonObject? = null, + val completions: JsonObject? = null, + val experimental: JsonObject? = null, +) { + + public companion object { + public val Logging: JsonObject = EmptyJsonObject + public val Completions: JsonObject = EmptyJsonObject + } + + /** + * Indicates that the server offers tools to call. + * + * When present (non-null), clients can list and invoke tools (functions, actions, etc.) + * provided by the server. + * + * @property listChanged Whether this server supports notifications for changes to the tool list. + * If true, the server will send notifications when tools are added, modified, or removed. + */ + @Serializable + public data class Tools(val listChanged: Boolean? = null) + + /** + * Indicates that the server offers resources to read. + * + * When present (non-null), clients can list and read resources (files, data sources, etc.) + * provided by the server. + * + * @property listChanged Whether this server supports notifications for changes to the resource list. + * If true, the server will send notifications when resources are added or removed. + * @property subscribe Whether this server supports subscribing to resource updates. + * If true, clients can subscribe to receive notifications when specific + * resources are modified. + */ + @Serializable + public data class Resources(val listChanged: Boolean? = null, val subscribe: Boolean? = null) + + /** + * Indicates that the server offers prompt templates. + * + * When present (non-null), clients can list and invoke prompts provided by the server. + * + * @property listChanged Whether this server supports notifications for changes to the prompt list. + * If true, the server will send notifications when prompts are added, modified, or removed. + */ + @Serializable + public data class Prompts(val listChanged: Boolean? = null) +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt new file mode 100644 index 0000000..f539c41 --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt @@ -0,0 +1,164 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonObject + +// ============================================================================ +// Protocol Version Constants +// ============================================================================ + +public const val LATEST_PROTOCOL_VERSION: String = "2025-03-26" + +public val SUPPORTED_PROTOCOL_VERSIONS: List = listOf( + LATEST_PROTOCOL_VERSION, + "2024-11-05", +) + +// ============================================================================ +// Base Interfaces +// ============================================================================ + +/** + * Represents an entity that includes additional metadata in its responses. + */ +@Serializable +public sealed interface WithMeta { + @SerialName("_meta") + public val meta: JsonObject? +} + +// ============================================================================ +// Tokens +// ============================================================================ + +/** + * A progress token, used to associate progress notifications with the original request. + */ +public typealias ProgressToken = RequestId + +public fun ProgressToken(value: String): ProgressToken = RequestId(value) + +public fun ProgressToken(value: Long): ProgressToken = RequestId(value) + +// ============================================================================ +// Visual Elements +// ============================================================================ + +/** + * An optionally sized icon that can be displayed in a user interface. + * + * Icons help clients provide visual branding and identification for MCP implementations. + * + * **Security considerations:** + * - Consumers SHOULD ensure URLs serving icons are from the same domain as the client/server + * or a trusted domain to prevent malicious content. + * - Consumers SHOULD take appropriate precautions when rendering SVGs as they can contain + * executable JavaScript. Consider sanitizing SVG content or rendering in isolated contexts. + * + * @property src A standard URI pointing to an icon resource. + * Maybe an HTTP/HTTPS URL or a data: URI with Base64-encoded image data. + * Example: "https://example.com/icon.png" or "data:image/png;base64,iVBORw0KG..." + * @property mimeType Optional MIME type override if the source MIME type is missing or generic. + * For example, "image/png", "image/jpeg", or "image/svg+xml". + * Useful when the URL doesn't include a file extension or uses a generic MIME type. + * @property sizes Optional array of strings that specify sizes at which the icon can be used. + * Each string should be in WxH format (e.g., "48x48", "96x96") or "any" for + * scalable formats like SVG. If not provided, the client should assume that + * the icon can be used at any size. + * @property theme Optional specifier for the theme this icon is designed for. + * [Theme.Light] indicates the icon is designed for a light background, + * [Theme.Dark] indicates the icon is designed for a dark background. + * If not provided, the client should assume the icon can be used with any theme. + */ +@Serializable +public data class Icon( + val src: String, + val mimeType: String? = null, + val sizes: List? = null, + val theme: Theme? = null, +) { + /** + * The theme context for which an icon is designed. + * + * @property Light Icon designed for use with a light background (typically darker icon). + * @property Dark Icon designed for use with a dark background (typically lighter icon). + */ + @Serializable + public enum class Theme { + /** Icon designed for use with a light background */ + @SerialName("light") + Light, + + /** Icon designed for use with a dark background */ + @SerialName("dark") + Dark, + } +} + +// ============================================================================ +// Roles and References +// ============================================================================ + +/** + * The sender or recipient of messages and data in a conversation. + */ +@Serializable +public enum class Role { + @SerialName("user") + User, + + @SerialName("assistant") + Assistant, +} + +/** + * Base interface for reference types in the protocol. + * + * References are used to point to other entities (prompts, resources, etc.) + * without including their full definitions. + */ +@Serializable(with = ReferencePolymorphicSerializer::class) +public sealed interface Reference { + public val type: ReferenceType +} + +@Serializable +public enum class ReferenceType(public val value: String) { + @SerialName("ref/prompt") + Prompt("ref/prompt"), + + @SerialName("ref/resource") + ResourceTemplate("ref/resource"), +} + +// ============================================================================ +// Annotations and Metadata +// ============================================================================ + +/** + * Optional annotations for the client. + * + * The client can use annotations to inform how objects are used or displayed. + * + * @property audience Describes who the intended customer of this object or data is. + * Can include multiple entries to indicate content useful for multiple audiences + * (e.g., [Role.user, Role.assistant]). + * @property priority Describes how important this data is for operating the server. + * A value of 1.0 means "most important" and indicates that the data is effectively required, + * while 0.0 means "least important" and indicates that the data is entirely optional. + * Should be a value between 0.0 and 1.0. + * @property lastModified The moment the resource was last modified, as an ISO 8601 formatted string + * (e.g., "2025-01-12T15:00:58Z"). + * Examples: last activity timestamp in an open file, timestamp when the resource was attached, etc. + */ +@Serializable +public data class Annotations( + val audience: List? = null, + val priority: Double? = null, + val lastModified: String? = null, +) { + init { + require(priority == null || priority in 0.0..1.0) { "Priority must be between 0.0 and 1.0" } + } +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.kt new file mode 100644 index 0000000..e0c4295 --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.kt @@ -0,0 +1,93 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import kotlinx.serialization.EncodeDefault +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonObject + +/** + * A request from the client to the server to ask for completion options. + * + * @property params The request parameters containing the argument to complete and its context. + */ +@Serializable +public data class CompleteRequest(override val params: CompleteRequestParams) : ClientRequest { + @OptIn(ExperimentalSerializationApi::class) + @EncodeDefault + public override val method: Method = Method.Defined.CompletionComplete +} + +/** + * Parameters for the completion request. + * + * @property argument The argument's information for which completion options are requested. + * @property ref A reference to either a prompt or resource template to complete within. + * @property context Additional, optional context for generating completions. + */ +@Serializable +public data class CompleteRequestParams( + val argument: Argument, + val ref: Reference, + val context: Context? = null, + @SerialName("_meta") + override val meta: RequestMeta? = null, +) : RequestParams { + /** + * The argument for which completion is being requested. + * + * @property name The name of the argument being completed. + * @property value The partial value of the argument to use for completion matching. + * This is typically the text the user has typed so far. + */ + @Serializable + public data class Argument(val name: String, val value: String) + + /** + * Additional context to help generate more relevant completions. + * + * @property arguments Previously-resolved variables in a URI template or prompt. + * These can be used to provide context-aware completions. + * For example, if completing a file path, this might contain the repository or directory context. + */ + @Serializable + public data class Context(val arguments: Map? = null) +} + +/** + * The server's response to a [CompleteRequest]. + * + * Provides completion options for prompt or resource template arguments, + * along with pagination information if there are many possible completions. + * + * @property completion The completion options and metadata about available results. + * @property meta Optional metadata for this response. See MCP specification for details on _meta usage. + */ +@Serializable +public data class CompleteResult( + public val completion: Completion, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : ServerResult { + + /** + * Completion options and pagination information. + * + * @property values An array of completion values. Must not exceed 100 items. + * Each value represents a possible completion for the requested argument. + * @property total The total number of completion options available. + * This can exceed the number of values actually sent in the response, + * indicating that pagination or filtering may be needed. + * @property hasMore Indicates whether there are additional completion options beyond + * those provided in the current response, even if the exact total is unknown. + * Use this when the complete set of completions is too large to calculate upfront. + */ + @Serializable + public data class Completion(val values: List, val total: Int? = null, val hasMore: Boolean? = null) { + init { + require(values.size <= 100) { + "Completion 'values' must not exceed 100 items, got ${values.size}" + } + } + } +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/content.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/content.kt new file mode 100644 index 0000000..0e6377f --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/content.kt @@ -0,0 +1,163 @@ +@file:OptIn(ExperimentalSerializationApi::class) + +package io.modelcontextprotocol.kotlin.sdk.types + +import kotlinx.serialization.EncodeDefault +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonObject + +@Serializable +public enum class ContentTypes(public val value: String) { + @SerialName("text") + TEXT("text"), + + @SerialName("image") + IMAGE("image"), + + @SerialName("audio") + AUDIO("audio"), + + @SerialName("resource_link") + RESOURCE_LINK("resource_link"), + + @SerialName("resource") + EMBEDDED_RESOURCE("resource"), +} + +@Serializable(with = ContentBlockPolymorphicSerializer::class) +public sealed interface ContentBlock : WithMeta { + public val type: ContentTypes +} + +@Serializable(with = MediaContentPolymorphicSerializer::class) +public sealed interface MediaContent : ContentBlock + +/** + * Text provided to or from an LLM. + * + * @property text The text content of the message. + * @property annotations Optional annotations for the client. + * @property meta property/parameter is reserved by MCP to allow clients and servers + * to attach additional metadata to their interactions. + */ +@Serializable +public data class TextContent( + val text: String, + val annotations: Annotations? = null, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : MediaContent { + @EncodeDefault + public override val type: ContentTypes = ContentTypes.TEXT +} + +/** + * An image provided to or from an LLM. + * + * @property data The base64-encoded image data. + * @property mimeType The MIME type of the image. Different providers may support different image types. + * @property annotations Optional annotations for the client. + * @property meta property/parameter is reserved by MCP to allow clients and servers + * to attach additional metadata to their interactions. + */ +@Serializable +public data class ImageContent( + val data: String, + val mimeType: String, + val annotations: Annotations? = null, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : MediaContent { + @EncodeDefault + public override val type: ContentTypes = ContentTypes.IMAGE +} + +/** + * Audio provided to or from an LLM. + * + * @property data The base64-encoded audio data. + * @property mimeType The MIME type of the audio. Different providers may support different audio types. + * @property annotations Optional annotations for the client. + * @property meta property/parameter is reserved by MCP to allow clients and servers + * to attach additional metadata to their interactions. + */ +@Serializable +public data class AudioContent( + val data: String, + val mimeType: String, + val annotations: Annotations? = null, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : MediaContent { + @EncodeDefault + public override val type: ContentTypes = ContentTypes.AUDIO +} + +/** + * A resource that the server is capable of reading, included in a prompt or tool call result. + * + * Note: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests. + * + * @property name Intended for programmatic or logical use + * but used as a display name in past specs or fallback (if the title isn’t present). + * @property uri The URI of this resource. + * @property title Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + * even by those unfamiliar with domain-specific terminology. + * + * If not provided, the name should be used for display + * (except for Tool, where `annotations.title` should be given precedence over using `name`, if present). + * @property size The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), + * if known. + * + * Hosts can use this to display file sizes and estimate context window usage. + * @property mimeType The MIME type of this resource, if known. + * @property icons Optional set of sized icons that clients can display in their user interface. + * See [Icon] for supported formats and requirements. + * @property description A description of what this resource represents. + * + * Clients can use this to improve the LLM’s understanding of available resources. + * It can be thought of as a "hint" to the model. + * @property annotations Optional annotations for the client. + * @property meta property/parameter is reserved by MCP to allow clients and servers + * to attach additional metadata to their interactions. + */ +@Serializable +public data class ResourceLink( + val name: String, + val uri: String, + val title: String? = null, + val size: Long? = null, + val mimeType: String? = null, + val icons: List? = null, + val description: String? = null, + val annotations: Annotations? = null, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : ContentBlock, + ResourceLike { + @EncodeDefault + public override val type: ContentTypes = ContentTypes.RESOURCE_LINK +} + +/** + * The contents of a resource, embedded into a prompt or tool call result. + * + * It is up to the client how best to render embedded resources for the benefit of the LLM and/or the user. + * + * @property resource The resource contents. + * @property annotations Optional annotations for the client. + * @property meta property/parameter is reserved by MCP to allow clients and servers + * to attach additional metadata to their interactions. + */ +@Serializable +public data class EmbeddedResource( + val resource: ResourceContents, + val annotations: Annotations? = null, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : ContentBlock { + @EncodeDefault + public override val type: ContentTypes = ContentTypes.EMBEDDED_RESOURCE +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.kt new file mode 100644 index 0000000..75faa42 --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.kt @@ -0,0 +1,111 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import kotlinx.serialization.EncodeDefault +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonObject + +/** + * A request from the server to elicit additional information from the user via the client. + * + * This request type allows servers to prompt users for structured input through forms + * or dialogs presented by the client. The server defines a schema for the requested data, + * and the client presents an appropriate UI to collect this information. + * + * @property params The elicitation parameters including the message and requested schema. + */ +@Serializable +public data class ElicitRequest(override val params: ElicitRequestParams) : ServerRequest { + @OptIn(ExperimentalSerializationApi::class) + @EncodeDefault + public override val method: Method = Method.Defined.ElicitationCreate +} + +/** + * Parameters for an elicitation/create request. + * + * @property message The message to present to the user. This should clearly explain + * what information is being requested and why. + * @property requestedSchema A restricted subset of JSON Schema defining the structure + * of the requested data. Only top-level properties are allowed, + * without nesting. + * @property meta Optional metadata for this request. May include a progressToken for + * out-of-band progress notifications. + */ +@Serializable +public data class ElicitRequestParams( + val message: String, + val requestedSchema: RequestedSchema, + @SerialName("_meta") + override val meta: RequestMeta? = null, +) : RequestParams { + + /** + * A restricted JSON Schema for elicitation requests. + * + * Only supports top-level primitive properties without nesting. Each property + * represents a field in the form or dialog presented to the user. + * + * @property properties A map of property names to their schema definitions. + * Each property must be a primitive type (string, number, boolean). + * @property required Optional list of property names that must be provided by the user. + * If omitted, all fields are considered optional. + * @property type Always "object" for elicitation schemas. + */ + @Serializable + public data class RequestedSchema(val properties: JsonObject, val required: List? = null) { + @OptIn(ExperimentalSerializationApi::class) + @EncodeDefault + val type: String = "object" + } +} + +/** + * The client's response to an [ElicitRequest]. + * + * Represents the user's action and, if accepted, the submitted form data. + * + * @property action The user action in response to the elicitation prompt. + * @property content The submitted form data, only present when [action] is [Action.Accept]. + * Contains values matching the requested schema, where keys correspond + * to property names and values are primitives (string, number, or boolean). + * @property meta Optional metadata for this response. + * @throws IllegalArgumentException if content is provided with a non-accept action. + */ +@Serializable +public data class ElicitResult( + val action: Action, + val content: JsonObject? = null, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : ClientResult { + + init { + require(action == Action.Accept || content == null) { + "Content can only be provided when action is 'accept', got action=$action with content" + } + } + + /** + * The user's response action to an elicitation request. + * + * @property Accept User submitted the form/confirmed the action. Content will be provided. + * @property Decline User explicitly declined the action. No content provided. + * @property Cancel User dismissed the dialog without making an explicit choice. No content provided. + */ + @Serializable + public enum class Action { + /** User submitted the form/confirmed the action */ + @SerialName("accept") + Accept, + + /** User explicitly declined the action */ + @SerialName("decline") + Decline, + + /** User dismissed without making an explicit choice */ + @SerialName("cancel") + Cancel, + } +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/initialize.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/initialize.kt new file mode 100644 index 0000000..6a2fd5b --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/initialize.kt @@ -0,0 +1,74 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import kotlinx.serialization.EncodeDefault +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonObject + +/** + * This request is sent from the client to the server when it first connects, + * asking it to begin initialization. + * + * The initialize request is the first message exchanged in the MCP protocol handshake. + * It allows the client to communicate its capabilities and protocol version to the server, + * and receive the server's capabilities in response. + * + * @property params The initialization parameters including protocol version and client capabilities. + */ +@Serializable +public data class InitializeRequest(override val params: InitializeRequestParams) : ClientRequest { + @OptIn(ExperimentalSerializationApi::class) + @EncodeDefault + override val method: Method = Method.Defined.Initialize +} + +/** + * Parameters for an initialize request. + * + * @property protocolVersion The latest version of the Model Context Protocol that the client supports. + * The client MAY decide to support older versions as well. + * The server will respond with its preferred version, which may differ. + * @property capabilities The capabilities that this client supports. + * Describes which optional features the client has implemented. + * @property clientInfo Information about the client implementation, including name, version, and branding. + * @property meta Optional metadata for this request. May include a progressToken for + * out-of-band progress notifications. + */ +@Serializable +public data class InitializeRequestParams( + val protocolVersion: String, + val capabilities: ClientCapabilities, + val clientInfo: Implementation, + @SerialName("_meta") + override val meta: RequestMeta? = null, +) : RequestParams + +/** + * After receiving an [InitializeRequest] from the client, the server sends this response. + * + * The server communicates its chosen protocol version, capabilities, and optional + * instructions for how to effectively use its features. + * + * @property protocolVersion The version of the Model Context Protocol that the server wants to use. + * This may not match the version that the client requested. + * If the client cannot support this version, it MUST disconnect. + * @property capabilities The capabilities that this server supports. + * Describes which optional features the server has implemented. + * @property serverInfo Information about the server implementation, including name, version, and branding. + * @property instructions Optional instructions describing how to use the server and its features. + * Clients can use this to improve the LLM's understanding of available + * tools, resources, etc. It can be thought of as a "hint" to the model. + * For example, this information MAY be added to the system prompt to help + * the LLM make better use of the server's capabilities. + * @property meta Optional metadata for this response. + */ +@Serializable +public data class InitializeResult( + val protocolVersion: String = LATEST_PROTOCOL_VERSION, + val capabilities: ServerCapabilities, + val serverInfo: Implementation, + val instructions: String? = null, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : ServerResult diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt new file mode 100644 index 0000000..6622900 --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt @@ -0,0 +1,225 @@ +@file:OptIn(ExperimentalAtomicApi::class, ExperimentalSerializationApi::class) + +package io.modelcontextprotocol.kotlin.sdk.types + +import kotlinx.serialization.EncodeDefault +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.decodeFromJsonElement +import kotlinx.serialization.json.encodeToJsonElement +import kotlin.concurrent.atomics.AtomicLong +import kotlin.concurrent.atomics.ExperimentalAtomicApi +import kotlin.concurrent.atomics.incrementAndFetch +import kotlin.jvm.JvmInline + +public const val JSONRPC_VERSION: String = "2.0" + +private val REQUEST_MESSAGE_ID: AtomicLong = AtomicLong(0L) + +public fun RequestId(value: String): RequestId = RequestId.StringId(value) + +public fun RequestId(value: Long): RequestId = RequestId.NumberId(value) + +/** + * A uniquely identifying ID for a request in JSON-RPC. + */ +@Serializable(with = RequestIdPolymorphicSerializer::class) +public sealed interface RequestId { + + @JvmInline + @Serializable + public value class StringId(public val value: String) : RequestId + + @JvmInline + @Serializable + public value class NumberId(public val value: Long) : RequestId +} + +/** + * Converts the request to a JSON-RPC request. + * + * @return The JSON-RPC request representation. + */ +public fun Request.toJSON(): JSONRPCRequest = JSONRPCRequest( + method = method.value, + params = this.params?.let { + McpJson.encodeToJsonElement(it) + }, +) + +/** + * Decodes a JSON-RPC request into a protocol-specific [Request]. + * + * @return The decoded [Request] + */ +public fun JSONRPCRequest.fromJSON(): Request = + McpJson.decodeFromJsonElement(McpJson.encodeToJsonElement(this)) + +/** + * Converts the notification to a JSON-RPC notification. + * + * @return The JSON-RPC notification representation. + */ +public fun Notification.toJSON(): JSONRPCNotification = JSONRPCNotification( + method = method.value, + params = this.params?.let { + McpJson.encodeToJsonElement(it) + }, +) + +/** + * Decodes a JSON-RPC notification into a protocol-specific [Notification]. + * + * @return The decoded [Notification]. + */ +internal fun JSONRPCNotification.fromJSON(): Notification = + McpJson.decodeFromJsonElement(McpJson.encodeToJsonElement(this)) + +/** + * Base interface for all JSON-RPC 2.0 messages. + * + * All messages in the MCP protocol follow the JSON-RPC 2.0 specification. + */ +@Serializable(with = JSONRPCMessagePolymorphicSerializer::class) +public sealed interface JSONRPCMessage { + public val jsonrpc: String +} + +// ============================================================================ +// JSONRPCRequest +// ============================================================================ + +/** + * A request that expects a response. + * + * Requests are identified by a unique [id] and specify a [method] to invoke. + * The server or client (depending on direction) must respond with either a + * [JSONRPCResponse] or [JSONRPCError] that has the same [id]. + * + * @property jsonrpc Always "2.0" to indicate JSON-RPC 2.0 protocol. + * @property id A unique identifier for this request. The response will include the same ID. + * Can be a string or number. + * @property method The name of the method to invoke (e.g., "tools/list", "resources/read"). + * @property params Optional parameters for the method. Structure depends on the specific method. + */ +@Serializable +public data class JSONRPCRequest( + val id: RequestId = RequestId(REQUEST_MESSAGE_ID.incrementAndFetch()), + val method: String, + val params: JsonElement? = null, +) : JSONRPCMessage { + @EncodeDefault + override val jsonrpc: String = JSONRPC_VERSION +} + +// ============================================================================ +// JSONRPCNotification +// ============================================================================ + +/** + * A notification which does not expect a response. + * + * Notifications are fire-and-forget messages. They do not have an `id` and + * the recipient does not send any response (neither success nor error). + * + * Examples: progress updates, resource change notifications, log messages. + * + * @property jsonrpc Always "2.0" to indicate JSON-RPC 2.0 protocol. + * @property method The name of the notification method (e.g., "notifications/progress", + * "notifications/resources/updated"). + * @property params Optional parameters for the notification. Structure depends on the specific method. + */ +@Serializable +public data class JSONRPCNotification(val method: String, val params: JsonElement? = null) : JSONRPCMessage { + @EncodeDefault + override val jsonrpc: String = JSONRPC_VERSION +} + +// ============================================================================ +// JSONRPCResponse +// ============================================================================ + +/** + * A successful (non-error) response to a request. + * + * Sent in response to a [JSONRPCRequest] when the method execution succeeds. + * The [id] must match the [id] of the original request. + * + * @property jsonrpc Always "2.0" to indicate JSON-RPC 2.0 protocol. + * @property id The identifier from the original request. Used to match responses to requests. + * @property result The result of the method execution. Structure depends on the method that was called. + */ +@Serializable +public data class JSONRPCResponse(val id: RequestId, val result: RequestResult = EmptyResult()) : JSONRPCMessage { + @EncodeDefault + override val jsonrpc: String = JSONRPC_VERSION +} + +// ============================================================================ +// JSONRPCError +// ============================================================================ + +/** + * A response to a request that indicates an error occurred. + * + * Sent in response to a [JSONRPCRequest] when the method execution fails. + * The [id] must match the [id] of the original request. + * + * @property jsonrpc Always "2.0" to indicate JSON-RPC 2.0 protocol. + * @property id The identifier from the original request. Used to match error responses to requests. + * @property error Details about the error that occurred, including error code and message. + */ +@Serializable +public data class JSONRPCError(val id: RequestId, val error: RPCError) : JSONRPCMessage { + @EncodeDefault + override val jsonrpc: String = JSONRPC_VERSION +} + +/** + * Error information for a failed JSON-RPC request. + * + * @property code The error type that occurred. A number indicating the error category. + * See standard JSON-RPC 2.0 error codes: + * - -32700: Parse error (invalid JSON) + * - -32600: Invalid request (not valid JSON-RPC) + * - -32601: Method not found + * - -32602: Invalid params + * - -32603: Internal error + * - -32000 to -32099: Server-defined errors + * @property message A short description of the error. + * The message SHOULD be limited to a concise single sentence. + * @property data Additional information about the error. + * The value of this member is defined by the sender + * (e.g., detailed error information, nested errors, etc.). + */ +@Serializable +public data class RPCError(val code: Int, val message: String, val data: JsonElement? = null) { + /** + * Standard JSON-RPC 2.0 and MCP SDK error codes. + */ + public object ErrorCode { + // SDK-specific error codes + /** Connection was closed */ + public const val CONNECTION_CLOSED: Int = -32000 + + /** Request timed out */ + public const val REQUEST_TIMEOUT: Int = -32001 + + // Standard JSON-RPC 2.0 error codes + /** Invalid JSON was received */ + public const val PARSE_ERROR: Int = -32700 + + /** The JSON sent is not a valid Request object */ + public const val INVALID_REQUEST: Int = -32600 + + /** The method does not exist or is not available */ + public const val METHOD_NOT_FOUND: Int = -32601 + + /** Invalid method parameter(s) */ + public const val INVALID_PARAMS: Int = -32602 + + /** Internal JSON-RPC error */ + public const val INTERNAL_ERROR: Int = -32603 + } +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonUtils.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonUtils.kt new file mode 100644 index 0000000..b9fce4a --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonUtils.kt @@ -0,0 +1,83 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.json.ClassDiscriminatorMode +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonNull +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.add +import kotlinx.serialization.json.buildJsonArray +import kotlinx.serialization.json.buildJsonObject + +public val EmptyJsonObject: JsonObject = JsonObject(emptyMap()) + +@OptIn(ExperimentalSerializationApi::class) +public val McpJson: Json by lazy { + Json { + ignoreUnknownKeys = true + encodeDefaults = true + isLenient = true + classDiscriminatorMode = ClassDiscriminatorMode.NONE + explicitNulls = false + } +} + +public fun Map.toJson(): Map = this.mapValues { (_, value) -> + runCatching { convertToJsonElement(value) } + .getOrElse { JsonPrimitive(value.toString()) } +} + +@OptIn(ExperimentalUnsignedTypes::class, ExperimentalSerializationApi::class) +private fun convertToJsonElement(value: Any?): JsonElement = when (value) { + null -> JsonNull + + is JsonElement -> value + + is String -> JsonPrimitive(value) + + is Number -> JsonPrimitive(value) + + is Boolean -> JsonPrimitive(value) + + is Char -> JsonPrimitive(value.toString()) + + is Enum<*> -> JsonPrimitive(value.name) + + is Map<*, *> -> buildJsonObject { value.forEach { (k, v) -> put(k.toString(), convertToJsonElement(v)) } } + + is Collection<*> -> buildJsonArray { value.forEach { add(convertToJsonElement(it)) } } + + is Array<*> -> buildJsonArray { value.forEach { add(convertToJsonElement(it)) } } + + // Primitive arrays + is IntArray -> buildJsonArray { value.forEach { add(it) } } + + is LongArray -> buildJsonArray { value.forEach { add(it) } } + + is FloatArray -> buildJsonArray { value.forEach { add(it) } } + + is DoubleArray -> buildJsonArray { value.forEach { add(it) } } + + is BooleanArray -> buildJsonArray { value.forEach { add(it) } } + + is ShortArray -> buildJsonArray { value.forEach { add(it) } } + + is ByteArray -> buildJsonArray { value.forEach { add(it) } } + + is CharArray -> buildJsonArray { value.forEach { add(it.toString()) } } + + // Unsigned arrays + is UIntArray -> buildJsonArray { value.forEach { add(JsonPrimitive(it)) } } + + is ULongArray -> buildJsonArray { value.forEach { add(JsonPrimitive(it)) } } + + is UShortArray -> buildJsonArray { value.forEach { add(JsonPrimitive(it)) } } + + is UByteArray -> buildJsonArray { value.forEach { add(JsonPrimitive(it)) } } + + else -> { + JsonPrimitive(value.toString()) + } +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/logging.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/logging.kt new file mode 100644 index 0000000..2966b0c --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/logging.kt @@ -0,0 +1,84 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import kotlinx.serialization.EncodeDefault +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * The severity of a log message. + * + * These levels map to syslog message severities, as specified in + * [RFC-5424](https://datatracker.ietf.org/doc/html/rfc5424#section-6.2.1). + * + * Levels are ordered from least to most severe: + * [Debug] < [Info] < [Notice] < [Warning] < [Error] < [Critical] < [Alert] < [Emergency] + */ +@Serializable +public enum class LoggingLevel { + /** Detailed debug information for troubleshooting (RFC-5424: Debug) */ + @SerialName("debug") + Debug, + + /** Informational messages about normal operations (RFC-5424: Informational) */ + @SerialName("info") + Info, + + /** Normal but significant conditions (RFC-5424: Notice) */ + @SerialName("notice") + Notice, + + /** Warning conditions that may require attention (RFC-5424: Warning) */ + @SerialName("warning") + Warning, + + /** Error conditions that require attention (RFC-5424: Error) */ + @SerialName("error") + Error, + + /** Critical conditions requiring immediate action (RFC-5424: Critical) */ + @SerialName("critical") + Critical, + + /** Action must be taken immediately (RFC-5424: Alert) */ + @SerialName("alert") + Alert, + + /** System is unusable, highest severity (RFC-5424: Emergency) */ + @SerialName("emergency") + Emergency, +} + +/** + * A request from the client to the server to enable or adjust logging. + * + * After receiving this request, the server should send log messages at the specified + * [level][SetLevelRequestParams.level] and higher (more severe) to the client as + * notifications/message events. + * + * @property params The parameters specifying the desired logging level. + */ +@Serializable +public data class SetLevelRequest(override val params: SetLevelRequestParams) : ClientRequest { + @OptIn(ExperimentalSerializationApi::class) + @EncodeDefault + override val method: Method = Method.Defined.LoggingSetLevel +} + +/** + * Parameters for a logging/setLevel request. + * + * @property level The minimum severity level of logging that the client wants to receive + * from the server. The server should send all logs at this level and higher + * (i.e., more severe) to the client as notifications/message. + * For example, if [level] is [LoggingLevel.Warning], the server should send + * warning, error, critical, alert, and emergency messages, but not info, notice, or debug. + * @property meta Optional metadata for this request. May include a progressToken for + * out-of-band progress notifications. + */ +@Serializable +public data class SetLevelRequestParams( + val level: LoggingLevel, + @SerialName("_meta") + override val meta: RequestMeta? = null, +) : RequestParams diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/methods.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/methods.kt new file mode 100644 index 0000000..9970015 --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/methods.kt @@ -0,0 +1,49 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import kotlinx.serialization.Serializable + +/** + * Represents a method in the protocol, which can be predefined or custom. + */ +@Serializable(with = MethodSerializer::class) +public sealed interface Method { + public val value: String + + /** + * Enum of predefined methods supported by the protocol. + */ + @Serializable + public enum class Defined(override val value: String) : Method { + Initialize("initialize"), + Ping("ping"), + ResourcesList("resources/list"), + ResourcesTemplatesList("resources/templates/list"), + ResourcesRead("resources/read"), + ResourcesSubscribe("resources/subscribe"), + ResourcesUnsubscribe("resources/unsubscribe"), + PromptsList("prompts/list"), + PromptsGet("prompts/get"), + NotificationsCancelled("notifications/cancelled"), + NotificationsInitialized("notifications/initialized"), + NotificationsProgress("notifications/progress"), + NotificationsMessage("notifications/message"), + NotificationsResourcesUpdated("notifications/resources/updated"), + NotificationsResourcesListChanged("notifications/resources/list_changed"), + NotificationsToolsListChanged("notifications/tools/list_changed"), + NotificationsRootsListChanged("notifications/roots/list_changed"), + NotificationsPromptsListChanged("notifications/prompts/list_changed"), + ToolsList("tools/list"), + ToolsCall("tools/call"), + LoggingSetLevel("logging/setLevel"), + SamplingCreateMessage("sampling/createMessage"), + CompletionComplete("completion/complete"), + RootsList("roots/list"), + ElicitationCreate("elicitation/create"), + } + + /** + * Represents a custom method defined by the user. + */ + @Serializable + public data class Custom(override val value: String) : Method +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/notification.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/notification.kt new file mode 100644 index 0000000..2321584 --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/notification.kt @@ -0,0 +1,327 @@ +@file:OptIn(ExperimentalSerializationApi::class) + +package io.modelcontextprotocol.kotlin.sdk.types + +import kotlinx.serialization.EncodeDefault +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject + +/** + * Represents a notification in the protocol. + */ +@Serializable(with = NotificationPolymorphicSerializer::class) +public sealed interface Notification { + public val method: Method + public val params: NotificationParams? +} + +/** + * Represents a notification sent by the client. + */ +@Serializable(with = ClientNotificationPolymorphicSerializer::class) +public sealed interface ClientNotification : Notification + +/** + * Represents a notification sent by the server. + */ +@Serializable(with = ServerNotificationPolymorphicSerializer::class) +public sealed interface ServerNotification : Notification + +/** + * Interface for notification parameter types. + * + * @property meta Optional metadata for the notification. + */ +@Serializable +public sealed interface NotificationParams : WithMeta + +/** + * Base parameters for notifications that only contain metadata. + */ +@Serializable +public data class BaseNotificationParams(@SerialName("_meta") override val meta: JsonObject? = null) : + NotificationParams + +/** + * Represents a progress notification. + * + * @property progress The progress thus far. This should increase every time progress is made, + * even if the total is unknown. + * @property total Total number of items to a process (or total progress required), if known. + * @property message An optional message describing the current progress. + */ +@Serializable +public class Progress( + public val progress: Double, + public val total: Double? = null, + public val message: String? = null, +) + +// ============================================================================ +// Custom Notification +// ============================================================================ + +/** + * Represents a custom notification method that is not part of the core MCP specification. + * + * The MCP protocol allows implementations to define custom methods for extending functionality. + * This class captures such custom notifications while preserving all their data. + * + * @property method The custom method name. By convention, custom methods often contain + * organization-specific prefixes (e.g., "mycompany/custom_event"). + * @property params Raw JSON parameters for the custom notification, if present. + */ +@Serializable +public data class CustomNotification(override val method: Method, override val params: BaseNotificationParams? = null) : + ClientNotification, + ServerNotification + +// ============================================================================ +// Cancelled Notification +// ============================================================================ + +/** + * This notification can be sent by either side to indicate that it is cancelling a previously-issued request. + * + * The request SHOULD still be in-flight, but due to communication latency, + * it is always possible that this notification MAY arrive after the request has already finished. + * + * This notification indicates that the result will be unused, so any associated processing SHOULD cease. + * + * A client MUST NOT attempt to cancel its `initialize` request. + * + * @property params Details of the cancellation request. + */ +@Serializable +public data class CancelledNotification(override val params: CancelledNotificationParams) : + ClientNotification, + ServerNotification { + @EncodeDefault + override val method: Method = Method.Defined.NotificationsCancelled +} + +/** + * Parameters for a notifications/cancelled notification. + * + * @property requestId The ID of the request to cancel. + * This MUST correspond to the ID of a request previously issued in the same direction. + * @property reason An optional string describing the reason for the cancellation. + * This MAY be logged or presented to the user. + * @property meta Optional metadata for this notification. + */ +@Serializable +public data class CancelledNotificationParams( + val requestId: RequestId, + val reason: String? = null, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : NotificationParams + +// ============================================================================ +// Initialized Notification +// ============================================================================ + +/** + * This notification is sent from the client to the server after initialization has finished. + * + * The client sends this after receiving the [InitializeResult] to signal that it is ready + * to begin normal operations. + * + * @property params Optional notification parameters containing metadata. + */ +@Serializable +public data class InitializedNotification(override val params: BaseNotificationParams? = null) : ClientNotification { + @EncodeDefault + override val method: Method = Method.Defined.NotificationsInitialized +} + +// ============================================================================ +// Logging Message Notification +// ============================================================================ + +/** + * A notification of a log message passed from server to client. + * + * If no [SetLevelRequest] has been sent from the client, the server MAY decide + * which messages to send automatically. + * + * @property params The log message parameters including level and data. + */ +@Serializable +public data class LoggingMessageNotification(override val params: LoggingMessageNotificationParams) : + ServerNotification { + @EncodeDefault + override val method: Method = Method.Defined.NotificationsMessage +} + +/** + * Parameters for a notifications/message notification. + * + * @property level The severity of this log message. + * @property data The data to be logged, such as a string message or an object. + * Any JSON serializable type is allowed here. + * @property logger An optional name of the logger issuing this message. + * @property meta Optional metadata for this notification. + */ +@Serializable +public data class LoggingMessageNotificationParams( + val level: LoggingLevel, + val data: JsonElement, + val logger: String? = null, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : NotificationParams + +// ============================================================================ +// Progress Notification +// ============================================================================ + +/** + * An out-of-band notification used to inform the receiver of a progress update for a long-running request. + * + * This notification can be sent by either the client or server to provide progress updates + * for requests that include a progressToken in their _meta field. + * + * @property params The progress update parameters. + */ +@Serializable +public data class ProgressNotification(override val params: ProgressNotificationParams) : + ClientNotification, + ServerNotification { + @EncodeDefault + override val method: Method = Method.Defined.NotificationsProgress +} + +/** + * Parameters for a notifications/progress notification. + * + * @property progressToken The progress token which was given in the initial request, + * used to associate this notification with the request that is proceeding. + * @property progress The progress thus far. This should increase every time progress is made, + * even if the total is unknown. + * @property total Total number of items to process (or total progress required), if known. + * @property message An optional message describing the current progress. + * @property meta Optional metadata for this notification. + */ +@Serializable +public data class ProgressNotificationParams( + val progressToken: ProgressToken, + val progress: Double, + val total: Double? = null, + val message: String? = null, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : NotificationParams + +// ============================================================================ +// Prompts List Changed Notification +// ============================================================================ + +/** + * An optional notification from the server to the client, informing it that the list of prompts it offers has changed. + * + * Servers may issue this without any previous subscription from the client. + * Sent only if the server's [ServerCapabilities.prompts] has `listChanged = true`. + * + * @property params Optional notification parameters containing metadata. + */ +@Serializable +public data class PromptListChangedNotification(override val params: BaseNotificationParams? = null) : + ServerNotification { + @EncodeDefault + override val method: Method = Method.Defined.NotificationsPromptsListChanged +} + +// ============================================================================ +// Resources List Changed Notification +// ============================================================================ + +/** + * An optional notification from the server to the client, informing it that the list of resources it can read from has changed. + * + * Servers may issue this without any previous subscription from the client. + * Sent only if the server's [ServerCapabilities.resources] has `listChanged = true`. + * + * @property params Optional notification parameters containing metadata. + */ +@Serializable +public data class ResourceListChangedNotification(override val params: BaseNotificationParams? = null) : + ServerNotification { + @EncodeDefault + override val method: Method = Method.Defined.NotificationsResourcesListChanged +} + +// ============================================================================ +// Resource Updated Notification +// ============================================================================ + +/** + * A notification from the server to the client, informing it that a resource has changed and may need to be read again. + * + * This should only be sent if the client previously sent a resources/subscribe request + * and the server's [ServerCapabilities.resources] has `subscribe = true`. + * + * @property params Parameters identifying which resource was updated. + */ +@Serializable +public data class ResourceUpdatedNotification(override val params: ResourceUpdatedNotificationParams) : + ServerNotification { + @EncodeDefault + override val method: Method = Method.Defined.NotificationsResourcesUpdated +} + +/** + * Parameters for a notifications/resources/updated notification. + * + * @property uri The URI of the resource that has been updated. + * This might be a sub-resource of the one that the client actually subscribed to. + * @property meta Optional metadata for this notification. + */ +@Serializable +public data class ResourceUpdatedNotificationParams( + val uri: String, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : NotificationParams + +// ============================================================================ +// Roots List Changed Notification +// ============================================================================ + +/** + * A notification from the client to the server, informing it that the list of roots has changed. + * + * This notification should be sent whenever the client adds, removes, or modifies any root. + * The server should then request an updated list of roots using the ListRootsRequest. + * Sent only if the client's [ClientCapabilities.roots] has `listChanged = true`. + * + * @property params Optional notification parameters containing metadata. + */ +@Serializable +public data class RootsListChangedNotification(override val params: BaseNotificationParams? = null) : + ClientNotification { + @EncodeDefault + override val method: Method = Method.Defined.NotificationsRootsListChanged +} + +// ============================================================================ +// Tools List Changed Notification +// ============================================================================ + +/** + * An optional notification from the server to the client, informing it that the list of tools it offers has changed. + * + * Servers may issue this without any previous subscription from the client. + * Sent only if the server's [ServerCapabilities.tools] has `listChanged = true`. + * + * @property params Optional notification parameters containing metadata. + */ +@Serializable +public data class ToolListChangedNotification(override val params: BaseNotificationParams? = null) : + ServerNotification { + @EncodeDefault + override val method: Method = Method.Defined.NotificationsToolsListChanged +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.kt new file mode 100644 index 0000000..5d7054b --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.kt @@ -0,0 +1,212 @@ +@file:OptIn(ExperimentalSerializationApi::class) + +package io.modelcontextprotocol.kotlin.sdk.types + +import kotlinx.serialization.EncodeDefault +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonObject + +/** + * Describes an argument that a prompt can accept. + * + * Arguments allow prompts to be templated with user-provided values. + * + * @property name The programmatic identifier for this argument. + * Intended for logical use and API identification. If [title] is not provided, + * this should be used as a fallback display name. + * @property description A human-readable description of the argument, explaining what it's for + * and what values are expected. + * @property required Whether this argument must be provided when using the prompt. + * If true, the client must supply a value for this argument. + * @property title Optional human-readable display name for this argument. + * Intended for UI and end-user contexts, optimized to be easily understood + * even by those unfamiliar with domain-specific terminology. + * If not provided, [name] should be used for display purposes. + */ +@Serializable +public data class PromptArgument( + val name: String, + val description: String? = null, + val required: Boolean? = null, + val title: String? = null, +) + +/** + * A prompt or prompt template that the server offers. + * + * Prompts can be static messages or templates that accept arguments for dynamic content generation. + * + * @property name The programmatic identifier for this prompt. + * Intended for logical use and API identification. If [title] is not provided, + * this should be used as a fallback display name. + * @property description An optional description of what this prompt provides and when to use it. + * @property arguments Optional list of arguments to use for templating the prompt. + * If present, the prompt is a template that requires these arguments to be filled in. + * @property title Optional human-readable display name for this prompt. + * Intended for UI and end-user contexts, optimized to be easily understood + * even by those unfamiliar with domain-specific terminology. + * If not provided, [name] should be used for display purposes. + * @property icons Optional set of sized icons that clients can display in their user interface. + * Clients MUST support at least PNG and JPEG formats. + * Clients SHOULD also support SVG and WebP formats. + * @property meta Optional metadata for this prompt. + */ +@Serializable +public data class Prompt( + val name: String, + val description: String? = null, + val arguments: List? = null, + val title: String? = null, + val icons: List? = null, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : WithMeta + +/** + * Describes a message returned as part of a prompt. + * + * This is similar to SamplingMessage, but also supports the embedding of resources from the MCP server. + * + * @property role The role of the message sender (e.g., user, assistant, or system). + * @property content The content of the message. Can include text, images, resources, and other content types. + */ +@Serializable +public data class PromptMessage(val role: Role, val content: ContentBlock) + +/** + * Identifies a prompt by reference. + * + * Used in completion requests and other contexts where a prompt needs to be referenced + * without including its full definition. + * + * @property name The programmatic identifier of the prompt being referenced. + * Intended for logical use and API identification. If [title] is not provided, + * this should be used as a fallback display name. + * @property title Optional human-readable display name for the referenced prompt. + * Intended for UI and end-user contexts, optimized to be easily understood + * even by those unfamiliar with domain-specific terminology. + * If not provided, [name] should be used for display purposes. + */ +@Serializable +public data class PromptReference(val name: String, val title: String? = null) : Reference { + @EncodeDefault + public override val type: ReferenceType = ReferenceType.Prompt +} + +// ============================================================================ +// prompts/get +// ============================================================================ + +/** + * Used by the client to get a prompt provided by the server. + * + * Prompts can be static text or templates that accept arguments. When arguments + * are provided, the server will substitute them into the prompt template before + * returning the result. + * + * @property params The parameters specifying which prompt to retrieve and any template arguments. + */ +@Serializable +public data class GetPromptRequest(override val params: GetPromptRequestParams) : ClientRequest { + @EncodeDefault + override val method: Method = Method.Defined.PromptsGet + + /** + * The name of the prompt or prompt template to retrieve. + */ + public val name: String + get() = params.name + + /** + * Arguments to use for templating the prompt. + * Keys are argument names, values are the argument values to substitute. + */ + public val arguments: Map? + get() = params.arguments + + /** + * Metadata for this request. May include a progressToken for out-of-band progress notifications. + */ + public val meta: RequestMeta? + get() = params.meta +} + +/** + * Parameters for a prompts/get request. + * + * @property name The name of the prompt or prompt template to retrieve. + * @property arguments Optional arguments to use for templating the prompt. + * Keys are argument names, values are the argument values to substitute. + * @property meta Optional metadata for this request. May include a progressToken for + * out-of-band progress notifications. + */ +@Serializable +public data class GetPromptRequestParams( + val name: String, + val arguments: Map? = null, + @SerialName("_meta") + override val meta: RequestMeta? = null, +) : RequestParams + +/** + * The server's response to a [GetPromptRequest] from the client. + * + * Contains the prompt's messages and optional description. If the prompt was a template, + * the messages will have arguments already substituted. + * + * @property messages The list of messages that make up this prompt. + * Each message has a role (e.g., "user", "assistant") and content. + * @property description An optional description for the prompt, explaining its purpose or usage. + * @property meta Optional metadata for this response. + */ +@Serializable +public data class GetPromptResult( + val messages: List, + val description: String? = null, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : ServerResult + +// ============================================================================ +// prompts/list +// ============================================================================ + +/** + * Sent from the client to request a list of prompts and prompt templates the server has. + * + * This request supports pagination through the cursor parameter. If the server has many + * prompts, it may return a subset along with a cursor to fetch the next page. + * + * @property params Optional pagination parameters to control which page of results to return. + */ +@Serializable +public data class ListPromptsRequest(override val params: PaginatedRequestParams? = null) : + ClientRequest, + PaginatedRequest { + @EncodeDefault + override val method: Method = Method.Defined.PromptsList +} + +/** + * The server's response to a [ListPromptsRequest] from the client. + * + * Returns the available prompts and prompt templates, along with pagination information + * if there are more results available. + * + * @property prompts The list of available prompts. Each prompt includes its name, optional description, + * and information about any arguments it accepts. + * @property nextCursor An opaque token representing the pagination position after the last returned result. + * If present, there may be more results available. The client can pass this token + * in a subsequent request to fetch the next page. + * @property meta Optional metadata for this response. + */ +@Serializable +public data class ListPromptsResult( + val prompts: List, + override val nextCursor: String? = null, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : ServerResult, + PaginatedResult diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.kt new file mode 100644 index 0000000..855e2f2 --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.kt @@ -0,0 +1,116 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.long +import kotlinx.serialization.json.longOrNull +import kotlin.jvm.JvmInline + +@JvmInline +@Serializable +public value class RequestMeta(public val json: JsonObject) { + public val progressToken: ProgressToken? + get() = json["progressToken"]?.let { element -> + when (element) { + is JsonPrimitive if (element.isString) -> ProgressToken(element.content) + is JsonPrimitive if (element.longOrNull != null) -> ProgressToken(element.long) + else -> null + } + } + + public operator fun get(key: String): JsonElement? = json[key] +} + +@Serializable +public sealed interface RequestParams { + @SerialName("_meta") + public val meta: RequestMeta? +} + +@Serializable +public data class BaseRequestParams(@SerialName("_meta") override val meta: RequestMeta? = null) : RequestParams + +/** + * Represents a request in the protocol. + */ +@OptIn(ExperimentalSerializationApi::class) +@Serializable(with = RequestPolymorphicSerializer::class) +public sealed interface Request { + public val method: Method + public val params: RequestParams? +} + +/** + * A custom request with a specified method. + */ +@Serializable +public open class CustomRequest(override val method: Method, override val params: BaseRequestParams?) : Request + +/** + * Represents a request sent by the client. + */ +@Serializable +public sealed interface ClientRequest : Request + +/** + * Represents a request sent by the server. + */ +@Serializable +public sealed interface ServerRequest : Request + +/** + * Represents a request supporting pagination. + */ +@Serializable +public sealed interface PaginatedRequest : Request { + public override val params: PaginatedRequestParams? +} + +/** + * Represents the result of a request, including additional metadata. + */ +@Serializable(with = RequestResultPolymorphicSerializer::class) +public sealed interface RequestResult : WithMeta + +@Serializable(with = ClientResultPolymorphicSerializer::class) +public sealed interface ClientResult : RequestResult + +@Serializable(with = ServerResultPolymorphicSerializer::class) +public sealed interface ServerResult : RequestResult + +/** + * An empty result for a request containing optional metadata. + * + * @param meta Additional metadata for the response. Defaults to an empty JSON object. + */ +@Serializable +public data class EmptyResult(@SerialName("_meta") override val meta: JsonObject? = null) : + ClientResult, + ServerResult + +/** + * Represents a request supporting pagination. + */ +@Serializable +public sealed interface PaginatedResult : RequestResult { + public val nextCursor: String? +} + +/** + * Common parameters for paginated requests. + * + * @property cursor An opaque token representing the current pagination position. + * If provided, the server should return results starting after this cursor. + * @property meta Optional metadata for this request. May include a progressToken for + * out-of-band progress notifications. + */ +@Serializable +public data class PaginatedRequestParams( + val cursor: String? = null, + @SerialName("_meta") + override val meta: RequestMeta? = null, +) : RequestParams diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.kt new file mode 100644 index 0000000..8219cc7 --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.kt @@ -0,0 +1,388 @@ +@file:OptIn(ExperimentalSerializationApi::class) + +package io.modelcontextprotocol.kotlin.sdk.types + +import kotlinx.serialization.EncodeDefault +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonObject + +@Serializable +public sealed interface ResourceLike : WithMeta + +/** + * A known resource that the server is capable of reading. + * + * Resources represent data sources such as files, database entries, API responses, + * or other structured data that can be read by clients. + * + * @property uri The URI of this resource. Can use any protocol scheme (file://, http://, etc.). + * @property name The programmatic identifier for this resource. + * Intended for logical use and API identification. If [title] is not provided, + * this should be used as a fallback display name. + * @property description A description of what this resource represents. + * Clients can use this to improve the LLM's understanding of available resources. + * It can be thought of like a "hint" to the model. + * @property mimeType The MIME type of this resource, if known (e.g., "text/plain", "application/json", "image/png"). + * @property size The size of the raw resource content in bytes (i.e., before base64 encoding or any tokenization), if known. + * Hosts can use this to display file sizes and estimate context window usage. + * @property title Optional human-readable display name for this resource. + * Intended for UI and end-user contexts, optimized to be easily understood + * even by those unfamiliar with domain-specific terminology. + * If not provided, [name] should be used for display purposes. + * @property annotations Optional annotations for the client. Provides additional metadata and hints + * about how to use or display this resource. + * @property icons Optional set of sized icons that clients can display in their user interface. + * Clients MUST support at least PNG and JPEG formats. + * Clients SHOULD also support SVG and WebP formats. + * @property meta Optional metadata for this resource. + */ +@Serializable +public data class Resource( + val uri: String, + val name: String, + val description: String? = null, + val mimeType: String? = null, + val size: Long? = null, + val title: String? = null, + val annotations: Annotations? = null, + val icons: List? = null, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : ResourceLike + +/** + * A template description for resources available on the server. + * + * Resource templates allow servers to expose parameterized resources that clients can + * instantiate with specific values. Templates use RFC 6570 URI template syntax, + * where parameters are indicated with curly braces (e.g., `file:///{directory}/{filename}`). + * + * @property uriTemplate A URI template (according to RFC 6570) that can be used to construct resource URIs. + * Parameters are indicated with curly braces, e.g., `file:///{path}` or `db://users/{userId}`. + * @property name The programmatic identifier for this template. + * Intended for logical use and API identification. If [title] is not provided, + * this should be used as a fallback display name. + * @property description A description of what this template is for. + * Clients can use this to improve the LLM's understanding of available resources. + * It can be thought of like a "hint" to the model. + * @property mimeType The MIME type for all resources that match this template. + * This should only be included if all resources matching this template have the same type. + * For example, a file template might not have a MIME type since files can be of any type, + * but a database record template might always return JSON. + * @property title Optional human-readable display name for this template. + * Intended for UI and end-user contexts, optimized to be easily understood + * even by those unfamiliar with domain-specific terminology. + * If not provided, [name] should be used for display purposes. + * @property annotations Optional annotations for the client. Provides additional metadata and hints + * about how to use or display resources created from this template. + * @property icons Optional set of sized icons that clients can display in their user interface. + * Clients MUST support at least PNG and JPEG formats. + * Clients SHOULD also support SVG and WebP formats. + * @property meta Optional metadata for this template. + */ +@Serializable +public data class ResourceTemplate( + val uriTemplate: String, + val name: String, + val description: String? = null, + val mimeType: String? = null, + val title: String? = null, + val annotations: Annotations? = null, + val icons: List? = null, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : WithMeta + +/** + * A reference to a resource or resource template definition. + * + * Used in completion requests and other contexts where a resource needs to be referenced + * without including its full definition. The URI can be either a specific resource URI + * or a URI template pattern. + * + * @property uri The URI or URI template of the resource. + * Can be a specific resource URI (e.g., `file:///home/user/doc.txt`) + * or a URI template with parameters (e.g., `file:///{path}`). + */ +@Serializable +public data class ResourceTemplateReference(val uri: String) : Reference { + @EncodeDefault + public override val type: ReferenceType = ReferenceType.ResourceTemplate +} + +/** + * The contents of a specific resource or sub-resource. + * + * @property uri The URI of this resource. + * @property mimeType The MIME type of this resource, if known. + * @property meta Optional metadata for this response. + */ +@Serializable(with = ResourceContentsPolymorphicSerializer::class) +public sealed interface ResourceContents : WithMeta { + public val uri: String + public val mimeType: String? +} + +/** + * Represents the text contents of a resource. + * + * @property text The text of the item. + * This must only be set if the item can actually be represented as text (not binary data). + * @property uri The URI of this resource. + * @property mimeType The MIME type of this resource, if known. + */ +@Serializable +public data class TextResourceContents( + val text: String, + override val uri: String, + override val mimeType: String? = null, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : ResourceContents + +/** + * The contents of a specific resource or sub-resource. + * + * @property blob A base64-encoded string representing the binary data of the item. + * @property uri The URI of this resource. + * @property mimeType The MIME type of this resource, if known. + */ +@Serializable +public data class BlobResourceContents( + val blob: String, + override val uri: String, + override val mimeType: String? = null, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : ResourceContents + +/** + * Represents resource contents with unknown or unspecified data. + * + * @property uri The URI of this resource. + * @property mimeType The MIME type of this resource, if known. + */ +@Serializable +public data class UnknownResourceContents( + override val uri: String, + override val mimeType: String? = null, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : ResourceContents + +// ============================================================================ +// resources/list +// ============================================================================ + +/** + * Sent from the client to request a list of resources the server has. + * + * Resources are data sources that the server can provide access to, such as files, + * database entries, API responses, or other structured data. + * + * @property params Optional pagination parameters to control which page of results to return. + */ +@Serializable +public data class ListResourcesRequest(override val params: PaginatedRequestParams? = null) : + ClientRequest, + PaginatedRequest { + @EncodeDefault + override val method: Method = Method.Defined.ResourcesList +} + +/** + * The server's response to a [ListResourcesRequest] from the client. + * + * Returns the available resources along with pagination information if there are more results. + * + * @property resources The list of available resources. Each resource includes its URI, name, + * optional description, and MIME type information. + * @property nextCursor An opaque token representing the pagination position after the last returned result. + * If present, there may be more results available. The client can pass this token + * in a subsequent request to fetch the next page. + * @property meta Optional metadata for this response. + */ +@Serializable +public data class ListResourcesResult( + val resources: List, + override val nextCursor: String? = null, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : ServerResult, + PaginatedResult + +// ============================================================================ +// resources/read +// ============================================================================ + +/** + * Parameters for a resources/read request. + * + * @property uri The URI of the resource to read. The URI can use any protocol; + * it is up to the server how to interpret it. Common schemes include + * file://, http://, https://, or custom application-specific schemes. + * @property meta Optional metadata for this request. May include a progressToken for + * out-of-band progress notifications. + */ +@Serializable +public data class ReadResourceRequestParams( + val uri: String, + @SerialName("_meta") + override val meta: RequestMeta? = null, +) : RequestParams + +/** + * Sent from the client to the server to read a specific resource URI. + * + * The server will return the resource's contents, which can be either text or binary data. + * + * @property params The parameters specifying which resource URI to read. + */ +@Serializable +public data class ReadResourceRequest(override val params: ReadResourceRequestParams) : ClientRequest { + @EncodeDefault + override val method: Method = Method.Defined.ResourcesRead + + /** + * The URI of the resource to read + */ + public val uri: String + get() = params.uri + + /** + * Metadata for this request. May include a progressToken for out-of-band progress notifications. + */ + public val meta: RequestMeta? + get() = params.meta +} + +/** + * The server's response to a [ReadResourceRequest] from the client. + * + * Contains the resource contents, which can be a mix of text and binary data. + * A single resource can return multiple content blocks if it contains multiple parts. + * + * @property contents The contents of the resource. Can include text content (with MIME type and text data) + * or binary/blob content (with MIME type and Base64-encoded data). + * Multiple content blocks can be returned for resources with multiple parts. + * @property meta Optional metadata for this response. + */ +@Serializable +public data class ReadResourceResult( + val contents: List, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : ServerResult + +// ============================================================================ +// resources/subscribe +// ============================================================================ + +/** + * Parameters for a resources/subscribe request. + * + * @property uri The URI of the resource to subscribe to. The URI can use any protocol; + * it is up to the server how to interpret it. + * @property meta Optional metadata for this request. May include a progressToken for + * out-of-band progress notifications. + */ +@Serializable +public data class SubscribeRequestParams( + val uri: String, + @SerialName("_meta") + override val meta: RequestMeta? = null, +) : RequestParams + +/** + * Sent from the client to request resources/updated notifications from the server + * whenever a particular resource changes. + * + * After subscribing, the server will send [ResourceUpdatedNotification] messages + * whenever the subscribed resource is modified. This requires the server to support + * the `subscribe` capability in [ServerCapabilities.resources]. + * + * @property params The parameters specifying which resource URI to subscribe to. + */ +@Serializable +public data class SubscribeRequest(override val params: SubscribeRequestParams) : ClientRequest { + @EncodeDefault + override val method: Method = Method.Defined.ResourcesSubscribe +} + +// ============================================================================ +// resources/unsubscribe +// ============================================================================ + +/** + * Parameters for a resources/unsubscribe request. + * + * @property uri The URI of the resource to unsubscribe from. This should match + * a URI from a previous [SubscribeRequest]. + * @property meta Optional metadata for this request. May include a progressToken for + * out-of-band progress notifications. + */ +@Serializable +public data class UnsubscribeRequestParams( + val uri: String, + @SerialName("_meta") + override val meta: RequestMeta? = null, +) : RequestParams + +/** + * Sent from the client to request cancellation of resources/updated notifications from the server. + * + * This should follow a previous [SubscribeRequest]. After unsubscribing, the server will + * stop sending [ResourceUpdatedNotification] messages for this resource. + * + * @property params The parameters specifying which resource URI to unsubscribe from. + */ +@Serializable +public data class UnsubscribeRequest(override val params: UnsubscribeRequestParams) : ClientRequest { + @EncodeDefault + override val method: Method = Method.Defined.ResourcesUnsubscribe +} + +// ============================================================================ +// resources/templates/list +// ============================================================================ + +/** + * Sent from the client to request a list of resource templates the server has. + * + * Resource templates are parameterized resource URIs that can be instantiated with + * specific argument values. For example, a template like `file:///{path}` allows + * clients to construct URIs for any file path. + * + * @property params Optional pagination parameters to control which page of results to return. + */ +@Serializable +public data class ListResourceTemplatesRequest(override val params: PaginatedRequestParams? = null) : + ClientRequest, + PaginatedRequest { + @EncodeDefault + override val method: Method = Method.Defined.ResourcesTemplatesList +} + +/** + * The server's response to a [ListResourceTemplatesRequest] from the client. + * + * Returns the available resource templates along with pagination information if there are more results. + * + * @property resourceTemplates The list of available resource templates. Each template includes + * its URI template pattern, name, optional description, and information + * about the arguments it accepts. + * @property nextCursor An opaque token representing the pagination position after the last returned result. + * If present, there may be more results available. The client can pass this token + * in a subsequent request to fetch the next page. + * @property meta Optional metadata for this response. + */ +@Serializable +public data class ListResourceTemplatesResult( + val resourceTemplates: List, + override val nextCursor: String? = null, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : ServerResult, + PaginatedResult diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.kt new file mode 100644 index 0000000..2d7ddbc --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.kt @@ -0,0 +1,81 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import kotlinx.serialization.EncodeDefault +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonObject + +/** + * Represents a root directory or file that the server can operate on. + * + * Roots define the top-level locations that a server has access to. They act as + * boundaries for the server's operations, ensuring it only accesses permitted locations. + * + * @property uri The URI identifying the root. This **must** start with `file://` for now. + * This restriction may be relaxed in future versions of the protocol to allow other URI schemes. + * Examples: "file:///home/user/projects", "file:///workspace/repo", "file:///C:/Documents" + * @property name An optional name for the root. This can be used to provide a human-readable + * identifier for the root, which may be useful for display purposes or for + * referencing the root in other parts of the application. + * @property meta Optional metadata for this root. + */ +@Serializable +public data class Root( + val uri: String, + val name: String? = null, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : WithMeta { + init { + require(uri.startsWith("file://")) { + "Root URI must start with 'file://', got: $uri" + } + } +} + +// ============================================================================ +// roots/list +// ============================================================================ + +/** + * Sent from the server to request a list of root URIs from the client. + * + * Roots allow servers to ask for specific directories or files to operate on. + * A common example for roots is providing a set of repositories or directories + * a server should operate on. + * + * This request is typically used when the server needs to understand the file system + * structure or access specific locations that the client has permission to read from. + * + * **Note:** Unlike most other requests, this is sent from the **server to the client**, + * not from client to server. The client must support the `roots` capability with + * `listChanged = true` to receive these requests. + * + * @property params Optional request parameters containing metadata. + */ +@Serializable +public data class ListRootsRequest(override val params: BaseRequestParams? = null) : ServerRequest { + @OptIn(ExperimentalSerializationApi::class) + @EncodeDefault + override val method: Method = Method.Defined.RootsList +} + +/** + * The client's response to a [ListRootsRequest] from the server. + * + * This result contains an array of Root objects, each representing a root directory + * or file that the server can operate on. Roots define the boundaries of what + * the server is allowed to access. + * + * @property roots The list of root URIs that the server can access. + * Each root represents a top-level directory, file, or other location + * that the server has permission to work with. + * @property meta Optional metadata for this response. + */ +@Serializable +public data class ListRootsResult( + val roots: List, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : ClientResult diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.kt new file mode 100644 index 0000000..22d9239 --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.kt @@ -0,0 +1,200 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import kotlinx.serialization.EncodeDefault +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonObject +import kotlin.jvm.JvmInline + +/** + * Hints to use for model selection. + */ +@Serializable +public data class ModelHint( + /** + * A hint for a model name. + */ + val name: String?, +) + +/** + * The server's preferences for model selection, requested of the client during sampling. + * + * Because LLMs can vary along multiple dimensions, choosing the "best" model is rarely straightforward. + * Different models excel in different areas—some are faster but less capable, others are more capable + * but more expensive, and so on. This interface allows servers to express their priorities across + * multiple dimensions to help clients make an appropriate selection for their use case. + * + * **Important:** These preferences are always advisory. The client MAY ignore them completely. + * It is also up to the client to decide how to interpret these preferences and how to balance + * them against other considerations. + * + * @property hints Optional hints to use for model selection. + * If multiple hints are specified, the client MUST evaluate them in order + * (such that the first match is taken). + * The client SHOULD prioritize these hints over the numeric priorities, + * but MAY still use the priorities to select from ambiguous matches. + * @property costPriority How much to prioritize cost when selecting a model. + * A value of 0.0 means cost is not important, while a value of 1.0 means + * cost is the most important factor. + * Should be between 0.0 and 1.0. + * @property speedPriority How much to prioritize sampling speed (latency) when selecting a model. + * A value of 0.0 means speed is not important, while a value of 1.0 means + * speed is the most important factor. + * Should be between 0.0 and 1.0. + * @property intelligencePriority How much to prioritize intelligence and capabilities when selecting a model. + * A value of 0.0 means intelligence is not important, while a value of 1.0 + * means intelligence is the most important factor. + * Should be between 0.0 and 1.0. + */ +@Serializable +public data class ModelPreferences( + val hints: List? = null, + val costPriority: Double? = null, + val speedPriority: Double? = null, + val intelligencePriority: Double? = null, +) { + init { + require(costPriority == null || costPriority in 0.0..1.0) { + "costPriority must be in 0.0 <= x <= 1.0 value range" + } + + require(speedPriority == null || speedPriority in 0.0..1.0) { + "costPriority must be in 0.0 <= x <= 1.0 value range" + } + + require(intelligencePriority == null || intelligencePriority in 0.0..1.0) { + "intelligencePriority must be in 0.0 <= x <= 1.0 value range" + } + } +} + +/** + * Describes a message issued to or received from an LLM API. + * + * Used in sampling requests to provide conversation context and history to the LLM. + * + * @property role The role of the message sender (user, assistant, or system). + * @property content The content of the message. Can be text, image, or audio content. + */ +@Serializable +public data class SamplingMessage(val role: Role, val content: MediaContent) + +// ============================================================================ +// sampling/createMessage +// ============================================================================ + +/** + * A request from the server to sample an LLM via the client. + * + * The client has full discretion over which model to select. The client should also + * inform the user before beginning sampling, to allow them to inspect the request + * (human in the loop) and decide whether to approve it. + * + * **Important:** This is a request from **server to client**, not client to server. + * The server is asking the client to use its LLM to generate a completion. + * + * **Human-in-the-loop:** Clients should: + * 1. Show the sampling request to the user before executing it + * 2. Allow the user to approve or reject the request + * 3. Show the generated response to the user before sending it back to the server + * + * @property params The parameters for the sampling request, including messages and model preferences. + */ +@Serializable +public data class CreateMessageRequest(override val params: CreateMessageRequestParams) : ServerRequest { + @OptIn(ExperimentalSerializationApi::class) + @EncodeDefault + override val method: Method = Method.Defined.SamplingCreateMessage +} + +/** + * Parameters for a sampling/createMessage request. + * + * @property maxTokens The requested maximum number of tokens to sample (to prevent runaway completions). + * The client MAY choose to sample fewer tokens than the requested maximum. + * @property messages The messages to use as context for sampling. Typically includes conversation history + * and the current user message. + * @property modelPreferences The server's preferences for which model to select. + * The client MAY ignore these preferences and choose any model. + * @property systemPrompt An optional system prompt the server wants to use for sampling. + * The client MAY modify or omit this prompt. + * @property includeContext A request to include context from one or more MCP servers (including the caller), + * to be attached to the prompt. The client MAY ignore this request. + * - "none": Don't include any server context + * - "thisServer": Include context only from the requesting server + * - "allServers": Include context from all connected MCP servers + * @property temperature Optional temperature parameter for sampling (typically 0.0-2.0). + * Higher values make output more random, lower values make it more deterministic. + * @property stopSequences Optional list of sequences that will stop generation if encountered. + * @property metadata Optional metadata to pass through to the LLM provider. + * The format of this metadata is provider-specific. + * @property meta Optional metadata for this request. May include a progressToken for + * out-of-band progress notifications. + */ +@Serializable +public data class CreateMessageRequestParams( + val maxTokens: Int, + val messages: List, + val modelPreferences: ModelPreferences? = null, + val systemPrompt: String? = null, + val includeContext: IncludeContext? = null, + val temperature: Double? = null, + val stopSequences: List? = null, + val metadata: JsonObject? = null, + @SerialName("_meta") + override val meta: RequestMeta? = null, +) : RequestParams + +/** + * Specifies which MCP server context to include in the sampling request. + */ +@Serializable +public enum class IncludeContext { + /** Don't include any server context */ + @SerialName("none") + None, + + /** Include context only from the requesting server */ + @SerialName("thisServer") + ThisServer, + + /** Include context from all connected MCP servers */ + @SerialName("allServers") + AllServers, +} + +/** + * The client's response to a [CreateMessageRequest] from the server. + * + * The client should inform the user before returning the sampled message, to allow them + * to inspect the response (human in the loop) and decide whether to allow the server to see it. + * + * @property role The role of the message sender. Typically [Role.Assistant] for LLM-generated responses. + * @property content The generated content. Can be text, image, or audio content. + * @property model The name of the model that generated the message (e.g., "claude-3-opus-20240229", + * "gpt-4-turbo-preview"). This helps the server understand which model was used. + * @property stopReason The reason why sampling stopped, if known. + * Common values: "end_turn", "max_tokens", "stop_sequence", "content_filter" + * @property meta Optional metadata for this response. + */ +@Serializable +public data class CreateMessageResult( + val role: Role, + val content: MediaContent, + val model: String, + val stopReason: StopReason? = null, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : ClientResult + +@JvmInline +@Serializable +public value class StopReason(public val value: String) { + public companion object { + public val EndTurn: StopReason = StopReason("endTurn") + public val StopSequence: StopReason = StopReason("stopSequence") + public val MaxTokens: StopReason = StopReason("maxTokens") + } +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt new file mode 100644 index 0000000..721a2aa --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt @@ -0,0 +1,402 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import io.github.oshai.kotlinlogging.KotlinLogging +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerializationException +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.JsonContentPolymorphicSerializer +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.longOrNull + +private val logger = KotlinLogging.logger {} + +// ============================================================================ +// Utility Functions +// ============================================================================ + +/** + * Safely extracts the method field from a JSON element. + * Returns null if the method field is not present. + */ +private fun JsonElement.getMethodOrNull(): String? = jsonObject["method"]?.jsonPrimitive?.content + +/** + * Safely extracts the type field from a JSON element. + * Returns null if the type field is not present. + */ +private fun JsonElement.getTypeOrNull(): String? = jsonObject["type"]?.jsonPrimitive?.content + +/** + * Extracts the type field from a JSON element. + * Throws [SerializationException] if the type field is not present. + */ +private fun JsonElement.getType(): String = requireNotNull(getTypeOrNull()) { "Missing required 'type' field" } + +// ============================================================================ +// Method Serializer +// ============================================================================ + +/** + * Custom serializer for [Method] that handles both defined and custom methods. + * Defined methods are deserialized to [Method.Defined], while unknown methods + * are deserialized to [Method.Custom]. + */ +internal object MethodSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("io.modelcontextprotocol.kotlin.sdk.types.Method", PrimitiveKind.STRING) + + private val definedMethods: Map by lazy { + Method.Defined.entries.associateBy { it.value } + } + + override fun serialize(encoder: Encoder, value: Method) { + encoder.encodeString(value.value) + } + + override fun deserialize(decoder: Decoder): Method { + val decodedString = decoder.decodeString() + return definedMethods[decodedString] ?: Method.Custom(decodedString) + } +} + +// ============================================================================ +// Reference Serializers +// ============================================================================ + +/** + * Polymorphic serializer for [Reference] types. + * Determines the concrete type based on the "type" field in JSON. + */ +internal object ReferencePolymorphicSerializer : JsonContentPolymorphicSerializer(Reference::class) { + override fun selectDeserializer(element: JsonElement): DeserializationStrategy = + when (element.getType()) { + ReferenceType.Prompt.value -> PromptReference.serializer() + ReferenceType.ResourceTemplate.value -> ResourceTemplateReference.serializer() + else -> throw SerializationException("Unknown reference type: ${element.getTypeOrNull()}") + } +} + +// ============================================================================ +// Content Serializers +// ============================================================================ + +/** + * Polymorphic serializer for [ContentBlock] types. + * Determines the concrete type based on the "type" field in JSON. + */ +internal object ContentBlockPolymorphicSerializer : + JsonContentPolymorphicSerializer(ContentBlock::class) { + override fun selectDeserializer(element: JsonElement): DeserializationStrategy = + when (element.getType()) { + ContentTypes.TEXT.value -> TextContent.serializer() + ContentTypes.IMAGE.value -> ImageContent.serializer() + ContentTypes.AUDIO.value -> AudioContent.serializer() + ContentTypes.RESOURCE_LINK.value -> ResourceLink.serializer() + ContentTypes.EMBEDDED_RESOURCE.value -> EmbeddedResource.serializer() + else -> throw SerializationException("Unknown content block type: ${element.getTypeOrNull()}") + } +} + +/** + * Polymorphic serializer for [MediaContent] types (text, image, audio). + * Determines the concrete type based on the "type" field in JSON. + */ +internal object MediaContentPolymorphicSerializer : + JsonContentPolymorphicSerializer(MediaContent::class) { + override fun selectDeserializer(element: JsonElement): DeserializationStrategy = + when (element.getType()) { + ContentTypes.TEXT.value -> TextContent.serializer() + ContentTypes.IMAGE.value -> ImageContent.serializer() + ContentTypes.AUDIO.value -> AudioContent.serializer() + else -> throw SerializationException("Unknown media content type: ${element.getTypeOrNull()}") + } +} + +// ============================================================================ +// Resource Serializers +// ============================================================================ + +/** + * Polymorphic serializer for [ResourceContents] types. + * Determines the concrete type based on the presence of "text" or "blob" fields. + */ +internal object ResourceContentsPolymorphicSerializer : + JsonContentPolymorphicSerializer(ResourceContents::class) { + override fun selectDeserializer(element: JsonElement): DeserializationStrategy { + val jsonObject = element.jsonObject + return when { + "text" in jsonObject -> TextResourceContents.serializer() + "blob" in jsonObject -> BlobResourceContents.serializer() + else -> UnknownResourceContents.serializer() + } + } +} + +// ============================================================================ +// Request Serializers +// ============================================================================ + +private val clientRequestDeserializers: Map> by lazy { + mapOf( + Method.Defined.CompletionComplete.value to CompleteRequest.serializer(), + Method.Defined.Initialize.value to InitializeRequest.serializer(), + Method.Defined.Ping.value to PingRequest.serializer(), + Method.Defined.LoggingSetLevel.value to SetLevelRequest.serializer(), + Method.Defined.PromptsGet.value to GetPromptRequest.serializer(), + Method.Defined.PromptsList.value to ListPromptsRequest.serializer(), + Method.Defined.ResourcesList.value to ListResourcesRequest.serializer(), + Method.Defined.ResourcesRead.value to ReadResourceRequest.serializer(), + Method.Defined.ResourcesSubscribe.value to SubscribeRequest.serializer(), + Method.Defined.ResourcesUnsubscribe.value to UnsubscribeRequest.serializer(), + Method.Defined.ResourcesTemplatesList.value to ListResourceTemplatesRequest.serializer(), + Method.Defined.ToolsCall.value to CallToolRequest.serializer(), + Method.Defined.ToolsList.value to ListToolsRequest.serializer(), + ) +} + +/** + * Selects the appropriate deserializer for client requests based on the method name. + * Returns null if the method is not a known client request method. + */ +internal fun selectClientRequestDeserializer(method: String): DeserializationStrategy? = + clientRequestDeserializers[method] + +private val serverRequestDeserializers: Map> by lazy { + mapOf( + Method.Defined.ElicitationCreate.value to ElicitRequest.serializer(), + Method.Defined.Ping.value to PingRequest.serializer(), + Method.Defined.RootsList.value to ListRootsRequest.serializer(), + Method.Defined.SamplingCreateMessage.value to CreateMessageRequest.serializer(), + ) +} + +/** + * Selects the appropriate deserializer for server requests based on the method name. + * Returns null if the method is not a known server request method. + */ +internal fun selectServerRequestDeserializer(method: String): DeserializationStrategy? = + serverRequestDeserializers[method] + +/** + * Polymorphic serializer for [Request] types. + * Supports both client and server requests, as well as custom requests. + */ +internal object RequestPolymorphicSerializer : JsonContentPolymorphicSerializer(Request::class) { + override fun selectDeserializer(element: JsonElement): DeserializationStrategy { + val method = element.getMethodOrNull() ?: run { + logger.error { "Missing 'method' for Request: $element" } + throw SerializationException("Missing 'method' for Request: $element") + } + + return selectClientRequestDeserializer(method) + ?: selectServerRequestDeserializer(method) + ?: CustomRequest.serializer() + } +} + +// ============================================================================ +// Notification Serializers +// ============================================================================ + +private val clientNotificationDeserializers: Map> by lazy { + mapOf( + Method.Defined.NotificationsCancelled.value to CancelledNotification.serializer(), + Method.Defined.NotificationsProgress.value to ProgressNotification.serializer(), + Method.Defined.NotificationsInitialized.value to InitializedNotification.serializer(), + Method.Defined.NotificationsRootsListChanged.value to RootsListChangedNotification.serializer(), + ) +} + +/** + * Selects the appropriate deserializer for client notifications based on the method name. + * Returns null if the method is not a known client notification method. + */ +private fun selectClientNotificationDeserializer(element: JsonElement): DeserializationStrategy? = + element.getMethodOrNull()?.let(clientNotificationDeserializers::get) + +private val serverNotificationDeserializers: Map> by lazy { + mapOf( + Method.Defined.NotificationsCancelled.value to CancelledNotification.serializer(), + Method.Defined.NotificationsProgress.value to ProgressNotification.serializer(), + Method.Defined.NotificationsMessage.value to LoggingMessageNotification.serializer(), + Method.Defined.NotificationsResourcesUpdated.value to ResourceUpdatedNotification.serializer(), + Method.Defined.NotificationsResourcesListChanged.value to ResourceListChangedNotification.serializer(), + Method.Defined.NotificationsToolsListChanged.value to ToolListChangedNotification.serializer(), + Method.Defined.NotificationsPromptsListChanged.value to PromptListChangedNotification.serializer(), + ) +} + +/** + * Selects the appropriate deserializer for server notifications based on the method name. + * Returns null if the method is not a known server notification method. + */ +internal fun selectServerNotificationDeserializer(element: JsonElement): DeserializationStrategy? = + element.getMethodOrNull()?.let(serverNotificationDeserializers::get) + +/** + * Polymorphic serializer for [Notification] types. + * Supports both client and server notifications, as well as custom notifications. + */ +internal object NotificationPolymorphicSerializer : + JsonContentPolymorphicSerializer(Notification::class) { + override fun selectDeserializer(element: JsonElement): DeserializationStrategy = + selectClientNotificationDeserializer(element) + ?: selectServerNotificationDeserializer(element) + ?: CustomNotification.serializer() +} + +/** + * Polymorphic serializer for [ClientNotification] types. + * Falls back to [CustomNotification] for unknown methods. + */ +internal object ClientNotificationPolymorphicSerializer : + JsonContentPolymorphicSerializer(ClientNotification::class) { + override fun selectDeserializer(element: JsonElement): DeserializationStrategy = + selectClientNotificationDeserializer(element) + ?: CustomNotification.serializer() +} + +/** + * Polymorphic serializer for [ServerNotification] types. + * Falls back to [CustomNotification] for unknown methods. + */ +internal object ServerNotificationPolymorphicSerializer : + JsonContentPolymorphicSerializer(ServerNotification::class) { + override fun selectDeserializer(element: JsonElement): DeserializationStrategy = + selectServerNotificationDeserializer(element) + ?: CustomNotification.serializer() +} + +// ============================================================================ +// Result Serializers +// ============================================================================ + +/** + * Selects the appropriate deserializer for empty results. + * Returns EmptyResult serializer if the JSON object is empty or contains only metadata. + */ +private fun selectEmptyResult(element: JsonElement): DeserializationStrategy? { + val jsonObject = element.jsonObject + return when { + jsonObject.isEmpty() || (jsonObject.size == 1 && "_meta" in jsonObject) -> EmptyResult.serializer() + else -> null + } +} + +/** + * Selects the appropriate deserializer for client results based on JSON content. + * Returns null if the structure doesn't match any known client result type. + */ +private fun selectClientResultDeserializer(element: JsonElement): DeserializationStrategy? { + val jsonObject = element.jsonObject + return when { + "model" in jsonObject && "role" in jsonObject -> CreateMessageResult.serializer() + "roots" in jsonObject -> ListRootsResult.serializer() + "action" in jsonObject -> ElicitResult.serializer() + else -> null + } +} + +/** + * Selects the appropriate deserializer for server results based on JSON content. + * Returns null if the structure doesn't match any known server result type. + */ +private fun selectServerResultDeserializer(element: JsonElement): DeserializationStrategy? { + val jsonObject = element.jsonObject + return when { + "protocolVersion" in jsonObject && "capabilities" in jsonObject -> InitializeResult.serializer() + "completion" in jsonObject -> CompleteResult.serializer() + "tools" in jsonObject -> ListToolsResult.serializer() + "resources" in jsonObject -> ListResourcesResult.serializer() + "resourceTemplates" in jsonObject -> ListResourceTemplatesResult.serializer() + "prompts" in jsonObject -> ListPromptsResult.serializer() + "messages" in jsonObject -> GetPromptResult.serializer() + "contents" in jsonObject -> ReadResourceResult.serializer() + "content" in jsonObject -> CallToolResult.serializer() + else -> null + } +} + +/** + * Polymorphic serializer for [RequestResult] types. + * Supports both client and server results. + * Throws [SerializationException] if the result type cannot be determined. + */ +internal object RequestResultPolymorphicSerializer : + JsonContentPolymorphicSerializer(RequestResult::class) { + override fun selectDeserializer(element: JsonElement): DeserializationStrategy = + selectClientResultDeserializer(element) + ?: selectServerResultDeserializer(element) + ?: selectEmptyResult(element) + ?: throw SerializationException("Cannot determine RequestResult type from JSON: ${element.jsonObject.keys}") +} + +/** + * Polymorphic serializer for [ClientResult] types. + * Throws [SerializationException] if the result type cannot be determined. + */ +internal object ClientResultPolymorphicSerializer : + JsonContentPolymorphicSerializer(ClientResult::class) { + override fun selectDeserializer(element: JsonElement): DeserializationStrategy = + selectClientResultDeserializer(element) + ?: selectEmptyResult(element) + ?: throw SerializationException("Cannot determine RequestResult type from JSON: ${element.jsonObject.keys}") +} + +/** + * Polymorphic serializer for [ServerResult] types. + * Throws [SerializationException] if the result type cannot be determined. + */ +internal object ServerResultPolymorphicSerializer : + JsonContentPolymorphicSerializer(ServerResult::class) { + override fun selectDeserializer(element: JsonElement): DeserializationStrategy = + selectServerResultDeserializer(element) + ?: selectEmptyResult(element) + ?: throw SerializationException("Cannot determine RequestResult type from JSON: ${element.jsonObject.keys}") +} + +// ============================================================================ +// JSON-RPC Serializers +// ============================================================================ + +/** + * Polymorphic serializer for [JSONRPCMessage] types. + * Determines the message type based on the presence of specific fields: + * - "error" -> JSONRPCError + * - "result" -> JSONRPCResponse + * - "method" + "id" -> JSONRPCRequest + * - "method" -> JSONRPCNotification + */ +internal object JSONRPCMessagePolymorphicSerializer : + JsonContentPolymorphicSerializer(JSONRPCMessage::class) { + override fun selectDeserializer(element: JsonElement): DeserializationStrategy { + val jsonObject = element.jsonObject + return when { + "error" in jsonObject -> JSONRPCError.serializer() + "result" in jsonObject -> JSONRPCResponse.serializer() + "method" in jsonObject && "id" in jsonObject -> JSONRPCRequest.serializer() + "method" in jsonObject -> JSONRPCNotification.serializer() + else -> throw SerializationException("Invalid JSONRPCMessage type: ${jsonObject.keys}") + } + } +} + +/** + * Polymorphic serializer for [RequestId] types. + * Supports both string and number IDs. + */ +internal object RequestIdPolymorphicSerializer : JsonContentPolymorphicSerializer(RequestId::class) { + override fun selectDeserializer(element: JsonElement): DeserializationStrategy = when (element) { + is JsonPrimitive if (element.isString) -> RequestId.StringId.serializer() + is JsonPrimitive if (element.longOrNull != null) -> RequestId.NumberId.serializer() + else -> throw SerializationException("Invalid RequestId type: $element") + } +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt new file mode 100644 index 0000000..5fbc8f5 --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt @@ -0,0 +1,250 @@ +@file:OptIn(ExperimentalSerializationApi::class) + +package io.modelcontextprotocol.kotlin.sdk.types + +import kotlinx.serialization.EncodeDefault +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonObject + +/** + * Creates a [CallToolResult] with single [TextContent] and [meta]. + */ +public fun CallToolResult.Companion.success(content: String, meta: JsonObject? = null): CallToolResult = CallToolResult( + content = listOf(TextContent(content)), + isError = false, + meta = meta, +) + +/** + * Creates a [CallToolResult] with single [TextContent] and [meta], with `isError` being true. + */ +public fun CallToolResult.Companion.error(content: String, meta: JsonObject? = null): CallToolResult = CallToolResult( + content = listOf(TextContent(content)), + isError = true, + meta = meta, +) + +/** + * Definition for a tool the client can call. + * + * Tools allow servers to expose functionality that can be invoked by clients and LLMs. + * Each tool has a name, description, and schema defining its input (and optionally output) parameters. + * + * **Display name precedence order:** [title] → [annotations].[title] → [name] + * + * @property name The programmatic identifier for this tool. + * Intended for logical use and API identification. Used as a display name fallback + * if both [title] and [annotations].[title] are not provided. + * @property inputSchema A JSON Schema object defining the expected parameters for the tool. + * Must be an object type schema. Defines what arguments the tool accepts. + * @property description A human-readable description of the tool and when to use it. + * Clients can use this to improve the LLM's understanding of available tools. + * It can be thought of like a "hint" to the model. + * @property outputSchema An optional JSON Schema object defining the structure of the tool's output + * returned in the [structuredContent][CallToolResult.structuredContent] field of a [CallToolResult]. + * Must be an object type schema if provided. + * @property title Optional human-readable display name for this tool. + * Intended for UI and end-user contexts, optimized to be easily understood + * even by those unfamiliar with domain-specific terminology. + * Note: For Tool specifically, [annotations].[title] takes precedence over this field. + * @property annotations Optional additional tool information providing hints about tool behavior. + * All properties in [ToolAnnotations] are hints and not guaranteed to provide + * a faithful description of tool behavior. + * @property icons Optional set of sized icons that clients can display in their user interface. + * Clients MUST support at least PNG and JPEG formats. + * Clients SHOULD also support SVG and WebP formats. + * @property meta Optional metadata for this tool. + */ +@Serializable +public data class Tool( + val name: String, + val inputSchema: ToolSchema, + val description: String? = null, + val outputSchema: ToolSchema? = null, + val title: String? = null, + val annotations: ToolAnnotations? = null, + val icons: List? = null, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : WithMeta + +/** + * A JSON Schema for tool input or output parameters. + * + * This is a simplified schema structure that must be of type "object". + * + * @property type Always "object" for tool schemas. + * @property properties Optional map of property names to their schema definitions. + * @property required Optional list of property names that are required. + */ +@Serializable +public data class ToolSchema(val properties: JsonObject? = null, val required: List? = null) { + @OptIn(ExperimentalSerializationApi::class) + @EncodeDefault + val type: String = "object" +} + +/** + * Additional properties describing a Tool to clients. + * + * **IMPORTANT:** All properties in ToolAnnotations are **hints**. They are NOT guaranteed to provide + * a faithful description of tool behavior (including descriptive properties like [title]). + * + * **Security warning:** Clients should NEVER make tool use decisions based on ToolAnnotations + * received from untrusted servers. + * + * @property title A human-readable title for the tool. + * Takes precedence over [Tool.title] and [Tool.name] for display purposes. + * @property readOnlyHint If true, the tool does not modify its environment. + * If false, the tool may perform modifications. + * Default: false + * @property destructiveHint If true, the tool may perform destructive updates to its environment. + * If false, the tool performs only additive updates. + * This property is meaningful only when [readOnlyHint] == false. + * Default: true + * @property idempotentHint If true, calling the tool repeatedly with the same arguments will have + * no additional effect on its environment. + * This property is meaningful only when [readOnlyHint] == false. + * Default: false + * @property openWorldHint If true, this tool may interact with an "open world" of external entities. + * If false, the tool's domain of interaction is closed. + * For example, the world of a web search tool is open, whereas that of a + * memory tool is not. + * Default: true + */ +@Serializable +public data class ToolAnnotations( + val title: String? = null, + val readOnlyHint: Boolean? = null, + val destructiveHint: Boolean? = null, + val idempotentHint: Boolean? = null, + val openWorldHint: Boolean? = null, +) + +// ============================================================================ +// tools/call +// ============================================================================ + +/** + * Used by the client to invoke a tool provided by the server. + * + * Tools allow servers to expose functionality that clients (and LLMs) can call. + * Examples include file operations, API calls, database queries, or any other + * server-side operations. + * + * @property params The parameters specifying which tool to call and what arguments to pass. + */ +@Serializable +public data class CallToolRequest(override val params: CallToolRequestParams) : ClientRequest { + @EncodeDefault + override val method: Method = Method.Defined.ToolsCall + + /** + * The name of the tool to invoke. + */ + public val name: String + get() = params.name + + /** + * Arguments to pass to the tool. Keys are argument names, values are the argument values. + * The structure must match the tool's input schema. + */ + public val arguments: JsonObject? + get() = params.arguments + + /** + * Metadata for this request. May include a progressToken for out-of-band progress notifications. + */ + public val meta: RequestMeta? + get() = params.meta +} + +/** + * Parameters for a tools/call request. + * + * @property name The name of the tool to invoke. + * @property arguments Arguments to pass to the tool. Keys are argument names, values are the argument values. + * The structure must match the tool's input schema. + * @property meta Optional metadata for this request. May include a progressToken for + * out-of-band progress notifications. + */ +@Serializable +public data class CallToolRequestParams( + val name: String, + val arguments: JsonObject? = null, + @SerialName("_meta") + override val meta: RequestMeta? = null, +) : RequestParams + +/** + * The server's response to a [CallToolRequest]. + * + * Contains the result of the tool execution, which can be successful or an error. + * + * **Important error handling:** + * - Errors that originate from the **tool itself** SHOULD be reported inside the result object + * with [isError] set to true, NOT as an MCP protocol-level error response. This allows the LLM + * to see that an error occurred and potentially self-correct. + * - Errors in **finding the tool**, unsupported operations, or other exceptional conditions + * SHOULD be reported as MCP protocol-level error responses. + * + * @property content A list of content blocks that represent the unstructured result of the tool call. + * This is what the LLM will see as the tool's output. + * @property isError Whether the tool call ended in an error. + * If not set, this is assumed to be false (the call was successful). + * When true, the [content] should describe the error that occurred. + * @property structuredContent An optional JSON object that represents the structured result of the tool call. + * Provides machine-readable output in addition to the human-readable [content]. + * @property meta Optional metadata for this response. + */ +@Serializable +public data class CallToolResult( + val content: List, + val isError: Boolean? = null, + val structuredContent: JsonObject? = null, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : ServerResult + +// ============================================================================ +// tools/list +// ============================================================================ + +/** + * Sent from the client to request a list of tools the server has. + * + * This request supports pagination through the cursor parameter. If the server has many + * tools, it may return a subset along with a cursor to fetch the next page. + * + * @property params Optional pagination parameters to control which page of results to return. + */ +@Serializable +public data class ListToolsRequest(override val params: PaginatedRequestParams? = null) : + ClientRequest, + PaginatedRequest { + @EncodeDefault + override val method: Method = Method.Defined.ToolsList +} + +/** + * The server's response to a [ListToolsRequest] from the client. + * + * Returns the available tools along with pagination information if there are more results. + * + * @property tools The list of available tools. Each tool includes its name, description, + * and input schema that defines what arguments it accepts. + * @property nextCursor An opaque token representing the pagination position after the last returned result. + * If present, there may be more results available. The client can pass this token + * in a subsequent request to fetch the next page. + * @property meta Optional metadata for this response. + */ +@Serializable +public data class ListToolsResult( + val tools: List, + override val nextCursor: String? = null, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : ServerResult, + PaginatedResult diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/CallToolResultUtilsTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaCallToolResultUtilsTest.kt similarity index 76% rename from kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/CallToolResultUtilsTest.kt rename to kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaCallToolResultUtilsTest.kt index d331b01..b7bac6d 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/CallToolResultUtilsTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaCallToolResultUtilsTest.kt @@ -7,17 +7,17 @@ import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue -class CallToolResultUtilsTest { +class OldSchemaCallToolResultUtilsTest { @Test fun testOkWithOnlyText() { val content = "TextMessage" - val result = CallToolResult.Companion.ok(content) + val result = CallToolResult.ok(content) assertEquals(1, result.content.size) assertEquals(content, (result.content[0] as TextContent).text) assertFalse(result.isError == true) - assertEquals(EmptyJsonObject, result._meta) + assertEquals(EmptyJsonObject, result.meta) } @Test @@ -27,23 +27,23 @@ class CallToolResultUtilsTest { put("key1", JsonPrimitive("value1")) put("key2", JsonPrimitive(42)) } - val result = CallToolResult.Companion.ok(content, meta) + val result = CallToolResult.ok(content, meta) assertEquals(1, result.content.size) assertEquals(content, (result.content[0] as TextContent).text) assertFalse(result.isError == true) - assertEquals(meta, result._meta) + assertEquals(meta, result.meta) } @Test fun testErrorWithOnlyText() { val content = "ErrorMessage" - val result = CallToolResult.Companion.error(content) + val result = CallToolResult.error(content) assertEquals(1, result.content.size) assertEquals(content, (result.content[0] as TextContent).text) assertTrue(result.isError == true) - assertEquals(EmptyJsonObject, result._meta) + assertEquals(EmptyJsonObject, result.meta) } @Test @@ -53,11 +53,11 @@ class CallToolResultUtilsTest { put("errorCode", JsonPrimitive(404)) put("errorDetail", JsonPrimitive("资源未找到")) } - val result = CallToolResult.Companion.error(content, meta) + val result = CallToolResult.error(content, meta) assertEquals(1, result.content.size) assertEquals(content, (result.content[0] as TextContent).text) assertTrue(result.isError == true) - assertEquals(meta, result._meta) + assertEquals(meta, result.meta) } } diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/RequestSerializerTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaRequestSerializerTest.kt similarity index 94% rename from kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/RequestSerializerTest.kt rename to kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaRequestSerializerTest.kt index 96a74dc..2360b5b 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/RequestSerializerTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaRequestSerializerTest.kt @@ -1,13 +1,18 @@ package io.modelcontextprotocol.kotlin.sdk import io.modelcontextprotocol.kotlin.sdk.shared.McpJson +import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult +import io.modelcontextprotocol.kotlin.sdk.types.CompleteResult +import io.modelcontextprotocol.kotlin.sdk.types.ElicitResult +import kotlinx.serialization.SerializationException import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.put import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFailsWith import kotlin.test.assertIs -class RequestSerializerTest { +class OldSchemaRequestSerializerTest { // Client Result Tests @Test @@ -60,7 +65,7 @@ class RequestSerializerTest { val decoded = McpJson.decodeFromString(json) - assertIs(decoded) + assertIs(decoded) assertEquals(CreateElicitationResult.Action.accept, decoded.action) } @@ -276,7 +281,7 @@ class RequestSerializerTest { "isError": false }""" - val decoded = McpJson.decodeFromString(json) + val decoded = McpJson.decodeFromString(json) assertIs(decoded) assertEquals(buildJsonObject { put("result", "Legacy tool result") }, decoded.toolResult) @@ -287,10 +292,9 @@ class RequestSerializerTest { fun `should deserialize EmptyRequestResult for unknown result type`() { val json = """{"unknownField": "value"}""" - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals(EmptyJsonObject, decoded._meta) + assertFailsWith { + McpJson.decodeFromString(json) + } } @Test diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/ToolSerializationTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaToolSerializationTest.kt similarity index 95% rename from kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/ToolSerializationTest.kt rename to kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaToolSerializationTest.kt index 969a1f3..7e96dcf 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/ToolSerializationTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaToolSerializationTest.kt @@ -2,6 +2,7 @@ package io.modelcontextprotocol.kotlin.sdk import io.kotest.assertions.json.shouldEqualJson import io.modelcontextprotocol.kotlin.sdk.shared.McpJson +import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive @@ -9,7 +10,7 @@ import kotlinx.serialization.json.buildJsonObject import kotlin.test.Test import kotlin.test.assertEquals -class ToolSerializationTest { +class OldSchemaToolSerializationTest { // see https://docs.anthropic.com/en/docs/build-with-claude/tool-use /* language=json */ @@ -262,7 +263,7 @@ class ToolSerializationTest { @Test fun `should deserialize get_weather tool`() { - val actualTool = McpJson.decodeFromString(getWeatherToolJson) + val actualTool = McpJson.decodeFromString(getWeatherToolJson) assertEquals(expected = getWeatherTool, actual = actualTool) } @@ -270,7 +271,7 @@ class ToolSerializationTest { fun `should deserialize get_weather tool without optional properties`() { val toolJson = createWeatherToolJson(name = "get_weather") val expectedTool = createWeatherTool(name = "get_weather") - val actualTool = McpJson.decodeFromString(toolJson) + val actualTool = McpJson.decodeFromString(toolJson) assertEquals(expected = expectedTool, actual = actualTool) } @@ -280,7 +281,7 @@ class ToolSerializationTest { val toolJson = createWeatherToolJson(name = "get_weather", title = "Get weather") val expectedTool = createWeatherTool(name = "get_weather", title = "Get weather") - val actualTool = McpJson.decodeFromString(toolJson) + val actualTool = McpJson.decodeFromString(toolJson) assertEquals(expected = expectedTool, actual = actualTool) } @@ -342,7 +343,7 @@ class ToolSerializationTest { ), ) - val actualTool = McpJson.decodeFromString(toolJson) + val actualTool = McpJson.decodeFromString(toolJson) assertEquals(expected = expectedTool, actual = actualTool) } @@ -405,7 +406,7 @@ class ToolSerializationTest { ), ) - val actualTool = McpJson.decodeFromString(toolJson) + val actualTool = McpJson.decodeFromString(toolJson) assertEquals(expected = expectedTool, actual = actualTool) } @@ -479,9 +480,9 @@ class ToolSerializationTest { private fun createWeatherTool( name: String = "get_weather", title: String? = null, - outputSchema: Tool.Output? = null, + outputSchema: ToolSchema? = null, @Suppress("LocalVariableName") _meta: JsonObject? = null, - ): Tool = Tool( + ): io.modelcontextprotocol.kotlin.sdk.types.Tool = Tool( name = name, title = title, description = "Get the current weather in a given location", diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/TypesTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaTypesTest.kt similarity index 78% rename from kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/TypesTest.kt rename to kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaTypesTest.kt index 96d2534..b3a4d5b 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/TypesTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaTypesTest.kt @@ -1,6 +1,9 @@ package io.modelcontextprotocol.kotlin.sdk import io.modelcontextprotocol.kotlin.sdk.shared.McpJson +import io.modelcontextprotocol.kotlin.sdk.types.CompleteRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.ContentTypes +import io.modelcontextprotocol.kotlin.sdk.types.ReferenceType import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs @@ -8,7 +11,7 @@ import kotlin.test.assertNotEquals import kotlin.test.assertTrue import kotlin.time.ExperimentalTime -class TypesTest { +class OldSchemaTypesTest { @Test fun `should have correct latest protocol version`() { @@ -18,7 +21,7 @@ class TypesTest { @Test fun `should have correct supported protocol versions`() { - assertIs>(SUPPORTED_PROTOCOL_VERSIONS) + assertIs>(SUPPORTED_PROTOCOL_VERSIONS) assertTrue(SUPPORTED_PROTOCOL_VERSIONS.contains(LATEST_PROTOCOL_VERSION)) assertTrue(SUPPORTED_PROTOCOL_VERSIONS.contains("2024-11-05")) assertEquals(2, SUPPORTED_PROTOCOL_VERSIONS.size) @@ -34,7 +37,7 @@ class TypesTest { fun `should validate ResourceTemplateReference`() { val resourceRef = ResourceTemplateReference(uri = "file:///path/to/file.txt") - assertEquals("ref/resource", resourceRef.type) + assertEquals(ReferenceType.ResourceTemplate, resourceRef.type) assertEquals("file:///path/to/file.txt", resourceRef.uri) } @@ -46,7 +49,7 @@ class TypesTest { val decoded = McpJson.decodeFromString(json) assertIs(decoded) - assertEquals("ref/resource", decoded.type) + assertEquals(ReferenceType.ResourceTemplate, decoded.type) assertEquals("https://example.com/resource", decoded.uri) } @@ -54,7 +57,7 @@ class TypesTest { fun `should validate PromptReference`() { val promptRef = PromptReference(name = "greeting") - assertEquals("ref/prompt", promptRef.type) + assertEquals(ReferenceType.Prompt, promptRef.type) assertEquals("greeting", promptRef.name) } @@ -66,26 +69,16 @@ class TypesTest { val decoded = McpJson.decodeFromString(json) assertIs(decoded) - assertEquals("ref/prompt", decoded.type) + assertEquals(ReferenceType.Prompt, decoded.type) assertEquals("test-prompt", decoded.name) } - @Test - fun `should handle UnknownReference for invalid type`() { - val invalidJson = """{"type": "invalid_type"}""" - - val decoded = McpJson.decodeFromString(invalidJson) - - assertIs(decoded) - assertEquals("invalid_type", decoded.type) - } - // PromptMessageContent Tests @Test fun `should validate text content`() { val textContent = TextContent(text = "Hello, world!") - assertEquals("text", textContent.type) + assertEquals(ContentTypes.TEXT, textContent.type) assertEquals("Hello, world!", textContent.text) } @@ -97,7 +90,7 @@ class TypesTest { val decoded = McpJson.decodeFromString(json) assertIs(decoded) - assertEquals("text", decoded.type) + assertEquals(ContentTypes.TEXT, decoded.type) assertEquals("Test message", decoded.text) } @@ -108,7 +101,7 @@ class TypesTest { mimeType = "image/png", ) - assertEquals("image", imageContent.type) + assertEquals(ContentTypes.IMAGE, imageContent.type) assertEquals("aGVsbG8=", imageContent.data) assertEquals("image/png", imageContent.mimeType) } @@ -124,7 +117,7 @@ class TypesTest { val decoded = McpJson.decodeFromString(json) assertIs(decoded) - assertEquals("image", decoded.type) + assertEquals(ContentTypes.IMAGE, decoded.type) assertEquals("dGVzdA==", decoded.data) assertEquals("image/jpeg", decoded.mimeType) } @@ -136,7 +129,7 @@ class TypesTest { mimeType = "audio/mp3", ) - assertEquals("audio", audioContent.type) + assertEquals(ContentTypes.AUDIO, audioContent.type) assertEquals("aGVsbG8=", audioContent.data) assertEquals("audio/mp3", audioContent.mimeType) } @@ -152,7 +145,7 @@ class TypesTest { val decoded = McpJson.decodeFromString(json) assertIs(decoded) - assertEquals("audio", decoded.type) + assertEquals(ContentTypes.AUDIO, decoded.type) assertEquals("YXVkaW8=", decoded.data) assertEquals("audio/wav", decoded.mimeType) } @@ -166,7 +159,7 @@ class TypesTest { ) val embeddedResource = EmbeddedResource(resource = resource) - assertEquals("resource", embeddedResource.type) + assertEquals(ContentTypes.EMBEDDED_RESOURCE, embeddedResource.type) assertEquals(resource, embeddedResource.resource) } @@ -183,7 +176,7 @@ class TypesTest { val decoded = McpJson.decodeFromString(json) assertIs(decoded) - assertEquals("resource", decoded.type) + assertEquals(ContentTypes.EMBEDDED_RESOURCE, decoded.type) assertIs(decoded.resource) val decodedBlob = decoded.resource assertEquals("YmluYXJ5ZGF0YQ==", decodedBlob.blob) @@ -191,16 +184,6 @@ class TypesTest { assertEquals("application/octet-stream", decodedBlob.mimeType) } - @Test - fun `should handle unknown content type`() { - val unknownJson = """{"type": "unknown_type"}""" - - val decoded = McpJson.decodeFromString(unknownJson) - - assertIs(decoded) - assertEquals("unknown_type", decoded.type) - } - // PromptMessage Tests @Test fun `should validate prompt message with text content`() { @@ -212,7 +195,7 @@ class TypesTest { assertEquals(Role.user, promptMessage.role) assertEquals(textContent, promptMessage.content) - assertEquals("text", promptMessage.content.type) + assertEquals(ContentTypes.TEXT, promptMessage.content.type) } @Test @@ -229,7 +212,7 @@ class TypesTest { ) assertEquals(Role.assistant, promptMessage.role) - assertEquals("resource", promptMessage.content.type) + assertEquals(ContentTypes.EMBEDDED_RESOURCE, promptMessage.content.type) val content = promptMessage.content as EmbeddedResource val textResource = content.resource as TextResourceContents assertEquals("Primary application entry point", textResource.text) @@ -299,10 +282,10 @@ class TypesTest { ) assertEquals(3, toolResult.content.size) - assertEquals("text", toolResult.content[0].type) - assertEquals("resource", toolResult.content[1].type) - assertEquals("resource", toolResult.content[2].type) - assertEquals(false, toolResult.isError) + assertEquals(ContentTypes.TEXT, toolResult.content[0].type) + assertEquals(ContentTypes.EMBEDDED_RESOURCE, toolResult.content[1].type) + assertEquals(ContentTypes.EMBEDDED_RESOURCE, toolResult.content[2].type) + assertEquals(null, toolResult.isError) } @Test @@ -310,7 +293,7 @@ class TypesTest { val toolResult = CallToolResult(content = emptyList()) assertEquals(0, toolResult.content.size) - assertEquals(false, toolResult.isError) + assertEquals(null, toolResult.isError) } @Test @@ -324,7 +307,7 @@ class TypesTest { ) val json = McpJson.encodeToString(toolResult) - val decoded = McpJson.decodeFromString(json) + val decoded = McpJson.decodeFromString(json) assertEquals(2, decoded.content.size) assertIs(decoded.content[0]) @@ -336,62 +319,70 @@ class TypesTest { @Test fun `should validate CompleteRequest with prompt reference`() { val request = CompleteRequest( - ref = PromptReference(name = "greeting"), - argument = CompleteRequest.Argument(name = "name", value = "A"), + CompleteRequestParams( + ref = PromptReference(name = "greeting"), + argument = CompleteRequest.Argument(name = "name", value = "A"), + ), ) assertEquals("completion/complete", request.method.value) - assertIs(request.ref) - val promptRef = request.ref + assertIs(request.params.ref) + val promptRef = request.params.ref assertEquals("greeting", promptRef.name) - assertEquals("name", request.argument.name) - assertEquals("A", request.argument.value) + assertEquals("name", request.params.argument.name) + assertEquals("A", request.params.argument.value) } @Test fun `should validate CompleteRequest with resource reference`() { val request = CompleteRequest( - ref = ResourceTemplateReference(uri = "github://repos/{owner}/{repo}"), - argument = CompleteRequest.Argument(name = "repo", value = "t"), + CompleteRequestParams( + ref = ResourceTemplateReference(uri = "github://repos/{owner}/{repo}"), + argument = CompleteRequest.Argument(name = "repo", value = "t"), + ), ) assertEquals("completion/complete", request.method.value) - assertIs(request.ref) - val resourceRef = request.ref + assertIs(request.params.ref) + val resourceRef = request.params.ref assertEquals("github://repos/{owner}/{repo}", resourceRef.uri) - assertEquals("repo", request.argument.name) - assertEquals("t", request.argument.value) + assertEquals("repo", request.params.argument.name) + assertEquals("t", request.params.argument.value) } @Test fun `should serialize and deserialize CompleteRequest correctly`() { val request = CompleteRequest( - ref = PromptReference(name = "test"), - argument = CompleteRequest.Argument(name = "arg", value = ""), + CompleteRequestParams( + ref = PromptReference(name = "test"), + argument = CompleteRequest.Argument(name = "arg", value = ""), + ), ) val json = McpJson.encodeToString(request) - val decoded = McpJson.decodeFromString(json) + val decoded = McpJson.decodeFromString(json) assertEquals("completion/complete", decoded.method.value) - assertIs(decoded.ref) - val promptRef = decoded.ref + assertIs(decoded.params.ref) + val promptRef = decoded.params.ref assertEquals("test", promptRef.name) - assertEquals("arg", decoded.argument.name) - assertEquals("", decoded.argument.value) + assertEquals("arg", decoded.params.argument.name) + assertEquals("", decoded.params.argument.value) } @Test fun `should validate CompleteRequest with complex URIs`() { val request = CompleteRequest( - ref = ResourceTemplateReference(uri = "api://v1/{tenant}/{resource}/{id}"), - argument = CompleteRequest.Argument(name = "id", value = "123"), + CompleteRequestParams( + ref = ResourceTemplateReference(uri = "api://v1/{tenant}/{resource}/{id}"), + argument = CompleteRequest.Argument(name = "id", value = "123"), + ), ) - val resourceRef = request.ref as ResourceTemplateReference + val resourceRef = request.params.ref as ResourceTemplateReference assertEquals("api://v1/{tenant}/{resource}/{id}", resourceRef.uri) - assertEquals("id", request.argument.name) - assertEquals("123", request.argument.value) + assertEquals("id", request.params.argument.name) + assertEquals("123", request.params.argument.value) } // InitializeResult Tests @@ -399,6 +390,7 @@ class TypesTest { fun `should create InitializeResult with default instructions`() { val serverInfo = Implementation(name = "test-server", version = "1.0.0") val result = InitializeResult( + capabilities = ServerCapabilities(), serverInfo = serverInfo, ) @@ -412,6 +404,7 @@ class TypesTest { val serverInfo = Implementation(name = "test-server", version = "1.0.0") val instructions = "Use this server to perform calculations. Call the 'add' tool to add numbers." val result = InitializeResult( + capabilities = ServerCapabilities(), serverInfo = serverInfo, instructions = instructions, ) @@ -426,6 +419,7 @@ class TypesTest { val serverInfo = Implementation(name = "test-server", version = "1.0.0") val instructions = "This server provides file system access. Use the 'read' tool to read files." val result = InitializeResult( + capabilities = ServerCapabilities(), serverInfo = serverInfo, instructions = instructions, ) @@ -442,6 +436,7 @@ class TypesTest { fun `should serialize and deserialize InitializeResult without instructions`() { val serverInfo = Implementation(name = "test-server", version = "1.0.0") val result = InitializeResult( + capabilities = ServerCapabilities(), serverInfo = serverInfo, ) diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/TypesUtilTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/TypesUtilTest.kt deleted file mode 100644 index 5207af3..0000000 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/TypesUtilTest.kt +++ /dev/null @@ -1,252 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk - -import io.modelcontextprotocol.kotlin.sdk.shared.McpJson -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.put -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertIs - -class TypesUtilTest { - - // ErrorCode Serializer Tests - @Test - fun `should serialize and deserialize ErrorCode Defined correctly`() { - val errorCode: ErrorCode = ErrorCode.Defined.InvalidRequest - - val json = McpJson.encodeToString(errorCode) - val decoded = McpJson.decodeFromString(json) - - assertEquals(ErrorCode.Defined.InvalidRequest, decoded) - assertEquals(-32600, decoded.code) - } - - @Test - fun `should serialize and deserialize ErrorCode Unknown correctly`() { - val errorCode: ErrorCode = ErrorCode.Unknown(1001) - - val json = McpJson.encodeToString(errorCode) - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals(1001, decoded.code) - } - - // Method Serializer Tests - @Test - fun `should serialize and deserialize Method Defined correctly`() { - val method: Method = Method.Defined.Initialize - - val json = McpJson.encodeToString(method) - val decoded = McpJson.decodeFromString(json) - - assertEquals(Method.Defined.Initialize, decoded) - assertEquals("initialize", decoded.value) - } - - @Test - fun `should serialize and deserialize Method Custom correctly`() { - val method: Method = Method.Custom("custom/method") - - val json = McpJson.encodeToString(method) - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals("custom/method", decoded.value) - } - - // StopReason Serializer Tests - @Test - fun `should serialize and deserialize StopReason EndTurn correctly`() { - val stopReason: StopReason = StopReason.EndTurn - - val json = McpJson.encodeToString(stopReason) - val decoded = McpJson.decodeFromString(json) - - assertEquals(StopReason.EndTurn, decoded) - assertEquals("endTurn", decoded.value) - } - - @Test - fun `should serialize and deserialize StopReason Other correctly`() { - val stopReason: StopReason = StopReason.Other("custom_reason") - - val json = McpJson.encodeToString(stopReason) - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals("custom_reason", decoded.value) - } - - // Reference Polymorphic Serializer Tests - @Test - fun `should deserialize ResourceTemplateReference polymorphically`() { - val json = """{"type": "ref/resource", "uri": "file:///test.txt"}""" - - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals("ref/resource", decoded.type) - assertEquals("file:///test.txt", decoded.uri) - } - - @Test - fun `should deserialize PromptReference polymorphically`() { - val json = """{"type": "ref/prompt", "name": "test-prompt"}""" - - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals("ref/prompt", decoded.type) - assertEquals("test-prompt", decoded.name) - } - - @Test - fun `should deserialize UnknownReference for invalid type`() { - val json = """{"type": "unknown_ref", "data": "test"}""" - - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals("unknown_ref", decoded.type) - } - - // PromptMessageContent Polymorphic Serializer Tests - @Test - fun `should deserialize TextContent polymorphically`() { - val json = """{"type": "text", "text": "Hello world"}""" - - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals("text", decoded.type) - assertEquals("Hello world", decoded.text) - } - - @Test - fun `should deserialize ImageContent polymorphically`() { - val json = """{"type": "image", "data": "aW1hZ2U=", "mimeType": "image/png"}""" - - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals("image", decoded.type) - assertEquals("aW1hZ2U=", decoded.data) - assertEquals("image/png", decoded.mimeType) - } - - @Test - fun `should deserialize AudioContent polymorphically`() { - val json = """{"type": "audio", "data": "YXVkaW8=", "mimeType": "audio/mp3"}""" - - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals("audio", decoded.type) - assertEquals("YXVkaW8=", decoded.data) - assertEquals("audio/mp3", decoded.mimeType) - } - - @Test - fun `should deserialize EmbeddedResource polymorphically`() { - val json = - """{"type": "resource", "resource": {"uri": "file:///test.txt", "mimeType": "text/plain", "text": "content"}}""" - - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals("resource", decoded.type) - assertIs(decoded.resource) - val textResource = decoded.resource - assertEquals("file:///test.txt", textResource.uri) - assertEquals("content", textResource.text) - } - - // ResourceContents Polymorphic Serializer Tests - @Test - fun `should deserialize TextResourceContents polymorphically`() { - val json = """{"uri": "file:///test.txt", "mimeType": "text/plain", "text": "file content"}""" - - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals("file:///test.txt", decoded.uri) - assertEquals("file content", decoded.text) - assertEquals("text/plain", decoded.mimeType) - } - - @Test - fun `should deserialize BlobResourceContents polymorphically`() { - val json = """{"uri": "file:///binary.dat", "mimeType": "application/octet-stream", "blob": "YmluYXJ5"}""" - - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals("file:///binary.dat", decoded.uri) - assertEquals("YmluYXJ5", decoded.blob) - assertEquals("application/octet-stream", decoded.mimeType) - } - - @Test - fun `should deserialize UnknownResourceContents for missing fields`() { - val json = """{"uri": "file:///unknown.dat", "mimeType": "unknown/type"}""" - - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals("file:///unknown.dat", decoded.uri) - assertEquals("unknown/type", decoded.mimeType) - } - - // RequestId Serializer Tests - @Test - fun `should serialize and deserialize RequestId StringId correctly`() { - val requestId: RequestId = RequestId.StringId("test-id") - - val json = McpJson.encodeToString(requestId) - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals("test-id", decoded.value) - } - - @Test - fun `should serialize and deserialize RequestId NumberId correctly`() { - val requestId: RequestId = RequestId.NumberId(42L) - - val json = McpJson.encodeToString(requestId) - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals(42L, decoded.value) - } - - // Utility Functions Tests - @Test - fun `should create CallToolResult ok correctly`() { - val result = CallToolResult.ok("Success message") - - assertEquals(listOf(TextContent("Success message")), result.content) - assertEquals(false, result.isError) - assertEquals(EmptyJsonObject, result._meta) - } - - @Test - fun `should create CallToolResult error correctly`() { - val result = CallToolResult.error("Error message") - - assertEquals(listOf(TextContent("Error message")), result.content) - assertEquals(true, result.isError) - assertEquals(EmptyJsonObject, result._meta) - } - - @Test - fun `should create CallToolResult with custom meta`() { - val meta = buildJsonObject { put("custom", "value") } - val result = CallToolResult.ok("Success", meta) - - assertEquals(listOf(TextContent("Success")), result.content) - assertEquals(false, result.isError) - assertEquals(meta, result._meta) - } -} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationsTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/models/OldSchemaProgressNotificationsTest.kt similarity index 88% rename from kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationsTest.kt rename to kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/models/OldSchemaProgressNotificationsTest.kt index 33927ec..29bc12d 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationsTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/models/OldSchemaProgressNotificationsTest.kt @@ -6,7 +6,7 @@ import io.modelcontextprotocol.kotlin.sdk.RequestId import io.modelcontextprotocol.kotlin.sdk.shared.McpJson import kotlin.test.Test -class ProgressNotificationsTest { +class OldSchemaProgressNotificationsTest { /** * https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/progress#progress-flow @@ -27,7 +27,7 @@ class ProgressNotificationsTest { } """.trimIndent() - val result = McpJson.decodeFromString(json) + val result = McpJson.decodeFromString(json) result shouldBe ProgressNotification( params = ProgressNotification.Params( @@ -58,7 +58,7 @@ class ProgressNotificationsTest { } """.trimIndent() - val result = McpJson.decodeFromString(json) + val result = McpJson.decodeFromString(json) result shouldBe ProgressNotification( params = ProgressNotification.Params( progressToken = RequestId.NumberId(100500), diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CapabilitiesTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CapabilitiesTest.kt new file mode 100644 index 0000000..c288cca --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CapabilitiesTest.kt @@ -0,0 +1,636 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import io.kotest.assertions.json.shouldEqualJson +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class CapabilitiesTest { + + @Test + fun `should serialize Implementation with minimal fields`() { + val implementation = Implementation( + name = "test-server", + version = "1.0.0", + ) + val json = McpJson.encodeToString(implementation) + + json shouldEqualJson """ + { + "name": "test-server", + "version": "1.0.0" + } + """.trimIndent() + } + + @Test + fun `should serialize Implementation with all fields`() { + val implementation = Implementation( + name = "test-server", + version = "1.0.0", + title = "Test Server", + websiteUrl = "https://example.com", + icons = listOf( + Icon(src = "https://example.com/icon.png"), + ), + ) + val json = McpJson.encodeToString(implementation) + + json shouldEqualJson """ + { + "name": "test-server", + "version": "1.0.0", + "title": "Test Server", + "websiteUrl": "https://example.com", + "icons": [ + {"src": "https://example.com/icon.png"} + ] + } + """.trimIndent() + } + + @Test + fun `should deserialize Implementation from JSON`() { + val json = """ + { + "name": "test-server", + "version": "2.1.3-beta", + "title": "Test Server", + "websiteUrl": "https://example.com" + } + """.trimIndent() + + val implementation = McpJson.decodeFromString(json) + + assertEquals("test-server", implementation.name) + assertEquals("2.1.3-beta", implementation.version) + assertEquals("Test Server", implementation.title) + assertEquals("https://example.com", implementation.websiteUrl) + } + + @Test + fun `should deserialize Implementation with minimal fields from JSON`() { + val json = """ + { + "name": "minimal-server", + "version": "0.1.0" + } + """.trimIndent() + + val implementation = McpJson.decodeFromString(json) + + assertEquals("minimal-server", implementation.name) + assertEquals("0.1.0", implementation.version) + assertNull(implementation.title) + assertNull(implementation.websiteUrl) + assertNull(implementation.icons) + } + + @Test + fun `should serialize and deserialize Implementation round trip`() { + val original = Implementation( + name = "round-trip-test", + version = "1.2.3", + title = "Round Trip Test", + websiteUrl = "https://test.com", + ) + + val json = McpJson.encodeToString(original) + val decoded = McpJson.decodeFromString(json) + + assertEquals(original.name, decoded.name) + assertEquals(original.version, decoded.version) + assertEquals(original.title, decoded.title) + assertEquals(original.websiteUrl, decoded.websiteUrl) + } + + // ClientCapabilities tests + @Test + fun `should serialize empty ClientCapabilities`() { + val capabilities = ClientCapabilities() + val json = McpJson.encodeToString(capabilities) + + json shouldEqualJson "{}" + } + + @Test + fun `should serialize ClientCapabilities with sampling`() { + val capabilities = ClientCapabilities( + sampling = ClientCapabilities.sampling, + ) + val json = McpJson.encodeToString(capabilities) + + json shouldEqualJson """ + { + "sampling": {} + } + """.trimIndent() + } + + @Test + fun `should serialize ClientCapabilities with roots without listChanged`() { + val capabilities = ClientCapabilities( + roots = ClientCapabilities.Roots(), + ) + val json = McpJson.encodeToString(capabilities) + + json shouldEqualJson """ + { + "roots": {} + } + """.trimIndent() + } + + @Test + fun `should serialize ClientCapabilities with roots with listChanged`() { + val capabilities = ClientCapabilities( + roots = ClientCapabilities.Roots(listChanged = true), + ) + val json = McpJson.encodeToString(capabilities) + + json shouldEqualJson """ + { + "roots": { + "listChanged": true + } + } + """.trimIndent() + } + + @Test + fun `should serialize ClientCapabilities with elicitation`() { + val capabilities = ClientCapabilities( + elicitation = ClientCapabilities.elicitation, + ) + val json = McpJson.encodeToString(capabilities) + + json shouldEqualJson """ + { + "elicitation": {} + } + """.trimIndent() + } + + @Test + fun `should serialize ClientCapabilities with experimental`() { + val experimental = buildJsonObject { + put( + "customFeature", + buildJsonObject { + put("enabled", true) + }, + ) + } + val capabilities = ClientCapabilities( + experimental = experimental, + ) + val json = McpJson.encodeToString(capabilities) + + json shouldEqualJson """ + { + "experimental": { + "customFeature": { + "enabled": true + } + } + } + """.trimIndent() + } + + @Test + fun `should serialize ClientCapabilities with all fields`() { + val experimental = buildJsonObject { + put("feature1", buildJsonObject { put("enabled", true) }) + } + val capabilities = ClientCapabilities( + sampling = ClientCapabilities.sampling, + roots = ClientCapabilities.Roots(listChanged = true), + elicitation = ClientCapabilities.elicitation, + experimental = experimental, + ) + val json = McpJson.encodeToString(capabilities) + + json shouldEqualJson """ + { + "sampling": {}, + "roots": { + "listChanged": true + }, + "elicitation": {}, + "experimental": { + "feature1": { + "enabled": true + } + } + } + """.trimIndent() + } + + @Test + fun `should deserialize ClientCapabilities from JSON`() { + val json = """ + { + "sampling": {}, + "roots": { + "listChanged": true + }, + "elicitation": {} + } + """.trimIndent() + + val capabilities = McpJson.decodeFromString(json) + + assertEquals(EmptyJsonObject, capabilities.sampling) + assertEquals(true, capabilities.roots?.listChanged) + assertEquals(EmptyJsonObject, capabilities.elicitation) + } + + @Test + fun `should deserialize empty ClientCapabilities from JSON`() { + val json = "{}" + + val capabilities = McpJson.decodeFromString(json) + + assertNull(capabilities.sampling) + assertNull(capabilities.roots) + assertNull(capabilities.elicitation) + assertNull(capabilities.experimental) + } + + @Test + fun `should serialize and deserialize ClientCapabilities round trip`() { + val original = ClientCapabilities( + sampling = ClientCapabilities.sampling, + roots = ClientCapabilities.Roots(listChanged = false), + elicitation = ClientCapabilities.elicitation, + ) + + val json = McpJson.encodeToString(original) + val decoded = McpJson.decodeFromString(json) + + assertEquals(original.sampling, decoded.sampling) + assertEquals(original.roots?.listChanged, decoded.roots?.listChanged) + assertEquals(original.elicitation, decoded.elicitation) + } + + // ServerCapabilities tests + @Test + fun `should serialize empty ServerCapabilities`() { + val capabilities = ServerCapabilities() + val json = McpJson.encodeToString(capabilities) + + json shouldEqualJson "{}" + } + + @Test + fun `should serialize ServerCapabilities with tools without listChanged`() { + val capabilities = ServerCapabilities( + tools = ServerCapabilities.Tools(), + ) + val json = McpJson.encodeToString(capabilities) + + json shouldEqualJson """ + { + "tools": {} + } + """.trimIndent() + } + + @Test + fun `should serialize ServerCapabilities with tools with listChanged`() { + val capabilities = ServerCapabilities( + tools = ServerCapabilities.Tools(listChanged = true), + ) + val json = McpJson.encodeToString(capabilities) + + json shouldEqualJson """ + { + "tools": { + "listChanged": true + } + } + """.trimIndent() + } + + @Test + fun `should serialize ServerCapabilities with resources without options`() { + val capabilities = ServerCapabilities( + resources = ServerCapabilities.Resources(), + ) + val json = McpJson.encodeToString(capabilities) + + json shouldEqualJson """ + { + "resources": {} + } + """.trimIndent() + } + + @Test + fun `should serialize ServerCapabilities with resources with listChanged`() { + val capabilities = ServerCapabilities( + resources = ServerCapabilities.Resources(listChanged = true), + ) + val json = McpJson.encodeToString(capabilities) + + json shouldEqualJson """ + { + "resources": { + "listChanged": true + } + } + """.trimIndent() + } + + @Test + fun `should serialize ServerCapabilities with resources with subscribe`() { + val capabilities = ServerCapabilities( + resources = ServerCapabilities.Resources(subscribe = true), + ) + val json = McpJson.encodeToString(capabilities) + + json shouldEqualJson """ + { + "resources": { + "subscribe": true + } + } + """.trimIndent() + } + + @Test + fun `should serialize ServerCapabilities with resources with both options`() { + val capabilities = ServerCapabilities( + resources = ServerCapabilities.Resources( + listChanged = true, + subscribe = false, + ), + ) + val json = McpJson.encodeToString(capabilities) + + json shouldEqualJson """ + { + "resources": { + "listChanged": true, + "subscribe": false + } + } + """.trimIndent() + } + + @Test + fun `should serialize ServerCapabilities with prompts without listChanged`() { + val capabilities = ServerCapabilities( + prompts = ServerCapabilities.Prompts(), + ) + val json = McpJson.encodeToString(capabilities) + + json shouldEqualJson """ + { + "prompts": {} + } + """.trimIndent() + } + + @Test + fun `should serialize ServerCapabilities with prompts with listChanged`() { + val capabilities = ServerCapabilities( + prompts = ServerCapabilities.Prompts(listChanged = false), + ) + val json = McpJson.encodeToString(capabilities) + + json shouldEqualJson """ + { + "prompts": { + "listChanged": false + } + } + """.trimIndent() + } + + @Test + fun `should serialize ServerCapabilities with logging`() { + val capabilities = ServerCapabilities( + logging = ServerCapabilities.Logging, + ) + val json = McpJson.encodeToString(capabilities) + + json shouldEqualJson """ + { + "logging": {} + } + """.trimIndent() + } + + @Test + fun `should serialize ServerCapabilities with completions`() { + val capabilities = ServerCapabilities( + completions = ServerCapabilities.Completions, + ) + val json = McpJson.encodeToString(capabilities) + + json shouldEqualJson """ + { + "completions": {} + } + """.trimIndent() + } + + @Test + fun `should serialize ServerCapabilities with experimental`() { + val experimental = buildJsonObject { + put( + "customCapability", + buildJsonObject { + put("version", "1.0") + }, + ) + } + val capabilities = ServerCapabilities( + experimental = experimental, + ) + val json = McpJson.encodeToString(capabilities) + + json shouldEqualJson """ + { + "experimental": { + "customCapability": { + "version": "1.0" + } + } + } + """.trimIndent() + } + + @Test + fun `should serialize ServerCapabilities with all fields`() { + val experimental = buildJsonObject { + put("feature1", buildJsonObject { put("enabled", true) }) + } + val capabilities = ServerCapabilities( + tools = ServerCapabilities.Tools(listChanged = true), + resources = ServerCapabilities.Resources( + listChanged = true, + subscribe = true, + ), + prompts = ServerCapabilities.Prompts(listChanged = false), + logging = ServerCapabilities.Logging, + completions = ServerCapabilities.Completions, + experimental = experimental, + ) + val json = McpJson.encodeToString(capabilities) + + json shouldEqualJson """ + { + "tools": { + "listChanged": true + }, + "resources": { + "listChanged": true, + "subscribe": true + }, + "prompts": { + "listChanged": false + }, + "logging": {}, + "completions": {}, + "experimental": { + "feature1": { + "enabled": true + } + } + } + """.trimIndent() + } + + @Test + fun `should deserialize ServerCapabilities from JSON`() { + val json = """ + { + "tools": { + "listChanged": true + }, + "resources": { + "listChanged": false, + "subscribe": true + }, + "prompts": { + "listChanged": true + }, + "logging": {}, + "completions": {} + } + """.trimIndent() + + val capabilities = McpJson.decodeFromString(json) + + assertEquals(true, capabilities.tools?.listChanged) + assertEquals(false, capabilities.resources?.listChanged) + assertEquals(true, capabilities.resources?.subscribe) + assertEquals(true, capabilities.prompts?.listChanged) + assertEquals(EmptyJsonObject, capabilities.logging) + assertEquals(EmptyJsonObject, capabilities.completions) + } + + @Test + fun `should deserialize empty ServerCapabilities from JSON`() { + val json = "{}" + + val capabilities = McpJson.decodeFromString(json) + + assertNull(capabilities.tools) + assertNull(capabilities.resources) + assertNull(capabilities.prompts) + assertNull(capabilities.logging) + assertNull(capabilities.completions) + assertNull(capabilities.experimental) + } + + @Test + fun `should serialize and deserialize ServerCapabilities round trip`() { + val original = ServerCapabilities( + tools = ServerCapabilities.Tools(listChanged = true), + resources = ServerCapabilities.Resources( + listChanged = false, + subscribe = true, + ), + prompts = ServerCapabilities.Prompts(listChanged = true), + logging = ServerCapabilities.Logging, + ) + + val json = McpJson.encodeToString(original) + val decoded = McpJson.decodeFromString(json) + + assertEquals(original.tools?.listChanged, decoded.tools?.listChanged) + assertEquals(original.resources?.listChanged, decoded.resources?.listChanged) + assertEquals(original.resources?.subscribe, decoded.resources?.subscribe) + assertEquals(original.prompts?.listChanged, decoded.prompts?.listChanged) + assertEquals(original.logging, decoded.logging) + } + + // Additional nested type tests + @Test + fun `should deserialize ClientCapabilities Roots with null listChanged`() { + val json = """ + { + "roots": {} + } + """.trimIndent() + + val capabilities = McpJson.decodeFromString(json) + + assertNull(capabilities.roots?.listChanged) + } + + @Test + fun `should deserialize ServerCapabilities nested types with null values`() { + val json = """ + { + "tools": {}, + "resources": {}, + "prompts": {} + } + """.trimIndent() + + val capabilities = McpJson.decodeFromString(json) + + assertNull(capabilities.tools?.listChanged) + assertNull(capabilities.resources?.listChanged) + assertNull(capabilities.resources?.subscribe) + assertNull(capabilities.prompts?.listChanged) + } + + @Test + fun `should handle ClientCapabilities with additionalProperties in sampling`() { + val json = """ + { + "sampling": { + "customProperty": "customValue" + } + } + """.trimIndent() + + val capabilities = McpJson.decodeFromString(json) + + // Should not fail - additionalProperties are allowed + assertEquals("customValue", capabilities.sampling?.get("customProperty")?.toString()?.trim('"')) + } + + @Test + fun `should handle ServerCapabilities with additionalProperties in logging`() { + val json = """ + { + "logging": { + "level": "debug" + } + } + """.trimIndent() + + val capabilities = McpJson.decodeFromString(json) + + // Should not fail - additionalProperties are allowed + assertEquals("debug", capabilities.logging?.get("level")?.toString()?.trim('"')) + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CommonTypeTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CommonTypeTest.kt new file mode 100644 index 0000000..6247a49 --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CommonTypeTest.kt @@ -0,0 +1,184 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import io.kotest.assertions.json.shouldEqualJson +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertIs +import kotlin.test.assertNotEquals +import kotlin.test.assertNull +import kotlin.test.assertTrue + +class CommonTypeTest { + + @Test + fun `should have correct latest protocol version`() { + assertNotEquals("", LATEST_PROTOCOL_VERSION) + assertEquals("2025-03-26", LATEST_PROTOCOL_VERSION) + } + + @Test + fun `should have correct supported protocol versions`() { + assertIs>(SUPPORTED_PROTOCOL_VERSIONS) + assertTrue(SUPPORTED_PROTOCOL_VERSIONS.contains(LATEST_PROTOCOL_VERSION)) + assertTrue(SUPPORTED_PROTOCOL_VERSIONS.contains("2024-11-05")) + assertEquals(2, SUPPORTED_PROTOCOL_VERSIONS.size) + } + + @Test + fun `should serialize Icon with minimal fields`() { + val icon = Icon(src = "https://example.com/icon.png") + val json = McpJson.encodeToString(icon) + + json shouldEqualJson """ + { + "src": "https://example.com/icon.png" + } + """.trimIndent() + } + + @Test + fun `should serialize Icon with all fields`() { + val icon = Icon( + src = "https://example.com/icon.png", + mimeType = "image/png", + sizes = listOf("48x48", "96x96"), + theme = Icon.Theme.Light, + ) + val json = McpJson.encodeToString(icon) + + json shouldEqualJson """ + { + "src": "https://example.com/icon.png", + "mimeType": "image/png", + "sizes": ["48x48", "96x96"], + "theme": "light" + } + """.trimIndent() + } + + @Test + fun `should deserialize Icon from JSON`() { + val json = """ + { + "src": "https://example.com/icon.png", + "mimeType": "image/png", + "sizes": ["48x48"], + "theme": "dark" + } + """.trimIndent() + + val icon = McpJson.decodeFromString(json) + + assertEquals("https://example.com/icon.png", icon.src) + assertEquals("image/png", icon.mimeType) + assertEquals(listOf("48x48"), icon.sizes) + assertEquals(Icon.Theme.Dark, icon.theme) + } + + @Test + fun `should deserialize Icon with minimal fields from JSON`() { + val json = """{"src": "https://example.com/icon.png"}""" + + val icon = McpJson.decodeFromString(json) + + assertEquals("https://example.com/icon.png", icon.src) + assertNull(icon.mimeType) + assertNull(icon.sizes) + assertNull(icon.theme) + } + + @Test + fun `should serialize theme as lowercase strings`() { + val lightIcon = Icon(src = "test.png", theme = Icon.Theme.Light) + val darkIcon = Icon(src = "test.png", theme = Icon.Theme.Dark) + + val lightJson = McpJson.encodeToString(lightIcon) + val darkJson = McpJson.encodeToString(darkIcon) + + assertTrue(lightJson.contains("\"theme\":\"light\"")) + assertTrue(darkJson.contains("\"theme\":\"dark\"")) + } + + @Test + fun `should serialize Role as lowercase strings`() { + val userJson = McpJson.encodeToString(Role.User) + val assistantJson = McpJson.encodeToString(Role.Assistant) + + assertEquals("\"user\"", userJson) + assertEquals("\"assistant\"", assistantJson) + } + + @Test + fun `should deserialize Role from lowercase strings`() { + val user = McpJson.decodeFromString("\"user\"") + val assistant = McpJson.decodeFromString("\"assistant\"") + + assertEquals(Role.User, user) + assertEquals(Role.Assistant, assistant) + } + + @Test + fun `should serialize Annotations with all fields`() { + val annotations = Annotations( + audience = listOf(Role.User, Role.Assistant), + priority = 0.8, + lastModified = "2025-01-12T15:00:58Z", + ) + + val json = McpJson.encodeToString(annotations) + + json shouldEqualJson """ + { + "audience": ["user", "assistant"], + "priority": 0.8, + "lastModified": "2025-01-12T15:00:58Z" + } + """.trimIndent() + } + + @Test + fun `should serialize Annotations with minimal fields`() { + val annotations = Annotations(priority = 0.5) + + val json = McpJson.encodeToString(annotations) + + json shouldEqualJson """ + { + "priority": 0.5 + } + """.trimIndent() + } + + @Test + fun `should deserialize Annotations from JSON`() { + val json = """ + { + "audience": ["user"], + "priority": 0.9, + "lastModified": "2025-01-12T15:00:58Z" + } + """.trimIndent() + + val annotations = McpJson.decodeFromString(json) + + assertEquals(listOf(Role.User), annotations.audience) + assertEquals(0.9, annotations.priority) + assertEquals("2025-01-12T15:00:58Z", annotations.lastModified) + } + + @Test + fun `should serialize and deserialize Annotations round trip`() { + val original = Annotations( + audience = listOf(Role.User, Role.Assistant), + priority = 0.42, + lastModified = "2025-01-12T15:00:58Z", + ) + + val json = McpJson.encodeToString(original) + val decoded = McpJson.decodeFromString(json) + + assertEquals(original.audience, decoded.audience) + assertEquals(original.priority, decoded.priority) + assertEquals(original.lastModified, decoded.lastModified) + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CompletionTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CompletionTest.kt new file mode 100644 index 0000000..9a27ac5 --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CompletionTest.kt @@ -0,0 +1,227 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import io.kotest.assertions.json.shouldEqualJson +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertIs +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +class CompletionTest { + + @Test + fun `should serialize CompleteRequest with minimal fields`() { + val request = CompleteRequest( + CompleteRequestParams( + argument = CompleteRequestParams.Argument( + name = "name", + value = "A", + ), + ref = PromptReference(name = "greeting"), + ), + ) + + val json = McpJson.encodeToString(request) + + json shouldEqualJson """ + { + "method": "completion/complete", + "params": { + "argument": { + "name": "name", + "value": "A" + }, + "ref": { + "type": "ref/prompt", + "name": "greeting" + } + } + } + """.trimIndent() + } + + @Test + fun `should serialize CompleteRequest with context and meta`() { + val request = CompleteRequest( + CompleteRequestParams( + argument = CompleteRequestParams.Argument( + name = "repo", + value = "mcp", + ), + ref = ResourceTemplateReference(uri = "github://repos/{owner}/{repo}"), + context = CompleteRequestParams.Context( + arguments = mapOf( + "owner" to "modelcontextprotocol", + "language" to "kotlin", + ), + ), + meta = RequestMeta( + buildJsonObject { + put("progressToken", "token-123") + }, + ), + ), + ) + + val json = McpJson.encodeToString(request) + + json shouldEqualJson """ + { + "method": "completion/complete", + "params": { + "argument": { + "name": "repo", + "value": "mcp" + }, + "ref": { + "type": "ref/resource", + "uri": "github://repos/{owner}/{repo}" + }, + "context": { + "arguments": { + "owner": "modelcontextprotocol", + "language": "kotlin" + } + }, + "_meta": { + "progressToken": "token-123" + } + } + } + """.trimIndent() + } + + @Test + fun `should deserialize CompleteRequest from JSON`() { + val json = """ + { + "method": "completion/complete", + "params": { + "argument": { + "name": "file", + "value": "mai" + }, + "ref": { + "type": "ref/resource", + "uri": "file:///{path}" + }, + "context": { + "arguments": { + "path": "src/main" + } + }, + "_meta": { + "progressToken": 42 + } + } + } + """.trimIndent() + + val request = McpJson.decodeFromString(json) + assertEquals(Method.Defined.CompletionComplete, request.method) + + val params = request.params + assertEquals("file", params.argument.name) + assertEquals("mai", params.argument.value) + val ref = params.ref + assertIs(ref) + assertEquals("file:///{path}", ref.uri) + assertNotNull(params.context) + assertEquals(mapOf("path" to "src/main"), params.context.arguments) + assertEquals(ProgressToken(42), params.meta?.progressToken) + } + + @Test + fun `should serialize CompleteResult with all fields`() { + val result = CompleteResult( + completion = CompleteResult.Completion( + values = listOf("src/main/kotlin/App.kt", "src/main/kotlin/Main.kt"), + total = 25, + hasMore = true, + ), + meta = buildJsonObject { + put("source", "cache") + }, + ) + + val json = McpJson.encodeToString(result) + + json shouldEqualJson """ + { + "completion": { + "values": [ + "src/main/kotlin/App.kt", + "src/main/kotlin/Main.kt" + ], + "total": 25, + "hasMore": true + }, + "_meta": { + "source": "cache" + } + } + """.trimIndent() + } + + @Test + fun `should deserialize CompleteResult from JSON`() { + val json = """ + { + "completion": { + "values": ["README.md", "CONTRIBUTING.md"], + "total": 2, + "hasMore": false + }, + "_meta": { + "fetchedAt": "2025-01-12T15:00:58Z" + } + } + """.trimIndent() + + val result = McpJson.decodeFromString(json) + assertEquals(listOf("README.md", "CONTRIBUTING.md"), result.completion.values) + assertEquals(2, result.completion.total) + assertEquals(false, result.completion.hasMore) + assertNotNull(result.meta) + assertEquals( + "2025-01-12T15:00:58Z", + result.meta["fetchedAt"]?.jsonPrimitive?.content, + ) + } + + @Test + fun `should allow omitting optional Total and HasMore`() { + val result = CompleteResult( + completion = CompleteResult.Completion(values = listOf("foo")), + meta = null, + ) + + val json = McpJson.encodeToString(result) + json shouldEqualJson """ + { + "completion": { + "values": ["foo"] + } + } + """.trimIndent() + + val decoded = McpJson.decodeFromString(json) + assertEquals(listOf("foo"), decoded.completion.values) + assertNull(decoded.completion.total) + assertNull(decoded.completion.hasMore) + assertNull(decoded.meta) + } + + @Test + fun `should enforce maximum of 100 completion values`() { + val values = (1..101).map { "entry-$it" } + + assertFailsWith { + CompleteResult.Completion(values = values) + } + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ContentTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ContentTest.kt new file mode 100644 index 0000000..2290dce --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ContentTest.kt @@ -0,0 +1,301 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import io.kotest.assertions.json.shouldEqualJson +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertIs +import kotlin.test.assertNull + +class ContentTest { + + @Test + fun `should serialize TextContent with minimal fields`() { + val content = TextContent(text = "Hello, MCP!") + + val json = McpJson.encodeToString(content) + + json shouldEqualJson """ + { + "type": "text", + "text": "Hello, MCP!" + } + """.trimIndent() + } + + @Test + fun `should serialize and deserialize TextContent with annotations`() { + val content = TextContent( + text = "Need help?", + annotations = Annotations(audience = listOf(Role.User)), + meta = buildJsonObject { put("origin", "assistant") }, + ) + + val json = McpJson.encodeToString(content) + + json shouldEqualJson """ + { + "type": "text", + "text": "Need help?", + "annotations": { + "audience": ["user"] + }, + "_meta": { + "origin": "assistant" + } + } + """.trimIndent() + + val decoded = McpJson.decodeFromString(json) as TextContent + assertEquals("Need help?", decoded.text) + assertEquals(listOf(Role.User), decoded.annotations?.audience) + assertEquals("assistant", decoded.meta?.get("origin")?.jsonPrimitive?.content) + } + + @Test + fun `should serialize ImageContent`() { + val content = ImageContent( + data = "aW1hZ2VEYXRh", + mimeType = "image/png", + ) + + val json = McpJson.encodeToString(content) + + json shouldEqualJson """ + { + "type": "image", + "data": "aW1hZ2VEYXRh", + "mimeType": "image/png" + } + """.trimIndent() + } + + @Test + fun `should deserialize ImageContent from JSON`() { + val json = """ + { + "type": "image", + "data": "Zm9vYmFy", + "mimeType": "image/jpeg", + "annotations": {"priority": 0.6} + } + """.trimIndent() + + val content = McpJson.decodeFromString(json) as ImageContent + assertEquals("Zm9vYmFy", content.data) + assertEquals("image/jpeg", content.mimeType) + assertEquals(0.6, content.annotations?.priority) + assertNull(content.meta) + } + + @Test + fun `should serialize AudioContent`() { + val content = AudioContent( + data = "YXVkaW9kYXRh", + mimeType = "audio/wav", + meta = buildJsonObject { put("durationMs", 1200) }, + ) + + val json = McpJson.encodeToString(content) + + json shouldEqualJson """ + { + "type": "audio", + "data": "YXVkaW9kYXRh", + "mimeType": "audio/wav", + "_meta": { + "durationMs": 1200 + } + } + """.trimIndent() + } + + @Test + fun `should deserialize AudioContent from JSON`() { + val json = """ + { + "type": "audio", + "data": "YmF6", + "mimeType": "audio/mpeg", + "_meta": {"speaker": "user"} + } + """.trimIndent() + + val content = McpJson.decodeFromString(json) as AudioContent + assertEquals("audio/mpeg", content.mimeType) + assertEquals("YmF6", content.data) + assertEquals("user", content.meta?.get("speaker")?.jsonPrimitive?.content) + } + + @Test + fun `should serialize ResourceLink with minimal fields`() { + val resource = ResourceLink( + name = "README", + uri = "file:///workspace/README.md", + ) + + val json = McpJson.encodeToString(resource) + + json shouldEqualJson """ + { + "type": "resource_link", + "name": "README", + "uri": "file:///workspace/README.md" + } + """.trimIndent() + } + + @Test + fun `should serialize ResourceLink with all fields`() { + val resource = ResourceLink( + name = "README", + uri = "file:///workspace/README.md", + title = "Workspace README", + size = 2048, + mimeType = "text/markdown", + description = "Primary documentation", + icons = listOf(Icon(src = "https://example.com/icon.png")), + annotations = Annotations(priority = 0.75), + meta = buildJsonObject { put("etag", "1234") }, + ) + + val json = McpJson.encodeToString(resource) + + json shouldEqualJson """ + { + "type": "resource_link", + "name": "README", + "uri": "file:///workspace/README.md", + "title": "Workspace README", + "size": 2048, + "mimeType": "text/markdown", + "description": "Primary documentation", + "icons": [ + { + "src": "https://example.com/icon.png" + } + ], + "annotations": { + "priority": 0.75 + }, + "_meta": { + "etag": "1234" + } + } + """.trimIndent() + } + + @Test + fun `should deserialize ResourceLink from JSON`() { + val json = """ + { + "type": "resource_link", + "name": "config", + "uri": "file:///config.yaml", + "size": 512, + "annotations": { "lastModified": "2025-01-12T15:00:58Z" } + } + """.trimIndent() + + val resource = McpJson.decodeFromString(json) as ResourceLink + assertEquals("config", resource.name) + assertEquals("file:///config.yaml", resource.uri) + assertEquals(512, resource.size) + assertEquals("2025-01-12T15:00:58Z", resource.annotations?.lastModified) + assertNull(resource.meta) + } + + @Test + fun `should serialize EmbeddedResource with text contents`() { + val embedded = EmbeddedResource( + resource = TextResourceContents( + text = "fun main() = println(\"Hello\")", + uri = "file:///workspace/Main.kt", + mimeType = "text/x-kotlin", + meta = buildJsonObject { put("languageId", "kotlin") }, + ), + annotations = Annotations(audience = listOf(Role.Assistant)), + meta = buildJsonObject { put("source", "analysis") }, + ) + + val json = McpJson.encodeToString(embedded) + + json shouldEqualJson """ + { + "type": "resource", + "resource": { + "text": "fun main() = println(\"Hello\")", + "uri": "file:///workspace/Main.kt", + "mimeType": "text/x-kotlin", + "_meta": { + "languageId": "kotlin" + } + }, + "annotations": { + "audience": ["assistant"] + }, + "_meta": { + "source": "analysis" + } + } + """.trimIndent() + } + + @Test + fun `should deserialize EmbeddedResource with blob contents`() { + val json = """ + { + "type": "resource", + "resource": { + "blob": "YmFzZTY0", + "uri": "file:///workspace/archive.bin", + "mimeType": "application/octet-stream" + }, + "_meta": {"encoding": "base64"} + } + """.trimIndent() + + val embedded = McpJson.decodeFromString(json) as EmbeddedResource + assertEquals(ContentTypes.EMBEDDED_RESOURCE, embedded.type) + assertNull(embedded.annotations) + assertEquals("base64", embedded.meta?.get("encoding")?.jsonPrimitive?.content) + + val resource = embedded.resource + assertIs(resource) + assertEquals("YmFzZTY0", resource.blob) + assertEquals("file:///workspace/archive.bin", resource.uri) + assertEquals("application/octet-stream", resource.mimeType) + } + + @Test + fun `should decode heterogeneous content blocks`() { + val json = """ + [ + { + "type": "text", + "text": "Hello" + }, + { + "type": "resource_link", + "name": "log", + "uri": "file:///tmp/log.txt" + }, + { + "type": "resource", + "resource": { + "text": "line1", + "uri": "file:///tmp/log.txt" + } + } + ] + """.trimIndent() + + val content = McpJson.decodeFromString>(json) + assertEquals(3, content.size) + assertIs(content[0]) + assertIs(content[1]) + assertIs(content[2]) + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ElicitationTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ElicitationTest.kt new file mode 100644 index 0000000..c59c23f --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ElicitationTest.kt @@ -0,0 +1,206 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import io.kotest.assertions.json.shouldEqualJson +import kotlinx.serialization.json.boolean +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +class ElicitationTest { + + @Test + fun `should serialize ElicitRequest with requested schema`() { + val request = ElicitRequest( + ElicitRequestParams( + message = "Provide repository details", + requestedSchema = ElicitRequestParams.RequestedSchema( + properties = buildJsonObject { + put( + "owner", + buildJsonObject { + put("type", "string") + put("description", "GitHub organization") + }, + ) + put( + "repository", + buildJsonObject { + put("type", "string") + put("title", "Repository name") + }, + ) + put( + "private", + buildJsonObject { + put("type", "boolean") + put("description", "Is the repository private?") + }, + ) + }, + required = listOf("owner", "repository"), + ), + meta = RequestMeta( + buildJsonObject { + put("progressToken", "token-42") + }, + ), + ), + ) + + val json = McpJson.encodeToString(request) + + json shouldEqualJson """ + { + "method": "elicitation/create", + "params": { + "message": "Provide repository details", + "requestedSchema": { + "properties": { + "owner": { + "type": "string", + "description": "GitHub organization" + }, + "repository": { + "type": "string", + "title": "Repository name" + }, + "private": { + "type": "boolean", + "description": "Is the repository private?" + } + }, + "required": ["owner", "repository"], + "type": "object" + }, + "_meta": { + "progressToken": "token-42" + } + } + } + """.trimIndent() + } + + @Test + fun `should deserialize ElicitRequest from JSON`() { + val json = """ + { + "method": "elicitation/create", + "params": { + "message": "Tell us about the repository", + "requestedSchema": { + "properties": { + "name": { + "type": "string", + "title": "Repository name" + }, + "stars": { + "type": "number", + "description": "GitHub stars" + } + }, + "type": "object" + } + } + } + """.trimIndent() + + val request = McpJson.decodeFromString(json) + assertEquals(Method.Defined.ElicitationCreate, request.method) + + val params = request.params + assertEquals("Tell us about the repository", params.message) + assertNull(params.meta) + + val schema = params.requestedSchema + assertEquals("object", schema.type) + assertNull(schema.required) + + val nameDefinition = schema.properties["name"]?.jsonObject + assertNotNull(nameDefinition) + assertEquals("string", nameDefinition["type"]?.jsonPrimitive?.content) + assertEquals("Repository name", nameDefinition["title"]?.jsonPrimitive?.content) + + val starsDefinition = schema.properties["stars"]?.jsonObject + assertNotNull(starsDefinition) + assertEquals("number", starsDefinition["type"]?.jsonPrimitive?.content) + assertEquals("GitHub stars", starsDefinition["description"]?.jsonPrimitive?.content) + } + + @Test + fun `should serialize and deserialize accept result with content`() { + val result = ElicitResult( + action = ElicitResult.Action.Accept, + content = buildJsonObject { + put("repository", "kotlin-sdk") + put("stars", 128) + put("private", false) + }, + meta = buildJsonObject { + put("submittedAt", "2025-01-12T15:00:58Z") + }, + ) + + val json = McpJson.encodeToString(result) + + json shouldEqualJson """ + { + "action": "accept", + "content": { + "repository": "kotlin-sdk", + "stars": 128, + "private": false + }, + "_meta": { + "submittedAt": "2025-01-12T15:00:58Z" + } + } + """.trimIndent() + + val decoded = McpJson.decodeFromString(json) + assertEquals(ElicitResult.Action.Accept, decoded.action) + + val content = decoded.content + assertNotNull(content) + assertEquals("kotlin-sdk", content["repository"]?.jsonPrimitive?.content) + assertEquals(128, content["stars"]?.jsonPrimitive?.int) + assertEquals(false, content["private"]?.jsonPrimitive?.boolean) + + val meta = decoded.meta + assertNotNull(meta) + assertEquals("2025-01-12T15:00:58Z", meta["submittedAt"]?.jsonPrimitive?.content) + } + + @Test + fun `should deserialize decline result without content`() { + val json = """ + { + "action": "decline", + "_meta": { + "reason": "User skipped" + } + } + """.trimIndent() + + val result = McpJson.decodeFromString(json) + assertEquals(ElicitResult.Action.Decline, result.action) + assertNull(result.content) + assertEquals("User skipped", result.meta?.get("reason")?.jsonPrimitive?.content) + } + + @Test + fun `should require content only for accept action`() { + assertFailsWith { + ElicitResult( + action = ElicitResult.Action.Cancel, + content = buildJsonObject { put("value", "ignored") }, + ) + } + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/InitializeTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/InitializeTest.kt new file mode 100644 index 0000000..6af7c4e --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/InitializeTest.kt @@ -0,0 +1,219 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import io.kotest.assertions.json.shouldEqualJson +import kotlinx.serialization.json.boolean +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +class InitializeTest { + + @Test + fun `should serialize InitializeRequest with capabilities and meta`() { + val request = InitializeRequest( + InitializeRequestParams( + protocolVersion = "2024-11-05", + capabilities = ClientCapabilities( + sampling = ClientCapabilities.sampling, + roots = ClientCapabilities.Roots(listChanged = true), + elicitation = ClientCapabilities.elicitation, + experimental = buildJsonObject { + put( + "workspace-sync", + buildJsonObject { put("enabled", true) }, + ) + }, + ), + clientInfo = Implementation( + name = "dev-client", + version = "1.2.3", + title = "Dev Client", + icons = listOf(Icon(src = "https://example.com/icon.png")), + ), + meta = RequestMeta( + buildJsonObject { put("progressToken", "init-42") }, + ), + ), + ) + + val json = McpJson.encodeToString(request) + + json shouldEqualJson """ + { + "method": "initialize", + "params": { + "protocolVersion": "2024-11-05", + "capabilities": { + "sampling": {}, + "roots": { + "listChanged": true + }, + "elicitation": {}, + "experimental": { + "workspace-sync": { + "enabled": true + } + } + }, + "clientInfo": { + "name": "dev-client", + "version": "1.2.3", + "title": "Dev Client", + "icons": [ + {"src": "https://example.com/icon.png"} + ] + }, + "_meta": { + "progressToken": "init-42" + } + } + } + """.trimIndent() + } + + @Test + fun `should deserialize InitializeRequest from JSON`() { + val json = """ + { + "method": "initialize", + "params": { + "protocolVersion": "2025-03-26", + "capabilities": { + "sampling": {}, + "roots": { + "listChanged": false + }, + "experimental": { + "custom-cap": { + "enabled": true + } + } + }, + "clientInfo": { + "name": "sdk-client", + "version": "2.1.0", + "title": "SDK Client" + }, + "_meta": { + "progressToken": 99 + } + } + } + """.trimIndent() + + val request = McpJson.decodeFromString(json) + assertEquals(Method.Defined.Initialize, request.method) + + val params = request.params + assertEquals("2025-03-26", params.protocolVersion) + assertEquals("sdk-client", params.clientInfo.name) + assertEquals("2.1.0", params.clientInfo.version) + assertEquals("SDK Client", params.clientInfo.title) + assertEquals(ProgressToken(99), params.meta?.progressToken) + + val capabilities = params.capabilities + assertNotNull(capabilities.sampling) + assertEquals(false, capabilities.roots?.listChanged) + val experimental = capabilities.experimental + assertNotNull(experimental) + val custom = experimental["custom-cap"]?.jsonObject + assertNotNull(custom) + assertEquals(true, custom["enabled"]?.jsonPrimitive?.boolean) + } + + @Test + fun `should serialize InitializeResult with instructions`() { + val result = InitializeResult( + protocolVersion = "2024-11-05", + capabilities = ServerCapabilities( + tools = ServerCapabilities.Tools(listChanged = true), + resources = ServerCapabilities.Resources( + listChanged = true, + subscribe = true, + ), + prompts = ServerCapabilities.Prompts(listChanged = false), + logging = ServerCapabilities.Logging, + completions = buildJsonObject { put("defaultCount", 5) }, + ), + serverInfo = Implementation( + name = "demo-server", + version = "5.0.0", + websiteUrl = "https://example.com/server", + ), + instructions = "Call the `read` tool to fetch files.", + meta = buildJsonObject { put("issuedAt", "2025-01-12T15:00:58Z") }, + ) + + val json = McpJson.encodeToString(result) + + json shouldEqualJson """ + { + "protocolVersion": "2024-11-05", + "capabilities": { + "tools": { + "listChanged": true + }, + "resources": { + "listChanged": true, + "subscribe": true + }, + "prompts": { + "listChanged": false + }, + "logging": {}, + "completions": { + "defaultCount": 5 + } + }, + "serverInfo": { + "name": "demo-server", + "version": "5.0.0", + "websiteUrl": "https://example.com/server" + }, + "instructions": "Call the `read` tool to fetch files.", + "_meta": { + "issuedAt": "2025-01-12T15:00:58Z" + } + } + """.trimIndent() + } + + @Test + fun `should deserialize InitializeResult from JSON`() { + val json = """ + { + "protocolVersion": "2025-03-26", + "capabilities": { + "tools": {}, + "resources": { + "listChanged": true + } + }, + "serverInfo": { + "name": "result-server", + "version": "4.0.0" + } + } + """.trimIndent() + + val result = McpJson.decodeFromString(json) + + assertEquals("2025-03-26", result.protocolVersion) + assertEquals("result-server", result.serverInfo.name) + assertEquals("4.0.0", result.serverInfo.version) + assertNull(result.instructions) + assertNull(result.meta) + + val capabilities = result.capabilities + assertNotNull(capabilities.tools) + assertEquals(true, capabilities.resources?.listChanged) + assertNull(capabilities.resources?.subscribe) + assertNull(capabilities.prompts) + assertNull(capabilities.logging) + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/JsonRpcTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/JsonRpcTest.kt new file mode 100644 index 0000000..9b97032 --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/JsonRpcTest.kt @@ -0,0 +1,401 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import io.kotest.assertions.json.shouldEqualJson +import kotlinx.serialization.json.boolean +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertIs +import kotlin.test.assertNotNull + +class JsonRpcTest { + + @Test + fun `should convert Request to JSONRPCRequest`() { + val request = ListToolsRequest( + PaginatedRequestParams( + cursor = "page-2", + meta = RequestMeta( + buildJsonObject { + put("progressToken", "token-123") + }, + ), + ), + ) + + val jsonRpc = request.toJSON() + + assertEquals(Method.Defined.ToolsList.value, jsonRpc.method) + val params = jsonRpc.params?.jsonObject + assertNotNull(params) + assertEquals("page-2", params["cursor"]?.jsonPrimitive?.content) + val meta = params["_meta"]?.jsonObject + assertNotNull(meta) + assertEquals("token-123", meta["progressToken"]?.jsonPrimitive?.content) + } + + @Test + fun `should convert JSONRPCRequest to Request`() { + val jsonRpc = McpJson.decodeFromString( + """ + { + "id": 17, + "method": "tools/list", + "params": { + "cursor": "page-5", + "_meta": { + "progressToken": 42 + } + }, + "jsonrpc": "2.0" + } + """.trimIndent(), + ) + + val request = jsonRpc.fromJSON() + val listToolsRequest = assertIs(request) + val decodedParams = assertNotNull(listToolsRequest.params) + assertEquals("page-5", decodedParams.cursor) + val meta = decodedParams.meta?.json + assertNotNull(meta) + assertEquals(42, meta["progressToken"]?.jsonPrimitive?.int) + } + + @Test + fun `should convert Notification to JSONRPCNotification`() { + val notification = LoggingMessageNotification( + LoggingMessageNotificationParams( + level = LoggingLevel.Warning, + data = buildJsonObject { put("message", "Disk space low") }, + logger = "disk-monitor", + meta = buildJsonObject { put("requestId", "req-99") }, + ), + ) + + val json = McpJson.encodeToString(notification.toJSON()) + + json shouldEqualJson """ + { + "method": "notifications/message", + "params": { + "level": "warning", + "data": { + "message": "Disk space low" + }, + "logger": "disk-monitor", + "_meta": { + "requestId": "req-99" + } + }, + "jsonrpc": "2.0" + } + """.trimIndent() + } + + @Test + fun `should convert JSONRPCNotification to Notification`() { + val jsonRpc = McpJson.decodeFromString( + """ + { + "method": "notifications/message", + "params": { + "level": "error", + "data": { + "lines": { + "count": 3 + } + }, + "logger": "pipeline", + "_meta": { + "traceIds": ["abc"] + } + }, + "jsonrpc": "2.0" + } + """.trimIndent(), + ) + + val notification = jsonRpc.fromJSON() + val messageNotification = assertIs(notification) + val decodedParams = messageNotification.params + assertEquals(LoggingLevel.Error, decodedParams.level) + val data = decodedParams.data.jsonObject + assertEquals(3, data["lines"]?.jsonObject?.get("count")?.jsonPrimitive?.int) + assertEquals("pipeline", decodedParams.logger) + val meta = decodedParams.meta + assertNotNull(meta) + val traceIds = meta["traceIds"]?.jsonArray + assertNotNull(traceIds) + assertEquals("abc", traceIds.first().jsonPrimitive.content) + } + + @Test + fun `should serialize JSONRPCRequest with params`() { + val request = JSONRPCRequest( + id = RequestId("req-1"), + method = "tools/list", + params = buildJsonObject { + put("cursor", "abc") + put("includeInactive", true) + }, + ) + + val json = McpJson.encodeToString(request) + + json shouldEqualJson """ + { + "id": "req-1", + "method": "tools/list", + "params": { + "cursor": "abc", + "includeInactive": true + }, + "jsonrpc": "2.0" + } + """.trimIndent() + } + + @Test + fun `should deserialize JSONRPCRequest with numeric id`() { + val json = """ + { + "id": 42, + "method": "resources/read", + "params": { + "uri": "file:///tmp/readme.md" + }, + "jsonrpc": "2.0" + } + """.trimIndent() + + val request = McpJson.decodeFromString(json) + + val id = request.id + assertIs(id) + assertEquals(42L, id.value) + assertEquals("resources/read", request.method) + val params = request.params?.jsonObject + assertNotNull(params) + assertEquals("file:///tmp/readme.md", params["uri"]?.jsonPrimitive?.content) + } + + @Test + fun `should serialize JSONRPCNotification with params`() { + val notification = JSONRPCNotification( + method = "notifications/log", + params = buildJsonObject { + put("level", "info") + put("message", "Completed operation") + }, + ) + + val json = McpJson.encodeToString(notification) + + json shouldEqualJson """ + { + "method": "notifications/log", + "params": { + "level": "info", + "message": "Completed operation" + }, + "jsonrpc": "2.0" + } + """.trimIndent() + } + + @Test + fun `should deserialize JSONRPCNotification`() { + val json = """ + { + "method": "notifications/progress", + "params": { + "progress": 50, + "total": 100 + }, + "jsonrpc": "2.0" + } + """.trimIndent() + + val notification = McpJson.decodeFromString(json) + assertEquals("notifications/progress", notification.method) + val params = notification.params?.jsonObject + assertNotNull(params) + assertEquals(50, params["progress"]?.jsonPrimitive?.int) + assertEquals(100, params["total"]?.jsonPrimitive?.int) + } + + @Test + fun `should serialize JSONRPCResponse with result`() { + val response = JSONRPCResponse( + id = RequestId("call-1"), + result = EmptyResult( + meta = buildJsonObject { put("durationMs", 15) }, + ), + ) + + val json = McpJson.encodeToString(response) + + json shouldEqualJson """ + { + "id": "call-1", + "result": { + "_meta": { + "durationMs": 15 + } + }, + "jsonrpc": "2.0" + } + """.trimIndent() + } + + @Test + fun `should deserialize JSONRPCResponse with EmptyResult`() { + val json = """ + { + "id": 7, + "jsonrpc": "2.0", + "result": { + "_meta": { + "cached": true + } + } + } + """.trimIndent() + + val response = McpJson.decodeFromString(json) + val result = response.result + assertIs(result) + assertEquals(RequestId.NumberId(7L), response.id) + val meta = result.meta + assertNotNull(meta) + assertEquals(true, meta["cached"]?.jsonPrimitive?.boolean) + } + + @Test + fun `should serialize JSONRPCError`() { + val error = JSONRPCError( + id = RequestId(99), + error = RPCError( + code = RPCError.ErrorCode.METHOD_NOT_FOUND, + message = "Method not found", + data = buildJsonObject { put("method", "tools/unknown") }, + ), + ) + + val json = McpJson.encodeToString(error) + + json shouldEqualJson """ + { + "id": 99, + "error": { + "code": -32601, + "message": "Method not found", + "data": { + "method": "tools/unknown" + } + }, + "jsonrpc": "2.0" + } + """.trimIndent() + } + + @Test + fun `should deserialize JSONRPCError`() { + val json = """ + { + "id": "req-404", + "jsonrpc": "2.0", + "error": { + "code": -32602, + "message": "Invalid params", + "data": { + "field": "limit", + "reason": "must be positive" + } + } + } + """.trimIndent() + + val error = McpJson.decodeFromString(json) + assertEquals(RequestId("req-404"), error.id) + assertEquals(RPCError.ErrorCode.INVALID_PARAMS, error.error.code) + assertEquals("Invalid params", error.error.message) + + val data = error.error.data?.jsonObject + assertNotNull(data) + assertEquals("limit", data["field"]?.jsonPrimitive?.content) + assertEquals("must be positive", data["reason"]?.jsonPrimitive?.content) + } + + @Test + fun `should decode JSONRPCMessage as request`() { + val json = """ + { + "id": "msg-1", + "method": "sampling/create", + "params": { + "model": "gpt", + "prompt": "Hello" + }, + "jsonrpc": "2.0" + } + """.trimIndent() + + val message = McpJson.decodeFromString(json) + val request = assertIs(message) + assertEquals("sampling/create", request.method) + val params = request.params?.jsonObject + assertNotNull(params) + assertEquals("gpt", params["model"]?.jsonPrimitive?.content) + assertEquals("Hello", params["prompt"]?.jsonPrimitive?.content) + } + + @Test + fun `should decode JSONRPCMessage as error response`() { + val json = """ + { + "id": 123, + "jsonrpc": "2.0", + "error": { + "code": -32001, + "message": "Request timeout", + "data": { + "timeoutMs": 1000 + } + } + } + """.trimIndent() + + val message = McpJson.decodeFromString(json) + val error = assertIs(message) + assertEquals(RPCError.ErrorCode.REQUEST_TIMEOUT, error.error.code) + val data = error.error.data?.jsonObject + assertNotNull(data) + assertEquals(1000, data["timeoutMs"]?.jsonPrimitive?.int) + } + + @Test + fun `should encode JSONRPCMessage polymorphically`() { + val message: JSONRPCMessage = JSONRPCNotification( + method = "notifications/log", + params = buildJsonObject { put("message", "Polymorphic") }, + ) + + val json = McpJson.encodeToString(message) + + json shouldEqualJson """ + { + "method": "notifications/log", + "params": { + "message": "Polymorphic" + }, + "jsonrpc": "2.0" + } + """.trimIndent() + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/LoggingTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/LoggingTest.kt new file mode 100644 index 0000000..2b8c61a --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/LoggingTest.kt @@ -0,0 +1,166 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import io.kotest.assertions.json.shouldEqualJson +import kotlinx.serialization.json.boolean +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class LoggingTest { + + @Test + fun `should serialize LoggingLevel to expected schema values`() { + assertEquals("\"debug\"", McpJson.encodeToString(LoggingLevel.Debug)) + assertEquals("\"info\"", McpJson.encodeToString(LoggingLevel.Info)) + assertEquals("\"notice\"", McpJson.encodeToString(LoggingLevel.Notice)) + assertEquals("\"warning\"", McpJson.encodeToString(LoggingLevel.Warning)) + assertEquals("\"error\"", McpJson.encodeToString(LoggingLevel.Error)) + assertEquals("\"critical\"", McpJson.encodeToString(LoggingLevel.Critical)) + assertEquals("\"alert\"", McpJson.encodeToString(LoggingLevel.Alert)) + assertEquals("\"emergency\"", McpJson.encodeToString(LoggingLevel.Emergency)) + } + + @Test + fun `should deserialize LoggingLevel from schema values`() { + assertEquals(LoggingLevel.Debug, McpJson.decodeFromString("\"debug\"")) + assertEquals(LoggingLevel.Info, McpJson.decodeFromString("\"info\"")) + assertEquals(LoggingLevel.Notice, McpJson.decodeFromString("\"notice\"")) + assertEquals(LoggingLevel.Warning, McpJson.decodeFromString("\"warning\"")) + assertEquals(LoggingLevel.Error, McpJson.decodeFromString("\"error\"")) + assertEquals(LoggingLevel.Critical, McpJson.decodeFromString("\"critical\"")) + assertEquals(LoggingLevel.Alert, McpJson.decodeFromString("\"alert\"")) + assertEquals(LoggingLevel.Emergency, McpJson.decodeFromString("\"emergency\"")) + } + + @Test + fun `should serialize SetLevelRequest with meta`() { + val request = SetLevelRequest( + SetLevelRequestParams( + level = LoggingLevel.Warning, + meta = RequestMeta( + buildJsonObject { put("progressToken", "log-42") }, + ), + ), + ) + + val json = McpJson.encodeToString(request) + + json shouldEqualJson """ + { + "method": "logging/setLevel", + "params": { + "level": "warning", + "_meta": { + "progressToken": "log-42" + } + } + } + """.trimIndent() + } + + @Test + fun `should deserialize SetLevelRequest with numeric progress token`() { + val json = """ + { + "method": "logging/setLevel", + "params": { + "level": "alert", + "_meta": { + "progressToken": 1001 + } + } + } + """.trimIndent() + + val request = McpJson.decodeFromString(json) + assertEquals(Method.Defined.LoggingSetLevel, request.method) + assertEquals(LoggingLevel.Alert, request.params.level) + assertEquals(ProgressToken(1001), request.params.meta?.progressToken) + } + + @Test + fun `should serialize LoggingMessageNotification with logger`() { + val notification = LoggingMessageNotification( + LoggingMessageNotificationParams( + level = LoggingLevel.Error, + data = buildJsonObject { put("message", "Disk space critically low") }, + logger = "infra.monitor", + meta = buildJsonObject { put("source", "node-1") }, + ), + ) + + val json = McpJson.encodeToString(notification) + + json shouldEqualJson """ + { + "method": "notifications/message", + "params": { + "level": "error", + "data": { + "message": "Disk space critically low" + }, + "logger": "infra.monitor", + "_meta": { + "source": "node-1" + } + } + } + """.trimIndent() + } + + @Test + fun `should deserialize LoggingMessageNotification with text data`() { + val json = """ + { + "method": "notifications/message", + "params": { + "level": "info", + "data": "Service started successfully" + } + } + """.trimIndent() + + val notification = McpJson.decodeFromString(json) + assertEquals(Method.Defined.NotificationsMessage, notification.method) + + val params = notification.params + assertEquals(LoggingLevel.Info, params.level) + assertEquals("Service started successfully", params.data.jsonPrimitive.content) + assertNull(params.logger) + assertNull(params.meta) + } + + @Test + fun `should deserialize LoggingMessageNotification with structured data`() { + val json = """ + { + "method": "notifications/message", + "params": { + "level": "critical", + "data": { + "code": "DB_CONN_FAIL", + "retry": false + }, + "logger": "database.monitor", + "_meta": { + "requestId": "req-77" + } + } + } + """.trimIndent() + + val notification = McpJson.decodeFromString(json) + val params = notification.params + assertEquals(LoggingLevel.Critical, params.level) + val data = params.data.jsonObject + assertEquals("DB_CONN_FAIL", data["code"]?.jsonPrimitive?.content) + assertEquals(false, data["retry"]?.jsonPrimitive?.boolean) + assertEquals("database.monitor", params.logger) + val meta = params.meta + assertEquals("req-77", meta?.get("requestId")?.jsonPrimitive?.content) + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/NotificationTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/NotificationTest.kt new file mode 100644 index 0000000..bbd209d --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/NotificationTest.kt @@ -0,0 +1,296 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import io.kotest.assertions.json.shouldEqualJson +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class NotificationTest { + + @Test + fun `should serialize CancelledNotification with reason and meta`() { + val notification = CancelledNotification( + CancelledNotificationParams( + requestId = RequestId("req-1"), + reason = "User requested cancellation", + meta = buildJsonObject { put("source", "client") }, + ), + ) + + val json = McpJson.encodeToString(notification) + + json shouldEqualJson """ + { + "method": "notifications/cancelled", + "params": { + "requestId": "req-1", + "reason": "User requested cancellation", + "_meta": { + "source": "client" + } + } + } + """.trimIndent() + } + + @Test + fun `should deserialize CancelledNotification with numeric request id`() { + val json = """ + { + "method": "notifications/cancelled", + "params": { + "requestId": 42, + "reason": "Timeout reached" + } + } + """.trimIndent() + + val notification = McpJson.decodeFromString(json) + val params = notification.params + + assertEquals(Method.Defined.NotificationsCancelled, notification.method) + assertEquals(RequestId(42), params.requestId) + assertEquals("Timeout reached", params.reason) + assertNull(params.meta) + } + + @Test + fun `should serialize InitializedNotification with meta`() { + val notification = InitializedNotification( + BaseNotificationParams( + meta = buildJsonObject { put("readyAt", "2025-01-12T15:00:58Z") }, + ), + ) + + val json = McpJson.encodeToString(notification) + + json shouldEqualJson """ + { + "method": "notifications/initialized", + "params": { + "_meta": { + "readyAt": "2025-01-12T15:00:58Z" + } + } + } + """.trimIndent() + } + + @Test + fun `should deserialize InitializedNotification without params`() { + val json = """ + { + "method": "notifications/initialized" + } + """.trimIndent() + + val notification = McpJson.decodeFromString(json) + + assertEquals(Method.Defined.NotificationsInitialized, notification.method) + assertNull(notification.params) + } + + @Test + fun `should serialize ProgressNotification with all fields`() { + val notification = ProgressNotification( + ProgressNotificationParams( + progressToken = ProgressToken("task-42"), + progress = 0.6, + total = 1.0, + message = "Syncing repository", + meta = buildJsonObject { put("stage", "download") }, + ), + ) + + val json = McpJson.encodeToString(notification) + + json shouldEqualJson """ + { + "method": "notifications/progress", + "params": { + "progressToken": "task-42", + "progress": 0.6, + "total": 1.0, + "message": "Syncing repository", + "_meta": { + "stage": "download" + } + } + } + """.trimIndent() + } + + @Test + fun `should deserialize ProgressNotification with numeric token`() { + val json = """ + { + "method": "notifications/progress", + "params": { + "progressToken": 7, + "progress": 0.25 + } + } + """.trimIndent() + + val notification = McpJson.decodeFromString(json) + val params = notification.params + + assertEquals(Method.Defined.NotificationsProgress, notification.method) + assertEquals(ProgressToken(7), params.progressToken) + assertEquals(0.25, params.progress) + assertNull(params.total) + assertNull(params.message) + assertNull(params.meta) + } + + @Test + fun `should serialize ResourceUpdatedNotification with meta`() { + val notification = ResourceUpdatedNotification( + ResourceUpdatedNotificationParams( + uri = "file:///workspace/README.md", + meta = buildJsonObject { put("checksum", "abcd1234") }, + ), + ) + + val json = McpJson.encodeToString(notification) + + json shouldEqualJson """ + { + "method": "notifications/resources/updated", + "params": { + "uri": "file:///workspace/README.md", + "_meta": { + "checksum": "abcd1234" + } + } + } + """.trimIndent() + } + + @Test + fun `should deserialize ResourceUpdatedNotification`() { + val json = """ + { + "method": "notifications/resources/updated", + "params": { + "uri": "file:///docs/guide.md", + "_meta": { + "etag": "W/\"42\"" + } + } + } + """.trimIndent() + + val notification = McpJson.decodeFromString(json) + val params = notification.params + + assertEquals(Method.Defined.NotificationsResourcesUpdated, notification.method) + assertEquals("file:///docs/guide.md", params.uri) + assertEquals("W/\"42\"", params.meta?.get("etag")?.jsonPrimitive?.content) + } + + @Test + fun `should serialize PromptListChangedNotification with meta`() { + val notification = PromptListChangedNotification( + BaseNotificationParams( + meta = buildJsonObject { put("reason", "catalog-updated") }, + ), + ) + + val json = McpJson.encodeToString(notification) + + json shouldEqualJson """ + { + "method": "notifications/prompts/list_changed", + "params": { + "_meta": { + "reason": "catalog-updated" + } + } + } + """.trimIndent() + } + + @Test + fun `should deserialize PromptListChangedNotification without params`() { + val json = """ + { + "method": "notifications/prompts/list_changed" + } + """.trimIndent() + + val notification = McpJson.decodeFromString(json) + + assertEquals(Method.Defined.NotificationsPromptsListChanged, notification.method) + assertNull(notification.params) + } + + @Test + fun `should serialize ResourceListChangedNotification with meta`() { + val notification = ResourceListChangedNotification( + BaseNotificationParams( + meta = buildJsonObject { put("reason", "subscription-updated") }, + ), + ) + + val json = McpJson.encodeToString(notification) + + json shouldEqualJson """ + { + "method": "notifications/resources/list_changed", + "params": { + "_meta": { + "reason": "subscription-updated" + } + } + } + """.trimIndent() + } + + @Test + fun `should serialize RootsListChangedNotification with meta`() { + val notification = RootsListChangedNotification( + BaseNotificationParams( + meta = buildJsonObject { put("reason", "workspace-moved") }, + ), + ) + + val json = McpJson.encodeToString(notification) + + json shouldEqualJson """ + { + "method": "notifications/roots/list_changed", + "params": { + "_meta": { + "reason": "workspace-moved" + } + } + } + """.trimIndent() + } + + @Test + fun `should serialize ToolListChangedNotification with meta`() { + val notification = ToolListChangedNotification( + BaseNotificationParams( + meta = buildJsonObject { put("reason", "tool-added") }, + ), + ) + + val json = McpJson.encodeToString(notification) + + json shouldEqualJson """ + { + "method": "notifications/tools/list_changed", + "params": { + "_meta": { + "reason": "tool-added" + } + } + } + """.trimIndent() + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PingRequestTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PingRequestTest.kt new file mode 100644 index 0000000..070d58e --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PingRequestTest.kt @@ -0,0 +1,95 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import io.kotest.assertions.json.shouldEqualJson +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertIs +import kotlin.test.assertNotNull + +class PingRequestTest { + + @Test + fun `should serialize PingRequest without params`() { + val request = PingRequest() + + val json = McpJson.encodeToString(request) + + json shouldEqualJson """ + { + "method": "ping" + } + """.trimIndent() + } + + @Test + fun `should serialize PingRequest with meta`() { + val request = PingRequest( + BaseRequestParams( + meta = RequestMeta( + buildJsonObject { put("progressToken", "ping-42") }, + ), + ), + ) + + val json = McpJson.encodeToString(request) + + json shouldEqualJson """ + { + "method": "ping", + "params": { + "_meta": { + "progressToken": "ping-42" + } + } + } + """.trimIndent() + } + + @Test + fun `should convert PingRequest to JSONRPCRequest`() { + val request = PingRequest( + BaseRequestParams( + meta = RequestMeta( + buildJsonObject { put("progressToken", 99) }, + ), + ), + ) + + val jsonRpc = request.toJSON() + + assertEquals("ping", jsonRpc.method) + val params = assertNotNull(jsonRpc.params).jsonObject + val meta = params["_meta"]?.jsonObject + assertNotNull(meta) + assertEquals(99, meta["progressToken"]?.jsonPrimitive?.int) + } + + @Test + fun `should deserialize JSONRPCRequest to PingRequest`() { + val json = """ + { + "id": "ping-1", + "method": "ping", + "jsonrpc": "2.0", + "params": { + "_meta": { + "progressToken": "pong-1" + } + } + } + """.trimIndent() + + val jsonRpc = McpJson.decodeFromString(json) + val request = jsonRpc.fromJSON() + + val pingRequest = assertIs(request) + val meta = pingRequest.params?.meta?.json + assertNotNull(meta) + assertEquals("pong-1", meta["progressToken"]?.jsonPrimitive?.content) + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PromptsTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PromptsTest.kt new file mode 100644 index 0000000..01eb01c --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PromptsTest.kt @@ -0,0 +1,453 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import io.kotest.assertions.json.shouldEqualJson +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.double +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertIs +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +class PromptsTest { + + @Test + fun `should serialize PromptArgument with minimal fields`() { + val argument = PromptArgument(name = "repository") + + val json = McpJson.encodeToString(argument) + + json shouldEqualJson """ + { + "name": "repository" + } + """.trimIndent() + } + + @Test + fun `should serialize PromptArgument with all fields`() { + val argument = PromptArgument( + name = "language", + description = "Programming language to use", + required = true, + title = "Language", + ) + + val json = McpJson.encodeToString(argument) + + json shouldEqualJson """ + { + "name": "language", + "description": "Programming language to use", + "required": true, + "title": "Language" + } + """.trimIndent() + } + + @Test + fun `should serialize Prompt with arguments and meta`() { + val prompt = Prompt( + name = "summarize_update", + title = "Summarize Update", + description = "Summarize the latest repository changes.", + arguments = listOf( + PromptArgument( + name = "summaryLength", + description = "Approximate length of the summary", + required = false, + title = "Summary length", + ), + ), + icons = listOf( + Icon(src = "https://example.com/icon.png"), + Icon(src = "https://example.com/icon-dark.svg", theme = Icon.Theme.Dark), + ), + meta = buildJsonObject { put("category", "status-report") }, + ) + + val json = McpJson.encodeToString(prompt) + + json shouldEqualJson """ + { + "name": "summarize_update", + "description": "Summarize the latest repository changes.", + "arguments": [ + { + "name": "summaryLength", + "description": "Approximate length of the summary", + "required": false, + "title": "Summary length" + } + ], + "title": "Summarize Update", + "icons": [ + { + "src": "https://example.com/icon.png" + }, + { + "src": "https://example.com/icon-dark.svg", + "theme": "dark" + } + ], + "_meta": { + "category": "status-report" + } + } + """.trimIndent() + } + + @Test + fun `should deserialize Prompt from JSON`() { + val json = """ + { + "name": "code_review", + "description": "Generate a succinct code review.", + "title": "Code Review", + "arguments": [ + { + "name": "filePath", + "description": "Path to the file to review", + "required": true + }, + { + "name": "severity", + "title": "Issue severity" + } + ], + "icons": [ + {"src": "https://example.com/review.png"} + ], + "_meta": { + "category": "quality" + } + } + """.trimIndent() + + val prompt = McpJson.decodeFromString(json) + + assertEquals("code_review", prompt.name) + assertEquals("Generate a succinct code review.", prompt.description) + assertEquals("Code Review", prompt.title) + val arguments = prompt.arguments + assertNotNull(arguments) + assertEquals(2, arguments.size) + assertEquals("filePath", arguments[0].name) + assertEquals(true, arguments[0].required) + assertEquals("severity", arguments[1].name) + assertEquals("Issue severity", arguments[1].title) + val meta = prompt.meta + assertNotNull(meta) + assertEquals("quality", meta["category"]?.jsonPrimitive?.content) + } + + @Test + fun `should serialize PromptMessage with text content`() { + val message = PromptMessage( + role = Role.Assistant, + content = TextContent(text = "Provide a concise summary of the changes."), + ) + + val json = McpJson.encodeToString(message) + + json shouldEqualJson """ + { + "role": "assistant", + "content": { + "type": "text", + "text": "Provide a concise summary of the changes." + } + } + """.trimIndent() + } + + @Test + fun `should deserialize PromptMessage with resource content`() { + val json = """ + { + "role": "user", + "content": { + "type": "resource", + "resource": { + "uri": "file:///workspace/README.md", + "mimeType": "text/markdown", + "text": "# Project Overview" + } + } + } + """.trimIndent() + + val message = McpJson.decodeFromString(json) + + assertEquals(Role.User, message.role) + val content = message.content + assertIs(content) + val resource = assertIs(content.resource) + assertEquals("file:///workspace/README.md", resource.uri) + assertEquals("# Project Overview", resource.text) + } + + @Test + fun `should serialize and deserialize PromptReference`() { + val reference = PromptReference(name = "daily-summary", title = "Daily Summary") + + val json = McpJson.encodeToString(reference) + json shouldEqualJson """ + { + "type": "ref/prompt", + "name": "daily-summary", + "title": "Daily Summary" + } + """.trimIndent() + + val decoded = McpJson.decodeFromString(json) + val promptReference = assertIs(decoded) + assertEquals("daily-summary", promptReference.name) + assertEquals("Daily Summary", promptReference.title) + assertEquals(ReferenceType.Prompt, promptReference.type) + } + + @Test + fun `should serialize GetPromptRequest with arguments and meta`() { + val request = GetPromptRequest( + GetPromptRequestParams( + name = "generate_release_notes", + arguments = mapOf("version" to "1.2.3"), + meta = RequestMeta( + buildJsonObject { put("progressToken", "get-prompt-1") }, + ), + ), + ) + + val json = McpJson.encodeToString(request) + + json shouldEqualJson """ + { + "method": "prompts/get", + "params": { + "name": "generate_release_notes", + "arguments": { + "version": "1.2.3" + }, + "_meta": { + "progressToken": "get-prompt-1" + } + } + } + """.trimIndent() + } + + @Test + fun `should deserialize GetPromptRequest from JSON`() { + val json = """ + { + "method": "prompts/get", + "params": { + "name": "draft-response", + "arguments": { + "tone": "confident", + "audience": "executive" + }, + "_meta": { + "progressToken": 88 + } + } + } + """.trimIndent() + + val request = McpJson.decodeFromString(json) + + assertEquals(Method.Defined.PromptsGet, request.method) + val params = request.params + assertEquals("draft-response", params.name) + val args = params.arguments + assertNotNull(args) + assertEquals("confident", args["tone"]) + assertEquals("executive", args["audience"]) + assertEquals(ProgressToken(88), params.meta?.progressToken) + } + + @Test + fun `should serialize GetPromptResult with messages and meta`() { + val result = GetPromptResult( + messages = listOf( + PromptMessage( + role = Role.User, + content = TextContent(text = "Use concise language suitable for executives."), + ), + PromptMessage( + role = Role.Assistant, + content = TextContent(text = "Here is the summary you requested."), + ), + ), + description = "Executive summary response template.", + meta = buildJsonObject { put("generatedAt", "2025-01-12T15:00:58Z") }, + ) + + val json = McpJson.encodeToString(result) + + json shouldEqualJson """ + { + "messages": [ + { + "role": "user", + "content": { + "type": "text", + "text": "Use concise language suitable for executives." + } + }, + { + "role": "assistant", + "content": { + "type": "text", + "text": "Here is the summary you requested." + } + } + ], + "description": "Executive summary response template.", + "_meta": { + "generatedAt": "2025-01-12T15:00:58Z" + } + } + """.trimIndent() + } + + @Test + fun `should deserialize GetPromptResult from JSON`() { + val json = """ + { + "messages": [ + { + "role": "user", + "content": { + "type": "text", + "text": "Summarize today's standup." + } + } + ], + "description": "Collect information from the team." + } + """.trimIndent() + + val result = McpJson.decodeFromString(json) + + val messages = result.messages + assertEquals(1, messages.size) + assertEquals(Role.User, messages.first().role) + val textContent = assertIs(messages.first().content) + assertEquals("Summarize today's standup.", textContent.text) + assertEquals("Collect information from the team.", result.description) + assertNull(result.meta) + } + + @Test + fun `should serialize ListPromptsRequest with pagination params`() { + val request = ListPromptsRequest( + PaginatedRequestParams( + cursor = "cursor-123", + meta = RequestMeta( + buildJsonObject { put("progressToken", "list-prompts-1") }, + ), + ), + ) + + val json = McpJson.encodeToString(request) + + json shouldEqualJson """ + { + "method": "prompts/list", + "params": { + "cursor": "cursor-123", + "_meta": { + "progressToken": "list-prompts-1" + } + } + } + """.trimIndent() + } + + @Test + fun `should serialize ListPromptsRequest without params`() { + val request = ListPromptsRequest() + + val json = McpJson.encodeToString(request) + + json shouldEqualJson """ + { + "method": "prompts/list" + } + """.trimIndent() + } + + @Test + fun `should serialize ListPromptsResult with next cursor and meta`() { + val result = ListPromptsResult( + prompts = listOf( + Prompt(name = "morning-briefing"), + Prompt(name = "incident-response"), + ), + nextCursor = "cursor-2", + meta = buildJsonObject { put("page", 1) }, + ) + + val json = McpJson.encodeToString(result) + + json shouldEqualJson """ + { + "prompts": [ + {"name": "morning-briefing"}, + {"name": "incident-response"} + ], + "nextCursor": "cursor-2", + "_meta": { + "page": 1 + } + } + """.trimIndent() + } + + @Test + fun `should deserialize ListPromptsResult from JSON`() { + val json = """ + { + "prompts": [ + { + "name": "weekly-update", + "description": "Summarize weekly activities.", + "arguments": [ + { + "name": "team", + "title": "Team name" + } + ] + } + ], + "nextCursor": "cursor-next", + "_meta": { + "page": 2, + "latencyMs": 12.5 + } + } + """.trimIndent() + + val result = McpJson.decodeFromString(json) + + val prompts = result.prompts + assertEquals(1, prompts.size) + val prompt = prompts.first() + assertEquals("weekly-update", prompt.name) + assertEquals("Summarize weekly activities.", prompt.description) + val args = prompt.arguments + assertNotNull(args) + assertEquals("team", args.first().name) + assertEquals("Team name", args.first().title) + assertEquals("cursor-next", result.nextCursor) + val meta = result.meta + assertNotNull(meta) + assertEquals(2, meta["page"]?.jsonPrimitive?.int) + assertEquals(12.5, meta["latencyMs"]?.jsonPrimitive?.double) + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/RequestTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/RequestTest.kt new file mode 100644 index 0000000..9341265 --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/RequestTest.kt @@ -0,0 +1,182 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import io.kotest.assertions.json.shouldEqualJson +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.double +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertIs +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +class RequestTest { + + @Test + fun `should expose progress token from RequestMeta`() { + val stringTokenMeta = RequestMeta(buildJsonObject { put("progressToken", "sync-1") }) + val numberTokenMeta = RequestMeta(buildJsonObject { put("progressToken", 7) }) + val customMeta = RequestMeta( + buildJsonObject { + put("progressToken", "ignored") + put("source", "client") + }, + ) + + assertEquals(ProgressToken("sync-1"), stringTokenMeta.progressToken) + assertEquals(ProgressToken(7), numberTokenMeta.progressToken) + assertEquals("client", customMeta["source"]?.jsonPrimitive?.content) + } + + @Test + fun `should serialize BaseRequestParams with meta`() { + val params = BaseRequestParams( + meta = RequestMeta( + buildJsonObject { + put("progressToken", "base-42") + put("origin", "test-suite") + }, + ), + ) + + val json = McpJson.encodeToString(params) + + json shouldEqualJson """ + { + "_meta": { + "progressToken": "base-42", + "origin": "test-suite" + } + } + """.trimIndent() + } + + @Test + fun `should deserialize BaseRequestParams with numeric progress token`() { + val json = """ + { + "_meta": { + "progressToken": 501, + "latencyMs": 12.5 + } + } + """.trimIndent() + + val params = McpJson.decodeFromString(json) + + val meta = params.meta + assertNotNull(meta) + assertEquals(ProgressToken(501), meta.progressToken) + assertEquals(12.5, meta["latencyMs"]?.jsonPrimitive?.double) + } + + @Test + fun `should serialize PaginatedRequestParams with cursor and meta`() { + val params = PaginatedRequestParams( + cursor = "cursor-1", + meta = RequestMeta(buildJsonObject { put("progressToken", "page-req") }), + ) + + val json = McpJson.encodeToString(params) + + json shouldEqualJson """ + { + "cursor": "cursor-1", + "_meta": { + "progressToken": "page-req" + } + } + """.trimIndent() + } + + @Test + fun `should deserialize PaginatedRequestParams`() { + val json = """ + { + "cursor": "cursor-2", + "_meta": { + "progressToken": 99 + } + } + """.trimIndent() + + val params = McpJson.decodeFromString(json) + + assertEquals("cursor-2", params.cursor) + assertEquals(ProgressToken(99), params.meta?.progressToken) + } + + @Test + fun `should serialize CustomRequest with params`() { + val request = CustomRequest( + method = Method.Custom("workspace/sync"), + params = BaseRequestParams( + meta = RequestMeta(buildJsonObject { put("progressToken", "sync-req") }), + ), + ) + + val json = McpJson.encodeToString(request) + + json shouldEqualJson """ + { + "method": "workspace/sync", + "params": { + "_meta": { + "progressToken": "sync-req" + } + } + } + """.trimIndent() + } + + @Test + fun `should deserialize unknown method into CustomRequest`() { + val json = """ + { + "method": "extensions/customAction", + "params": { + "_meta": { + "progressToken": "custom-1" + } + } + } + """.trimIndent() + + val request = McpJson.decodeFromString(json) + + val custom = assertIs(request) + val method = custom.method + val customMethod = assertIs(method) + assertEquals("extensions/customAction", customMethod.value) + assertEquals("custom-1", custom.params?.meta?.json?.get("progressToken")?.jsonPrimitive?.content) + } + + @Test + fun `should serialize EmptyResult with meta`() { + val result = EmptyResult( + meta = buildJsonObject { + put("processedBy", "worker-1") + }, + ) + + val json = McpJson.encodeToString(result) + + json shouldEqualJson """ + { + "_meta": { + "processedBy": "worker-1" + } + } + """.trimIndent() + } + + @Test + fun `should deserialize EmptyResult without meta`() { + val json = "{}" + + val result = McpJson.decodeFromString(json) + + assertNull(result.meta) + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ResourcesTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ResourcesTest.kt new file mode 100644 index 0000000..0313b75 --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ResourcesTest.kt @@ -0,0 +1,539 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import io.kotest.assertions.json.shouldEqualJson +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertIs +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +class ResourcesTest { + + @Test + fun `should serialize Resource with minimal fields`() { + val resource = Resource( + uri = "file:///workspace/README.md", + name = "README", + ) + + val json = McpJson.encodeToString(resource) + + json shouldEqualJson """ + { + "uri": "file:///workspace/README.md", + "name": "README" + } + """.trimIndent() + } + + @Test + fun `should serialize Resource with all fields`() { + val resource = Resource( + uri = "file:///workspace/CHANGELOG.md", + name = "CHANGELOG", + description = "Changelog for recent releases", + mimeType = "text/markdown", + size = 4096, + title = "Project Changelog", + annotations = Annotations(priority = 0.8, audience = listOf(Role.Assistant)), + icons = listOf( + Icon(src = "https://example.com/changelog.png"), + ), + meta = buildJsonObject { put("etag", "abc123") }, + ) + + val json = McpJson.encodeToString(resource) + + json shouldEqualJson """ + { + "uri": "file:///workspace/CHANGELOG.md", + "name": "CHANGELOG", + "description": "Changelog for recent releases", + "mimeType": "text/markdown", + "size": 4096, + "title": "Project Changelog", + "annotations": { + "priority": 0.8, + "audience": ["assistant"] + }, + "icons": [ + {"src": "https://example.com/changelog.png"} + ], + "_meta": { + "etag": "abc123" + } + } + """.trimIndent() + } + + @Test + fun `should deserialize Resource from JSON`() { + val json = """ + { + "uri": "file:///config/settings.json", + "name": "config-settings", + "description": "Configuration file", + "mimeType": "application/json", + "size": 1024, + "title": "Settings", + "annotations": { + "priority": 0.5 + }, + "icons": [ + {"src": "https://example.com/settings.png"} + ], + "_meta": { + "etag": "\"98765\"" + } + } + """.trimIndent() + + val resource = McpJson.decodeFromString(json) + + assertEquals("file:///config/settings.json", resource.uri) + assertEquals("config-settings", resource.name) + assertEquals("Configuration file", resource.description) + assertEquals("application/json", resource.mimeType) + assertEquals(1024, resource.size) + assertEquals("Settings", resource.title) + assertEquals(0.5, resource.annotations?.priority) + val meta = resource.meta + assertNotNull(meta) + assertEquals("\"98765\"", meta["etag"]?.jsonPrimitive?.content) + } + + @Test + fun `should serialize ResourceTemplate with meta`() { + val template = ResourceTemplate( + uriTemplate = "file:///workspace/{path}", + name = "workspace-file", + description = "Workspace file template", + mimeType = "text/plain", + title = "Workspace File", + annotations = Annotations( + priority = 0.6, + audience = listOf(Role.User, Role.Assistant), + ), + icons = listOf(Icon(src = "https://example.com/file.svg", theme = Icon.Theme.Light)), + meta = buildJsonObject { put("requiresAuth", true) }, + ) + + val json = McpJson.encodeToString(template) + + json shouldEqualJson """ + { + "uriTemplate": "file:///workspace/{path}", + "name": "workspace-file", + "description": "Workspace file template", + "mimeType": "text/plain", + "title": "Workspace File", + "annotations": { + "priority": 0.6, + "audience": ["user", "assistant"] + }, + "icons": [ + { + "src": "https://example.com/file.svg", + "theme": "light" + } + ], + "_meta": { + "requiresAuth": true + } + } + """.trimIndent() + } + + @Test + fun `should deserialize ResourceTemplate from JSON`() { + val json = """ + { + "uriTemplate": "db://users/{id}", + "name": "user-record", + "description": "Database record template", + "annotations": { + "priority": 1.0 + } + } + """.trimIndent() + + val template = McpJson.decodeFromString(json) + + assertEquals("db://users/{id}", template.uriTemplate) + assertEquals("user-record", template.name) + assertEquals("Database record template", template.description) + assertEquals(1.0, template.annotations?.priority) + assertNull(template.meta) + } + + @Test + fun `should serialize and deserialize ResourceTemplateReference`() { + val reference = ResourceTemplateReference(uri = "file:///workspace/{path}") + + val json = McpJson.encodeToString(reference) + + json shouldEqualJson """ + { + "type": "ref/resource", + "uri": "file:///workspace/{path}" + } + """.trimIndent() + + val decoded = McpJson.decodeFromString(json) + val templateReference = assertIs(decoded) + assertEquals("file:///workspace/{path}", templateReference.uri) + assertEquals(ReferenceType.ResourceTemplate, templateReference.type) + } + + @Test + fun `should serialize TextResourceContents`() { + val contents = TextResourceContents( + text = "Hello, MCP!", + uri = "file:///workspace/hello.txt", + mimeType = "text/plain", + meta = buildJsonObject { put("encoding", "utf-8") }, + ) + + val json = McpJson.encodeToString(contents) + + json shouldEqualJson """ + { + "text": "Hello, MCP!", + "uri": "file:///workspace/hello.txt", + "mimeType": "text/plain", + "_meta": { + "encoding": "utf-8" + } + } + """.trimIndent() + } + + @Test + fun `should deserialize BlobResourceContents`() { + val json = """ + { + "blob": "YmluYXJ5ZGF0YQ==", + "uri": "file:///workspace/logo.png", + "mimeType": "image/png", + "_meta": { + "size": 128 + } + } + """.trimIndent() + + val contents = McpJson.decodeFromString(json) + val blobContents = assertIs(contents) + assertEquals("YmluYXJ5ZGF0YQ==", blobContents.blob) + assertEquals("file:///workspace/logo.png", blobContents.uri) + assertEquals("image/png", blobContents.mimeType) + assertEquals(128, blobContents.meta?.get("size")?.jsonPrimitive?.int) + } + + @Test + fun `should serialize UnknownResourceContents`() { + val contents = UnknownResourceContents( + uri = "custom://resource/42", + mimeType = "application/octet-stream", + ) + + val json = McpJson.encodeToString(contents) + + json shouldEqualJson """ + { + "uri": "custom://resource/42", + "mimeType": "application/octet-stream" + } + """.trimIndent() + } + + @Test + fun `should serialize ListResourcesRequest with cursor`() { + val request = ListResourcesRequest( + PaginatedRequestParams( + cursor = "cursor-1", + meta = RequestMeta(buildJsonObject { put("progressToken", "list-resources-1") }), + ), + ) + + val json = McpJson.encodeToString(request) + + json shouldEqualJson """ + { + "method": "resources/list", + "params": { + "cursor": "cursor-1", + "_meta": { + "progressToken": "list-resources-1" + } + } + } + """.trimIndent() + } + + @Test + fun `should serialize ListResourcesRequest without params`() { + val request = ListResourcesRequest() + + val json = McpJson.encodeToString(request) + + json shouldEqualJson """ + { + "method": "resources/list" + } + """.trimIndent() + } + + @Test + fun `should serialize ListResourcesResult`() { + val result = ListResourcesResult( + resources = listOf( + Resource(uri = "file:///workspace/README.md", name = "README"), + Resource(uri = "file:///workspace/CONTRIBUTING.md", name = "CONTRIBUTING"), + ), + nextCursor = "cursor-2", + meta = buildJsonObject { put("page", 1) }, + ) + + val json = McpJson.encodeToString(result) + + json shouldEqualJson """ + { + "resources": [ + {"uri": "file:///workspace/README.md", "name": "README"}, + {"uri": "file:///workspace/CONTRIBUTING.md", "name": "CONTRIBUTING"} + ], + "nextCursor": "cursor-2", + "_meta": { + "page": 1 + } + } + """.trimIndent() + } + + @Test + fun `should deserialize ListResourcesResult`() { + val json = """ + { + "resources": [ + { + "uri": "file:///workspace/docs/guide.md", + "name": "guide", + "description": "User guide" + } + ], + "nextCursor": "cursor-next", + "_meta": { + "page": 2 + } + } + """.trimIndent() + + val result = McpJson.decodeFromString(json) + + assertEquals("cursor-next", result.nextCursor) + val resources = result.resources + assertEquals(1, resources.size) + val resource = resources.first() + assertEquals("file:///workspace/docs/guide.md", resource.uri) + assertEquals("guide", resource.name) + assertEquals("User guide", resource.description) + assertEquals(2, result.meta?.get("page")?.jsonPrimitive?.int) + } + + @Test + fun `should serialize ReadResourceRequest`() { + val request = ReadResourceRequest( + ReadResourceRequestParams( + uri = "file:///workspace/notes.txt", + meta = RequestMeta(buildJsonObject { put("progressToken", "read-1") }), + ), + ) + + val json = McpJson.encodeToString(request) + + json shouldEqualJson """ + { + "method": "resources/read", + "params": { + "uri": "file:///workspace/notes.txt", + "_meta": { + "progressToken": "read-1" + } + } + } + """.trimIndent() + } + + @Test + fun `should deserialize ReadResourceResult with mixed contents`() { + val json = """ + { + "contents": [ + { + "text": "Section 1", + "uri": "file:///workspace/report.txt", + "mimeType": "text/plain" + }, + { + "blob": "aW1hZ2VEYXRh", + "uri": "file:///workspace/diagram.png", + "mimeType": "image/png" + } + ], + "_meta": { + "generatedAt": "2025-01-12T15:00:58Z" + } + } + """.trimIndent() + + val result = McpJson.decodeFromString(json) + + val contents = result.contents + assertEquals(2, contents.size) + assertIs(contents[0]) + assertIs(contents[1]) + val meta = result.meta + assertNotNull(meta) + assertEquals("2025-01-12T15:00:58Z", meta["generatedAt"]?.jsonPrimitive?.content) + } + + @Test + fun `should serialize SubscribeRequest`() { + val request = SubscribeRequest( + SubscribeRequestParams( + uri = "file:///workspace/todo.md", + meta = RequestMeta(buildJsonObject { put("progressToken", 42) }), + ), + ) + + val json = McpJson.encodeToString(request) + + json shouldEqualJson """ + { + "method": "resources/subscribe", + "params": { + "uri": "file:///workspace/todo.md", + "_meta": { + "progressToken": 42 + } + } + } + """.trimIndent() + } + + @Test + fun `should serialize UnsubscribeRequest`() { + val request = UnsubscribeRequest( + UnsubscribeRequestParams( + uri = "file:///workspace/todo.md", + meta = RequestMeta(buildJsonObject { put("progressToken", "unsubscribe-1") }), + ), + ) + + val json = McpJson.encodeToString(request) + + json shouldEqualJson """ + { + "method": "resources/unsubscribe", + "params": { + "uri": "file:///workspace/todo.md", + "_meta": { + "progressToken": "unsubscribe-1" + } + } + } + """.trimIndent() + } + + @Test + fun `should serialize ListResourceTemplatesRequest`() { + val request = ListResourceTemplatesRequest( + PaginatedRequestParams( + cursor = "template-1", + meta = RequestMeta(buildJsonObject { put("progressToken", "templates-1") }), + ), + ) + + val json = McpJson.encodeToString(request) + + json shouldEqualJson """ + { + "method": "resources/templates/list", + "params": { + "cursor": "template-1", + "_meta": { + "progressToken": "templates-1" + } + } + } + """.trimIndent() + } + + @Test + fun `should serialize ListResourceTemplatesResult`() { + val result = ListResourceTemplatesResult( + resourceTemplates = listOf( + ResourceTemplate(uriTemplate = "file:///workspace/{path}", name = "workspace-file"), + ), + nextCursor = "cursor-templates-2", + meta = buildJsonObject { put("page", 3) }, + ) + + val json = McpJson.encodeToString(result) + + json shouldEqualJson """ + { + "resourceTemplates": [ + { + "uriTemplate": "file:///workspace/{path}", + "name": "workspace-file" + } + ], + "nextCursor": "cursor-templates-2", + "_meta": { + "page": 3 + } + } + """.trimIndent() + } + + @Test + fun `should deserialize ListResourceTemplatesResult`() { + val json = """ + { + "resourceTemplates": [ + { + "uriTemplate": "db://records/{id}", + "name": "record", + "description": "Database record", + "icons": [ + {"src": "https://example.com/db.png"} + ] + } + ], + "nextCursor": "cursor-next", + "_meta": { + "page": 1 + } + } + """.trimIndent() + + val result = McpJson.decodeFromString(json) + + assertEquals("cursor-next", result.nextCursor) + val templates = result.resourceTemplates + assertEquals(1, templates.size) + val template = templates.first() + assertEquals("db://records/{id}", template.uriTemplate) + assertEquals("record", template.name) + assertEquals("Database record", template.description) + val icons = template.icons + assertNotNull(icons) + assertEquals("https://example.com/db.png", icons.first().src) + assertEquals(1, result.meta?.get("page")?.jsonPrimitive?.int) + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/RootsTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/RootsTest.kt new file mode 100644 index 0000000..3f1b8d6 --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/RootsTest.kt @@ -0,0 +1,169 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import io.kotest.assertions.json.shouldEqualJson +import kotlinx.serialization.json.boolean +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull + +class RootsTest { + + @Test + fun `should serialize Root with minimal fields`() { + val root = Root(uri = "file:///workspace/project") + + val json = McpJson.encodeToString(root) + + json shouldEqualJson """ + { + "uri": "file:///workspace/project" + } + """.trimIndent() + } + + @Test + fun `should serialize Root with name and meta`() { + val root = Root( + uri = "file:///workspace/docs", + name = "Docs", + meta = buildJsonObject { put("writable", true) }, + ) + + val json = McpJson.encodeToString(root) + + json shouldEqualJson """ + { + "uri": "file:///workspace/docs", + "name": "Docs", + "_meta": { + "writable": true + } + } + """.trimIndent() + } + + @Test + fun `should deserialize Root with validation`() { + val json = """ + { + "uri": "file:///workspace/code", + "name": "Code", + "_meta": { + "default": true + } + } + """.trimIndent() + + val root = McpJson.decodeFromString(json) + + assertEquals("file:///workspace/code", root.uri) + assertEquals("Code", root.name) + assertEquals(true, root.meta?.get("default")?.jsonPrimitive?.boolean) + } + + @Test + fun `should serialize ListRootsRequest with meta`() { + val request = ListRootsRequest( + BaseRequestParams( + meta = RequestMeta( + buildJsonObject { put("progressToken", "roots-list-1") }, + ), + ), + ) + + val json = McpJson.encodeToString(request) + + json shouldEqualJson """ + { + "method": "roots/list", + "params": { + "_meta": { + "progressToken": "roots-list-1" + } + } + } + """.trimIndent() + } + + @Test + fun `should serialize ListRootsRequest without params`() { + val request = ListRootsRequest() + + val json = McpJson.encodeToString(request) + + json shouldEqualJson """ + { + "method": "roots/list" + } + """.trimIndent() + } + + @Test + fun `should serialize ListRootsResult`() { + val result = ListRootsResult( + roots = listOf( + Root(uri = "file:///workspace/project", name = "Project"), + Root(uri = "file:///workspace/docs", name = "Docs"), + ), + meta = buildJsonObject { put("issuedAt", "2025-01-12T15:00:58Z") }, + ) + + val json = McpJson.encodeToString(result) + + json shouldEqualJson """ + { + "roots": [ + {"uri": "file:///workspace/project", "name": "Project"}, + {"uri": "file:///workspace/docs", "name": "Docs"} + ], + "_meta": { + "issuedAt": "2025-01-12T15:00:58Z" + } + } + """.trimIndent() + } + + @Test + fun `should deserialize ListRootsResult`() { + val json = """ + { + "roots": [ + { + "uri": "file:///workspace/code", + "name": "Code" + } + ], + "_meta": { + "updatedBy": "client-1" + } + } + """.trimIndent() + + val result = McpJson.decodeFromString(json) + + val roots = result.roots + assertEquals(1, roots.size) + val root = roots.first() + assertEquals("file:///workspace/code", root.uri) + assertEquals("Code", root.name) + val meta = result.meta + assertNotNull(meta) + assertEquals("client-1", meta["updatedBy"]?.jsonPrimitive?.content) + } + + @Test + fun `should reject non file URI roots`() { + val json = """ + { + "uri": "https://example.com/root" + } + """.trimIndent() + + val exception = runCatching { McpJson.decodeFromString(json) }.exceptionOrNull() + assertNotNull(exception) + assertEquals(true, exception.message?.contains("Root URI must start with 'file://'")) + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/SamplingTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/SamplingTest.kt new file mode 100644 index 0000000..7dc3fe6 --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/SamplingTest.kt @@ -0,0 +1,258 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import io.kotest.assertions.json.shouldEqualJson +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.double +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertIs +import kotlin.test.assertNotNull + +class SamplingTest { + + @Test + fun `should serialize ModelHint`() { + val hint = ModelHint(name = "claude-3-5-sonnet") + + val json = McpJson.encodeToString(hint) + + json shouldEqualJson """ + { + "name": "claude-3-5-sonnet" + } + """.trimIndent() + } + + @Test + fun `should serialize ModelPreferences with priorities`() { + val preferences = ModelPreferences( + hints = listOf(ModelHint(name = "haiku"), ModelHint(name = "openaichat")), + costPriority = 0.25, + speedPriority = 0.75, + intelligencePriority = 1.0, + ) + + val json = McpJson.encodeToString(preferences) + + json shouldEqualJson """ + { + "hints": [ + {"name": "haiku"}, + {"name": "openaichat"} + ], + "costPriority": 0.25, + "speedPriority": 0.75, + "intelligencePriority": 1.0 + } + """.trimIndent() + } + + @Test + fun `should reject ModelPreferences with invalid priority`() { + assertFailsWith { + ModelPreferences(costPriority = 1.5) + } + } + + @Test + fun `should serialize SamplingMessage`() { + val message = SamplingMessage( + role = Role.User, + content = TextContent(text = "Summarize the latest release."), + ) + + val json = McpJson.encodeToString(message) + + json shouldEqualJson """ + { + "role": "user", + "content": { + "type": "text", + "text": "Summarize the latest release." + } + } + """.trimIndent() + } + + @Test + fun `should serialize CreateMessageRequest with all fields`() { + val request = CreateMessageRequest( + CreateMessageRequestParams( + maxTokens = 512, + messages = listOf( + SamplingMessage( + role = Role.User, + content = TextContent(text = "You are a helpful assistant."), + ), + SamplingMessage( + role = Role.User, + content = TextContent(text = "Provide a short summary."), + ), + ), + modelPreferences = ModelPreferences( + hints = listOf(ModelHint(name = "claude")), + speedPriority = 0.6, + ), + systemPrompt = "Respond with concise bullet points.", + includeContext = IncludeContext.AllServers, + temperature = 0.8, + stopSequences = listOf("END"), + metadata = buildJsonObject { put("provider", "anthropic") }, + meta = RequestMeta(buildJsonObject { put("progressToken", "sample-1") }), + ), + ) + + val json = McpJson.encodeToString(request) + + json shouldEqualJson """ + { + "method": "sampling/createMessage", + "params": { + "maxTokens": 512, + "messages": [ + { + "role": "user", + "content": { + "type": "text", + "text": "You are a helpful assistant." + } + }, + { + "role": "user", + "content": { + "type": "text", + "text": "Provide a short summary." + } + } + ], + "modelPreferences": { + "hints": [ + {"name": "claude"} + ], + "speedPriority": 0.6 + }, + "systemPrompt": "Respond with concise bullet points.", + "includeContext": "allServers", + "temperature": 0.8, + "stopSequences": ["END"], + "metadata": { + "provider": "anthropic" + }, + "_meta": { + "progressToken": "sample-1" + } + } + } + """.trimIndent() + } + + @Test + fun `should deserialize CreateMessageRequest`() { + val json = """ + { + "method": "sampling/createMessage", + "params": { + "maxTokens": 256, + "messages": [ + { + "role": "user", + "content": { + "type": "text", + "text": "Draft a project update." + } + } + ], + "modelPreferences": { + "costPriority": 0.4 + }, + "includeContext": "thisServer", + "temperature": 1.1, + "stopSequences": ["\n\n"], + "metadata": { + "provider": "openai" + }, + "_meta": { + "progressToken": 42 + } + } + } + """.trimIndent() + + val request = McpJson.decodeFromString(json) + + assertEquals(Method.Defined.SamplingCreateMessage, request.method) + val params = request.params + assertEquals(256, params.maxTokens) + assertEquals(IncludeContext.ThisServer, params.includeContext) + assertEquals(ProgressToken(42), params.meta?.progressToken) + assertEquals("openai", params.metadata?.get("provider")?.jsonPrimitive?.content) + + val message = params.messages.first() + assertEquals(Role.User, message.role) + val content = assertIs(message.content) + assertEquals("Draft a project update.", content.text) + + val preferences = assertNotNull(params.modelPreferences) + assertEquals(0.4, preferences.costPriority) + } + + @Test + fun `should serialize CreateMessageResult with stop reason`() { + val result = CreateMessageResult( + role = Role.Assistant, + content = TextContent(text = "Here is the requested update."), + model = "claude-3-5-sonnet", + stopReason = StopReason.MaxTokens, + meta = buildJsonObject { put("latencyMs", 850) }, + ) + + val json = McpJson.encodeToString(result) + + json shouldEqualJson """ + { + "role": "assistant", + "content": { + "type": "text", + "text": "Here is the requested update." + }, + "model": "claude-3-5-sonnet", + "stopReason": "maxTokens", + "_meta": { + "latencyMs": 850 + } + } + """.trimIndent() + } + + @Test + fun `should deserialize CreateMessageResult`() { + val json = """ + { + "role": "assistant", + "content": { + "type": "text", + "text": "Summary complete." + }, + "model": "gpt-4o", + "stopReason": "stopSequence", + "_meta": { + "latencyMs": 1200.5 + } + } + """.trimIndent() + + val result = McpJson.decodeFromString(json) + + assertEquals(Role.Assistant, result.role) + val text = assertIs(result.content) + assertEquals("Summary complete.", text.text) + assertEquals("gpt-4o", result.model) + assertEquals(StopReason.StopSequence, result.stopReason) + val meta = result.meta + assertNotNull(meta) + assertEquals(1200.5, meta["latencyMs"]?.jsonPrimitive?.double) + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ToolsTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ToolsTest.kt new file mode 100644 index 0000000..a662e44 --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ToolsTest.kt @@ -0,0 +1,523 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import io.kotest.assertions.json.shouldEqualJson +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertIs +import kotlin.test.assertNotNull + +class ToolsTest { + + @Test + fun `should serialize Tool with minimal fields`() { + val tool = Tool( + name = "search", + inputSchema = ToolSchema(), + ) + + val json = McpJson.encodeToString(tool) + + json shouldEqualJson """ + { + "name": "search", + "inputSchema": { + "type": "object" + } + } + """.trimIndent() + } + + @Test + fun `should serialize Tool with annotations and schemas`() { + val inputSchema = ToolSchema( + properties = buildJsonObject { + put( + "query", + buildJsonObject { + put("type", "string") + put("description", "Search query") + }, + ) + }, + required = listOf("query"), + ) + val outputSchema = ToolSchema( + properties = buildJsonObject { + put( + "results", + buildJsonObject { + put("type", "array") + }, + ) + }, + ) + val tool = Tool( + name = "web-search", + inputSchema = inputSchema, + description = "Search the web for information", + outputSchema = outputSchema, + title = "Web Search", + annotations = ToolAnnotations( + title = "Web Search (Preferred)", + readOnlyHint = true, + destructiveHint = false, + idempotentHint = true, + openWorldHint = true, + ), + icons = listOf(Icon(src = "https://example.com/search.png")), + meta = buildJsonObject { put("category", "search") }, + ) + + val json = McpJson.encodeToString(tool) + + json shouldEqualJson """ + { + "name": "web-search", + "inputSchema": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "Search query" + } + }, + "required": ["query"] + }, + "description": "Search the web for information", + "outputSchema": { + "type": "object", + "properties": { + "results": { + "type": "array" + } + } + }, + "title": "Web Search", + "annotations": { + "title": "Web Search (Preferred)", + "readOnlyHint": true, + "destructiveHint": false, + "idempotentHint": true, + "openWorldHint": true + }, + "icons": [ + {"src": "https://example.com/search.png"} + ], + "_meta": { + "category": "search" + } + } + """.trimIndent() + } + + @Test + fun `should deserialize Tool from JSON`() { + val json = """ + { + "name": "translate", + "inputSchema": { + "type": "object", + "properties": { + "text": {"type": "string"}, + "targetLanguage": {"type": "string"} + }, + "required": ["text", "targetLanguage"] + }, + "annotations": { + "title": "Translate Text", + "readOnlyHint": true + }, + "_meta": { + "category": "language" + } + } + """.trimIndent() + + val tool = McpJson.decodeFromString(json) + + assertEquals("translate", tool.name) + assertEquals("Translate Text", tool.annotations?.title) + assertEquals(true, tool.annotations?.readOnlyHint) + val schema = tool.inputSchema + val properties = schema.properties + assertNotNull(properties) + assertNotNull(properties["text"]) + assertEquals(listOf("text", "targetLanguage"), schema.required) + assertEquals("language", tool.meta?.get("category")?.jsonPrimitive?.content) + } + + @Test + fun `should serialize CallToolRequest with arguments`() { + val request = CallToolRequest( + CallToolRequestParams( + name = "web-search", + arguments = buildJsonObject { put("query", "MCP protocol") }, + meta = RequestMeta( + buildJsonObject { put("progressToken", "call-1") }, + ), + ), + ) + + val json = McpJson.encodeToString(request) + + json shouldEqualJson """ + { + "method": "tools/call", + "params": { + "name": "web-search", + "arguments": { + "query": "MCP protocol" + }, + "_meta": { + "progressToken": "call-1" + } + } + } + """.trimIndent() + } + + @Test + fun `should deserialize CallToolRequest`() { + val json = """ + { + "method": "tools/call", + "params": { + "name": "analyze-code", + "arguments": { + "path": "src/main.kt" + }, + "_meta": { + "progressToken": 77 + } + } + } + """.trimIndent() + + val request = McpJson.decodeFromString(json) + + assertEquals(Method.Defined.ToolsCall, request.method) + val params = request.params + assertEquals("analyze-code", params.name) + assertEquals("src/main.kt", params.arguments?.get("path")?.jsonPrimitive?.content) + assertEquals(ProgressToken(77), params.meta?.progressToken) + } + + @Test + fun `should serialize CallToolResult with structured content`() { + val result = CallToolResult( + content = listOf( + TextContent(text = "Found 3 relevant documents."), + ), + isError = false, + structuredContent = buildJsonObject { + put("count", 3) + put("items", buildJsonObject { put("first", "doc.md") }) + }, + meta = buildJsonObject { put("elapsedMs", 1200) }, + ) + + val json = McpJson.encodeToString(result) + + json shouldEqualJson """ + { + "content": [ + { + "type": "text", + "text": "Found 3 relevant documents." + } + ], + "isError": false, + "structuredContent": { + "count": 3, + "items": { + "first": "doc.md" + } + }, + "_meta": { + "elapsedMs": 1200 + } + } + """.trimIndent() + } + + @Test + fun `should deserialize CallToolResult`() { + val json = """ + { + "content": [ + { + "type": "text", + "text": "Unable to reach server." + } + ], + "isError": true, + "structuredContent": { + "errorCode": "NETWORK", + "retryable": true + }, + "_meta": { + "requestId": "req-9" + } + } + """.trimIndent() + + val result = McpJson.decodeFromString(json) + + assertEquals(true, result.isError) + val text = assertIs(result.content.first()) + assertEquals("Unable to reach server.", text.text) + val structured = result.structuredContent + assertNotNull(structured) + assertEquals("NETWORK", structured["errorCode"]?.jsonPrimitive?.content) + assertEquals("req-9", result.meta?.get("requestId")?.jsonPrimitive?.content) + } + + @Test + fun `should serialize ListToolsRequest with cursor`() { + val request = ListToolsRequest( + PaginatedRequestParams( + cursor = "cursor-1", + meta = RequestMeta(buildJsonObject { put("progressToken", "tools-list-1") }), + ), + ) + + val json = McpJson.encodeToString(request) + + json shouldEqualJson """ + { + "method": "tools/list", + "params": { + "cursor": "cursor-1", + "_meta": { + "progressToken": "tools-list-1" + } + } + } + """.trimIndent() + } + + @Test + fun `should serialize ListToolsRequest without params`() { + val request = ListToolsRequest() + + val json = McpJson.encodeToString(request) + + json shouldEqualJson """ + { + "method": "tools/list" + } + """.trimIndent() + } + + @Test + fun `should serialize ListToolsResult`() { + val result = ListToolsResult( + tools = listOf( + Tool(name = "search", inputSchema = ToolSchema()), + Tool(name = "summarize", inputSchema = ToolSchema()), + ), + nextCursor = "cursor-2", + meta = buildJsonObject { put("page", 1) }, + ) + + val json = McpJson.encodeToString(result) + + json shouldEqualJson """ + { + "tools": [ + { + "name": "search", + "inputSchema": { + "type": "object" + } + }, + { + "name": "summarize", + "inputSchema": { + "type": "object" + } + } + ], + "nextCursor": "cursor-2", + "_meta": { + "page": 1 + } + } + """.trimIndent() + } + + @Test + fun `should deserialize ListToolsResult`() { + val json = """ + { + "tools": [ + { + "name": "search", + "inputSchema": { + "type": "object" + }, + "description": "Search the workspace" + } + ], + "nextCursor": "cursor-next", + "_meta": { + "page": 3 + } + } + """.trimIndent() + + val result = McpJson.decodeFromString(json) + + assertEquals("cursor-next", result.nextCursor) + val tools = result.tools + assertEquals(1, tools.size) + val tool = tools.first() + assertEquals("search", tool.name) + assertEquals("Search the workspace", tool.description) + assertEquals("object", tool.inputSchema.type) + assertEquals(3, result.meta?.get("page")?.jsonPrimitive?.int) + } + + @Test + fun `should build success CallToolResult with text content`() { + val meta = buildJsonObject { put("source", "toolkit") } + + val result = CallToolResult.success("Operation complete", meta) + + assertEquals(false, result.isError) + val text = assertIs(result.content.single()) + assertEquals("Operation complete", text.text) + assertEquals("toolkit", result.meta?.get("source")?.jsonPrimitive?.content) + assertEquals(null, result.structuredContent) + } + + @Test + fun `should build error CallToolResult with text content`() { + val meta = buildJsonObject { put("code", "ERR42") } + + val result = CallToolResult.error("Failed to connect", meta) + + assertEquals(true, result.isError) + val text = assertIs(result.content.single()) + assertEquals("Failed to connect", text.text) + assertEquals("ERR42", result.meta?.get("code")?.jsonPrimitive?.content) + assertEquals(null, result.structuredContent) + } + + @Test + fun `should serialize tool schema type even when defaults disabled`() { + val tool = weatherTool() + + val customJson = Json(from = McpJson) { + encodeDefaults = false + } + val encoded = customJson.encodeToString(Tool.serializer(), tool) + val element = Json.parseToJsonElement(encoded).jsonObject + + val inputSchema = element["inputSchema"]?.jsonObject + val outputSchema = element["outputSchema"]?.jsonObject + + assertNotNull(inputSchema) + assertEquals("object", inputSchema["type"]?.jsonPrimitive?.content) + assertNotNull(outputSchema) + assertEquals("object", outputSchema["type"]?.jsonPrimitive?.content) + } + + @Test + fun `should deserialize complex tool definition`() { + val expected = weatherTool() + val actual = McpJson.decodeFromString(weatherToolJson()) + + assertEquals(expected, actual) + } + + private fun weatherTool(): Tool = Tool( + name = "get_weather", + title = "Get weather", + description = "Get the current weather in a given location", + inputSchema = ToolSchema( + properties = buildJsonObject { + put( + "location", + buildJsonObject { + put("type", "string") + put("description", "The city and state, e.g. San Francisco, CA") + }, + ) + }, + required = listOf("location"), + ), + outputSchema = ToolSchema( + properties = buildJsonObject { + put( + "temperature", + buildJsonObject { + put("type", "number") + put("description", "Temperature in celsius") + }, + ) + put( + "conditions", + buildJsonObject { + put("type", "string") + put("description", "Weather conditions description") + }, + ) + put( + "humidity", + buildJsonObject { + put("type", "number") + put("description", "Humidity percentage") + }, + ) + }, + required = listOf("temperature", "conditions", "humidity"), + ), + meta = buildJsonObject { put("_for_test_only", true) }, + ) + + private fun weatherToolJson(): String = """ + { + "name": "get_weather", + "title": "Get weather", + "description": "Get the current weather in a given location", + "inputSchema": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "The city and state, e.g. San Francisco, CA" + } + }, + "required": ["location"] + }, + "outputSchema": { + "type": "object", + "properties": { + "temperature": { + "type": "number", + "description": "Temperature in celsius" + }, + "conditions": { + "type": "string", + "description": "Weather conditions description" + }, + "humidity": { + "type": "number", + "description": "Humidity percentage" + } + }, + "required": ["temperature", "conditions", "humidity"] + }, + "_meta": { + "_for_test_only": true + } + } + """.trimIndent() +} diff --git a/kotlin-sdk-server/api/kotlin-sdk-server.api b/kotlin-sdk-server/api/kotlin-sdk-server.api index f859446..48e2ca7 100644 --- a/kotlin-sdk-server/api/kotlin-sdk-server.api +++ b/kotlin-sdk-server/api/kotlin-sdk-server.api @@ -6,62 +6,62 @@ public final class io/modelcontextprotocol/kotlin/sdk/server/KtorServerKt { } public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredPrompt : io/modelcontextprotocol/kotlin/sdk/server/Feature { - public fun (Lio/modelcontextprotocol/kotlin/sdk/Prompt;Lkotlin/jvm/functions/Function2;)V - public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/Prompt; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/Prompt;Lkotlin/jvm/functions/Function2;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/Prompt; public final fun component2 ()Lkotlin/jvm/functions/Function2; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/Prompt;Lkotlin/jvm/functions/Function2;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredPrompt; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredPrompt;Lio/modelcontextprotocol/kotlin/sdk/Prompt;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredPrompt; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/Prompt;Lkotlin/jvm/functions/Function2;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredPrompt; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredPrompt;Lio/modelcontextprotocol/kotlin/sdk/types/Prompt;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredPrompt; public fun equals (Ljava/lang/Object;)Z public fun getKey ()Ljava/lang/String; public final fun getMessageProvider ()Lkotlin/jvm/functions/Function2; - public final fun getPrompt ()Lio/modelcontextprotocol/kotlin/sdk/Prompt; + public final fun getPrompt ()Lio/modelcontextprotocol/kotlin/sdk/types/Prompt; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredResource : io/modelcontextprotocol/kotlin/sdk/server/Feature { - public fun (Lio/modelcontextprotocol/kotlin/sdk/Resource;Lkotlin/jvm/functions/Function2;)V - public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/Resource; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/Resource;Lkotlin/jvm/functions/Function2;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/Resource; public final fun component2 ()Lkotlin/jvm/functions/Function2; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/Resource;Lkotlin/jvm/functions/Function2;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredResource; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredResource;Lio/modelcontextprotocol/kotlin/sdk/Resource;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredResource; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/Resource;Lkotlin/jvm/functions/Function2;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredResource; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredResource;Lio/modelcontextprotocol/kotlin/sdk/types/Resource;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredResource; public fun equals (Ljava/lang/Object;)Z public fun getKey ()Ljava/lang/String; public final fun getReadHandler ()Lkotlin/jvm/functions/Function2; - public final fun getResource ()Lio/modelcontextprotocol/kotlin/sdk/Resource; + public final fun getResource ()Lio/modelcontextprotocol/kotlin/sdk/types/Resource; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredTool : io/modelcontextprotocol/kotlin/sdk/server/Feature { - public fun (Lio/modelcontextprotocol/kotlin/sdk/Tool;Lkotlin/jvm/functions/Function2;)V - public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/Tool; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/Tool;Lkotlin/jvm/functions/Function2;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/Tool; public final fun component2 ()Lkotlin/jvm/functions/Function2; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/Tool;Lkotlin/jvm/functions/Function2;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredTool; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredTool;Lio/modelcontextprotocol/kotlin/sdk/Tool;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredTool; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/Tool;Lkotlin/jvm/functions/Function2;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredTool; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredTool;Lio/modelcontextprotocol/kotlin/sdk/types/Tool;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredTool; public fun equals (Ljava/lang/Object;)Z public final fun getHandler ()Lkotlin/jvm/functions/Function2; public fun getKey ()Ljava/lang/String; - public final fun getTool ()Lio/modelcontextprotocol/kotlin/sdk/Tool; + public final fun getTool ()Lio/modelcontextprotocol/kotlin/sdk/types/Tool; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public class io/modelcontextprotocol/kotlin/sdk/server/Server { - public fun (Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun (Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun addPrompt (Lio/modelcontextprotocol/kotlin/sdk/Prompt;Lkotlin/jvm/functions/Function2;)V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/Implementation;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/Implementation;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/Implementation;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/Implementation;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun addPrompt (Lio/modelcontextprotocol/kotlin/sdk/types/Prompt;Lkotlin/jvm/functions/Function2;)V public final fun addPrompt (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lkotlin/jvm/functions/Function2;)V public static synthetic fun addPrompt$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V public final fun addPrompts (Ljava/util/List;)V public final fun addResource (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function2;)V public static synthetic fun addResource$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V public final fun addResources (Ljava/util/List;)V - public final fun addTool (Lio/modelcontextprotocol/kotlin/sdk/Tool;Lkotlin/jvm/functions/Function2;)V - public final fun addTool (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/functions/Function2;)V - public static synthetic fun addTool$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V + public final fun addTool (Lio/modelcontextprotocol/kotlin/sdk/types/Tool;Lkotlin/jvm/functions/Function2;)V + public final fun addTool (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/functions/Function2;)V + public static synthetic fun addTool$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V public final fun addTools (Ljava/util/List;)V public final fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun connect (Lio/modelcontextprotocol/kotlin/sdk/shared/Transport;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -70,7 +70,7 @@ public class io/modelcontextprotocol/kotlin/sdk/server/Server { protected final fun getOptions ()Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions; public final fun getPrompts ()Ljava/util/Map; public final fun getResources ()Ljava/util/Map; - protected final fun getServerInfo ()Lio/modelcontextprotocol/kotlin/sdk/Implementation; + protected final fun getServerInfo ()Lio/modelcontextprotocol/kotlin/sdk/types/Implementation; public final fun getTools ()Ljava/util/Map; public final fun onClose (Lkotlin/jvm/functions/Function0;)V public final fun onConnect (Lkotlin/jvm/functions/Function0;)V @@ -84,34 +84,34 @@ public class io/modelcontextprotocol/kotlin/sdk/server/Server { } public final class io/modelcontextprotocol/kotlin/sdk/server/ServerOptions : io/modelcontextprotocol/kotlin/sdk/shared/ProtocolOptions { - public fun (Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities;Z)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getCapabilities ()Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;Z)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getCapabilities ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities; } public class io/modelcontextprotocol/kotlin/sdk/server/ServerSession : io/modelcontextprotocol/kotlin/sdk/shared/Protocol { - public fun (Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Ljava/lang/String;)V - protected fun assertCapabilityForMethod (Lio/modelcontextprotocol/kotlin/sdk/Method;)V - protected fun assertNotificationCapability (Lio/modelcontextprotocol/kotlin/sdk/Method;)V - public fun assertRequestHandlerCapability (Lio/modelcontextprotocol/kotlin/sdk/Method;)V - public final fun createElicitation (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest$RequestedSchema;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun createElicitation$default (Lio/modelcontextprotocol/kotlin/sdk/server/ServerSession;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest$RequestedSchema;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public final fun createMessage (Lio/modelcontextprotocol/kotlin/sdk/CreateMessageRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun createMessage$default (Lio/modelcontextprotocol/kotlin/sdk/server/ServerSession;Lio/modelcontextprotocol/kotlin/sdk/CreateMessageRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public final fun getClientCapabilities ()Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities; - public final fun getClientVersion ()Lio/modelcontextprotocol/kotlin/sdk/Implementation; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/Implementation;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Ljava/lang/String;)V + protected fun assertCapabilityForMethod (Lio/modelcontextprotocol/kotlin/sdk/types/Method;)V + protected fun assertNotificationCapability (Lio/modelcontextprotocol/kotlin/sdk/types/Method;)V + public fun assertRequestHandlerCapability (Lio/modelcontextprotocol/kotlin/sdk/types/Method;)V + public final fun createElicitation (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun createElicitation$default (Lio/modelcontextprotocol/kotlin/sdk/server/ServerSession;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun createMessage (Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun createMessage$default (Lio/modelcontextprotocol/kotlin/sdk/server/ServerSession;Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun getClientCapabilities ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities; + public final fun getClientVersion ()Lio/modelcontextprotocol/kotlin/sdk/types/Implementation; protected final fun getInstructions ()Ljava/lang/String; - protected final fun getServerInfo ()Lio/modelcontextprotocol/kotlin/sdk/Implementation; + protected final fun getServerInfo ()Lio/modelcontextprotocol/kotlin/sdk/types/Implementation; public final fun listRoots (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun listRoots$default (Lio/modelcontextprotocol/kotlin/sdk/server/ServerSession;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public fun onClose ()V 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 sendLoggingMessage (Lio/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification;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; - public final fun sendResourceUpdated (Lio/modelcontextprotocol/kotlin/sdk/ResourceUpdatedNotification;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun sendResourceUpdated (Lio/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotification;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun sendToolListChanged (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } @@ -121,14 +121,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/server/SseServerTransport public final fun getSessionId ()Ljava/lang/String; public final fun handleMessage (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun handlePostMessage (Lio/ktor/server/application/ApplicationCall;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun send (Lio/modelcontextprotocol/kotlin/sdk/JSONRPCMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun send (Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun start (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport : io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport { public fun (Lkotlinx/io/Source;Lkotlinx/io/Sink;)V public fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun send (Lio/modelcontextprotocol/kotlin/sdk/JSONRPCMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun send (Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun start (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Features.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Features.kt index a956f1b..78bed9c 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Features.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Features.kt @@ -1,14 +1,14 @@ package io.modelcontextprotocol.kotlin.sdk.server -import io.modelcontextprotocol.kotlin.sdk.CallToolRequest -import io.modelcontextprotocol.kotlin.sdk.CallToolResult -import io.modelcontextprotocol.kotlin.sdk.GetPromptRequest -import io.modelcontextprotocol.kotlin.sdk.GetPromptResult -import io.modelcontextprotocol.kotlin.sdk.Prompt -import io.modelcontextprotocol.kotlin.sdk.ReadResourceRequest -import io.modelcontextprotocol.kotlin.sdk.ReadResourceResult -import io.modelcontextprotocol.kotlin.sdk.Resource -import io.modelcontextprotocol.kotlin.sdk.Tool +import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest +import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult +import io.modelcontextprotocol.kotlin.sdk.types.Prompt +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult +import io.modelcontextprotocol.kotlin.sdk.types.Resource +import io.modelcontextprotocol.kotlin.sdk.types.Tool internal typealias FeatureKey = String diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt index 64e32fe..b8c1556 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt @@ -8,9 +8,9 @@ import io.ktor.server.request.contentType import io.ktor.server.request.receiveText import io.ktor.server.response.respondText import io.ktor.server.sse.ServerSSESession -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport -import io.modelcontextprotocol.kotlin.sdk.shared.McpJson +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.McpJson import kotlinx.coroutines.InternalCoroutinesApi import kotlinx.coroutines.job import kotlin.concurrent.atomics.AtomicBoolean diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt index 99f7aa8..3ce426f 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt @@ -1,32 +1,32 @@ package io.modelcontextprotocol.kotlin.sdk.server import io.github.oshai.kotlinlogging.KotlinLogging -import io.modelcontextprotocol.kotlin.sdk.CallToolRequest -import io.modelcontextprotocol.kotlin.sdk.CallToolResult -import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject -import io.modelcontextprotocol.kotlin.sdk.GetPromptRequest -import io.modelcontextprotocol.kotlin.sdk.GetPromptResult -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.ListPromptsRequest -import io.modelcontextprotocol.kotlin.sdk.ListPromptsResult -import io.modelcontextprotocol.kotlin.sdk.ListResourceTemplatesRequest -import io.modelcontextprotocol.kotlin.sdk.ListResourceTemplatesResult -import io.modelcontextprotocol.kotlin.sdk.ListResourcesRequest -import io.modelcontextprotocol.kotlin.sdk.ListResourcesResult -import io.modelcontextprotocol.kotlin.sdk.ListToolsRequest -import io.modelcontextprotocol.kotlin.sdk.ListToolsResult -import io.modelcontextprotocol.kotlin.sdk.Method -import io.modelcontextprotocol.kotlin.sdk.Prompt -import io.modelcontextprotocol.kotlin.sdk.PromptArgument -import io.modelcontextprotocol.kotlin.sdk.ReadResourceRequest -import io.modelcontextprotocol.kotlin.sdk.ReadResourceResult -import io.modelcontextprotocol.kotlin.sdk.Resource -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities -import io.modelcontextprotocol.kotlin.sdk.TextContent -import io.modelcontextprotocol.kotlin.sdk.Tool -import io.modelcontextprotocol.kotlin.sdk.ToolAnnotations import io.modelcontextprotocol.kotlin.sdk.shared.ProtocolOptions import io.modelcontextprotocol.kotlin.sdk.shared.Transport +import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest +import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.ListPromptsRequest +import io.modelcontextprotocol.kotlin.sdk.types.ListPromptsResult +import io.modelcontextprotocol.kotlin.sdk.types.ListResourceTemplatesRequest +import io.modelcontextprotocol.kotlin.sdk.types.ListResourceTemplatesResult +import io.modelcontextprotocol.kotlin.sdk.types.ListResourcesRequest +import io.modelcontextprotocol.kotlin.sdk.types.ListResourcesResult +import io.modelcontextprotocol.kotlin.sdk.types.ListToolsRequest +import io.modelcontextprotocol.kotlin.sdk.types.ListToolsResult +import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.Prompt +import io.modelcontextprotocol.kotlin.sdk.types.PromptArgument +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult +import io.modelcontextprotocol.kotlin.sdk.types.Resource +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.sdk.types.Tool +import io.modelcontextprotocol.kotlin.sdk.types.ToolAnnotations +import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema import kotlinx.atomicfu.atomic import kotlinx.atomicfu.update import kotlinx.collections.immutable.persistentListOf @@ -245,7 +245,7 @@ public open class Server( * @param inputSchema The expected input schema for the tool. * @param outputSchema The optional expected output schema for the tool. * @param toolAnnotations Optional additional tool information. - * @param _meta Optional metadata as a [JsonObject]. + * @param meta Optional metadata as a [JsonObject]. * @param handler A suspend function that handles executing the tool when called by the client. * @throws IllegalStateException If the server does not support tools. */ @@ -253,21 +253,21 @@ public open class Server( public fun addTool( name: String, description: String, - inputSchema: Tool.Input = Tool.Input(), + inputSchema: ToolSchema = ToolSchema(), title: String? = null, - outputSchema: Tool.Output? = null, + outputSchema: ToolSchema? = null, toolAnnotations: ToolAnnotations? = null, - @Suppress("LocalVariableName") _meta: JsonObject? = null, + meta: JsonObject? = null, handler: suspend (CallToolRequest) -> CallToolResult, ) { val tool = Tool( name = name, - title = title, - description = description, inputSchema = inputSchema, outputSchema = outputSchema, + description = description, + title = title, annotations = toolAnnotations, - _meta = _meta ?: EmptyJsonObject, + meta = meta, ) addTool(tool, handler) } @@ -474,13 +474,14 @@ public open class Server( } private suspend fun handleCallTool(request: CallToolRequest): CallToolResult { - logger.debug { "Handling tool call request for tool: ${request.name}" } + val requestParams = request.params + logger.debug { "Handling tool call request for tool: ${requestParams.name}" } // Check if the tool exists - val tool = toolRegistry.get(request.name) ?: run { - logger.error { "Tool not found: ${request.name}" } + val tool = toolRegistry.get(requestParams.name) ?: run { + logger.error { "Tool not found: ${requestParams.name}" } return CallToolResult( - content = listOf(TextContent(text = "Tool ${request.name} not found")), + content = listOf(TextContent(text = "Tool ${requestParams.name} not found")), isError = true, ) } @@ -488,14 +489,14 @@ public open class Server( @Suppress("TooGenericExceptionCaught") // Execute the tool handler and catch any errors return try { - logger.trace { "Executing tool ${request.name} with input: ${request.arguments}" } + logger.trace { "Executing tool ${requestParams.name} with input: ${requestParams.arguments}" } tool.handler(request) } catch (e: CancellationException) { throw e } catch (e: Exception) { - logger.error(e) { "Error executing tool ${request.name}" } + logger.error(e) { "Error executing tool ${requestParams.name}" } CallToolResult( - content = listOf(TextContent(text = "Error executing tool ${request.name}: ${e.message}")), + content = listOf(TextContent(text = "Error executing tool ${requestParams.name}: ${e.message}")), isError = true, ) } @@ -507,11 +508,12 @@ public open class Server( } private suspend fun handleGetPrompt(request: GetPromptRequest): GetPromptResult { - logger.debug { "Handling get prompt request for: ${request.name}" } - val prompt = promptRegistry.get(request.name) + val requestParams = request.params + logger.debug { "Handling get prompt request for: ${requestParams.name}" } + val prompt = promptRegistry.get(requestParams.name) ?: run { - logger.error { "Prompt not found: ${request.name}" } - throw IllegalArgumentException("Prompt not found: ${request.name}") + logger.error { "Prompt not found: ${requestParams.name}" } + throw IllegalArgumentException("Prompt not found: ${requestParams.name}") } return prompt.messageProvider(request) } @@ -522,11 +524,12 @@ public open class Server( } private suspend fun handleReadResource(request: ReadResourceRequest): ReadResourceResult { - logger.debug { "Handling read resource request for: ${request.uri}" } - val resource = resourceRegistry.get(request.uri) + val requestParams = request.params + logger.debug { "Handling read resource request for: ${requestParams.uri}" } + val resource = resourceRegistry.get(requestParams.uri) ?: run { - logger.error { "Resource not found: ${request.uri}" } - throw IllegalArgumentException("Resource not found: ${request.uri}") + logger.error { "Resource not found: ${requestParams.uri}" } + throw IllegalArgumentException("Resource not found: ${requestParams.uri}") } return resource.readHandler(request) } diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt index 887b57b..53d47b6 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt @@ -1,33 +1,36 @@ package io.modelcontextprotocol.kotlin.sdk.server import io.github.oshai.kotlinlogging.KotlinLogging -import io.modelcontextprotocol.kotlin.sdk.ClientCapabilities -import io.modelcontextprotocol.kotlin.sdk.CreateElicitationRequest -import io.modelcontextprotocol.kotlin.sdk.CreateElicitationRequest.RequestedSchema -import io.modelcontextprotocol.kotlin.sdk.CreateElicitationResult -import io.modelcontextprotocol.kotlin.sdk.CreateMessageRequest -import io.modelcontextprotocol.kotlin.sdk.CreateMessageResult -import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject -import io.modelcontextprotocol.kotlin.sdk.EmptyRequestResult -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.InitializeRequest -import io.modelcontextprotocol.kotlin.sdk.InitializeResult -import io.modelcontextprotocol.kotlin.sdk.InitializedNotification -import io.modelcontextprotocol.kotlin.sdk.LATEST_PROTOCOL_VERSION -import io.modelcontextprotocol.kotlin.sdk.ListRootsRequest -import io.modelcontextprotocol.kotlin.sdk.ListRootsResult -import io.modelcontextprotocol.kotlin.sdk.LoggingLevel -import io.modelcontextprotocol.kotlin.sdk.LoggingMessageNotification -import io.modelcontextprotocol.kotlin.sdk.Method -import io.modelcontextprotocol.kotlin.sdk.Method.Defined -import io.modelcontextprotocol.kotlin.sdk.PingRequest -import io.modelcontextprotocol.kotlin.sdk.PromptListChangedNotification -import io.modelcontextprotocol.kotlin.sdk.ResourceListChangedNotification -import io.modelcontextprotocol.kotlin.sdk.ResourceUpdatedNotification -import io.modelcontextprotocol.kotlin.sdk.SUPPORTED_PROTOCOL_VERSIONS -import io.modelcontextprotocol.kotlin.sdk.ToolListChangedNotification import io.modelcontextprotocol.kotlin.sdk.shared.Protocol import io.modelcontextprotocol.kotlin.sdk.shared.RequestOptions +import io.modelcontextprotocol.kotlin.sdk.types.BaseRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest +import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageResult +import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequest +import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.ElicitResult +import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject +import io.modelcontextprotocol.kotlin.sdk.types.EmptyResult +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest +import io.modelcontextprotocol.kotlin.sdk.types.InitializeResult +import io.modelcontextprotocol.kotlin.sdk.types.InitializedNotification +import io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION +import io.modelcontextprotocol.kotlin.sdk.types.ListRootsRequest +import io.modelcontextprotocol.kotlin.sdk.types.ListRootsResult +import io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel +import io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotification +import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.Method.Defined +import io.modelcontextprotocol.kotlin.sdk.types.PingRequest +import io.modelcontextprotocol.kotlin.sdk.types.PromptListChangedNotification +import io.modelcontextprotocol.kotlin.sdk.types.RequestMeta +import io.modelcontextprotocol.kotlin.sdk.types.ResourceListChangedNotification +import io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotification +import io.modelcontextprotocol.kotlin.sdk.types.SUPPORTED_PROTOCOL_VERSIONS +import io.modelcontextprotocol.kotlin.sdk.types.SetLevelRequest +import io.modelcontextprotocol.kotlin.sdk.types.ToolListChangedNotification import kotlinx.atomicfu.AtomicRef import kotlinx.atomicfu.atomic import kotlinx.coroutines.CompletableDeferred @@ -81,10 +84,10 @@ public open class ServerSession( // Logging level handler if (options.capabilities.logging != null) { - setRequestHandler(Defined.LoggingSetLevel) { request, _ -> - currentLoggingLevel.value = request.level - logger.debug { "Logging level set to: ${request.level}" } - EmptyRequestResult() + setRequestHandler(Defined.LoggingSetLevel) { request, _ -> + currentLoggingLevel.value = request.params.level + logger.debug { "Logging level set to: ${request.params.level}" } + EmptyResult() } } } @@ -125,7 +128,7 @@ public open class ServerSession( * @return The result of the ping request. * @throws IllegalStateException If for some reason the method is not supported or the connection is closed. */ - public suspend fun ping(): EmptyRequestResult = request(PingRequest()) + public suspend fun ping(): EmptyResult = request(PingRequest()) /** * Creates a message using the server's sampling capability. @@ -140,8 +143,7 @@ public open class ServerSession( options: RequestOptions? = null, ): CreateMessageResult { logger.debug { - "Creating message with ${params.messages.size} messages, maxTokens=${params.maxTokens}, " + - "temperature=${params.temperature}, systemPrompt=${if (params.systemPrompt != null) "present" else "absent"}" + "Creating message with ${params.params.messages.size} messages, maxTokens=${params.params.maxTokens}, temperature=${params.params.temperature}, systemPrompt=${if (params.params.systemPrompt != null) "present" else "absent"}" } logger.trace { "Full createMessage params: $params" } return request(params, options) @@ -160,20 +162,20 @@ public open class ServerSession( options: RequestOptions? = null, ): ListRootsResult { logger.debug { "Listing roots with params: $params" } - return request(ListRootsRequest(params), options) + return request(ListRootsRequest(BaseRequestParams(RequestMeta(params))), options) } public suspend fun createElicitation( message: String, - requestedSchema: RequestedSchema, + requestedSchema: ElicitRequestParams.RequestedSchema, options: RequestOptions? = null, - ): CreateElicitationResult { + ): ElicitResult { logger.debug { "Creating elicitation with message length=${message.length}, " + "schema properties count=${requestedSchema.properties.size}" } logger.trace { "Full elicitation message: $message, requestedSchema: $requestedSchema" } - return request(CreateElicitationRequest(message, requestedSchema), options) + return request(ElicitRequest(ElicitRequestParams(message, requestedSchema)), options) } /** @@ -336,7 +338,7 @@ public open class ServerSession( logger.trace { "Asserting request handler capability for method: ${method.value}" } when (method) { Defined.SamplingCreateMessage -> { - if (serverCapabilities.sampling == null) { + if (serverCapabilities.experimental?.get("sampling") == null) { logger.error { "Server capability assertion failed: sampling not supported" } throw IllegalStateException("Server does not support sampling (required for $method)") } @@ -388,10 +390,10 @@ public open class ServerSession( private suspend fun handleInitialize(request: InitializeRequest): InitializeResult { logger.debug { "Handling initialization request from client" } - clientCapabilities = request.capabilities - clientVersion = request.clientInfo + clientCapabilities = request.params.capabilities + clientVersion = request.params.clientInfo - val requestedVersion = request.protocolVersion + val requestedVersion = request.params.protocolVersion val protocolVersion = if (SUPPORTED_PROTOCOL_VERSIONS.contains(requestedVersion)) { requestedVersion } else { @@ -418,7 +420,7 @@ public open class ServerSession( private fun isMessageIgnored(level: LoggingLevel): Boolean { val current = currentLoggingLevel.value ?: return false // If no level is set, don't filter - return level.ordinal < current.ordinal + return level < current } /** diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport.kt index 0fa18b8..b65aecd 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport.kt @@ -1,11 +1,11 @@ package io.modelcontextprotocol.kotlin.sdk.server import io.github.oshai.kotlinlogging.KotlinLogging -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage 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.serializeMessage +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.NonCancellable diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt index cfa8ed8..b4d8849 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt @@ -11,10 +11,10 @@ import io.ktor.server.websocket.WebSockets import io.ktor.server.websocket.webSocket import io.ktor.utils.io.CancellationException import io.ktor.utils.io.KtorDsl -import io.modelcontextprotocol.kotlin.sdk.Implementation import io.modelcontextprotocol.kotlin.sdk.LIB_VERSION -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.shared.IMPLEMENTATION_NAME +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities import kotlinx.coroutines.awaitCancellation private val logger = KotlinLogging.logger {} diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaClientTest.kt similarity index 97% rename from kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt rename to kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaClientTest.kt index ad2a958..4c3c752 100644 --- a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt +++ b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaClientTest.kt @@ -3,7 +3,6 @@ package io.modelcontextprotocol.kotlin.sdk.client import io.modelcontextprotocol.kotlin.sdk.ClientCapabilities import io.modelcontextprotocol.kotlin.sdk.CreateElicitationRequest import io.modelcontextprotocol.kotlin.sdk.CreateElicitationResult -import io.modelcontextprotocol.kotlin.sdk.CreateMessageRequest import io.modelcontextprotocol.kotlin.sdk.CreateMessageResult import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject import io.modelcontextprotocol.kotlin.sdk.Implementation @@ -23,7 +22,6 @@ import io.modelcontextprotocol.kotlin.sdk.LoggingMessageNotification import io.modelcontextprotocol.kotlin.sdk.Method import io.modelcontextprotocol.kotlin.sdk.Role import io.modelcontextprotocol.kotlin.sdk.Root -import io.modelcontextprotocol.kotlin.sdk.RootsListChangedNotification import io.modelcontextprotocol.kotlin.sdk.SUPPORTED_PROTOCOL_VERSIONS import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.TextContent @@ -52,7 +50,7 @@ import kotlin.test.assertIs import kotlin.test.assertTrue import kotlin.test.fail -class ClientTest { +class OldSchemaClientTest { @Test fun `should initialize with matching protocol version`() = runTest { var initialised = false @@ -545,7 +543,12 @@ class ClientTest { ), ) - client.setRequestHandler(Method.Defined.SamplingCreateMessage) { _, _ -> + client.setRequestHandler( + Method.Defined.SamplingCreateMessage, + ) { + _, + _, + -> CreateMessageResult( model = "test-model", role = Role.assistant, @@ -643,7 +646,6 @@ class ClientTest { assertEquals(request.id, receivedAsResponse.id) assertEquals(request.jsonrpc, receivedAsResponse.jsonrpc) assertEquals(serverListToolsResult, receivedAsResponse.result) - assertEquals(null, receivedAsResponse.error) } @Test @@ -818,7 +820,7 @@ class ClientTest { ).joinAll() val serverSession = serverSessionResult.await() - serverSession.setNotificationHandler( + serverSession.setNotificationHandler( Method.Defined.NotificationsRootsListChanged, ) { rootListChangedNotificationReceived = true @@ -906,8 +908,10 @@ class ClientTest { val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - val receivedMessages = mutableListOf() - client.setNotificationHandler(Method.Defined.NotificationsMessage) { notification -> + val receivedMessages = mutableListOf() + client.setNotificationHandler( + Method.Defined.NotificationsMessage, + ) { notification -> receivedMessages.add(notification) CompletableDeferred(Unit) } @@ -930,7 +934,7 @@ class ClientTest { // Set logging level to warning val minLevel = LoggingLevel.warning val result = client.setLoggingLevel(minLevel) - assertEquals(EmptyJsonObject, result._meta) + assertEquals(null, result.meta) // Send messages of different levels val testMessages = listOf( @@ -993,8 +997,8 @@ class ClientTest { } client.setElicitationHandler { request -> - assertEquals(elicitationMessage, request.message) - assertEquals(requestedSchema, request.requestedSchema) + assertEquals(elicitationMessage, request.params.message) + assertEquals(requestedSchema, request.params.requestedSchema) CreateElicitationResult( action = elicitationResultAction, diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractPromptIntegrationTest.kt similarity index 88% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt rename to kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractPromptIntegrationTest.kt index d5644bb..1c7c130 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractPromptIntegrationTest.kt @@ -7,6 +7,8 @@ import io.modelcontextprotocol.kotlin.sdk.PromptMessage import io.modelcontextprotocol.kotlin.sdk.Role import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.TextContent +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.McpException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @@ -17,7 +19,7 @@ import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue -abstract class AbstractPromptIntegrationTest : KotlinTestBase() { +abstract class OldSchemaAbstractPromptIntegrationTest : OldSchemaKotlinTestBase() { private val basicPromptName = "basic-prompt" private val basicPromptDescription = "A basic prompt for testing" @@ -37,11 +39,12 @@ abstract class AbstractPromptIntegrationTest : KotlinTestBase() { private val specialCharsPromptDescription = "A prompt with special characters" private val specialCharsContent = "!@#$%^&*()_+{}|:\"<>?~`-=[]\\;',./\n\t" - override fun configureServerCapabilities(): ServerCapabilities = ServerCapabilities( - prompts = ServerCapabilities.Prompts( - listChanged = true, - ), - ) + override fun configureServerCapabilities(): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities = + ServerCapabilities( + prompts = ServerCapabilities.Prompts( + listChanged = true, + ), + ) override fun configureServer() { // basic prompt with a name parameter @@ -56,7 +59,7 @@ abstract class AbstractPromptIntegrationTest : KotlinTestBase() { ), ), ) { request -> - val name = request.arguments?.get("name") ?: "World" + val name = request.params.arguments?.get("name") ?: "World" GetPromptResult( description = basicPromptDescription, @@ -85,7 +88,7 @@ abstract class AbstractPromptIntegrationTest : KotlinTestBase() { ), ), ) { request -> - val special = request.arguments?.get("special") ?: specialCharsContent + val special = request.params.arguments?.get("special") ?: specialCharsContent GetPromptResult( description = specialCharsPromptDescription, @@ -114,7 +117,7 @@ abstract class AbstractPromptIntegrationTest : KotlinTestBase() { ), ), ) { request -> - val size = request.arguments?.get("size")?.toIntOrNull() ?: 1 + val size = request.params.arguments?.get("size")?.toIntOrNull() ?: 1 val content = largePromptContent.repeat(size) GetPromptResult( @@ -152,7 +155,7 @@ abstract class AbstractPromptIntegrationTest : KotlinTestBase() { // validate required arguments val requiredArgs = listOf("arg1", "arg2", "arg3") for (argName in requiredArgs) { - if (request.arguments?.get(argName) == null) { + if (request.params.arguments?.get(argName) == null) { throw IllegalArgumentException("Missing required argument: $argName") } } @@ -160,7 +163,7 @@ abstract class AbstractPromptIntegrationTest : KotlinTestBase() { val args = mutableMapOf() for (i in 1..10) { val argName = "arg$i" - val argValue = request.arguments?.get(argName) + val argValue = request.params.arguments?.get(argName) if (argValue != null) { args[argName] = argValue } @@ -199,7 +202,7 @@ abstract class AbstractPromptIntegrationTest : KotlinTestBase() { ), ), ) { request -> - val topic = request.arguments?.get("topic") ?: "weather" + val topic = request.params.arguments?.get("topic") ?: "weather" GetPromptResult( description = conversationPromptDescription, @@ -261,7 +264,7 @@ abstract class AbstractPromptIntegrationTest : KotlinTestBase() { ), ), ) { request -> - val args = request.arguments ?: emptyMap() + val args = request.params.arguments ?: emptyMap() val arg1 = args["requiredArg1"] ?: throw IllegalArgumentException( "Missing required argument: requiredArg1", ) @@ -319,8 +322,10 @@ abstract class AbstractPromptIntegrationTest : KotlinTestBase() { val testName = "Alice" val result = client.getPrompt( GetPromptRequest( - name = basicPromptName, - arguments = mapOf("name" to testName), + GetPromptRequestParams( + name = basicPromptName, + arguments = mapOf("name" to testName), + ), ), ) @@ -373,12 +378,14 @@ abstract class AbstractPromptIntegrationTest : KotlinTestBase() { ) // test missing required arg - val exception = assertThrows { + val exception = assertThrows { runBlocking { client.getPrompt( GetPromptRequest( - name = strictPromptName, - arguments = mapOf("requiredArg1" to "value1"), + GetPromptRequestParams( + name = strictPromptName, + arguments = mapOf("requiredArg1" to "value1"), + ), ), ) } @@ -391,12 +398,14 @@ abstract class AbstractPromptIntegrationTest : KotlinTestBase() { ) // test with no args - val exception2 = assertThrows { + val exception2 = assertThrows { runBlocking { client.getPrompt( GetPromptRequest( - name = strictPromptName, - arguments = emptyMap(), + GetPromptRequestParams( + name = strictPromptName, + arguments = emptyMap(), + ), ), ) } @@ -411,10 +420,12 @@ abstract class AbstractPromptIntegrationTest : KotlinTestBase() { // test with all required args val result = client.getPrompt( GetPromptRequest( - name = strictPromptName, - arguments = mapOf( - "requiredArg1" to "value1", - "requiredArg2" to "value2", + GetPromptRequestParams( + name = strictPromptName, + arguments = mapOf( + "requiredArg1" to "value1", + "requiredArg2" to "value2", + ), ), ), ) @@ -436,8 +447,10 @@ abstract class AbstractPromptIntegrationTest : KotlinTestBase() { val topic = "climate change" val result = client.getPrompt( GetPromptRequest( - name = conversationPromptName, - arguments = mapOf("topic" to topic), + GetPromptRequestParams( + name = conversationPromptName, + arguments = mapOf("topic" to topic), + ), ), ) @@ -487,8 +500,10 @@ abstract class AbstractPromptIntegrationTest : KotlinTestBase() { val testName = "Alice" val result = client.getPrompt( GetPromptRequest( - name = basicPromptName, - arguments = mapOf("name" to testName), + GetPromptRequestParams( + name = basicPromptName, + arguments = mapOf("name" to testName), + ), ), ) @@ -520,8 +535,10 @@ abstract class AbstractPromptIntegrationTest : KotlinTestBase() { val result = client.getPrompt( GetPromptRequest( - name = complexPromptName, - arguments = arguments, + GetPromptRequestParams( + name = complexPromptName, + arguments = arguments, + ), ), ) @@ -556,8 +573,10 @@ abstract class AbstractPromptIntegrationTest : KotlinTestBase() { fun testLargePrompt() = runBlocking(Dispatchers.IO) { val result = client.getPrompt( GetPromptRequest( - name = largePromptName, - arguments = mapOf("size" to "1"), + GetPromptRequestParams( + name = largePromptName, + arguments = mapOf("size" to "1"), + ), ), ) @@ -578,8 +597,10 @@ abstract class AbstractPromptIntegrationTest : KotlinTestBase() { fun testSpecialCharacters() = runBlocking(Dispatchers.IO) { val result = client.getPrompt( GetPromptRequest( - name = specialCharsPromptName, - arguments = mapOf("special" to specialCharsContent), + GetPromptRequestParams( + name = specialCharsPromptName, + arguments = mapOf("special" to specialCharsContent), + ), ), ) @@ -630,8 +651,10 @@ abstract class AbstractPromptIntegrationTest : KotlinTestBase() { val result = client.getPrompt( GetPromptRequest( - name = promptName, - arguments = arguments, + GetPromptRequestParams( + name = promptName, + arguments = arguments, + ), ), ) @@ -654,19 +677,21 @@ abstract class AbstractPromptIntegrationTest : KotlinTestBase() { fun testNonExistentPrompt() = runTest { val nonExistentPromptName = "non-existent-prompt" - val exception = assertThrows { + val exception = assertThrows { runBlocking { client.getPrompt( GetPromptRequest( - name = nonExistentPromptName, - arguments = mapOf("name" to "Test"), + GetPromptRequestParams( + name = nonExistentPromptName, + arguments = mapOf("name" to "Test"), + ), ), ) } } val msg = exception.message ?: "" - val expectedMessage = "JSONRPCError(code=InternalError, message=Prompt not found: non-existent-prompt, data={})" + val expectedMessage = "MCP error -32603: Prompt not found: non-existent-prompt" assertEquals(expectedMessage, msg, "Unexpected error message for non-existent prompt") } diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractResourceIntegrationTest.kt similarity index 83% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt rename to kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractResourceIntegrationTest.kt index ad80567..a7a4202 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractResourceIntegrationTest.kt @@ -7,6 +7,10 @@ import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.SubscribeRequest import io.modelcontextprotocol.kotlin.sdk.TextResourceContents import io.modelcontextprotocol.kotlin.sdk.UnsubscribeRequest +import io.modelcontextprotocol.kotlin.sdk.types.McpException +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.SubscribeRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.UnsubscribeRequestParams import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @@ -19,7 +23,7 @@ import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue -abstract class AbstractResourceIntegrationTest : KotlinTestBase() { +abstract class OldSchemaAbstractResourceIntegrationTest : OldSchemaKotlinTestBase() { private val testResourceUri = "test://example.txt" private val testResourceName = "Test Resource" @@ -41,12 +45,13 @@ abstract class AbstractResourceIntegrationTest : KotlinTestBase() { private val dynamicResourceName = "Dynamic Resource" private val dynamicResourceContent = AtomicBoolean(false) - override fun configureServerCapabilities(): ServerCapabilities = ServerCapabilities( - resources = ServerCapabilities.Resources( - subscribe = true, - listChanged = true, - ), - ) + override fun configureServerCapabilities(): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities = + ServerCapabilities( + resources = ServerCapabilities.Resources( + subscribe = true, + listChanged = true, + ), + ) override fun configureServer() { server.addResource( @@ -59,7 +64,7 @@ abstract class AbstractResourceIntegrationTest : KotlinTestBase() { contents = listOf( TextResourceContents( text = testResourceContent, - uri = request.uri, + uri = request.params.uri, mimeType = "text/plain", ), ), @@ -76,7 +81,7 @@ abstract class AbstractResourceIntegrationTest : KotlinTestBase() { contents = listOf( TextResourceContents( text = testResourceContent, - uri = request.uri, + uri = request.params.uri, mimeType = "text/plain", ), ), @@ -93,7 +98,7 @@ abstract class AbstractResourceIntegrationTest : KotlinTestBase() { contents = listOf( BlobResourceContents( blob = binaryResourceContent, - uri = request.uri, + uri = request.params.uri, mimeType = "image/png", ), ), @@ -110,7 +115,7 @@ abstract class AbstractResourceIntegrationTest : KotlinTestBase() { contents = listOf( TextResourceContents( text = largeResourceContent, - uri = request.uri, + uri = request.params.uri, mimeType = "text/plain", ), ), @@ -127,7 +132,7 @@ abstract class AbstractResourceIntegrationTest : KotlinTestBase() { contents = listOf( TextResourceContents( text = if (dynamicResourceContent.get()) "Updated content" else "Original content", - uri = request.uri, + uri = request.params.uri, mimeType = "text/plain", ), ), @@ -150,7 +155,7 @@ abstract class AbstractResourceIntegrationTest : KotlinTestBase() { @Test fun testReadResource() = runBlocking(Dispatchers.IO) { - val result = client.readResource(ReadResourceRequest(uri = testResourceUri)) + val result = client.readResource(ReadResourceRequest(ReadResourceRequestParams(uri = testResourceUri))) assertNotNull(result, "Read resource result should not be null") assertTrue(result.contents.isNotEmpty(), "Resource contents should not be empty") @@ -164,17 +169,19 @@ abstract class AbstractResourceIntegrationTest : KotlinTestBase() { @Test fun testSubscribeAndUnsubscribe() { runBlocking(Dispatchers.IO) { - val subscribeResult = client.subscribeResource(SubscribeRequest(uri = testResourceUri)) + val subscribeResult = + client.subscribeResource(SubscribeRequest(SubscribeRequestParams(uri = testResourceUri))) assertNotNull(subscribeResult, "Subscribe result should not be null") - val unsubscribeResult = client.unsubscribeResource(UnsubscribeRequest(uri = testResourceUri)) + val unsubscribeResult = + client.unsubscribeResource(UnsubscribeRequest(UnsubscribeRequestParams(uri = testResourceUri))) assertNotNull(unsubscribeResult, "Unsubscribe result should not be null") } } @Test fun testBinaryResource() = runBlocking(Dispatchers.IO) { - val result = client.readResource(ReadResourceRequest(uri = binaryResourceUri)) + val result = client.readResource(ReadResourceRequest(ReadResourceRequestParams(uri = binaryResourceUri))) assertNotNull(result, "Read resource result should not be null") assertTrue(result.contents.isNotEmpty(), "Resource contents should not be empty") @@ -187,7 +194,7 @@ abstract class AbstractResourceIntegrationTest : KotlinTestBase() { @Test fun testLargeResource() = runBlocking(Dispatchers.IO) { - val result = client.readResource(ReadResourceRequest(uri = largeResourceUri)) + val result = client.readResource(ReadResourceRequest(ReadResourceRequestParams(uri = largeResourceUri))) assertNotNull(result, "Read resource result should not be null") assertTrue(result.contents.isNotEmpty(), "Resource contents should not be empty") @@ -202,22 +209,24 @@ abstract class AbstractResourceIntegrationTest : KotlinTestBase() { fun testInvalidResourceUri() = runTest { val invalidUri = "test://nonexistent.txt" - val exception = assertThrows { + val exception = assertThrows { runBlocking { - client.readResource(ReadResourceRequest(uri = invalidUri)) + client.readResource(ReadResourceRequest(ReadResourceRequestParams(uri = invalidUri))) } } val msg = exception.message ?: "" val expectedMessage = - "JSONRPCError(code=InternalError, message=Resource not found: test://nonexistent.txt, data={})" + "MCP error -32603: Resource not found: test://nonexistent.txt" assertEquals(expectedMessage, msg, "Unexpected error message for invalid resource URI") } @Test fun testDynamicResource() = runBlocking(Dispatchers.IO) { - val initialResult = client.readResource(ReadResourceRequest(uri = dynamicResourceUri)) + val initialResult = client.readResource( + ReadResourceRequest(ReadResourceRequestParams(uri = dynamicResourceUri)), + ) assertNotNull(initialResult, "Initial read result should not be null") val initialContent = (initialResult.contents.firstOrNull() as? TextResourceContents)?.text assertEquals("Original content", initialContent, "Initial content should match") @@ -225,7 +234,9 @@ abstract class AbstractResourceIntegrationTest : KotlinTestBase() { // update resource dynamicResourceContent.set(true) - val updatedResult = client.readResource(ReadResourceRequest(uri = dynamicResourceUri)) + val updatedResult = client.readResource( + ReadResourceRequest(ReadResourceRequestParams(uri = dynamicResourceUri)), + ) assertNotNull(updatedResult, "Updated read result should not be null") val updatedContent = (updatedResult.contents.firstOrNull() as? TextResourceContents)?.text assertEquals("Updated content", updatedContent, "Updated content should match") @@ -248,7 +259,7 @@ abstract class AbstractResourceIntegrationTest : KotlinTestBase() { contents = listOf( TextResourceContents( text = "New resource content", - uri = request.uri, + uri = request.params.uri, mimeType = "text/plain", ), ), @@ -288,7 +299,7 @@ abstract class AbstractResourceIntegrationTest : KotlinTestBase() { else -> largeResourceUri } - val result = client.readResource(ReadResourceRequest(uri = uri)) + val result = client.readResource(ReadResourceRequest(ReadResourceRequestParams(uri = uri))) synchronized(results) { results.add(result) } diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractToolIntegrationTest.kt similarity index 87% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt rename to kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractToolIntegrationTest.kt index ca88723..6086b47 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractToolIntegrationTest.kt @@ -3,12 +3,12 @@ package io.modelcontextprotocol.kotlin.sdk.integration.kotlin import io.kotest.assertions.json.shouldEqualJson import io.modelcontextprotocol.kotlin.sdk.CallToolRequest import io.modelcontextprotocol.kotlin.sdk.CallToolResult -import io.modelcontextprotocol.kotlin.sdk.CallToolResultBase import io.modelcontextprotocol.kotlin.sdk.ImageContent import io.modelcontextprotocol.kotlin.sdk.PromptMessageContent import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.TextContent import io.modelcontextprotocol.kotlin.sdk.Tool +import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequestParams import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -28,7 +28,7 @@ import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue -abstract class AbstractToolIntegrationTest : KotlinTestBase() { +abstract class OldSchemaAbstractToolIntegrationTest : OldSchemaKotlinTestBase() { private val testToolName = "echo" private val testToolDescription = "A simple echo tool that returns the input text" private val complexToolName = "calculator" @@ -52,11 +52,12 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() { private val specialCharsToolDescription = "A tool that handles special characters" private val specialCharsContent = "!@#$%^&*()_+{}|:\"<>?~`-=[]\\;',./\n\t" - override fun configureServerCapabilities(): ServerCapabilities = ServerCapabilities( - tools = ServerCapabilities.Tools( - listChanged = true, - ), - ) + override fun configureServerCapabilities(): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities = + ServerCapabilities( + tools = ServerCapabilities.Tools( + listChanged = true, + ), + ) override fun configureServer() { setupEchoTool() @@ -82,7 +83,7 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() { required = listOf("text"), ), ) { request -> - val text = (request.arguments["text"] as? JsonPrimitive)?.content ?: "No text provided" + val text = (request.params.arguments?.get("text") as? JsonPrimitive)?.content ?: "No text provided" CallToolResult( content = listOf(TextContent(text = "Echo: $text")), @@ -110,7 +111,7 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() { required = listOf("text"), ), ) { request -> - val text = (request.arguments["text"] as? JsonPrimitive)?.content ?: "No text provided" + val text = (request.params.arguments?.get("text") as? JsonPrimitive)?.content ?: "No text provided" CallToolResult( content = listOf(TextContent(text = "Echo: $text")), @@ -135,7 +136,7 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() { }, ), ) { request -> - val special = (request.arguments["special"] as? JsonPrimitive)?.content ?: specialCharsContent + val special = (request.params.arguments?.get("special") as? JsonPrimitive)?.content ?: specialCharsContent CallToolResult( content = listOf(TextContent(text = "Received special characters: $special")), @@ -161,7 +162,7 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() { }, ), ) { request -> - val delay = (request.arguments["delay"] as? JsonPrimitive)?.content?.toIntOrNull() ?: 1000 + val delay = (request.params.arguments?.get("delay") as? JsonPrimitive)?.content?.toIntOrNull() ?: 1000 // simulate slow operation runBlocking { @@ -191,7 +192,7 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() { }, ), ) { request -> - val size = (request.arguments["size"] as? JsonPrimitive)?.content?.toIntOrNull() ?: 1 + val size = (request.params.arguments?.get("size") as? JsonPrimitive)?.content?.toIntOrNull() ?: 1 val content = largeToolContent.take(largeToolContent.length.coerceAtMost(size * 1000)) CallToolResult( @@ -270,12 +271,13 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() { required = listOf("operation", "a", "b"), ), ) { request -> - val operation = (request.arguments["operation"] as? JsonPrimitive)?.content ?: "add" - val a = (request.arguments["a"] as? JsonPrimitive)?.content?.toDoubleOrNull() ?: 0.0 - val b = (request.arguments["b"] as? JsonPrimitive)?.content?.toDoubleOrNull() ?: 0.0 - val precision = (request.arguments["precision"] as? JsonPrimitive)?.content?.toIntOrNull() ?: 2 - val showSteps = (request.arguments["showSteps"] as? JsonPrimitive)?.content?.toBoolean() ?: false - val tags = (request.arguments["tags"] as? JsonArray)?.mapNotNull { + val operation = (request.params.arguments?.get("operation") as? JsonPrimitive)?.content ?: "add" + val a = (request.params.arguments?.get("a") as? JsonPrimitive)?.content?.toDoubleOrNull() ?: 0.0 + val b = (request.params.arguments?.get("b") as? JsonPrimitive)?.content?.toDoubleOrNull() ?: 0.0 + val precision = (request.params.arguments?.get("precision") as? JsonPrimitive)?.content?.toIntOrNull() ?: 2 + val showSteps = + (request.params.arguments?.get("showSteps") as? JsonPrimitive)?.content?.toBoolean() ?: false + val tags = (request.params.arguments?.get("tags") as? JsonArray)?.mapNotNull { (it as? JsonPrimitive)?.content } ?: emptyList() @@ -348,8 +350,8 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() { required = listOf("errorType"), ), ) { request -> - val errorType = (request.arguments["errorType"] as? JsonPrimitive)?.content ?: "none" - val message = (request.arguments["message"] as? JsonPrimitive)?.content ?: "An error occurred" + val errorType = (request.params.arguments?.get("errorType") as? JsonPrimitive)?.content ?: "none" + val message = (request.params.arguments?.get("message") as? JsonPrimitive)?.content ?: "An error occurred" when (errorType) { "exception" -> throw IllegalArgumentException(message) @@ -398,8 +400,9 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() { required = listOf("text"), ), ) { request -> - val text = (request.arguments["text"] as? JsonPrimitive)?.content ?: "Default text" - val includeImage = (request.arguments["includeImage"] as? JsonPrimitive)?.content?.toBoolean() ?: true + val text = (request.params.arguments?.get("text") as? JsonPrimitive)?.content ?: "Default text" + val includeImage = + (request.params.arguments?.get("includeImage") as? JsonPrimitive)?.content?.toBoolean() ?: true val content = mutableListOf( TextContent(text = "Text content: $text"), @@ -446,7 +449,7 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() { val testText = "Hello, world!" val arguments = mapOf("text" to testText) - val result = client.callTool(testToolName, arguments) as CallToolResultBase + val result = client.callTool(testToolName, arguments) as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult val actualContent = result.structuredContent.toString() val expectedContent = """ @@ -472,7 +475,10 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() { "tags" to listOf("test", "calculator", "integration"), ) - val result = client.callTool(complexToolName, arguments) as CallToolResultBase + val result = client.callTool( + complexToolName, + arguments, + ) as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult val actualContent = result.structuredContent.toString() val expectedContent = """ @@ -509,7 +515,10 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() { "errorType" to "error", "message" to "Custom error message", ) - val errorResult = client.callTool(errorToolName, errorArgs) as CallToolResultBase + val errorResult = client.callTool( + errorToolName, + errorArgs, + ) as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult val actualError = errorResult.structuredContent.toString() val expectedError = """ @@ -526,7 +535,10 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() { "message" to "My exception message", ) - val exceptionResult = client.callTool(errorToolName, exceptionArgs) as CallToolResultBase + val exceptionResult = client.callTool( + errorToolName, + exceptionArgs, + ) as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult assertTrue(exceptionResult.isError ?: false, "isError should be true for exception") @@ -548,7 +560,10 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() { "includeImage" to true, ) - val result = client.callTool(multiContentToolName, arguments) as CallToolResultBase + val result = client.callTool( + multiContentToolName, + arguments, + ) as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult assertEquals( 2, @@ -585,7 +600,10 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() { "includeImage" to false, ) - val textOnlyResult = client.callTool(multiContentToolName, textOnlyArgs) as CallToolResultBase + val textOnlyResult = client.callTool( + multiContentToolName, + textOnlyArgs, + ) as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult assertEquals( 1, @@ -622,10 +640,12 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() { val result = client.callTool( CallToolRequest( - name = complexToolName, - arguments = arguments, + CallToolRequestParams( + name = complexToolName, + arguments = arguments, + ), ), - ) as CallToolResultBase + ) as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult val actualContent = result.structuredContent.toString() val expectedContent = """ @@ -648,7 +668,10 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() { val size = 10 val arguments = mapOf("size" to size) - val result = client.callTool(largeToolName, arguments) as CallToolResultBase + val result = client.callTool( + largeToolName, + arguments, + ) as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult val content = result.content.firstOrNull() as TextContent assertNotNull(content, "Tool result content should be TextContent") @@ -669,7 +692,7 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() { val arguments = mapOf("delay" to delay) val startTime = System.currentTimeMillis() - val result = client.callTool(slowToolName, arguments) as CallToolResultBase + val result = client.callTool(slowToolName, arguments) as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult val endTime = System.currentTimeMillis() val content = result.content.firstOrNull() as? TextContent @@ -692,7 +715,10 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() { runBlocking(Dispatchers.IO) { val arguments = mapOf("special" to specialCharsContent) - val result = client.callTool(specialCharsToolName, arguments) as CallToolResultBase + val result = client.callTool( + specialCharsToolName, + arguments, + ) as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult val content = result.content.firstOrNull() as? TextContent assertNotNull(content, "Tool result content should be TextContent") @@ -715,7 +741,7 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() { @Test fun testConcurrentToolCalls() = runTest { val concurrentCount = 10 - val results = mutableListOf() + val results = mutableListOf() runBlocking { repeat(concurrentCount) { index -> @@ -776,7 +802,7 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() { } assertNotNull(result, "Tool call result should not be null") - val callResult = result as CallToolResult + val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult assertTrue(callResult.isError ?: false, "isError should be true for non-existent tool") val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/KotlinTestBase.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaKotlinTestBase.kt similarity index 97% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/KotlinTestBase.kt rename to kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaKotlinTestBase.kt index 66b166c..c781fdf 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/KotlinTestBase.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaKotlinTestBase.kt @@ -8,7 +8,6 @@ import io.ktor.server.engine.EmbeddedServer import io.ktor.server.engine.embeddedServer import io.ktor.server.routing.routing 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.SseClientTransport import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport @@ -34,7 +33,7 @@ import io.ktor.server.cio.CIO as ServerCIO import io.ktor.server.sse.SSE as ServerSSE @Retry(times = 3) -abstract class KotlinTestBase { +abstract class OldSchemaKotlinTestBase { protected val host = "localhost" protected var port: Int = 0 @@ -52,7 +51,7 @@ abstract class KotlinTestBase { private var stdioClientInput: Source? = null private var stdioClientOutput: Sink? = null - protected abstract fun configureServerCapabilities(): ServerCapabilities + protected abstract fun configureServerCapabilities(): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities protected abstract fun configureServer() @BeforeEach diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaPromptIntegrationTestSse.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaPromptIntegrationTestSse.kt new file mode 100644 index 0000000..63d5865 --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaPromptIntegrationTestSse.kt @@ -0,0 +1,7 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.sse + +import io.modelcontextprotocol.kotlin.sdk.integration.kotlin.OldSchemaAbstractPromptIntegrationTest + +class OldSchemaPromptIntegrationTestSse : OldSchemaAbstractPromptIntegrationTest() { + override val transportKind: TransportKind = TransportKind.SSE +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaResourceIntegrationTestSse.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaResourceIntegrationTestSse.kt new file mode 100644 index 0000000..6837753 --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaResourceIntegrationTestSse.kt @@ -0,0 +1,7 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.sse + +import io.modelcontextprotocol.kotlin.sdk.integration.kotlin.OldSchemaAbstractResourceIntegrationTest + +class OldSchemaResourceIntegrationTestSse : OldSchemaAbstractResourceIntegrationTest() { + override val transportKind: TransportKind = TransportKind.SSE +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaToolIntegrationTestSse.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaToolIntegrationTestSse.kt new file mode 100644 index 0000000..bd39d77 --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaToolIntegrationTestSse.kt @@ -0,0 +1,7 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.sse + +import io.modelcontextprotocol.kotlin.sdk.integration.kotlin.OldSchemaAbstractToolIntegrationTest + +class OldSchemaToolIntegrationTestSse : OldSchemaAbstractToolIntegrationTest() { + override val transportKind: TransportKind = TransportKind.SSE +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/PromptIntegrationTestSse.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/PromptIntegrationTestSse.kt deleted file mode 100644 index d8f218b..0000000 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/PromptIntegrationTestSse.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.sse - -import io.modelcontextprotocol.kotlin.sdk.integration.kotlin.AbstractPromptIntegrationTest - -class PromptIntegrationTestSse : AbstractPromptIntegrationTest() { - override val transportKind: TransportKind = TransportKind.SSE -} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/ResourceIntegrationTestSse.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/ResourceIntegrationTestSse.kt deleted file mode 100644 index bf1240d..0000000 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/ResourceIntegrationTestSse.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.sse - -import io.modelcontextprotocol.kotlin.sdk.integration.kotlin.AbstractResourceIntegrationTest - -class ResourceIntegrationTestSse : AbstractResourceIntegrationTest() { - override val transportKind: TransportKind = TransportKind.SSE -} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/ToolIntegrationTestSse.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/ToolIntegrationTestSse.kt deleted file mode 100644 index dd007c6..0000000 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/ToolIntegrationTestSse.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.sse - -import io.modelcontextprotocol.kotlin.sdk.integration.kotlin.AbstractToolIntegrationTest - -class ToolIntegrationTestSse : AbstractToolIntegrationTest() { - override val transportKind: TransportKind = TransportKind.SSE -} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaPromptIntegrationTestStdio.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaPromptIntegrationTestStdio.kt new file mode 100644 index 0000000..68d03eb --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaPromptIntegrationTestStdio.kt @@ -0,0 +1,7 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.stdio + +import io.modelcontextprotocol.kotlin.sdk.integration.kotlin.OldSchemaAbstractPromptIntegrationTest + +class OldSchemaPromptIntegrationTestStdio : OldSchemaAbstractPromptIntegrationTest() { + override val transportKind: TransportKind = TransportKind.STDIO +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaResourceIntegrationTestStdio.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaResourceIntegrationTestStdio.kt new file mode 100644 index 0000000..4b07760 --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaResourceIntegrationTestStdio.kt @@ -0,0 +1,7 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.stdio + +import io.modelcontextprotocol.kotlin.sdk.integration.kotlin.OldSchemaAbstractResourceIntegrationTest + +class OldSchemaResourceIntegrationTestStdio : OldSchemaAbstractResourceIntegrationTest() { + override val transportKind: TransportKind = TransportKind.STDIO +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaToolIntegrationTestStdio.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaToolIntegrationTestStdio.kt new file mode 100644 index 0000000..046eebf --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaToolIntegrationTestStdio.kt @@ -0,0 +1,7 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.stdio + +import io.modelcontextprotocol.kotlin.sdk.integration.kotlin.OldSchemaAbstractToolIntegrationTest + +class OldSchemaToolIntegrationTestStdio : OldSchemaAbstractToolIntegrationTest() { + override val transportKind: TransportKind = TransportKind.STDIO +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/PromptIntegrationTestStdio.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/PromptIntegrationTestStdio.kt deleted file mode 100644 index 88be1e8..0000000 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/PromptIntegrationTestStdio.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.stdio - -import io.modelcontextprotocol.kotlin.sdk.integration.kotlin.AbstractPromptIntegrationTest - -class PromptIntegrationTestStdio : AbstractPromptIntegrationTest() { - override val transportKind: TransportKind = TransportKind.STDIO -} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/ResourceIntegrationTestStdio.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/ResourceIntegrationTestStdio.kt deleted file mode 100644 index 88eca7b..0000000 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/ResourceIntegrationTestStdio.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.stdio - -import io.modelcontextprotocol.kotlin.sdk.integration.kotlin.AbstractResourceIntegrationTest - -class ResourceIntegrationTestStdio : AbstractResourceIntegrationTest() { - override val transportKind: TransportKind = TransportKind.STDIO -} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/ToolIntegrationTestStdio.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/ToolIntegrationTestStdio.kt deleted file mode 100644 index 673d44b..0000000 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/ToolIntegrationTestStdio.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.stdio - -import io.modelcontextprotocol.kotlin.sdk.integration.kotlin.AbstractToolIntegrationTest - -class ToolIntegrationTestStdio : AbstractToolIntegrationTest() { - override val transportKind: TransportKind = TransportKind.STDIO -} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/WebSocketIntegrationTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/OldSchemaWebSocketIntegrationTest.kt similarity index 94% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/WebSocketIntegrationTest.kt rename to kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/OldSchemaWebSocketIntegrationTest.kt index 6200e08..0ac98e3 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/WebSocketIntegrationTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/OldSchemaWebSocketIntegrationTest.kt @@ -19,6 +19,7 @@ import io.modelcontextprotocol.kotlin.sdk.client.mcpWebSocketTransport import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions import io.modelcontextprotocol.kotlin.sdk.server.mcpWebSocket +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequestParams import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.test.runTest import kotlinx.coroutines.withContext @@ -30,7 +31,7 @@ import io.ktor.client.plugins.websocket.WebSockets as ClientWebSocket import io.ktor.server.cio.CIO as ServerCIO import io.ktor.server.websocket.WebSockets as ServerWebSockets -class WebSocketIntegrationTest { +class OldSchemaWebSocketIntegrationTest { @Test fun `client should be able to connect to websocket server 2`() = runTest(timeout = 5.seconds) { @@ -154,11 +155,11 @@ class WebSocketIntegrationTest { ), ) { request -> GetPromptResult( - "Prompt for ${request.name}", + description = "Prompt for ${request.params.name}", messages = listOf( PromptMessage( role = Role.user, - content = TextContent("Prompt for client ${request.arguments?.get("client")}"), + content = TextContent("Prompt for client ${request.params.arguments?.get("client")}"), ), ), ) @@ -180,8 +181,10 @@ class WebSocketIntegrationTest { private suspend fun getPrompt(client: Client, clientName: String): String { val response = client.getPrompt( GetPromptRequest( - "prompt", - arguments = mapOf("client" to clientName), + GetPromptRequestParams( + "prompt", + arguments = mapOf("client" to clientName), + ), ), ) diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/AbstractSseIntegrationTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaAbstractSseIntegrationTest.kt similarity index 95% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/AbstractSseIntegrationTest.kt rename to kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaAbstractSseIntegrationTest.kt index 2628925..a0b3e19 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/AbstractSseIntegrationTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaAbstractSseIntegrationTest.kt @@ -23,7 +23,7 @@ import io.ktor.client.engine.cio.CIO as ClientCIO import io.ktor.server.cio.CIO as ServerCIO import io.ktor.server.sse.SSE as ServerSSE -open class AbstractSseIntegrationTest { +open class OldSchemaAbstractSseIntegrationTest { suspend fun EmbeddedServer<*, *>.actualPort() = engine.resolvedConnectors().single().port @@ -70,11 +70,11 @@ open class AbstractSseIntegrationTest { ), ) { request -> GetPromptResult( - "Prompt for ${request.name}", + description = "Prompt for ${request.params.name}", messages = listOf( PromptMessage( role = Role.user, - content = TextContent("Prompt for client ${request.arguments?.get("client")}"), + content = TextContent("Prompt for client ${request.params.arguments?.get("client")}"), ), ), ) diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/SseIntegrationTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaSseIntegrationTest.kt similarity index 93% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/SseIntegrationTest.kt rename to kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaSseIntegrationTest.kt index 55e833b..a5cb133 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/SseIntegrationTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaSseIntegrationTest.kt @@ -5,6 +5,7 @@ import io.ktor.server.engine.EmbeddedServer import io.modelcontextprotocol.kotlin.sdk.GetPromptRequest import io.modelcontextprotocol.kotlin.sdk.TextContent import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequestParams import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.test.runTest import kotlinx.coroutines.withContext @@ -12,7 +13,7 @@ import kotlin.test.Test import kotlin.test.assertTrue import kotlin.time.Duration.Companion.seconds -class SseIntegrationTest : AbstractSseIntegrationTest() { +class OldSchemaSseIntegrationTest : OldSchemaAbstractSseIntegrationTest() { @Test fun `client should be able to connect to sse server`() = runTest(timeout = 5.seconds) { @@ -100,8 +101,10 @@ class SseIntegrationTest : AbstractSseIntegrationTest() { private suspend fun getPrompt(client: Client, clientName: String): String { val response = client.getPrompt( GetPromptRequest( - "prompt", - arguments = mapOf("client" to clientName), + GetPromptRequestParams( + "prompt", + arguments = mapOf("client" to clientName), + ), ), ) diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractKotlinClientTsServerTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/OldSchemaAbstractKotlinClientTsServerTest.kt similarity index 95% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractKotlinClientTsServerTest.kt rename to kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/OldSchemaAbstractKotlinClientTsServerTest.kt index 5117998..5d0406c 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractKotlinClientTsServerTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/OldSchemaAbstractKotlinClientTsServerTest.kt @@ -1,6 +1,5 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript -import io.modelcontextprotocol.kotlin.sdk.CallToolResult import io.modelcontextprotocol.kotlin.sdk.TextContent import io.modelcontextprotocol.kotlin.sdk.client.Client import kotlinx.coroutines.Dispatchers @@ -12,7 +11,7 @@ import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue -abstract class AbstractKotlinClientTsServerTest : TsTestBase() { +abstract class OldSchemaAbstractKotlinClientTsServerTest : TsTestBase() { protected abstract suspend fun useClient(block: suspend (Client) -> T): T @Test @@ -50,7 +49,7 @@ abstract class AbstractKotlinClientTsServerTest : TsTestBase() { val arguments = mapOf("name" to testName) val result = client.callTool("greet", arguments) assertNotNull(result, "Tool call result should not be null") - val callResult = result as CallToolResult + val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent assertNotNull(textContent, "Text content should be present in the result") assertEquals("Hello, $testName!", textContent.text) diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerTestSse.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerTestSse.kt index 95a80f0..51fd2bf 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerTestSse.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerTestSse.kt @@ -1,14 +1,14 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.AbstractKotlinClientTsServerTest +import io.modelcontextprotocol.kotlin.sdk.integration.typescript.OldSchemaAbstractKotlinClientTsServerTest import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind import kotlinx.coroutines.withTimeout import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import kotlin.time.Duration.Companion.seconds -class KotlinClientTsServerTestSse : AbstractKotlinClientTsServerTest() { +class KotlinClientTsServerTestSse : OldSchemaAbstractKotlinClientTsServerTest() { override val transportKind = TransportKind.SSE diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerEdgeCasesTestSse.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerEdgeCasesTestSse.kt similarity index 92% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerEdgeCasesTestSse.kt rename to kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerEdgeCasesTestSse.kt index de27566..44ff5a4 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerEdgeCasesTestSse.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerEdgeCasesTestSse.kt @@ -1,6 +1,5 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse -import io.modelcontextprotocol.kotlin.sdk.CallToolResult import io.modelcontextprotocol.kotlin.sdk.TextContent import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind @@ -23,7 +22,7 @@ import kotlin.test.assertNotNull import kotlin.test.assertTrue import kotlin.time.Duration.Companion.seconds -class KotlinClientTsServerEdgeCasesTestSse : TsTestBase() { +class OldSchemaKotlinClientTsServerEdgeCasesTestSse : TsTestBase() { override val transportKind = TransportKind.SSE @@ -76,7 +75,7 @@ class KotlinClientTsServerEdgeCasesTestSse : TsTestBase() { val result = client.callTool(nonExistentToolName, arguments) assertNotNull(result, "Tool call result should not be null") - val callResult = result as CallToolResult + val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult assertTrue(callResult.isError ?: false, "isError should be true for non-existent tool") val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent @@ -100,7 +99,7 @@ class KotlinClientTsServerEdgeCasesTestSse : TsTestBase() { val result = client.callTool("greet", arguments) assertNotNull(result, "Tool call result should not be null") - val callResult = result as CallToolResult + val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent assertNotNull(textContent, "Text content should be present in the result") @@ -122,7 +121,7 @@ class KotlinClientTsServerEdgeCasesTestSse : TsTestBase() { val result = client.callTool("greet", arguments) assertNotNull(result, "Tool call result should not be null") - val callResult = result as CallToolResult + val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent assertNotNull(textContent, "Text content should be present in the result") @@ -148,7 +147,7 @@ class KotlinClientTsServerEdgeCasesTestSse : TsTestBase() { val result = client.callTool("greet", arguments) assertNotNull(result, "Tool call result should not be null for client $i") - val callResult = result as CallToolResult + val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent assertNotNull(textContent, "Text content should be present for client $i") @@ -181,7 +180,7 @@ class KotlinClientTsServerEdgeCasesTestSse : TsTestBase() { val result = client.callTool("greet", invalidArguments) assertNotNull(result, "Tool call result should not be null") - val callResult = result as CallToolResult + val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult assertTrue(callResult.isError ?: false, "isError should be true for invalid arguments") val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent @@ -206,7 +205,7 @@ class KotlinClientTsServerEdgeCasesTestSse : TsTestBase() { val result = client.callTool("greet", arguments) assertNotNull(result, "Tool call result should not be null for call $i") - val callResult = result as CallToolResult + val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent assertNotNull(textContent, "Text content should be present for call $i") diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinServerForTsClientSse.kt similarity index 96% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt rename to kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinServerForTsClientSse.kt index c51454d..5101bd8 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinServerForTsClientSse.kt @@ -39,6 +39,7 @@ import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport import io.modelcontextprotocol.kotlin.sdk.shared.McpJson +import io.modelcontextprotocol.kotlin.sdk.types.RPCError import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.channels.Channel @@ -224,7 +225,7 @@ class KotlinServerForTsClient { required = listOf("name"), ), ) { request -> - val name = (request.arguments["name"] as? JsonPrimitive)?.content ?: "World" + val name = (request.params.arguments?.get("name") as? JsonPrimitive)?.content ?: "World" CallToolResult( content = listOf(TextContent("Hello, $name!")), structuredContent = buildJsonObject { @@ -249,7 +250,7 @@ class KotlinServerForTsClient { required = listOf("name"), ), ) { request -> - val name = (request.arguments["name"] as? JsonPrimitive)?.content ?: "World" + val name = (request.params.arguments?.get("name") as? JsonPrimitive)?.content ?: "World" CallToolResult( content = listOf(TextContent("Multiple greetings sent to $name!")), @@ -272,12 +273,12 @@ class KotlinServerForTsClient { ), ) { request -> GetPromptResult( - "Greeting for ${request.name}", + description = "Greeting for ${request.params.name}", messages = listOf( PromptMessage( role = Role.user, content = TextContent( - "Please greet ${request.arguments?.get("name") ?: "someone"} in a friendly manner.", + "Please greet ${request.params.arguments?.get("name") ?: "someone"} in a friendly manner.", ), ), ), @@ -292,7 +293,7 @@ class KotlinServerForTsClient { ) { request -> ReadResourceResult( contents = listOf( - TextResourceContents("Hello, world!", request.uri, "text/plain"), + TextResourceContents("Hello, world!", request.params.uri, "text/plain"), ), ) } @@ -366,9 +367,9 @@ class HttpServerTransport(private val sessionId: String) : AbstractTransport() { logger.warn { "Timeout waiting for response to request ID: $id" } call.respondText( McpJson.encodeToString( - JSONRPCResponse( + JSONRPCError( id = message.id, - error = JSONRPCError( + error = RPCError( code = ErrorCode.Defined.RequestTimeout, message = "Request timed out", ), @@ -383,9 +384,9 @@ class HttpServerTransport(private val sessionId: String) : AbstractTransport() { if (!call.response.isCommitted) { call.respondText( McpJson.encodeToString( - JSONRPCResponse( + JSONRPCError( id = message.id, - error = JSONRPCError( + error = RPCError( code = ErrorCode.Defined.ConnectionClosed, message = "Request cancelled", ), @@ -403,9 +404,9 @@ class HttpServerTransport(private val sessionId: String) : AbstractTransport() { logger.error(e) { "Error handling request" } if (!call.response.isCommitted) { try { - val errorResponse = JSONRPCResponse( + val errorResponse = JSONRPCError( id = RequestId.NumberId(0), - error = JSONRPCError( + error = RPCError( code = ErrorCode.Defined.InternalError, message = "Internal server error: ${e.message}", ), diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerEdgeCasesTestStdio.kt similarity index 91% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt rename to kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerEdgeCasesTestStdio.kt index b6e385f..0ed8676 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerEdgeCasesTestStdio.kt @@ -1,6 +1,5 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.stdio -import io.modelcontextprotocol.kotlin.sdk.CallToolResult import io.modelcontextprotocol.kotlin.sdk.TextContent import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind @@ -19,7 +18,7 @@ import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue -class KotlinClientTsServerEdgeCasesTestStdio : TsTestBase() { +class OldSchemaKotlinClientTsServerEdgeCasesTestStdio : TsTestBase() { override val transportKind = TransportKind.STDIO @@ -33,7 +32,7 @@ class KotlinClientTsServerEdgeCasesTestStdio : TsTestBase() { val result = client.callTool(nonExistentToolName, arguments) assertNotNull(result, "Tool call result should not be null") - val callResult = result as CallToolResult + val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult assertTrue(callResult.isError ?: false, "isError should be true for non-existent tool") val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent @@ -57,7 +56,7 @@ class KotlinClientTsServerEdgeCasesTestStdio : TsTestBase() { val result = client.callTool("greet", arguments) assertNotNull(result, "Tool call result should not be null") - val callResult = result as CallToolResult + val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent assertNotNull(textContent, "Text content should be present in the result") @@ -79,7 +78,7 @@ class KotlinClientTsServerEdgeCasesTestStdio : TsTestBase() { val result = client.callTool("greet", arguments) assertNotNull(result, "Tool call result should not be null") - val callResult = result as CallToolResult + val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent assertNotNull(textContent, "Text content should be present in the result") @@ -105,7 +104,7 @@ class KotlinClientTsServerEdgeCasesTestStdio : TsTestBase() { val result = client.callTool("greet", arguments) assertNotNull(result, "Tool call result should not be null for client $i") - val callResult = result as CallToolResult + val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent assertNotNull(textContent, "Text content should be present for client $i") @@ -138,7 +137,7 @@ class KotlinClientTsServerEdgeCasesTestStdio : TsTestBase() { val result = client.callTool("greet", invalidArguments) assertNotNull(result, "Tool call result should not be null") - val callResult = result as CallToolResult + val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult assertTrue(callResult.isError ?: false, "isError should be true for invalid arguments") val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent @@ -163,7 +162,7 @@ class KotlinClientTsServerEdgeCasesTestStdio : TsTestBase() { val result = client.callTool("greet", arguments) assertNotNull(result, "Tool call result should not be null for call $i") - val callResult = result as CallToolResult + val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent assertNotNull(textContent, "Text content should be present for call $i") diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerTestStdio.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerTestStdio.kt similarity index 84% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerTestStdio.kt rename to kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerTestStdio.kt index 8273724..e87a5c8 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerTestStdio.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerTestStdio.kt @@ -1,10 +1,10 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.stdio import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.AbstractKotlinClientTsServerTest +import io.modelcontextprotocol.kotlin.sdk.integration.typescript.OldSchemaAbstractKotlinClientTsServerTest import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind -class KotlinClientTsServerTestStdio : AbstractKotlinClientTsServerTest() { +class OldSchemaKotlinClientTsServerTestStdio : OldSchemaAbstractKotlinClientTsServerTest() { override val transportKind = TransportKind.STDIO diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/AbstractServerFeaturesTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaAbstractServerFeaturesTest.kt similarity index 88% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/AbstractServerFeaturesTest.kt rename to kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaAbstractServerFeaturesTest.kt index 3feea02..d74c0b0 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/AbstractServerFeaturesTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaAbstractServerFeaturesTest.kt @@ -1,19 +1,18 @@ package io.modelcontextprotocol.kotlin.sdk.server import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.BeforeEach -abstract class AbstractServerFeaturesTest { +abstract class OldSchemaAbstractServerFeaturesTest { protected lateinit var server: Server protected lateinit var client: Client - abstract fun getServerCapabilities(): ServerCapabilities + abstract fun getServerCapabilities(): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities protected open fun getServerInstructionsProvider(): (() -> String)? = null diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerPromptsTest.kt similarity index 87% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsTest.kt rename to kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerPromptsTest.kt index ffaff6b..fe0826b 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerPromptsTest.kt @@ -4,7 +4,6 @@ import io.modelcontextprotocol.kotlin.sdk.GetPromptResult import io.modelcontextprotocol.kotlin.sdk.Implementation import io.modelcontextprotocol.kotlin.sdk.Method import io.modelcontextprotocol.kotlin.sdk.Prompt -import io.modelcontextprotocol.kotlin.sdk.PromptListChangedNotification import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.test.runTest @@ -14,11 +13,12 @@ import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue -class ServerPromptsTest : AbstractServerFeaturesTest() { +class OldSchemaServerPromptsTest : OldSchemaAbstractServerFeaturesTest() { - override fun getServerCapabilities(): ServerCapabilities = ServerCapabilities( - prompts = ServerCapabilities.Prompts(false), - ) + override fun getServerCapabilities(): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities = + ServerCapabilities( + prompts = ServerCapabilities.Prompts(false), + ) @Test fun `removePrompt should remove a prompt`() = runTest { @@ -67,7 +67,9 @@ class ServerPromptsTest : AbstractServerFeaturesTest() { fun `removePrompt should return false when prompt does not exist`() = runTest { // Track notifications var promptListChangedNotificationReceived = false - client.setNotificationHandler(Method.Defined.NotificationsPromptsListChanged) { + client.setNotificationHandler( + Method.Defined.NotificationsPromptsListChanged, + ) { promptListChangedNotificationReceived = true CompletableDeferred(Unit) } diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerResourcesTest.kt similarity index 91% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesTest.kt rename to kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerResourcesTest.kt index ac9e979..90cb9a0 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerResourcesTest.kt @@ -3,7 +3,6 @@ package io.modelcontextprotocol.kotlin.sdk.server import io.modelcontextprotocol.kotlin.sdk.Implementation import io.modelcontextprotocol.kotlin.sdk.Method import io.modelcontextprotocol.kotlin.sdk.ReadResourceResult -import io.modelcontextprotocol.kotlin.sdk.ResourceListChangedNotification import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.TextResourceContents import kotlinx.coroutines.CompletableDeferred @@ -14,11 +13,12 @@ import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue -class ServerResourcesTest : AbstractServerFeaturesTest() { +class OldSchemaServerResourcesTest : OldSchemaAbstractServerFeaturesTest() { - override fun getServerCapabilities(): ServerCapabilities = ServerCapabilities( - resources = ServerCapabilities.Resources(null, null), - ) + override fun getServerCapabilities(): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities = + ServerCapabilities( + resources = ServerCapabilities.Resources(null, null), + ) @Test fun `removeResource should remove a resource and send notification`() = runTest { @@ -97,7 +97,7 @@ class ServerResourcesTest : AbstractServerFeaturesTest() { fun `removeResource should return false when resource does not exist`() = runTest { // Track notifications var resourceListChangedNotificationReceived = false - client.setNotificationHandler( + client.setNotificationHandler( Method.Defined.NotificationsResourcesListChanged, ) { resourceListChangedNotificationReceived = true diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerToolsTest.kt similarity index 86% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsTest.kt rename to kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerToolsTest.kt index 2ccb460..b956e2c 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerToolsTest.kt @@ -6,7 +6,6 @@ import io.modelcontextprotocol.kotlin.sdk.Method import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.TextContent import io.modelcontextprotocol.kotlin.sdk.Tool -import io.modelcontextprotocol.kotlin.sdk.ToolListChangedNotification import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Test @@ -15,11 +14,12 @@ import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue -class ServerToolsTest : AbstractServerFeaturesTest() { +class OldSchemaServerToolsTest : OldSchemaAbstractServerFeaturesTest() { - override fun getServerCapabilities(): ServerCapabilities = ServerCapabilities( - tools = ServerCapabilities.Tools(null), - ) + override fun getServerCapabilities(): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities = + ServerCapabilities( + tools = ServerCapabilities.Tools(null), + ) @Test fun `removeTool should remove a tool`() = runTest { @@ -39,7 +39,9 @@ class ServerToolsTest : AbstractServerFeaturesTest() { fun `removeTool should return false when tool does not exist`() = runTest { // Track notifications var toolListChangedNotificationReceived = false - client.setNotificationHandler(Method.Defined.NotificationsToolsListChanged) { + client.setNotificationHandler( + Method.Defined.NotificationsToolsListChanged, + ) { toolListChangedNotificationReceived = true CompletableDeferred(Unit) } diff --git a/samples/kotlin-mcp-client/src/main/kotlin/io/modelcontextprotocol/sample/client/MCPClient.kt b/samples/kotlin-mcp-client/src/main/kotlin/io/modelcontextprotocol/sample/client/MCPClient.kt index d9d482d..0a0817e 100644 --- a/samples/kotlin-mcp-client/src/main/kotlin/io/modelcontextprotocol/sample/client/MCPClient.kt +++ b/samples/kotlin-mcp-client/src/main/kotlin/io/modelcontextprotocol/sample/client/MCPClient.kt @@ -9,6 +9,7 @@ import com.anthropic.models.messages.Tool import com.anthropic.models.messages.ToolUnion import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.databind.ObjectMapper +import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject import io.modelcontextprotocol.kotlin.sdk.Implementation import io.modelcontextprotocol.kotlin.sdk.TextContent import io.modelcontextprotocol.kotlin.sdk.client.Client @@ -76,7 +77,7 @@ class MCPClient : AutoCloseable { .inputSchema( Tool.InputSchema.builder() .type(JsonValue.from(tool.inputSchema.type)) - .properties(tool.inputSchema.properties.toJsonValue()) + .properties(tool.inputSchema.properties?.toJsonValue() ?: EmptyJsonObject.toJsonValue()) .putAdditionalProperty("required", JsonValue.from(tool.inputSchema.required)) .build(), ) @@ -135,9 +136,11 @@ class MCPClient : AutoCloseable { """ "type": "tool_result", "tool_name": $toolName, - "result": ${result?.content?.joinToString("\n") { - (it as TextContent).text ?: "" - }} + "result": ${ + result?.content?.joinToString("\n") { + (it as TextContent).text ?: "" + } + } """.trimIndent(), ) .build(), diff --git a/samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/server.kt b/samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/server.kt index fc82c5d..eb61071 100644 --- a/samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/server.kt +++ b/samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/server.kt @@ -64,7 +64,6 @@ fun configureServer(): Server { ), ) { request -> GetPromptResult( - "Description for ${request.name}", messages = listOf( PromptMessage( role = Role.user, @@ -73,6 +72,7 @@ fun configureServer(): Server { ), ), ), + description = "Description for ${request.name}", ) } diff --git a/samples/kotlin-mcp-server/src/test/kotlin/SseServerIntegrationTest.kt b/samples/kotlin-mcp-server/src/test/kotlin/SseServerIntegrationTest.kt index 8399173..09780ab 100644 --- a/samples/kotlin-mcp-server/src/test/kotlin/SseServerIntegrationTest.kt +++ b/samples/kotlin-mcp-server/src/test/kotlin/SseServerIntegrationTest.kt @@ -2,7 +2,6 @@ import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject import io.modelcontextprotocol.kotlin.sdk.TextContent import io.modelcontextprotocol.kotlin.sdk.client.Client import kotlinx.coroutines.runBlocking -import org.junit.jupiter.api.TestInstance import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs @@ -39,6 +38,6 @@ class SseServerIntegrationTest { assertIs(content, "Tool result should be a text content") assertEquals(expected = "Hello, world!", actual = content.text) - assertEquals(expected = "text", actual = content.type) + assertEquals(expected = "text", actual = "${content.type}".lowercase()) } } diff --git a/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt b/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt index eb921a9..a0d1968 100644 --- a/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt +++ b/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt @@ -82,7 +82,7 @@ fun runMcpServer() { required = listOf("state"), ), ) { request -> - val state = request.arguments["state"]?.jsonPrimitive?.content ?: return@addTool CallToolResult( + val state = request.arguments?.get("state")?.jsonPrimitive?.content ?: return@addTool CallToolResult( content = listOf(TextContent("The 'state' parameter is required.")), ) @@ -109,8 +109,8 @@ fun runMcpServer() { required = listOf("latitude", "longitude"), ), ) { request -> - val latitude = request.arguments["latitude"]?.jsonPrimitive?.doubleOrNull - val longitude = request.arguments["longitude"]?.jsonPrimitive?.doubleOrNull + val latitude = request.arguments?.get("latitude")?.jsonPrimitive?.doubleOrNull + val longitude = request.arguments?.get("longitude")?.jsonPrimitive?.doubleOrNull if (latitude == null || longitude == null) { return@addTool CallToolResult( content = listOf(TextContent("The 'latitude' and 'longitude' parameters are required.")), diff --git a/samples/weather-stdio-server/src/test/kotlin/io/modelcontextprotocol/sample/client/ClientStdio.kt b/samples/weather-stdio-server/src/test/kotlin/io/modelcontextprotocol/sample/client/ClientStdio.kt index c08d156..480bc1c 100644 --- a/samples/weather-stdio-server/src/test/kotlin/io/modelcontextprotocol/sample/client/ClientStdio.kt +++ b/samples/weather-stdio-server/src/test/kotlin/io/modelcontextprotocol/sample/client/ClientStdio.kt @@ -1,6 +1,5 @@ package io.modelcontextprotocol.sample.client -import io.modelcontextprotocol.kotlin.sdk.CallToolRequest import io.modelcontextprotocol.kotlin.sdk.Implementation import io.modelcontextprotocol.kotlin.sdk.TextContent import io.modelcontextprotocol.kotlin.sdk.client.Client @@ -9,9 +8,6 @@ import kotlinx.coroutines.runBlocking import kotlinx.io.asSink import kotlinx.io.asSource import kotlinx.io.buffered -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.buildJsonObject fun main(): Unit = runBlocking { val process = ProcessBuilder("java", "-jar", "./build/libs/weather-stdio-server-0.1.0-all.jar") @@ -33,12 +29,10 @@ fun main(): Unit = runBlocking { println("Available Tools = $toolsList") val weatherForecastResult = client.callTool( - CallToolRequest( - name = "get_forecast", - arguments = buildJsonObject { - put("latitude", JsonPrimitive(38.5816)) - put("longitude", JsonPrimitive(-121.4944)) - }, + name = "get_forecast", + arguments = mapOf( + "latitude" to 38.5816, + "longitude" to -121.4944, ), )?.content?.map { if (it is TextContent) it.text else it.toString() } @@ -46,10 +40,8 @@ fun main(): Unit = runBlocking { val alertResult = client.callTool( - CallToolRequest( - name = "get_alerts", - arguments = JsonObject(mapOf("state" to JsonPrimitive("TX"))), - ), + name = "get_alert", + arguments = mapOf("state" to "TX"), )?.content?.map { if (it is TextContent) it.text else it.toString() } println("Alert Response = $alertResult") From 0e6a41dcd587f2f0e5940f561335d1479bb85114 Mon Sep 17 00:00:00 2001 From: Maria Tigina <31625351+tiginamaria@users.noreply.github.com> Date: Tue, 11 Nov 2025 13:15:09 +0100 Subject: [PATCH 060/449] Add server session id (#381) ## Motivation and Context Add ids for server sessions (auto generated when created) Support methods proxying from server to server session by id ## How Has This Been Tested? ## Breaking Changes ## 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 - [ ] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [ ] My code follows the repository's style guidelines - [ ] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed ## Additional context --- kotlin-sdk-server/api/kotlin-sdk-server.api | 16 ++ .../kotlin/sdk/server/Server.kt | 153 +++++++++++++++++- .../kotlin/sdk/server/ServerSession.kt | 18 +++ .../sdk/server/ServerSessionRegistry.kt | 60 +++++++ 4 files changed, 239 insertions(+), 8 deletions(-) create mode 100644 kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSessionRegistry.kt diff --git a/kotlin-sdk-server/api/kotlin-sdk-server.api b/kotlin-sdk-server/api/kotlin-sdk-server.api index 48e2ca7..045addf 100644 --- a/kotlin-sdk-server/api/kotlin-sdk-server.api +++ b/kotlin-sdk-server/api/kotlin-sdk-server.api @@ -65,22 +65,35 @@ public class io/modelcontextprotocol/kotlin/sdk/server/Server { public final fun addTools (Ljava/util/List;)V public final fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun connect (Lio/modelcontextprotocol/kotlin/sdk/shared/Transport;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun createElicitation (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun createElicitation$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun createMessage (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun createMessage$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun createSession (Lio/modelcontextprotocol/kotlin/sdk/shared/Transport;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; protected final fun getInstructionsProvider ()Lkotlin/jvm/functions/Function0; protected final fun getOptions ()Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions; public final fun getPrompts ()Ljava/util/Map; public final fun getResources ()Ljava/util/Map; protected final fun getServerInfo ()Lio/modelcontextprotocol/kotlin/sdk/types/Implementation; + public final fun getSessions ()Ljava/util/Map; public final fun getTools ()Ljava/util/Map; + public final fun listRoots (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun listRoots$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun onClose (Lkotlin/jvm/functions/Function0;)V public final fun onConnect (Lkotlin/jvm/functions/Function0;)V public final fun onInitialized (Lkotlin/jvm/functions/Function0;)V + public final fun ping (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun removePrompt (Ljava/lang/String;)Z public final fun removePrompts (Ljava/util/List;)I public final fun removeResource (Ljava/lang/String;)Z 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 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; + public final fun sendResourceUpdated (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotification;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun sendToolListChanged (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class io/modelcontextprotocol/kotlin/sdk/server/ServerOptions : io/modelcontextprotocol/kotlin/sdk/shared/ProtocolOptions { @@ -98,10 +111,13 @@ public class io/modelcontextprotocol/kotlin/sdk/server/ServerSession : io/modelc public static synthetic fun createElicitation$default (Lio/modelcontextprotocol/kotlin/sdk/server/ServerSession;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun createMessage (Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun createMessage$default (Lio/modelcontextprotocol/kotlin/sdk/server/ServerSession;Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public fun equals (Ljava/lang/Object;)Z public final fun getClientCapabilities ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities; public final fun getClientVersion ()Lio/modelcontextprotocol/kotlin/sdk/types/Implementation; protected final fun getInstructions ()Ljava/lang/String; protected final fun getServerInfo ()Lio/modelcontextprotocol/kotlin/sdk/types/Implementation; + public final fun getSessionId ()Ljava/lang/String; + public fun hashCode ()I public final fun listRoots (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun listRoots$default (Lio/modelcontextprotocol/kotlin/sdk/server/ServerSession;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public fun onClose ()V diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt index 3ce426f..7fa7a98 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt @@ -2,9 +2,16 @@ package io.modelcontextprotocol.kotlin.sdk.server import io.github.oshai.kotlinlogging.KotlinLogging import io.modelcontextprotocol.kotlin.sdk.shared.ProtocolOptions +import io.modelcontextprotocol.kotlin.sdk.shared.RequestOptions import io.modelcontextprotocol.kotlin.sdk.shared.Transport import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult +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.EmptyJsonObject +import io.modelcontextprotocol.kotlin.sdk.types.EmptyResult import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult import io.modelcontextprotocol.kotlin.sdk.types.Implementation @@ -14,22 +21,22 @@ import io.modelcontextprotocol.kotlin.sdk.types.ListResourceTemplatesRequest import io.modelcontextprotocol.kotlin.sdk.types.ListResourceTemplatesResult import io.modelcontextprotocol.kotlin.sdk.types.ListResourcesRequest import io.modelcontextprotocol.kotlin.sdk.types.ListResourcesResult +import io.modelcontextprotocol.kotlin.sdk.types.ListRootsResult import io.modelcontextprotocol.kotlin.sdk.types.ListToolsRequest import io.modelcontextprotocol.kotlin.sdk.types.ListToolsResult +import io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotification import io.modelcontextprotocol.kotlin.sdk.types.Method import io.modelcontextprotocol.kotlin.sdk.types.Prompt import io.modelcontextprotocol.kotlin.sdk.types.PromptArgument import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult import io.modelcontextprotocol.kotlin.sdk.types.Resource +import io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotification import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.types.TextContent import io.modelcontextprotocol.kotlin.sdk.types.Tool import io.modelcontextprotocol.kotlin.sdk.types.ToolAnnotations import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema -import kotlinx.atomicfu.atomic -import kotlinx.atomicfu.update -import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.CancellationException import kotlinx.serialization.json.JsonObject @@ -45,7 +52,7 @@ public class ServerOptions(public val capabilities: ServerCapabilities, enforceS ProtocolOptions(enforceStrictCapabilities = enforceStrictCapabilities) /** - * An MCP server on top of a pluggable transport. + * An MCP server is responsible for storing features and handling new connections. * * This server automatically responds to the initialization flow as initiated by the client. * You can register tools, prompts, and resources using [addTool], [addPrompt], and [addResource]. @@ -79,7 +86,13 @@ public open class Server( block: Server.() -> Unit = {}, ) : this(serverInfo, options, { instructions }, block) - private val sessions = atomic(persistentListOf()) + private val sessionRegistry = ServerSessionRegistry() + + /** + * Provides a snapshot of all sessions currently registered in the server + */ + public val sessions: Map + get() = sessionRegistry.sessions @Suppress("ktlint:standard:backing-property-naming") private var _onInitialized: (() -> Unit) = {} @@ -107,7 +120,10 @@ public open class Server( public suspend fun close() { logger.debug { "Closing MCP server" } - sessions.value.forEach { session -> session.close() } + sessions.forEach { (sessionId, session) -> + logger.info { "Closing session $sessionId" } + session.close() + } _onClose() } @@ -171,12 +187,12 @@ public open class Server( // Register cleanup handler to remove session from list when it closes session.onClose { logger.debug { "Removing closed session from active sessions list" } - sessions.update { list -> list.remove(session) } + sessionRegistry.removeSession(session.sessionId) } logger.debug { "Server session connecting to transport" } session.connect(transport) logger.debug { "Server session successfully connected to transport" } - sessions.update { sessions -> sessions.add(session) } + sessionRegistry.addSession(session) _onConnect() return session @@ -538,4 +554,125 @@ public open class Server( // If you have resource templates, return them here. For now, return empty. return ListResourceTemplatesResult(listOf()) } + + // Start the ServerSession redirection section + + /** + * Triggers [ServerSession.ping] request for session by provided [sessionId]. + * @param sessionId The session ID to ping + */ + public suspend fun ping(sessionId: String): EmptyResult = with(sessionRegistry.getSession(sessionId)) { + ping() + } + + /** + * Triggers [ServerSession.createMessage] request for session by provided [sessionId]. + * + * @param sessionId The session ID to create a message. + * @param params The parameters for creating a message. + * @param options Optional request options. + * @return The created message result. + * @throws IllegalStateException If the server does not support sampling or if the request fails. + */ + public suspend fun createMessage( + sessionId: String, + params: CreateMessageRequest, + options: RequestOptions? = null, + ): CreateMessageResult = with(sessionRegistry.getSession(sessionId)) { + request(params, options) + } + + /** + * Triggers [ServerSession.listRoots] request for session by provided [sessionId]. + * + * @param sessionId The session ID to list roots for. + * @param params JSON parameters for the request, usually empty. + * @param options Optional request options. + * @return The list of roots. + * @throws IllegalStateException If the server or client does not support roots. + */ + public suspend fun listRoots( + sessionId: String, + params: JsonObject = EmptyJsonObject, + options: RequestOptions? = null, + ): ListRootsResult = with(sessionRegistry.getSession(sessionId)) { + listRoots(params, options) + } + + /** + * Triggers [ServerSession.createElicitation] request for session by provided [sessionId]. + * + * @param sessionId The session ID to create elicitation for. + * @param message The elicitation message. + * @param requestedSchema The requested schema for the elicitation. + * @param options Optional request options. + * @return The created elicitation result. + * @throws IllegalStateException If the server does not support elicitation or if the request fails. + */ + public suspend fun createElicitation( + sessionId: String, + message: String, + requestedSchema: ElicitRequestParams.RequestedSchema, + options: RequestOptions? = null, + ): ElicitResult = with(sessionRegistry.getSession(sessionId)) { + createElicitation(message, requestedSchema, options) + } + + /** + * Triggers [ServerSession.sendLoggingMessage] for session by provided [sessionId]. + * + * @param sessionId The session ID to send the logging message to. + * @param notification The logging message notification. + */ + public suspend fun sendLoggingMessage(sessionId: String, notification: LoggingMessageNotification) { + with(sessionRegistry.getSession(sessionId)) { + sendLoggingMessage(notification) + } + } + + /** + * Triggers [ServerSession.sendResourceUpdated] for session by provided [sessionId]. + * + * @param sessionId The session ID to send the resource updated notification to. + * @param notification Details of the updated resource. + */ + public suspend fun sendResourceUpdated(sessionId: String, notification: ResourceUpdatedNotification) { + with(sessionRegistry.getSession(sessionId)) { + sendResourceUpdated(notification) + } + } + + /** + * Triggers [ServerSession.sendResourceListChanged] for session by provided [sessionId]. + * + * @param sessionId The session ID to send the resource list changed notification to. + */ + public suspend fun sendResourceListChanged(sessionId: String) { + with(sessionRegistry.getSession(sessionId)) { + sendResourceListChanged() + } + } + + /** + * Triggers [ServerSession.sendToolListChanged] for session by provided [sessionId]. + * + * @param sessionId The session ID to send the tool list changed notification to. + */ + public suspend fun sendToolListChanged(sessionId: String) { + with(sessionRegistry.getSession(sessionId)) { + sendToolListChanged() + } + } + + /** + * Triggers [ServerSession.sendPromptListChanged] for session by provided [sessionId]. + * + * @param sessionId The session ID to send the prompt list changed notification to. + */ + public suspend fun sendPromptListChanged(sessionId: String) { + with(sessionRegistry.getSession(sessionId)) { + sendPromptListChanged() + } + } + // End the ServerSession redirection section } diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt index 53d47b6..e65db2f 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt @@ -35,14 +35,24 @@ import kotlinx.atomicfu.AtomicRef import kotlinx.atomicfu.atomic import kotlinx.coroutines.CompletableDeferred import kotlinx.serialization.json.JsonObject +import kotlin.uuid.ExperimentalUuidApi +import kotlin.uuid.Uuid private val logger = KotlinLogging.logger {} +/** + * Represents a server session. + */ +@Suppress("TooManyFunctions") public open class ServerSession( protected val serverInfo: Implementation, options: ServerOptions, protected val instructions: String?, ) : Protocol(options) { + + @OptIn(ExperimentalUuidApi::class) + public val sessionId: String = Uuid.random().toString() + @Suppress("ktlint:standard:backing-property-naming") private var _onInitialized: (() -> Unit) = {} @@ -430,4 +440,12 @@ public open class ServerSession( * @return true if the message should be accepted (not filtered out), false otherwise. */ private fun isMessageAccepted(level: LoggingLevel): Boolean = !isMessageIgnored(level) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is ServerSession) return false + return sessionId == other.sessionId + } + + override fun hashCode(): Int = sessionId.hashCode() } diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSessionRegistry.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSessionRegistry.kt new file mode 100644 index 0000000..57980d3 --- /dev/null +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSessionRegistry.kt @@ -0,0 +1,60 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.github.oshai.kotlinlogging.KotlinLogging +import kotlinx.atomicfu.atomic +import kotlinx.atomicfu.update +import kotlinx.collections.immutable.persistentMapOf + +internal typealias ServerSessionKey = String + +/** + * Represents a registry for managing server sessions. + */ +internal class ServerSessionRegistry { + + private val logger = KotlinLogging.logger {} + + /** + * Atomic variable used to maintain a thread-safe registry of sessions. + * Stores a persistent map where each session is identified by its unique key. + */ + private val registry = atomic(persistentMapOf()) + + /** + * Returns a read-only view of the current server sessions. + */ + internal val sessions: Map + get() = registry.value + + /** + * Returns a server session by its ID. + * @param sessionId The ID of the session to retrieve. + * @throws IllegalArgumentException If the session doesn't exist. + */ + internal fun getSession(sessionId: ServerSessionKey): ServerSession = + sessions[sessionId] ?: throw IllegalArgumentException("Session not found: $sessionId") + + /** + * Returns a server session by its ID, or null if it doesn't exist. + * @param sessionId The ID of the session to retrieve. + */ + internal fun getSessionOrNull(sessionId: ServerSessionKey): ServerSession? = sessions[sessionId] + + /** + * Registers a server session. + * @param session The session to register. + */ + internal fun addSession(session: ServerSession) { + logger.info { "Adding session: ${session.sessionId}" } + registry.update { sessions -> sessions.put(session.sessionId, session) } + } + + /** + * Removes a server session by its ID. + * @param sessionId The ID of the session to remove. + */ + internal fun removeSession(sessionId: ServerSessionKey) { + logger.info { "Removing session: $sessionId" } + registry.update { sessions -> sessions.remove(sessionId) } + } +} From 13f858d5d24a1e8a07d06a200bab32a4a640cc6f Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Tue, 11 Nov 2025 14:55:58 +0100 Subject: [PATCH 061/449] Add new helper properties to request and notification types (#388) Add properties for types of new schema ## Motivation and Context Added getters to simplify access to properties in request and notification types. ## How Has This Been Tested? All tests pass ## 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- kotlin-sdk-core/api/kotlin-sdk-core.api | 40 +++++ .../kotlin/sdk/types/PingRequest.kt | 3 + .../kotlin/sdk/types/completion.kt | 21 +++ .../kotlin/sdk/types/elicitation.kt | 18 +++ .../kotlin/sdk/types/initialize.kt | 24 +++ .../kotlin/sdk/types/logging.kt | 12 ++ .../kotlin/sdk/types/notification.kt | 24 ++- .../kotlin/sdk/types/prompts.kt | 12 ++ .../kotlin/sdk/types/resources.kt | 140 ++++++++++++------ .../kotlin/sdk/types/roots.kt | 3 + .../kotlin/sdk/types/sampling.kt | 76 ++++++++-- .../kotlin/sdk/types/tools.kt | 12 ++ 12 files changed, 327 insertions(+), 58 deletions(-) diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index 1b5c44c..7c051ee 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -773,9 +773,12 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CancelledNotificatio public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotification; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotification;Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotificationParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotification; public fun equals (Ljava/lang/Object;)Z + public final fun getMeta ()Lkotlinx/serialization/json/JsonObject; public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotificationParams; public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/NotificationParams; + public final fun getReason ()Ljava/lang/String; + public final fun getRequestId ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestId; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -931,9 +934,13 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CompleteRequest : io public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequest; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequest;Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequest; public fun equals (Ljava/lang/Object;)Z + public final fun getArgument ()Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Argument; + public final fun getContext ()Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Context; + public final fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams; public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; + public final fun getRef ()Lio/modelcontextprotocol/kotlin/sdk/types/Reference; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -1140,9 +1147,18 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest;Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest; public fun equals (Ljava/lang/Object;)Z + public final fun getIncludeContext ()Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext; + public final fun getMaxTokens ()I + public final fun getMessages ()Ljava/util/List; + public final fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun getMetadata ()Lkotlinx/serialization/json/JsonObject; public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public final fun getModelPreferences ()Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences; public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams; public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; + public final fun getStopSequences ()Ljava/util/List; + public final fun getSystemPrompt ()Ljava/lang/String; + public final fun getTemperature ()Ljava/lang/Double; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -1251,6 +1267,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CustomNotification : public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/Method;Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/types/CustomNotification; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/CustomNotification;Lio/modelcontextprotocol/kotlin/sdk/types/Method;Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CustomNotification; public fun equals (Ljava/lang/Object;)Z + public final fun getMeta ()Lkotlinx/serialization/json/JsonObject; public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/NotificationParams; @@ -1305,9 +1322,12 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequest : io/m public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequest; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequest;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequest; public fun equals (Ljava/lang/Object;)Z + public final fun getMessage ()Ljava/lang/String; + public final fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams; public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; + public final fun getRequestedSchema ()Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -1733,9 +1753,13 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/InitializeRequest : public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequest; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequest;Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequest; public fun equals (Ljava/lang/Object;)Z + public final fun getCapabilities ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities; + public final fun getClientInfo ()Lio/modelcontextprotocol/kotlin/sdk/types/Implementation; + public final fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequestParams; public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; + public final fun getProtocolVersion ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -2014,6 +2038,8 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest : public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest;Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest; public fun equals (Ljava/lang/Object;)Z + public final fun getCursor ()Ljava/lang/String; + public final fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams; public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; @@ -2077,6 +2103,8 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplate public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesRequest; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesRequest;Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesRequest; public fun equals (Ljava/lang/Object;)Z + public final fun getCursor ()Ljava/lang/String; + public final fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams; public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; @@ -2140,6 +2168,8 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest;Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest; public fun equals (Ljava/lang/Object;)Z + public final fun getCursor ()Ljava/lang/String; + public final fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams; public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; @@ -2203,6 +2233,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest : i public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest;Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest; public fun equals (Ljava/lang/Object;)Z + public final fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams; public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; @@ -2264,6 +2295,8 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest : i public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest;Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest; public fun equals (Ljava/lang/Object;)Z + public final fun getCursor ()Ljava/lang/String; + public final fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams; public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; @@ -2626,6 +2659,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/PingRequest : io/mod public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/PingRequest; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/PingRequest;Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/PingRequest; public fun equals (Ljava/lang/Object;)Z + public final fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams; public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; @@ -3717,6 +3751,8 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/SetLevelRequest : io public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequest; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequest;Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequest; public fun equals (Ljava/lang/Object;)Z + public final fun getLevel ()Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; + public final fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequestParams; @@ -3809,9 +3845,11 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest : i public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest;Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest; public fun equals (Ljava/lang/Object;)Z + public final fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequestParams; + public final fun getUri ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -4116,9 +4154,11 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest : public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest;Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest; public fun equals (Ljava/lang/Object;)Z + public final fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequestParams; + public final fun getUri ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PingRequest.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PingRequest.kt index 575d3db..2f1e3ae 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PingRequest.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PingRequest.kt @@ -11,4 +11,7 @@ public data class PingRequest(override val params: BaseRequestParams? = null) : @OptIn(ExperimentalSerializationApi::class) @EncodeDefault override val method: Method = Method.Defined.Ping + + public val meta: RequestMeta? + get() = params?.meta } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.kt index e0c4295..89d853d 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.kt @@ -16,6 +16,27 @@ public data class CompleteRequest(override val params: CompleteRequestParams) : @OptIn(ExperimentalSerializationApi::class) @EncodeDefault public override val method: Method = Method.Defined.CompletionComplete + + /** + * The argument's information for which completion options are requested. + */ + public val argument: CompleteRequestParams.Argument + get() = params.argument + + /** + * A reference to either a prompt or resource template to complete within. + */ + public val ref: Reference + get() = params.ref + + /** + * Additional, context for generating completions. + */ + public val context: CompleteRequestParams.Context? + get() = params.context + + public val meta: RequestMeta? + get() = params.meta } /** diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.kt index 75faa42..dfda9bf 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.kt @@ -20,6 +20,24 @@ public data class ElicitRequest(override val params: ElicitRequestParams) : Serv @OptIn(ExperimentalSerializationApi::class) @EncodeDefault public override val method: Method = Method.Defined.ElicitationCreate + + /** + * The message to present to the user. This should clearly explain what information is being requested and why. + */ + public val message: String + get() = params.message + + /** + * A restricted subset of JSON Schema defining the structure of the requested data. + */ + public val requestedSchema: ElicitRequestParams.RequestedSchema + get() = params.requestedSchema + + /** + * Metadata for this request. May include a progressToken for out-of-band progress notifications. + */ + public val meta: RequestMeta? + get() = params.meta } /** diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/initialize.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/initialize.kt index 6a2fd5b..6c69112 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/initialize.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/initialize.kt @@ -21,6 +21,30 @@ public data class InitializeRequest(override val params: InitializeRequestParams @OptIn(ExperimentalSerializationApi::class) @EncodeDefault override val method: Method = Method.Defined.Initialize + + /** + * The latest version of the Model Context Protocol that the client supports. + */ + public val protocolVersion: String + get() = params.protocolVersion + + /** + * The capabilities that this client supports. Describes which optional features the client has implemented. + */ + public val capabilities: ClientCapabilities + get() = params.capabilities + + /** + * Information about the client implementation, including name, version, and branding. + */ + public val clientInfo: Implementation + get() = params.clientInfo + + /** + * Metadata for this request. May include a progressToken for out-of-band progress notifications. + */ + public val meta: RequestMeta? + get() = params.meta } /** diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/logging.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/logging.kt index 2966b0c..d1e5f00 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/logging.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/logging.kt @@ -63,6 +63,18 @@ public data class SetLevelRequest(override val params: SetLevelRequestParams) : @OptIn(ExperimentalSerializationApi::class) @EncodeDefault override val method: Method = Method.Defined.LoggingSetLevel + + /** + * The minimum severity level of logging that the client wants to receive from the server. + */ + public val level: LoggingLevel + get() = params.level + + /** + * Metadata for this request. May include a progressToken for out-of-band progress notifications. + */ + public val meta: RequestMeta? + get() = params.meta } /** diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/notification.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/notification.kt index 2321584..9270884 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/notification.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/notification.kt @@ -77,7 +77,11 @@ public class Progress( @Serializable public data class CustomNotification(override val method: Method, override val params: BaseNotificationParams? = null) : ClientNotification, - ServerNotification + ServerNotification { + + public val meta: JsonObject? + get() = params?.meta +} // ============================================================================ // Cancelled Notification @@ -101,6 +105,24 @@ public data class CancelledNotification(override val params: CancelledNotificati ServerNotification { @EncodeDefault override val method: Method = Method.Defined.NotificationsCancelled + + /** + * The ID of the request to cancel. + */ + public val requestId: RequestId + get() = params.requestId + + /** + * A string describing the reason for the cancellation. + */ + public val reason: String? + get() = params.reason + + /** + * Metadata for this notification. + */ + public val meta: JsonObject? + get() = params.meta } /** diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.kt index 5d7054b..f1d352e 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.kt @@ -187,6 +187,18 @@ public data class ListPromptsRequest(override val params: PaginatedRequestParams PaginatedRequest { @EncodeDefault override val method: Method = Method.Defined.PromptsList + + /** + * An opaque token representing the current pagination position. + */ + public val cursor: String? + get() = params?.cursor + + /** + * Metadata for this request. May include a progressToken for out-of-band progress notifications. + */ + public val meta: RequestMeta? + get() = params?.meta } /** diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.kt index 8219cc7..121864f 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.kt @@ -190,6 +190,18 @@ public data class ListResourcesRequest(override val params: PaginatedRequestPara PaginatedRequest { @EncodeDefault override val method: Method = Method.Defined.ResourcesList + + /** + * An opaque token representing the current pagination position. + */ + public val cursor: String? + get() = params?.cursor + + /** + * Metadata for this request. May include a progressToken for out-of-band progress notifications. + */ + public val meta: RequestMeta? + get() = params?.meta } /** @@ -217,22 +229,6 @@ public data class ListResourcesResult( // resources/read // ============================================================================ -/** - * Parameters for a resources/read request. - * - * @property uri The URI of the resource to read. The URI can use any protocol; - * it is up to the server how to interpret it. Common schemes include - * file://, http://, https://, or custom application-specific schemes. - * @property meta Optional metadata for this request. May include a progressToken for - * out-of-band progress notifications. - */ -@Serializable -public data class ReadResourceRequestParams( - val uri: String, - @SerialName("_meta") - override val meta: RequestMeta? = null, -) : RequestParams - /** * Sent from the client to the server to read a specific resource URI. * @@ -258,6 +254,22 @@ public data class ReadResourceRequest(override val params: ReadResourceRequestPa get() = params.meta } +/** + * Parameters for a resources/read request. + * + * @property uri The URI of the resource to read. The URI can use any protocol; + * it is up to the server how to interpret it. Common schemes include + * file://, http://, https://, or custom application-specific schemes. + * @property meta Optional metadata for this request. May include a progressToken for + * out-of-band progress notifications. + */ +@Serializable +public data class ReadResourceRequestParams( + val uri: String, + @SerialName("_meta") + override val meta: RequestMeta? = null, +) : RequestParams + /** * The server's response to a [ReadResourceRequest] from the client. * @@ -280,6 +292,34 @@ public data class ReadResourceResult( // resources/subscribe // ============================================================================ +/** + * Sent from the client to request resources/updated notifications from the server + * whenever a particular resource changes. + * + * After subscribing, the server will send [ResourceUpdatedNotification] messages + * whenever the subscribed resource is modified. This requires the server to support + * the `subscribe` capability in [ServerCapabilities.resources]. + * + * @property params The parameters specifying which resource URI to subscribe to. + */ +@Serializable +public data class SubscribeRequest(override val params: SubscribeRequestParams) : ClientRequest { + @EncodeDefault + override val method: Method = Method.Defined.ResourcesSubscribe + + /** + * The URI of the resource to subscribe to. + */ + public val uri: String + get() = params.uri + + /** + * Metadata for this request. May include a progressToken for out-of-band progress notifications. + */ + public val meta: RequestMeta? + get() = params.meta +} + /** * Parameters for a resources/subscribe request. * @@ -295,26 +335,36 @@ public data class SubscribeRequestParams( override val meta: RequestMeta? = null, ) : RequestParams -/** - * Sent from the client to request resources/updated notifications from the server - * whenever a particular resource changes. - * - * After subscribing, the server will send [ResourceUpdatedNotification] messages - * whenever the subscribed resource is modified. This requires the server to support - * the `subscribe` capability in [ServerCapabilities.resources]. - * - * @property params The parameters specifying which resource URI to subscribe to. - */ -@Serializable -public data class SubscribeRequest(override val params: SubscribeRequestParams) : ClientRequest { - @EncodeDefault - override val method: Method = Method.Defined.ResourcesSubscribe -} - // ============================================================================ // resources/unsubscribe // ============================================================================ +/** + * Sent from the client to request cancellation of resources/updated notifications from the server. + * + * This should follow a previous [SubscribeRequest]. After unsubscribing, the server will + * stop sending [ResourceUpdatedNotification] messages for this resource. + * + * @property params The parameters specifying which resource URI to unsubscribe from. + */ +@Serializable +public data class UnsubscribeRequest(override val params: UnsubscribeRequestParams) : ClientRequest { + @EncodeDefault + override val method: Method = Method.Defined.ResourcesUnsubscribe + + /** + * The URI of the resource to unsubscribe from. + */ + public val uri: String + get() = params.uri + + /** + * Metadata for this request. May include a progressToken for out-of-band progress notifications. + */ + public val meta: RequestMeta? + get() = params.meta +} + /** * Parameters for a resources/unsubscribe request. * @@ -330,20 +380,6 @@ public data class UnsubscribeRequestParams( override val meta: RequestMeta? = null, ) : RequestParams -/** - * Sent from the client to request cancellation of resources/updated notifications from the server. - * - * This should follow a previous [SubscribeRequest]. After unsubscribing, the server will - * stop sending [ResourceUpdatedNotification] messages for this resource. - * - * @property params The parameters specifying which resource URI to unsubscribe from. - */ -@Serializable -public data class UnsubscribeRequest(override val params: UnsubscribeRequestParams) : ClientRequest { - @EncodeDefault - override val method: Method = Method.Defined.ResourcesUnsubscribe -} - // ============================================================================ // resources/templates/list // ============================================================================ @@ -363,6 +399,18 @@ public data class ListResourceTemplatesRequest(override val params: PaginatedReq PaginatedRequest { @EncodeDefault override val method: Method = Method.Defined.ResourcesTemplatesList + + /** + * An opaque token representing the current pagination position. + */ + public val cursor: String? + get() = params?.cursor + + /** + * Metadata for this request. May include a progressToken for out-of-band progress notifications. + */ + public val meta: RequestMeta? + get() = params?.meta } /** diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.kt index 2d7ddbc..af7b1e7 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.kt @@ -59,6 +59,9 @@ public data class ListRootsRequest(override val params: BaseRequestParams? = nul @OptIn(ExperimentalSerializationApi::class) @EncodeDefault override val method: Method = Method.Defined.RootsList + + public val meta: RequestMeta? + get() = params?.meta } /** diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.kt index 22d9239..41dc864 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.kt @@ -107,31 +107,85 @@ public data class CreateMessageRequest(override val params: CreateMessageRequest @OptIn(ExperimentalSerializationApi::class) @EncodeDefault override val method: Method = Method.Defined.SamplingCreateMessage + + /** + * The requested maximum number of tokens to sample (to prevent runaway completions). + */ + public val maxTokens: Int + get() = params.maxTokens + + /** + * The messages to use as context for sampling. + */ + public val messages: List + get() = params.messages + + /** + * The server's preferences for which model to select. + */ + public val modelPreferences: ModelPreferences? + get() = params.modelPreferences + + /** + * An optional system prompt the server wants to use for sampling. + */ + public val systemPrompt: String? + get() = params.systemPrompt + + /** + * A request to include context from one or more MCP servers (including the caller), to be attached to the prompt. + */ + public val includeContext: IncludeContext? + get() = params.includeContext + + /** + * Temperature parameter for sampling (typically 0.0-2.0). + */ + public val temperature: Double? + get() = params.temperature + + /** + * List of sequences that will stop generation if encountered. + */ + public val stopSequences: List? + get() = params.stopSequences + + /** + * Metadata to pass through to the LLM provider. The format of this metadata is provider-specific. + */ + public val metadata: JsonObject? + get() = params.metadata + + /** + * Metadata for this request. May include a progressToken for out-of-band progress notifications. + */ + public val meta: RequestMeta? + get() = params.meta } /** * Parameters for a sampling/createMessage request. * * @property maxTokens The requested maximum number of tokens to sample (to prevent runaway completions). - * The client MAY choose to sample fewer tokens than the requested maximum. + * The client MAY choose to sample fewer tokens than the requested maximum. * @property messages The messages to use as context for sampling. Typically includes conversation history - * and the current user message. + * and the current user message. * @property modelPreferences The server's preferences for which model to select. - * The client MAY ignore these preferences and choose any model. + * The client MAY ignore these preferences and choose any model. * @property systemPrompt An optional system prompt the server wants to use for sampling. - * The client MAY modify or omit this prompt. + * The client MAY modify or omit this prompt. * @property includeContext A request to include context from one or more MCP servers (including the caller), - * to be attached to the prompt. The client MAY ignore this request. - * - "none": Don't include any server context - * - "thisServer": Include context only from the requesting server - * - "allServers": Include context from all connected MCP servers + * to be attached to the prompt. The client MAY ignore this request. + * - `none`: Don't include any server context + * - `thisServer`: Include context only from the requesting server + * - `allServers`: Include context from all connected MCP servers * @property temperature Optional temperature parameter for sampling (typically 0.0-2.0). - * Higher values make output more random, lower values make it more deterministic. + * Higher values make output more random, lower values make it more deterministic. * @property stopSequences Optional list of sequences that will stop generation if encountered. * @property metadata Optional metadata to pass through to the LLM provider. - * The format of this metadata is provider-specific. + * The format of this metadata is provider-specific. * @property meta Optional metadata for this request. May include a progressToken for - * out-of-band progress notifications. + * out-of-band progress notifications. */ @Serializable public data class CreateMessageRequestParams( diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt index 5fbc8f5..15f0480 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt @@ -226,6 +226,18 @@ public data class ListToolsRequest(override val params: PaginatedRequestParams? PaginatedRequest { @EncodeDefault override val method: Method = Method.Defined.ToolsList + + /** + * An opaque token representing the current pagination position. + */ + public val cursor: String? + get() = params?.cursor + + /** + * Metadata for this request. May include a progressToken for out-of-band progress notifications. + */ + public val meta: RequestMeta? + get() = params?.meta } /** From 2996ce45ead9f61f0ed4750f3546b322944ebf81 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Wed, 12 Nov 2025 06:15:34 +0100 Subject: [PATCH 062/449] Increase deprecation levels (#393) --- .../kotlin/sdk/client/SSEClientTransport.kt | 2 +- kotlin-sdk-server/api/kotlin-sdk-server.api | 1 - .../io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt | 6 ------ .../kotlin/sdk/server/SSEServerTransport.kt | 2 +- .../kotlin/io/modelcontextprotocol/sample/server/server.kt | 4 ++-- .../modelcontextprotocol/sample/server/McpWeatherServer.kt | 2 +- 6 files changed, 5 insertions(+), 12 deletions(-) diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt index f2b7d3d..d858c1d 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt @@ -33,7 +33,7 @@ import kotlin.concurrent.atomics.AtomicBoolean import kotlin.concurrent.atomics.ExperimentalAtomicApi import kotlin.time.Duration -@Deprecated("Use SseClientTransport instead", ReplaceWith("SseClientTransport"), DeprecationLevel.WARNING) +@Deprecated("Use SseClientTransport instead", ReplaceWith("SseClientTransport"), DeprecationLevel.ERROR) public typealias SSEClientTransport = SseClientTransport /** diff --git a/kotlin-sdk-server/api/kotlin-sdk-server.api b/kotlin-sdk-server/api/kotlin-sdk-server.api index 045addf..11ac161 100644 --- a/kotlin-sdk-server/api/kotlin-sdk-server.api +++ b/kotlin-sdk-server/api/kotlin-sdk-server.api @@ -1,5 +1,4 @@ public final class io/modelcontextprotocol/kotlin/sdk/server/KtorServerKt { - public static final fun MCP (Lio/ktor/server/application/Application;Lkotlin/jvm/functions/Function1;)V public static final fun mcp (Lio/ktor/server/application/Application;Lkotlin/jvm/functions/Function1;)V public static final fun mcp (Lio/ktor/server/routing/Routing;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V public static final fun mcp (Lio/ktor/server/routing/Routing;Lkotlin/jvm/functions/Function1;)V diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt index 8ad4c2f..2a2f950 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt @@ -60,12 +60,6 @@ public fun Routing.mcp(block: ServerSSESession.() -> Server) { } } -@Suppress("FunctionName") -@Deprecated("Use mcp() instead", ReplaceWith("mcp(block)"), DeprecationLevel.ERROR) -public fun Application.MCP(block: ServerSSESession.() -> Server) { - mcp(block) -} - @KtorDsl public fun Application.mcp(block: ServerSSESession.() -> Server) { install(SSE) diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt index b8c1556..2e1a285 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt @@ -21,7 +21,7 @@ import kotlin.uuid.Uuid internal const val SESSION_ID_PARAM = "sessionId" -@Deprecated("Use SseServerTransport instead", ReplaceWith("SseServerTransport"), DeprecationLevel.WARNING) +@Deprecated("Use SseServerTransport instead", ReplaceWith("SseServerTransport"), DeprecationLevel.ERROR) public typealias SSEServerTransport = SseServerTransport /** diff --git a/samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/server.kt b/samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/server.kt index eb61071..a593377 100644 --- a/samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/server.kt +++ b/samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/server.kt @@ -116,7 +116,7 @@ fun runSseMcpServerWithPlainConfiguration(port: Int, wait: Boolean = true) { routing { sse("/sse") { val transport = SseServerTransport("/message", this) - val serverSession = server.connect(transport) + val serverSession = server.createSession(transport) serverSessions[transport.sessionId] = serverSession serverSession.onClose { @@ -197,7 +197,7 @@ fun runMcpServerUsingStdio() { ) runBlocking { - server.connect(transport) + server.createSession(transport) val done = Job() server.onClose { done.complete() diff --git a/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt b/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt index a0d1968..181431e 100644 --- a/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt +++ b/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt @@ -129,7 +129,7 @@ fun runMcpServer() { ) runBlocking { - val session = server.connect(transport) + val session = server.createSession(transport) val done = Job() session.onClose { done.complete() From 01de6e6800024ae68b9863afebb5a8acd8aa8755 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Wed, 12 Nov 2025 06:54:46 +0100 Subject: [PATCH 063/449] Raise the version of the supported specification (#391) --- .../io/modelcontextprotocol/kotlin/sdk/types/common.kt | 3 ++- .../io/modelcontextprotocol/kotlin/sdk/OldSchemaTypesTest.kt | 4 ++-- .../modelcontextprotocol/kotlin/sdk/types/CommonTypeTest.kt | 5 +++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt index f539c41..1066843 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt @@ -8,10 +8,11 @@ import kotlinx.serialization.json.JsonObject // Protocol Version Constants // ============================================================================ -public const val LATEST_PROTOCOL_VERSION: String = "2025-03-26" +public const val LATEST_PROTOCOL_VERSION: String = "2025-06-18" public val SUPPORTED_PROTOCOL_VERSIONS: List = listOf( LATEST_PROTOCOL_VERSION, + "2025-03-26", "2024-11-05", ) diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaTypesTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaTypesTest.kt index b3a4d5b..f8ad0be 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaTypesTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaTypesTest.kt @@ -16,7 +16,7 @@ class OldSchemaTypesTest { @Test fun `should have correct latest protocol version`() { assertNotEquals("", LATEST_PROTOCOL_VERSION) - assertEquals("2025-03-26", LATEST_PROTOCOL_VERSION) + assertEquals("2025-06-18", LATEST_PROTOCOL_VERSION) } @Test @@ -24,7 +24,7 @@ class OldSchemaTypesTest { assertIs>(SUPPORTED_PROTOCOL_VERSIONS) assertTrue(SUPPORTED_PROTOCOL_VERSIONS.contains(LATEST_PROTOCOL_VERSION)) assertTrue(SUPPORTED_PROTOCOL_VERSIONS.contains("2024-11-05")) - assertEquals(2, SUPPORTED_PROTOCOL_VERSIONS.size) + assertEquals(3, SUPPORTED_PROTOCOL_VERSIONS.size) } @Test diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CommonTypeTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CommonTypeTest.kt index 6247a49..679d14a 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CommonTypeTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CommonTypeTest.kt @@ -13,15 +13,16 @@ class CommonTypeTest { @Test fun `should have correct latest protocol version`() { assertNotEquals("", LATEST_PROTOCOL_VERSION) - assertEquals("2025-03-26", LATEST_PROTOCOL_VERSION) + assertEquals("2025-06-18", LATEST_PROTOCOL_VERSION) } @Test fun `should have correct supported protocol versions`() { assertIs>(SUPPORTED_PROTOCOL_VERSIONS) assertTrue(SUPPORTED_PROTOCOL_VERSIONS.contains(LATEST_PROTOCOL_VERSION)) + assertTrue(SUPPORTED_PROTOCOL_VERSIONS.contains("2025-03-26")) assertTrue(SUPPORTED_PROTOCOL_VERSIONS.contains("2024-11-05")) - assertEquals(2, SUPPORTED_PROTOCOL_VERSIONS.size) + assertEquals(3, SUPPORTED_PROTOCOL_VERSIONS.size) } @Test From 6afef103c97ff29a48e598fb1a11699b0c655186 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Wed, 12 Nov 2025 13:40:44 +0200 Subject: [PATCH 064/449] Update JVM target to 11 in Gradle build configuration (#394) Update JVM target to 11 in Gradle build configuration ## Motivation and Context Drop the obsolete JVM 1.8 target to keep up with up-to-date tools, like Kotest ## How Has This Been Tested? CI ## Breaking Changes The minimum JVM version is now 11 ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [x] Breaking change (fix or feature that would cause existing functionality to change) - [x] 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 ## Additional context --- README.md | 2 +- buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2a756e5..7250499 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Build](https://github.com/modelcontextprotocol/kotlin-sdk/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/modelcontextprotocol/kotlin-sdk/actions/workflows/build.yml) [![Kotlin](https://img.shields.io/badge/kotlin-2.2+-blueviolet.svg?logo=kotlin)](http://kotlinlang.org) [![Kotlin Multiplatform](https://img.shields.io/badge/Platforms-%20JVM%20%7C%20Wasm%2FJS%20%7C%20Native%20-blueviolet?logo=kotlin)](https://kotlinlang.org/docs/multiplatform.html) -[![JVM](https://img.shields.io/badge/JVM-1.8+-red.svg?logo=jvm)](http://java.com) +[![JVM](https://img.shields.io/badge/JVM-11+-red.svg?logo=jvm)](http://java.com) [![License](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) Kotlin Multiplatform implementation of the [Model Context Protocol](https://modelcontextprotocol.io) (MCP), diff --git a/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts b/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts index 0a57e98..a676db7 100644 --- a/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts +++ b/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts @@ -21,12 +21,9 @@ kotlin { "-Xmulti-dollar-interpolation", ) } - - // coreLibrariesVersion = "2.0.10" - jvm { compilerOptions { - jvmTarget = JvmTarget.JVM_1_8 + jvmTarget = JvmTarget.JVM_11 javaParameters = true jvmDefault = JvmDefaultMode.ENABLE freeCompilerArgs.addAll( From 5636faf60c21b5b879dd83b6acfc45ae4bd1b69c Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Wed, 12 Nov 2025 14:40:33 +0200 Subject: [PATCH 065/449] Updated ServerPromptsTest, move AbstractTransport to it's own file (#376) # Refactor and enhance prompt handling logic and tests, update Kotest version and modularize `AbstractTransport` - Added more post-conditions/scenarios to ServerPromptsTest - Extracted `AbstractTransport` to a dedicated file for modularity. - Improved Protocol connection state validation: When disconnected, the Protocol will throw `IllegalStateException` instead of `Error`. - Minor Kotlin fixes and suppressions to improve code quality. ## Motivation and Context Improve test scenarios in ServerPromptsTest ## How Has This Been Tested? Integration tests updated ## Breaking Changes When disconnected, the Protocol will throw `IllegalStateException` instead of `Error`. ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [x] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [x] Test update - [x] Refactoring ## 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 ## Additional context --- kotlin-sdk-core/api/kotlin-sdk-core.api | 74 +++++++++ .../kotlin/sdk/shared/AbstractTransport.kt | 54 +++++++ .../kotlin/sdk/shared/Transport.kt | 51 ------- .../kotlin/sdk/types/common.kt | 10 ++ .../kotlin/sdk/types/prompts.kt | 16 ++ kotlin-sdk-test/build.gradle.kts | 1 + .../sdk/server/OldSchemaServerPromptsTest.kt | 143 ++++++++++++++++-- 7 files changed, 282 insertions(+), 67 deletions(-) create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport.kt diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index 7c051ee..3c1aec4 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -558,6 +558,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/AudioContent : io/mo public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public final fun getMimeType ()Ljava/lang/String; public fun getType ()Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -587,6 +588,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/BaseNotificationPara public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; public fun equals (Ljava/lang/Object;)Z public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -649,6 +651,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/BlobResourceContents public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public fun getMimeType ()Ljava/lang/String; public fun getUri ()Ljava/lang/String; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -746,6 +749,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CallToolResult : io/ public final fun getContent ()Ljava/util/List; public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public final fun getStructuredContent ()Lkotlinx/serialization/json/JsonObject; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public final fun isError ()Ljava/lang/Boolean; public fun toString ()Ljava/lang/String; @@ -811,6 +815,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CancelledNotificatio public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public final fun getReason ()Ljava/lang/String; public final fun getRequestId ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestId; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -920,6 +925,10 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ClientResult$Compani public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ClientResult$DefaultImpls { + public static fun get_meta (Lio/modelcontextprotocol/kotlin/sdk/types/ClientResult;)Lkotlinx/serialization/json/JsonObject; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/CommonKt { public static final field LATEST_PROTOCOL_VERSION Ljava/lang/String; public static final fun ProgressToken (J)Lio/modelcontextprotocol/kotlin/sdk/types/RequestId; @@ -1063,6 +1072,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CompleteResult : io/ public fun equals (Ljava/lang/Object;)Z public final fun getCompletion ()Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult$Completion; public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -1123,6 +1133,10 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ContentBlock$Compani public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ContentBlock$DefaultImpls { + public static fun get_meta (Lio/modelcontextprotocol/kotlin/sdk/types/ContentBlock;)Lkotlinx/serialization/json/JsonObject; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/ContentTypes : java/lang/Enum { public static final field AUDIO Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes$Companion; @@ -1239,6 +1253,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageResult public final fun getModel ()Ljava/lang/String; public final fun getRole ()Lio/modelcontextprotocol/kotlin/sdk/types/Role; public final fun getStopReason-6olV-UY ()Ljava/lang/String; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -1423,6 +1438,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitResult : io/mo public final fun getAction ()Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Action; public final fun getContent ()Lkotlinx/serialization/json/JsonObject; public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -1470,6 +1486,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/EmbeddedResource : i public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public final fun getResource ()Lio/modelcontextprotocol/kotlin/sdk/types/ResourceContents; public fun getType ()Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -1499,6 +1516,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/EmptyResult : io/mod public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/EmptyResult;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/EmptyResult; public fun equals (Ljava/lang/Object;)Z public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -1521,6 +1539,8 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/EmptyResult$Companio public final class io/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest$Companion; public fun (Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequestParams;)V + public synthetic fun (Ljava/lang/String;Ljava/util/Map;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Ljava/util/Map;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequestParams; public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest;Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest; @@ -1595,6 +1615,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/GetPromptResult : io public final fun getDescription ()Ljava/lang/String; public final fun getMessages ()Ljava/util/List; public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -1677,6 +1698,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ImageContent : io/mo public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public final fun getMimeType ()Ljava/lang/String; public fun getType ()Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -1830,6 +1852,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/InitializeResult : i public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public final fun getProtocolVersion ()Ljava/lang/String; public final fun getServerInfo ()Lio/modelcontextprotocol/kotlin/sdk/types/Implementation; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -2075,6 +2098,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListPromptsResult : public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public fun getNextCursor ()Ljava/lang/String; public final fun getPrompts ()Ljava/util/List; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -2140,6 +2164,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplate public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public fun getNextCursor ()Ljava/lang/String; public final fun getResourceTemplates ()Ljava/util/List; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -2205,6 +2230,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourcesResult public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public fun getNextCursor ()Ljava/lang/String; public final fun getResources ()Ljava/util/List; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -2267,6 +2293,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListRootsResult : io public fun equals (Ljava/lang/Object;)Z public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public final fun getRoots ()Ljava/util/List; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -2332,6 +2359,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListToolsResult : io public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public fun getNextCursor ()Ljava/lang/String; public final fun getTools ()Ljava/util/List; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -2414,6 +2442,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotifi public final fun getLevel ()Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; public final fun getLogger ()Ljava/lang/String; public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -2449,6 +2478,10 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/MediaContent$Compani public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/MediaContent$DefaultImpls { + public static fun get_meta (Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;)Lkotlinx/serialization/json/JsonObject; +} + public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/Method { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Method$Companion; public abstract fun getValue ()Ljava/lang/String; @@ -2602,6 +2635,10 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/NotificationParams$C public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/NotificationParams$DefaultImpls { + public static fun get_meta (Lio/modelcontextprotocol/kotlin/sdk/types/NotificationParams;)Lkotlinx/serialization/json/JsonObject; +} + public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequest : io/modelcontextprotocol/kotlin/sdk/types/Request { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequest$Companion; public abstract fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams; @@ -2650,6 +2687,10 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/PaginatedResult$Comp public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/PaginatedResult$DefaultImpls { + public static fun get_meta (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedResult;)Lkotlinx/serialization/json/JsonObject; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/PingRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest, io/modelcontextprotocol/kotlin/sdk/types/ServerRequest { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/PingRequest$Companion; public fun ()V @@ -2752,6 +2793,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ProgressNotification public final fun getProgress ()D public final fun getProgressToken ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestId; public final fun getTotal ()Ljava/lang/Double; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -2790,6 +2832,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/Prompt : io/modelcon public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public final fun getName ()Ljava/lang/String; public final fun getTitle ()Ljava/lang/String; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -3049,6 +3092,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ReadResourceResult : public fun equals (Ljava/lang/Object;)Z public final fun getContents ()Ljava/util/List; public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -3218,6 +3262,10 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/RequestResult$Compan public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/RequestResult$DefaultImpls { + public static fun get_meta (Lio/modelcontextprotocol/kotlin/sdk/types/RequestResult;)Lkotlinx/serialization/json/JsonObject; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/Resource : io/modelcontextprotocol/kotlin/sdk/types/ResourceLike { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Resource$Companion; public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)V @@ -3243,6 +3291,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/Resource : io/modelc public final fun getSize ()Ljava/lang/Long; public final fun getTitle ()Ljava/lang/String; public final fun getUri ()Ljava/lang/String; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -3272,6 +3321,10 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ResourceContents$Com public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ResourceContents$DefaultImpls { + public static fun get_meta (Lio/modelcontextprotocol/kotlin/sdk/types/ResourceContents;)Lkotlinx/serialization/json/JsonObject; +} + public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/ResourceLike : io/modelcontextprotocol/kotlin/sdk/types/WithMeta { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ResourceLike$Companion; } @@ -3280,6 +3333,10 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ResourceLike$Compani public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ResourceLike$DefaultImpls { + public static fun get_meta (Lio/modelcontextprotocol/kotlin/sdk/types/ResourceLike;)Lkotlinx/serialization/json/JsonObject; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/ResourceLink : io/modelcontextprotocol/kotlin/sdk/types/ContentBlock, io/modelcontextprotocol/kotlin/sdk/types/ResourceLike { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ResourceLink$Companion; public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Lkotlinx/serialization/json/JsonObject;)V @@ -3306,6 +3363,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ResourceLink : io/mo public final fun getTitle ()Ljava/lang/String; public fun getType ()Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; public final fun getUri ()Ljava/lang/String; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -3379,6 +3437,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ResourceTemplate : i public final fun getName ()Ljava/lang/String; public final fun getTitle ()Ljava/lang/String; public final fun getUriTemplate ()Ljava/lang/String; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -3466,6 +3525,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotif public fun equals (Ljava/lang/Object;)Z public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public final fun getUri ()Ljava/lang/String; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -3511,6 +3571,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/Root : io/modelconte public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public final fun getName ()Ljava/lang/String; public final fun getUri ()Ljava/lang/String; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -3744,6 +3805,10 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ServerResult$Compani public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ServerResult$DefaultImpls { + public static fun get_meta (Lio/modelcontextprotocol/kotlin/sdk/types/ServerResult;)Lkotlinx/serialization/json/JsonObject; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/SetLevelRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequest$Companion; public fun (Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequestParams;)V @@ -3913,6 +3978,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/TextContent : io/mod public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public final fun getText ()Ljava/lang/String; public fun getType ()Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -3947,6 +4013,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/TextResourceContents public fun getMimeType ()Ljava/lang/String; public final fun getText ()Ljava/lang/String; public fun getUri ()Ljava/lang/String; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -3989,6 +4056,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/Tool : io/modelconte public final fun getName ()Ljava/lang/String; public final fun getOutputSchema ()Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema; public final fun getTitle ()Ljava/lang/String; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -4128,6 +4196,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/UnknownResourceConte public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public fun getMimeType ()Ljava/lang/String; public fun getUri ()Ljava/lang/String; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -4211,9 +4280,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequestPa public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/WithMeta { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/WithMeta$Companion; public abstract fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; } public final class io/modelcontextprotocol/kotlin/sdk/types/WithMeta$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/WithMeta$DefaultImpls { + public static fun get_meta (Lio/modelcontextprotocol/kotlin/sdk/types/WithMeta;)Lkotlinx/serialization/json/JsonObject; +} + diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport.kt new file mode 100644 index 0000000..55e09da --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport.kt @@ -0,0 +1,54 @@ +package io.modelcontextprotocol.kotlin.sdk.shared + +import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage +import kotlinx.coroutines.CompletableDeferred + +/** + * Implements [onClose], [onError] and [onMessage] functions of [Transport] providing + * corresponding [_onClose], [_onError] and [_onMessage] properties to use for an implementation. + */ +@Suppress("PropertyName") +public abstract class AbstractTransport : Transport { + protected var _onClose: (() -> Unit) = {} + private set + protected var _onError: ((Throwable) -> Unit) = {} + private set + + // to not skip messages + private val _onMessageInitialized = CompletableDeferred() + protected var _onMessage: (suspend ((JSONRPCMessage) -> Unit)) = { + _onMessageInitialized.await() + _onMessage.invoke(it) + } + private set + + override fun onClose(block: () -> Unit) { + val old = _onClose + _onClose = { + old() + block() + } + } + + override fun onError(block: (Throwable) -> Unit) { + val old = _onError + _onError = { e -> + old(e) + block(e) + } + } + + override fun onMessage(block: suspend (JSONRPCMessage) -> Unit) { + val old: suspend (JSONRPCMessage) -> Unit = when (_onMessageInitialized.isCompleted) { + true -> _onMessage + false -> { _ -> } + } + + _onMessage = { message -> + old(message) + block(message) + } + + _onMessageInitialized.complete(Unit) + } +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Transport.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Transport.kt index 0ce13ec..aa698b4 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Transport.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Transport.kt @@ -1,7 +1,6 @@ package io.modelcontextprotocol.kotlin.sdk.shared import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage -import kotlinx.coroutines.CompletableDeferred /** * Describes the minimal contract for MCP transport that a client or server can communicate over. @@ -47,53 +46,3 @@ public interface Transport { */ public fun onMessage(block: suspend (JSONRPCMessage) -> Unit) } - -/** - * Implements [onClose], [onError] and [onMessage] functions of [Transport] providing - * corresponding [_onClose], [_onError] and [_onMessage] properties to use for an implementation. - */ -@Suppress("PropertyName") -public abstract class AbstractTransport : Transport { - protected var _onClose: (() -> Unit) = {} - private set - protected var _onError: ((Throwable) -> Unit) = {} - private set - - // to not skip messages - private val _onMessageInitialized = CompletableDeferred() - protected var _onMessage: (suspend ((JSONRPCMessage) -> Unit)) = { - _onMessageInitialized.await() - _onMessage.invoke(it) - } - private set - - override fun onClose(block: () -> Unit) { - val old = _onClose - _onClose = { - old() - block() - } - } - - override fun onError(block: (Throwable) -> Unit) { - val old = _onError - _onError = { e -> - old(e) - block(e) - } - } - - override fun onMessage(block: suspend (JSONRPCMessage) -> Unit) { - val old: suspend (JSONRPCMessage) -> Unit = when (_onMessageInitialized.isCompleted) { - true -> _onMessage - false -> { _ -> } - } - - _onMessage = { message -> - old(message) - block(message) - } - - _onMessageInitialized.complete(Unit) - } -} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt index 1066843..f715be7 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt @@ -1,5 +1,7 @@ package io.modelcontextprotocol.kotlin.sdk.types +import io.modelcontextprotocol.kotlin.sdk.types.Icon.Theme.Dark +import io.modelcontextprotocol.kotlin.sdk.types.Icon.Theme.Light import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonObject @@ -27,6 +29,14 @@ public val SUPPORTED_PROTOCOL_VERSIONS: List = listOf( public sealed interface WithMeta { @SerialName("_meta") public val meta: JsonObject? + + @Deprecated( + message = "Use 'meta' instead.", + replaceWith = ReplaceWith("meta"), + ) + @Suppress("PropertyName", "VariableNaming") + public val _meta: JsonObject + get() = meta ?: EmptyJsonObject } // ============================================================================ diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.kt index f1d352e..fcaa22a 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.kt @@ -113,6 +113,22 @@ public data class GetPromptRequest(override val params: GetPromptRequestParams) @EncodeDefault override val method: Method = Method.Defined.PromptsGet + @Deprecated( + message = "Use constructor with GetPromptRequestParams instead", + replaceWith = ReplaceWith("GetPromptRequest(GetPromptRequestParams(name, arguments, meta))"), + ) + public constructor( + name: String, + arguments: Map? = null, + meta: RequestMeta? = null, + ) : this( + GetPromptRequestParams( + name = name, + arguments = arguments, + meta = meta, + ), + ) + /** * The name of the prompt or prompt template to retrieve. */ diff --git a/kotlin-sdk-test/build.gradle.kts b/kotlin-sdk-test/build.gradle.kts index b8532e1..ec44904 100644 --- a/kotlin-sdk-test/build.gradle.kts +++ b/kotlin-sdk-test/build.gradle.kts @@ -14,6 +14,7 @@ kotlin { implementation(dependencies.platform(libs.ktor.bom)) implementation(project(":kotlin-sdk")) implementation(kotlin("test")) + implementation(libs.kotest.assertions.core) implementation(libs.kotest.assertions.json) implementation(libs.kotlin.logging) implementation(libs.kotlinx.coroutines.test) diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerPromptsTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerPromptsTest.kt index fe0826b..1721bec 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerPromptsTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerPromptsTest.kt @@ -1,12 +1,24 @@ package io.modelcontextprotocol.kotlin.sdk.server +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.collections.shouldBeEmpty +import io.kotest.matchers.collections.shouldContain +import io.kotest.matchers.collections.shouldContainExactly +import io.kotest.matchers.collections.shouldHaveSize +import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.shouldBe +import io.kotest.matchers.throwable.shouldHaveMessage +import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject +import io.modelcontextprotocol.kotlin.sdk.GetPromptRequest import io.modelcontextprotocol.kotlin.sdk.GetPromptResult import io.modelcontextprotocol.kotlin.sdk.Implementation import io.modelcontextprotocol.kotlin.sdk.Method import io.modelcontextprotocol.kotlin.sdk.Prompt import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.McpException import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import kotlin.test.assertEquals @@ -21,9 +33,51 @@ class OldSchemaServerPromptsTest : OldSchemaAbstractServerFeaturesTest() { ) @Test - fun `removePrompt should remove a prompt`() = runTest { + fun `Should list no prompts by default`() = runTest { + client.listPrompts() shouldNotBeNull { + prompts.shouldBeEmpty() + } + } + + @Test + fun `Should add a prompt`() = runTest { // Add a prompt - val testPrompt = Prompt("test-prompt", "Test Prompt", null) + val testPrompt = Prompt( + name = "test-prompt-with-custom-handler", + description = "Test Prompt", + arguments = null, + ) + val expectedPromptResult = GetPromptResult( + description = "Test prompt description", + messages = listOf(), + ) + + server.addPrompt(testPrompt) { + expectedPromptResult + } + + client.getPrompt( + GetPromptRequest( + name = "test-prompt-with-custom-handler", + arguments = null, + ), + ) shouldBe expectedPromptResult + + client.listPrompts() shouldNotBeNull { + prompts shouldContainExactly listOf(testPrompt) + nextCursor shouldBe null + _meta shouldBe EmptyJsonObject + } + } + + @Test + fun `Should remove a prompt`() = runTest { + // given + val testPrompt = Prompt( + name = "test-prompt-to-remove", + description = "Test Prompt", + arguments = null, + ) server.addPrompt(testPrompt) { GetPromptResult( description = "Test prompt description", @@ -31,15 +85,32 @@ class OldSchemaServerPromptsTest : OldSchemaAbstractServerFeaturesTest() { ) } - // Remove the prompt + client.listPrompts() shouldNotBeNull { + prompts shouldContain testPrompt + } + + // when val result = server.removePrompt(testPrompt.name) - // Verify the prompt was removed + // then assertTrue(result, "Prompt should be removed successfully") + val mcpException = shouldThrow { + client.getPrompt( + GetPromptRequest( + name = testPrompt.name, + arguments = null, + ), + ) + } + mcpException shouldHaveMessage "MCP error -32603: Prompt not found: ${testPrompt.name}" + + client.listPrompts() shouldNotBeNull { + prompts.firstOrNull { it.name == testPrompt.name } shouldBe null + } } @Test - fun `removePrompts should remove multiple prompts and send notification`() = runTest { + fun `Should remove multiple prompts and send notification`() = runTest { // Add prompts val testPrompt1 = Prompt("test-prompt-1", "Test Prompt 1", null) val testPrompt2 = Prompt("test-prompt-2", "Test Prompt 2", null) @@ -56,11 +127,17 @@ class OldSchemaServerPromptsTest : OldSchemaAbstractServerFeaturesTest() { ) } + client.listPrompts() shouldNotBeNull { + prompts shouldHaveSize 2 + } // Remove the prompts val result = server.removePrompts(listOf(testPrompt1.name, testPrompt2.name)) // Verify the prompts were removed assertEquals(2, result, "Both prompts should be removed") + client.listPrompts() shouldNotBeNull { + prompts.shouldBeEmpty() + } } @Test @@ -82,21 +159,55 @@ class OldSchemaServerPromptsTest : OldSchemaAbstractServerFeaturesTest() { assertFalse(promptListChangedNotificationReceived, "No notification should be sent when prompt doesn't exist") } - @Test - fun `removePrompt should throw when prompts capability is not supported`() = runTest { + @Nested + inner class NoPromptsCapabilitiesTests { // Create server without prompts capability - val serverOptions = ServerOptions( - capabilities = ServerCapabilities(), - ) - val server = Server( + val serverWithoutPrompts = Server( Implementation(name = "test server", version = "1.0"), - serverOptions, + ServerOptions( + capabilities = ServerCapabilities(), + ), ) - // Verify that removing a prompt throws an exception - val exception = assertThrows { - server.removePrompt("test-prompt") + @Test + fun `RemovePrompt should throw when prompts capability is not supported`() = runTest { + // Verify that removing a prompt throws an exception + val exception = assertThrows { + serverWithoutPrompts.removePrompt("test-prompt") + } + assertEquals("Server does not support prompts capability.", exception.message) + } + + @Test + fun `Remove Prompts should throw when prompts capability is not supported`() = runTest { + // Verify that removing a prompt throws an exception + val exception = assertThrows { + serverWithoutPrompts.removePrompts(emptyList()) + } + assertEquals("Server does not support prompts capability.", exception.message) + } + + @Test + fun `Add Prompt should throw when prompts capability is not supported`() = runTest { + // Verify that removing a prompt throws an exception + val exception = assertThrows { + serverWithoutPrompts.addPrompt(name = "test-prompt") { + GetPromptResult( + description = "Test prompt description", + messages = listOf(), + ) + } + } + assertEquals("Server does not support prompts capability.", exception.message) + } + + @Test + fun `Add Prompts should throw when prompts capability is not supported`() = runTest { + // Verify that removing a prompt throws an exception + val exception = assertThrows { + serverWithoutPrompts.addPrompts(emptyList()) + } + assertEquals("Server does not support prompts capability.", exception.message) } - assertEquals("Server does not support prompts capability.", exception.message) } } From 31fd6d93da6bff435850f2ea8acdbae61017628a Mon Sep 17 00:00:00 2001 From: "Ruslan Ibrahimau (Ibragimov)" <493656+IRus@users.noreply.github.com> Date: Wed, 12 Nov 2025 14:32:54 +0100 Subject: [PATCH 066/449] Kotlin-MCP can't be used without ktor-bom starting from 0.7.5 (#390) (#392) --- gradle/libs.versions.toml | 21 ++++++++++----------- kotlin-sdk-client/build.gradle.kts | 1 - kotlin-sdk-core/build.gradle.kts | 1 - kotlin-sdk-server/build.gradle.kts | 1 - kotlin-sdk-test/build.gradle.kts | 1 - kotlin-sdk/build.gradle.kts | 1 - 6 files changed, 10 insertions(+), 16 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0283a64..dc8c4df 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -39,28 +39,27 @@ kotlinx-io-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-io-core", v kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "serialization" } # Ktor -ktor-bom = { group = "io.ktor", name = "ktor-bom", version.ref = "ktor" } -ktor-client-apache5 = { group = "io.ktor", name = "ktor-client-apache5" } -ktor-client-core = { group = "io.ktor", name = "ktor-client-core" } -ktor-client-logging = { group = "io.ktor", name = "ktor-client-logging" } -ktor-server-core = { group = "io.ktor", name = "ktor-server-core" } -ktor-server-sse = { group = "io.ktor", name = "ktor-server-sse" } -ktor-server-websockets = { group = "io.ktor", name = "ktor-server-websockets" } +ktor-client-apache5 = { group = "io.ktor", name = "ktor-client-apache5", version.ref = "ktor" } +ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" } +ktor-client-logging = { group = "io.ktor", name = "ktor-client-logging", version.ref = "ktor" } +ktor-server-core = { group = "io.ktor", name = "ktor-server-core", version.ref = "ktor" } +ktor-server-sse = { group = "io.ktor", name = "ktor-server-sse", version.ref = "ktor" } +ktor-server-websockets = { group = "io.ktor", name = "ktor-server-websockets", version.ref = "ktor" } # Testing awaitility = { group = "org.awaitility", name = "awaitility-kotlin", version.ref = "awaitility" } kotest-assertions-core = { group = "io.kotest", name = "kotest-assertions-core", version.ref = "kotest" } kotest-assertions-json = { group = "io.kotest", name = "kotest-assertions-json", version.ref = "kotest" } kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines" } -ktor-client-mock = { group = "io.ktor", name = "ktor-client-mock" } -ktor-server-test-host = { group = "io.ktor", name = "ktor-server-test-host" } +ktor-client-mock = { group = "io.ktor", name = "ktor-client-mock", version.ref = "ktor" } +ktor-server-test-host = { group = "io.ktor", name = "ktor-server-test-host", version.ref = "ktor" } mokksy = { group = "dev.mokksy", name = "mokksy", version.ref = "mokksy" } netty-bom = { group = "io.netty", name = "netty-bom", version.ref = "netty" } slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" } # Samples -ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio" } -ktor-server-cio = { group = "io.ktor", name = "ktor-server-cio" } +ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio", version.ref = "ktor" } +ktor-server-cio = { group = "io.ktor", name = "ktor-server-cio", version.ref = "ktor" } [plugins] kotlinx-binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binaryCompatibilityValidatorPlugin" } diff --git a/kotlin-sdk-client/build.gradle.kts b/kotlin-sdk-client/build.gradle.kts index 838afce..473c32b 100644 --- a/kotlin-sdk-client/build.gradle.kts +++ b/kotlin-sdk-client/build.gradle.kts @@ -32,7 +32,6 @@ kotlin { sourceSets { commonMain { dependencies { - implementation(dependencies.platform(libs.ktor.bom)) api(project(":kotlin-sdk-core")) api(libs.ktor.client.core) implementation(libs.kotlin.logging) diff --git a/kotlin-sdk-core/build.gradle.kts b/kotlin-sdk-core/build.gradle.kts index 9e07904..4e2531b 100644 --- a/kotlin-sdk-core/build.gradle.kts +++ b/kotlin-sdk-core/build.gradle.kts @@ -110,7 +110,6 @@ kotlin { commonMain { kotlin.srcDir(generateLibVersion) dependencies { - implementation(dependencies.platform(libs.ktor.bom)) api(libs.kotlinx.serialization.json) api(libs.kotlinx.coroutines.core) api(libs.kotlinx.io.core) diff --git a/kotlin-sdk-server/build.gradle.kts b/kotlin-sdk-server/build.gradle.kts index f00fc40..64a2749 100644 --- a/kotlin-sdk-server/build.gradle.kts +++ b/kotlin-sdk-server/build.gradle.kts @@ -9,7 +9,6 @@ kotlin { sourceSets { commonMain { dependencies { - implementation(dependencies.platform(libs.ktor.bom)) api(project(":kotlin-sdk-core")) api(libs.ktor.server.core) api(libs.ktor.server.sse) diff --git a/kotlin-sdk-test/build.gradle.kts b/kotlin-sdk-test/build.gradle.kts index ec44904..252d0e2 100644 --- a/kotlin-sdk-test/build.gradle.kts +++ b/kotlin-sdk-test/build.gradle.kts @@ -11,7 +11,6 @@ kotlin { sourceSets { commonTest { dependencies { - implementation(dependencies.platform(libs.ktor.bom)) implementation(project(":kotlin-sdk")) implementation(kotlin("test")) implementation(libs.kotest.assertions.core) diff --git a/kotlin-sdk/build.gradle.kts b/kotlin-sdk/build.gradle.kts index c59ba69..e59df60 100644 --- a/kotlin-sdk/build.gradle.kts +++ b/kotlin-sdk/build.gradle.kts @@ -7,7 +7,6 @@ kotlin { sourceSets { commonMain { dependencies { - implementation(dependencies.platform(libs.ktor.bom)) api(project(":kotlin-sdk-core")) api(project(":kotlin-sdk-client")) api(project(":kotlin-sdk-server")) From 8bdbd36c32e9d85f240c24a2c11bfd0f08b6fc6f Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Wed, 12 Nov 2025 20:18:57 +0200 Subject: [PATCH 067/449] Add backwards compatibility constructors and tests (#395) - Updated integration tests to include assertions for `meta` being `null` in the `listTools`, `listResources`, `readResource`, and `callTool` results. - Renamed test cases in `SseServerIntegrationTest` for clarity. - Added (deprecated) synthetic constructors for various request parameter classes. - Enhanced naming conventions and structure in integration test cases. - Replaced `throw Error(...)` with `error(...)` for cleaner error handling. ## Motivation and Context To verify backwards-compatibility after schema change ## How Has This Been Tested? CI, integration tests, regression tests (samples) ## Breaking Changes ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [x] Test update ## Checklist - [x] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [x] My code follows the repository's style guidelines - [ ] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed ## Additional context --- kotlin-sdk-core/api/kotlin-sdk-core.api | 40 +++++++-- .../kotlin/sdk/shared/Protocol.kt | 2 +- .../kotlin/sdk/types/completion.kt | 24 ++++-- .../kotlin/sdk/types/prompts.kt | 26 ++++-- .../kotlin/sdk/types/request.kt | 25 ++++++ .../kotlin/sdk/types/resources.kt | 82 ++++++++++++++----- .../kotlin/sdk/types/roots.kt | 9 ++ .../kotlin/sdk/types/tools.kt | 26 ++++-- ...ldSchemaAbstractResourceIntegrationTest.kt | 3 + .../OldSchemaAbstractToolIntegrationTest.kt | 27 +++--- .../test/kotlin/SseServerIntegrationTest.kt | 57 +++++++++++-- 11 files changed, 250 insertions(+), 71 deletions(-) diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index 3c1aec4..3e8851a 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -938,6 +938,8 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CommonKt { public final class io/modelcontextprotocol/kotlin/sdk/types/CompleteRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequest$Companion; + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Argument;Lio/modelcontextprotocol/kotlin/sdk/types/Reference;Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Context;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Argument;Lio/modelcontextprotocol/kotlin/sdk/types/Reference;Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Context;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public fun (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams; public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequest; @@ -2057,12 +2059,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest : public fun ()V public fun (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;)V public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams; public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest;Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest; public fun equals (Ljava/lang/Object;)Z - public final fun getCursor ()Ljava/lang/String; - public final fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public fun getCursor ()Ljava/lang/String; + public fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams; public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; @@ -2123,12 +2127,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplate public fun ()V public fun (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;)V public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams; public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesRequest; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesRequest;Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesRequest; public fun equals (Ljava/lang/Object;)Z - public final fun getCursor ()Ljava/lang/String; - public final fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public fun getCursor ()Ljava/lang/String; + public fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams; public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; @@ -2189,12 +2195,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest public fun ()V public fun (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;)V public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams; public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest;Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest; public fun equals (Ljava/lang/Object;)Z - public final fun getCursor ()Ljava/lang/String; - public final fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public fun getCursor ()Ljava/lang/String; + public fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams; public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; @@ -2255,6 +2263,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest : i public fun ()V public fun (Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams;)V public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams; public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest;Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest; @@ -2318,12 +2327,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest : i public fun ()V public fun (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;)V public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams; public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest;Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest; public fun equals (Ljava/lang/Object;)Z - public final fun getCursor ()Ljava/lang/String; - public final fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public fun getCursor ()Ljava/lang/String; + public fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams; public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; @@ -2641,6 +2652,8 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/NotificationParams$D public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequest : io/modelcontextprotocol/kotlin/sdk/types/Request { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequest$Companion; + public fun getCursor ()Ljava/lang/String; + public fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; public abstract fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams; } @@ -2648,6 +2661,11 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequest$Com public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequest$DefaultImpls { + public static fun getCursor (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequest;)Ljava/lang/String; + public static fun getMeta-VI-3G7E (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequest;)Lkotlinx/serialization/json/JsonObject; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams$Companion; public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -3023,6 +3041,8 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/RPCError$ErrorCode { public final class io/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest$Companion; public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequestParams;)V + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequestParams; public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest;Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest; @@ -3906,6 +3926,8 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/StopReason$Companion public final class io/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest$Companion; public fun (Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequestParams;)V + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequestParams; public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest;Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest; @@ -4219,6 +4241,8 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/UnknownResourceConte public final class io/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest$Companion; public fun (Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequestParams;)V + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequestParams; public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest;Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest; diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt index 7d55924..64fe355 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt @@ -397,7 +397,7 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio public suspend fun request(request: Request, options: RequestOptions? = null): T { logger.trace { "Sending request: ${request.method}" } val result = CompletableDeferred() - val transport = transport ?: throw Error("Not connected") + val transport = transport ?: error("Not connected") if (this@Protocol.options?.enforceStrictCapabilities == true) { assertCapabilityForMethod(request.method) diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.kt index 89d853d..59f558c 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.kt @@ -1,5 +1,7 @@ package io.modelcontextprotocol.kotlin.sdk.types +import io.modelcontextprotocol.kotlin.sdk.types.CompleteRequestParams.Argument +import io.modelcontextprotocol.kotlin.sdk.types.CompleteRequestParams.Context import kotlinx.serialization.EncodeDefault import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerialName @@ -20,7 +22,7 @@ public data class CompleteRequest(override val params: CompleteRequestParams) : /** * The argument's information for which completion options are requested. */ - public val argument: CompleteRequestParams.Argument + public val argument: Argument get() = params.argument /** @@ -32,11 +34,23 @@ public data class CompleteRequest(override val params: CompleteRequestParams) : /** * Additional, context for generating completions. */ - public val context: CompleteRequestParams.Context? + public val context: Context? get() = params.context public val meta: RequestMeta? get() = params.meta + + @Deprecated( + message = "Will be removed in a future release.", + replaceWith = ReplaceWith(expression = "CompleteRequest(CompleteRequestParams(argument, ref, context, meta))"), + level = DeprecationLevel.WARNING, + ) + public constructor( + argument: Argument, + ref: Reference, + context: Context? = null, + meta: RequestMeta? = null, + ) : this(CompleteRequestParams(argument, ref, context, meta)) } /** @@ -67,7 +81,7 @@ public data class CompleteRequestParams( /** * Additional context to help generate more relevant completions. * - * @property arguments Previously-resolved variables in a URI template or prompt. + * @property arguments Previously resolved variables in a URI template or prompt. * These can be used to provide context-aware completions. * For example, if completing a file path, this might contain the repository or directory context. */ @@ -100,8 +114,8 @@ public data class CompleteResult( * This can exceed the number of values actually sent in the response, * indicating that pagination or filtering may be needed. * @property hasMore Indicates whether there are additional completion options beyond - * those provided in the current response, even if the exact total is unknown. - * Use this when the complete set of completions is too large to calculate upfront. + * those provided in the current response, even if the exact total is unknown. + * Use this when the complete set of completions is too large to calculate upfront. */ @Serializable public data class Completion(val values: List, val total: Int? = null, val hasMore: Boolean? = null) { diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.kt index fcaa22a..68a892f 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.kt @@ -205,16 +205,24 @@ public data class ListPromptsRequest(override val params: PaginatedRequestParams override val method: Method = Method.Defined.PromptsList /** - * An opaque token representing the current pagination position. + * Secondary constructor for creating a [ListPromptsRequest] instance + * using optional cursor and metadata parameters. + * + * This constructor simplifies the creation of the [ListPromptsRequest] by allowing a cursor + * and metadata to be provided. + * + * @param cursor Optional cursor string to specify the starting point of the paginated request. + * @param meta Optional metadata associated with the request. */ - public val cursor: String? - get() = params?.cursor - - /** - * Metadata for this request. May include a progressToken for out-of-band progress notifications. - */ - public val meta: RequestMeta? - get() = params?.meta + @Deprecated( + message = "Use constructor with ListPromptsParams instead", + replaceWith = ReplaceWith("ListPromptsRequest(ListPromptsParams(name, arguments, meta))"), + level = DeprecationLevel.WARNING, + ) + public constructor( + cursor: String?, + meta: RequestMeta? = null, + ) : this(paginatedRequestParams(cursor, meta)) } /** diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.kt index 855e2f2..7e10204 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.kt @@ -27,6 +27,12 @@ public value class RequestMeta(public val json: JsonObject) { @Serializable public sealed interface RequestParams { + /** + * The `_meta` property/parameter is reserved by MCP + * to allow clients and servers to attach additional metadata to their interactions. + * + * @see MCP specification + */ @SerialName("_meta") public val meta: RequestMeta? } @@ -68,6 +74,18 @@ public sealed interface ServerRequest : Request @Serializable public sealed interface PaginatedRequest : Request { public override val params: PaginatedRequestParams? + + /** + * An opaque token representing the current pagination position. + */ + public val cursor: String? + get() = params?.cursor + + /** + * Metadata for this request. May include a progressToken for out-of-band progress notifications. + */ + public val meta: RequestMeta? + get() = params?.meta } /** @@ -114,3 +132,10 @@ public data class PaginatedRequestParams( @SerialName("_meta") override val meta: RequestMeta? = null, ) : RequestParams + +internal fun paginatedRequestParams(cursor: String?, meta: RequestMeta?): PaginatedRequestParams? = + if (cursor == null && meta == null) { + null + } else { + PaginatedRequestParams(cursor = cursor, meta = meta) + } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.kt index 121864f..ba9533e 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.kt @@ -17,7 +17,7 @@ public sealed interface ResourceLike : WithMeta * Resources represent data sources such as files, database entries, API responses, * or other structured data that can be read by clients. * - * @property uri The URI of this resource. Can use any protocol scheme (file://, http://, etc.). + * @property uri The URI of this resource. Can use any protocol scheme (`file://`, `http://`, etc.). * @property name The programmatic identifier for this resource. * Intended for logical use and API identification. If [title] is not provided, * this should be used as a fallback display name. @@ -25,7 +25,8 @@ public sealed interface ResourceLike : WithMeta * Clients can use this to improve the LLM's understanding of available resources. * It can be thought of like a "hint" to the model. * @property mimeType The MIME type of this resource, if known (e.g., "text/plain", "application/json", "image/png"). - * @property size The size of the raw resource content in bytes (i.e., before base64 encoding or any tokenization), if known. + * @property size The size of the raw resource content in bytes + * (i.e., before base64 encoding or any tokenization), if known. * Hosts can use this to display file sizes and estimate context window usage. * @property title Optional human-readable display name for this resource. * Intended for UI and end-user contexts, optimized to be easily understood @@ -192,16 +193,24 @@ public data class ListResourcesRequest(override val params: PaginatedRequestPara override val method: Method = Method.Defined.ResourcesList /** - * An opaque token representing the current pagination position. + * Secondary constructor for creating a [ListResourcesRequest] instance + * using optional cursor and metadata parameters. + * + * This constructor simplifies the creation of the [ListResourcesRequest] by allowing a cursor + * and metadata to be provided. + * + * @param cursor Optional cursor string to specify the starting point of the paginated request. + * @param meta Optional metadata associated with the request. */ - public val cursor: String? - get() = params?.cursor - - /** - * Metadata for this request. May include a progressToken for out-of-band progress notifications. - */ - public val meta: RequestMeta? - get() = params?.meta + @Deprecated( + message = "Use the constructor with PaginatedRequestParams property instead", + replaceWith = ReplaceWith("ReadResourceRequest(PaginatedRequestParams(cursor, meta))"), + level = DeprecationLevel.WARNING, + ) + public constructor( + cursor: String?, + meta: RequestMeta? = null, + ) : this(paginatedRequestParams(cursor, meta)) } /** @@ -252,6 +261,16 @@ public data class ReadResourceRequest(override val params: ReadResourceRequestPa */ public val meta: RequestMeta? get() = params.meta + + @Deprecated( + message = "Use the constructor with ReadResourceRequestParams property instead", + replaceWith = ReplaceWith("ReadResourceRequest(ReadResourceRequestParams(uri, meta))"), + level = DeprecationLevel.WARNING, + ) + public constructor( + uri: String, + meta: RequestMeta? = null, + ) : this(ReadResourceRequestParams(uri, meta)) } /** @@ -318,6 +337,16 @@ public data class SubscribeRequest(override val params: SubscribeRequestParams) */ public val meta: RequestMeta? get() = params.meta + + @Deprecated( + message = "Use the constructor with SubscribeRequestParams property instead", + replaceWith = ReplaceWith("ReadResourceRequest(SubscribeRequestParams(uri, meta))"), + level = DeprecationLevel.WARNING, + ) + public constructor( + uri: String, + meta: RequestMeta? = null, + ) : this(SubscribeRequestParams(uri, meta)) } /** @@ -363,6 +392,11 @@ public data class UnsubscribeRequest(override val params: UnsubscribeRequestPara */ public val meta: RequestMeta? get() = params.meta + + public constructor( + uri: String, + meta: RequestMeta? = null, + ) : this(UnsubscribeRequestParams(uri, meta)) } /** @@ -401,16 +435,24 @@ public data class ListResourceTemplatesRequest(override val params: PaginatedReq override val method: Method = Method.Defined.ResourcesTemplatesList /** - * An opaque token representing the current pagination position. + * Secondary constructor for creating a [ListResourceTemplatesRequest] instance + * using optional cursor and metadata parameters. + * + * This constructor simplifies the creation of the [ListResourceTemplatesRequest] by allowing a cursor + * and metadata to be provided. + * + * @param cursor Optional cursor string to specify the starting point of the paginated request. + * @param meta Optional metadata associated with the request. */ - public val cursor: String? - get() = params?.cursor - - /** - * Metadata for this request. May include a progressToken for out-of-band progress notifications. - */ - public val meta: RequestMeta? - get() = params?.meta + @Deprecated( + message = "Use the constructor with PaginatedRequestParams property instead", + replaceWith = ReplaceWith("ListResourceTemplatesRequest(PaginatedRequestParams(cursor, meta))"), + level = DeprecationLevel.WARNING, + ) + public constructor( + cursor: String?, + meta: RequestMeta? = null, + ) : this(paginatedRequestParams(cursor, meta)) } /** diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.kt index af7b1e7..cc08739 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.kt @@ -62,6 +62,15 @@ public data class ListRootsRequest(override val params: BaseRequestParams? = nul public val meta: RequestMeta? get() = params?.meta + + @Deprecated( + message = "Use the constructor with BaseRequestParams property instead", + replaceWith = ReplaceWith("ListRootsRequest(BaseRequestParams(meta))"), + level = DeprecationLevel.WARNING, + ) + public constructor( + meta: RequestMeta?, + ) : this(BaseRequestParams(meta)) } /** diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt index 15f0480..6cba208 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt @@ -228,16 +228,24 @@ public data class ListToolsRequest(override val params: PaginatedRequestParams? override val method: Method = Method.Defined.ToolsList /** - * An opaque token representing the current pagination position. + * Secondary constructor for creating a [ListToolsRequest] instance + * using optional cursor and metadata parameters. + * + * This constructor simplifies the creation of the [ListToolsRequest] by allowing a cursor + * and metadata to be provided. + * + * @param cursor Optional cursor string to specify the starting point of the paginated request. + * @param meta Optional metadata associated with the request. */ - public val cursor: String? - get() = params?.cursor - - /** - * Metadata for this request. May include a progressToken for out-of-band progress notifications. - */ - public val meta: RequestMeta? - get() = params?.meta + @Deprecated( + message = "Use the constructor with BaseRequestParams property instead", + replaceWith = ReplaceWith("ListToolsRequest(BaseRequestParams(meta))"), + level = DeprecationLevel.WARNING, + ) + public constructor( + cursor: String?, + meta: RequestMeta? = null, + ) : this(paginatedRequestParams(cursor, meta)) } /** diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractResourceIntegrationTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractResourceIntegrationTest.kt index a7a4202..7cc5f10 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractResourceIntegrationTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractResourceIntegrationTest.kt @@ -16,6 +16,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertNull import org.junit.jupiter.api.assertThrows import java.util.concurrent.atomic.AtomicBoolean import kotlin.test.Ignore @@ -145,6 +146,7 @@ abstract class OldSchemaAbstractResourceIntegrationTest : OldSchemaKotlinTestBas val result = client.listResources() assertNotNull(result, "List resources result should not be null") + assertNull(actual = result.meta, "List resources meta should be null") assertTrue(result.resources.isNotEmpty(), "Resources list should not be empty") val testResource = result.resources.find { it.uri == testResourceUri } @@ -158,6 +160,7 @@ abstract class OldSchemaAbstractResourceIntegrationTest : OldSchemaKotlinTestBas val result = client.readResource(ReadResourceRequest(ReadResourceRequestParams(uri = testResourceUri))) assertNotNull(result, "Read resource result should not be null") + assertNull(actual = result.meta, "Read resource result meta should be null") assertTrue(result.contents.isNotEmpty(), "Resource contents should not be empty") val content = result.contents.firstOrNull() as? TextResourceContents diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractToolIntegrationTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractToolIntegrationTest.kt index 6086b47..cd12d79 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractToolIntegrationTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractToolIntegrationTest.kt @@ -21,6 +21,7 @@ import kotlinx.serialization.json.buildJsonArray import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.put import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertNull import java.text.DecimalFormat import java.text.DecimalFormatSymbols import java.util.Locale @@ -431,7 +432,8 @@ abstract class OldSchemaAbstractToolIntegrationTest : OldSchemaKotlinTestBase() fun testListTools(): Unit = runBlocking(Dispatchers.IO) { val result = client.listTools() - assertNotNull(result, "List utils result should not be null") + assertNotNull(result, "List tools result should not be null") + assertNull(actual = result.meta, "List tools result meta should be null") assertTrue(result.tools.isNotEmpty(), "Tools list should not be empty") val testTool = result.tools.find { it.name == testToolName } @@ -449,7 +451,8 @@ abstract class OldSchemaAbstractToolIntegrationTest : OldSchemaKotlinTestBase() val testText = "Hello, world!" val arguments = mapOf("text" to testText) - val result = client.callTool(testToolName, arguments) as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult + val result = client.callTool(testToolName, arguments) + assertNull(actual = result.meta, "CallTool result meta should be null") val actualContent = result.structuredContent.toString() val expectedContent = """ @@ -478,7 +481,7 @@ abstract class OldSchemaAbstractToolIntegrationTest : OldSchemaKotlinTestBase() val result = client.callTool( complexToolName, arguments, - ) as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult + ) val actualContent = result.structuredContent.toString() val expectedContent = """ @@ -518,7 +521,7 @@ abstract class OldSchemaAbstractToolIntegrationTest : OldSchemaKotlinTestBase() val errorResult = client.callTool( errorToolName, errorArgs, - ) as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult + ) val actualError = errorResult.structuredContent.toString() val expectedError = """ @@ -538,7 +541,7 @@ abstract class OldSchemaAbstractToolIntegrationTest : OldSchemaKotlinTestBase() val exceptionResult = client.callTool( errorToolName, exceptionArgs, - ) as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult + ) assertTrue(exceptionResult.isError ?: false, "isError should be true for exception") @@ -563,7 +566,7 @@ abstract class OldSchemaAbstractToolIntegrationTest : OldSchemaKotlinTestBase() val result = client.callTool( multiContentToolName, arguments, - ) as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult + ) assertEquals( 2, @@ -603,7 +606,7 @@ abstract class OldSchemaAbstractToolIntegrationTest : OldSchemaKotlinTestBase() val textOnlyResult = client.callTool( multiContentToolName, textOnlyArgs, - ) as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult + ) assertEquals( 1, @@ -645,7 +648,7 @@ abstract class OldSchemaAbstractToolIntegrationTest : OldSchemaKotlinTestBase() arguments = arguments, ), ), - ) as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult + ) val actualContent = result.structuredContent.toString() val expectedContent = """ @@ -671,7 +674,7 @@ abstract class OldSchemaAbstractToolIntegrationTest : OldSchemaKotlinTestBase() val result = client.callTool( largeToolName, arguments, - ) as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult + ) val content = result.content.firstOrNull() as TextContent assertNotNull(content, "Tool result content should be TextContent") @@ -692,7 +695,7 @@ abstract class OldSchemaAbstractToolIntegrationTest : OldSchemaKotlinTestBase() val arguments = mapOf("delay" to delay) val startTime = System.currentTimeMillis() - val result = client.callTool(slowToolName, arguments) as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult + val result = client.callTool(slowToolName, arguments) val endTime = System.currentTimeMillis() val content = result.content.firstOrNull() as? TextContent @@ -718,7 +721,7 @@ abstract class OldSchemaAbstractToolIntegrationTest : OldSchemaKotlinTestBase() val result = client.callTool( specialCharsToolName, arguments, - ) as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult + ) val content = result.content.firstOrNull() as? TextContent assertNotNull(content, "Tool result content should be TextContent") @@ -802,7 +805,7 @@ abstract class OldSchemaAbstractToolIntegrationTest : OldSchemaKotlinTestBase() } assertNotNull(result, "Tool call result should not be null") - val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult + val callResult = result assertTrue(callResult.isError ?: false, "isError should be true for non-existent tool") val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent diff --git a/samples/kotlin-mcp-server/src/test/kotlin/SseServerIntegrationTest.kt b/samples/kotlin-mcp-server/src/test/kotlin/SseServerIntegrationTest.kt index 09780ab..7f8b6a9 100644 --- a/samples/kotlin-mcp-server/src/test/kotlin/SseServerIntegrationTest.kt +++ b/samples/kotlin-mcp-server/src/test/kotlin/SseServerIntegrationTest.kt @@ -1,40 +1,83 @@ import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject +import io.modelcontextprotocol.kotlin.sdk.ReadResourceRequest import io.modelcontextprotocol.kotlin.sdk.TextContent +import io.modelcontextprotocol.kotlin.sdk.TextResourceContents import io.modelcontextprotocol.kotlin.sdk.client.Client import kotlinx.coroutines.runBlocking import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs +import kotlin.test.assertNotNull +import kotlin.test.assertTrue class SseServerIntegrationTest { private val client: Client = TestEnvironment.client @Test - fun `should get tools`(): Unit = runBlocking { - val tools = client.listTools().tools + fun `should list tools`(): Unit = runBlocking { + // when + val listToolsResult = client.listTools() + // then + assertEquals(expected = EmptyJsonObject, actual = listToolsResult._meta) + + val tools = listToolsResult.tools + assertEquals(actual = tools.size, expected = 1) assertEquals(expected = listOf("kotlin-sdk-tool"), actual = tools.map { it.name }) } @Test - fun `should get prompts`(): Unit = runBlocking { - val prompts = client.listPrompts().prompts + fun `should list prompts`(): Unit = runBlocking { + // when + val listPromptsResult = client.listPrompts() + + // then + assertEquals(expected = EmptyJsonObject, actual = listPromptsResult._meta) + + val prompts = listPromptsResult.prompts assertEquals(expected = listOf("Kotlin Developer"), actual = prompts.map { it.name }) } @Test - fun `should get resources`(): Unit = runBlocking { - val resources = client.listResources().resources + fun `should list resources`(): Unit = runBlocking { + val listResourcesResult = client.listResources() + + // then + assertEquals(expected = EmptyJsonObject, actual = listResourcesResult._meta) + val resources = listResourcesResult.resources assertEquals(expected = listOf("Web Search"), actual = resources.map { it.name }) } + @Test + fun `should get resource`(): Unit = runBlocking { + val testResourceUri = "https://search.com/" + val getResourcesResult = client.readResource( + ReadResourceRequest(uri = testResourceUri), + ) + + // then + assertEquals(expected = EmptyJsonObject, actual = getResourcesResult._meta) + val contents = getResourcesResult.contents + assertEquals(expected = 1, actual = contents.size) + assertTrue { + contents.contains( + TextResourceContents("Placeholder content for $testResourceUri", testResourceUri, "text/html"), + ) + } + } + @Test fun `should call tool`(): Unit = runBlocking { + // when val toolResult = client.callTool("kotlin-sdk-tool", EmptyJsonObject) - val content = toolResult?.content?.single() + + // then + assertNotNull(toolResult) + assertEquals(expected = EmptyJsonObject, actual = toolResult._meta) + val content = toolResult.content.single() assertIs(content, "Tool result should be a text content") assertEquals(expected = "Hello, world!", actual = content.text) From 93d544e618060f2ba941626c4879ad89c550eb05 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Wed, 12 Nov 2025 20:53:25 +0200 Subject: [PATCH 068/449] 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 - [ ] 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 ## Additional context --- kotlin-sdk-client/api/kotlin-sdk-client.api | 5 ++ .../kotlin/sdk/client/Client.kt | 25 +++++++++ kotlin-sdk-core/api/kotlin-sdk-core.api | 3 ++ .../kotlin/sdk/ExperimentalMcpApi.kt | 54 +++++++++++++++++++ .../sdk/client/StdioClientTransportTest.kt | 12 ++--- .../sdk/server/ServerInstructionsTest.kt | 34 ++++++++---- 6 files changed, 118 insertions(+), 15 deletions(-) create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/ExperimentalMcpApi.kt diff --git a/kotlin-sdk-client/api/kotlin-sdk-client.api b/kotlin-sdk-client/api/kotlin-sdk-client.api index 7a1e0fa..e071e81 100644 --- a/kotlin-sdk-client/api/kotlin-sdk-client.api +++ b/kotlin-sdk-client/api/kotlin-sdk-client.api @@ -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 ()V public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;Z)V diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt index f0283c9..c86cad8 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt @@ -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. * diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index 3e8851a..fa85476 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -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; diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/ExperimentalMcpApi.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/ExperimentalMcpApi.kt new file mode 100644 index 0000000..7278f4e --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/ExperimentalMcpApi.kt @@ -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 diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt index 7d0fa36..f0c7aed 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt @@ -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() diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerInstructionsTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerInstructionsTest.kt index 89eeaef..53d0fd1 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerInstructionsTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerInstructionsTest.kt @@ -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) } From 4aeed923f6a5bf5210334550bde835bfe8b9fb9b Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Thu, 13 Nov 2025 21:05:45 +0100 Subject: [PATCH 069/449] Add warning message to ExperimentalMcpApi annotation and change level (#398) - add the warning message to `ExperimentalMcpApi` annotation - change level from error to warning ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] 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 - [x] I have added or updated documentation as needed --- .../io/modelcontextprotocol/kotlin/sdk/ExperimentalMcpApi.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/ExperimentalMcpApi.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/ExperimentalMcpApi.kt index 7278f4e..aa8f179 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/ExperimentalMcpApi.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/ExperimentalMcpApi.kt @@ -35,7 +35,10 @@ import kotlin.annotation.AnnotationTarget.VALUE_PARAMETER * - Property setters * - Type aliases */ -@RequiresOptIn +@RequiresOptIn( + message = "This API is experimental. It may be changed in the future without notice.", + level = RequiresOptIn.Level.WARNING, +) @MustBeDocumented @Target( CLASS, From 666cfd6b8298abf3a68a772a8a9e3eabe1d6d320 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Fri, 14 Nov 2025 10:39:43 +0100 Subject: [PATCH 070/449] Add test with new schema types (#389) Add tests with new schema types ## Motivation and Context ## How Has This Been Tested? The repository currently contains duplicated tests: - tests using the new schema - tests using the old schema for compatibility checks. they will be removed once the deprecation level is raised ## Breaking Changes No ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [x] Testing ## 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --------- Co-authored-by: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> --- .../sdk/client/ClientMetaParameterTest.kt | 4 +- .../kotlin/sdk/client/MockTransport.kt | 14 +- .../OldSchemaClientMetaParameterTest.kt | 275 +++++ .../sdk/client/OldSchemaMockTransport.kt | 94 ++ ...SchemaStreamableHttpClientTransportTest.kt | 422 +++++++ .../StreamableHttpClientTransportTest.kt | 12 +- .../AbstractStreamableHttpClientTest.kt | 2 +- .../kotlin/sdk/client/MockMcp.kt | 229 ++++ .../kotlin/sdk/client/OldSchemaMockMcp.kt | 4 +- .../sdk/client/StreamableHttpClientTest.kt | 214 ++++ ...OldSchemaAudioContentSerializationTest.kt} | 2 +- .../sdk/models/ProgressNotificationsTest.kt | 72 ++ .../sdk/shared/OldSchemaReadBufferTest.kt | 62 + .../kotlin/sdk/shared/ReadBufferTest.kt | 4 +- .../OldSchemaStdioServerTransportTest.kt | 140 +++ .../sdk/server/StdioServerTransportTest.kt | 8 +- .../kotlin/sdk/client/ClientTest.kt | 1039 +++++++++++++++++ .../sdk/client/OldSchemaSseTransportTest.kt | 123 ++ .../kotlin/sdk/client/SseTransportTest.kt | 4 +- .../sdk/integration/InMemoryTransportTest.kt | 6 +- .../OldSchemaInMemoryTransportTest.kt | 110 ++ .../kotlin/sdk/shared/BaseTransportTest.kt | 8 +- .../kotlin/sdk/shared/InMemoryTransport.kt | 2 +- .../sdk/shared/OldSchemaBaseTransportTest.kt | 63 + .../sdk/shared/OldSchemaInMemoryTransport.kt | 47 + .../kotlin/AbstractPromptIntegrationTest.kt | 694 +++++++++++ .../kotlin/AbstractResourceIntegrationTest.kt | 317 +++++ .../kotlin/AbstractToolIntegrationTest.kt | 794 +++++++++++++ .../sdk/integration/kotlin/KotlinTestBase.kt | 185 +++ ...kt => OldSchemaResourceIntegrationTestSse} | 0 .../kotlin/sse/PromptIntegrationTestSse.kt | 7 + .../kotlin/sse/ResourceIntegrationTestSse.kt | 7 + .../kotlin/sse/ToolIntegrationTestSse.kt | 7 + .../stdio/PromptIntegrationTestStdio.kt | 7 + .../stdio/ResourceIntegrationTestStdio.kt | 7 + .../kotlin/stdio/ToolIntegrationTestStdio.kt | 7 + .../websocket/WebSocketIntegrationTest.kt | 199 ++++ .../sse/AbstractSseIntegrationTest.kt | 105 ++ .../sdk/integration/sse/SseIntegrationTest.kt | 114 ++ .../AbstractKotlinClientTsServerTest.kt | 77 ++ ...dSchemaAbstractKotlinClientTsServerTest.kt | 2 +- .../typescript/OldSchemaTsTestBase.kt | 455 ++++++++ .../sdk/integration/typescript/TsTestBase.kt | 2 +- .../KotlinClientTsServerEdgeCasesTestSse.kt | 215 ++++ .../sse/KotlinClientTsServerTestSse.kt | 4 +- .../sse/KotlinServerForTsClientSse.kt | 464 ++++++++ ...emaKotlinClientTsServerEdgeCasesTestSse.kt | 8 +- .../OldSchemaKotlinClientTsServerTestSse.kt | 49 + .../OldSchemaKotlinServerForTsClientSse.kt | 10 +- .../KotlinClientTsServerEdgeCasesTestStdio.kt | 174 +++ .../stdio/KotlinClientTsServerTestStdio.kt | 23 + ...aKotlinClientTsServerEdgeCasesTestStdio.kt | 8 +- .../OldSchemaKotlinClientTsServerTestStdio.kt | 4 +- .../sdk/server/AbstractServerFeaturesTest.kt | 44 + .../server/OldSchemaServerInstructionsTest.kt | 68 ++ .../kotlin/sdk/server/ServerPromptsTest.kt | 100 ++ .../kotlin/sdk/server/ServerResourcesTest.kt | 135 +++ .../kotlin/sdk/server/ServerToolsTest.kt | 89 ++ 58 files changed, 7286 insertions(+), 55 deletions(-) create mode 100644 kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaClientMetaParameterTest.kt create mode 100644 kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaMockTransport.kt create mode 100644 kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaStreamableHttpClientTransportTest.kt create mode 100644 kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt create mode 100644 kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt rename kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/{AudioContentSerializationTest.kt => OldSchemaAudioContentSerializationTest.kt} (94%) create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationsTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaReadBufferTest.kt create mode 100644 kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaStdioServerTransportTest.kt create mode 100644 kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt create mode 100644 kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaSseTransportTest.kt create mode 100644 kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/OldSchemaInMemoryTransportTest.kt create mode 100644 kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaBaseTransportTest.kt create mode 100644 kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaInMemoryTransport.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/KotlinTestBase.kt rename kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/{OldSchemaResourceIntegrationTestSse.kt => OldSchemaResourceIntegrationTestSse} (100%) create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/PromptIntegrationTestSse.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/ResourceIntegrationTestSse.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/ToolIntegrationTestSse.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/PromptIntegrationTestStdio.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/ResourceIntegrationTestStdio.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/ToolIntegrationTestStdio.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/WebSocketIntegrationTest.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/AbstractSseIntegrationTest.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/SseIntegrationTest.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractKotlinClientTsServerTest.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/OldSchemaTsTestBase.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerEdgeCasesTestSse.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerTestSse.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerTestStdio.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/AbstractServerFeaturesTest.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerInstructionsTest.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsTest.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesTest.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsTest.kt diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientMetaParameterTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientMetaParameterTest.kt index e706107..600859f 100644 --- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientMetaParameterTest.kt +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientMetaParameterTest.kt @@ -1,7 +1,7 @@ package io.modelcontextprotocol.kotlin.sdk.client -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.JSONRPCRequest +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest import kotlinx.coroutines.test.runTest import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.boolean diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockTransport.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockTransport.kt index c987619..860ed14 100644 --- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockTransport.kt +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockTransport.kt @@ -1,13 +1,13 @@ package io.modelcontextprotocol.kotlin.sdk.client -import io.modelcontextprotocol.kotlin.sdk.CallToolResult -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.InitializeResult -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage -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.types.CallToolResult +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.InitializeResult +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaClientMetaParameterTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaClientMetaParameterTest.kt new file mode 100644 index 0000000..b541176 --- /dev/null +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaClientMetaParameterTest.kt @@ -0,0 +1,275 @@ +package io.modelcontextprotocol.kotlin.sdk.client + +import io.modelcontextprotocol.kotlin.sdk.Implementation +import io.modelcontextprotocol.kotlin.sdk.JSONRPCRequest +import kotlinx.coroutines.test.runTest +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.boolean +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonPrimitive +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertContains +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertTrue + +/** + * Comprehensive test suite for MCP Client meta parameter functionality + * + * Tests cover: + * - Meta key validation according to MCP specification + * - JSON type conversion for various data types + * - Error handling for invalid meta keys + * - Integration with callTool method + */ +class OldSchemaClientMetaParameterTest { + + private lateinit var client: Client + private lateinit var mockTransport: OldSchemaMockTransport + private val clientInfo = Implementation("test-client", "1.0.0") + + @BeforeTest + fun setup() = runTest { + mockTransport = OldSchemaMockTransport() + client = Client(clientInfo = clientInfo) + mockTransport.setupInitializationResponse() + client.connect(mockTransport) + } + + @Test + fun `should accept valid meta keys without throwing exception`() = runTest { + val validMeta = buildMap { + put("simple-key", "value1") + put("api.example.com/version", "1.0") + put("com.company.app/setting", "enabled") + put("retry_count", 3) + put("user.preference", true) + put("valid123", "alphanumeric") + put("multi.dot.name", "multiple-dots") + put("under_score", "underscore") + put("hyphen-dash", "hyphen") + put("org.apache.kafka/consumer-config", "complex-valid-prefix") + } + + val result = runCatching { + client.callTool("test-tool", mapOf("arg" to "value"), validMeta) + } + + assertTrue(result.isSuccess, "Valid meta keys should not cause exceptions") + mockTransport.lastJsonRpcRequest()?.let { request -> + val params = request.params as JsonObject + assertTrue(params.containsKey("_meta"), "Request should contain _meta field") + val metaField = params["_meta"] as JsonObject + + // Verify all meta keys are present + assertEquals(validMeta.size, metaField.size, "All meta keys should be included") + + // Verify specific key-value pairs + assertEquals("value1", metaField["simple-key"]?.jsonPrimitive?.content) + assertEquals("1.0", metaField["api.example.com/version"]?.jsonPrimitive?.content) + assertEquals("enabled", metaField["com.company.app/setting"]?.jsonPrimitive?.content) + assertEquals(3, metaField["retry_count"]?.jsonPrimitive?.int) + assertEquals(true, metaField["user.preference"]?.jsonPrimitive?.boolean) + assertEquals("alphanumeric", metaField["valid123"]?.jsonPrimitive?.content) + assertEquals("multiple-dots", metaField["multi.dot.name"]?.jsonPrimitive?.content) + assertEquals("underscore", metaField["under_score"]?.jsonPrimitive?.content) + assertEquals("hyphen", metaField["hyphen-dash"]?.jsonPrimitive?.content) + assertEquals("complex-valid-prefix", metaField["org.apache.kafka/consumer-config"]?.jsonPrimitive?.content) + } + } + + @Test + fun `should accept edge case valid prefixes and names`() = runTest { + val edgeCaseValidMeta = buildMap { + put("a/", "single-char-prefix-empty-name") // empty name is allowed + put("a1-b2/test", "alphanumeric-hyphen-prefix") + put("long.domain.name.here/config", "long-prefix") + put("x/a", "minimal-valid-key") + put("test123", "alphanumeric-name-only") + } + + val result = runCatching { + client.callTool("test-tool", emptyMap(), edgeCaseValidMeta) + } + + assertTrue(result.isSuccess, "Edge case valid meta keys should be accepted") + } + + @Test + fun `should reject mcp reserved prefix`() = runTest { + val invalidMeta = mapOf("mcp/internal" to "value") + + val exception = assertFailsWith { + client.callTool("test-tool", emptyMap(), invalidMeta) + } + + assertContains( + charSequence = exception.message ?: "", + other = "Invalid _meta key", + ) + } + + @Test + fun `should reject modelcontextprotocol reserved prefix`() = runTest { + val invalidMeta = mapOf("modelcontextprotocol/config" to "value") + + val exception = assertFailsWith { + client.callTool("test-tool", emptyMap(), invalidMeta) + } + + assertContains( + charSequence = exception.message ?: "", + other = "Invalid _meta key", + ) + } + + @Test + fun `should reject nested reserved prefixes`() = runTest { + val invalidKeys = listOf( + "api.mcp.io/setting", + "com.modelcontextprotocol.test/value", + "example.mcp/data", + "subdomain.mcp.com/config", + "app.modelcontextprotocol.dev/setting", + "test.mcp/value", + "service.modelcontextprotocol/data", + ) + + invalidKeys.forEach { key -> + val exception = assertFailsWith( + message = "Should reject nested reserved key: $key", + ) { + client.callTool("test-tool", emptyMap(), mapOf(key to "value")) + } + assertContains( + charSequence = exception.message ?: "", + other = "Invalid _meta key", + ) + } + } + + @Test + fun `should reject case-insensitive reserved prefixes`() = runTest { + val invalidKeys = listOf( + "MCP/internal", + "Mcp/config", + "mCp/setting", + "MODELCONTEXTPROTOCOL/data", + "ModelContextProtocol/value", + "modelContextProtocol/test", + ) + + invalidKeys.forEach { key -> + val exception = assertFailsWith( + message = "Should reject case-insensitive reserved key: $key", + ) { + client.callTool("test-tool", emptyMap(), mapOf(key to "value")) + } + assertContains( + charSequence = exception.message ?: "", + other = "Invalid _meta key", + ) + } + } + + @Test + fun `should reject invalid key formats`() = runTest { + val invalidKeys = listOf( + "", // empty key - not allowed at key level + "/invalid", // starts with slash + "-invalid", // starts with hyphen + ".invalid", // starts with dot + "in valid", // contains space + "api../test", // consecutive dots + "api./test", // label ends with dot + ) + + invalidKeys.forEach { key -> + assertFailsWith( + message = "Should reject invalid key format: '$key'", + ) { + client.callTool("test-tool", emptyMap(), mapOf(key to "value")) + } + } + } + + @Test + fun `should convert various data types to JSON correctly`() = runTest { + val complexMeta = createComplexMetaData() + + val result = runCatching { + client.callTool( + "test-tool", + emptyMap(), + complexMeta, + ) + } + + assertTrue(result.isSuccess, "Complex data type conversion should not throw exceptions") + + mockTransport.lastJsonRpcRequest()?.let { request -> + assertEquals("tools/call", request.method) + val params = request.params as JsonObject + assertTrue(params.containsKey("_meta"), "Request should contain _meta field") + } + } + + @Test + fun `should handle nested map structures correctly`() = runTest { + val nestedMeta = buildNestedConfiguration() + + val result = runCatching { + client.callTool("test-tool", emptyMap(), nestedMeta) + } + + assertTrue(result.isSuccess) + + mockTransport.lastJsonRpcRequest()?.let { request -> + val params = request.params as JsonObject + val metaField = params["_meta"] as JsonObject + assertTrue(metaField.containsKey("config")) + } + } + + @Test + fun `should include empty meta object when meta parameter not provided`() = runTest { + client.callTool("test-tool", mapOf("arg" to "value")) + + mockTransport.lastJsonRpcRequest()?.let { request -> + val params = request.params as JsonObject + val metaField = params["_meta"] as JsonObject + assertTrue(metaField.isEmpty(), "Meta field should be empty when not provided") + } + } + + private fun createComplexMetaData(): Map = buildMap { + put("string", "text") + put("number", 42) + put("boolean", true) + put("null_value", null) + put("list", listOf(1, 2, 3)) + put("map", mapOf("nested" to "value")) + put("enum", "STRING") + put("int_array", intArrayOf(1, 2, 3)) + } + + private fun buildNestedConfiguration(): Map = buildMap { + put( + "config", + buildMap { + put( + "database", + buildMap { + put("host", "localhost") + put("port", 5432) + }, + ) + put("features", listOf("feature1", "feature2")) + }, + ) + } +} + +suspend fun OldSchemaMockTransport.lastJsonRpcRequest(): JSONRPCRequest? = + getSentMessages().lastOrNull() as? JSONRPCRequest diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaMockTransport.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaMockTransport.kt new file mode 100644 index 0000000..da813c7 --- /dev/null +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaMockTransport.kt @@ -0,0 +1,94 @@ +package io.modelcontextprotocol.kotlin.sdk.client + +import io.modelcontextprotocol.kotlin.sdk.CallToolResult +import io.modelcontextprotocol.kotlin.sdk.Implementation +import io.modelcontextprotocol.kotlin.sdk.InitializeResult +import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage +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 kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock + +class OldSchemaMockTransport : Transport { + private val _sentMessages = mutableListOf() + private val _receivedMessages = mutableListOf() + private val mutex = Mutex() + + suspend fun getSentMessages() = mutex.withLock { _sentMessages.toList() } + suspend fun getReceivedMessages() = mutex.withLock { _receivedMessages.toList() } + + private var onMessageBlock: (suspend (JSONRPCMessage) -> Unit)? = null + private var onCloseBlock: (() -> Unit)? = null + private var onErrorBlock: ((Throwable) -> Unit)? = null + + override suspend fun start() = Unit + + override suspend fun send(message: JSONRPCMessage) { + mutex.withLock { + _sentMessages += message + } + + // Auto-respond to initialization and tool calls + when (message) { + is JSONRPCRequest -> { + when (message.method) { + "initialize" -> { + val initResponse = JSONRPCResponse( + id = message.id, + result = InitializeResult( + protocolVersion = "2024-11-05", + capabilities = ServerCapabilities( + tools = ServerCapabilities.Tools(listChanged = null), + ), + serverInfo = Implementation("mock-server", "1.0.0"), + ), + ) + onMessageBlock?.invoke(initResponse) + } + + "tools/call" -> { + val toolResponse = JSONRPCResponse( + id = message.id, + result = CallToolResult( + content = listOf(), + isError = false, + ), + ) + onMessageBlock?.invoke(toolResponse) + } + } + } + + else -> { + // Handle other message types if needed + } + } + } + + override suspend fun close() { + onCloseBlock?.invoke() + } + + override fun onMessage(block: suspend (JSONRPCMessage) -> Unit) { + onMessageBlock = { message -> + mutex.withLock { + _receivedMessages += message + } + block(message) + } + } + + override fun onClose(block: () -> Unit) { + onCloseBlock = block + } + + override fun onError(block: (Throwable) -> Unit) { + onErrorBlock = block + } + + fun setupInitializationResponse() { + // This method helps set up the mock for proper initialization + } +} diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaStreamableHttpClientTransportTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaStreamableHttpClientTransportTest.kt new file mode 100644 index 0000000..c5be21e --- /dev/null +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaStreamableHttpClientTransportTest.kt @@ -0,0 +1,422 @@ +package io.modelcontextprotocol.kotlin.sdk.client + +import io.ktor.client.HttpClient +import io.ktor.client.engine.mock.MockEngine +import io.ktor.client.engine.mock.MockRequestHandler +import io.ktor.client.engine.mock.respond +import io.ktor.client.plugins.sse.SSE +import io.ktor.http.ContentType +import io.ktor.http.HttpHeaders +import io.ktor.http.HttpMethod +import io.ktor.http.HttpStatusCode +import io.ktor.http.content.TextContent +import io.ktor.http.headersOf +import io.ktor.utils.io.ByteReadChannel +import io.modelcontextprotocol.kotlin.sdk.Implementation +import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.JSONRPCNotification +import io.modelcontextprotocol.kotlin.sdk.JSONRPCRequest +import io.modelcontextprotocol.kotlin.sdk.RequestId +import io.modelcontextprotocol.kotlin.sdk.shared.McpJson +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.withContext +import kotlinx.coroutines.withTimeout +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.buildJsonObject +import kotlin.test.Ignore +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertNull +import kotlin.test.assertTrue +import kotlin.time.Duration.Companion.seconds + +class OldSchemaStreamableHttpClientTransportTest { + + private fun createTransport(handler: MockRequestHandler): StreamableHttpClientTransport { + val mockEngine = MockEngine(handler) + val httpClient = HttpClient(mockEngine) { + install(SSE) { + reconnectionTime = 1.seconds + } + } + + return StreamableHttpClientTransport(httpClient, url = "http://localhost:8080/mcp") + } + + @Test + fun testSendJsonRpcMessage() = runTest { + val message = JSONRPCRequest( + id = RequestId.StringId("test-id"), + method = "test", + params = buildJsonObject { }, + ) + + val transport = createTransport { request -> + assertEquals(HttpMethod.Post, request.method) + assertEquals("http://localhost:8080/mcp", request.url.toString()) + assertEquals(ContentType.Application.Json, request.body.contentType) + + val body = (request.body as TextContent).text + val decodedMessage = McpJson.decodeFromString(body) + assertEquals(message, decodedMessage) + + respond( + content = "", + status = HttpStatusCode.Accepted, + ) + } + + transport.start() + transport.send(message) + transport.close() + } + + @Test + fun testStoreSessionId() = runTest { + val initMessage = JSONRPCRequest( + id = RequestId.StringId("test-id"), + method = "initialize", + params = buildJsonObject { + put( + "clientInfo", + buildJsonObject { + put("name", JsonPrimitive("test-client")) + put("version", JsonPrimitive("1.0")) + }, + ) + put("protocolVersion", JsonPrimitive("2025-06-18")) + }, + ) + + val transport = createTransport { request -> + when (val msg = McpJson.decodeFromString((request.body as TextContent).text)) { + is JSONRPCRequest if msg.method == "initialize" -> respond( + content = "", + status = HttpStatusCode.OK, + headers = headersOf("mcp-session-id", "test-session-id"), + ) + + is JSONRPCNotification if msg.method == "test" -> { + assertEquals("test-session-id", request.headers["mcp-session-id"]) + respond( + content = "", + status = HttpStatusCode.Accepted, + ) + } + + else -> error("Unexpected message: $msg") + } + } + + transport.start() + transport.send(initMessage) + + assertEquals("test-session-id", transport.sessionId) + + transport.send(JSONRPCNotification(method = "test")) + + transport.close() + } + + @Test + fun testTerminateSession() = runTest { +// transport.sessionId = "test-session-id" + + val transport = createTransport { request -> + assertEquals(HttpMethod.Delete, request.method) + assertEquals("test-session-id", request.headers["mcp-session-id"]) + respond( + content = "", + status = HttpStatusCode.OK, + ) + } + + transport.start() + transport.terminateSession() + + assertNull(transport.sessionId) + transport.close() + } + + @Test + fun testTerminateSessionHandle405() = runTest { +// transport.sessionId = "test-session-id" + + val transport = createTransport { request -> + assertEquals(HttpMethod.Delete, request.method) + respond( + content = "", + status = HttpStatusCode.MethodNotAllowed, + ) + } + + transport.start() + // Should not throw for 405 + transport.terminateSession() + + // Session ID should still be cleared + assertNull(transport.sessionId) + transport.close() + } + + @Test + fun testProtocolVersionHeader() = runTest { + val transport = createTransport { request -> + assertEquals("2025-06-18", request.headers["mcp-protocol-version"]) + respond( + content = "", + status = HttpStatusCode.Accepted, + ) + } + transport.protocolVersion = "2025-06-18" + + transport.start() + transport.send(JSONRPCNotification(method = "test")) + transport.close() + } + + // Engine doesn't support SSECapability: https://youtrack.jetbrains.com/issue/KTOR-8177/MockEngine-Add-SSE-support + @Ignore + @Test + fun testNotificationSchemaE2E() = runTest { + val receivedMessages = mutableListOf() + var sseStarted = false + + val transport = createTransport { request -> + when (request.method) { + HttpMethod.Post if request.body.toString().contains("notifications/initialized") -> { + respond( + content = "", + status = HttpStatusCode.Accepted, + headers = headersOf("mcp-session-id", "notification-test-session"), + ) + } + + // Handle SSE connection + HttpMethod.Get -> { + sseStarted = true + val sseContent = buildString { + // Server sends various notifications + appendLine("event: message") + appendLine("id: 1") + appendLine( + """data: {"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"upload-123","progress":50,"total":100}}""", + ) + appendLine() + + appendLine("event: message") + appendLine("id: 2") + appendLine("""data: {"jsonrpc":"2.0","method":"notifications/resources/list_changed"}""") + appendLine() + + appendLine("event: message") + appendLine("id: 3") + appendLine("""data: {"jsonrpc":"2.0","method":"notifications/tools/list_changed"}""") + appendLine() + } + respond( + content = ByteReadChannel(sseContent), + status = HttpStatusCode.OK, + headers = headersOf( + HttpHeaders.ContentType, + ContentType.Text.EventStream.toString(), + ), + ) + } + + // Handle regular notifications + HttpMethod.Post -> { + respond( + content = "", + status = HttpStatusCode.Accepted, + ) + } + + else -> respond("", HttpStatusCode.OK) + } + } + + transport.onMessage { message -> + receivedMessages.add(message) + } + + transport.start() + + // Test 1: Send initialized notification to trigger SSE + val initializedNotification = JSONRPCNotification( + method = "notifications/initialized", + params = buildJsonObject { + put("protocolVersion", JsonPrimitive("1.0")) + put( + "capabilities", + buildJsonObject { + put("tools", JsonPrimitive(true)) + put("resources", JsonPrimitive(true)) + }, + ) + }, + ) + + transport.send(initializedNotification) + + // Verify SSE was triggered + assertTrue(sseStarted, "SSE should start after initialized notification") + + // Test 2: Verify received notifications + assertEquals(3, receivedMessages.size) + assertTrue(receivedMessages.all { it is JSONRPCNotification }) + + val notifications = receivedMessages.filterIsInstance() + + // Verify progress notification + val progressNotif = notifications[0] + assertEquals("notifications/progress", progressNotif.method) + val progressParams = progressNotif.params as JsonObject + assertEquals("upload-123", (progressParams["progressToken"] as JsonPrimitive).content) + assertEquals(50, (progressParams["progress"] as JsonPrimitive).content.toInt()) + + // Verify list changed notifications + assertEquals("notifications/resources/list_changed", notifications[1].method) + assertEquals("notifications/tools/list_changed", notifications[2].method) + + // Test 3: Send various client notifications + val clientNotifications = listOf( + JSONRPCNotification( + method = "notifications/progress", + params = buildJsonObject { + put("progressToken", JsonPrimitive("download-456")) + put("progress", JsonPrimitive(75)) + }, + ), + JSONRPCNotification( + method = "notifications/cancelled", + params = buildJsonObject { + put("requestId", JsonPrimitive("req-789")) + put("reason", JsonPrimitive("user_cancelled")) + }, + ), + JSONRPCNotification( + method = "notifications/message", + params = buildJsonObject { + put("level", JsonPrimitive("info")) + put("message", JsonPrimitive("Operation completed")) + put( + "data", + buildJsonObject { + put("duration", JsonPrimitive(1234)) + }, + ) + }, + ), + ) + + // Send all client notifications + clientNotifications.forEach { notification -> + transport.send(notification) + } + + // Verify session ID is maintained + assertEquals("notification-test-session", transport.sessionId) + transport.close() + } + + // Engine doesn't support SSECapability: https://youtrack.jetbrains.com/issue/KTOR-8177/MockEngine-Add-SSE-support + @Ignore + @Test + fun testNotificationWithResumptionToken() = runTest { + var resumptionTokenReceived: String? = null + var lastEventIdSent: String? = null + + val transport = createTransport { request -> + // Capture Last-Event-ID header + lastEventIdSent = request.headers["Last-Event-ID"] + + when (request.method) { + HttpMethod.Get -> { + val sseContent = buildString { + appendLine("event: message") + appendLine("id: resume-100") + appendLine( + """data: {"jsonrpc":"2.0","method":"notifications/resumed","params":{"fromToken":"$lastEventIdSent"}}""", + ) + appendLine() + } + respond( + content = ByteReadChannel(sseContent), + status = HttpStatusCode.OK, + headers = headersOf( + HttpHeaders.ContentType, + ContentType.Text.EventStream.toString(), + ), + ) + } + + else -> respond("", HttpStatusCode.Accepted) + } + } + + transport.start() + + // Send notification with resumption token + transport.send( + message = JSONRPCNotification( + method = "notifications/test", + params = buildJsonObject { + put("data", JsonPrimitive("test-data")) + }, + ), + resumptionToken = "previous-token-99", + onResumptionToken = { token -> + resumptionTokenReceived = token + }, + ) + + // Wait for response + delay(1.seconds) + + // Verify resumption token was sent in header + assertEquals("previous-token-99", lastEventIdSent) + + // Verify new resumption token was received + assertEquals("resume-100", resumptionTokenReceived) + transport.close() + } + + @Test + fun testClientConnectWithInvalidJson() = runTest { + // Transport under test: respond with invalid JSON for the initialize request + val transport = createTransport { _ -> + respond( + "this is not valid json", + status = HttpStatusCode.OK, + headers = headersOf(HttpHeaders.ContentType, ContentType.Application.Json.toString()), + ) + } + + val client = Client( + clientInfo = Implementation( + name = "test-client", + version = "1.0", + ), + ) + + try { + // Real time-keeping is needed; otherwise Protocol will always throw TimeoutCancellationException in tests + assertFailsWith( + message = "Expected client.connect to fail on invalid JSON response", + ) { + withContext(Dispatchers.Default.limitedParallelism(1)) { + withTimeout(5.seconds) { + client.connect(transport) + } + } + } + } finally { + transport.close() + } + } +} diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransportTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransportTest.kt index a72bd85..fea1de1 100644 --- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransportTest.kt +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransportTest.kt @@ -12,12 +12,12 @@ import io.ktor.http.HttpStatusCode import io.ktor.http.content.TextContent import io.ktor.http.headersOf import io.ktor.utils.io.ByteReadChannel -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage -import io.modelcontextprotocol.kotlin.sdk.JSONRPCNotification -import io.modelcontextprotocol.kotlin.sdk.JSONRPCRequest -import io.modelcontextprotocol.kotlin.sdk.RequestId -import io.modelcontextprotocol.kotlin.sdk.shared.McpJson +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest +import io.modelcontextprotocol.kotlin.sdk.types.McpJson +import io.modelcontextprotocol.kotlin.sdk.types.RequestId import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.test.runTest diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractStreamableHttpClientTest.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractStreamableHttpClientTest.kt index 77ba553..6f1e30f 100644 --- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractStreamableHttpClientTest.kt +++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractStreamableHttpClientTest.kt @@ -12,7 +12,7 @@ import org.junit.jupiter.api.TestInstance internal abstract class AbstractStreamableHttpClientTest { // start mokksy on random port - protected val mockMcp: MockMcp = MockMcp(verbose = true) + protected val mockMcp: OldSchemaMockMcp = OldSchemaMockMcp(verbose = true) @AfterEach fun afterEach() { diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt new file mode 100644 index 0000000..af2110a --- /dev/null +++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt @@ -0,0 +1,229 @@ +package io.modelcontextprotocol.kotlin.sdk.client + +import dev.mokksy.mokksy.BuildingStep +import dev.mokksy.mokksy.Mokksy +import dev.mokksy.mokksy.StubConfiguration +import io.ktor.http.ContentType +import io.ktor.http.HttpMethod +import io.ktor.http.HttpStatusCode +import io.ktor.sse.ServerSentEvent +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest +import io.modelcontextprotocol.kotlin.sdk.types.RequestId +import kotlinx.coroutines.flow.Flow +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.contentOrNull +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.putJsonObject + +const val MCP_SESSION_ID_HEADER = "Mcp-Session-Id" + +internal class MockMcp(verbose: Boolean = false) { + + private val mokksy: Mokksy = Mokksy(verbose = verbose) + + fun checkForUnmatchedRequests() { + mokksy.checkForUnmatchedRequests() + } + + val url = "${mokksy.baseUrl()}/mcp" + + @Suppress("LongParameterList") + fun onInitialize( + clientName: String? = null, + sessionId: String, + protocolVersion: String = "2025-03-26", + serverName: String = "Mock MCP Server", + serverVersion: String = "1.0.0", + capabilities: JsonObject = buildJsonObject { + putJsonObject("tools") { + put("listChanged", JsonPrimitive(false)) + } + }, + ) { + val predicates = if (clientName != null) { + arrayOf<(JSONRPCRequest?) -> Boolean>({ + it?.params?.jsonObject + ?.get("clientInfo")?.jsonObject + ?.get("name")?.jsonPrimitive + ?.contentOrNull == clientName + }) + } else { + emptyArray() + } + + handleWithResult( + jsonRpcMethod = "initialize", + sessionId = sessionId, + bodyPredicates = predicates, + // language=json + result = """ + { + "capabilities": $capabilities, + "protocolVersion": "$protocolVersion", + "serverInfo": { + "name": "$serverName", + "version": "$serverVersion" + }, + "_meta": { + "foo": "bar" + } + } + """.trimIndent(), + ) + } + + fun onJSONRPCRequest( + httpMethod: HttpMethod = HttpMethod.Post, + jsonRpcMethod: String, + expectedSessionId: String? = null, + vararg bodyPredicates: (JSONRPCRequest) -> Boolean, + ): BuildingStep = mokksy.method( + configuration = StubConfiguration(removeAfterMatch = true), + httpMethod = httpMethod, + requestType = JSONRPCRequest::class, + ) { + path("/mcp") + expectedSessionId?.let { + containsHeader(MCP_SESSION_ID_HEADER, it) + } + bodyMatchesPredicate( + description = "JSON-RPC version is '2.0'", + predicate = + { + it!!.jsonrpc == "2.0" + }, + ) + bodyMatchesPredicate( + description = "JSON-RPC Method should be '$jsonRpcMethod'", + predicate = + { + it!!.method == jsonRpcMethod + }, + ) + bodyPredicates.forEach { predicate -> + bodyMatchesPredicate(predicate = { predicate.invoke(it!!) }) + } + } + + @Suppress("LongParameterList") + fun handleWithResult( + httpMethod: HttpMethod = HttpMethod.Post, + jsonRpcMethod: String, + expectedSessionId: String? = null, + sessionId: String, + contentType: ContentType = ContentType.Application.Json, + statusCode: HttpStatusCode = HttpStatusCode.OK, + vararg bodyPredicates: (JSONRPCRequest) -> Boolean, + result: () -> JsonObject, + ) { + onJSONRPCRequest( + httpMethod = httpMethod, + jsonRpcMethod = jsonRpcMethod, + expectedSessionId = expectedSessionId, + bodyPredicates = bodyPredicates, + ) respondsWith { + val requestId = when (request.body.id) { + is RequestId.NumberId -> (request.body.id as RequestId.NumberId).value.toString() + is RequestId.StringId -> "\"${(request.body.id as RequestId.StringId).value}\"" + } + val resultObject = result.invoke() + // language=json + body = """ + { + "jsonrpc": "2.0", + "id": $requestId, + "result": $resultObject + } + """.trimIndent() + this.contentType = contentType + headers += MCP_SESSION_ID_HEADER to sessionId + httpStatus = statusCode + } + } + + @Suppress("LongParameterList") + fun handleWithResult( + httpMethod: HttpMethod = HttpMethod.Post, + jsonRpcMethod: String, + expectedSessionId: String? = null, + sessionId: String, + contentType: ContentType = ContentType.Application.Json, + statusCode: HttpStatusCode = HttpStatusCode.OK, + vararg bodyPredicates: (JSONRPCRequest) -> Boolean, + result: String, + ) { + handleWithResult( + httpMethod = httpMethod, + jsonRpcMethod = jsonRpcMethod, + expectedSessionId = expectedSessionId, + sessionId = sessionId, + contentType = contentType, + statusCode = statusCode, + bodyPredicates = bodyPredicates, + result = { + Json.parseToJsonElement(result).jsonObject + }, + ) + } + + @Suppress("LongParameterList") + fun handleJSONRPCRequest( + httpMethod: HttpMethod = HttpMethod.Post, + jsonRpcMethod: String, + expectedSessionId: String? = null, + sessionId: String, + contentType: ContentType = ContentType.Application.Json, + statusCode: HttpStatusCode = HttpStatusCode.OK, + vararg bodyPredicates: (JSONRPCRequest?) -> Boolean, + bodyBuilder: () -> String = { "" }, + ) { + onJSONRPCRequest( + httpMethod = httpMethod, + jsonRpcMethod = jsonRpcMethod, + expectedSessionId = expectedSessionId, + bodyPredicates = bodyPredicates, + ) respondsWith { + body = bodyBuilder.invoke() + this.contentType = contentType + headers += MCP_SESSION_ID_HEADER to sessionId + httpStatus = statusCode + } + } + + fun onSubscribe(httpMethod: HttpMethod = HttpMethod.Post, sessionId: String): BuildingStep = mokksy.method( + httpMethod = httpMethod, + name = "MCP GETs", + requestType = Any::class, + ) { + path("/mcp") + containsHeader(MCP_SESSION_ID_HEADER, sessionId) + containsHeader("Accept", "application/json,text/event-stream") + containsHeader("Cache-Control", "no-store") + } + + fun handleSubscribeWithGet(sessionId: String, block: () -> Flow) { + onSubscribe( + httpMethod = HttpMethod.Get, + sessionId = sessionId, + ) respondsWithSseStream { + headers += MCP_SESSION_ID_HEADER to sessionId + this.flow = block.invoke() + } + } + + fun mockUnsubscribeRequest(sessionId: String) { + mokksy.delete( + configuration = StubConfiguration(removeAfterMatch = true), + requestType = JSONRPCRequest::class, + ) { + path("/mcp") + containsHeader(MCP_SESSION_ID_HEADER, sessionId) + } respondsWith { + body = null + } + } +} diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaMockMcp.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaMockMcp.kt index eb2760b..5d1a144 100644 --- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaMockMcp.kt +++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaMockMcp.kt @@ -19,8 +19,6 @@ import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.putJsonObject -const val MCP_SESSION_ID_HEADER = "Mcp-Session-Id" - /** * High-level helper for simulating an MCP server over Streaming HTTP transport with Server-Sent Events (SSE), * built on top of an HTTP server using the [Mokksy](https://mokksy.dev) library. @@ -30,7 +28,7 @@ const val MCP_SESSION_ID_HEADER = "Mcp-Session-Id" * @param verbose Whether to print detailed logs. Defaults to `false`. * @author Konstantin Pavlov */ -internal class MockMcp(verbose: Boolean = false) { +internal class OldSchemaMockMcp(verbose: Boolean = false) { private val mokksy: Mokksy = Mokksy(verbose = verbose) diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt new file mode 100644 index 0000000..37f5a30 --- /dev/null +++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt @@ -0,0 +1,214 @@ +package io.modelcontextprotocol.kotlin.sdk.client + +import io.kotest.matchers.collections.shouldContain +import io.ktor.http.ContentType +import io.ktor.http.HttpMethod +import io.ktor.http.HttpStatusCode +import io.ktor.sse.ServerSentEvent +import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.Tool +import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import kotlinx.serialization.json.putJsonObject +import org.junit.jupiter.api.TestInstance +import kotlin.test.Test +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds +import kotlin.uuid.ExperimentalUuidApi +import kotlin.uuid.Uuid + +@OptIn(ExperimentalUuidApi::class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@Suppress("LongMethod") +internal class StreamableHttpClientTest : AbstractStreamableHttpClientTest() { + + @Test + fun `test streamableHttpClient`() = runBlocking { + val client = Client( + clientInfo = Implementation( + name = "client1", + version = "1.0.0", + ), + options = ClientOptions( + capabilities = ClientCapabilities(), + ), + ) + + val sessionId = Uuid.random().toString() + + mockMcp.onInitialize( + clientName = "client1", + sessionId = sessionId, + ) + + mockMcp.handleJSONRPCRequest( + jsonRpcMethod = "notifications/initialized", + expectedSessionId = sessionId, + sessionId = sessionId, + statusCode = HttpStatusCode.Accepted, + ) + + mockMcp.handleSubscribeWithGet(sessionId) { + flow { + delay(500.milliseconds) + emit( + ServerSentEvent( + event = "message", + id = "1", + data = @Suppress("MaxLineLength") + //language=json + """{"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"upload-123","progress":50,"total":100}}""", + ), + ) + delay(200.milliseconds) + emit( + ServerSentEvent( + data = @Suppress("MaxLineLength") + //language=json + """{"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"upload-123","progress":50,"total":100}}""", + ), + ) + } + } + + // TODO: how to get notifications via Client API? + + mockMcp.handleWithResult( + jsonRpcMethod = "tools/list", + sessionId = sessionId, + // language=json + result = """ + { + "tools": [ + { + "name": "get_weather", + "title": "Weather Information Provider", + "description": "Get current weather information for a location", + "inputSchema": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "City name or zip code" + } + }, + "required": ["location"] + }, + "outputSchema": { + "type": "object", + "properties": { + "temperature": { + "type": "number", + "description": "Temperature, Celsius" + } + }, + "required": ["temperature"] + }, + "_meta": {} + } + ] + } + """.trimIndent(), + ) + + connect(client) + + val listToolsResult = client.listTools() + + listToolsResult.tools shouldContain Tool( + name = "get_weather", + title = "Weather Information Provider", + description = "Get current weather information for a location", + inputSchema = ToolSchema( + properties = buildJsonObject { + putJsonObject("location") { + put("type", "string") + put("description", "City name or zip code") + } + }, + required = listOf("location"), + ), + outputSchema = ToolSchema( + properties = buildJsonObject { + putJsonObject("temperature") { + put("type", "number") + put("description", "Temperature, Celsius") + } + }, + required = listOf("temperature"), + ), + annotations = null, + meta = EmptyJsonObject, + ) + + mockMcp.mockUnsubscribeRequest(sessionId = sessionId) + + client.close() + } + + @Test + fun `handle MethodNotAllowed`() = runBlocking { + checkSupportNonStreamingResponse( + ContentType.Text.EventStream, + HttpStatusCode.MethodNotAllowed, + ) + } + + @Test + fun `handle non-streaming response`() = runBlocking { + checkSupportNonStreamingResponse( + ContentType.Application.Json, + HttpStatusCode.OK, + ) + } + + private suspend fun checkSupportNonStreamingResponse(contentType: ContentType, statusCode: HttpStatusCode) { + val sessionId = "SID_${Uuid.random().toHexString()}" + val clientName = "client-${Uuid.random().toHexString()}" + val client = Client( + clientInfo = Implementation(name = clientName, version = "1.0.0"), + options = ClientOptions( + capabilities = ClientCapabilities(), + ), + ) + + mockMcp.onInitialize(clientName = clientName, sessionId = sessionId) + + mockMcp.handleJSONRPCRequest( + jsonRpcMethod = "notifications/initialized", + expectedSessionId = sessionId, + sessionId = sessionId, + statusCode = HttpStatusCode.Accepted, + ) + + mockMcp.onSubscribe( + httpMethod = HttpMethod.Get, + sessionId = sessionId, + ) respondsWith { + headers += MCP_SESSION_ID_HEADER to sessionId + body = null + httpStatus = statusCode + this.contentType = contentType + } + + mockMcp.handleWithResult(jsonRpcMethod = "ping", sessionId = sessionId) { + buildJsonObject {} + } + + mockMcp.mockUnsubscribeRequest(sessionId = sessionId) + + connect(client) + + delay(1.seconds) + + client.ping() // connection is still alive + + client.close() + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/AudioContentSerializationTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaAudioContentSerializationTest.kt similarity index 94% rename from kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/AudioContentSerializationTest.kt rename to kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaAudioContentSerializationTest.kt index 247388f..738539f 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/AudioContentSerializationTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaAudioContentSerializationTest.kt @@ -5,7 +5,7 @@ import io.modelcontextprotocol.kotlin.sdk.shared.McpJson import kotlin.test.Test import kotlin.test.assertEquals -class AudioContentSerializationTest { +class OldSchemaAudioContentSerializationTest { private val audioContentJson = """ { diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationsTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationsTest.kt new file mode 100644 index 0000000..9b52ef9 --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/models/ProgressNotificationsTest.kt @@ -0,0 +1,72 @@ +package io.modelcontextprotocol.kotlin.sdk.models + +import io.kotest.matchers.shouldBe +import io.modelcontextprotocol.kotlin.sdk.types.McpJson +import io.modelcontextprotocol.kotlin.sdk.types.ProgressNotification +import io.modelcontextprotocol.kotlin.sdk.types.ProgressNotificationParams +import io.modelcontextprotocol.kotlin.sdk.types.RequestId +import kotlin.test.Test + +class ProgressNotificationsTest { + + /** + * https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/progress#progress-flow + */ + @Test + fun `Read ProgressNotifications with string token`() { + //language=json + val json = """ + { + "jsonrpc": "2.0", + "method": "notifications/progress", + "params": { + "progressToken": "abc123", + "progress": 50, + "total": 100, + "message": "Reticulating splines..." + } + } + """.trimIndent() + + val result = McpJson.decodeFromString(json) + + result shouldBe ProgressNotification( + params = ProgressNotificationParams( + progressToken = RequestId.StringId("abc123"), + progress = 50.0, + message = "Reticulating splines...", + total = 100.0, + ), + ) + } + + /** + * https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/progress#progress-flow + */ + @Test + fun `Read ProgressNotifications with integer token`() { + //language=json + val json = """ + { + "jsonrpc": "2.0", + "method": "notifications/progress", + "params": { + "progressToken": 100500, + "progress": 50, + "total": 100, + "message": "Reticulating splines..." + } + } + """.trimIndent() + + val result = McpJson.decodeFromString(json) + result shouldBe ProgressNotification( + params = ProgressNotificationParams( + progressToken = RequestId.NumberId(100500), + progress = 50.0, + message = "Reticulating splines...", + total = 100.0, + ), + ) + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaReadBufferTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaReadBufferTest.kt new file mode 100644 index 0000000..0311c13 --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaReadBufferTest.kt @@ -0,0 +1,62 @@ +package io.modelcontextprotocol.kotlin.sdk.shared + +import io.ktor.utils.io.charsets.Charsets +import io.ktor.utils.io.core.toByteArray +import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.JSONRPCNotification +import kotlinx.serialization.json.Json +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class OldSchemaReadBufferTest { + private val testMessage: JSONRPCMessage = JSONRPCNotification(method = "foobar") + + private val json = Json { + ignoreUnknownKeys = true + encodeDefaults = true + } + + @Test + fun `should have no messages after initialization`() { + val readBuffer = ReadBuffer() + assertNull(readBuffer.readMessage()) + } + + @Test + fun `should only yield a message after a newline`() { + val readBuffer = ReadBuffer() + + // Append message without a newline + val messageBytes = json.encodeToString(testMessage).encodeToByteArray() + readBuffer.append(messageBytes) + assertNull(readBuffer.readMessage()) + + // Append a newline and verify message is now available + readBuffer.append("\n".encodeToByteArray()) + assertEquals(testMessage, readBuffer.readMessage()) + assertNull(readBuffer.readMessage()) + } + + @Test + fun `skip empty line`() { + val readBuffer = ReadBuffer() + readBuffer.append("\n".toByteArray()) + assertNull(readBuffer.readMessage()) + } + + @Test + fun `should be reusable after clearing`() { + val readBuffer = ReadBuffer() + + readBuffer.append("foobar".toByteArray(Charsets.UTF_8)) + readBuffer.clear() + assertNull(readBuffer.readMessage()) + + val messageJson = serializeMessage(testMessage) + readBuffer.append(messageJson.toByteArray(Charsets.UTF_8)) + readBuffer.append("\n".toByteArray(Charsets.UTF_8)) + val message = readBuffer.readMessage() + assertEquals(testMessage, message) + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBufferTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBufferTest.kt index 8e6f4f6..a49ff3d 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBufferTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBufferTest.kt @@ -2,8 +2,8 @@ package io.modelcontextprotocol.kotlin.sdk.shared import io.ktor.utils.io.charsets.Charsets import io.ktor.utils.io.core.toByteArray -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage -import io.modelcontextprotocol.kotlin.sdk.JSONRPCNotification +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification import kotlinx.serialization.json.Json import kotlin.test.Test import kotlin.test.assertEquals diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaStdioServerTransportTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaStdioServerTransportTest.kt new file mode 100644 index 0000000..32e25ba --- /dev/null +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaStdioServerTransportTest.kt @@ -0,0 +1,140 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.modelcontextprotocol.kotlin.sdk.InitializedNotification +import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.PingRequest +import io.modelcontextprotocol.kotlin.sdk.shared.ReadBuffer +import io.modelcontextprotocol.kotlin.sdk.shared.serializeMessage +import io.modelcontextprotocol.kotlin.sdk.toJSON +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest +import kotlinx.io.Sink +import kotlinx.io.Source +import kotlinx.io.asSink +import kotlinx.io.asSource +import kotlinx.io.buffered +import java.io.ByteArrayOutputStream +import java.io.PipedInputStream +import java.io.PipedOutputStream +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class OldSchemaStdioServerTransportTest { + private lateinit var input: PipedInputStream + private lateinit var inputWriter: PipedOutputStream + private lateinit var outputBuffer: ReadBuffer + private lateinit var output: ByteArrayOutputStream + + // We'll store the wrapped streams that meet the constructor requirements + private lateinit var bufferedInput: Source + private lateinit var printOutput: Sink + + @BeforeTest + fun setUp() { + // Simulate an input stream that we can push data into using inputWriter. + input = PipedInputStream() + inputWriter = PipedOutputStream(input) + + outputBuffer = ReadBuffer() + + // A custom ByteArrayOutputStream that appends all written data into outputBuffer + output = object : ByteArrayOutputStream() { + override fun write(b: ByteArray, off: Int, len: Int) { + super.write(b, off, len) + outputBuffer.append(b.copyOfRange(off, off + len)) + } + } + + bufferedInput = input.asSource().buffered() + + printOutput = output.asSink().buffered() + } + + @Test + fun `should start then close cleanly`() { + runBlocking { + val server = StdioServerTransport(bufferedInput, printOutput) + server.onError { error -> + throw error + } + + var didClose = false + server.onClose { + didClose = true + } + + server.start() + assertFalse(didClose, "Should not have closed yet") + + server.close() + assertTrue(didClose, "Should have closed after calling close()") + } + } + + @Test + fun `should not read until started`() = runTest { + val server = StdioServerTransport(bufferedInput, printOutput) + server.onError { error -> + throw error + } + + var didRead = false + val readMessage = CompletableDeferred() + + server.onMessage { message -> + didRead = true + readMessage.complete(message) + } + + val message = PingRequest().toJSON() + + // Push a message before the server started + val serialized = serializeMessage(message) + inputWriter.write(serialized) + inputWriter.flush() + + assertFalse(didRead, "Should not have read message before start") + + server.start() + val received = readMessage.await() + assertEquals(message, received) + } + + @Test + fun `should read multiple messages`() = runTest { + val server = StdioServerTransport(bufferedInput, printOutput) + server.onError { error -> + throw error + } + + val messages = listOf( + PingRequest().toJSON(), + InitializedNotification().toJSON(), + ) + + val readMessages = mutableListOf() + val finished = CompletableDeferred() + + server.onMessage { message -> + readMessages.add(message) + if (message == messages[1]) { + finished.complete(Unit) + } + } + + // Push both messages before starting the server + for (m in messages) { + inputWriter.write(serializeMessage(m)) + } + inputWriter.flush() + + server.start() + finished.await() + + assertEquals(messages, readMessages) + } +} diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransportTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransportTest.kt index be1e64e..ff46263 100644 --- a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransportTest.kt +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransportTest.kt @@ -1,11 +1,11 @@ package io.modelcontextprotocol.kotlin.sdk.server -import io.modelcontextprotocol.kotlin.sdk.InitializedNotification -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage -import io.modelcontextprotocol.kotlin.sdk.PingRequest import io.modelcontextprotocol.kotlin.sdk.shared.ReadBuffer import io.modelcontextprotocol.kotlin.sdk.shared.serializeMessage -import io.modelcontextprotocol.kotlin.sdk.toJSON +import io.modelcontextprotocol.kotlin.sdk.types.InitializedNotification +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.PingRequest +import io.modelcontextprotocol.kotlin.sdk.types.toJSON import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt new file mode 100644 index 0000000..d863e00 --- /dev/null +++ b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt @@ -0,0 +1,1039 @@ +package io.modelcontextprotocol.kotlin.sdk.client + +import io.modelcontextprotocol.kotlin.sdk.server.Server +import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions +import io.modelcontextprotocol.kotlin.sdk.server.ServerSession +import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport +import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport +import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities +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.EmptyJsonObject +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest +import io.modelcontextprotocol.kotlin.sdk.types.InitializeResult +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse +import io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION +import io.modelcontextprotocol.kotlin.sdk.types.ListResourcesRequest +import io.modelcontextprotocol.kotlin.sdk.types.ListResourcesResult +import io.modelcontextprotocol.kotlin.sdk.types.ListRootsRequest +import io.modelcontextprotocol.kotlin.sdk.types.ListToolsRequest +import io.modelcontextprotocol.kotlin.sdk.types.ListToolsResult +import io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel +import io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotification +import io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotificationParams +import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.Role +import io.modelcontextprotocol.kotlin.sdk.types.Root +import io.modelcontextprotocol.kotlin.sdk.types.RootsListChangedNotification +import io.modelcontextprotocol.kotlin.sdk.types.SUPPORTED_PROTOCOL_VERSIONS +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.sdk.types.Tool +import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.TimeoutCancellationException +import kotlinx.coroutines.cancel +import kotlinx.coroutines.delay +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.withTimeout +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import kotlinx.serialization.json.putJsonObject +import kotlin.coroutines.cancellation.CancellationException +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertIs +import kotlin.test.assertNull +import kotlin.test.assertTrue +import kotlin.test.fail + +class ClientTest { + @Test + fun `should initialize with matching protocol version`() = runTest { + var initialised = false + val clientTransport = object : AbstractTransport() { + override suspend fun start() {} + + override suspend fun send(message: JSONRPCMessage) { + if (message !is JSONRPCRequest) return + initialised = true + val result = InitializeResult( + protocolVersion = LATEST_PROTOCOL_VERSION, + capabilities = ServerCapabilities(), + serverInfo = Implementation( + name = "test", + version = "1.0", + ), + ) + + val response = JSONRPCResponse( + id = message.id, + result = result, + ) + + _onMessage.invoke(response) + } + + override suspend fun close() { + } + } + + val client = Client( + clientInfo = Implementation( + name = "test client", + version = "1.0", + ), + options = ClientOptions( + capabilities = ClientCapabilities( + sampling = EmptyJsonObject, + ), + ), + ) + + client.connect(clientTransport) + assertTrue(initialised) + } + + @Test + fun `should initialize with supported older protocol version`() = runTest { + val oldVersion = SUPPORTED_PROTOCOL_VERSIONS[1] + val clientTransport = object : AbstractTransport() { + override suspend fun start() {} + + override suspend fun send(message: JSONRPCMessage) { + if (message !is JSONRPCRequest) return + check(message.method == Method.Defined.Initialize.value) + + val result = InitializeResult( + protocolVersion = oldVersion, + capabilities = ServerCapabilities(), + serverInfo = Implementation( + name = "test", + version = "1.0", + ), + ) + + val response = JSONRPCResponse( + id = message.id, + result = result, + ) + _onMessage.invoke(response) + } + + override suspend fun close() { + } + } + + val client = Client( + clientInfo = Implementation( + name = "test client", + version = "1.0", + ), + options = ClientOptions( + capabilities = ClientCapabilities( + sampling = EmptyJsonObject, + ), + ), + ) + + client.connect(clientTransport) + assertEquals( + Implementation("test", "1.0"), + client.serverVersion, + ) + } + + @Test + fun `should reject unsupported protocol version`() = runTest { + var closed = false + val clientTransport = object : AbstractTransport() { + override suspend fun start() {} + + override suspend fun send(message: JSONRPCMessage) { + if (message !is JSONRPCRequest) return + check(message.method == Method.Defined.Initialize.value) + + val result = InitializeResult( + protocolVersion = "invalid-version", + capabilities = ServerCapabilities(), + serverInfo = Implementation( + name = "test", + version = "1.0", + ), + ) + + val response = JSONRPCResponse( + id = message.id, + result = result, + ) + + _onMessage.invoke(response) + } + + override suspend fun close() { + closed = true + } + } + + val client = Client( + clientInfo = Implementation( + name = "test client", + version = "1.0", + ), + options = ClientOptions(), + ) + + assertFailsWith("Server's protocol version is not supported: invalid-version") { + client.connect(clientTransport) + } + + assertTrue(closed) + } + + @Test + fun `should reject due to non cancellation exception`() = runTest { + var closed = false + val failingTransport = object : AbstractTransport() { + override suspend fun start() {} + + override suspend fun send(message: JSONRPCMessage) { + if (message !is JSONRPCRequest) return + check(message.method == Method.Defined.Initialize.value) + throw IllegalStateException("Test error") + } + + override suspend fun close() { + closed = true + } + } + + val client = Client( + clientInfo = Implementation( + name = "test client", + version = "1.0", + ), + options = ClientOptions(), + ) + + val exception = assertFailsWith { + client.connect(failingTransport) + } + + assertEquals("Error connecting to transport: Test error", exception.message) + + assertTrue(closed) + } + + @Test + fun `should respect server capabilities`() = runTest { + val serverOptions = ServerOptions( + capabilities = ServerCapabilities( + resources = ServerCapabilities.Resources(null, null), + tools = ServerCapabilities.Tools(null), + ), + ) + val server = Server( + Implementation(name = "test server", version = "1.0"), + serverOptions, + ) + + val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() + + val client = Client( + clientInfo = Implementation(name = "test client", version = "1.0"), + options = ClientOptions( + capabilities = ClientCapabilities(sampling = EmptyJsonObject), + ), + ) + + val serverSessionResult = CompletableDeferred() + + listOf( + launch { + client.connect(clientTransport) + }, + launch { + serverSessionResult.complete(server.createSession(serverTransport)) + }, + ).joinAll() + + val serverSession = serverSessionResult.await() + serverSession.setRequestHandler(Method.Defined.Initialize) { _, _ -> + InitializeResult( + protocolVersion = LATEST_PROTOCOL_VERSION, + capabilities = ServerCapabilities( + resources = ServerCapabilities.Resources(null, null), + tools = ServerCapabilities.Tools(null), + ), + serverInfo = Implementation(name = "test", version = "1.0"), + ) + } + + serverSession.setRequestHandler(Method.Defined.ResourcesList) { _, _ -> + ListResourcesResult(resources = emptyList(), nextCursor = null) + } + + serverSession.setRequestHandler(Method.Defined.ToolsList) { _, _ -> + ListToolsResult(tools = emptyList(), nextCursor = null) + } + // Server supports resources and tools, but not prompts + val caps = client.serverCapabilities + assertEquals(ServerCapabilities.Resources(null, null), caps?.resources) + assertEquals(ServerCapabilities.Tools(null), caps?.tools) + assertTrue(caps?.prompts == null) // or check that prompts are absent + + // These should not throw + client.listResources() + client.listTools() + + // This should fail because prompts are not supported + val ex = assertFailsWith { + client.listPrompts() + } + assertTrue(ex.message?.contains("Server does not support prompts") == true) + } + + @Test + fun `should respect client notification capabilities`() = runTest { + val server = Server( + Implementation(name = "test server", version = "1.0"), + ServerOptions(capabilities = ServerCapabilities()), + ) + + val client = Client( + clientInfo = Implementation(name = "test client", version = "1.0"), + options = ClientOptions( + capabilities = ClientCapabilities( + roots = ClientCapabilities.Roots(listChanged = true), + ), + ), + ) + + val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() + + listOf( + launch { + client.connect(clientTransport) + println("Client connected") + }, + launch { + server.createSession(serverTransport) + println("Server connected") + }, + ).joinAll() + + // This should not throw because the client supports roots.listChanged + client.sendRootsListChanged() + + // Create a new client without the roots.listChanged capability + val clientWithoutCapability = Client( + clientInfo = Implementation(name = "test client without capability", version = "1.0"), + options = ClientOptions( + capabilities = ClientCapabilities(), + // enforceStrictCapabilities = true // TODO() + ), + ) + + clientWithoutCapability.connect(clientTransport) + // Using the same transport pair might not be realistic - in a real scenario you'd create another pair. + // Adjust if necessary. + + // This should fail + val ex = assertFailsWith { + clientWithoutCapability.sendRootsListChanged() + } + assertTrue(ex.message?.startsWith("Client does not support") == true) + } + + @Test + fun `should respect server notification capabilities`() = runTest { + val server = Server( + Implementation(name = "test server", version = "1.0"), + ServerOptions( + capabilities = ServerCapabilities( + logging = EmptyJsonObject, + resources = ServerCapabilities.Resources(listChanged = true, subscribe = null), + ), + ), + ) + + val client = Client( + clientInfo = Implementation(name = "test client", version = "1.0"), + options = ClientOptions( + capabilities = ClientCapabilities(), + ), + ) + + val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() + + val serverSessionResult = CompletableDeferred() + + listOf( + launch { + client.connect(clientTransport) + println("Client connected") + }, + launch { + serverSessionResult.complete(server.createSession(serverTransport)) + println("Server connected") + }, + ).joinAll() + + val serverSession = serverSessionResult.await() + // These should not throw + val jsonObject = buildJsonObject { + put("name", "John") + put("age", 30) + put("isStudent", false) + } + serverSession.sendLoggingMessage( + LoggingMessageNotification( + params = LoggingMessageNotificationParams( + level = LoggingLevel.Info, + data = jsonObject, + ), + ), + ) + serverSession.sendResourceListChanged() + + // This should fail because the server doesn't have the tools capability + val ex = assertFailsWith { + serverSession.sendToolListChanged() + } + assertTrue(ex.message?.contains("Server does not support notifying of tool list changes") == true) + } + + @Test + fun `should handle client cancelling a request`() = runTest { + val server = Server( + Implementation(name = "test server", version = "1.0"), + ServerOptions( + capabilities = ServerCapabilities( + resources = ServerCapabilities.Resources( + listChanged = null, + subscribe = null, + ), + ), + ), + ) + + val def = CompletableDeferred() + val defTimeOut = CompletableDeferred() + val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() + + val client = Client( + clientInfo = Implementation(name = "test client", version = "1.0"), + options = ClientOptions(capabilities = ClientCapabilities()), + ) + + val serverSessionResult = CompletableDeferred() + + listOf( + launch { + client.connect(clientTransport) + println("Client connected") + }, + launch { + serverSessionResult.complete(server.createSession(serverTransport)) + println("Server connected") + }, + ).joinAll() + + val serverSession = serverSessionResult.await() + + serverSession.setRequestHandler(Method.Defined.ResourcesList) { _, _ -> + // Simulate delay + def.complete(Unit) + try { + delay(1000) + } catch (e: CancellationException) { + defTimeOut.complete(Unit) + throw e + } + ListResourcesResult(resources = emptyList()) + fail("Shouldn't have been called") + } + + val defCancel = CompletableDeferred() + val job = launch { + try { + client.listResources() + } catch (e: CancellationException) { + defCancel.complete(Unit) + assertEquals("Cancelled by test", e.message) + } + } + def.await() + runCatching { job.cancel("Cancelled by test") } + defCancel.await() + defTimeOut.await() + } + + @Test + fun `should handle request timeout`() = runTest { + val server = Server( + Implementation(name = "test server", version = "1.0"), + ServerOptions( + capabilities = ServerCapabilities( + resources = ServerCapabilities.Resources( + listChanged = null, + subscribe = null, + ), + ), + ), + ) + + val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() + val client = Client( + clientInfo = Implementation(name = "test client", version = "1.0"), + options = ClientOptions(capabilities = ClientCapabilities()), + ) + + val serverSessionResult = CompletableDeferred() + + listOf( + launch { + client.connect(clientTransport) + println("Client connected") + }, + launch { + serverSessionResult.complete(server.createSession(serverTransport)) + println("Server connected") + }, + ).joinAll() + + val serverSession = serverSessionResult.await() + serverSession.setRequestHandler(Method.Defined.ResourcesList) { _, _ -> + // Simulate a delayed response + // Wait ~100ms unless canceled + try { + withTimeout(100L) { + // Just delay here, if timeout is 0 on the client side, this won't return in time + delay(100) + } + } catch (_: Exception) { + // If aborted, just rethrow or return early + } + ListResourcesResult(resources = emptyList()) + } + + // Request with 1 msec timeout should fail immediately + val ex = assertFailsWith { + withTimeout(1) { + client.listResources() + } + } + assertTrue(ex is TimeoutCancellationException) + } + + @Test + fun `should only allow setRequestHandler for declared capabilities`() = runTest { + val client = Client( + clientInfo = Implementation( + name = "test client", + version = "1.0", + ), + options = ClientOptions( + capabilities = ClientCapabilities( + sampling = EmptyJsonObject, + ), + ), + ) + + client.setRequestHandler(Method.Defined.SamplingCreateMessage) { _, _ -> + CreateMessageResult( + model = "test-model", + role = Role.Assistant, + content = TextContent( + text = "Test response", + ), + ) + } + + assertFailsWith("Client does not support roots capability (required for RootsList)") { + client.setRequestHandler(Method.Defined.RootsList) { _, _ -> null } + } + } + + @Test + fun `JSONRPCRequest with ToolsList method and default params returns list of tools`() = runTest { + val serverOptions = ServerOptions( + capabilities = ServerCapabilities( + tools = ServerCapabilities.Tools(null), + ), + ) + val server = Server( + Implementation(name = "test server", version = "1.0"), + serverOptions, + ) + + val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() + + val client = Client( + clientInfo = Implementation(name = "test client", version = "1.0"), + options = ClientOptions( + capabilities = ClientCapabilities(sampling = EmptyJsonObject), + ), + ) + + var receivedMessage: JSONRPCMessage? = null + clientTransport.onMessage { msg -> + receivedMessage = msg + } + + val serverSessionResult = CompletableDeferred() + + listOf( + launch { + client.connect(clientTransport) + println("Client connected") + }, + launch { + serverSessionResult.complete(server.createSession(serverTransport)) + println("Server connected") + }, + ).joinAll() + + val serverSession = serverSessionResult.await() + + serverSession.setRequestHandler(Method.Defined.Initialize) { _, _ -> + InitializeResult( + protocolVersion = LATEST_PROTOCOL_VERSION, + capabilities = ServerCapabilities( + resources = ServerCapabilities.Resources(null, null), + tools = ServerCapabilities.Tools(null), + ), + serverInfo = Implementation(name = "test", version = "1.0"), + ) + } + + val serverListToolsResult = ListToolsResult( + tools = listOf( + Tool( + name = "testTool", + title = "testTool title", + description = "testTool description", + annotations = null, + inputSchema = ToolSchema(), + outputSchema = null, + ), + ), + nextCursor = null, + ) + + serverSession.setRequestHandler(Method.Defined.ToolsList) { _, _ -> + serverListToolsResult + } + + val serverCapabilities = client.serverCapabilities + assertEquals(ServerCapabilities.Tools(null), serverCapabilities?.tools) + + val request = JSONRPCRequest( + method = Method.Defined.ToolsList.value, + ) + clientTransport.send(request) + + assertIs(receivedMessage) + val receivedAsResponse = receivedMessage as JSONRPCResponse + assertEquals(request.id, receivedAsResponse.id) + assertEquals(request.jsonrpc, receivedAsResponse.jsonrpc) + assertEquals(serverListToolsResult, receivedAsResponse.result) + } + + @Test + fun `listRoots returns list of roots`() = runTest { + val client = Client( + Implementation(name = "test client", version = "1.0"), + ClientOptions( + capabilities = ClientCapabilities( + roots = ClientCapabilities.Roots(null), + ), + ), + ) + + val clientRoots = listOf( + Root(uri = "file:///test-root", name = "testRoot"), + ) + + client.addRoots(clientRoots) + + val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() + + val server = Server( + serverInfo = Implementation(name = "test server", version = "1.0"), + options = ServerOptions( + capabilities = ServerCapabilities(), + ), + ) + + val serverSessionResult = CompletableDeferred() + + listOf( + launch { + client.connect(clientTransport) + println("Client connected") + }, + launch { + serverSessionResult.complete(server.createSession(serverTransport)) + println("Server connected") + }, + ).joinAll() + + val serverSession = serverSessionResult.await() + + val clientCapabilities = serverSession.clientCapabilities + assertEquals(ClientCapabilities.Roots(null), clientCapabilities?.roots) + + val listRootsResult = serverSession.listRoots() + + assertEquals(listRootsResult.roots, clientRoots) + } + + @Test + fun `addRoot should throw when roots capability is not supported`() = runTest { + val client = Client( + Implementation(name = "test client", version = "1.0"), + ClientOptions( + capabilities = ClientCapabilities(), + ), + ) + + // Verify that adding a root throws an exception + val exception = assertFailsWith { + client.addRoot(uri = "file:///test-root1", name = "testRoot1") + } + assertEquals("Client does not support roots capability.", exception.message) + } + + @Test + fun `removeRoot should throw when roots capability is not supported`() = runTest { + val client = Client( + Implementation(name = "test client", version = "1.0"), + ClientOptions( + capabilities = ClientCapabilities(), + ), + ) + + // Verify that removing a root throws an exception + val exception = assertFailsWith { + client.removeRoot(uri = "file:///test-root1") + } + assertEquals("Client does not support roots capability.", exception.message) + } + + @Test + fun `removeRoot should remove a root`() = runTest { + val client = Client( + Implementation(name = "test client", version = "1.0"), + ClientOptions( + capabilities = ClientCapabilities( + roots = ClientCapabilities.Roots(null), + ), + ), + ) + + // Add some roots + client.addRoots( + listOf( + Root(uri = "file:///test-root1", name = "testRoot1"), + Root(uri = "file:///test-root2", name = "testRoot2"), + ), + ) + + // Remove a root + val result = client.removeRoot("file:///test-root1") + + // Verify the root was removed + assertTrue(result, "Root should be removed successfully") + } + + @Test + fun `removeRoots should remove multiple roots`() = runTest { + val client = Client( + Implementation(name = "test client", version = "1.0"), + ClientOptions( + capabilities = ClientCapabilities( + roots = ClientCapabilities.Roots(null), + ), + ), + ) + + // Add some roots + client.addRoots( + listOf( + Root(uri = "file:///test-root1", name = "testRoot1"), + Root(uri = "file:///test-root2", name = "testRoot2"), + ), + ) + + // Remove multiple roots + val result = client.removeRoots( + listOf("file:///test-root1", "file:///test-root2"), + ) + + // Verify the root was removed + assertEquals(2, result, "Both roots should be removed") + } + + @Test + fun `sendRootsListChanged should notify server`() = runTest { + val client = Client( + Implementation(name = "test client", version = "1.0"), + ClientOptions( + capabilities = ClientCapabilities( + roots = ClientCapabilities.Roots(listChanged = true), + ), + ), + ) + + val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() + + val server = Server( + serverInfo = Implementation(name = "test server", version = "1.0"), + options = ServerOptions( + capabilities = ServerCapabilities(), + ), + ) + + // Track notifications + var rootListChangedNotificationReceived = false + + val serverSessionResult = CompletableDeferred() + + listOf( + launch { + client.connect(clientTransport) + println("Client connected") + }, + launch { + serverSessionResult.complete(server.createSession(serverTransport)) + println("Server connected") + }, + ).joinAll() + + val serverSession = serverSessionResult.await() + serverSession.setNotificationHandler( + Method.Defined.NotificationsRootsListChanged, + ) { + rootListChangedNotificationReceived = true + CompletableDeferred(Unit) + } + + client.sendRootsListChanged() + + assertTrue( + rootListChangedNotificationReceived, + "Notification should be sent when sendRootsListChanged is called", + ) + } + + @Test + fun `should reject server elicitation when elicitation capability is not supported`() = runTest { + val client = Client( + Implementation(name = "test client", version = "1.0"), + ClientOptions( + capabilities = ClientCapabilities(), + ), + ) + + val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() + + val server = Server( + serverInfo = Implementation(name = "test server", version = "1.0"), + options = ServerOptions( + capabilities = ServerCapabilities(), + ), + ) + + val serverSessionResult = CompletableDeferred() + + listOf( + launch { + client.connect(clientTransport) + println("Client connected") + }, + launch { + serverSessionResult.complete(server.createSession(serverTransport)) + println("Server connected") + }, + ).joinAll() + + val serverSession = serverSessionResult.await() + + // Verify that creating an elicitation throws an exception + val exception = assertFailsWith { + serverSession.createElicitation( + message = "Please provide your GitHub username", + requestedSchema = ElicitRequestParams.RequestedSchema( + properties = buildJsonObject { + putJsonObject("name") { + put("type", "string") + } + }, + required = listOf("name"), + ), + ) + } + assertEquals( + "Client does not support elicitation (required for elicitation/create)", + exception.message, + ) + } + + @Test + fun `should handle logging setLevel request`() = runTest { + val server = Server( + Implementation(name = "test server", version = "1.0"), + ServerOptions( + capabilities = ServerCapabilities( + logging = EmptyJsonObject, + ), + ), + ) + + val client = Client( + clientInfo = Implementation(name = "test client", version = "1.0"), + options = ClientOptions( + capabilities = ClientCapabilities(), + ), + ) + + val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() + + val receivedMessages = mutableListOf() + client.setNotificationHandler(Method.Defined.NotificationsMessage) { notification -> + receivedMessages.add(notification) + CompletableDeferred(Unit) + } + + val serverSessionResult = CompletableDeferred() + + listOf( + launch { + client.connect(clientTransport) + println("Client connected") + }, + launch { + serverSessionResult.complete(server.connect(serverTransport)) + println("Server connected") + }, + ).joinAll() + + val serverSession = serverSessionResult.await() + + // Set logging level to warning + val minLevel = LoggingLevel.Warning + val result = client.setLoggingLevel(minLevel) + assertNull(result.meta) + + // Send messages of different levels + val testMessages = listOf( + LoggingLevel.Debug to "Debug - should be filtered", + LoggingLevel.Info to "Info - should be filtered", + LoggingLevel.Warning to "Warning - should pass", + LoggingLevel.Error to "Error - should pass", + ) + + testMessages.forEach { (level, message) -> + serverSession.sendLoggingMessage( + LoggingMessageNotification( + params = LoggingMessageNotificationParams( + level = level, + data = buildJsonObject { put("message", message) }, + ), + ), + ) + } + + delay(100) + + // Only warning and error should be received + assertEquals(2, receivedMessages.size, "Should receive only 2 messages (warning and error)") + + // Verify all received messages have severity >= minLevel + receivedMessages.forEach { message -> + val messageSeverity = message.params.level.ordinal + assertTrue( + messageSeverity >= minLevel.ordinal, + "Received message with level ${message.params.level} should have severity >= $minLevel", + ) + } + } + + @Test + fun `should handle server elicitation`() = runTest { + val client = Client( + Implementation(name = "test client", version = "1.0"), + ClientOptions( + capabilities = ClientCapabilities( + elicitation = EmptyJsonObject, + ), + ), + ) + + val elicitationMessage = "Please provide your GitHub username" + val requestedSchema = ElicitRequestParams.RequestedSchema( + properties = buildJsonObject { + putJsonObject("name") { + put("type", "string") + } + }, + required = listOf("name"), + ) + + val elicitationResultAction = ElicitResult.Action.Accept + val elicitationResultContent = buildJsonObject { + put("name", "octocat") + } + + client.setElicitationHandler { request -> + assertEquals(elicitationMessage, request.params.message) + assertEquals(requestedSchema, request.params.requestedSchema) + + ElicitResult( + action = elicitationResultAction, + content = elicitationResultContent, + ) + } + + val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() + + val server = Server( + serverInfo = Implementation(name = "test server", version = "1.0"), + options = ServerOptions( + capabilities = ServerCapabilities(), + ), + ) + + val serverSessionResult = CompletableDeferred() + + listOf( + launch { + client.connect(clientTransport) + println("Client connected") + }, + launch { + serverSessionResult.complete(server.createSession(serverTransport)) + println("Server connected") + }, + ).joinAll() + + val serverSession = serverSessionResult.await() + + val result = serverSession.createElicitation( + message = elicitationMessage, + requestedSchema = requestedSchema, + ) + + assertEquals(elicitationResultAction, result.action) + assertEquals(elicitationResultContent, result.content) + } +} diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaSseTransportTest.kt b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaSseTransportTest.kt new file mode 100644 index 0000000..3c2d75a --- /dev/null +++ b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaSseTransportTest.kt @@ -0,0 +1,123 @@ +package io.modelcontextprotocol.kotlin.sdk.client + +import io.ktor.client.HttpClient +import io.ktor.server.application.install +import io.ktor.server.cio.CIO +import io.ktor.server.engine.EmbeddedServer +import io.ktor.server.engine.embeddedServer +import io.ktor.server.routing.routing +import io.modelcontextprotocol.kotlin.sdk.Implementation +import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.server.Server +import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions +import io.modelcontextprotocol.kotlin.sdk.server.mcp +import io.modelcontextprotocol.kotlin.sdk.shared.BaseTransportTest +import kotlinx.coroutines.test.runTest +import kotlin.test.BeforeTest +import kotlin.test.Ignore +import kotlin.test.Test +import io.ktor.client.plugins.sse.SSE as ClientSSE +import io.ktor.server.sse.SSE as ServerSSE + +class OldSchemaSseTransportTest : BaseTransportTest() { + + private suspend fun EmbeddedServer<*, *>.actualPort() = engine.resolvedConnectors().first().port + + private lateinit var mcpServer: Server + + @BeforeTest + fun setUp() { + mcpServer = Server( + serverInfo = Implementation( + name = "test-server", + version = "1.0", + ), + options = ServerOptions(ServerCapabilities()), + ) + } + + @Test + @Ignore // Ignored because it doesn’t work with wasm/js in Ktor 3.2.3 + fun `should start then close cleanly`() = runTest { + val server = embeddedServer(CIO, port = 0) { + install(ServerSSE) + routing { + mcp { mcpServer } + } + }.startSuspend(wait = false) + + val actualPort = server.actualPort() + + val transport = HttpClient { + install(ClientSSE) + }.mcpSseTransport { + url { + host = "localhost" + this.port = actualPort + } + } + + try { + testTransportRead(transport) + } finally { + server.stopSuspend() + } + } + + @Ignore + @Test + fun `should read messages`() = runTest { + val server = embeddedServer(CIO, port = 0) { + install(ServerSSE) + routing { + mcp { mcpServer } + } + }.startSuspend(wait = false) + + val actualPort = server.actualPort() + + val transport = HttpClient { + install(ClientSSE) + }.mcpSseTransport { + url { + host = "localhost" + this.port = actualPort + } + } + + try { + testTransportRead(transport) + } finally { + server.stopSuspend() + } + } + + @Ignore + @Test + fun `test sse path not root path`() = runTest { + val server = embeddedServer(CIO, port = 0) { + install(ServerSSE) + routing { + mcp { mcpServer } + } + }.startSuspend(wait = false) + + val actualPort = server.actualPort() + + val transport = HttpClient { + install(ClientSSE) + }.mcpSseTransport { + url { + host = "localhost" + this.port = actualPort + pathSegments = listOf("sse") + } + } + + try { + testTransportRead(transport) + } finally { + server.stopSuspend() + } + } +} diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt index e81332e..d82ff47 100644 --- a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt +++ b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt @@ -6,12 +6,12 @@ import io.ktor.server.cio.CIO import io.ktor.server.engine.EmbeddedServer import io.ktor.server.engine.embeddedServer import io.ktor.server.routing.routing -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions import io.modelcontextprotocol.kotlin.sdk.server.mcp import io.modelcontextprotocol.kotlin.sdk.shared.BaseTransportTest +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities import kotlinx.coroutines.test.runTest import kotlin.test.BeforeTest import kotlin.test.Ignore diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/InMemoryTransportTest.kt b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/InMemoryTransportTest.kt index 3e7cdb2..d13f584 100644 --- a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/InMemoryTransportTest.kt +++ b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/InMemoryTransportTest.kt @@ -1,9 +1,9 @@ package io.modelcontextprotocol.kotlin.sdk.integration -import io.modelcontextprotocol.kotlin.sdk.InitializedNotification -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport -import io.modelcontextprotocol.kotlin.sdk.toJSON +import io.modelcontextprotocol.kotlin.sdk.types.InitializedNotification +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.toJSON import kotlinx.coroutines.test.runTest import kotlin.test.BeforeTest import kotlin.test.Test diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/OldSchemaInMemoryTransportTest.kt b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/OldSchemaInMemoryTransportTest.kt new file mode 100644 index 0000000..5f31bc1 --- /dev/null +++ b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/OldSchemaInMemoryTransportTest.kt @@ -0,0 +1,110 @@ +package io.modelcontextprotocol.kotlin.sdk.integration + +import io.modelcontextprotocol.kotlin.sdk.InitializedNotification +import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport +import io.modelcontextprotocol.kotlin.sdk.toJSON +import kotlinx.coroutines.test.runTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class OldSchemaInMemoryTransportTest { + private lateinit var clientTransport: InMemoryTransport + private lateinit var serverTransport: InMemoryTransport + + @BeforeTest + fun setUp() { + val (client, server) = InMemoryTransport.createLinkedPair() + clientTransport = client + serverTransport = server + } + + @Test + fun `should create linked pair`() { + assertNotNull(clientTransport) + assertNotNull(serverTransport) + } + + @Test + fun `should start without error`() = runTest { + clientTransport.start() + serverTransport.start() + // If no exception is thrown, the test passes + } + + @Test + fun `should send message from client to server`() = runTest { + val message = InitializedNotification() + + var receivedMessage: JSONRPCMessage? = null + serverTransport.onMessage { msg -> + receivedMessage = msg + } + + val rpcNotification = message.toJSON() + clientTransport.send(rpcNotification) + assertEquals(rpcNotification, receivedMessage) + } + + @Test + fun `should send message from server to client`() = runTest { + val message = InitializedNotification() + .toJSON() + + var receivedMessage: JSONRPCMessage? = null + clientTransport.onMessage { msg -> + receivedMessage = msg + } + + serverTransport.send(message) + assertEquals(message, receivedMessage) + } + + @Test + fun `should handle close`() = runTest { + var clientClosed = false + var serverClosed = false + + clientTransport.onClose { + clientClosed = true + } + + serverTransport.onClose { + serverClosed = true + } + + clientTransport.close() + assertTrue(clientClosed) + assertTrue(serverClosed) + } + + @Test + fun `should throw error when sending after close`() = runTest { + clientTransport.close() + + assertFailsWith { + clientTransport.send( + InitializedNotification().toJSON(), + ) + } + } + + @Test + fun `should queue messages sent before start`() = runTest { + val message = InitializedNotification() + .toJSON() + + var receivedMessage: JSONRPCMessage? = null + serverTransport.onMessage { msg -> + receivedMessage = msg + } + + clientTransport.send(message) + serverTransport.start() + assertEquals(message, receivedMessage) + } +} diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/BaseTransportTest.kt b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/BaseTransportTest.kt index 26404b6..1929fac 100644 --- a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/BaseTransportTest.kt +++ b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/BaseTransportTest.kt @@ -1,9 +1,9 @@ package io.modelcontextprotocol.kotlin.sdk.shared -import io.modelcontextprotocol.kotlin.sdk.InitializedNotification -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage -import io.modelcontextprotocol.kotlin.sdk.PingRequest -import io.modelcontextprotocol.kotlin.sdk.toJSON +import io.modelcontextprotocol.kotlin.sdk.types.InitializedNotification +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.PingRequest +import io.modelcontextprotocol.kotlin.sdk.types.toJSON import kotlinx.coroutines.CompletableDeferred import kotlin.test.assertEquals import kotlin.test.assertFalse diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/InMemoryTransport.kt b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/InMemoryTransport.kt index fd567c7..5c5fdd7 100644 --- a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/InMemoryTransport.kt +++ b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/InMemoryTransport.kt @@ -1,6 +1,6 @@ package io.modelcontextprotocol.kotlin.sdk.shared -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage /** * In-memory transport for creating clients and servers that talk to each other within the same process. diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaBaseTransportTest.kt b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaBaseTransportTest.kt new file mode 100644 index 0000000..d2d0402 --- /dev/null +++ b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaBaseTransportTest.kt @@ -0,0 +1,63 @@ +package io.modelcontextprotocol.kotlin.sdk.shared + +import io.modelcontextprotocol.kotlin.sdk.InitializedNotification +import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.PingRequest +import io.modelcontextprotocol.kotlin.sdk.toJSON +import kotlinx.coroutines.CompletableDeferred +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue +import kotlin.test.fail + +abstract class OldSchemaBaseTransportTest { + + protected suspend fun testTransportOpenClose(transport: Transport) { + transport.onError { error -> + fail("Unexpected error: $error") + } + + var didClose = false + transport.onClose { didClose = true } + + transport.start() + assertFalse(didClose, "Transport should not be closed immediately after start") + + transport.close() + assertTrue(didClose, "Transport should be closed after close() call") + } + + protected suspend fun testTransportRead(transport: Transport) { + transport.onError { error -> + error.printStackTrace() + fail("Unexpected error: $error") + } + + val messages = listOf( + PingRequest().toJSON(), + InitializedNotification().toJSON(), + ) + + val readMessages = mutableListOf() + val finished = CompletableDeferred() + + transport.onMessage { message -> + readMessages.add(message) + if (message == messages.last()) { + finished.complete(Unit) + } + } + + transport.start() + + for (message in messages) { + transport.send(message) + } + + finished.await() + + assertEquals(messages, readMessages, "Assert messages received") + + transport.close() + } +} diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaInMemoryTransport.kt b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaInMemoryTransport.kt new file mode 100644 index 0000000..e788918 --- /dev/null +++ b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaInMemoryTransport.kt @@ -0,0 +1,47 @@ +package io.modelcontextprotocol.kotlin.sdk.shared + +import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage + +/** + * In-memory transport for creating clients and servers that talk to each other within the same process. + */ +class OldSchemaInMemoryTransport : AbstractTransport() { + private var otherTransport: OldSchemaInMemoryTransport? = null + private val messageQueue: MutableList = mutableListOf() + + /** + * Creates a pair of linked in-memory transports that can communicate with each other. + * One should be passed to a Client and one to a Server. + */ + companion object { + fun createLinkedPair(): Pair { + val clientTransport = OldSchemaInMemoryTransport() + val serverTransport = OldSchemaInMemoryTransport() + clientTransport.otherTransport = serverTransport + serverTransport.otherTransport = clientTransport + return Pair(clientTransport, serverTransport) + } + } + + override suspend fun start() { + // Process any messages that were queued before start was called + while (messageQueue.isNotEmpty()) { + messageQueue.removeFirstOrNull()?.let { message -> + _onMessage.invoke(message) // todo? + } + } + } + + override suspend fun close() { + val other = otherTransport + otherTransport = null + other?.close() + _onClose.invoke() + } + + override suspend fun send(message: JSONRPCMessage) { + val other = otherTransport ?: throw IllegalStateException("Not connected") + + other._onMessage.invoke(message) + } +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt new file mode 100644 index 0000000..b66ac7b --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt @@ -0,0 +1,694 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.kotlin + +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult +import io.modelcontextprotocol.kotlin.sdk.types.McpException +import io.modelcontextprotocol.kotlin.sdk.types.PromptArgument +import io.modelcontextprotocol.kotlin.sdk.types.PromptMessage +import io.modelcontextprotocol.kotlin.sdk.types.RPCError +import io.modelcontextprotocol.kotlin.sdk.types.Role +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +abstract class AbstractPromptIntegrationTest : KotlinTestBase() { + + private val basicPromptName = "basic-prompt" + private val basicPromptDescription = "A basic prompt for testing" + + private val complexPromptName = "multimodal-prompt" + private val complexPromptDescription = "A prompt with multiple content types" + private val conversationPromptName = "conversation" + private val conversationPromptDescription = "A prompt with multiple messages and roles" + private val strictPromptName = "strict-prompt" + private val strictPromptDescription = "A prompt with required arguments" + + private val largePromptName = "large-prompt" + private val largePromptDescription = "A very large prompt for testing" + private val largePromptContent = "X".repeat(100_000) // 100KB of data + + private val specialCharsPromptName = "special-chars-prompt" + private val specialCharsPromptDescription = "A prompt with special characters" + private val specialCharsContent = "!@#$%^&*()_+{}|:\"<>?~`-=[]\\;',./\n\t" + + override fun configureServerCapabilities(): ServerCapabilities = ServerCapabilities( + prompts = ServerCapabilities.Prompts( + listChanged = true, + ), + ) + + override fun configureServer() { + // basic prompt with a name parameter + server.addPrompt( + name = basicPromptName, + description = basicPromptDescription, + arguments = listOf( + PromptArgument( + name = "name", + description = "The name to greet", + required = true, + ), + ), + ) { request -> + val name = request.params.arguments?.get("name") ?: "World" + + GetPromptResult( + description = basicPromptDescription, + messages = listOf( + PromptMessage( + role = Role.User, + content = TextContent(text = "Hello, $name!"), + ), + PromptMessage( + role = Role.Assistant, + content = TextContent(text = "Greetings, $name! How can I assist you today?"), + ), + ), + ) + } + + // special chars prompt + server.addPrompt( + name = specialCharsPromptName, + description = specialCharsPromptDescription, + arguments = listOf( + PromptArgument( + name = "special", + description = "Special characters to include", + required = false, + ), + ), + ) { request -> + val special = request.params.arguments?.get("special") ?: specialCharsContent + + GetPromptResult( + description = specialCharsPromptDescription, + messages = listOf( + PromptMessage( + role = Role.User, + content = TextContent(text = "Special characters: $special"), + ), + PromptMessage( + role = Role.Assistant, + content = TextContent(text = "Received special characters: $special"), + ), + ), + ) + } + + // very large prompt + server.addPrompt( + name = largePromptName, + description = largePromptDescription, + arguments = listOf( + PromptArgument( + name = "size", + description = "Size multiplier", + required = false, + ), + ), + ) { request -> + val size = request.params.arguments?.get("size")?.toIntOrNull() ?: 1 + val content = largePromptContent.repeat(size) + + GetPromptResult( + description = largePromptDescription, + messages = listOf( + PromptMessage( + role = Role.User, + content = TextContent(text = "Generate a large response"), + ), + PromptMessage( + role = Role.Assistant, + content = TextContent(text = content), + ), + ), + ) + } + + // complext prompt + server.addPrompt( + name = complexPromptName, + description = complexPromptDescription, + arguments = listOf( + PromptArgument(name = "arg1", description = "Argument 1", required = true), + PromptArgument(name = "arg2", description = "Argument 2", required = true), + PromptArgument(name = "arg3", description = "Argument 3", required = true), + PromptArgument(name = "arg4", description = "Argument 4", required = false), + PromptArgument(name = "arg5", description = "Argument 5", required = false), + PromptArgument(name = "arg6", description = "Argument 6", required = false), + PromptArgument(name = "arg7", description = "Argument 7", required = false), + PromptArgument(name = "arg8", description = "Argument 8", required = false), + PromptArgument(name = "arg9", description = "Argument 9", required = false), + PromptArgument(name = "arg10", description = "Argument 10", required = false), + ), + ) { request -> + // validate required arguments + val requiredArgs = listOf("arg1", "arg2", "arg3") + for (argName in requiredArgs) { + if (request.params.arguments?.get(argName) == null) { + throw IllegalArgumentException("Missing required argument: $argName") + } + } + + val args = mutableMapOf() + for (i in 1..10) { + val argName = "arg$i" + val argValue = request.params.arguments?.get(argName) + if (argValue != null) { + args[argName] = argValue + } + } + + GetPromptResult( + description = complexPromptDescription, + messages = listOf( + PromptMessage( + role = Role.User, + content = TextContent( + text = "Arguments: ${ + args.entries.joinToString { + "${it.key}=${it.value}" + } + }", + ), + ), + PromptMessage( + role = Role.Assistant, + content = TextContent(text = "Received ${args.size} arguments"), + ), + ), + ) + } + + // prompt with multiple messages and roles + server.addPrompt( + name = conversationPromptName, + description = conversationPromptDescription, + arguments = listOf( + PromptArgument( + name = "topic", + description = "The topic of the conversation", + required = false, + ), + ), + ) { request -> + val topic = request.params.arguments?.get("topic") ?: "weather" + + GetPromptResult( + description = conversationPromptDescription, + messages = listOf( + PromptMessage( + role = Role.User, + content = TextContent(text = "Let's talk about the $topic."), + ), + PromptMessage( + role = Role.Assistant, + content = TextContent( + text = "Sure, I'd love to discuss the $topic. What would you like to know?", + ), + ), + PromptMessage( + role = Role.User, + content = TextContent(text = "What's your opinion on the $topic?"), + ), + PromptMessage( + role = Role.Assistant, + content = TextContent( + text = "As an AI, I don't have personal opinions," + + " but I can provide information about $topic.", + ), + ), + PromptMessage( + role = Role.User, + content = TextContent(text = "That's helpful, thank you!"), + ), + PromptMessage( + role = Role.Assistant, + content = TextContent( + text = "You're welcome! Let me know if you have more questions about $topic.", + ), + ), + ), + ) + } + + // prompt with strict required arguments + server.addPrompt( + name = strictPromptName, + description = strictPromptDescription, + arguments = listOf( + PromptArgument( + name = "requiredArg1", + description = "First required argument", + required = true, + ), + PromptArgument( + name = "requiredArg2", + description = "Second required argument", + required = true, + ), + PromptArgument( + name = "optionalArg", + description = "Optional argument", + required = false, + ), + ), + ) { request -> + val args = request.params.arguments ?: emptyMap() + val arg1 = args["requiredArg1"] ?: throw IllegalArgumentException( + "Missing required argument: requiredArg1", + ) + val arg2 = args["requiredArg2"] ?: throw IllegalArgumentException( + "Missing required argument: requiredArg2", + ) + val optArg = args["optionalArg"] ?: "default" + + GetPromptResult( + description = strictPromptDescription, + messages = listOf( + PromptMessage( + role = Role.User, + content = TextContent(text = "Required arguments: $arg1, $arg2. Optional: $optArg"), + ), + PromptMessage( + role = Role.Assistant, + content = TextContent(text = "I received your arguments: $arg1, $arg2, and $optArg"), + ), + ), + ) + } + } + + @Test + fun testListPrompts() = runBlocking(Dispatchers.IO) { + val result = client.listPrompts() + + assertNotNull(result, "List prompts result should not be null") + assertTrue(result.prompts.isNotEmpty(), "Prompts list should not be empty") + + val testPrompt = result.prompts.find { it.name == basicPromptName } + assertNotNull(testPrompt, "Test prompt should be in the list") + assertEquals( + basicPromptDescription, + testPrompt.description, + "Prompt description should match", + ) + + val arguments = testPrompt.arguments ?: error("Prompt arguments should not be null") + assertTrue(arguments.isNotEmpty(), "Prompt arguments should not be empty") + + val nameArg = arguments.find { it.name == "name" } + assertNotNull(nameArg, "Name argument should be in the list") + assertEquals( + "The name to greet", + nameArg.description, + "Argument description should match", + ) + assertEquals(true, nameArg.required, "Argument required flag should match") + } + + @Test + fun testGetPrompt() = runBlocking(Dispatchers.IO) { + val testName = "Alice" + val result = client.getPrompt( + GetPromptRequest( + GetPromptRequestParams( + name = basicPromptName, + arguments = mapOf("name" to testName), + ), + ), + ) + + assertNotNull(result, "Get prompt result should not be null") + assertEquals( + basicPromptDescription, + result.description, + "Prompt description should match", + ) + + assertTrue(result.messages.isNotEmpty(), "Prompt messages should not be empty") + assertEquals(2, result.messages.size, "Prompt should have 2 messages") + + val userMessage = result.messages.find { it.role == Role.User } + assertNotNull(userMessage, "User message should be in the list") + val userContent = userMessage.content as? TextContent + assertNotNull(userContent, "User message content should be TextContent") + assertNotNull(userContent.text, "User message text should not be null") + assertEquals( + "Hello, $testName!", + userContent.text, + "User message content should match", + ) + + val assistantMessage = result.messages.find { it.role == Role.Assistant } + assertNotNull(assistantMessage, "Assistant message should be in the list") + val assistantContent = assistantMessage.content as? TextContent + assertNotNull(assistantContent, "Assistant message content should be TextContent") + assertNotNull(assistantContent.text, "Assistant message text should not be null") + assertEquals( + "Greetings, $testName! How can I assist you today?", + assistantContent.text, + "Assistant message content should match", + ) + } + + @Test + fun testMissingRequiredArguments() = runBlocking(Dispatchers.IO) { + val promptsList = client.listPrompts() + assertNotNull(promptsList, "Prompts list should not be null") + val strictPrompt = promptsList.prompts.find { it.name == strictPromptName } + assertNotNull(strictPrompt, "Strict prompt should be in the list") + + val argsDef = strictPrompt.arguments ?: error("Prompt arguments should not be null") + val requiredArgs = argsDef.filter { it.required == true } + assertEquals( + 2, + requiredArgs.size, + "Strict prompt should have 2 required arguments", + ) + + // test missing required arg + val exception = assertThrows { + runBlocking { + client.getPrompt( + GetPromptRequest( + GetPromptRequestParams( + name = strictPromptName, + arguments = mapOf("requiredArg1" to "value1"), + ), + ), + ) + } + } + + assertTrue(exception.message.contains("requiredArg2"), "Exception should mention the missing argument") + + // test with no args + val exception2 = assertThrows { + runBlocking { + client.getPrompt( + GetPromptRequest( + GetPromptRequestParams( + name = strictPromptName, + arguments = emptyMap(), + ), + ), + ) + } + } + + assertTrue(exception2.message.contains("requiredArg"), "Exception should mention a missing required argument") + + // test with all required args + val result = client.getPrompt( + GetPromptRequest( + GetPromptRequestParams( + name = strictPromptName, + arguments = mapOf( + "requiredArg1" to "value1", + "requiredArg2" to "value2", + ), + ), + ), + ) + + assertNotNull(result, "Get prompt result should not be null") + assertEquals(2, result.messages.size, "Prompt should have 2 messages") + + val userMessage = result.messages.find { it.role == Role.User } + assertNotNull(userMessage, "User message should be in the list") + val userContent = userMessage.content as? TextContent + assertNotNull(userContent, "User message content should be TextContent") + val userText = requireNotNull(userContent.text) + assertTrue(userText.contains("value1"), "Message should contain first argument") + assertTrue(userText.contains("value2"), "Message should contain second argument") + } + + @Test + fun testMultipleMessagesAndRoles() = runBlocking(Dispatchers.IO) { + val topic = "climate change" + val result = client.getPrompt( + GetPromptRequest( + GetPromptRequestParams( + name = conversationPromptName, + arguments = mapOf("topic" to topic), + ), + ), + ) + + assertNotNull(result, "Get prompt result should not be null") + assertEquals( + conversationPromptDescription, + result.description, + "Prompt description should match", + ) + + assertTrue(result.messages.isNotEmpty(), "Prompt messages should not be empty") + assertEquals(6, result.messages.size, "Prompt should have 6 messages") + + val userMessages = result.messages.filter { it.role == Role.User } + val assistantMessages = result.messages.filter { it.role == Role.Assistant } + + assertEquals(3, userMessages.size, "Should have 3 user messages") + assertEquals(3, assistantMessages.size, "Should have 3 assistant messages") + + for (i in 0 until result.messages.size) { + val expectedRole = if (i % 2 == 0) Role.User else Role.Assistant + assertEquals( + expectedRole, + result.messages[i].role, + "Message $i should have role $expectedRole", + ) + } + + for (message in result.messages) { + val content = message.content as? TextContent + assertNotNull(content, "Message content should be TextContent") + val text = requireNotNull(content.text) + + // Either the message contains the topic or it's a generic conversation message + val containsTopic = text.contains(topic) + val isGenericMessage = text.contains("thank you") || text.contains("welcome") + + assertTrue( + containsTopic || isGenericMessage, + "Message should either contain the topic or be a generic conversation message", + ) + } + } + + @Test + fun testBasicPrompt() = runBlocking(Dispatchers.IO) { + val testName = "Alice" + val result = client.getPrompt( + GetPromptRequest( + GetPromptRequestParams( + name = basicPromptName, + arguments = mapOf("name" to testName), + ), + ), + ) + + assertNotNull(result, "Get prompt result should not be null") + assertEquals(basicPromptDescription, result.description, "Prompt description should match") + + assertEquals(2, result.messages.size, "Prompt should have 2 messages") + + val userMessage = result.messages.find { it.role == Role.User } + assertNotNull(userMessage, "User message should be in the list") + val userContent = userMessage.content as? TextContent + assertNotNull(userContent, "User message content should be TextContent") + assertEquals("Hello, $testName!", userContent.text, "User message content should match") + + val assistantMessage = result.messages.find { it.role == Role.Assistant } + assertNotNull(assistantMessage, "Assistant message should be in the list") + val assistantContent = assistantMessage.content as? TextContent + assertNotNull(assistantContent, "Assistant message content should be TextContent") + assertEquals( + "Greetings, $testName! How can I assist you today?", + assistantContent.text, + "Assistant message content should match", + ) + } + + @Test + fun testComplexPromptWithManyArguments() = runBlocking(Dispatchers.IO) { + val arguments = (1..10).associate { i -> "arg$i" to "value$i" } + + val result = client.getPrompt( + GetPromptRequest( + GetPromptRequestParams( + name = complexPromptName, + arguments = arguments, + ), + ), + ) + + assertNotNull(result, "Get prompt result should not be null") + assertEquals(complexPromptDescription, result.description, "Prompt description should match") + + assertEquals(2, result.messages.size, "Prompt should have 2 messages") + + val userMessage = result.messages.find { it.role == Role.User } + assertNotNull(userMessage, "User message should be in the list") + val userContent = userMessage.content as? TextContent + assertNotNull(userContent, "User message content should be TextContent") + + // verify all arguments + val text = userContent.text + for (i in 1..10) { + assertTrue(text.contains("arg$i=value$i"), "Message should contain arg$i=value$i") + } + + val assistantMessage = result.messages.find { it.role == Role.Assistant } + assertNotNull(assistantMessage, "Assistant message should be in the list") + val assistantContent = assistantMessage.content as? TextContent + assertNotNull(assistantContent, "Assistant message content should be TextContent") + assertEquals( + "Received 10 arguments", + assistantContent.text, + "Assistant message should indicate 10 arguments", + ) + } + + @Test + fun testLargePrompt() = runBlocking(Dispatchers.IO) { + val result = client.getPrompt( + GetPromptRequest( + GetPromptRequestParams( + name = largePromptName, + arguments = mapOf("size" to "1"), + ), + ), + ) + + assertNotNull(result, "Get prompt result should not be null") + assertEquals(largePromptDescription, result.description, "Prompt description should match") + + assertEquals(2, result.messages.size, "Prompt should have 2 messages") + + val assistantMessage = result.messages.find { it.role == Role.Assistant } + assertNotNull(assistantMessage, "Assistant message should be in the list") + val assistantContent = assistantMessage.content as? TextContent + assertNotNull(assistantContent, "Assistant message content should be TextContent") + val text = assistantContent.text + assertEquals(100_000, text.length, "Assistant message should be 100KB in size") + } + + @Test + fun testSpecialCharacters() = runBlocking(Dispatchers.IO) { + val result = client.getPrompt( + GetPromptRequest( + GetPromptRequestParams( + name = specialCharsPromptName, + arguments = mapOf("special" to specialCharsContent), + ), + ), + ) + + assertNotNull(result, "Get prompt result should not be null") + assertEquals(specialCharsPromptDescription, result.description, "Prompt description should match") + + assertEquals(2, result.messages.size, "Prompt should have 2 messages") + + val userMessage = result.messages.find { it.role == Role.User } + assertNotNull(userMessage, "User message should be in the list") + val userContent = userMessage.content as? TextContent + assertNotNull(userContent, "User message content should be TextContent") + val userText = userContent.text + assertTrue(userText.contains(specialCharsContent), "User message should contain special characters") + + val assistantMessage = result.messages.find { it.role == Role.Assistant } + assertNotNull(assistantMessage, "Assistant message should be in the list") + val assistantContent = assistantMessage.content as? TextContent + assertNotNull(assistantContent, "Assistant message content should be TextContent") + val assistantText = assistantContent.text + assertTrue( + assistantText.contains(specialCharsContent), + "Assistant message should contain special characters", + ) + } + + @Test + fun testConcurrentPromptRequests() = runTest { + val concurrentCount = 10 + val results = mutableListOf() + + runBlocking { + repeat(concurrentCount) { index -> + launch { + val promptName = when (index % 4) { + 0 -> basicPromptName + 1 -> complexPromptName + 2 -> largePromptName + else -> specialCharsPromptName + } + + val arguments = when (promptName) { + basicPromptName -> mapOf("name" to "User$index") + complexPromptName -> mapOf("arg1" to "v1", "arg2" to "v2", "arg3" to "v3") + largePromptName -> mapOf("size" to "1") + else -> mapOf("special" to "!@#$%^&*()") + } + + val result = client.getPrompt( + GetPromptRequest( + GetPromptRequestParams( + name = promptName, + arguments = arguments, + ), + ), + ) + + synchronized(results) { + results.add(result) + } + } + } + } + + assertEquals(concurrentCount, results.size, "All concurrent operations should complete") + + results.forEach { result -> + assertNotNull(result, "Result should not be null") + assertTrue(result.messages.isNotEmpty(), "Result messages should not be empty") + } + } + + @Test + fun testNonExistentPrompt() = runTest { + val nonExistentPromptName = "non-existent-prompt" + + val exception = assertThrows { + runBlocking { + client.getPrompt( + GetPromptRequest( + GetPromptRequestParams( + name = nonExistentPromptName, + arguments = mapOf("name" to "Test"), + ), + ), + ) + } + } + + val expectedMessage = "MCP error -32603: Prompt not found: non-existent-prompt" + + assertEquals( + RPCError.ErrorCode.INTERNAL_ERROR, + exception.code, + "Exception code should be INTERNAL_ERROR: ${RPCError.ErrorCode.INTERNAL_ERROR}", + ) + assertEquals(expectedMessage, exception.message, "Unexpected error message for non-existent prompt") + } +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt new file mode 100644 index 0000000..fc08810 --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt @@ -0,0 +1,317 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.kotlin + +import io.modelcontextprotocol.kotlin.sdk.types.BlobResourceContents +import io.modelcontextprotocol.kotlin.sdk.types.McpException +import io.modelcontextprotocol.kotlin.sdk.types.RPCError +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.SubscribeRequest +import io.modelcontextprotocol.kotlin.sdk.types.SubscribeRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents +import io.modelcontextprotocol.kotlin.sdk.types.UnsubscribeRequest +import io.modelcontextprotocol.kotlin.sdk.types.UnsubscribeRequestParams +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import java.util.concurrent.atomic.AtomicBoolean +import kotlin.test.Ignore +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +abstract class AbstractResourceIntegrationTest : KotlinTestBase() { + + private val testResourceUri = "test://example.txt" + private val testResourceName = "Test Resource" + private val testResourceDescription = "A test resource for integration testing" + private val testResourceContent = "This is the content of the test resource." + + private val binaryResourceUri = "test://image.png" + private val binaryResourceName = "Binary Resource" + private val binaryResourceDescription = "A binary resource for testing" + private val binaryResourceContent = + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==" + + private val largeResourceUri = "test://large.txt" + private val largeResourceName = "Large Resource" + private val largeResourceDescription = "A large text resource for testing" + private val largeResourceContent = "X".repeat(100_000) // 100KB of data + + private val dynamicResourceUri = "test://dynamic.txt" + private val dynamicResourceName = "Dynamic Resource" + private val dynamicResourceContent = AtomicBoolean(false) + + override fun configureServerCapabilities(): ServerCapabilities = ServerCapabilities( + resources = ServerCapabilities.Resources( + subscribe = true, + listChanged = true, + ), + ) + + override fun configureServer() { + server.addResource( + uri = testResourceUri, + name = testResourceName, + description = testResourceDescription, + mimeType = "text/plain", + ) { request -> + ReadResourceResult( + contents = listOf( + TextResourceContents( + text = testResourceContent, + uri = request.params.uri, + mimeType = "text/plain", + ), + ), + ) + } + + server.addResource( + uri = testResourceUri, + name = testResourceName, + description = testResourceDescription, + mimeType = "text/plain", + ) { request -> + ReadResourceResult( + contents = listOf( + TextResourceContents( + text = testResourceContent, + uri = request.params.uri, + mimeType = "text/plain", + ), + ), + ) + } + + server.addResource( + uri = binaryResourceUri, + name = binaryResourceName, + description = binaryResourceDescription, + mimeType = "image/png", + ) { request -> + ReadResourceResult( + contents = listOf( + BlobResourceContents( + blob = binaryResourceContent, + uri = request.params.uri, + mimeType = "image/png", + ), + ), + ) + } + + server.addResource( + uri = largeResourceUri, + name = largeResourceName, + description = largeResourceDescription, + mimeType = "text/plain", + ) { request -> + ReadResourceResult( + contents = listOf( + TextResourceContents( + text = largeResourceContent, + uri = request.params.uri, + mimeType = "text/plain", + ), + ), + ) + } + + server.addResource( + uri = dynamicResourceUri, + name = dynamicResourceName, + description = "A resource that can be updated", + mimeType = "text/plain", + ) { request -> + ReadResourceResult( + contents = listOf( + TextResourceContents( + text = if (dynamicResourceContent.get()) "Updated content" else "Original content", + uri = request.params.uri, + mimeType = "text/plain", + ), + ), + ) + } + } + + @Test + fun testListResources() = runBlocking(Dispatchers.IO) { + val result = client.listResources() + + assertNotNull(result, "List resources result should not be null") + assertTrue(result.resources.isNotEmpty(), "Resources list should not be empty") + + val testResource = result.resources.find { it.uri == testResourceUri } + assertNotNull(testResource, "Test resource should be in the list") + assertEquals(testResourceName, testResource.name, "Resource name should match") + assertEquals(testResourceDescription, testResource.description, "Resource description should match") + } + + @Test + fun testReadResource() = runBlocking(Dispatchers.IO) { + val result = client.readResource(ReadResourceRequest(ReadResourceRequestParams(uri = testResourceUri))) + + assertNotNull(result, "Read resource result should not be null") + assertTrue(result.contents.isNotEmpty(), "Resource contents should not be empty") + + val content = result.contents.firstOrNull() as? TextResourceContents + assertNotNull(content, "Resource content should be TextResourceContents") + assertEquals(testResourceContent, content.text, "Resource content should match") + } + + @Ignore("Blocked by https://github.com/modelcontextprotocol/kotlin-sdk/issues/249") + @Test + fun testSubscribeAndUnsubscribe() { + runBlocking(Dispatchers.IO) { + val subscribeResult = + client.subscribeResource(SubscribeRequest(SubscribeRequestParams(uri = testResourceUri))) + assertNotNull(subscribeResult, "Subscribe result should not be null") + + val unsubscribeResult = + client.unsubscribeResource(UnsubscribeRequest(UnsubscribeRequestParams(uri = testResourceUri))) + assertNotNull(unsubscribeResult, "Unsubscribe result should not be null") + } + } + + @Test + fun testBinaryResource() = runBlocking(Dispatchers.IO) { + val result = client.readResource(ReadResourceRequest(ReadResourceRequestParams(uri = binaryResourceUri))) + + assertNotNull(result, "Read resource result should not be null") + assertTrue(result.contents.isNotEmpty(), "Resource contents should not be empty") + + val content = result.contents.firstOrNull() as? BlobResourceContents + assertNotNull(content, "Resource content should be BlobResourceContents") + assertEquals(binaryResourceContent, content.blob, "Binary resource content should match") + assertEquals("image/png", content.mimeType, "MIME type should match") + } + + @Test + fun testLargeResource() = runBlocking(Dispatchers.IO) { + val result = client.readResource(ReadResourceRequest(ReadResourceRequestParams(uri = largeResourceUri))) + + assertNotNull(result, "Read resource result should not be null") + assertTrue(result.contents.isNotEmpty(), "Resource contents should not be empty") + + val content = result.contents.firstOrNull() as? TextResourceContents + assertNotNull(content, "Resource content should be TextResourceContents") + assertEquals(100_000, content.text.length, "Large resource content length should match") + assertEquals("X".repeat(100_000), content.text, "Large resource content should match") + } + + @Test + fun testInvalidResourceUri() = runTest { + val invalidUri = "test://nonexistent.txt" + + val exception = assertThrows { + runBlocking { + client.readResource(ReadResourceRequest(ReadResourceRequestParams(uri = invalidUri))) + } + } + + val expectedMessage = "MCP error -32603: Resource not found: test://nonexistent.txt" + + assertEquals( + RPCError.ErrorCode.INTERNAL_ERROR, + exception.code, + "Exception code should be INTERNAL_ERROR: ${RPCError.ErrorCode.INTERNAL_ERROR}", + ) + assertEquals(expectedMessage, exception.message, "Unexpected error message for invalid resource URI") + } + + @Test + fun testDynamicResource() = runBlocking(Dispatchers.IO) { + val initialResult = + client.readResource(ReadResourceRequest(ReadResourceRequestParams(uri = dynamicResourceUri))) + assertNotNull(initialResult, "Initial read result should not be null") + val initialContent = (initialResult.contents.firstOrNull() as? TextResourceContents)?.text + assertEquals("Original content", initialContent, "Initial content should match") + + // update resource + dynamicResourceContent.set(true) + + val updatedResult = + client.readResource(ReadResourceRequest(ReadResourceRequestParams(uri = dynamicResourceUri))) + assertNotNull(updatedResult, "Updated read result should not be null") + val updatedContent = (updatedResult.contents.firstOrNull() as? TextResourceContents)?.text + assertEquals("Updated content", updatedContent, "Updated content should match") + } + + @Test + fun testResourceAddAndRemove() = runBlocking(Dispatchers.IO) { + val initialList = client.listResources() + assertNotNull(initialList, "Initial list result should not be null") + val initialCount = initialList.resources.size + + val newResourceUri = "test://new-resource.txt" + server.addResource( + uri = newResourceUri, + name = "New Resource", + description = "A newly added resource", + mimeType = "text/plain", + ) { request -> + ReadResourceResult( + contents = listOf( + TextResourceContents( + text = "New resource content", + uri = request.params.uri, + mimeType = "text/plain", + ), + ), + ) + } + + val updatedList = client.listResources() + assertNotNull(updatedList, "Updated list result should not be null") + val updatedCount = updatedList.resources.size + + assertEquals(initialCount + 1, updatedCount, "Resource count should increase by 1") + val newResource = updatedList.resources.find { it.uri == newResourceUri } + assertNotNull(newResource, "New resource should be in the list") + + server.removeResource(newResourceUri) + + val finalList = client.listResources() + assertNotNull(finalList, "Final list result should not be null") + val finalCount = finalList.resources.size + + assertEquals(initialCount, finalCount, "Resource count should return to initial value") + val removedResource = finalList.resources.find { it.uri == newResourceUri } + assertEquals(null, removedResource, "Resource should be removed from the list") + } + + @Test + fun testConcurrentResourceOperations() = runTest { + val concurrentCount = 10 + val results = mutableListOf() + + runBlocking { + repeat(concurrentCount) { index -> + launch { + val uri = when (index % 3) { + 0 -> testResourceUri + 1 -> binaryResourceUri + else -> largeResourceUri + } + + val result = client.readResource(ReadResourceRequest(ReadResourceRequestParams(uri = uri))) + synchronized(results) { + results.add(result) + } + } + } + } + + assertEquals(concurrentCount, results.size, "All concurrent operations should complete") + results.forEach { result -> + assertNotNull(result, "Result should not be null") + assertTrue(result.contents.isNotEmpty(), "Result contents should not be empty") + } + } +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt new file mode 100644 index 0000000..7da82cc --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt @@ -0,0 +1,794 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.kotlin + +import io.kotest.assertions.json.shouldEqualJson +import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest +import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult +import io.modelcontextprotocol.kotlin.sdk.types.ContentBlock +import io.modelcontextprotocol.kotlin.sdk.types.ImageContent +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.add +import kotlinx.serialization.json.buildJsonArray +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import org.junit.jupiter.api.Test +import java.text.DecimalFormat +import java.text.DecimalFormatSymbols +import java.util.Locale +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +abstract class AbstractToolIntegrationTest : KotlinTestBase() { + private val testToolName = "echo" + private val testToolDescription = "A simple echo tool that returns the input text" + private val complexToolName = "calculator" + private val complexToolDescription = "A calculator tool that performs operations on numbers" + private val errorToolName = "error-tool" + private val errorToolDescription = "A tool that demonstrates error handling" + private val multiContentToolName = "multi-content" + private val multiContentToolDescription = "A tool that returns multiple content types" + + private val basicToolName = "basic-tool" + private val basicToolDescription = "A basic tool for testing" + + private val largeToolName = "large-tool" + private val largeToolDescription = "A tool that returns a large response" + private val largeToolContent = "X".repeat(100_000) // 100KB of data + + private val slowToolName = "slow-tool" + private val slowToolDescription = "A tool that takes time to respond" + + private val specialCharsToolName = "special-chars-tool" + private val specialCharsToolDescription = "A tool that handles special characters" + private val specialCharsContent = "!@#$%^&*()_+{}|:\"<>?~`-=[]\\;',./\n\t" + + override fun configureServerCapabilities(): ServerCapabilities = ServerCapabilities( + tools = ServerCapabilities.Tools( + listChanged = true, + ), + ) + + override fun configureServer() { + setupEchoTool() + setupCalculatorTool() + setupErrorHandlingTool() + setupMultiContentTool() + } + + private fun setupEchoTool() { + server.addTool( + name = testToolName, + description = testToolDescription, + inputSchema = ToolSchema( + properties = buildJsonObject { + put( + "text", + buildJsonObject { + put("type", "string") + put("description", "The text to echo back") + }, + ) + }, + required = listOf("text"), + ), + ) { request -> + val text = (request.params.arguments?.get("text") as? JsonPrimitive)?.content ?: "No text provided" + + CallToolResult( + content = listOf(TextContent(text = "Echo: $text")), + structuredContent = buildJsonObject { + put("result", text) + }, + ) + } + } + + private fun setupCalculatorTool() { + server.addTool( + name = basicToolName, + description = basicToolDescription, + inputSchema = ToolSchema( + properties = buildJsonObject { + put( + "text", + buildJsonObject { + put("type", "string") + put("description", "The text to echo back") + }, + ) + }, + required = listOf("text"), + ), + ) { request -> + val text = (request.params.arguments?.get("text") as? JsonPrimitive)?.content ?: "No text provided" + + CallToolResult( + content = listOf(TextContent(text = "Echo: $text")), + structuredContent = buildJsonObject { + put("result", text) + }, + ) + } + + server.addTool( + name = specialCharsToolName, + description = specialCharsToolDescription, + inputSchema = ToolSchema( + properties = buildJsonObject { + put( + "special", + buildJsonObject { + put("type", "string") + put("description", "Special characters to process") + }, + ) + }, + ), + ) { request -> + val special = (request.params.arguments?.get("special") as? JsonPrimitive)?.content ?: specialCharsContent + + CallToolResult( + content = listOf(TextContent(text = "Received special characters: $special")), + structuredContent = buildJsonObject { + put("special", special) + put("length", special.length) + }, + ) + } + + server.addTool( + name = slowToolName, + description = slowToolDescription, + inputSchema = ToolSchema( + properties = buildJsonObject { + put( + "delay", + buildJsonObject { + put("type", "integer") + put("description", "Delay in milliseconds") + }, + ) + }, + ), + ) { request -> + val delay = (request.params.arguments?.get("delay") as? JsonPrimitive)?.content?.toIntOrNull() ?: 1000 + + // simulate slow operation + runBlocking { + delay(delay.toLong()) + } + + CallToolResult( + content = listOf(TextContent(text = "Completed after ${delay}ms delay")), + structuredContent = buildJsonObject { + put("delay", delay) + }, + ) + } + + server.addTool( + name = largeToolName, + description = largeToolDescription, + inputSchema = ToolSchema( + properties = buildJsonObject { + put( + "size", + buildJsonObject { + put("type", "integer") + put("description", "Size multiplier") + }, + ) + }, + ), + ) { request -> + val size = (request.params.arguments?.get("size") as? JsonPrimitive)?.content?.toIntOrNull() ?: 1 + val content = largeToolContent.take(largeToolContent.length.coerceAtMost(size * 1000)) + + CallToolResult( + content = listOf(TextContent(text = content)), + structuredContent = buildJsonObject { + put("size", content.length) + }, + ) + } + + server.addTool( + name = complexToolName, + description = complexToolDescription, + inputSchema = ToolSchema( + properties = buildJsonObject { + put( + "operation", + buildJsonObject { + put("type", "string") + put("description", "The operation to perform (add, subtract, multiply, divide)") + put( + "enum", + buildJsonArray { + add("add") + add("subtract") + add("multiply") + add("divide") + }, + ) + }, + ) + put( + "a", + buildJsonObject { + put("type", "number") + put("description", "First operand") + }, + ) + put( + "b", + buildJsonObject { + put("type", "number") + put("description", "Second operand") + }, + ) + put( + "precision", + buildJsonObject { + put("type", "integer") + put("description", "Number of decimal places (optional)") + put("default", 2) + }, + ) + put( + "showSteps", + buildJsonObject { + put("type", "boolean") + put("description", "Whether to show calculation steps") + put("default", false) + }, + ) + put( + "tags", + buildJsonObject { + put("type", "array") + put("description", "Optional tags for the calculation") + put( + "items", + buildJsonObject { + put("type", "string") + }, + ) + }, + ) + }, + required = listOf("operation", "a", "b"), + ), + ) { request -> + val operation = (request.params.arguments?.get("operation") as? JsonPrimitive)?.content ?: "add" + val a = (request.params.arguments?.get("a") as? JsonPrimitive)?.content?.toDoubleOrNull() ?: 0.0 + val b = (request.params.arguments?.get("b") as? JsonPrimitive)?.content?.toDoubleOrNull() ?: 0.0 + val precision = (request.params.arguments?.get("precision") as? JsonPrimitive)?.content?.toIntOrNull() ?: 2 + val showSteps = + (request.params.arguments?.get("showSteps") as? JsonPrimitive)?.content?.toBoolean() ?: false + val tags = (request.params.arguments?.get("tags") as? JsonArray)?.mapNotNull { + (it as? JsonPrimitive)?.content + } ?: emptyList() + + val result = when (operation) { + "add" -> a + b + "subtract" -> a - b + "multiply" -> a * b + "divide" -> if (b != 0.0) a / b else Double.POSITIVE_INFINITY + else -> 0.0 + } + + val pattern = if (precision > 0) "0." + "0".repeat(precision) else "0" + val symbols = DecimalFormatSymbols(Locale.US).apply { decimalSeparator = '.' } + val df = DecimalFormat(pattern, symbols).apply { isGroupingUsed = false } + val formattedResult = df.format(result) + + val textContent = if (showSteps) { + "Operation: $operation\nA: $a\nB: $b\nResult: $formattedResult\nTags: ${ + tags.joinToString(", ") + }" + } else { + "Result: $formattedResult" + } + + CallToolResult( + content = listOf(TextContent(text = textContent)), + structuredContent = buildJsonObject { + put("operation", operation) + put("a", a) + put("b", b) + put("result", result) + put("formattedResult", formattedResult) + put("precision", precision) + put("tags", buildJsonArray { tags.forEach { add(it) } }) + }, + ) + } + } + + private fun setupErrorHandlingTool() { + server.addTool( + name = errorToolName, + description = errorToolDescription, + inputSchema = ToolSchema( + properties = buildJsonObject { + put( + "errorType", + buildJsonObject { + put("type", "string") + put("description", "Type of error to simulate (none, exception, error)") + put( + "enum", + buildJsonArray { + add("none") + add("exception") + add("error") + }, + ) + }, + ) + put( + "message", + buildJsonObject { + put("type", "string") + put("description", "Custom error message") + put("default", "An error occurred") + }, + ) + }, + required = listOf("errorType"), + ), + ) { request -> + val errorType = (request.params.arguments?.get("errorType") as? JsonPrimitive)?.content ?: "none" + val message = (request.params.arguments?.get("message") as? JsonPrimitive)?.content ?: "An error occurred" + + when (errorType) { + "exception" -> throw IllegalArgumentException(message) + + "error" -> CallToolResult( + content = listOf(TextContent(text = "Error: $message")), + structuredContent = buildJsonObject { + put("error", true) + put("message", message) + }, + ) + + else -> CallToolResult( + content = listOf(TextContent(text = "No error occurred")), + structuredContent = buildJsonObject { + put("error", false) + put("message", "Success") + }, + ) + } + } + } + + private fun setupMultiContentTool() { + server.addTool( + name = multiContentToolName, + description = multiContentToolDescription, + inputSchema = ToolSchema( + properties = buildJsonObject { + put( + "text", + buildJsonObject { + put("type", "string") + put("description", "Text to include in the response") + }, + ) + put( + "includeImage", + buildJsonObject { + put("type", "boolean") + put("description", "Whether to include an image in the response") + put("default", true) + }, + ) + }, + required = listOf("text"), + ), + ) { request -> + val text = (request.params.arguments?.get("text") as? JsonPrimitive)?.content ?: "Default text" + val includeImage = + (request.params.arguments?.get("includeImage") as? JsonPrimitive)?.content?.toBoolean() ?: true + + val content = mutableListOf( + TextContent(text = "Text content: $text"), + ) + + if (includeImage) { + content.add( + ImageContent( + data = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==", + mimeType = "image/png", + ), + ) + } + + CallToolResult( + content = content, + structuredContent = buildJsonObject { + put("text", text) + put("includeImage", includeImage) + }, + ) + } + } + + @Test + fun testListTools(): Unit = runBlocking(Dispatchers.IO) { + val result = client.listTools() + + assertNotNull(result, "List utils result should not be null") + assertTrue(result.tools.isNotEmpty(), "Tools list should not be empty") + + val testTool = result.tools.find { it.name == testToolName } + + assertNotNull(testTool, "Test tool should be in the list") + assertEquals( + testToolDescription, + testTool.description, + "Tool description should match", + ) + } + + @Test + fun testCallTool(): Unit = runBlocking(Dispatchers.IO) { + val testText = "Hello, world!" + val arguments = mapOf("text" to testText) + + val result = client.callTool(testToolName, arguments) + + val actualContent = result.structuredContent.toString() + val expectedContent = """ + {"result":"Hello, world!"} + """.trimIndent() + + actualContent shouldEqualJson expectedContent + } + + @Test + fun testComplexInputSchemaTool(): Unit = runBlocking(Dispatchers.IO) { + val toolsList = client.listTools() + assertNotNull(toolsList, "Tools list should not be null") + val calculatorTool = toolsList.tools.find { it.name == complexToolName } + assertNotNull(calculatorTool, "Calculator tool should be in the list") + + val arguments = mapOf( + "operation" to "multiply", + "a" to 5.5, + "b" to 2.0, + "precision" to 3, + "showSteps" to true, + "tags" to listOf("test", "calculator", "integration"), + ) + + val result = client.callTool(complexToolName, arguments) + + val actualContent = result.structuredContent.toString() + val expectedContent = """ + { + "operation" : "multiply", + "a" : 5.5, + "b" : 2.0, + "result" : 11.0, + "formattedResult" : "11.000", + "precision" : 3, + "tags" : ["test", "calculator", "integration"] + } + """.trimIndent() + + actualContent shouldEqualJson expectedContent + } + + @Test + fun testToolErrorHandling(): Unit = runBlocking(Dispatchers.IO) { + val successArgs = mapOf("errorType" to "none") + val successResult = client.callTool(errorToolName, successArgs) + + val actualContent = successResult.structuredContent.toString() + val expectedContent = """ + { + "error" : false, + "message" : "Success" + } + """.trimIndent() + + actualContent shouldEqualJson expectedContent + + val errorArgs = mapOf( + "errorType" to "error", + "message" to "Custom error message", + ) + val errorResult = client.callTool(errorToolName, errorArgs) + + val actualError = errorResult.structuredContent.toString() + val expectedError = """ + { + "error" : true, + "message" : "Custom error message" + } + """.trimIndent() + + actualError shouldEqualJson expectedError + + val exceptionArgs = mapOf( + "errorType" to "exception", + "message" to "My exception message", + ) + + val exceptionResult = client.callTool(errorToolName, exceptionArgs) + + assertTrue(exceptionResult.isError ?: false, "isError should be true for exception") + + val exceptionContent = exceptionResult.content.firstOrNull { it is TextContent } as? TextContent + assertNotNull(exceptionContent, "Error content should be present in the result") + + val exceptionText = exceptionContent.text + assertTrue( + exceptionText.contains("Error executing tool") && exceptionText.contains("My exception message"), + "Error message should contain the exception details", + ) + } + + @Test + fun testMultiContentTool(): Unit = runBlocking(Dispatchers.IO) { + val testText = "Test multi-content" + val arguments = mapOf( + "text" to testText, + "includeImage" to true, + ) + + val result = client.callTool(multiContentToolName, arguments) + + assertEquals( + 2, + result.content.size, + "Tool result should have 2 content items", + ) + + val textContent = result.content.firstOrNull { it is TextContent } as? TextContent + assertNotNull(textContent, "Result should contain TextContent") + assertNotNull(textContent.text, "Text content should not be null") + assertEquals( + "Text content: $testText", + textContent.text, + "Text content should match", + ) + + val imageContent = result.content.firstOrNull { it is ImageContent } as? ImageContent + assertNotNull(imageContent, "Result should contain ImageContent") + assertEquals("image/png", imageContent.mimeType, "Image MIME type should match") + assertTrue(imageContent.data.isNotEmpty(), "Image data should not be empty") + + val actualContent = result.structuredContent.toString() + val expectedContent = """ + { + "text" : "Test multi-content", + "includeImage" : true + } + """.trimIndent() + + actualContent shouldEqualJson expectedContent + + val textOnlyArgs = mapOf( + "text" to testText, + "includeImage" to false, + ) + + val textOnlyResult = client.callTool(multiContentToolName, textOnlyArgs) + + assertEquals( + 1, + textOnlyResult.content.size, + "Text-only result should have 1 content item", + ) + } + + @Test + fun testComplexNestedSchema(): Unit = runBlocking(Dispatchers.IO) { + val userJson = buildJsonObject { + put("name", JsonPrimitive("John Galt")) + put("age", JsonPrimitive(30)) + put( + "address", + buildJsonObject { + put("street", JsonPrimitive("123 Main St")) + put("city", JsonPrimitive("New York")) + put("country", JsonPrimitive("USA")) + }, + ) + } + + val optionsJson = buildJsonArray { + add(JsonPrimitive("option1")) + add(JsonPrimitive("option2")) + add(JsonPrimitive("option3")) + } + + val arguments = buildJsonObject { + put("user", userJson) + put("options", optionsJson) + } + + val result = client.callTool( + CallToolRequest( + CallToolRequestParams( + name = complexToolName, + arguments = arguments, + ), + ), + ) + + val actualContent = result.structuredContent.toString() + val expectedContent = """ + { + "operation": "add", + "a": 0.0, + "b": 0.0, + "result": 0.0, + "formattedResult": "0.00", + "precision": 2, + "tags": [] + } + """.trimIndent() + + actualContent shouldEqualJson expectedContent + } + + @Test + fun testLargeResponse(): Unit = runBlocking(Dispatchers.IO) { + val size = 10 + val arguments = mapOf("size" to size) + + val result = client.callTool(largeToolName, arguments) + + val content = result.content.firstOrNull() as TextContent + assertNotNull(content, "Tool result content should be TextContent") + + val actualContent = result.structuredContent.toString() + val expectedContent = """ + { + "size" : 10000 + } + """.trimIndent() + + actualContent shouldEqualJson expectedContent + } + + @Test + fun testSlowTool(): Unit = runBlocking(Dispatchers.IO) { + val delay = 500 + val arguments = mapOf("delay" to delay) + + val startTime = System.currentTimeMillis() + val result = client.callTool(slowToolName, arguments) + val endTime = System.currentTimeMillis() + + val content = result.content.firstOrNull() as? TextContent + assertNotNull(content, "Tool result content should be TextContent") + + assertTrue(endTime - startTime >= delay, "Tool should take at least the specified delay") + + val actualContent = result.structuredContent.toString() + val expectedContent = """ + { + "delay" : 500 + } + """.trimIndent() + + actualContent shouldEqualJson expectedContent + } + + @Test + fun testSpecialCharacters() { + runBlocking(Dispatchers.IO) { + val arguments = mapOf("special" to specialCharsContent) + + val result = client.callTool(specialCharsToolName, arguments) + + val content = result.content.firstOrNull() as? TextContent + assertNotNull(content, "Tool result content should be TextContent") + val text = content.text + + assertTrue(text.contains(specialCharsContent), "Result should contain the special characters") + + val actualContent = result.structuredContent.toString() + val expectedContent = """ + { + "special" : "!@#$%^&*()_+{}|:\"<>?~`-=[]\\;',./\n\t", + "length" : 34 + } + """.trimIndent() + + actualContent shouldEqualJson expectedContent + } + } + + @Test + fun testConcurrentToolCalls() = runTest { + val concurrentCount = 10 + val results = mutableListOf() + + runBlocking { + repeat(concurrentCount) { index -> + launch { + val toolName = when (index % 5) { + 0 -> basicToolName + 1 -> complexToolName + 2 -> largeToolName + 3 -> slowToolName + else -> specialCharsToolName + } + + val arguments = when (toolName) { + basicToolName -> mapOf("text" to "Concurrent call $index") + + complexToolName -> mapOf( + "user" to mapOf( + "name" to "User $index", + "age" to 20 + index, + "address" to mapOf( + "street" to "Street $index", + "city" to "City $index", + "country" to "Country $index", + ), + ), + ) + + largeToolName -> mapOf("size" to 1) + + slowToolName -> mapOf("delay" to 100) + + else -> mapOf("special" to "!@#$%^&*()") + } + + val result = client.callTool(toolName, arguments) + + synchronized(results) { + results.add(result) + } + } + } + } + + assertEquals(concurrentCount, results.size, "All concurrent operations should complete") + results.forEach { result -> + assertNotNull(result, "Result should not be null") + assertTrue(result.content.isNotEmpty(), "Result content should not be empty") + } + } + + @Test + fun testNonExistentTool() = runTest { + val nonExistentToolName = "non-existent-tool" + val arguments = mapOf("text" to "Test") + + val result = runBlocking { + client.callTool(nonExistentToolName, arguments) + } + + assertNotNull(result, "Tool call result should not be null") + assertTrue(result.isError ?: false, "isError should be true for non-existent tool") + + val textContent = result.content.firstOrNull { it is TextContent } as? TextContent + assertNotNull(textContent, "Error content should be present in the result") + + val errorText = textContent.text + assertTrue( + errorText.contains("non-existent-tool") && errorText.contains("not found"), + "Error message should indicate the tool was not found", + ) + } +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/KotlinTestBase.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/KotlinTestBase.kt new file mode 100644 index 0000000..e409ec6 --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/KotlinTestBase.kt @@ -0,0 +1,185 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.kotlin + +import io.ktor.client.HttpClient +import io.ktor.client.engine.cio.CIO +import io.ktor.client.plugins.sse.SSE +import io.ktor.server.application.install +import io.ktor.server.engine.EmbeddedServer +import io.ktor.server.engine.embeddedServer +import io.ktor.server.routing.routing +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.client.SseClientTransport +import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport +import io.modelcontextprotocol.kotlin.sdk.integration.utils.Retry +import io.modelcontextprotocol.kotlin.sdk.server.Server +import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions +import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport +import io.modelcontextprotocol.kotlin.sdk.server.mcp +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeout +import kotlinx.io.Sink +import kotlinx.io.Source +import kotlinx.io.asSink +import kotlinx.io.asSource +import kotlinx.io.buffered +import org.awaitility.kotlin.await +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import java.io.PipedInputStream +import java.io.PipedOutputStream +import kotlin.time.Duration.Companion.seconds +import io.ktor.server.cio.CIO as ServerCIO +import io.ktor.server.sse.SSE as ServerSSE + +@Retry(times = 3) +abstract class KotlinTestBase { + + protected val host = "localhost" + protected var port: Int = 0 + + protected lateinit var server: Server + protected lateinit var client: Client + protected lateinit var serverEngine: EmbeddedServer<*, *> + + // Transport selection + protected enum class TransportKind { SSE, STDIO } + protected open val transportKind: TransportKind = TransportKind.STDIO + + // STDIO-specific fields + private var stdioServerTransport: StdioServerTransport? = null + private var stdioClientInput: Source? = null + private var stdioClientOutput: Sink? = null + + protected abstract fun configureServerCapabilities(): ServerCapabilities + protected abstract fun configureServer() + + @BeforeEach + fun setUp() { + setupServer() + if (transportKind == TransportKind.SSE) { + await + .ignoreExceptions() + .until { + port = runBlocking { serverEngine.engine.resolvedConnectors().first().port } + port != 0 + } + } + runBlocking { + setupClient() + } + } + + protected suspend fun setupClient() { + when (transportKind) { + TransportKind.SSE -> { + val transport = SseClientTransport( + HttpClient(CIO) { + install(SSE) + }, + "http://$host:$port", + ) + client = Client( + Implementation("test", "1.0"), + ) + client.connect(transport) + } + + TransportKind.STDIO -> { + val input = checkNotNull(stdioClientInput) { "STDIO client input not initialized" } + val output = checkNotNull(stdioClientOutput) { "STDIO client output not initialized" } + val transport = StdioClientTransport( + input = input, + output = output, + ) + client = Client( + Implementation("test", "1.0"), + ) + client.connect(transport) + } + } + } + + protected fun setupServer() { + val capabilities = configureServerCapabilities() + + server = Server( + Implementation(name = "test-server", version = "1.0"), + ServerOptions(capabilities = capabilities), + ) + + configureServer() + + if (transportKind == TransportKind.SSE) { + serverEngine = embeddedServer(ServerCIO, host = host, port = port) { + install(ServerSSE) + routing { + mcp { server } + } + }.start(wait = false) + } else { + // Create in-memory stdio pipes: client->server and server->client + val clientToServerOut = PipedOutputStream() + val clientToServerIn = PipedInputStream(clientToServerOut) + + val serverToClientOut = PipedOutputStream() + val serverToClientIn = PipedInputStream(serverToClientOut) + + // Server transport reads from client and writes to client + val serverTransport = StdioServerTransport( + inputStream = clientToServerIn.asSource().buffered(), + outputStream = serverToClientOut.asSink().buffered(), + ) + stdioServerTransport = serverTransport + + // Prepare client-side streams for later client initialization + stdioClientInput = serverToClientIn.asSource().buffered() + stdioClientOutput = clientToServerOut.asSink().buffered() + + // Start server transport by connecting the server + runBlocking { + server.createSession(serverTransport) + } + } + } + + @AfterEach + fun tearDown() { + // close client + if (::client.isInitialized) { + try { + runBlocking { + withTimeout(3.seconds) { + client.close() + } + } + } catch (e: Exception) { + println("Warning: Error during client close: ${e.message}") + } + } + + // stop server + if (transportKind == TransportKind.SSE) { + if (::serverEngine.isInitialized) { + try { + serverEngine.stop(500, 1000) + } catch (e: Exception) { + println("Warning: Error during server stop: ${e.message}") + } + } + } else { + stdioServerTransport?.let { + try { + runBlocking { it.close() } + } catch (e: Exception) { + println("Warning: Error during stdio server stop: ${e.message}") + } finally { + stdioServerTransport = null + stdioClientInput = null + stdioClientOutput = null + } + } + } + } +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaResourceIntegrationTestSse.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaResourceIntegrationTestSse similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaResourceIntegrationTestSse.kt rename to kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaResourceIntegrationTestSse diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/PromptIntegrationTestSse.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/PromptIntegrationTestSse.kt new file mode 100644 index 0000000..10cde27 --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/PromptIntegrationTestSse.kt @@ -0,0 +1,7 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.sse + +import io.modelcontextprotocol.kotlin.sdk.integration.kotlin.AbstractPromptIntegrationTest + +class SchemaPromptIntegrationTestSse : AbstractPromptIntegrationTest() { + override val transportKind: TransportKind = TransportKind.SSE +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/ResourceIntegrationTestSse.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/ResourceIntegrationTestSse.kt new file mode 100644 index 0000000..bf1240d --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/ResourceIntegrationTestSse.kt @@ -0,0 +1,7 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.sse + +import io.modelcontextprotocol.kotlin.sdk.integration.kotlin.AbstractResourceIntegrationTest + +class ResourceIntegrationTestSse : AbstractResourceIntegrationTest() { + override val transportKind: TransportKind = TransportKind.SSE +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/ToolIntegrationTestSse.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/ToolIntegrationTestSse.kt new file mode 100644 index 0000000..dd007c6 --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/ToolIntegrationTestSse.kt @@ -0,0 +1,7 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.sse + +import io.modelcontextprotocol.kotlin.sdk.integration.kotlin.AbstractToolIntegrationTest + +class ToolIntegrationTestSse : AbstractToolIntegrationTest() { + override val transportKind: TransportKind = TransportKind.SSE +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/PromptIntegrationTestStdio.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/PromptIntegrationTestStdio.kt new file mode 100644 index 0000000..88be1e8 --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/PromptIntegrationTestStdio.kt @@ -0,0 +1,7 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.stdio + +import io.modelcontextprotocol.kotlin.sdk.integration.kotlin.AbstractPromptIntegrationTest + +class PromptIntegrationTestStdio : AbstractPromptIntegrationTest() { + override val transportKind: TransportKind = TransportKind.STDIO +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/ResourceIntegrationTestStdio.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/ResourceIntegrationTestStdio.kt new file mode 100644 index 0000000..88eca7b --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/ResourceIntegrationTestStdio.kt @@ -0,0 +1,7 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.stdio + +import io.modelcontextprotocol.kotlin.sdk.integration.kotlin.AbstractResourceIntegrationTest + +class ResourceIntegrationTestStdio : AbstractResourceIntegrationTest() { + override val transportKind: TransportKind = TransportKind.STDIO +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/ToolIntegrationTestStdio.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/ToolIntegrationTestStdio.kt new file mode 100644 index 0000000..673d44b --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/ToolIntegrationTestStdio.kt @@ -0,0 +1,7 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.stdio + +import io.modelcontextprotocol.kotlin.sdk.integration.kotlin.AbstractToolIntegrationTest + +class ToolIntegrationTestStdio : AbstractToolIntegrationTest() { + override val transportKind: TransportKind = TransportKind.STDIO +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/WebSocketIntegrationTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/WebSocketIntegrationTest.kt new file mode 100644 index 0000000..ab0d848 --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/WebSocketIntegrationTest.kt @@ -0,0 +1,199 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.websocket + +import io.ktor.client.HttpClient +import io.ktor.server.application.install +import io.ktor.server.cio.CIOApplicationEngine +import io.ktor.server.engine.EmbeddedServer +import io.ktor.server.engine.embeddedServer +import io.ktor.server.routing.routing +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.client.mcpWebSocketTransport +import io.modelcontextprotocol.kotlin.sdk.server.Server +import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions +import io.modelcontextprotocol.kotlin.sdk.server.mcpWebSocket +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.PromptArgument +import io.modelcontextprotocol.kotlin.sdk.types.PromptMessage +import io.modelcontextprotocol.kotlin.sdk.types.Role +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.withContext +import kotlin.test.Test +import kotlin.test.assertTrue +import kotlin.time.Duration.Companion.seconds +import io.ktor.client.engine.cio.CIO as ClientCIO +import io.ktor.client.plugins.websocket.WebSockets as ClientWebSocket +import io.ktor.server.cio.CIO as ServerCIO +import io.ktor.server.websocket.WebSockets as ServerWebSockets + +class WebSocketIntegrationTest { + + @Test + fun `client should be able to connect to websocket server 2`() = runTest(timeout = 5.seconds) { + var server: EmbeddedServer? = null + var client: Client? = null + + try { + withContext(Dispatchers.Default) { + server = initServer() + val port = server.engine.resolvedConnectors().first().port + client = initClient(serverPort = port) + } + } finally { + client?.close() + server?.stop(1000, 2000) + } + } + + /** + * Test Case #1: One opened connection, a client gets a prompt + * + * 1. Open WebSocket from Client A. + * 2. Send a POST request from Client A to POST /prompts/get. + * 3. Observe that Client A receives a response related to it. + */ + @Test + fun `single websocket connection`() = runTest(timeout = 5.seconds) { + var server: EmbeddedServer? = null + var client: Client? = null + + try { + withContext(Dispatchers.Default) { + server = initServer() + val port = server.engine.resolvedConnectors().first().port + client = initClient("Client A", port) + + val promptA = getPrompt(client, "Client A") + assertTrue { "Client A" in promptA } + } + } finally { + client?.close() + server?.stop(1000, 2000) + } + } + + /** + * Test Case #1: Two open connections, each client gets a client-specific prompt + * + * 1. Open WebSocket connection #1 from Client A and note the sessionId= value. + * 2. Open WebSocket connection #2 from Client B and note the sessionId= value. + * 3. Send a POST request to POST /message with the corresponding sessionId#1. + * 4. Observe that Client B (connection #2) receives a response related to sessionId#1. + */ + @Test + fun `multiple websocket connections`() = runTest(timeout = 5.seconds) { + var server: EmbeddedServer? = null + var clientA: Client? = null + var clientB: Client? = null + + try { + withContext(Dispatchers.Default) { + server = initServer() + val port = server.engine.resolvedConnectors().first().port + clientA = initClient("Client A", port) + clientB = initClient("Client B", port) + + // Step 3: Send a prompt request from Client A + val promptA = getPrompt(clientA, "Client A") + // Step 4: Send a prompt request from Client B + val promptB = getPrompt(clientB, "Client B") + + assertTrue { "Client A" in promptA } + assertTrue { "Client B" in promptB } + } + } finally { + clientA?.close() + clientB?.close() + server?.stop(1000, 2000) + } + } + + private suspend fun initClient(name: String = "", serverPort: Int): Client { + val client = Client( + Implementation(name = name, version = "1.0.0"), + ) + + val httpClient = HttpClient(ClientCIO) { + install(ClientWebSocket) + } + + // Create a transport wrapper that captures the session ID and received messages + val transport = httpClient.mcpWebSocketTransport { + url { + host = URL + port = serverPort + } + } + + client.connect(transport) + + return client + } + + private fun initServer(): EmbeddedServer { + val server = Server( + Implementation(name = "websocket-server", version = "1.0.0"), + ServerOptions( + capabilities = ServerCapabilities(prompts = ServerCapabilities.Prompts(listChanged = true)), + ), + ) + + server.addPrompt( + name = "prompt", + description = "Prompt description", + arguments = listOf( + PromptArgument( + name = "client", + description = "Client name who requested a prompt", + required = true, + ), + ), + ) { request -> + GetPromptResult( + messages = listOf( + PromptMessage( + role = Role.User, + content = TextContent("Prompt for client ${request.params.arguments?.get("client")}"), + ), + ), + description = "Prompt for ${request.params.name}", + ) + } + + val ktorServer = embeddedServer(ServerCIO, host = URL, port = PORT) { + install(ServerWebSockets) + routing { + mcpWebSocket(block = { server }) + } + } + + return ktorServer.start(wait = false) + } + + /** + * Retrieves a prompt result using the provided client and client name. + */ + private suspend fun getPrompt(client: Client, clientName: String): String { + val response = client.getPrompt( + GetPromptRequest( + GetPromptRequestParams( + name = "prompt", + arguments = mapOf("client" to clientName), + ), + ), + ) + + return (response.messages.first().content as? TextContent)?.text + ?: error("Failed to receive prompt for Client $clientName") + } + + companion object { + private const val PORT = 0 + private const val URL = "127.0.0.1" + } +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/AbstractSseIntegrationTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/AbstractSseIntegrationTest.kt new file mode 100644 index 0000000..8f6aed6 --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/AbstractSseIntegrationTest.kt @@ -0,0 +1,105 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.sse + +import io.ktor.client.HttpClient +import io.ktor.client.plugins.sse.SSE +import io.ktor.server.application.install +import io.ktor.server.cio.CIOApplicationEngine +import io.ktor.server.engine.EmbeddedServer +import io.ktor.server.engine.embeddedServer +import io.ktor.server.routing.routing +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.client.mcpSseTransport +import io.modelcontextprotocol.kotlin.sdk.server.Server +import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions +import io.modelcontextprotocol.kotlin.sdk.server.mcp +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.PromptArgument +import io.modelcontextprotocol.kotlin.sdk.types.PromptMessage +import io.modelcontextprotocol.kotlin.sdk.types.Role +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.ktor.client.engine.cio.CIO as ClientCIO +import io.ktor.server.cio.CIO as ServerCIO +import io.ktor.server.sse.SSE as ServerSSE + +open class AbstractSseIntegrationTest { + + suspend fun EmbeddedServer<*, *>.actualPort() = engine.resolvedConnectors().single().port + + suspend fun initTestClient(serverPort: Int, name: String? = null): Client { + val client = Client( + Implementation(name = name ?: DEFAULT_CLIENT_NAME, version = VERSION), + ) + + val httpClient = HttpClient(ClientCIO) { + install(SSE) + } + + // Create a transport wrapper that captures the session ID and received messages + val transport = httpClient.mcpSseTransport { + url { + host = URL + port = serverPort + } + } + + client.connect(transport) + + return client + } + + suspend fun initTestServer( + name: String? = null, + ): EmbeddedServer { + val server = Server( + Implementation(name = name ?: DEFAULT_SERVER_NAME, version = VERSION), + ServerOptions( + capabilities = ServerCapabilities(prompts = ServerCapabilities.Prompts(listChanged = true)), + ), + ) { + addPrompt( + name = "prompt", + description = "Prompt description", + arguments = listOf( + PromptArgument( + name = "client", + description = "Client name who requested a prompt", + required = true, + ), + ), + ) { request -> + GetPromptResult( + messages = listOf( + PromptMessage( + role = Role.User, + content = TextContent("Prompt for client ${request.params.arguments?.get("client")}"), + ), + ), + description = "Prompt for ${request.params.name}", + ) + } + } + + val ktorServer = embeddedServer( + ServerCIO, + host = URL, + port = PORT, + ) { + install(ServerSSE) + routing { + mcp { server } + } + } + + return ktorServer.startSuspend(wait = false) + } + + companion object { + private const val DEFAULT_CLIENT_NAME = "sse-test-client" + private const val DEFAULT_SERVER_NAME = "sse-test-server" + private const val VERSION = "1.0.0" + private const val URL = "127.0.0.1" + private const val PORT = 0 + } +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/SseIntegrationTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/SseIntegrationTest.kt new file mode 100644 index 0000000..0826eb2 --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/SseIntegrationTest.kt @@ -0,0 +1,114 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.sse + +import io.ktor.server.cio.CIOApplicationEngine +import io.ktor.server.engine.EmbeddedServer +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.withContext +import kotlin.test.Test +import kotlin.test.assertTrue +import kotlin.time.Duration.Companion.seconds + +class SseIntegrationTest : AbstractSseIntegrationTest() { + + @Test + fun `client should be able to connect to sse server`() = runTest(timeout = 5.seconds) { + var server: EmbeddedServer? = null + var client: Client? = null + + try { + withContext(Dispatchers.Default) { + server = initTestServer() + val port = server.engine.resolvedConnectors().single().port + client = initTestClient(serverPort = port) + } + } finally { + client?.close() + server?.stopSuspend(1000, 2000) + } + } + + /** + * Test Case #1: One opened connection, a client gets a prompt + * + * 1. Open SSE from Client A. + * 2. Send a POST request from Client A to POST /prompts/get. + * 3. Observe that Client A receives a response related to it. + */ + @Test + fun `single sse connection`() = runTest(timeout = 5.seconds) { + var server: EmbeddedServer? = null + var client: Client? = null + try { + withContext(Dispatchers.Default) { + server = initTestServer() + val port = server.engine.resolvedConnectors().single().port + client = initTestClient(port, "Client A") + + val promptA = getPrompt(client, "Client A") + assertTrue { "Client A" in promptA } + } + } finally { + client?.close() + server?.stopSuspend(1000, 2000) + } + } + + /** + * Test Case #1: Two open connections, each client gets a client-specific prompt + * + * 1. Open SSE connection #1 from Client A and note the sessionId= value. + * 2. Open SSE connection #2 from Client B and note the sessionId= value. + * 3. Send a POST request to POST /message with the corresponding sessionId#1. + * 4. Observe that Client B (connection #2) receives a response related to sessionId#1. + */ + @Test + fun `multiple sse connections`() = runTest(timeout = 5.seconds) { + var server: EmbeddedServer? = null + var clientA: Client? = null + var clientB: Client? = null + + try { + withContext(Dispatchers.Default) { + server = initTestServer() + val port = server.engine.resolvedConnectors().first().port + + clientA = initTestClient(port, "Client A") + clientB = initTestClient(port, "Client B") + + // Step 3: Send a prompt request from Client A + val promptA = getPrompt(clientA, "Client A") + // Step 4: Send a prompt request from Client B + val promptB = getPrompt(clientB, "Client B") + + assertTrue { "Client A" in promptA } + assertTrue { "Client B" in promptB } + } + } finally { + clientA?.close() + clientB?.close() + server?.stopSuspend(1000, 2000) + } + } + + /** + * Retrieves a prompt result using the provided client and client name. + */ + private suspend fun getPrompt(client: Client, clientName: String): String { + val response = client.getPrompt( + GetPromptRequest( + GetPromptRequestParams( + name = "prompt", + arguments = mapOf("client" to clientName), + ), + ), + ) + + return (response.messages.first().content as? TextContent)?.text + ?: error("Failed to receive prompt for Client $clientName") + } +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractKotlinClientTsServerTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractKotlinClientTsServerTest.kt new file mode 100644 index 0000000..5d04512 --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractKotlinClientTsServerTest.kt @@ -0,0 +1,77 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.typescript + +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Timeout +import java.util.concurrent.TimeUnit +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +abstract class AbstractKotlinClientTsServerTest : TsTestBase() { + protected abstract suspend fun useClient(block: suspend (Client) -> T): T + + @Test + @Timeout(30, unit = TimeUnit.SECONDS) + fun connectsAndPings() = runBlocking(Dispatchers.IO) { + useClient { client -> + assertNotNull(client, "Client should be initialized") + val ping = client.ping() + assertNotNull(ping, "Ping result should not be null") + val serverImpl = client.serverVersion + assertNotNull(serverImpl, "Server implementation should not be null") + println("Connected to TypeScript server: ${serverImpl.name} v${serverImpl.version}") + } + } + + @Test + @Timeout(30, unit = TimeUnit.SECONDS) + fun listsTools() = runBlocking(Dispatchers.IO) { + useClient { client -> + val result = client.listTools() + assertNotNull(result, "Tools list should not be null") + assertTrue(result.tools.isNotEmpty(), "Tools list should not be empty") + val toolNames = result.tools.map { it.name } + assertTrue("greet" in toolNames, "Greet tool should be available") + assertTrue("multi-greet" in toolNames, "Multi-greet tool should be available") + // Some tests also check collect-user-info; keep base minimal and non-breaking + } + } + + @Test + @Timeout(30, unit = TimeUnit.SECONDS) + fun callGreet() = runBlocking(Dispatchers.IO) { + useClient { client -> + val testName = "TestUser" + val arguments = mapOf("name" to testName) + val result = client.callTool("greet", arguments) + assertNotNull(result, "Tool call result should not be null") + val callResult = result + val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent + assertNotNull(textContent, "Text content should be present in the result") + assertEquals("Hello, $testName!", textContent.text) + } + } + + @Test + @Timeout(30, unit = TimeUnit.SECONDS) + fun multipleClients() = runBlocking(Dispatchers.IO) { + useClient { client1 -> + useClient { client2 -> + val tools1 = client1.listTools() + val tools2 = client2.listTools() + assertTrue(tools1.tools.isNotEmpty(), "Tools list for first client should not be empty") + assertTrue(tools2.tools.isNotEmpty(), "Tools list for second client should not be empty") + val toolNames1 = tools1.tools.map { it.name } + val toolNames2 = tools2.tools.map { it.name } + assertTrue("greet" in toolNames1, "Greet tool should be available to first client") + assertTrue("multi-greet" in toolNames1, "Multi-greet tool should be available to first client") + assertTrue("greet" in toolNames2, "Greet tool should be available to second client") + assertTrue("multi-greet" in toolNames2, "Multi-greet tool should be available to second client") + } + } + } +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/OldSchemaAbstractKotlinClientTsServerTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/OldSchemaAbstractKotlinClientTsServerTest.kt index 5d0406c..64e114f 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/OldSchemaAbstractKotlinClientTsServerTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/OldSchemaAbstractKotlinClientTsServerTest.kt @@ -11,7 +11,7 @@ import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue -abstract class OldSchemaAbstractKotlinClientTsServerTest : TsTestBase() { +abstract class OldSchemaAbstractKotlinClientTsServerTest : OldSchemaTsTestBase() { protected abstract suspend fun useClient(block: suspend (Client) -> T): T @Test diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/OldSchemaTsTestBase.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/OldSchemaTsTestBase.kt new file mode 100644 index 0000000..98d83d3 --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/OldSchemaTsTestBase.kt @@ -0,0 +1,455 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.typescript + +import io.ktor.client.HttpClient +import io.ktor.client.engine.cio.CIO +import io.ktor.client.plugins.sse.SSE +import io.modelcontextprotocol.kotlin.sdk.Implementation +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport +import io.modelcontextprotocol.kotlin.sdk.client.mcpStreamableHttp +import io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse.KotlinServerForTsClient +import io.modelcontextprotocol.kotlin.sdk.integration.utils.Retry +import io.modelcontextprotocol.kotlin.sdk.server.Server +import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeout +import kotlinx.io.Sink +import kotlinx.io.Source +import kotlinx.io.asSink +import kotlinx.io.asSource +import kotlinx.io.buffered +import org.awaitility.kotlin.await +import org.junit.jupiter.api.BeforeAll +import java.io.BufferedReader +import java.io.File +import java.io.InputStreamReader +import java.net.ServerSocket +import java.net.Socket +import java.util.concurrent.TimeUnit +import kotlin.io.path.createTempDirectory +import kotlin.time.Duration.Companion.seconds + +enum class OldSchemaTransportKind { SSE, STDIO, DEFAULT } + +@Retry(times = 3) +abstract class OldSchemaTsTestBase { + + protected open val transportKind: OldSchemaTransportKind = OldSchemaTransportKind.DEFAULT + + protected val projectRoot: File get() = File(System.getProperty("user.dir")) + protected val tsClientDir: File + get() { + val base = File( + projectRoot, + "src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript", + ) + + // Allow override via system property for CI: -Dts.transport=stdio|sse + val fromProp = System.getProperty("ts.transport")?.lowercase() + val overrideSubDir = when (fromProp) { + "stdio" -> "stdio" + "sse" -> "sse" + else -> null + } + + val subDirName = overrideSubDir ?: when (transportKind) { + OldSchemaTransportKind.STDIO -> "stdio" + OldSchemaTransportKind.SSE -> "sse" + OldSchemaTransportKind.DEFAULT -> null + } + if (subDirName != null) { + val sub = File(base, subDirName) + if (sub.exists()) return sub + } + return base + } + + companion object { + @JvmStatic + private val tempRootDir: File = createTempDirectory("typescript-sdk-").toFile().apply { deleteOnExit() } + + @JvmStatic + protected val sdkDir: File = File(tempRootDir, "typescript-sdk") + + @JvmStatic + @BeforeAll + fun setupTypeScriptSdk() { + println("Cloning TypeScript SDK repository") + + if (!sdkDir.exists()) { + val process = ProcessBuilder( + "git", + "clone", + "--depth", + "1", + "https://github.com/modelcontextprotocol/typescript-sdk.git", + sdkDir.absolutePath, + ) + .redirectErrorStream(true) + .start() + val exitCode = process.waitFor() + if (exitCode != 0) { + throw RuntimeException("Failed to clone TypeScript SDK repository: exit code $exitCode") + } + } + + println("Installing TypeScript SDK dependencies") + executeCommand("npm install", sdkDir, allowFailure = false, timeoutSeconds = null) + } + + @JvmStatic + protected fun killProcessOnPort(port: Int) { + val isWindows = System.getProperty("os.name").lowercase().contains("windows") + val killCommand = if (isWindows) { + "netstat -ano | findstr :$port | for /f \"tokens=5\" %a in ('more')" + + " do taskkill /F /PID %a 2>nul || echo No process found" + } else { + "lsof -ti:$port | xargs kill -9 2>/dev/null || true" + } + executeCommand(killCommand, File("."), allowFailure = true, timeoutSeconds = null) + } + + @JvmStatic + protected fun findFreePort(): Int { + ServerSocket(0).use { socket -> + return socket.localPort + } + } + + @JvmStatic + protected fun executeCommand( + command: String, + workingDir: File, + allowFailure: Boolean = false, + timeoutSeconds: Long? = null, + ): String { + if (!workingDir.exists()) { + if (!workingDir.mkdirs()) { + throw RuntimeException("Failed to create working directory: ${workingDir.absolutePath}") + } + } + + if (!workingDir.isDirectory || !workingDir.canRead()) { + throw RuntimeException("Working directory is not accessible: ${workingDir.absolutePath}") + } + + val isWindows = System.getProperty("os.name").lowercase().contains("windows") + val processBuilder = if (isWindows) { + ProcessBuilder() + .command("cmd.exe", "/c", "set TYPESCRIPT_SDK_DIR=${sdkDir.absolutePath} && $command") + } else { + ProcessBuilder() + .command("bash", "-c", "TYPESCRIPT_SDK_DIR='${sdkDir.absolutePath}' $command") + } + + val process = processBuilder + .directory(workingDir) + .redirectErrorStream(true) + .start() + + val output = StringBuilder() + BufferedReader(InputStreamReader(process.inputStream)).use { reader -> + var line: String? + while (reader.readLine().also { line = it } != null) { + println(line) + output.append(line).append("\n") + } + } + + if (timeoutSeconds == null) { + val exitCode = process.waitFor() + if (!allowFailure && exitCode != 0) { + throw RuntimeException( + "Command execution failed with exit code $exitCode: $command\n" + + "Working dir: ${workingDir.absolutePath}\nOutput:\n$output", + ) + } + } else { + process.waitFor(timeoutSeconds, TimeUnit.SECONDS) + } + + return output.toString() + } + } + + private fun waitForProcessTermination(process: Process, timeoutSeconds: Long): Boolean { + if (process.isAlive && !process.waitFor(timeoutSeconds, TimeUnit.SECONDS)) { + process.destroyForcibly() + process.waitFor(2, TimeUnit.SECONDS) + return false + } + return true + } + + private fun createProcessOutputReader(process: Process, prefix: String = "TS-SERVER"): Thread { + val outputReader = Thread { + try { + process.inputStream.bufferedReader().useLines { lines -> + for (line in lines) { + println("[$prefix] $line") + } + } + } catch (e: Exception) { + println("Warning: Error reading process output: ${e.message}") + } + } + outputReader.isDaemon = true + return outputReader + } + + private fun createProcessErrorReader(process: Process, prefix: String = "TS-SERVER"): Thread { + val errorReader = Thread { + try { + process.errorStream.bufferedReader().useLines { lines -> + for (line in lines) { + println("[$prefix][err] $line") + } + } + } catch (e: Exception) { + println("Warning: Error reading process error stream: ${e.message}") + } + } + errorReader.isDaemon = true + return errorReader + } + + protected fun waitForPort(host: String = "localhost", port: Int, timeoutSeconds: Long = 10): Boolean = try { + await.atMost(timeoutSeconds, TimeUnit.SECONDS) + .pollDelay(200, TimeUnit.MILLISECONDS) + .pollInterval(100, TimeUnit.MILLISECONDS) + .until { + try { + Socket(host, port).use { true } + } catch (_: Exception) { + false + } + } + true + } catch (_: Exception) { + false + } + + protected fun executeCommandAllowingFailure(command: String, workingDir: File, timeoutSeconds: Long = 20): String = + executeCommand(command, workingDir, allowFailure = true, timeoutSeconds = timeoutSeconds) + + protected fun startTypeScriptServer(port: Int): Process { + killProcessOnPort(port) + + if (!sdkDir.exists() || !sdkDir.isDirectory) { + throw IllegalStateException( + "TypeScript SDK directory does not exist or is not accessible: ${sdkDir.absolutePath}", + ) + } + + val isWindows = System.getProperty("os.name").lowercase().contains("windows") + val localServerPath = File(tsClientDir, "simpleStreamableHttp.ts").absolutePath + val processBuilder = if (isWindows) { + ProcessBuilder() + .command( + "cmd.exe", + "/c", + "set MCP_PORT=$port && set NODE_PATH=${sdkDir.absolutePath}\\node_modules && npx --prefix \"${sdkDir.absolutePath}\" tsx \"$localServerPath\"", + ) + } else { + ProcessBuilder() + .command( + "bash", + "-c", + "MCP_PORT=$port NODE_PATH='${sdkDir.absolutePath}/node_modules' npx --prefix '${sdkDir.absolutePath}' tsx \"$localServerPath\"", + ) + } + + processBuilder.environment()["TYPESCRIPT_SDK_DIR"] = sdkDir.absolutePath + + val process = processBuilder + .directory(tsClientDir) + .redirectErrorStream(true) + .start() + + createProcessOutputReader(process).start() + + if (!waitForPort(port = port, timeoutSeconds = 20)) { + throw IllegalStateException("TypeScript server did not become ready on localhost:$port within timeout") + } + return process + } + + protected fun stopProcess(process: Process, waitSeconds: Long = 3, name: String = "TypeScript server") { + process.destroy() + if (waitForProcessTermination(process, waitSeconds)) { + println("$name stopped gracefully") + } else { + println("$name did not stop gracefully, forced termination") + } + } + + // ===== SSE client helpers ===== + protected suspend fun newClient(serverUrl: String): Client = + HttpClient(CIO) { install(SSE) }.mcpStreamableHttp(serverUrl) + + protected suspend fun withClient(serverUrl: String, block: suspend (Client) -> T): T { + val client = newClient(serverUrl) + return try { + withTimeout(20.seconds) { block(client) } + } finally { + try { + withTimeout(3.seconds) { client.close() } + } catch (_: Exception) { + // ignore errors + } + } + } + + // ===== STDIO client + server helpers ===== + protected fun startTypeScriptServerStdio(): Process { + if (!sdkDir.exists() || !sdkDir.isDirectory) { + throw IllegalStateException( + "TypeScript SDK directory does not exist or is not accessible: ${sdkDir.absolutePath}", + ) + } + val isWindows = System.getProperty("os.name").lowercase().contains("windows") + val localServerPath = File(tsClientDir, "simpleStdio.ts").absolutePath + val processBuilder = if (isWindows) { + ProcessBuilder() + .command( + "cmd.exe", + "/c", + "set NODE_PATH=${sdkDir.absolutePath}\\node_modules && npx --prefix \"${sdkDir.absolutePath}\" tsx \"$localServerPath\"", + ) + } else { + ProcessBuilder() + .command( + "bash", + "-c", + "NODE_PATH='${sdkDir.absolutePath}/node_modules' npx --prefix '${sdkDir.absolutePath}' tsx \"$localServerPath\"", + ) + } + processBuilder.environment()["TYPESCRIPT_SDK_DIR"] = sdkDir.absolutePath + val process = processBuilder + .directory(tsClientDir) + .redirectErrorStream(false) + .start() + // For stdio transports, do NOT read from stdout (it's used for protocol). Read stderr for logs only. + createProcessErrorReader(process, prefix = "TS-SERVER-STDIO").start() + // Give the process a moment to start + await.atMost(2, TimeUnit.SECONDS) + .pollDelay(200, TimeUnit.MILLISECONDS) + .pollInterval(100, TimeUnit.MILLISECONDS) + .until { process.isAlive } + return process + } + + protected suspend fun newClientStdio(process: Process): Client { + val input: Source = process.inputStream.asSource().buffered() + val output: Sink = process.outputStream.asSink().buffered() + val transport = StdioClientTransport(input = input, output = output) + val client = Client(Implementation("test", "1.0")) + client.connect(transport) + return client + } + + protected suspend fun withClientStdio(block: suspend (Client, Process) -> T): T { + val proc = startTypeScriptServerStdio() + val client = newClientStdio(proc) + return try { + withTimeout(20.seconds) { block(client, proc) } + } finally { + try { + withTimeout(3.seconds) { client.close() } + } catch (_: Exception) { + } + try { + stopProcess(proc, name = "TypeScript stdio server") + } catch (_: Exception) { + } + } + } + + // ===== Helpers to run TypeScript client over STDIO against Kotlin server over STDIO ===== + protected fun runStdioClient(vararg args: String): String { + // Start Node stdio client (it will speak MCP over its stdout/stdin) + val isWindows = System.getProperty("os.name").lowercase().contains("windows") + val clientPath = File(tsClientDir, "myClient.ts").absolutePath + + val process = if (isWindows) { + ProcessBuilder() + .command( + "cmd.exe", + "/c", + ( + "set TYPESCRIPT_SDK_DIR=${sdkDir.absolutePath} && " + + "set NODE_PATH=${sdkDir.absolutePath}\\node_modules && " + + "npx --prefix \"${sdkDir.absolutePath}\" tsx \"$clientPath\" " + + args.joinToString(" ") + ), + ) + .directory(tsClientDir) + .redirectErrorStream(false) + .start() + } else { + ProcessBuilder() + .command( + "bash", + "-c", + ( + "TYPESCRIPT_SDK_DIR='${sdkDir.absolutePath}' " + + "NODE_PATH='${sdkDir.absolutePath}/node_modules' " + + "npx --prefix '${sdkDir.absolutePath}' tsx \"$clientPath\" " + + args.joinToString(" ") + ), + ) + .directory(tsClientDir) + .redirectErrorStream(false) + .start() + } + + // Create Kotlin server and attach stdio transport to the process streams + val server: Server = KotlinServerForTsClient().createMcpServer() + val transport = StdioServerTransport( + inputStream = process.inputStream.asSource().buffered(), + outputStream = process.outputStream.asSink().buffered(), + ) + + // Connect server in a background thread to avoid blocking + val serverThread = Thread { + try { + runBlocking { server.createSession(transport) } + } catch (e: Exception) { + println("[STDIO-SERVER] Error connecting: ${e.message}") + } + } + serverThread.isDaemon = true + serverThread.start() + + // Read ONLY stderr from client for human-readable output + val output = StringBuilder() + val errReader = Thread { + try { + process.errorStream.bufferedReader().useLines { lines -> + lines.forEach { line -> + println("[TS-CLIENT-STDIO][err] $line") + output.append(line).append('\n') + } + } + } catch (e: Exception) { + println("Warning: Error reading stdio client stderr: ${e.message}") + } + } + errReader.isDaemon = true + errReader.start() + + // Wait up to 25s for client to exit + val finished = process.waitFor(25, TimeUnit.SECONDS) + if (!finished) { + println("Stdio client did not finish in time; destroying") + process.destroyForcibly() + } + + try { + runBlocking { transport.close() } + } catch (_: Exception) { + } + + return output.toString() + } +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TsTestBase.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TsTestBase.kt index 21593fe..22af65a 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TsTestBase.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TsTestBase.kt @@ -3,7 +3,6 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript import io.ktor.client.HttpClient import io.ktor.client.engine.cio.CIO import io.ktor.client.plugins.sse.SSE -import io.modelcontextprotocol.kotlin.sdk.Implementation import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport import io.modelcontextprotocol.kotlin.sdk.client.mcpStreamableHttp @@ -11,6 +10,7 @@ import io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse.KotlinServe import io.modelcontextprotocol.kotlin.sdk.integration.utils.Retry import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport +import io.modelcontextprotocol.kotlin.sdk.types.Implementation import kotlinx.coroutines.withTimeout import kotlinx.io.Sink import kotlinx.io.Source diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerEdgeCasesTestSse.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerEdgeCasesTestSse.kt new file mode 100644 index 0000000..54c6e0f --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerEdgeCasesTestSse.kt @@ -0,0 +1,215 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse + +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind +import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TsTestBase +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeout +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Timeout +import java.util.concurrent.TimeUnit +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue +import kotlin.time.Duration.Companion.seconds + +class KotlinClientTsServerEdgeCasesTestSse : TsTestBase() { + + override val transportKind = TransportKind.SSE + + private var port: Int = 0 + private val host = "localhost" + private lateinit var serverUrl: String + + private lateinit var client: Client + private lateinit var tsServerProcess: Process + + @BeforeEach + fun setUp() { + port = findFreePort() + serverUrl = "http://$host:$port/mcp" + tsServerProcess = startTypeScriptServer(port) + println("TypeScript server started on port $port") + } + + @AfterEach + fun tearDown() { + if (::client.isInitialized) { + try { + runBlocking { + withTimeout(3.seconds) { + client.close() + } + } + } catch (e: Exception) { + println("Warning: Error during client close: ${e.message}") + } + } + + if (::tsServerProcess.isInitialized) { + try { + println("Stopping TypeScript server") + stopProcess(tsServerProcess) + } catch (e: Exception) { + println("Warning: Error during TypeScript server stop: ${e.message}") + } + } + } + + @Test + @Timeout(30, unit = TimeUnit.SECONDS) + fun testNonExistentTool(): Unit = runBlocking(Dispatchers.IO) { + withClient(serverUrl) { client -> + val nonExistentToolName = "non-existent-tool" + val arguments = mapOf("name" to "TestUser") + + val result = client.callTool(nonExistentToolName, arguments) + assertNotNull(result, "Tool call result should not be null") + + assertTrue(result.isError ?: false, "isError should be true for non-existent tool") + + val textContent = result.content.firstOrNull { it is TextContent } as? TextContent + assertNotNull(textContent, "Error content should be present in the result") + + val errorText = textContent.text + assertTrue( + errorText.contains("non-existent-tool") && errorText.contains("not found"), + "Error message should indicate the tool was not found", + ) + } + } + + @Test + @Timeout(30, unit = TimeUnit.SECONDS) + fun testSpecialCharactersInArguments(): Unit = runBlocking(Dispatchers.IO) { + withClient(serverUrl) { client -> + val specialChars = "!@#$%^&*()_+{}[]|\\:;\"'<>,.?/" + val arguments = mapOf("name" to specialChars) + + val result = client.callTool("greet", arguments) + assertNotNull(result, "Tool call result should not be null") + + val textContent = result.content.firstOrNull { it is TextContent } as? TextContent + assertNotNull(textContent, "Text content should be present in the result") + + val text = textContent.text + assertTrue( + text.contains(specialChars), + "Tool response should contain the special characters", + ) + } + } + + @Test + @Timeout(30, unit = TimeUnit.SECONDS) + fun testLargePayload(): Unit = runBlocking(Dispatchers.IO) { + withClient(serverUrl) { client -> + val largeName = "A".repeat(10 * 1024) + val arguments = mapOf("name" to largeName) + + val result = client.callTool("greet", arguments) + assertNotNull(result, "Tool call result should not be null") + + val textContent = result.content.firstOrNull { it is TextContent } as? TextContent + assertNotNull(textContent, "Text content should be present in the result") + + val text = textContent.text + assertTrue( + text.contains("Hello,") && text.contains("A"), + "Tool response should contain the greeting with the large name", + ) + } + } + + @Test + @Timeout(60, unit = TimeUnit.SECONDS) + fun testConcurrentRequests(): Unit = runBlocking(Dispatchers.IO) { + withClient(serverUrl) { client -> + val concurrentCount = 5 + val responses = coroutineScope { + val results = (1..concurrentCount).map { i -> + async { + val name = "ConcurrentClient$i" + val arguments = mapOf("name" to name) + + val result = client.callTool("greet", arguments) + assertNotNull(result, "Tool call result should not be null for client $i") + + val textContent = result.content.firstOrNull { it is TextContent } as? TextContent + assertNotNull(textContent, "Text content should be present for client $i") + + textContent.text + } + } + results.awaitAll() + } + + for (i in 1..concurrentCount) { + val expectedName = "ConcurrentClient$i" + val matchingResponses = responses.filter { it.contains("Hello, $expectedName!") } + assertEquals( + 1, + matchingResponses.size, + "Should have exactly one response for $expectedName", + ) + } + } + } + + @Test + @Timeout(30, unit = TimeUnit.SECONDS) + fun testInvalidArguments(): Unit = runBlocking(Dispatchers.IO) { + withClient(serverUrl) { client -> + val invalidArguments = mapOf( + "name" to JsonObject(mapOf("nested" to JsonPrimitive("value"))), + ) + + val result = client.callTool("greet", invalidArguments) + assertNotNull(result, "Tool call result should not be null") + + assertTrue(result.isError ?: false, "isError should be true for invalid arguments") + + val textContent = result.content.firstOrNull { it is TextContent } as? TextContent + assertNotNull(textContent, "Error content should be present in the result") + + val errorText = textContent.text + assertTrue( + errorText.contains("Invalid arguments") && errorText.contains("greet"), + "Error message should indicate invalid arguments for tool greet", + ) + } + } + + @Test + @Timeout(30, unit = TimeUnit.SECONDS) + fun testMultipleToolCalls(): Unit = runBlocking(Dispatchers.IO) { + withClient(serverUrl) { client -> + repeat(10) { i -> + val name = "SequentialClient$i" + val arguments = mapOf("name" to name) + + val result = client.callTool("greet", arguments) + assertNotNull(result, "Tool call result should not be null for call $i") + + val callResult = result + val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent + assertNotNull(textContent, "Text content should be present for call $i") + + assertEquals( + "Hello, $name!", + textContent.text, + "Tool response should contain the greeting with the provided name", + ) + } + } + } +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerTestSse.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerTestSse.kt index 51fd2bf..95a80f0 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerTestSse.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerTestSse.kt @@ -1,14 +1,14 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.OldSchemaAbstractKotlinClientTsServerTest +import io.modelcontextprotocol.kotlin.sdk.integration.typescript.AbstractKotlinClientTsServerTest import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind import kotlinx.coroutines.withTimeout import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import kotlin.time.Duration.Companion.seconds -class KotlinClientTsServerTestSse : OldSchemaAbstractKotlinClientTsServerTest() { +class KotlinClientTsServerTestSse : AbstractKotlinClientTsServerTest() { override val transportKind = TransportKind.SSE diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt new file mode 100644 index 0000000..3aa36bc --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt @@ -0,0 +1,464 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse + +import io.github.oshai.kotlinlogging.KotlinLogging +import io.ktor.http.ContentType +import io.ktor.http.HttpStatusCode +import io.ktor.server.application.ApplicationCall +import io.ktor.server.cio.CIO +import io.ktor.server.engine.EmbeddedServer +import io.ktor.server.engine.embeddedServer +import io.ktor.server.request.header +import io.ktor.server.request.receiveText +import io.ktor.server.response.header +import io.ktor.server.response.respond +import io.ktor.server.response.respondText +import io.ktor.server.response.respondTextWriter +import io.ktor.server.routing.delete +import io.ktor.server.routing.get +import io.ktor.server.routing.post +import io.ktor.server.routing.routing +import io.modelcontextprotocol.kotlin.sdk.server.Server +import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions +import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport +import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCError +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse +import io.modelcontextprotocol.kotlin.sdk.types.McpJson +import io.modelcontextprotocol.kotlin.sdk.types.PromptArgument +import io.modelcontextprotocol.kotlin.sdk.types.PromptMessage +import io.modelcontextprotocol.kotlin.sdk.types.RPCError +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult +import io.modelcontextprotocol.kotlin.sdk.types.RequestId +import io.modelcontextprotocol.kotlin.sdk.types.Role +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents +import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeoutOrNull +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonNull +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.contentOrNull +import kotlinx.serialization.json.decodeFromJsonElement +import kotlinx.serialization.json.jsonPrimitive +import java.util.UUID +import java.util.concurrent.ConcurrentHashMap + +private val logger = KotlinLogging.logger {} + +class KotlinServerForTsClient { + private val serverTransports = ConcurrentHashMap() + private val jsonFormat = Json { ignoreUnknownKeys = true } + private var server: EmbeddedServer<*, *>? = null + + fun start(port: Int = 3000) { + logger.info { "Starting HTTP server on port $port" } + + server = embeddedServer(CIO, port = port) { + routing { + get("/mcp") { + val sessionId = call.request.header("mcp-session-id") + if (sessionId == null) { + call.respond(HttpStatusCode.BadRequest, "Missing mcp-session-id header") + return@get + } + val transport = serverTransports[sessionId] + if (transport == null) { + call.respond(HttpStatusCode.BadRequest, "Invalid mcp-session-id") + return@get + } + transport.stream(call) + } + + post("/mcp") { + val sessionId = call.request.header("mcp-session-id") + val requestBody = call.receiveText() + + logger.debug { "Received request with sessionId: $sessionId" } + logger.trace { "Request body: $requestBody" } + + val jsonElement = try { + jsonFormat.parseToJsonElement(requestBody) + } catch (e: Exception) { + logger.error(e) { "Failed to parse request body as JSON" } + call.respond( + HttpStatusCode.BadRequest, + jsonFormat.encodeToString( + JsonObject.serializer(), + JsonObject( + mapOf( + "jsonrpc" to JsonPrimitive("2.0"), + "error" to JsonObject( + mapOf( + "code" to JsonPrimitive(-32700), + "message" to JsonPrimitive("Parse error: ${e.message}"), + ), + ), + "id" to JsonNull, + ), + ), + ), + ) + return@post + } + + if (sessionId != null && serverTransports.containsKey(sessionId)) { + logger.debug { "Using existing transport for session: $sessionId" } + val transport = serverTransports[sessionId]!! + transport.handleRequest(call, jsonElement) + } else { + if (isInitializeRequest(jsonElement)) { + val newSessionId = UUID.randomUUID().toString() + logger.info { "Creating new session with ID: $newSessionId" } + + val transport = HttpServerTransport(newSessionId) + + serverTransports[newSessionId] = transport + + val mcpServer = createMcpServer() + + call.response.header("mcp-session-id", newSessionId) + + val serverThread = Thread { + runBlocking { + mcpServer.createSession(transport) + } + } + serverThread.start() + + Thread.sleep(500) + + transport.handleRequest(call, jsonElement) + } else { + logger.warn { "Invalid request: no session ID or not an initialization request" } + call.respond( + HttpStatusCode.BadRequest, + jsonFormat.encodeToString( + JsonObject.serializer(), + JsonObject( + mapOf( + "jsonrpc" to JsonPrimitive("2.0"), + "error" to JsonObject( + mapOf( + "code" to JsonPrimitive(-32000), + "message" to + JsonPrimitive("Bad Request: No valid session ID provided"), + ), + ), + "id" to JsonNull, + ), + ), + ), + ) + } + } + } + + delete("/mcp") { + val sessionId = call.request.header("mcp-session-id") + if (sessionId != null && serverTransports.containsKey(sessionId)) { + logger.info { "Terminating session: $sessionId" } + val transport = serverTransports[sessionId]!! + serverTransports.remove(sessionId) + runBlocking { + transport.close() + } + call.respond(HttpStatusCode.OK) + } else { + logger.warn { "Invalid session termination request: $sessionId" } + call.respond(HttpStatusCode.BadRequest, "Invalid or missing session ID") + } + } + } + } + + server?.start(wait = false) + } + + fun stop() { + logger.info { "Stopping HTTP server" } + server?.stop(500, 1000) + server = null + } + + fun createMcpServer(): Server { + val server = Server( + Implementation( + name = "kotlin-http-server", + version = "1.0.0", + ), + ServerOptions( + capabilities = ServerCapabilities( + prompts = ServerCapabilities.Prompts(listChanged = true), + resources = ServerCapabilities.Resources(subscribe = true, listChanged = true), + tools = ServerCapabilities.Tools(listChanged = true), + ), + ), + ) + + server.addTool( + name = "greet", + description = "A simple greeting tool", + inputSchema = ToolSchema( + properties = buildJsonObject { + put( + "name", + buildJsonObject { + put("type", JsonPrimitive("string")) + put("description", JsonPrimitive("Name to greet")) + }, + ) + }, + required = listOf("name"), + ), + ) { request -> + val name = (request.params.arguments?.get("name") as? JsonPrimitive)?.content ?: "World" + CallToolResult( + content = listOf(TextContent("Hello, $name!")), + structuredContent = buildJsonObject { + put("greeting", JsonPrimitive("Hello, $name!")) + }, + ) + } + + server.addTool( + name = "multi-greet", + description = "A greeting tool that sends multiple notifications", + inputSchema = ToolSchema( + properties = buildJsonObject { + put( + "name", + buildJsonObject { + put("type", JsonPrimitive("string")) + put("description", JsonPrimitive("Name to greet")) + }, + ) + }, + required = listOf("name"), + ), + ) { request -> + val name = (request.params.arguments?.get("name") as? JsonPrimitive)?.content ?: "World" + + CallToolResult( + content = listOf(TextContent("Multiple greetings sent to $name!")), + structuredContent = buildJsonObject { + put("greeting", JsonPrimitive("Multiple greetings sent to $name!")) + put("notificationCount", JsonPrimitive(3)) + }, + ) + } + + server.addPrompt( + name = "greeting-template", + description = "A simple greeting prompt template", + arguments = listOf( + PromptArgument( + name = "name", + description = "Name to include in greeting", + required = true, + ), + ), + ) { request -> + GetPromptResult( + messages = listOf( + PromptMessage( + role = Role.User, + content = TextContent( + "Please greet ${request.params.arguments?.get("name") ?: "someone"} in a friendly manner.", + ), + ), + ), + description = "Greeting for ${request.params.name}", + ) + } + + server.addResource( + uri = "https://example.com/greetings/default", + name = "Default Greeting", + description = "A simple greeting resource", + mimeType = "text/plain", + ) { request -> + ReadResourceResult( + contents = listOf( + TextResourceContents("Hello, world!", request.params.uri, "text/plain"), + ), + ) + } + + return server + } + + private fun isInitializeRequest(json: JsonElement): Boolean { + if (json !is JsonObject) return false + + val method = json["method"]?.jsonPrimitive?.contentOrNull + return method == "initialize" + } +} + +class HttpServerTransport(private val sessionId: String) : AbstractTransport() { + private val logger = KotlinLogging.logger {} + private val pendingResponses = ConcurrentHashMap>() + private val messageQueue = Channel(Channel.UNLIMITED) + + suspend fun stream(call: ApplicationCall) { + logger.debug { "Starting SSE stream for session: $sessionId" } + call.response.header("Cache-Control", "no-cache") + call.response.header("Connection", "keep-alive") + call.respondTextWriter(ContentType.Text.EventStream) { + try { + while (true) { + val result = messageQueue.receiveCatching() + val msg = result.getOrNull() ?: break + val json = McpJson.encodeToString(msg) + write("event: message\n") + write("data: ") + write(json) + write("\n\n") + flush() + } + } catch (e: Exception) { + logger.warn(e) { "SSE stream terminated for session: $sessionId" } + } finally { + logger.debug { "SSE stream closed for session: $sessionId" } + } + } + } + + suspend fun handleRequest(call: ApplicationCall, requestBody: JsonElement) { + try { + logger.info { "Handling request body: $requestBody" } + val message = McpJson.decodeFromJsonElement(requestBody) + logger.info { "Decoded message: $message" } + + if (message is JSONRPCRequest) { + val id = message.id.toString() + logger.info { "Received request with ID: $id, method: ${message.method}" } + val responseDeferred = CompletableDeferred() + pendingResponses[id] = responseDeferred + logger.info { "Created deferred response for ID: $id" } + + logger.info { "Invoking onMessage handler" } + _onMessage.invoke(message) + logger.info { "onMessage handler completed" } + + try { + val response = withTimeoutOrNull(10000) { + responseDeferred.await() + } + + if (response != null) { + val jsonResponse = McpJson.encodeToString(response) + call.respondText(jsonResponse, ContentType.Application.Json) + } else { + logger.warn { "Timeout waiting for response to request ID: $id" } + call.respondText( + McpJson.encodeToString( + JSONRPCError( + id = message.id, + error = RPCError( + code = RPCError.ErrorCode.REQUEST_TIMEOUT, + message = "Request timed out", + ), + ), + ), + ContentType.Application.Json, + ) + } + } catch (_: CancellationException) { + logger.warn { "Request cancelled for ID: $id" } + pendingResponses.remove(id) + if (!call.response.isCommitted) { + call.respondText( + McpJson.encodeToString( + JSONRPCError( + id = message.id, + error = RPCError( + code = RPCError.ErrorCode.CONNECTION_CLOSED, + message = "Request cancelled", + ), + ), + ), + ContentType.Application.Json, + HttpStatusCode.ServiceUnavailable, + ) + } + } + } else { + call.respondText("", ContentType.Application.Json, HttpStatusCode.Accepted) + } + } catch (e: Exception) { + logger.error(e) { "Error handling request" } + if (!call.response.isCommitted) { + try { + val errorResponse = JSONRPCError( + id = RequestId(0), + error = RPCError( + code = RPCError.ErrorCode.INTERNAL_ERROR, + message = "Internal server error: ${e.message}", + ), + ) + + call.respondText( + McpJson.encodeToString(errorResponse), + ContentType.Application.Json, + HttpStatusCode.InternalServerError, + ) + } catch (responseEx: Exception) { + logger.error(responseEx) { "Failed to send error response" } + } + } + } + } + + override suspend fun start() { + logger.debug { "Starting HTTP server transport for session: $sessionId" } + } + + override suspend fun send(message: JSONRPCMessage) { + logger.info { "Sending message: $message" } + + if (message is JSONRPCResponse) { + val id = message.id.toString() + logger.info { "Sending response for request ID: $id" } + val deferred = pendingResponses.remove(id) + if (deferred != null) { + logger.info { "Found pending response for ID: $id, completing deferred" } + deferred.complete(message) + return + } else { + logger.warn { "No pending response found for ID: $id" } + } + } else if (message is JSONRPCRequest) { + logger.info { "Sending request with ID: ${message.id}" } + } else if (message is JSONRPCNotification) { + logger.info { "Sending notification: ${message.method}" } + } + + logger.info { "Queueing message for next client request" } + messageQueue.send(message) + } + + override suspend fun close() { + logger.debug { "Closing HTTP server transport for session: $sessionId" } + messageQueue.close() + _onClose.invoke() + } +} + +fun main() { + val server = KotlinServerForTsClient() + server.start() +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerEdgeCasesTestSse.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerEdgeCasesTestSse.kt index 44ff5a4..5af315b 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerEdgeCasesTestSse.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerEdgeCasesTestSse.kt @@ -2,8 +2,8 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse import io.modelcontextprotocol.kotlin.sdk.TextContent import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TsTestBase +import io.modelcontextprotocol.kotlin.sdk.integration.typescript.OldSchemaTransportKind +import io.modelcontextprotocol.kotlin.sdk.integration.typescript.OldSchemaTsTestBase import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll @@ -22,9 +22,9 @@ import kotlin.test.assertNotNull import kotlin.test.assertTrue import kotlin.time.Duration.Companion.seconds -class OldSchemaKotlinClientTsServerEdgeCasesTestSse : TsTestBase() { +class OldSchemaKotlinClientTsServerEdgeCasesTestSse : OldSchemaTsTestBase() { - override val transportKind = TransportKind.SSE + override val transportKind = OldSchemaTransportKind.SSE private var port: Int = 0 private val host = "localhost" diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerTestSse.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerTestSse.kt new file mode 100644 index 0000000..39ac18c --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerTestSse.kt @@ -0,0 +1,49 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse + +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.integration.typescript.OldSchemaAbstractKotlinClientTsServerTest +import io.modelcontextprotocol.kotlin.sdk.integration.typescript.OldSchemaTransportKind +import kotlinx.coroutines.withTimeout +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import kotlin.time.Duration.Companion.seconds + +class OldSchemaKotlinClientTsServerTestSse : OldSchemaAbstractKotlinClientTsServerTest() { + + override val transportKind = OldSchemaTransportKind.SSE + + private var port: Int = 0 + private val host = "localhost" + private lateinit var serverUrl: String + private lateinit var tsServerProcess: Process + + @BeforeEach + fun setUpSse() { + port = findFreePort() + serverUrl = "http://$host:$port/mcp" + tsServerProcess = startTypeScriptServer(port) + println("TypeScript server started on port $port") + } + + @AfterEach + fun tearDownSse() { + if (::tsServerProcess.isInitialized) { + try { + println("Stopping TypeScript server") + stopProcess(tsServerProcess) + } catch (e: Exception) { + println("Warning: Error during TypeScript server stop: ${e.message}") + } + } + } + + override suspend fun useClient(block: suspend (Client) -> T): T = withClient(serverUrl) { client -> + try { + withTimeout(20.seconds) { block(client) } + } finally { + try { + withTimeout(3.seconds) { client.close() } + } catch (_: Exception) {} + } + } +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinServerForTsClientSse.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinServerForTsClientSse.kt index 5101bd8..d9a5221 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinServerForTsClientSse.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinServerForTsClientSse.kt @@ -59,8 +59,8 @@ import java.util.concurrent.ConcurrentHashMap private val logger = KotlinLogging.logger {} -class KotlinServerForTsClient { - private val serverTransports = ConcurrentHashMap() +class OldSchemaKotlinServerForTsClient { + private val serverTransports = ConcurrentHashMap() private val jsonFormat = Json { ignoreUnknownKeys = true } private var server: EmbeddedServer<*, *>? = null @@ -124,7 +124,7 @@ class KotlinServerForTsClient { val newSessionId = UUID.randomUUID().toString() logger.info { "Creating new session with ID: $newSessionId" } - val transport = HttpServerTransport(newSessionId) + val transport = OldSchemaHttpServerTransport(newSessionId) serverTransports[newSessionId] = transport @@ -309,7 +309,7 @@ class KotlinServerForTsClient { } } -class HttpServerTransport(private val sessionId: String) : AbstractTransport() { +class OldSchemaHttpServerTransport(private val sessionId: String) : AbstractTransport() { private val logger = KotlinLogging.logger {} private val pendingResponses = ConcurrentHashMap>() private val messageQueue = Channel(Channel.UNLIMITED) @@ -460,6 +460,6 @@ class HttpServerTransport(private val sessionId: String) : AbstractTransport() { } fun main() { - val server = KotlinServerForTsClient() + val server = OldSchemaKotlinServerForTsClient() server.start() } diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt new file mode 100644 index 0000000..fcab03c --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt @@ -0,0 +1,174 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.typescript.stdio + +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind +import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TsTestBase +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Timeout +import java.util.concurrent.TimeUnit +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class KotlinClientTsServerEdgeCasesTestStdio : TsTestBase() { + + override val transportKind = TransportKind.STDIO + + @Test + @Timeout(30, unit = TimeUnit.SECONDS) + fun testNonExistentToolOverStdio(): Unit = runBlocking(Dispatchers.IO) { + withClientStdio { client: Client, _ -> + val nonExistentToolName = "non-existent-tool" + val arguments = mapOf("name" to "TestUser") + + val result = client.callTool(nonExistentToolName, arguments) + assertNotNull(result, "Tool call result should not be null") + + assertTrue(result.isError ?: false, "isError should be true for non-existent tool") + + val textContent = result.content.firstOrNull { it is TextContent } as? TextContent + assertNotNull(textContent, "Error content should be present in the result") + + val errorText = textContent.text + assertTrue( + errorText.contains("non-existent-tool") && errorText.contains("not found"), + "Error message should indicate the tool was not found", + ) + } + } + + @Test + @Timeout(30, unit = TimeUnit.SECONDS) + fun testSpecialCharactersInArgumentsOverStdio(): Unit = runBlocking(Dispatchers.IO) { + withClientStdio { client: Client, _ -> + val specialChars = "!@#$%^&*()_+{}[]|\\:;\"'<>.,?/" + val arguments = mapOf("name" to specialChars) + + val result = client.callTool("greet", arguments) + assertNotNull(result, "Tool call result should not be null") + + val textContent = result.content.firstOrNull { it is TextContent } as? TextContent + assertNotNull(textContent, "Text content should be present in the result") + + val text = textContent.text + assertTrue( + text.contains(specialChars), + "Tool response should contain the special characters", + ) + } + } + + @Test + @Timeout(30, unit = TimeUnit.SECONDS) + fun testLargePayloadOverStdio(): Unit = runBlocking(Dispatchers.IO) { + withClientStdio { client: Client, _ -> + val largeName = "A".repeat(10 * 1024) + val arguments = mapOf("name" to largeName) + + val result = client.callTool("greet", arguments) + assertNotNull(result, "Tool call result should not be null") + + val callResult = result + val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent + assertNotNull(textContent, "Text content should be present in the result") + + val text = textContent.text + assertTrue( + text.contains("Hello,") && text.contains("A"), + "Tool response should contain the greeting with the large name", + ) + } + } + + @Test + @Timeout(60, unit = TimeUnit.SECONDS) + fun testConcurrentRequestsOverStdio(): Unit = runBlocking(Dispatchers.IO) { + withClientStdio { client: Client, _ -> + val concurrentCount = 5 + val responses = coroutineScope { + val results = (1..concurrentCount).map { i -> + async { + val name = "ConcurrentClient$i" + val arguments = mapOf("name" to name) + + val result = client.callTool("greet", arguments) + assertNotNull(result, "Tool call result should not be null for client $i") + + val callResult = result + val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent + assertNotNull(textContent, "Text content should be present for client $i") + + textContent.text + } + } + results.awaitAll() + } + + for (i in 1..concurrentCount) { + val expectedName = "ConcurrentClient$i" + val matchingResponses = responses.filter { it.contains("Hello, $expectedName!") } + assertEquals( + 1, + matchingResponses.size, + "Should have exactly one response for $expectedName", + ) + } + } + } + + @Test + @Timeout(30, unit = TimeUnit.SECONDS) + fun testInvalidArgumentsOverStdio(): Unit = runBlocking(Dispatchers.IO) { + withClientStdio { client: Client, _ -> + val invalidArguments = mapOf( + "name" to JsonObject(mapOf("nested" to JsonPrimitive("value"))), + ) + + val result = client.callTool("greet", invalidArguments) + assertNotNull(result, "Tool call result should not be null") + + val callResult = result + assertTrue(callResult.isError ?: false, "isError should be true for invalid arguments") + + val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent + assertNotNull(textContent, "Error content should be present in the result") + + val errorText = textContent.text + assertTrue( + errorText.contains("Invalid arguments") && errorText.contains("greet"), + "Error message should indicate invalid arguments for tool greet", + ) + } + } + + @Test + @Timeout(30, unit = TimeUnit.SECONDS) + fun testMultipleToolCallsOverStdio(): Unit = runBlocking(Dispatchers.IO) { + withClientStdio { client: Client, _ -> + repeat(10) { i -> + val name = "SequentialClient$i" + val arguments = mapOf("name" to name) + + val result = client.callTool("greet", arguments) + assertNotNull(result, "Tool call result should not be null for call $i") + + val textContent = result.content.firstOrNull { it is TextContent } as? TextContent + assertNotNull(textContent, "Text content should be present for call $i") + + assertEquals( + "Hello, $name!", + textContent.text, + "Tool response should contain the greeting with the provided name", + ) + } + } + } +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerTestStdio.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerTestStdio.kt new file mode 100644 index 0000000..8273724 --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerTestStdio.kt @@ -0,0 +1,23 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.typescript.stdio + +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.integration.typescript.AbstractKotlinClientTsServerTest +import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind + +class KotlinClientTsServerTestStdio : AbstractKotlinClientTsServerTest() { + + override val transportKind = TransportKind.STDIO + + override suspend fun useClient(block: suspend (Client) -> T): T = withClientStdio { client, proc -> + try { + block(client) + } finally { + try { + client.close() + } catch (_: Exception) {} + try { + stopProcess(proc, name = "TypeScript stdio server") + } catch (_: Exception) {} + } + } +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerEdgeCasesTestStdio.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerEdgeCasesTestStdio.kt index 0ed8676..3666622 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerEdgeCasesTestStdio.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerEdgeCasesTestStdio.kt @@ -2,8 +2,8 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.stdio import io.modelcontextprotocol.kotlin.sdk.TextContent import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TsTestBase +import io.modelcontextprotocol.kotlin.sdk.integration.typescript.OldSchemaTransportKind +import io.modelcontextprotocol.kotlin.sdk.integration.typescript.OldSchemaTsTestBase import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll @@ -18,9 +18,9 @@ import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue -class OldSchemaKotlinClientTsServerEdgeCasesTestStdio : TsTestBase() { +class OldSchemaKotlinClientTsServerEdgeCasesTestStdio : OldSchemaTsTestBase() { - override val transportKind = TransportKind.STDIO + override val transportKind = OldSchemaTransportKind.STDIO @Test @Timeout(30, unit = TimeUnit.SECONDS) diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerTestStdio.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerTestStdio.kt index e87a5c8..b1b5bae 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerTestStdio.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerTestStdio.kt @@ -2,11 +2,11 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.stdio import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.integration.typescript.OldSchemaAbstractKotlinClientTsServerTest -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind +import io.modelcontextprotocol.kotlin.sdk.integration.typescript.OldSchemaTransportKind class OldSchemaKotlinClientTsServerTestStdio : OldSchemaAbstractKotlinClientTsServerTest() { - override val transportKind = TransportKind.STDIO + override val transportKind = OldSchemaTransportKind.STDIO override suspend fun useClient(block: suspend (Client) -> T): T = withClientStdio { client, proc -> try { diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/AbstractServerFeaturesTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/AbstractServerFeaturesTest.kt new file mode 100644 index 0000000..21b46d2 --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/AbstractServerFeaturesTest.kt @@ -0,0 +1,44 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.BeforeEach + +abstract class AbstractServerFeaturesTest { + + protected lateinit var server: Server + protected lateinit var client: Client + + abstract fun getServerCapabilities(): ServerCapabilities + + protected open fun getServerInstructionsProvider(): (() -> String)? = null + + @BeforeEach + fun setUp() { + val serverOptions = ServerOptions( + capabilities = getServerCapabilities(), + ) + + server = Server( + serverInfo = Implementation(name = "test server", version = "1.0"), + options = serverOptions, + instructionsProvider = getServerInstructionsProvider(), + ) + + val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() + + client = Client( + clientInfo = Implementation(name = "test client", version = "1.0"), + ) + + runBlocking { + // Connect client and server + launch { client.connect(clientTransport) } + launch { server.createSession(serverTransport) } + } + } +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerInstructionsTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerInstructionsTest.kt new file mode 100644 index 0000000..26bb7bf --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerInstructionsTest.kt @@ -0,0 +1,68 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.modelcontextprotocol.kotlin.sdk.Implementation +import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertNull +import kotlin.test.assertEquals + +class OldSchemaServerInstructionsTest { + + @Test + fun `Server constructor should accept instructions provider parameter`() = runTest { + val serverInfo = Implementation(name = "test server", version = "1.0") + val serverOptions = ServerOptions(capabilities = ServerCapabilities()) + val instructions = "This is a test server. Use it for testing purposes only." + + val server = Server(serverInfo, serverOptions, { instructions }) + + // 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) + + assertEquals(instructions, client.serverInstructions) + } + + @Test + fun `Server constructor should accept instructions parameter`() = runTest { + val serverInfo = Implementation(name = "test server", version = "1.0") + val serverOptions = ServerOptions(capabilities = ServerCapabilities()) + val instructions = "This is a test server. Use it for testing purposes only." + + val server = Server(serverInfo, serverOptions, instructions) + + // 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) + + assertEquals(instructions, client.serverInstructions) + } + + @Test + fun `Server constructor should work without instructions parameter`() = runTest { + val serverInfo = Implementation(name = "test server", version = "1.0") + val serverOptions = ServerOptions(capabilities = ServerCapabilities()) + + // Test that server works when instructions parameter is omitted (defaults to null) + 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) + + assertNull(client.serverInstructions) + } +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsTest.kt new file mode 100644 index 0000000..60de0ca --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsTest.kt @@ -0,0 +1,100 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.Prompt +import io.modelcontextprotocol.kotlin.sdk.types.PromptListChangedNotification +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class ServerPromptsTest : AbstractServerFeaturesTest() { + + override fun getServerCapabilities(): ServerCapabilities = ServerCapabilities( + prompts = ServerCapabilities.Prompts(false), + ) + + @Test + fun `removePrompt should remove a prompt`() = runTest { + // Add a prompt + val testPrompt = Prompt("test-prompt", "Test Prompt", null) + server.addPrompt(testPrompt) { + GetPromptResult( + description = "Test prompt description", + messages = listOf(), + ) + } + + // Remove the prompt + val result = server.removePrompt(testPrompt.name) + + // Verify the prompt was removed + assertTrue(result, "Prompt should be removed successfully") + } + + @Test + fun `removePrompts should remove multiple prompts and send notification`() = runTest { + // Add prompts + val testPrompt1 = Prompt("test-prompt-1", "Test Prompt 1", null) + val testPrompt2 = Prompt("test-prompt-2", "Test Prompt 2", null) + server.addPrompt(testPrompt1) { + GetPromptResult( + description = "Test prompt description 1", + messages = listOf(), + ) + } + server.addPrompt(testPrompt2) { + GetPromptResult( + description = "Test prompt description 2", + messages = listOf(), + ) + } + + // Remove the prompts + val result = server.removePrompts(listOf(testPrompt1.name, testPrompt2.name)) + + // Verify the prompts were removed + assertEquals(2, result, "Both prompts should be removed") + } + + @Test + fun `removePrompt should return false when prompt does not exist`() = runTest { + // Track notifications + var promptListChangedNotificationReceived = false + client.setNotificationHandler(Method.Defined.NotificationsPromptsListChanged) { + promptListChangedNotificationReceived = true + CompletableDeferred(Unit) + } + + // Try to remove a non-existent prompt + val result = server.removePrompt("non-existent-prompt") + + // Verify the result + assertFalse(result, "Removing non-existent prompt should return false") + assertFalse(promptListChangedNotificationReceived, "No notification should be sent when prompt doesn't exist") + } + + @Test + fun `removePrompt should throw when prompts capability is not supported`() = runTest { + // Create server without prompts capability + val serverOptions = ServerOptions( + capabilities = ServerCapabilities(), + ) + val server = Server( + Implementation(name = "test server", version = "1.0"), + serverOptions, + ) + + // Verify that removing a prompt throws an exception + val exception = assertThrows { + server.removePrompt("test-prompt") + } + assertEquals("Server does not support prompts capability.", exception.message) + } +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesTest.kt new file mode 100644 index 0000000..ad73c83 --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesTest.kt @@ -0,0 +1,135 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult +import io.modelcontextprotocol.kotlin.sdk.types.ResourceListChangedNotification +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class ServerResourcesTest : AbstractServerFeaturesTest() { + + override fun getServerCapabilities(): ServerCapabilities = ServerCapabilities( + resources = ServerCapabilities.Resources(null, null), + ) + + @Test + fun `removeResource should remove a resource and send notification`() = runTest { + // Add a resource + val testResourceUri = "test://resource" + server.addResource( + uri = testResourceUri, + name = "Test Resource", + description = "A test resource", + mimeType = "text/plain", + ) { + ReadResourceResult( + contents = listOf( + TextResourceContents( + text = "Test resource content", + uri = testResourceUri, + mimeType = "text/plain", + ), + ), + ) + } + + // Remove the resource + val result = server.removeResource(testResourceUri) + + // Verify the resource was removed + assertTrue(result, "Resource should be removed successfully") + } + + @Test + fun `removeResources should remove multiple resources and send notification`() = runTest { + // Add resources + val testResourceUri1 = "test://resource1" + val testResourceUri2 = "test://resource2" + server.addResource( + uri = testResourceUri1, + name = "Test Resource 1", + description = "A test resource 1", + mimeType = "text/plain", + ) { + ReadResourceResult( + contents = listOf( + TextResourceContents( + text = "Test resource content 1", + uri = testResourceUri1, + mimeType = "text/plain", + ), + ), + ) + } + server.addResource( + uri = testResourceUri2, + name = "Test Resource 2", + description = "A test resource 2", + mimeType = "text/plain", + ) { + ReadResourceResult( + contents = listOf( + TextResourceContents( + text = "Test resource content 2", + uri = testResourceUri2, + mimeType = "text/plain", + ), + ), + ) + } + + // Remove the resources + val result = server.removeResources(listOf(testResourceUri1, testResourceUri2)) + + // Verify the resources were removed + assertEquals(2, result, "Both resources should be removed") + } + + @Test + fun `removeResource should return false when resource does not exist`() = runTest { + // Track notifications + var resourceListChangedNotificationReceived = false + client.setNotificationHandler( + Method.Defined.NotificationsResourcesListChanged, + ) { + resourceListChangedNotificationReceived = true + CompletableDeferred(Unit) + } + + // Try to remove a non-existent resource + val result = server.removeResource("non-existent-resource") + + // Verify the result + assertFalse(result, "Removing non-existent resource should return false") + assertFalse( + resourceListChangedNotificationReceived, + "No notification should be sent when resource doesn't exist", + ) + } + + @Test + fun `removeResource should throw when resources capability is not supported`() = runTest { + // Create server without resources capability + val serverOptions = ServerOptions( + capabilities = ServerCapabilities(), + ) + val server = Server( + Implementation(name = "test server", version = "1.0"), + serverOptions, + ) + + // Verify that removing a resource throws an exception + val exception = assertThrows { + server.removeResource("test://resource") + } + assertEquals("Server does not support resources capability.", exception.message) + } +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsTest.kt new file mode 100644 index 0000000..eae2eb2 --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsTest.kt @@ -0,0 +1,89 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.sdk.types.ToolListChangedNotification +import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class ServerToolsTest : AbstractServerFeaturesTest() { + + override fun getServerCapabilities(): ServerCapabilities = ServerCapabilities( + tools = ServerCapabilities.Tools(null), + ) + + @Test + fun `removeTool should remove a tool`() = runTest { + // Add a tool + server.addTool("test-tool", "Test Tool", ToolSchema()) { + CallToolResult(listOf(TextContent("Test result"))) + } + + // Remove the tool + val result = server.removeTool("test-tool") + + // Verify the tool was removed + assertTrue(result, "Tool should be removed successfully") + } + + @Test + fun `removeTool should return false when tool does not exist`() = runTest { + // Track notifications + var toolListChangedNotificationReceived = false + client.setNotificationHandler(Method.Defined.NotificationsToolsListChanged) { + toolListChangedNotificationReceived = true + CompletableDeferred(Unit) + } + + // Try to remove a non-existent tool + val result = server.removeTool("non-existent-tool") + + // Verify the result + assertFalse(result, "Removing non-existent tool should return false") + assertFalse(toolListChangedNotificationReceived, "No notification should be sent when tool doesn't exist") + } + + @Test + fun `removeTool should throw when tools capability is not supported`() = runTest { + // Create server without tools capability + val serverOptions = ServerOptions( + capabilities = ServerCapabilities(), + ) + val server = Server( + Implementation(name = "test server", version = "1.0"), + serverOptions, + ) + + // Verify that removing a tool throws an exception + val exception = assertThrows { + server.removeTool("test-tool") + } + assertEquals("Server does not support tools capability.", exception.message) + } + + @Test + fun `removeTools should remove multiple tools`() = runTest { + // Add tools + server.addTool("test-tool-1", "Test Tool 1") { + CallToolResult(listOf(TextContent("Test result 1"))) + } + server.addTool("test-tool-2", "Test Tool 2") { + CallToolResult(listOf(TextContent("Test result 2"))) + } + + // Remove the tools + val result = server.removeTools(listOf("test-tool-1", "test-tool-2")) + + // Verify the tools were removed + assertEquals(2, result, "Both tools should be removed") + } +} From d2f0893b2ccc50e2f756ae727d4affffd2540edd Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Fri, 14 Nov 2025 16:43:55 +0200 Subject: [PATCH 071/449] Add example Kotlin Notebook demonstrating MCP client (#397) Add `McpClient.ipynb` example notebook demonstrating MCP client ## Motivation and Context Provide more examples ## How Has This Been Tested? Run Kotlin Notebook locally ## Breaking Changes No ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] 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 - [x] I have added or updated documentation as needed ## Additional context --- samples/notebooks/McpClient.ipynb | 678 ++++++++++++++++++++++++++++++ 1 file changed, 678 insertions(+) create mode 100644 samples/notebooks/McpClient.ipynb diff --git a/samples/notebooks/McpClient.ipynb b/samples/notebooks/McpClient.ipynb new file mode 100644 index 0000000..9b63771 --- /dev/null +++ b/samples/notebooks/McpClient.ipynb @@ -0,0 +1,678 @@ +{ + "cells": [ + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "# MCP Client Example\n", + "\n", + "Here is the example of how you can use Kotlin MCP client.\n", + "\n", + "Let's start with adding dependencies and imports.\n", + "\n", + "We'll need:\n", + "- Kotlin SDK Client\n", + "- Ktor client\n", + "- Kotlinx.coroutines" + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-11-14T14:12:47.844373Z", + "start_time": "2025-11-14T14:12:47.723693Z" + } + }, + "cell_type": "code", + "source": [ + "%use coroutines (1.10.2)\n", + "%use ktor-client (0.3.0-5)\n", + "\n", + "USE {\n", + " dependencies(\n", + " \"io.modelcontextprotocol:kotlin-sdk-client-jvm:0.7.7\",\n", + " )\n", + "}" + ], + "outputs": [], + "execution_count": 10 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-11-14T14:12:47.859619Z", + "start_time": "2025-11-14T14:12:47.847619Z" + } + }, + "cell_type": "code", + "source": [ + "import kotlinx.coroutines.runBlocking\n", + "import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject\n", + "import io.modelcontextprotocol.kotlin.sdk.Implementation\n", + "import io.modelcontextprotocol.kotlin.sdk.TextContent\n", + "import io.modelcontextprotocol.kotlin.sdk.client.Client\n", + "import io.modelcontextprotocol.kotlin.sdk.client.ClientOptions\n", + "import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport\n", + "import io.modelcontextprotocol.kotlin.sdk.client.StreamableHttpClientTransport\n", + "import io.ktor.client.HttpClient\n", + "import io.ktor.client.call.body\n", + "import io.ktor.client.request.get\n", + "import kotlinx.serialization.Serializable\n", + "import kotlinx.serialization.json.JsonObject\n", + "import io.ktor.client.plugins.logging.*\n", + "import io.ktor.client.plugins.sse.SSE" + ], + "outputs": [], + "execution_count": 11 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "First, let's create the Ktor HTTP Client first.\n", + "We are installing Server-Sent Events (SSE) plugin, and, also, a Logging plugin for convenience." + ] + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-11-14T14:12:47.907402Z", + "start_time": "2025-11-14T14:12:47.862414Z" + } + }, + "cell_type": "code", + "source": [ + "val httpClient = HttpClient {\n", + " install(SSE)\n", + " install(Logging) {\n", + " level = LogLevel.ALL\n", + " logger = object : Logger {\n", + " override fun log(message: String) {\n", + " DISPLAY(HTML(\"

HTTP Log
$message
\"))\n", + " }\n", + " }\n", + " }\n", + "}\n" + ], + "outputs": [], + "execution_count": 12 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Now let's create an MCP client:" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-11-14T14:12:47.939057Z", + "start_time": "2025-11-14T14:12:47.910220Z" + } + }, + "cell_type": "code", + "source": [ + "val mcpClient = Client(\n", + " clientInfo = Implementation(\n", + " name = \"my-client\",\n", + " version = \"1.0.0\"\n", + " ),\n", + " options = ClientOptions(),\n", + ")" + ], + "outputs": [], + "execution_count": 13 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Now we can connect an MCP client to remote MCP server via StreamableHttpClientTransport" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-11-14T14:12:48.846736Z", + "start_time": "2025-11-14T14:12:47.941357Z" + } + }, + "cell_type": "code", + "source": [ + "runBlocking {\n", + " mcpClient.connect(\n", + " StreamableHttpClientTransport(\n", + " client = httpClient,\n", + " url = \"https://learn.microsoft.com/api/mcp\"\n", + " )\n", + " )\n", + "\n", + " println(\"✅ Server version: ${mcpClient.serverVersion}\")\n", + " println(\"✅ Server capabilities: ${mcpClient.serverCapabilities}\")\n", + "}" + ], + "outputs": [ + { + "data": { + "text/html": [ + "
HTTP Log
REQUEST: https://learn.microsoft.com/api/mcp\n",
+       "METHOD: POST\n",
+       "COMMON HEADERS\n",
+       "-> Accept: application/json, text/event-stream\n",
+       "-> Accept-Charset: UTF-8\n",
+       "CONTENT HEADERS\n",
+       "-> Content-Length: 200\n",
+       "-> Content-Type: application/json\n",
+       "BODY Content-Type: application/json\n",
+       "BODY START\n",
+       "{\"id\":5,\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2025-03-26\",\"capabilities\":{\"experimental\":{},\"sampling\":{}},\"clientInfo\":{\"name\":\"my-client\",\"version\":\"1.0.0\"},\"_meta\":{}},\"jsonrpc\":\"2.0\"}\n",
+       "BODY END
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "data": { + "text/html": [ + "
HTTP Log
RESPONSE: 200 OK\n",
+       "METHOD: POST\n",
+       "FROM: https://learn.microsoft.com/api/mcp\n",
+       "COMMON HEADERS\n",
+       "-> Akamai-Cache-Status: Miss from child, Miss from parent\n",
+       "-> Cache-Control: no-cache, no-store\n",
+       "-> Connection: keep-alive\n",
+       "-> Content-Encoding: identity\n",
+       "-> Content-Length: 1914\n",
+       "-> Content-Type: text/event-stream\n",
+       "-> Date: Fri, 14 Nov 2025 14:12:48 GMT\n",
+       "-> Expires: Fri, 14 Nov 2025 14:12:48 GMT\n",
+       "-> Mcp-Session-Id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n",
+       "-> Request-Context: appId=cid-v1:ccd19013-8f20-4877-8d3f-84a0107e7119\n",
+       "-> Set-Cookie: ASLBSA=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; Path=/; Secure; HttpOnly;; ASLBSACORS=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; SameSite=none; Path=/; Secure; HttpOnly;\n",
+       "-> Strict-Transport-Security: max-age=31536000; includeSubDomains; preload\n",
+       "-> X-Content-Type-Options: nosniff\n",
+       "-> X-Powered-By: ASP.NET\n",
+       "-> nel: {\"report_to\":\"network-errors\",\"max_age\":604800,\"success_fraction\":0.01,\"failure_fraction\":1.0}\n",
+       "-> report-to: {\"group\":\"network-errors\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://mdec.nelreports.net/api/report?cat=mdocs\"}]}\n",
+       "-> x-azure-ref: 20251114T141248Z-169b4b8d65848xdthC1AMS8x840000000xfg00000000arwf\n",
+       "BODY Content-Type: text/event-stream\n",
+       "BODY START\n",
+       "event: message\n",
+       "data: {\"result\":{\"protocolVersion\":\"2025-03-26\",\"capabilities\":{\"logging\":{},\"prompts\":{},\"resources\":{},\"tools\":{\"listChanged\":true}},\"serverInfo\":{\"name\":\"Microsoft Learn MCP Server\",\"version\":\"1.0.0\"},\"instructions\":\"# Microsoft Learn MCP Server\\r\\n\\r\\nThis server gives structured access to official Microsoft and Azure documentation via three tools:\\r\\n\\r\\n## Tools\\r\\n\\r\\n### microsoft_docs_search\\r\\nSearch official documentation and return up to 10 concise, high-quality content chunks (max 500 tokens each), including title, URL, and excerpt.\\r\\n\\r\\n- Use first to get a quick, reliable overview\\r\\n- Ideal for grounding answers in Microsoft knowledge\\r\\n\\r\\n### microsoft_code_sample_search\\r\\nSearch for code snippets and examples in official Microsoft Learn documentation and return up to 20 relevant, high-quality code samples.\\r\\n\\r\\n- Use when you need to provide sample Microsoft/Azure related code in your answers.  \\r\\n- Ideal for generating code snippets or practical implementation examples.  \\r\\n- Optional parameter \\u0060language\\u0060 can filter results. \\r\\n\\r\\n### microsoft_docs_fetch\\r\\nFetch and convert full Microsoft documentation pages to markdown.\\r\\n\\r\\n- Use after search when you need full content from a specific URL\\r\\n- Required for detailed tutorials, troubleshooting, prerequisites, code samples, or when search results are incomplete or outdated\\r\\n\\r\\n## Workflow\\r\\n\\r\\n1. Use \\u0060microsoft_docs_search\\u0060 to find relevant documents.\\r\\n2. If you need code examples or practical snippets, use \\u0060microsoft_code_sample_search\\u0060.\\r\\n3. If deeper or complete information is needed, use \\u0060microsoft_docs_fetch\\u0060.\\r\\n\\r\\n**Search gives breadth. Code Sample Search gives practical examples. Fetch gives depth.**\\r\\n\\r\\nAll content comes from Microsoft Learn or official sources, returned in clean markdown format.\"},\"id\":5,\"jsonrpc\":\"2.0\"}\n",
+       "\n",
+       "\n",
+       "BODY END
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "data": { + "text/html": [ + "
HTTP Log
REQUEST: https://learn.microsoft.com/api/mcp\n",
+       "METHOD: POST\n",
+       "COMMON HEADERS\n",
+       "-> Accept: application/json, text/event-stream\n",
+       "-> Accept-Charset: UTF-8\n",
+       "-> mcp-session-id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n",
+       "CONTENT HEADERS\n",
+       "-> Content-Length: 76\n",
+       "-> Content-Type: application/json\n",
+       "BODY Content-Type: application/json\n",
+       "BODY START\n",
+       "{\"method\":\"notifications/initialized\",\"params\":{\"_meta\":{}},\"jsonrpc\":\"2.0\"}\n",
+       "BODY END
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "data": { + "text/html": [ + "
HTTP Log
RESPONSE: 202 Accepted\n",
+       "METHOD: POST\n",
+       "FROM: https://learn.microsoft.com/api/mcp\n",
+       "COMMON HEADERS\n",
+       "-> Akamai-Cache-Status: Miss from child, Miss from parent\n",
+       "-> Cache-Control: no-cache, no-store\n",
+       "-> Connection: keep-alive\n",
+       "-> Content-Encoding: identity\n",
+       "-> Content-Length: 0\n",
+       "-> Date: Fri, 14 Nov 2025 14:12:48 GMT\n",
+       "-> Expires: Fri, 14 Nov 2025 14:12:48 GMT\n",
+       "-> Mcp-Session-Id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n",
+       "-> Request-Context: appId=cid-v1:ccd19013-8f20-4877-8d3f-84a0107e7119\n",
+       "-> Set-Cookie: ASLBSA=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; Path=/; Secure; HttpOnly;; ASLBSACORS=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; SameSite=none; Path=/; Secure; HttpOnly;\n",
+       "-> Strict-Transport-Security: max-age=31536000; includeSubDomains; preload\n",
+       "-> X-Content-Type-Options: nosniff\n",
+       "-> X-Powered-By: ASP.NET\n",
+       "-> nel: {\"report_to\":\"network-errors\",\"max_age\":604800,\"success_fraction\":0.01,\"failure_fraction\":1.0}\n",
+       "-> report-to: {\"group\":\"network-errors\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://mdec.nelreports.net/api/report?cat=mdocs\"}]}\n",
+       "-> x-azure-ref: 20251114T141248Z-169b4b8d6588t6fthC1AMSxncg00000018v000000000ruuv\n",
+       "BODY Content-Type: null\n",
+       "BODY START\n",
+       "\n",
+       "BODY END
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "data": { + "text/html": [ + "
HTTP Log
REQUEST: https://learn.microsoft.com/api/mcp\n",
+       "METHOD: GET\n",
+       "COMMON HEADERS\n",
+       "-> Accept: application/json\n",
+       "-> Accept-Charset: UTF-8\n",
+       "-> mcp-session-id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n",
+       "CONTENT HEADERS\n",
+       "-> Content-Length: 0\n",
+       "-> Accept: text/event-stream\n",
+       "-> Cache-Control: no-store\n",
+       "BODY Content-Type: null\n",
+       "BODY START\n",
+       "\n",
+       "BODY END
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "✅ Server version: Implementation(name=Microsoft Learn MCP Server, version=1.0.0)\n", + "✅ Server capabilities: ServerCapabilities(experimental={}, sampling={}, logging={}, prompts=Prompts(listChanged=null), resources=Resources(subscribe=null, listChanged=null), tools=Tools(listChanged=true))\n" + ] + } + ], + "execution_count": 14 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Now we can send ping request to MCP server:" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-11-14T14:12:49.148355Z", + "start_time": "2025-11-14T14:12:48.859801Z" + } + }, + "cell_type": "code", + "source": [ + "runBlocking {\n", + " mcpClient.ping()\n", + "}" + ], + "outputs": [ + { + "data": { + "text/html": [ + "
HTTP Log
REQUEST: https://learn.microsoft.com/api/mcp\n",
+       "METHOD: POST\n",
+       "COMMON HEADERS\n",
+       "-> Accept: application/json, text/event-stream\n",
+       "-> Accept-Charset: UTF-8\n",
+       "-> mcp-session-id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n",
+       "CONTENT HEADERS\n",
+       "-> Content-Length: 52\n",
+       "-> Content-Type: application/json\n",
+       "BODY Content-Type: application/json\n",
+       "BODY START\n",
+       "{\"id\":6,\"method\":\"ping\",\"params\":{},\"jsonrpc\":\"2.0\"}\n",
+       "BODY END
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "data": { + "text/html": [ + "
HTTP Log
RESPONSE: 200 OK\n",
+       "METHOD: POST\n",
+       "FROM: https://learn.microsoft.com/api/mcp\n",
+       "COMMON HEADERS\n",
+       "-> Akamai-Cache-Status: Miss from child, Miss from parent\n",
+       "-> Cache-Control: no-cache, no-store\n",
+       "-> Connection: keep-alive\n",
+       "-> Content-Encoding: identity\n",
+       "-> Content-Length: 59\n",
+       "-> Content-Type: text/event-stream\n",
+       "-> Date: Fri, 14 Nov 2025 14:12:49 GMT\n",
+       "-> Expires: Fri, 14 Nov 2025 14:12:49 GMT\n",
+       "-> Mcp-Session-Id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n",
+       "-> Request-Context: appId=cid-v1:ccd19013-8f20-4877-8d3f-84a0107e7119\n",
+       "-> Set-Cookie: ASLBSA=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; Path=/; Secure; HttpOnly;; ASLBSACORS=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; SameSite=none; Path=/; Secure; HttpOnly;\n",
+       "-> Strict-Transport-Security: max-age=31536000; includeSubDomains; preload\n",
+       "-> X-Content-Type-Options: nosniff\n",
+       "-> X-Powered-By: ASP.NET\n",
+       "-> nel: {\"report_to\":\"network-errors\",\"max_age\":604800,\"success_fraction\":0.01,\"failure_fraction\":1.0}\n",
+       "-> report-to: {\"group\":\"network-errors\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://mdec.nelreports.net/api/report?cat=mdocs\"}]}\n",
+       "-> x-azure-ref: 20251114T141249Z-169b4b8d6588pb5bhC1AMS2m0w00000017n000000000w3kt\n",
+       "BODY Content-Type: text/event-stream\n",
+       "BODY START\n",
+       "event: message\n",
+       "data: {\"result\":{},\"id\":6,\"jsonrpc\":\"2.0\"}\n",
+       "\n",
+       "\n",
+       "BODY END
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "data": { + "text/plain": [ + "EmptyRequestResult(_meta={})" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "execution_count": 15 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Let's get a list of available tools:" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-11-14T14:12:49.511356Z", + "start_time": "2025-11-14T14:12:49.201984Z" + } + }, + "cell_type": "code", + "source": [ + "runBlocking {\n", + " val listToolsResult = mcpClient.listTools()\n", + "\n", + " for (tool in listToolsResult.tools) {\n", + " DISPLAY(tool)\n", + " }\n", + "}\n" + ], + "outputs": [ + { + "data": { + "text/html": [ + "
HTTP Log
REQUEST: https://learn.microsoft.com/api/mcp\n",
+       "METHOD: POST\n",
+       "COMMON HEADERS\n",
+       "-> Accept: application/json, text/event-stream\n",
+       "-> Accept-Charset: UTF-8\n",
+       "-> mcp-session-id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n",
+       "CONTENT HEADERS\n",
+       "-> Content-Length: 68\n",
+       "-> Content-Type: application/json\n",
+       "BODY Content-Type: application/json\n",
+       "BODY START\n",
+       "{\"id\":7,\"method\":\"tools/list\",\"params\":{\"_meta\":{}},\"jsonrpc\":\"2.0\"}\n",
+       "BODY END
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "data": { + "text/html": [ + "
HTTP Log
RESPONSE: 200 OK\n",
+       "METHOD: POST\n",
+       "FROM: https://learn.microsoft.com/api/mcp\n",
+       "COMMON HEADERS\n",
+       "-> Akamai-Cache-Status: Miss from child, Miss from parent\n",
+       "-> Cache-Control: no-cache, no-store\n",
+       "-> Connection: keep-alive\n",
+       "-> Content-Encoding: identity\n",
+       "-> Content-Length: 4462\n",
+       "-> Content-Type: text/event-stream\n",
+       "-> Date: Fri, 14 Nov 2025 14:12:49 GMT\n",
+       "-> Expires: Fri, 14 Nov 2025 14:12:49 GMT\n",
+       "-> Mcp-Session-Id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n",
+       "-> Request-Context: appId=cid-v1:ccd19013-8f20-4877-8d3f-84a0107e7119\n",
+       "-> Set-Cookie: ASLBSA=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; Path=/; Secure; HttpOnly;; ASLBSACORS=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; SameSite=none; Path=/; Secure; HttpOnly;\n",
+       "-> Strict-Transport-Security: max-age=31536000; includeSubDomains; preload\n",
+       "-> X-Content-Type-Options: nosniff\n",
+       "-> X-Powered-By: ASP.NET\n",
+       "-> nel: {\"report_to\":\"network-errors\",\"max_age\":604800,\"success_fraction\":0.01,\"failure_fraction\":1.0}\n",
+       "-> report-to: {\"group\":\"network-errors\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://mdec.nelreports.net/api/report?cat=mdocs\"}]}\n",
+       "-> x-azure-ref: 20251114T141249Z-169b4b8d6588t6fthC1AMSxncg00000018v000000000ruxq\n",
+       "BODY Content-Type: text/event-stream\n",
+       "BODY START\n",
+       "event: message\n",
+       "data: {\"result\":{\"tools\":[{\"name\":\"microsoft_docs_search\",\"title\":\"Microsoft Docs Search\",\"description\":\"Search official Microsoft/Azure documentation to find the most relevant and trustworthy content for a user\\u0027s query. This tool returns up to 10 high-quality content chunks (each max 500 tokens), extracted from Microsoft Learn and other official sources. Each result includes the article title, URL, and a self-contained content excerpt optimized for fast retrieval and reasoning. Always use this tool to quickly ground your answers in accurate, first-party Microsoft/Azure knowledge.\\n\\nThe \\u0060question\\u0060 parameter is no longer used, use \\u0060query\\u0060 instead.\\n\\n## Follow-up Pattern\\nTo ensure completeness, use microsoft_docs_fetch when high-value pages are identified by search. The fetch tool complements search by providing the full detail. This is a required step for comprehensive results.\",\"inputSchema\":{\"type\":\"object\",\"properties\":{\"query\":{\"description\":\"a query or topic about Microsoft/Azure products, services, platforms, developer tools, frameworks, or APIs\",\"type\":\"string\",\"default\":null},\"question\":{\"description\":\"this parameter is no longer used, use query instead.\",\"type\":\"string\",\"default\":null}}},\"annotations\":{\"title\":\"Microsoft Docs Search\",\"destructiveHint\":false,\"idempotentHint\":true,\"readOnlyHint\":true}},{\"name\":\"microsoft_code_sample_search\",\"title\":\"Microsoft Code Sample Search\",\"description\":\"Search for code snippets and examples in official Microsoft Learn documentation. This tool retrieves relevant code samples from Microsoft documentation pages providing developers with practical implementation examples and best practices for Microsoft/Azure products and services related coding tasks. This tool will help you use the **LATEST OFFICIAL** code snippets to empower coding capabilities.\\n\\n## When to Use This Tool\\n- When you are going to provide sample Microsoft/Azure related code snippets in your answers.\\n- When you are **generating any Microsoft/Azure related code**.\\n\\n## Usage Pattern\\nInput a descriptive query, or SDK/class/method name to retrieve related code samples. The optional parameter \\u0060language\\u0060 can help to filter results.\\n\\nEligible values for \\u0060language\\u0060 parameter include: csharp javascript typescript python powershell azurecli al sql java kusto cpp go rust ruby php\",\"inputSchema\":{\"type\":\"object\",\"properties\":{\"query\":{\"description\":\"a descriptive query, SDK name, method name or code snippet related to Microsoft/Azure products, services, platforms, developer tools, frameworks, APIs or SDKs\",\"type\":\"string\"},\"language\":{\"description\":\"Optional parameter specifying the programming language of code snippets to retrieve. Can significantly improve search quality if provided. Eligible values: csharp javascript typescript python powershell azurecli al sql java kusto cpp go rust ruby php\",\"type\":\"string\",\"default\":null}},\"required\":[\"query\"]},\"annotations\":{\"title\":\"Microsoft Code Sample Search\",\"destructiveHint\":false,\"idempotentHint\":true,\"readOnlyHint\":true}},{\"name\":\"microsoft_docs_fetch\",\"title\":\"Microsoft Docs Fetch\",\"description\":\"Fetch and convert a Microsoft Learn documentation page to markdown format. This tool retrieves the latest complete content of Microsoft documentation pages including Azure, .NET, Microsoft 365, and other Microsoft technologies.\\n\\n## When to Use This Tool\\n- When search results provide incomplete information or truncated content\\n- When you need complete step-by-step procedures or tutorials\\n- When you need troubleshooting sections, prerequisites, or detailed explanations\\n- When search results reference a specific page that seems highly relevant\\n- For comprehensive guides that require full context\\n\\n## Usage Pattern\\nUse this tool AFTER microsoft_docs_search when you identify specific high-value pages that need complete content. The search tool gives you an overview; this tool gives you the complete picture.\\n\\n## URL Requirements\\n- The URL must be a valid link from the microsoft.com domain.\\n\\n## Output Format\\nmarkdown with headings, code blocks, tables, and links preserved.\",\"inputSchema\":{\"type\":\"object\",\"properties\":{\"url\":{\"description\":\"URL of the Microsoft documentation page to read\",\"type\":\"string\"}},\"required\":[\"url\"]},\"annotations\":{\"title\":\"Microsoft Docs Fetch\",\"destructiveHint\":false,\"idempotentHint\":true,\"readOnlyHint\":true}}]},\"id\":7,\"jsonrpc\":\"2.0\"}\n",
+       "\n",
+       "\n",
+       "BODY END
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "data": { + "text/plain": [ + "Tool(name=microsoft_docs_search, title=Microsoft Docs Search, description=Search official Microsoft/Azure documentation to find the most relevant and trustworthy content for a user's query. This tool returns up to 10 high-quality content chunks (each max 500 tokens), extracted from Microsoft Learn and other official sources. Each result includes the article title, URL, and a self-contained content excerpt optimized for fast retrieval and reasoning. Always use this tool to quickly ground your answers in accurate, first-party Microsoft/Azure knowledge.\n", + "\n", + "The `question` parameter is no longer used, use `query` instead.\n", + "\n", + "## Follow-up Pattern\n", + "To ensure completeness, use microsoft_docs_fetch when high-value pages are identified by search. The fetch tool complements search by providing the full detail. This is a required step for comprehensive results., inputSchema=Input(properties={\"query\":{\"description\":\"a query or topic about Microsoft/Azure products, services, platforms, developer tools, frameworks, or APIs\",\"type\":\"string\",\"default\":null},\"question\":{\"description\":\"this parameter is no longer used, use query instead.\",\"type\":\"string\",\"default\":null}}, required=null), outputSchema=null, annotations=ToolAnnotations(title=Microsoft Docs Search, readOnlyHint=true, destructiveHint=false, idempotentHint=true, openWorldHint=true), _meta={})" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "data": { + "text/plain": [ + "Tool(name=microsoft_code_sample_search, title=Microsoft Code Sample Search, description=Search for code snippets and examples in official Microsoft Learn documentation. This tool retrieves relevant code samples from Microsoft documentation pages providing developers with practical implementation examples and best practices for Microsoft/Azure products and services related coding tasks. This tool will help you use the **LATEST OFFICIAL** code snippets to empower coding capabilities.\n", + "\n", + "## When to Use This Tool\n", + "- When you are going to provide sample Microsoft/Azure related code snippets in your answers.\n", + "- When you are **generating any Microsoft/Azure related code**.\n", + "\n", + "## Usage Pattern\n", + "Input a descriptive query, or SDK/class/method name to retrieve related code samples. The optional parameter `language` can help to filter results.\n", + "\n", + "Eligible values for `language` parameter include: csharp javascript typescript python powershell azurecli al sql java kusto cpp go rust ruby php, inputSchema=Input(properties={\"query\":{\"description\":\"a descriptive query, SDK name, method name or code snippet related to Microsoft/Azure products, services, platforms, developer tools, frameworks, APIs or SDKs\",\"type\":\"string\"},\"language\":{\"description\":\"Optional parameter specifying the programming language of code snippets to retrieve. Can significantly improve search quality if provided. Eligible values: csharp javascript typescript python powershell azurecli al sql java kusto cpp go rust ruby php\",\"type\":\"string\",\"default\":null}}, required=[query]), outputSchema=null, annotations=ToolAnnotations(title=Microsoft Code Sample Search, readOnlyHint=true, destructiveHint=false, idempotentHint=true, openWorldHint=true), _meta={})" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "data": { + "text/plain": [ + "Tool(name=microsoft_docs_fetch, title=Microsoft Docs Fetch, description=Fetch and convert a Microsoft Learn documentation page to markdown format. This tool retrieves the latest complete content of Microsoft documentation pages including Azure, .NET, Microsoft 365, and other Microsoft technologies.\n", + "\n", + "## When to Use This Tool\n", + "- When search results provide incomplete information or truncated content\n", + "- When you need complete step-by-step procedures or tutorials\n", + "- When you need troubleshooting sections, prerequisites, or detailed explanations\n", + "- When search results reference a specific page that seems highly relevant\n", + "- For comprehensive guides that require full context\n", + "\n", + "## Usage Pattern\n", + "Use this tool AFTER microsoft_docs_search when you identify specific high-value pages that need complete content. The search tool gives you an overview; this tool gives you the complete picture.\n", + "\n", + "## URL Requirements\n", + "- The URL must be a valid link from the microsoft.com domain.\n", + "\n", + "## Output Format\n", + "markdown with headings, code blocks, tables, and links preserved., inputSchema=Input(properties={\"url\":{\"description\":\"URL of the Microsoft documentation page to read\",\"type\":\"string\"}}, required=[url]), outputSchema=null, annotations=ToolAnnotations(title=Microsoft Docs Fetch, readOnlyHint=true, destructiveHint=false, idempotentHint=true, openWorldHint=true), _meta={})" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + } + ], + "execution_count": 16 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Let's call a tool:" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2025-11-14T14:12:50.568880Z", + "start_time": "2025-11-14T14:12:49.524596Z" + } + }, + "cell_type": "code", + "source": [ + "runBlocking {\n", + " val toolCallResult = mcpClient.callTool(\n", + " name = \"microsoft_docs_search\",\n", + " arguments = mapOf(\"query\" to \"Linux\")\n", + " )\n", + " toolCallResult?.let {\n", + " for (content in it.content) {\n", + " println(content)\n", + " }\n", + " }\n", + "}\n" + ], + "outputs": [ + { + "data": { + "text/html": [ + "
HTTP Log
REQUEST: https://learn.microsoft.com/api/mcp\n",
+       "METHOD: POST\n",
+       "COMMON HEADERS\n",
+       "-> Accept: application/json, text/event-stream\n",
+       "-> Accept-Charset: UTF-8\n",
+       "-> mcp-session-id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n",
+       "CONTENT HEADERS\n",
+       "-> Content-Length: 129\n",
+       "-> Content-Type: application/json\n",
+       "BODY Content-Type: application/json\n",
+       "BODY START\n",
+       "{\"id\":8,\"method\":\"tools/call\",\"params\":{\"name\":\"microsoft_docs_search\",\"arguments\":{\"query\":\"Linux\"},\"_meta\":{}},\"jsonrpc\":\"2.0\"}\n",
+       "BODY END
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "data": { + "text/html": [ + "
HTTP Log
RESPONSE: 200 OK\n",
+       "METHOD: POST\n",
+       "FROM: https://learn.microsoft.com/api/mcp\n",
+       "COMMON HEADERS\n",
+       "-> Akamai-Cache-Status: Miss from child, Miss from parent\n",
+       "-> Cache-Control: no-cache, no-store\n",
+       "-> Connection: keep-alive\n",
+       "-> Content-Encoding: identity\n",
+       "-> Content-Length: 22458\n",
+       "-> Content-Type: text/event-stream\n",
+       "-> Date: Fri, 14 Nov 2025 14:12:50 GMT\n",
+       "-> Expires: Fri, 14 Nov 2025 14:12:50 GMT\n",
+       "-> Mcp-Session-Id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n",
+       "-> Request-Context: appId=cid-v1:ccd19013-8f20-4877-8d3f-84a0107e7119\n",
+       "-> Set-Cookie: ASLBSA=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; Path=/; Secure; HttpOnly;; ASLBSACORS=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; SameSite=none; Path=/; Secure; HttpOnly;\n",
+       "-> Strict-Transport-Security: max-age=31536000; includeSubDomains; preload\n",
+       "-> X-Content-Type-Options: nosniff\n",
+       "-> X-Powered-By: ASP.NET\n",
+       "-> nel: {\"report_to\":\"network-errors\",\"max_age\":604800,\"success_fraction\":0.01,\"failure_fraction\":1.0}\n",
+       "-> report-to: {\"group\":\"network-errors\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://mdec.nelreports.net/api/report?cat=mdocs\"}]}\n",
+       "-> x-azure-ref: 20251114T141249Z-169b4b8d6588pb5bhC1AMS2m0w00000017n000000000w3q8\n",
+       "BODY Content-Type: text/event-stream\n",
+       "BODY START\n",
+       "event: message\n",
+       "data: {\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"[{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\n## Step 2 - Choose a Linux distribution\\\\nThe different versions of Linux are called \\\\u0022distributions\\\\u0022 (sometimes shortened to \\\\u201Cdistros\\\\u201D). So there isn\\\\u2019t actually a way to just \\\\u201Cinstall Linux\\\\u201D, you first need to choose which distribution that uses the Linux kernel you want to download and install. There are over 600 active Linux distributions, such as Ubuntu, Debian, Kali Linux, openSUSE, and more. There are a number of factors to consider in choosing a distribution, such as:\\\\n### Required Linux experience\\\\nIf you\\\\u2019re new to Linux, you may want to start with Ubuntu. This is one of the most common starting places for Linux users due to the level of support and ease of use. Ubuntu offers a desktop-version and a server-version depending on how you plan to use it. The specific tasks that the operating system will be responsible for may help you determine which distribution to use. Arch Linux is a popular choice for those who want a highly customizable, do-it-yourself approach that is still stable and well-supported by a large user base. It is a much more complicated place to start, but can help you to get a better understanding of how Linux works due to the amount of custom configuration.\\\\n### System requirements\\\\nA minimal, or \\\\u0022lightweight\\\\u0022, distribution means a small footprint, with lower memory and processing speed requirements. Alpine Linux is a popular option where users will find most features disabled or not installed by default, but still has everything you need for a containerized app. Security needs: Some Linux users may have specific security concerns to consider. Kali Linux is an example of a distro designed for security tasks, such as Penetration Testing, Security Research, Computer Forensics and Reverse Engineering.\\\\n### Business and Enterprise requirements\\\\nBusiness, corporate, or academic environments may have unique concerns from consumer environments. For example Red Hat Enterprise Linux (RHEL), SUSE Linux Enterprise Server (SLES) and Oracle Linux offer 24/7 global support and subscription-based service. CentOS is another popular enterprise distro that is community-supported but still compatible with RHEL.\\\\n### Community support\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/linux/install#step-2---choose-a-linux-distribution\\u0022},{\\u0022title\\u0022:\\u0022New to Linux resources for SQL users\\u0022,\\u0022content\\u0022:\\u0022# New to Linux resources for SQL users\\\\n**Applies to:**[SQL Server](https://learn.microsoft.com/en-us/sql/sql-server/sql-docs-navigation-guide?view=sql-server-ver17#applies-to) - Linux\\\\nThis article is intended to provide a learning path through an introduction of Linux concepts. Use the sections in the article as a guided learning path to familiarize yourself with the Linux environment.\\\\nThis isn\\\\u0027t intended to be a comprehensive list but instead provides the bare minimum you need to help you administer and navigate your SQL Server on Linux environment. See the [full list of tutorials](https://www.linux.org/forums/linux-beginner-tutorials.123/) to learn more.\\\\n## What is Linux?\\\\nThe [What is Linux](https://www.linux.org/threads/what-is-linux.4106/) module introduces the history of Linux as an operating system. The module explains *the Kernel* and where Linux is today. This tutorial serves as an introduction to Linux, helping you get started.\\\\n## Select a distribution\\\\nAfter you know the history of Linux, decide which [distribution of Linux](https://www.linux.org/threads/selecting-a-linux-distribution.4117/) best suits your business needs. [SQL Server is supported](https://learn.microsoft.com/en-us/sql/linux/sql-server-linux-release-notes-2019?view=sql-server-ver17#supported-platforms) on various distributions such as Red Hat Enterprise Linux (RHEL), SUSE Linux Enterprise Server (SLES), and Ubuntu.\\\\n## Get around directories\\\\nAfter you have chosen the appropriate distribution of Linux, familiarize yourself with [getting around Linux directories](https://www.linux.org/threads/getting-around-in-linux-directories.4120/).\\\\nThis module will help you answer the following questions:\\\\n1. How to navigate between different files\\\\n2. How to identify a file from a directory\\\\n3. Identify different directories\\\\n## Install new software\\\\nNext, learn how to [install new software](https://www.linux.org/threads/installing-new-software-debian-red-hat-slackware.4119/) on your new Linux operating system. This module details installing new software to the Debian and Red Hat Linux operating systems.\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/sql/linux/new-to-linux-resources?view=sql-server-ver17\\u0022},{\\u0022title\\u0022:\\u0022Part 1.2 - Basic operations on Linux\\u0022,\\u0022content\\u0022:\\u0022# Part 1.2 - Basic operations on Linux\\\\n## Run commands by using the \\\\u0022sudo\\\\u0022 prefix\\\\nInstead of becoming the root account user in your session, you can run commands as root by using the \\\\u0060sudo \\\\u003Ccommand\\\\u003E\\\\u0060 format. This approach is safer because it will run the given command only as a superuser.\\\\nFor example, if you run \\\\u0060restart\\\\u0060 while you\\\\u0027re not elevated to superuser status, the result is as follows.\\\\n![Screenshot of restart command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/restart-command.png)\\\\nTo run this command as a superuser instead, you can either change the session context to root (by running \\\\u0060sudo su\\\\u0060) or add the \\\\u0060sudo\\\\u0060 prefix, as follows:\\\\n\\\\u0060\\\\u0060\\\\u0060bash\\\\nsudo reboot\\\\n\\\\u0060\\\\u0060\\\\u0060\\\\nNow, the restart operation runs as expected by the virtual machine.\\\\n![Screenshot of reboot command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/reboot-command.png)\\\\n## Package managers\\\\nPackage managers are used to install, upgrade, and remove applications in Linux. There are several different package managers, such as DPKG (Debian Package Management System), RPM (Red Hat Package Manager), and Zypper for SUSE Package managers themselves are low-level. Therefore, it might be difficult to manage software directly by using the package manager. Fortunately, there are wrappers for these tools that make it easier to install and uninstall software. For example, [yum](https://wikipedia.org/wiki/Yum_%28software%29) is a wrapper around RPM (Red Hat Package Manager) and [APT](https://wikipedia.org/wiki/APT_%28software%29) is a wrapper around DPKG (Debian Package Management System).\\\\nThis tutorial assumes that you\\\\u0027re running the [Ubuntu package management system](https://ubuntu.com/server/docs/package-management). This system is derived from the same system that\\\\u0027s used by the Debian GNU/Linux distribution. Therefore, we can use APT to install the software.\\\\nDepending on your Linux choice, you might have to use other tools. For example, you can use yum to install or uninstall software if you\\\\u0027re running Red Hat.\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-by-using-the-sudo-prefix\\u0022},{\\u0022title\\u0022:\\u0022Part 1.2 - Basic operations on Linux\\u0022,\\u0022content\\u0022:\\u0022# Part 1.2 - Basic operations on Linux\\\\n## Run commands as a superuser\\\\nThe superuser is the most privileged user account. It has root (unrestricted) access to all files and folders, and complete control over the operation of the computer. The system administrator uses this account for system maintenance.\\\\nOn UNIX-like systems, the conventional name of the superuser is \\\\u0022root\\\\u0022. The root user can do tasks that are otherwise restricted for standard users, including the following ones:\\\\n1. Changing system directories\\\\n2. Changing user privileges\\\\n3. Changing file ownership\\\\n4. System-related tasks such as restarts\\\\n5. Installation of certain applications\\\\nThere are several ways to run commands as the root account user. One is to use the \\\\u0060sudo su\\\\u0060 command. In this command, \\\\u0060sudo\\\\u0060 is short for \\\\u0060superuser do\\\\u0060, and \\\\u0060su\\\\u0060 is an abbreviation for \\\\u0022switch user.\\\\u0022 You can also use the \\\\u0060su\\\\u0060 command to switch between standard users.\\\\nImportant\\\\nAfter you become the root account user, everything you do runs in root context. Therefore, you should act carefully because the root account has access to the entire system.\\\\nTo demonstrate a permissions issue, create a text file in a special folder that\\\\u0027s named */etc*.\\\\nThe */etc/* folder is where system configuration files are located. This folder protects its contents from being changed by a standard user.\\\\nRun the following command:\\\\n\\\\u0060\\\\u0060\\\\u0060bash\\\\necho hello world \\\\u003E /etc/helloworld.txt\\\\n\\\\u0060\\\\u0060\\\\u0060\\\\nThe \\\\u0060echo\\\\u0060 command writes all the text that follows it to the output. The angle bracket (\\\\u0060\\\\u003E\\\\u0060) tells the system to send the output to the */etc/helloworld.txt* file instead of the console. This behavior is similar to how Windows works.\\\\nNote\\\\nLinux is case-sensitive: *Helloworld.txt*, *helloworld.txt* and *helloworld.Txt* are different files.\\\\nYou don\\\\u0027t have to provide a file name extension. The name \\\\u0022helloworld\\\\u0022 by itself is perfectly valid. (The \\\\u0022txt\\\\u0022 extension is used here only as an example.)\\\\nWhen you run this command, you receive a **permission denied** error message.\\\\n![Screenshot of permission denied message.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/permission-denied-message.png)\\\\nThe operation fails because the */etc* folder is special folder that a standard user can\\\\u0027t change. Verify the user by running the \\\\u0060id\\\\u0060 command.\\\\n![Screenshot of I D command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/id-command.png)\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-as-a-superuser\\u0022},{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\n## Step 1 - Choose a method to install Linux\\\\nThe choice of which method to use for installing Linux depends on your needs and preferences.\\\\n1. **New to using Linux?** We recommend starting with Windows Subsystem for Linux (WSL) as it\\\\u0027s the easiest way to get up and running.\\\\n2. **Work in a business environment with more complex needs related to scale or security?** We recommend running Linux as a Virtual Machine (VM) in the cloud and checking out the support that Azure has to offer. This also applies if you want to run Linux as a server.\\\\n3. **Only want to run Linux as your primary operating system?** If you are good with a slightly more complicated install process and don\\\\u0027t need access to Windows tools (like Outlook, Teams, Word, PowerPoint, etc), you can run Linux on bare metal to access the full potential of your hardware without any overhead from virtualization or emulation.\\\\nLearn more about these options below.\\\\n### Windows Subsystem for Linux (WSL)\\\\nAvailable to those with a Windows machine, this is the most simple way to install Linux. Just run the Linux install command: \\\\u0060wsl --install\\\\u0060 to install the Ubuntu distribution.\\\\nThis method uses virtualization to integrate the Windows operating system (OS) with the Linux OS (running on an actual Linux kernel). You can add as many different Linux distributions as you please and run your favorite Linux tools, including GUI apps, alongside your favorite Windows tools. You can also mix and match Bash and PowerShell commands in the same command line. A Linux file system hosts your project files, while a separate Windows file systems hosts any developer tools, such as VS Code, that you\\\\u0027d like to work on the files with. This method of running Linux is highly efficient and performant. Once you choose a Linux distribution or opt to use the default Ubuntu distro, follow the instructions to Use the install Linux command with Windows Subsystem for Linux or learn more in the [WSL documentation](https://learn.microsoft.com/en-us/windows/wsl).\\\\n### Create a Virtual Machine (VM)\\\\nAvailable to everyone regardless if you\\\\u0027re using a Windows or MacOS-based machine, but will require either an Azure account, if creating a VM in the cloud, or virtualization software, if creating a VM directly on your machine (locally).\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/linux/install#step-1---choose-a-method-to-install-linux\\u0022},{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\nLinux is an operating system, similar to Windows, but with many different versions due to the nature of being open source and fully customizable. To install Linux, you must choose an install method and choose a Linux distribution.\\\\nTo install Linux:\\\\n1. Choose an install method: Windows Subsystem for Linux (WSL), Bare metal Linux; or create a Virtual Machine (VM) to run Linux locally or in the cloud.\\\\n2. Choose a Linux distribution: Ubuntu, Debian, Kali Linux, openSUSE, etc.\\\\n3. Follow the steps for your preferred install method:\\\\n3.1. Use the install Linux command with Windows Subsystem for Linux (WSL)\\\\n3.2. Create a Linux Virtual Machine (VM) in the cloud\\\\n3.3. Create a Linux Virtual Machine (VM) on your local machine\\\\n3.4. Create a bootable USB to install bare-metal Linux\\\\n4. After installing Linux: Get familiar with your distribution\\\\u0027s package manager, update and upgrade the packages available, and get familiar with the other [Linux resources at Microsoft](https://learn.microsoft.com/en-us/linux/), such as training courses, Linux-versions of popular tools, news, and Open Source events.\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/linux/install\\u0022},{\\u0022title\\u0022:\\u0022Guide for changing your dev environment from Mac to Windows\\u0022,\\u0022content\\u0022:\\u0022# Guide for changing your dev environment from Mac to Windows\\\\n## Command-line shells and terminals\\\\nYou can now install Windows Subsystem for Linux (WSL) to support running a Linux shell within Windows. This means that you can run **bash**, with whichever specific Linux distribution you choose, integrated right inside Windows. Using WSL provides the kind of environment most familiar to Mac users. For example, you use **ls** to list the files in a current directory, not **dir** as you would with the traditional Windows Cmd Shell. To learn about installing and using WSL, see the [Windows Subsystem for Linux Installation Guide](https://learn.microsoft.com/en-us/windows/wsl/install). Linux distributions that you can install on Windows with WSL include:\\\\n1. [Ubuntu 20.04 LTS](https://www.microsoft.com/store/apps/9n6svws3rx71)\\\\n2. [Kali Linux](https://www.microsoft.com/store/apps/9PKR34TNCV07)\\\\n3. [Debian GNU/Linux](https://www.microsoft.com/store/apps/9MSVKQC78PK6)\\\\n4. [openSUSE Leap 15.1](https://www.microsoft.com/store/apps/9NJFZK00FGKV)\\\\n5. [SUSE Linux Enterprise Server 15 SP1](https://www.microsoft.com/store/apps/9PN498VPMF3Z)\\\\nJust to name a few. Find more in the [WSL install docs](https://learn.microsoft.com/en-us/windows/wsl/install-win10#install-your-linux-distribution-of-choice) and install them directly from the [Microsoft Store](https://aka.ms/wslstore).\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/windows/dev-environment/mac-to-windows#command-line-shells-and-terminals\\u0022},{\\u0022title\\u0022:\\u0022Supported Linux operating system versions\\u0022,\\u0022content\\u0022:\\u0022# Supported Linux operating system versions\\\\n## Universal Linux (Debian package)\\\\nSupported versions:\\\\n1. Debian 8, 9, 10, 11, and 12\\\\n2. Ubuntu 16.04, 18.04, 20.04, 22.04 and 24.04\\\\nNote\\\\n1. Ubuntu 22.04 is supported from Operations Manager 2022 UR1 and later when you apply [this hotfix](https://support.microsoft.com/topic/system-center-operations-manager-2022-now-has-openssl3-0-integration-kb-5024286-331bd221-10f9-42d5-bc06-775eaabe3081).\\\\n2. Ubuntu 24.04 is supported from Operations Manager 2022 UR2 and later.\\\\n| Required package | Description | Minimum version| \\\\n|  --- | --- | ---  |\\\\n| libc6 | C Standard shared library | 2.24-11 |\\\\n| OpenSSL | OpenSSL Libraries; Secure Network Communications Protocol | 1.0, 1.1 or 3.0 and later |\\\\n| PAM | Pluggable Authentication Modules | 1.1.8-3.1 |\\\\n\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/system-center/scom/plan-supported-crossplat-linux-os?view=sc-om-2025#universal-linux-debian-package\\u0022},{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\n## Step 3 - Follow install method instructions\\\\n### Install Linux with Windows Subsystem for Linux\\\\nTo install Linux on a Windows PC, use the install Linux command.\\\\n1. Open PowerShell or Windows Command Prompt in *administrator mode* by right-clicking and selecting \\\\u0022Run as administrator\\\\u0022.\\\\n2. Enter the Linux install command: \\\\u0060wsl --install\\\\u0060.\\\\n3. Restart your machine.\\\\n#### Install Linux command options\\\\nBy default, the install command will use the Ubuntu distribution of Linux. You can see the other distributions that are available in the Microsoft Store by entering the command: \\\\u0060wsl -l -o\\\\u0060. If you\\\\u0027d like to install a distribution other than Ubuntu, just add \\\\u0060-d\\\\u0060 to the command with the name of the distribution you\\\\u0027d like to install:\\\\n\\\\u0060\\\\u0060\\\\u0060powershell\\\\nwsl --install -d \\\\u003CDistro Name\\\\u003E\\\\n\\\\u0060\\\\u0060\\\\u0060\\\\nIf the distribution you want to install isn\\\\u0027t available in the Microsoft Store, see the Windows Subsystem for Linux (WSL) documentation on how to [Import any Linux distribution to use with WSL](https://learn.microsoft.com/en-us/windows/wsl/use-custom-distro).\\\\nFor more information on how to use VS Code, Git, databases, Docker, GUI apps, GPU acceleration, NodesJS, USB devices, or mount a disk, build a custom distribution, manage disk space, or set up WSL for your enterprise business, see the [WSL documentation](https://learn.microsoft.com/en-us/windows/wsl/).\\\\n### Create a Linux Virtual Machine in the cloud\\\\nTo create a Linux Virtual Machine (VM) that is hosted in the cloud on Azure, log-in to the Azure Portal and select the \\\\u201CCreate a Virtual Machine\\\\u201D service. Then follow the instructions for creating a user name, project, instance, SSH key, ports, and copying the IP address.\\\\nAzure supports a variety of options for setting up Linux VMs in the cloud. For a quickstart guide on how to set up a Linux VM, choose your preferred method:\\\\n1. [Azure Portal](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-portal?tabs=ubuntu\\\\u0026source=docs)\\\\n2. [Azure CLI](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-cli)\\\\n3. [Azure PowerShell](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-powershell)\\\\n4. [Terraform](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-terraform)\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/linux/install#step-3---follow-install-method-instructions\\u0022},{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\n## Step 1 - Choose a method to install Linux\\\\nYou are responsible for managing the virtual machine when using a hypervisor, including allocating resources like memory and disk space, and ensuring that it is secure and up-to-date. This requires more technical expertise than some of the other options and may not be as scalable or fault-tolerant.\\\\nOnce you\\\\u0027ve chosen a distribution and decided whether you want to run the VM locally, using a hypervisor, or in the cloud, using Azure, follow the instructions to Create a Linux Virtual Machine in the Cloud or Create a Linux Virtual Machine locally using a Hypervisor\\\\n### Bare metal Linux\\\\nBare metal Linux just means that Linux is running directly on the device hardware. This install method requires you to create a bootable USB drive by downloading an iso install file from the site hosting your chosen Linux distribution. You will need to use a Windows computer (or any desktop device with an existing OS) to create this drive.\\\\nMany users choose the traditional method of installing bare metal Linux on a device that is also running Windows and using the **\\\\u0022dual boot\\\\u0022** method. To boot Linux and Windows, you need to partition your hard drive to create separate spaces for both the Linux and Windows OS. The performance speed between WSL, virtual machines, and bare metal Linux has become so close that few developers choose this method due to the overhead of needing to restart (reboot) your device any time you want to switch between the operating systems. If you choose the bare metal Linux install route, you may also need to deal with potential driver issues or hardware compatibility problems that may arise with Linux on some devices.\\\\nYou can find a list of [Popular Linux Distributions available for download on Linux.org](https://linux.org/pages/download/). Once you\\\\u0027ve chosen a distro, follow the instructions to Create a bootable USB to install bare-metal Linux.\\\\nWarning\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/linux/install#step-1---choose-a-method-to-install-linux\\u0022}]\"}]},\"id\":8,\"jsonrpc\":\"2.0\"}\n",
+       "\n",
+       "\n",
+       "BODY END
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "TextContent(text=[{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\n## Step 2 - Choose a Linux distribution\\nThe different versions of Linux are called \\u0022distributions\\u0022 (sometimes shortened to \\u201Cdistros\\u201D). So there isn\\u2019t actually a way to just \\u201Cinstall Linux\\u201D, you first need to choose which distribution that uses the Linux kernel you want to download and install. There are over 600 active Linux distributions, such as Ubuntu, Debian, Kali Linux, openSUSE, and more. There are a number of factors to consider in choosing a distribution, such as:\\n### Required Linux experience\\nIf you\\u2019re new to Linux, you may want to start with Ubuntu. This is one of the most common starting places for Linux users due to the level of support and ease of use. Ubuntu offers a desktop-version and a server-version depending on how you plan to use it. The specific tasks that the operating system will be responsible for may help you determine which distribution to use. Arch Linux is a popular choice for those who want a highly customizable, do-it-yourself approach that is still stable and well-supported by a large user base. It is a much more complicated place to start, but can help you to get a better understanding of how Linux works due to the amount of custom configuration.\\n### System requirements\\nA minimal, or \\u0022lightweight\\u0022, distribution means a small footprint, with lower memory and processing speed requirements. Alpine Linux is a popular option where users will find most features disabled or not installed by default, but still has everything you need for a containerized app. Security needs: Some Linux users may have specific security concerns to consider. Kali Linux is an example of a distro designed for security tasks, such as Penetration Testing, Security Research, Computer Forensics and Reverse Engineering.\\n### Business and Enterprise requirements\\nBusiness, corporate, or academic environments may have unique concerns from consumer environments. For example Red Hat Enterprise Linux (RHEL), SUSE Linux Enterprise Server (SLES) and Oracle Linux offer 24/7 global support and subscription-based service. CentOS is another popular enterprise distro that is community-supported but still compatible with RHEL.\\n### Community support\",\"contentUrl\":\"https://learn.microsoft.com/en-us/linux/install#step-2---choose-a-linux-distribution\"},{\"title\":\"New to Linux resources for SQL users\",\"content\":\"# New to Linux resources for SQL users\\n**Applies to:**[SQL Server](https://learn.microsoft.com/en-us/sql/sql-server/sql-docs-navigation-guide?view=sql-server-ver17#applies-to) - Linux\\nThis article is intended to provide a learning path through an introduction of Linux concepts. Use the sections in the article as a guided learning path to familiarize yourself with the Linux environment.\\nThis isn\\u0027t intended to be a comprehensive list but instead provides the bare minimum you need to help you administer and navigate your SQL Server on Linux environment. See the [full list of tutorials](https://www.linux.org/forums/linux-beginner-tutorials.123/) to learn more.\\n## What is Linux?\\nThe [What is Linux](https://www.linux.org/threads/what-is-linux.4106/) module introduces the history of Linux as an operating system. The module explains *the Kernel* and where Linux is today. This tutorial serves as an introduction to Linux, helping you get started.\\n## Select a distribution\\nAfter you know the history of Linux, decide which [distribution of Linux](https://www.linux.org/threads/selecting-a-linux-distribution.4117/) best suits your business needs. [SQL Server is supported](https://learn.microsoft.com/en-us/sql/linux/sql-server-linux-release-notes-2019?view=sql-server-ver17#supported-platforms) on various distributions such as Red Hat Enterprise Linux (RHEL), SUSE Linux Enterprise Server (SLES), and Ubuntu.\\n## Get around directories\\nAfter you have chosen the appropriate distribution of Linux, familiarize yourself with [getting around Linux directories](https://www.linux.org/threads/getting-around-in-linux-directories.4120/).\\nThis module will help you answer the following questions:\\n1. How to navigate between different files\\n2. How to identify a file from a directory\\n3. Identify different directories\\n## Install new software\\nNext, learn how to [install new software](https://www.linux.org/threads/installing-new-software-debian-red-hat-slackware.4119/) on your new Linux operating system. This module details installing new software to the Debian and Red Hat Linux operating systems.\",\"contentUrl\":\"https://learn.microsoft.com/en-us/sql/linux/new-to-linux-resources?view=sql-server-ver17\"},{\"title\":\"Part 1.2 - Basic operations on Linux\",\"content\":\"# Part 1.2 - Basic operations on Linux\\n## Run commands by using the \\u0022sudo\\u0022 prefix\\nInstead of becoming the root account user in your session, you can run commands as root by using the \\u0060sudo \\u003Ccommand\\u003E\\u0060 format. This approach is safer because it will run the given command only as a superuser.\\nFor example, if you run \\u0060restart\\u0060 while you\\u0027re not elevated to superuser status, the result is as follows.\\n![Screenshot of restart command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/restart-command.png)\\nTo run this command as a superuser instead, you can either change the session context to root (by running \\u0060sudo su\\u0060) or add the \\u0060sudo\\u0060 prefix, as follows:\\n\\u0060\\u0060\\u0060bash\\nsudo reboot\\n\\u0060\\u0060\\u0060\\nNow, the restart operation runs as expected by the virtual machine.\\n![Screenshot of reboot command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/reboot-command.png)\\n## Package managers\\nPackage managers are used to install, upgrade, and remove applications in Linux. There are several different package managers, such as DPKG (Debian Package Management System), RPM (Red Hat Package Manager), and Zypper for SUSE Package managers themselves are low-level. Therefore, it might be difficult to manage software directly by using the package manager. Fortunately, there are wrappers for these tools that make it easier to install and uninstall software. For example, [yum](https://wikipedia.org/wiki/Yum_%28software%29) is a wrapper around RPM (Red Hat Package Manager) and [APT](https://wikipedia.org/wiki/APT_%28software%29) is a wrapper around DPKG (Debian Package Management System).\\nThis tutorial assumes that you\\u0027re running the [Ubuntu package management system](https://ubuntu.com/server/docs/package-management). This system is derived from the same system that\\u0027s used by the Debian GNU/Linux distribution. Therefore, we can use APT to install the software.\\nDepending on your Linux choice, you might have to use other tools. For example, you can use yum to install or uninstall software if you\\u0027re running Red Hat.\",\"contentUrl\":\"https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-by-using-the-sudo-prefix\"},{\"title\":\"Part 1.2 - Basic operations on Linux\",\"content\":\"# Part 1.2 - Basic operations on Linux\\n## Run commands as a superuser\\nThe superuser is the most privileged user account. It has root (unrestricted) access to all files and folders, and complete control over the operation of the computer. The system administrator uses this account for system maintenance.\\nOn UNIX-like systems, the conventional name of the superuser is \\u0022root\\u0022. The root user can do tasks that are otherwise restricted for standard users, including the following ones:\\n1. Changing system directories\\n2. Changing user privileges\\n3. Changing file ownership\\n4. System-related tasks such as restarts\\n5. Installation of certain applications\\nThere are several ways to run commands as the root account user. One is to use the \\u0060sudo su\\u0060 command. In this command, \\u0060sudo\\u0060 is short for \\u0060superuser do\\u0060, and \\u0060su\\u0060 is an abbreviation for \\u0022switch user.\\u0022 You can also use the \\u0060su\\u0060 command to switch between standard users.\\nImportant\\nAfter you become the root account user, everything you do runs in root context. Therefore, you should act carefully because the root account has access to the entire system.\\nTo demonstrate a permissions issue, create a text file in a special folder that\\u0027s named */etc*.\\nThe */etc/* folder is where system configuration files are located. This folder protects its contents from being changed by a standard user.\\nRun the following command:\\n\\u0060\\u0060\\u0060bash\\necho hello world \\u003E /etc/helloworld.txt\\n\\u0060\\u0060\\u0060\\nThe \\u0060echo\\u0060 command writes all the text that follows it to the output. The angle bracket (\\u0060\\u003E\\u0060) tells the system to send the output to the */etc/helloworld.txt* file instead of the console. This behavior is similar to how Windows works.\\nNote\\nLinux is case-sensitive: *Helloworld.txt*, *helloworld.txt* and *helloworld.Txt* are different files.\\nYou don\\u0027t have to provide a file name extension. The name \\u0022helloworld\\u0022 by itself is perfectly valid. (The \\u0022txt\\u0022 extension is used here only as an example.)\\nWhen you run this command, you receive a **permission denied** error message.\\n![Screenshot of permission denied message.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/permission-denied-message.png)\\nThe operation fails because the */etc* folder is special folder that a standard user can\\u0027t change. Verify the user by running the \\u0060id\\u0060 command.\\n![Screenshot of I D command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/id-command.png)\",\"contentUrl\":\"https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-as-a-superuser\"},{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\n## Step 1 - Choose a method to install Linux\\nThe choice of which method to use for installing Linux depends on your needs and preferences.\\n1. **New to using Linux?** We recommend starting with Windows Subsystem for Linux (WSL) as it\\u0027s the easiest way to get up and running.\\n2. **Work in a business environment with more complex needs related to scale or security?** We recommend running Linux as a Virtual Machine (VM) in the cloud and checking out the support that Azure has to offer. This also applies if you want to run Linux as a server.\\n3. **Only want to run Linux as your primary operating system?** If you are good with a slightly more complicated install process and don\\u0027t need access to Windows tools (like Outlook, Teams, Word, PowerPoint, etc), you can run Linux on bare metal to access the full potential of your hardware without any overhead from virtualization or emulation.\\nLearn more about these options below.\\n### Windows Subsystem for Linux (WSL)\\nAvailable to those with a Windows machine, this is the most simple way to install Linux. Just run the Linux install command: \\u0060wsl --install\\u0060 to install the Ubuntu distribution.\\nThis method uses virtualization to integrate the Windows operating system (OS) with the Linux OS (running on an actual Linux kernel). You can add as many different Linux distributions as you please and run your favorite Linux tools, including GUI apps, alongside your favorite Windows tools. You can also mix and match Bash and PowerShell commands in the same command line. A Linux file system hosts your project files, while a separate Windows file systems hosts any developer tools, such as VS Code, that you\\u0027d like to work on the files with. This method of running Linux is highly efficient and performant. Once you choose a Linux distribution or opt to use the default Ubuntu distro, follow the instructions to Use the install Linux command with Windows Subsystem for Linux or learn more in the [WSL documentation](https://learn.microsoft.com/en-us/windows/wsl).\\n### Create a Virtual Machine (VM)\\nAvailable to everyone regardless if you\\u0027re using a Windows or MacOS-based machine, but will require either an Azure account, if creating a VM in the cloud, or virtualization software, if creating a VM directly on your machine (locally).\",\"contentUrl\":\"https://learn.microsoft.com/en-us/linux/install#step-1---choose-a-method-to-install-linux\"},{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\nLinux is an operating system, similar to Windows, but with many different versions due to the nature of being open source and fully customizable. To install Linux, you must choose an install method and choose a Linux distribution.\\nTo install Linux:\\n1. Choose an install method: Windows Subsystem for Linux (WSL), Bare metal Linux; or create a Virtual Machine (VM) to run Linux locally or in the cloud.\\n2. Choose a Linux distribution: Ubuntu, Debian, Kali Linux, openSUSE, etc.\\n3. Follow the steps for your preferred install method:\\n3.1. Use the install Linux command with Windows Subsystem for Linux (WSL)\\n3.2. Create a Linux Virtual Machine (VM) in the cloud\\n3.3. Create a Linux Virtual Machine (VM) on your local machine\\n3.4. Create a bootable USB to install bare-metal Linux\\n4. After installing Linux: Get familiar with your distribution\\u0027s package manager, update and upgrade the packages available, and get familiar with the other [Linux resources at Microsoft](https://learn.microsoft.com/en-us/linux/), such as training courses, Linux-versions of popular tools, news, and Open Source events.\",\"contentUrl\":\"https://learn.microsoft.com/en-us/linux/install\"},{\"title\":\"Guide for changing your dev environment from Mac to Windows\",\"content\":\"# Guide for changing your dev environment from Mac to Windows\\n## Command-line shells and terminals\\nYou can now install Windows Subsystem for Linux (WSL) to support running a Linux shell within Windows. This means that you can run **bash**, with whichever specific Linux distribution you choose, integrated right inside Windows. Using WSL provides the kind of environment most familiar to Mac users. For example, you use **ls** to list the files in a current directory, not **dir** as you would with the traditional Windows Cmd Shell. To learn about installing and using WSL, see the [Windows Subsystem for Linux Installation Guide](https://learn.microsoft.com/en-us/windows/wsl/install). Linux distributions that you can install on Windows with WSL include:\\n1. [Ubuntu 20.04 LTS](https://www.microsoft.com/store/apps/9n6svws3rx71)\\n2. [Kali Linux](https://www.microsoft.com/store/apps/9PKR34TNCV07)\\n3. [Debian GNU/Linux](https://www.microsoft.com/store/apps/9MSVKQC78PK6)\\n4. [openSUSE Leap 15.1](https://www.microsoft.com/store/apps/9NJFZK00FGKV)\\n5. [SUSE Linux Enterprise Server 15 SP1](https://www.microsoft.com/store/apps/9PN498VPMF3Z)\\nJust to name a few. Find more in the [WSL install docs](https://learn.microsoft.com/en-us/windows/wsl/install-win10#install-your-linux-distribution-of-choice) and install them directly from the [Microsoft Store](https://aka.ms/wslstore).\",\"contentUrl\":\"https://learn.microsoft.com/en-us/windows/dev-environment/mac-to-windows#command-line-shells-and-terminals\"},{\"title\":\"Supported Linux operating system versions\",\"content\":\"# Supported Linux operating system versions\\n## Universal Linux (Debian package)\\nSupported versions:\\n1. Debian 8, 9, 10, 11, and 12\\n2. Ubuntu 16.04, 18.04, 20.04, 22.04 and 24.04\\nNote\\n1. Ubuntu 22.04 is supported from Operations Manager 2022 UR1 and later when you apply [this hotfix](https://support.microsoft.com/topic/system-center-operations-manager-2022-now-has-openssl3-0-integration-kb-5024286-331bd221-10f9-42d5-bc06-775eaabe3081).\\n2. Ubuntu 24.04 is supported from Operations Manager 2022 UR2 and later.\\n| Required package | Description | Minimum version| \\n| --- | --- | --- |\\n| libc6 | C Standard shared library | 2.24-11 |\\n| OpenSSL | OpenSSL Libraries; Secure Network Communications Protocol | 1.0, 1.1 or 3.0 and later |\\n| PAM | Pluggable Authentication Modules | 1.1.8-3.1 |\\n\",\"contentUrl\":\"https://learn.microsoft.com/en-us/system-center/scom/plan-supported-crossplat-linux-os?view=sc-om-2025#universal-linux-debian-package\"},{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\n## Step 3 - Follow install method instructions\\n### Install Linux with Windows Subsystem for Linux\\nTo install Linux on a Windows PC, use the install Linux command.\\n1. Open PowerShell or Windows Command Prompt in *administrator mode* by right-clicking and selecting \\u0022Run as administrator\\u0022.\\n2. Enter the Linux install command: \\u0060wsl --install\\u0060.\\n3. Restart your machine.\\n#### Install Linux command options\\nBy default, the install command will use the Ubuntu distribution of Linux. You can see the other distributions that are available in the Microsoft Store by entering the command: \\u0060wsl -l -o\\u0060. If you\\u0027d like to install a distribution other than Ubuntu, just add \\u0060-d\\u0060 to the command with the name of the distribution you\\u0027d like to install:\\n\\u0060\\u0060\\u0060powershell\\nwsl --install -d \\u003CDistro Name\\u003E\\n\\u0060\\u0060\\u0060\\nIf the distribution you want to install isn\\u0027t available in the Microsoft Store, see the Windows Subsystem for Linux (WSL) documentation on how to [Import any Linux distribution to use with WSL](https://learn.microsoft.com/en-us/windows/wsl/use-custom-distro).\\nFor more information on how to use VS Code, Git, databases, Docker, GUI apps, GPU acceleration, NodesJS, USB devices, or mount a disk, build a custom distribution, manage disk space, or set up WSL for your enterprise business, see the [WSL documentation](https://learn.microsoft.com/en-us/windows/wsl/).\\n### Create a Linux Virtual Machine in the cloud\\nTo create a Linux Virtual Machine (VM) that is hosted in the cloud on Azure, log-in to the Azure Portal and select the \\u201CCreate a Virtual Machine\\u201D service. Then follow the instructions for creating a user name, project, instance, SSH key, ports, and copying the IP address.\\nAzure supports a variety of options for setting up Linux VMs in the cloud. For a quickstart guide on how to set up a Linux VM, choose your preferred method:\\n1. [Azure Portal](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-portal?tabs=ubuntu\\u0026source=docs)\\n2. [Azure CLI](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-cli)\\n3. [Azure PowerShell](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-powershell)\\n4. [Terraform](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-terraform)\",\"contentUrl\":\"https://learn.microsoft.com/en-us/linux/install#step-3---follow-install-method-instructions\"},{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\n## Step 1 - Choose a method to install Linux\\nYou are responsible for managing the virtual machine when using a hypervisor, including allocating resources like memory and disk space, and ensuring that it is secure and up-to-date. This requires more technical expertise than some of the other options and may not be as scalable or fault-tolerant.\\nOnce you\\u0027ve chosen a distribution and decided whether you want to run the VM locally, using a hypervisor, or in the cloud, using Azure, follow the instructions to Create a Linux Virtual Machine in the Cloud or Create a Linux Virtual Machine locally using a Hypervisor\\n### Bare metal Linux\\nBare metal Linux just means that Linux is running directly on the device hardware. This install method requires you to create a bootable USB drive by downloading an iso install file from the site hosting your chosen Linux distribution. You will need to use a Windows computer (or any desktop device with an existing OS) to create this drive.\\nMany users choose the traditional method of installing bare metal Linux on a device that is also running Windows and using the **\\u0022dual boot\\u0022** method. To boot Linux and Windows, you need to partition your hard drive to create separate spaces for both the Linux and Windows OS. The performance speed between WSL, virtual machines, and bare metal Linux has become so close that few developers choose this method due to the overhead of needing to restart (reboot) your device any time you want to switch between the operating systems. If you choose the bare metal Linux install route, you may also need to deal with potential driver issues or hardware compatibility problems that may arise with Linux on some devices.\\nYou can find a list of [Popular Linux Distributions available for download on Linux.org](https://linux.org/pages/download/). Once you\\u0027ve chosen a distro, follow the instructions to Create a bootable USB to install bare-metal Linux.\\nWarning\",\"contentUrl\":\"https://learn.microsoft.com/en-us/linux/install#step-1---choose-a-method-to-install-linux\"}], annotations=null)\n" + ] + } + ], + "execution_count": 17 + } + ], + "metadata": { + "kernelspec": { + "display_name": "Kotlin", + "language": "kotlin", + "name": "kotlin" + }, + "language_info": { + "name": "kotlin", + "version": "2.2.20-Beta2", + "mimetype": "text/x-kotlin", + "file_extension": ".kt", + "pygments_lexer": "kotlin", + "codemirror_mode": "text/x-kotlin", + "nbconvert_exporter": "" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} From f24c801ee6f44651c0f3ee1ff921ba2fe78b29dc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Nov 2025 13:15:54 +0200 Subject: [PATCH 072/449] Bump org.jlleitschuh.gradle.ktlint from 13.1.0 to 14.0.1 (#402) Bumps org.jlleitschuh.gradle.ktlint from 13.1.0 to 14.0.1. [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.jlleitschuh.gradle.ktlint&package-manager=gradle&previous-version=13.1.0&new-version=14.0.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dc8c4df..d56b762 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ kotlin = "2.2.21" dokka = "2.1.0" atomicfu = "0.29.0" -ktlint = "13.1.0" +ktlint = "14.0.1" kover = "0.9.3" netty = "4.2.7.Final" mavenPublish = "0.34.0" From 42c92608dd47278680f51bd6775eece630df4cf8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Nov 2025 17:14:44 +0200 Subject: [PATCH 073/449] Bump com.vanniktech:gradle-maven-publish-plugin from 0.34.0 to 0.35.0 (#403) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [com.vanniktech:gradle-maven-publish-plugin](https://github.com/vanniktech/gradle-maven-publish-plugin) from 0.34.0 to 0.35.0.
Release notes

Sourced from com.vanniktech:gradle-maven-publish-plugin's releases.

0.35.0

  • Add support for publishing Kotlin Multiplatform libraries that use com.android.kotlin.multiplatform.library.
  • Add support for validating deployments to Central Portal
  • Raise minimum Gradle version to 8.13
  • Raise minimum Android Gradle Plugin version to 8.2.2
  • Do not unconditionally disable DocLint
  • Fail publishing if SONATYPE_HOST is not set to CENTRAL_PORTAL.
  • Fix misleading error message when Android library variant is not found.
  • Downgrade transitive OkHttp version.
  • Don't check project heirarchy for POM properties when Isolated proejcts is enabled.

Thanks to @​joshfriend, @​Flowdalic and @​Goooler for their contributions to this release.

Minimum supported versions

  • JDK 11
  • Gradle 8.13
  • Android Gradle Plugin 8.2.2
  • Kotlin Gradle Plugin 1.9.20

Compatibility tested up to

  • JDK 24
  • Gradle 9.2.0
  • Gradle 9.3.0-milestone-1
  • Android Gradle Plugin 8.13.1
  • Android Gradle Plugin 9.0.0-alpha14
  • Kotlin Gradle Plugin 2.2.21
  • Kotlin Gradle Plugin 2.3.0-Beta2

0.35.0-rc1

  • Add support for publishing Kotlin Multiplatform libraries that use com.android.kotlin.multiplatform.library.
  • Add support for validating deployments to Central Portal
  • Raise minimum Gradle version to 8.13
  • Raise minimum Android Gradle Plugin version to 8.2.2
  • Do not unconditionally disable DocLint
  • Fail publishing if SONATYPE_HOST is not set to CENTRAL_PORTAL.
  • Fix misleading error message when Android library variant is not found.
  • Downgrade transitive OkHttp version.
  • Don't check project heirarchy for POM properties when Isolated proejcts is enabled.

Thanks to @​joshfriend, @​Flowdalic and @​Goooler for their contributions to this release.

Minimum supported versions

  • JDK 11
  • Gradle 8.13
  • Android Gradle Plugin 8.2.2
  • Kotlin Gradle Plugin 1.9.20

Compatibility tested up to

  • JDK 24
  • Gradle 9.2.0

... (truncated)

Changelog

Sourced from com.vanniktech:gradle-maven-publish-plugin's changelog.

0.35.0 (2025-11-11)

  • Add support for publishing Kotlin Multiplatform libraries that use com.android.kotlin.multiplatform.library.
  • Add support for validating deployments to Central Portal
  • Raise minimum Gradle version to 8.13
  • Raise minimum Android Gradle Plugin version to 8.2.2
  • Do not unconditionally disable DocLint
  • Fail publishing if SONATYPE_HOST is not set to CENTRAL_PORTAL.
  • Fix misleading error message when Android library variant is not found.
  • Downgrade transitive OkHttp version.
  • Don't check project heirarchy for POM properties when Isolated proejcts is enabled.

Thanks to @​joshfriend, @​Flowdalic and @​Goooler for their contributions to this release.

Minimum supported versions

  • JDK 11
  • Gradle 8.13
  • Android Gradle Plugin 8.2.2
  • Kotlin Gradle Plugin 1.9.20

Compatibility tested up to

  • JDK 24
  • Gradle 9.2.0
  • Gradle 9.3.0-milestone-1
  • Android Gradle Plugin 8.13.1
  • Android Gradle Plugin 9.0.0-alpha14
  • Kotlin Gradle Plugin 2.2.21
  • Kotlin Gradle Plugin 2.3.0-Beta2
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=com.vanniktech:gradle-maven-publish-plugin&package-manager=gradle&previous-version=0.34.0&new-version=0.35.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d56b762..9c57db3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ atomicfu = "0.29.0" ktlint = "14.0.1" kover = "0.9.3" netty = "4.2.7.Final" -mavenPublish = "0.34.0" +mavenPublish = "0.35.0" binaryCompatibilityValidatorPlugin = "0.18.1" openapi-generator = "7.17.0" From 0f9209ad4a13e208d211b11f9f8e4d54fd5a47bb Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Thu, 20 Nov 2025 19:09:23 +0100 Subject: [PATCH 074/449] Handle additional error types in client initialization handling (#404) May throw the following exceptions: `CancellationException`, `McpException`, `StreamableHttpError`, and `SerializationException` ## Motivation and Context This makes it possible to handle errors directly without needing to inspect the cause. For example: ```kotlin try { client.connect(transport) } catch (e: StreamableHttpError) { // ... } ``` ## How Has This Been Tested? locally ## Breaking Changes None ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 - [x] I have added appropriate error handling - [ ] I have added or updated documentation as needed --- .../kotlin/sdk/client/Client.kt | 14 +++- .../kotlin/sdk/client/ClientTest.kt | 79 ++++++++++++++++++- 2 files changed, 88 insertions(+), 5 deletions(-) diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt index c86cad8..d861bd0 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt @@ -34,6 +34,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.ListRootsResult import io.modelcontextprotocol.kotlin.sdk.types.ListToolsRequest import io.modelcontextprotocol.kotlin.sdk.types.ListToolsResult import io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel +import io.modelcontextprotocol.kotlin.sdk.types.McpException import io.modelcontextprotocol.kotlin.sdk.types.Method import io.modelcontextprotocol.kotlin.sdk.types.PingRequest import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest @@ -54,6 +55,7 @@ import kotlinx.atomicfu.update import kotlinx.collections.immutable.minus import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.toPersistentSet +import kotlinx.serialization.SerializationException import kotlinx.serialization.json.JsonObject import kotlin.coroutines.cancellation.CancellationException @@ -196,11 +198,15 @@ public open class Client(private val clientInfo: Implementation, options: Client logger.error(error) { "Failed to initialize client: ${error.message}" } close() - if (error !is CancellationException) { - throw IllegalStateException("Error connecting to transport: ${error.message}", error) - } + when (error) { + is CancellationException, + is McpException, + is StreamableHttpError, + is SerializationException, + -> throw error - throw error + else -> throw IllegalStateException("Error connecting to transport: ${error.message}", error) + } } } diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt index d863e00..b4fbcb7 100644 --- a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt +++ b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt @@ -26,6 +26,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.ListToolsResult import io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel import io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotification import io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotificationParams +import io.modelcontextprotocol.kotlin.sdk.types.McpException import io.modelcontextprotocol.kotlin.sdk.types.Method import io.modelcontextprotocol.kotlin.sdk.types.Role import io.modelcontextprotocol.kotlin.sdk.types.Root @@ -232,6 +233,82 @@ class ClientTest { assertTrue(closed) } + @Test + fun `should rethrow McpException as is`() = runTest { + var closed = false + val failingTransport = object : AbstractTransport() { + override suspend fun start() {} + + override suspend fun send(message: JSONRPCMessage) { + if (message !is JSONRPCRequest) return + check(message.method == Method.Defined.Initialize.value) + throw McpException( + code = -32600, + message = "Invalid Request", + ) + } + + override suspend fun close() { + closed = true + } + } + + val client = Client( + clientInfo = Implementation( + name = "test client", + version = "1.0", + ), + options = ClientOptions(), + ) + + val exception = assertFailsWith { + client.connect(failingTransport) + } + + assertEquals(-32600, exception.code) + assertEquals("MCP error -32600: Invalid Request", exception.message) + + assertTrue(closed) + } + + @Test + fun `should rethrow StreamableHttpError as is`() = runTest { + var closed = false + val failingTransport = object : AbstractTransport() { + override suspend fun start() {} + + override suspend fun send(message: JSONRPCMessage) { + if (message !is JSONRPCRequest) return + check(message.method == Method.Defined.Initialize.value) + throw StreamableHttpError( + code = 500, + message = "Internal Server Error", + ) + } + + override suspend fun close() { + closed = true + } + } + + val client = Client( + clientInfo = Implementation( + name = "test client", + version = "1.0", + ), + options = ClientOptions(), + ) + + val exception = assertFailsWith { + client.connect(failingTransport) + } + + assertEquals(500, exception.code) + assertEquals("Streamable HTTP error: Internal Server Error", exception.message) + + assertTrue(closed) + } + @Test fun `should respect server capabilities`() = runTest { val serverOptions = ServerOptions( @@ -922,7 +999,7 @@ class ClientTest { println("Client connected") }, launch { - serverSessionResult.complete(server.connect(serverTransport)) + serverSessionResult.complete(server.createSession(serverTransport)) println("Server connected") }, ).joinAll() From e6e38ac0a80fe3c447d07174c32f328bd5cfacfe Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Thu, 20 Nov 2025 19:21:20 +0100 Subject: [PATCH 075/449] Fix providing progressToken with request (#405) ## Motivation and Context fix #220 ## How Has This Been Tested? locally ## Breaking Changes None ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- kotlin-sdk-core/build.gradle.kts | 1 + .../kotlin/sdk/shared/Protocol.kt | 48 +++-- .../kotlin/sdk/shared/ProtocolTest.kt | 201 ++++++++++++++++++ 3 files changed, 234 insertions(+), 16 deletions(-) create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolTest.kt diff --git a/kotlin-sdk-core/build.gradle.kts b/kotlin-sdk-core/build.gradle.kts index 4e2531b..dd095e6 100644 --- a/kotlin-sdk-core/build.gradle.kts +++ b/kotlin-sdk-core/build.gradle.kts @@ -122,6 +122,7 @@ kotlin { commonTest { dependencies { implementation(kotlin("test")) + implementation(libs.kotlinx.coroutines.test) implementation(libs.kotest.assertions.core) implementation(libs.kotest.assertions.json) } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt index 64fe355..db8b6ef 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt @@ -344,7 +344,11 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio if (handler != null) { messageId?.let { msg -> _progressHandlers.update { it.remove(msg) } } } else { - onError(Error("Received a response for an unknown message ID: ${McpJson.encodeToString(response)}")) + onError( + IllegalStateException( + "Received a response for an unknown message ID: ${McpJson.encodeToString(error ?: response)}", + ), + ) return } @@ -352,12 +356,12 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio handler(response, null) } else { check(error != null) - val error = McpException( + val mcpException = McpException( code = error.error.code, message = error.error.message, data = error.error.data, ) - handler(null, error) + handler(null, mcpException) } } @@ -403,18 +407,30 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio assertCapabilityForMethod(request.method) } - val message = request.toJSON() - val messageId = message.id + val jsonRpcRequest = request.toJSON().run { + options?.onProgress?.let { progressHandler -> + logger.trace { "Registering progress handler for request id: $id" } + _progressHandlers.update { current -> + current.put(id, progressHandler) + } - if (options?.onProgress != null) { - logger.trace { "Registering progress handler for request id: $messageId" } - _progressHandlers.update { current -> - current.put(messageId, options.onProgress) - } + val paramsObject = (this.params as? JsonObject) ?: JsonObject(emptyMap()) + val metaObject = request.params?.meta?.json ?: JsonObject(emptyMap()) + + val updatedMeta = JsonObject( + metaObject + ("progressToken" to McpJson.encodeToJsonElement(id)), + ) + val updatedParams = JsonObject( + paramsObject + ("_meta" to updatedMeta), + ) + + this.copy(params = updatedParams) + } ?: this } + val jsonRpcRequestId = jsonRpcRequest.id _responseHandlers.update { current -> - current.put(messageId) { response, error -> + current.put(jsonRpcRequestId) { response, error -> if (error != null) { result.completeExceptionally(error) return@put @@ -430,12 +446,12 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio } val cancel: suspend (Throwable) -> Unit = { reason: Throwable -> - _responseHandlers.update { current -> current.remove(messageId) } - _progressHandlers.update { current -> current.remove(messageId) } + _responseHandlers.update { current -> current.remove(jsonRpcRequestId) } + _progressHandlers.update { current -> current.remove(jsonRpcRequestId) } val notification = CancelledNotification( params = CancelledNotificationParams( - requestId = messageId, + requestId = jsonRpcRequestId, reason = reason.message ?: "Unknown", ), ) @@ -452,8 +468,8 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio val timeout = options?.timeout ?: DEFAULT_REQUEST_TIMEOUT try { withTimeout(timeout) { - logger.trace { "Sending request message with id: $messageId" } - this@Protocol.transport?.send(message) + logger.trace { "Sending request message with id: $jsonRpcRequestId" } + this@Protocol.transport?.send(jsonRpcRequest) } return result.await() } catch (cause: TimeoutCancellationException) { diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolTest.kt new file mode 100644 index 0000000..f53366c --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolTest.kt @@ -0,0 +1,201 @@ +package io.modelcontextprotocol.kotlin.sdk.shared + +import io.kotest.matchers.collections.shouldContainExactly +import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.shouldBe +import io.modelcontextprotocol.kotlin.sdk.types.CustomRequest +import io.modelcontextprotocol.kotlin.sdk.types.EmptyResult +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse +import io.modelcontextprotocol.kotlin.sdk.types.McpJson +import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.RequestMeta +import kotlinx.coroutines.async +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.test.runTest +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonObjectBuilder +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.encodeToJsonElement +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlin.test.BeforeTest +import kotlin.test.Test + +class ProtocolTest { + private lateinit var protocol: TestProtocol + private lateinit var transport: RecordingTransport + + @BeforeTest + fun setUp() { + protocol = TestProtocol() + transport = RecordingTransport() + } + + @Test + fun `should preserve existing meta when adding progress token`() = runTest { + protocol.connect(transport) + val request = ReadResourceRequest( + ReadResourceRequestParams( + uri = "test://resource", + meta = metaOf { + put("customField", JsonPrimitive("customValue")) + put("anotherField", JsonPrimitive(123)) + }, + ), + ) + + val inFlight = async { + protocol.request( + request = request, + options = RequestOptions(onProgress = {}), + ) + } + + val sent = transport.awaitRequest() + val params = sent.params?.jsonObject.shouldNotBeNull() + val meta = params["_meta"]?.jsonObject.shouldNotBeNull() + + params["uri"]?.jsonPrimitive?.content shouldBe "test://resource" + meta["customField"]?.jsonPrimitive?.content shouldBe "customValue" + meta["anotherField"]?.jsonPrimitive?.int shouldBe 123 + meta["progressToken"] shouldBe McpJson.encodeToJsonElement(sent.id) + + transport.deliver(JSONRPCResponse(sent.id, EmptyResult())) + inFlight.await() + } + + @Test + fun `should create meta with progress token when none exists`() = runTest { + protocol.connect(transport) + val request = ReadResourceRequest( + ReadResourceRequestParams( + uri = "test://resource", + meta = null, + ), + ) + + val inFlight = async { + protocol.request( + request = request, + options = RequestOptions(onProgress = {}), + ) + } + + val sent = transport.awaitRequest() + val params = sent.params?.jsonObject.shouldNotBeNull() + val meta = params["_meta"]?.jsonObject.shouldNotBeNull() + + params["uri"]?.jsonPrimitive?.content shouldBe "test://resource" + meta["progressToken"] shouldBe McpJson.encodeToJsonElement(sent.id) + + transport.deliver(JSONRPCResponse(sent.id, EmptyResult())) + inFlight.await() + } + + @Test + fun `should not modify meta when onProgress is absent`() = runTest { + protocol.connect(transport) + val originalMeta = metaJson { + put("customField", JsonPrimitive("customValue")) + } + val request = ReadResourceRequest( + ReadResourceRequestParams( + uri = "test://resource", + meta = RequestMeta(originalMeta), + ), + ) + + val inFlight = async { + protocol.request(request) + } + + val sent = transport.awaitRequest() + val params = sent.params?.jsonObject.shouldNotBeNull() + val meta = params["_meta"]?.jsonObject.shouldNotBeNull() + + meta shouldBe originalMeta + params["uri"]?.jsonPrimitive?.content shouldBe "test://resource" + + transport.deliver(JSONRPCResponse(sent.id, EmptyResult())) + inFlight.await() + } + + @Test + fun `should create params object when request params are null`() = runTest { + protocol.connect(transport) + val request = CustomRequest( + method = Method.Custom("example"), + params = null, + ) + + val inFlight = async { + protocol.request( + request = request, + options = RequestOptions(onProgress = {}), + ) + } + + val sent = transport.awaitRequest() + val params = sent.params?.jsonObject.shouldNotBeNull() + val meta = params["_meta"]?.jsonObject.shouldNotBeNull() + + params.keys shouldContainExactly setOf("_meta") + meta["progressToken"] shouldBe McpJson.encodeToJsonElement(sent.id) + + transport.deliver(JSONRPCResponse(sent.id, EmptyResult())) + inFlight.await() + } +} + +private class TestProtocol : Protocol(null) { + override fun assertCapabilityForMethod(method: Method) {} + override fun assertNotificationCapability(method: Method) {} + override fun assertRequestHandlerCapability(method: Method) {} +} + +private class RecordingTransport : Transport { + private val sentMessages = Channel(Channel.UNLIMITED) + private var onMessageCallback: (suspend (JSONRPCMessage) -> Unit)? = null + private var onCloseCallback: (() -> Unit)? = null + + override suspend fun start() {} + + override suspend fun send(message: JSONRPCMessage) { + sentMessages.send(message) + } + + override suspend fun close() { + onCloseCallback?.invoke() + } + + override fun onClose(block: () -> Unit) { + onCloseCallback = block + } + + override fun onError(block: (Throwable) -> Unit) {} + + override fun onMessage(block: suspend (JSONRPCMessage) -> Unit) { + onMessageCallback = block + } + + suspend fun awaitRequest(): JSONRPCRequest { + val message = sentMessages.receive() + return message as? JSONRPCRequest + ?: error("Expected JSONRPCRequest but received ${message::class.simpleName}") + } + + suspend fun deliver(message: JSONRPCMessage) { + val callback = onMessageCallback ?: error("onMessage callback not registered") + callback(message) + } +} + +private fun metaOf(builderAction: JsonObjectBuilder.() -> Unit): RequestMeta = RequestMeta(metaJson(builderAction)) + +private fun metaJson(builderAction: JsonObjectBuilder.() -> Unit): JsonObject = buildJsonObject(builderAction) From 74c93745cde9b746f8090115467e50d7acb607d3 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Thu, 20 Nov 2025 19:46:07 +0100 Subject: [PATCH 076/449] Proposal: DSL Builders for request types (#399) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR introduces type-safe DSL builders for all MCP request types, making it easier to construct requests ### Current Solution All request types now have DSL builders: ```kotlin // Initialize request val request = initializeRequest { protocolVersion = "2024-11-05" capabilities { sampling(ClientCapabilities.sampling) roots(listChanged = true) } info("MyClient", "1.0.0") } // Create message request with conversation val messageRequest = createMessageRequest { maxTokens = 1000 systemPrompt = "You are a helpful assistant" messages { user { "What is the capital of France?" } assistant { "The capital of France is Paris." } user { "What about Germany?" } } } // Call tool request val toolRequest = callToolRequest { name = "searchDatabase" arguments { put("query", "users") put("limit", 10) } } // Elicit request with schema val elicitRequest = elicitationRequest { message = "Please provide your contact information" requestedSchema { properties { put("email", JsonObject(mapOf( "type" to JsonPrimitive("string"), "description" to JsonPrimitive("Your email address") ))) } required = listOf("email") } } ``` Each DSL builder includes: - Clear separation of required (but runtime) vs optional fields - Multiple usage examples - Concise error messages with examples when required fields are missing - Cross-references to related types ### Design Considerations 1. Function Naming Convention The current naming convention (e.g., initializeRequest, callToolRequest) might be confusing since these functions don't send requests, they just build request objects. Alternative approach: Add a build prefix to clarify intent: - `initializeRequest` → `buildInitializeRequest` - `callToolRequest` → `buildCallToolRequest` - `createMessageRequest` → `buildCreateMessageRequest` This would make it clearer that these are builder functions. 2. Runtime vs Compile-Time Validation Currently, required fields are validated at runtime in the `build()` method: ```kotlin val request = initializeRequest { protocolVersion = "2024-11-05" // Missing required fields: capabilities, info // Error thrown at runtime ❌ } ``` Alternative approach: Use Compose UI style where required parameters are in the function signature: ```kotlin fun initializeRequest( protocolVersion: String, capabilities: ClientCapabilities, info: Implementation, block: InitializeRequestBuilder.() -> Unit = {} ): InitializeRequest ``` ```kotlin // Usage val request = initializeRequest( protocolVersion = "2024-11-05", capabilities = ClientCapabilities(sampling = ClientCapabilities.sampling), info = Implementation("MyClient", "1.0.0") ) { // Optional fields only meta { put("key", "value") } } ``` Pros: - Compile-time safety for required fields ✅ - Clear which fields are required ✅ - Better IDE autocomplete ✅ Cons: - Multiple trailing lambdas can be confusing: ```kotlin // When required fields include lambdas val request = elicitationRequest( message = "Enter info", requestedSchema = { /* lambda 1 */ } ) { /* lambda 2 for optional fields */ } ``` - Less consistent DSL feel - More verbose for simple cases --- kotlin-sdk-core/api/kotlin-sdk-core.api | 250 +++++++++++ .../kotlin/sdk/types/McpDsl.kt | 12 + .../kotlin/sdk/types/capabilities.dsl.kt | 181 ++++++++ .../kotlin/sdk/types/completion.dsl.kt | 169 ++++++++ .../kotlin/sdk/types/content.dsl.kt | 272 ++++++++++++ .../kotlin/sdk/types/elicitation.dsl.kt | 228 ++++++++++ .../kotlin/sdk/types/initialize.dsl.kt | 206 +++++++++ .../kotlin/sdk/types/logging.dsl.kt | 87 ++++ .../kotlin/sdk/types/pingRequest.dsl.kt | 59 +++ .../kotlin/sdk/types/prompts.dsl.kt | 145 +++++++ .../kotlin/sdk/types/request.dsl.kt | 208 +++++++++ .../kotlin/sdk/types/resources.dsl.kt | 302 ++++++++++++++ .../kotlin/sdk/types/roots.dsl.kt | 59 +++ .../kotlin/sdk/types/sampling.dsl.kt | 394 ++++++++++++++++++ .../kotlin/sdk/types/tools.dsl.kt | 172 ++++++++ 15 files changed, 2744 insertions(+) create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/McpDsl.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.dsl.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.dsl.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/content.dsl.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.dsl.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/initialize.dsl.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/logging.dsl.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/pingRequest.dsl.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.dsl.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.dsl.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.dsl.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.dsl.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.dsl.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.dsl.kt diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index fa85476..def7e8b 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -581,6 +581,15 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/AudioContent$Compani public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/AudioContentBuilder : io/modelcontextprotocol/kotlin/sdk/types/MediaContentBuilder { + public fun ()V + public synthetic fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent; + public final fun getData ()Ljava/lang/String; + public final fun getMimeType ()Ljava/lang/String; + public final fun setData (Ljava/lang/String;)V + public final fun setMimeType (Ljava/lang/String;)V +} + public final class io/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams : io/modelcontextprotocol/kotlin/sdk/types/NotificationParams { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams$Companion; public fun ()V @@ -706,6 +715,16 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CallToolRequest$Comp public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/CallToolRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/RequestBuilder { + public fun ()V + public final fun arguments (Lkotlin/jvm/functions/Function1;)V + public final fun arguments (Lkotlinx/serialization/json/JsonObject;)V + public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequest; + public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; + public final fun getName ()Ljava/lang/String; + public final fun setName (Ljava/lang/String;)V +} + public final class io/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams$Companion; public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -904,6 +923,19 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$R public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilitiesBuilder { + public fun ()V + public final fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities; + public final fun elicitation (Lkotlin/jvm/functions/Function1;)V + public final fun elicitation (Lkotlinx/serialization/json/JsonObject;)V + public final fun experimental (Lkotlin/jvm/functions/Function1;)V + public final fun experimental (Lkotlinx/serialization/json/JsonObject;)V + public final fun roots (Ljava/lang/Boolean;)V + public static synthetic fun roots$default (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilitiesBuilder;Ljava/lang/Boolean;ILjava/lang/Object;)V + public final fun sampling (Lkotlin/jvm/functions/Function1;)V + public final fun sampling (Lkotlinx/serialization/json/JsonObject;)V +} + public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/ClientNotification : io/modelcontextprotocol/kotlin/sdk/types/Notification { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ClientNotification$Companion; } @@ -974,6 +1006,16 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CompleteRequest$Comp public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/CompleteRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/RequestBuilder { + public fun ()V + public final fun argument (Ljava/lang/String;Ljava/lang/String;)V + public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequest; + public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; + public final fun context (Ljava/util/Map;)V + public final fun context (Lkotlin/jvm/functions/Function1;)V + public final fun ref (Lio/modelcontextprotocol/kotlin/sdk/types/Reference;)V +} + public final class io/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Companion; public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Argument;Lio/modelcontextprotocol/kotlin/sdk/types/Reference;Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Context;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -1197,6 +1239,28 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/RequestBuilder { + public fun ()V + public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest; + public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; + public final fun getContext ()Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext; + public final fun getMaxTokens ()Ljava/lang/Integer; + public final fun getStopSequences ()Ljava/util/List; + public final fun getSystemPrompt ()Ljava/lang/String; + public final fun getTemperature ()Ljava/lang/Double; + public final fun messages (Ljava/util/List;)V + public final fun messages (Lkotlin/jvm/functions/Function1;)V + public final fun metadata (Lkotlin/jvm/functions/Function1;)V + public final fun preferences (Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences;)V + public final fun preferences (Ljava/util/List;Ljava/lang/Double;Ljava/lang/Double;Ljava/lang/Double;)V + public static synthetic fun preferences$default (Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestBuilder;Ljava/util/List;Ljava/lang/Double;Ljava/lang/Double;Ljava/lang/Double;ILjava/lang/Object;)V + public final fun setContext (Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext;)V + public final fun setMaxTokens (Ljava/lang/Integer;)V + public final fun setStopSequences (Ljava/util/List;)V + public final fun setSystemPrompt (Ljava/lang/String;)V + public final fun setTemperature (Ljava/lang/Double;)V +} + public final class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams$Companion; public synthetic fun (ILjava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext;Ljava/lang/Double;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -1367,6 +1431,16 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequest$Compan public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/RequestBuilder { + public fun ()V + public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequest; + public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; + public final fun getMessage ()Ljava/lang/String; + public final fun requestedSchema (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;)V + public final fun requestedSchema (Lkotlin/jvm/functions/Function1;)V + public final fun setMessage (Ljava/lang/String;)V +} + public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$Companion; public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -1430,6 +1504,15 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$ public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestedSchemaBuilder { + public fun ()V + public final fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema; + public final fun getRequired ()Ljava/util/List; + public final fun properties (Lkotlin/jvm/functions/Function1;)V + public final fun properties (Lkotlinx/serialization/json/JsonObject;)V + public final fun setRequired (Ljava/util/List;)V +} + public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitResult : io/modelcontextprotocol/kotlin/sdk/types/ClientResult { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Companion; public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Action;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)V @@ -1575,6 +1658,16 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest$Com public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/GetPromptRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/RequestBuilder { + public fun ()V + public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest; + public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; + public final fun getArguments ()Ljava/util/Map; + public final fun getName ()Ljava/lang/String; + public final fun setArguments (Ljava/util/Map;)V + public final fun setName (Ljava/lang/String;)V +} + public final class io/modelcontextprotocol/kotlin/sdk/types/GetPromptRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequestParams$Companion; public synthetic fun (Ljava/lang/String;Ljava/util/Map;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -1723,6 +1816,15 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ImageContent$Compani public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ImageContentBuilder : io/modelcontextprotocol/kotlin/sdk/types/MediaContentBuilder { + public fun ()V + public synthetic fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent; + public final fun getData ()Ljava/lang/String; + public final fun getMimeType ()Ljava/lang/String; + public final fun setData (Ljava/lang/String;)V + public final fun setMimeType (Ljava/lang/String;)V +} + public final class io/modelcontextprotocol/kotlin/sdk/types/Implementation { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Implementation$Companion; public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)V @@ -1806,6 +1908,19 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/InitializeRequest$Co public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/InitializeRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/RequestBuilder { + public fun ()V + public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequest; + public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; + public final fun capabilities (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;)V + public final fun capabilities (Lkotlin/jvm/functions/Function1;)V + public final fun getProtocolVersion ()Ljava/lang/String; + public final fun info (Lio/modelcontextprotocol/kotlin/sdk/types/Implementation;)V + public final fun info (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)V + public static synthetic fun info$default (Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequestBuilder;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;ILjava/lang/Object;)V + public final fun setProtocolVersion (Ljava/lang/String;)V +} + public final class io/modelcontextprotocol/kotlin/sdk/types/InitializeRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequestParams$Companion; public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;Lio/modelcontextprotocol/kotlin/sdk/types/Implementation;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -2092,6 +2207,12 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest$C public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestBuilder { + public fun ()V + public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest; + public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/ListPromptsResult : io/modelcontextprotocol/kotlin/sdk/types/PaginatedResult, io/modelcontextprotocol/kotlin/sdk/types/ServerResult { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsResult$Companion; public fun (Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V @@ -2160,6 +2281,12 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplate public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestBuilder { + public fun ()V + public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesRequest; + public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesResult : io/modelcontextprotocol/kotlin/sdk/types/PaginatedResult, io/modelcontextprotocol/kotlin/sdk/types/ServerResult { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesResult$Companion; public fun (Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V @@ -2228,6 +2355,12 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestBuilder { + public fun ()V + public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest; + public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourcesResult : io/modelcontextprotocol/kotlin/sdk/types/PaginatedResult, io/modelcontextprotocol/kotlin/sdk/types/ServerResult { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesResult$Companion; public fun (Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V @@ -2294,6 +2427,12 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest$Com public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ListRootsRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/RequestBuilder { + public fun ()V + public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest; + public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/ListRootsResult : io/modelcontextprotocol/kotlin/sdk/types/ClientResult { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsResult$Companion; public fun (Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)V @@ -2360,6 +2499,12 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest$Com public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ListToolsRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestBuilder { + public fun ()V + public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest; + public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/ListToolsResult : io/modelcontextprotocol/kotlin/sdk/types/PaginatedResult, io/modelcontextprotocol/kotlin/sdk/types/ServerResult { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsResult$Companion; public fun (Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V @@ -2476,6 +2621,9 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotifi public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public abstract interface annotation class io/modelcontextprotocol/kotlin/sdk/types/McpDsl : java/lang/annotation/Annotation { +} + public final class io/modelcontextprotocol/kotlin/sdk/types/McpException : java/lang/Exception { public fun (ILjava/lang/String;Lkotlinx/serialization/json/JsonElement;)V public synthetic fun (ILjava/lang/String;Lkotlinx/serialization/json/JsonElement;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -2496,6 +2644,19 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/MediaContent$Default public static fun get_meta (Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;)Lkotlinx/serialization/json/JsonObject; } +public abstract class io/modelcontextprotocol/kotlin/sdk/types/MediaContentBuilder { + public fun ()V + public final fun annotations (Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;)V + public final fun annotations (Ljava/util/List;Ljava/lang/Double;Ljava/lang/String;)V + public static synthetic fun annotations$default (Lio/modelcontextprotocol/kotlin/sdk/types/MediaContentBuilder;Ljava/util/List;Ljava/lang/Double;Ljava/lang/String;ILjava/lang/Object;)V + public abstract fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent; + protected final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/types/Annotations; + protected final fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public final fun meta (Lkotlin/jvm/functions/Function1;)V + protected final fun setAnnotations (Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;)V + protected final fun setMeta (Lkotlinx/serialization/json/JsonObject;)V +} + public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/Method { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Method$Companion; public abstract fun getValue ()Ljava/lang/String; @@ -2669,6 +2830,12 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequest$Def public static fun getMeta-VI-3G7E (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequest;)Lkotlinx/serialization/json/JsonObject; } +public abstract class io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/RequestBuilder { + public fun ()V + public final fun getCursor ()Ljava/lang/String; + public final fun setCursor (Ljava/lang/String;)V +} + public final class io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams$Companion; public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -2744,6 +2911,12 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/PingRequest$Companio public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/PingRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/RequestBuilder { + public fun ()V + public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/PingRequest; + public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/Progress { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Progress$Companion; public fun (DLjava/lang/Double;Ljava/lang/String;)V @@ -3074,6 +3247,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest$ public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/RequestBuilder { + public fun ()V + public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest; + public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; + public final fun getUri ()Ljava/lang/String; + public final fun setUri (Ljava/lang/String;)V +} + public final class io/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequestParams$Companion; public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -3168,6 +3349,13 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/Request$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public abstract class io/modelcontextprotocol/kotlin/sdk/types/RequestBuilder { + public fun ()V + protected final fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun meta (Lkotlin/jvm/functions/Function1;)V + protected final fun setMeta-mJxasco (Lkotlinx/serialization/json/JsonObject;)V +} + public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/RequestId { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/RequestId$Companion; } @@ -3268,6 +3456,19 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/RequestMeta$Companio public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/RequestMetaBuilder { + public final fun progressToken (I)V + public final fun progressToken (J)V + public final fun progressToken (Ljava/lang/String;)V + public final fun put (Ljava/lang/String;Ljava/lang/Number;)Lkotlinx/serialization/json/JsonElement; + public final fun put (Ljava/lang/String;Ljava/lang/String;)Lkotlinx/serialization/json/JsonElement; + public final fun put (Ljava/lang/String;Ljava/lang/Void;)Lkotlinx/serialization/json/JsonElement; + public final fun put (Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;)Lkotlinx/serialization/json/JsonElement; + public final fun put (Ljava/lang/String;Z)Lkotlinx/serialization/json/JsonElement; + public final fun putJsonArray (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lkotlinx/serialization/json/JsonElement; + public final fun putJsonObject (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lkotlinx/serialization/json/JsonElement; +} + public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/RequestParams { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams$Companion; public abstract fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; @@ -3674,6 +3875,24 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/SamplingMessage$Comp public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/SamplingMessageBuilder { + public fun ()V + public final fun assistant (Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;)V + public final fun build ()Ljava/util/List; + public final fun user (Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;)V +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/Sampling_dslKt { + public static final fun assistant (Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageBuilder;Lkotlin/jvm/functions/Function0;)V + public static final fun assistantAudio (Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageBuilder;Lkotlin/jvm/functions/Function1;)V + public static final fun assistantImage (Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageBuilder;Lkotlin/jvm/functions/Function1;)V + public static final fun assistantText (Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageBuilder;Lkotlin/jvm/functions/Function1;)V + public static final fun user (Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageBuilder;Lkotlin/jvm/functions/Function0;)V + public static final fun userAudio (Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageBuilder;Lkotlin/jvm/functions/Function1;)V + public static final fun userImage (Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageBuilder;Lkotlin/jvm/functions/Function1;)V + public static final fun userText (Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageBuilder;Lkotlin/jvm/functions/Function1;)V +} + public final class io/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Companion; public fun ()V @@ -3863,6 +4082,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/SetLevelRequest$Comp public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/SetLevelRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/RequestBuilder { + public fun ()V + public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequest; + public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; + public final fun getLoggingLevel ()Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; + public final fun setLoggingLevel (Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel;)V +} + public final class io/modelcontextprotocol/kotlin/sdk/types/SetLevelRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequestParams$Companion; public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -3959,6 +4186,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest$Com public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/SubscribeRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/RequestBuilder { + public fun ()V + public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest; + public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; + public final fun getUri ()Ljava/lang/String; + public final fun setUri (Ljava/lang/String;)V +} + public final class io/modelcontextprotocol/kotlin/sdk/types/SubscribeRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequestParams$Companion; public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -4023,6 +4258,13 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/TextContent$Companio public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/TextContentBuilder : io/modelcontextprotocol/kotlin/sdk/types/MediaContentBuilder { + public fun ()V + public synthetic fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent; + public final fun getText ()Ljava/lang/String; + public final fun setText (Ljava/lang/String;)V +} + public final class io/modelcontextprotocol/kotlin/sdk/types/TextResourceContents : io/modelcontextprotocol/kotlin/sdk/types/ResourceContents { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/TextResourceContents$Companion; public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V @@ -4274,6 +4516,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest$C public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/RequestBuilder { + public fun ()V + public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest; + public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; + public final fun getUri ()Ljava/lang/String; + public final fun setUri (Ljava/lang/String;)V +} + public final class io/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequestParams$Companion; public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/McpDsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/McpDsl.kt new file mode 100644 index 0000000..3c5e3ed --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/McpDsl.kt @@ -0,0 +1,12 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +/** + * DSL marker annotation for MCP builder classes. + * + * This annotation is used to prevent accidental access to outer DSL scopes + * within nested DSL blocks, ensuring type-safe and unambiguous builder usage. + * + * @see DslMarker + */ +@DslMarker +public annotation class McpDsl diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.dsl.kt new file mode 100644 index 0000000..b132a5e --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.dsl.kt @@ -0,0 +1,181 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonObjectBuilder +import kotlinx.serialization.json.buildJsonObject + +/** + * DSL builder for constructing [ClientCapabilities] instances. + * + * This builder is used within [InitializeRequestBuilder] to configure client capabilities. + * All capabilities are optional - the presence of a capability indicates support for that feature. + * + * ## Available Functions (all optional) + * - [sampling] - Indicates support for sampling from an LLM + * - [roots] - Indicates support for listing roots + * - [elicitation] - Indicates support for elicitation from the server + * - [experimental] - Defines experimental, non-standard capabilities + * + * Example usage within [buildInitializeRequest][buildInitializeRequest]: + * ```kotlin + * val request = buildInitializeRequest { + * protocolVersion = "1.0" + * capabilities { + * sampling(ClientCapabilities.sampling) + * roots(listChanged = true) + * experimental { + * put("customFeature", JsonPrimitive(true)) + * } + * } + * info("MyClient", "1.0.0") + * } + * ``` + * + * @see ClientCapabilities + * @see InitializeRequestBuilder.capabilities + */ +@McpDsl +public class ClientCapabilitiesBuilder @PublishedApi internal constructor() { + private var sampling: JsonObject? = null + private var roots: ClientCapabilities.Roots? = null + private var elicitation: JsonObject? = null + private var experimental: JsonObject? = null + + /** + * Indicates that the client supports sampling from an LLM. + * + * Use [ClientCapabilities.sampling] for default empty configuration. + * + * Example: + * ```kotlin + * capabilities { + * sampling(ClientCapabilities.sampling) + * } + * ``` + * + * @param value The sampling capability configuration + */ + public fun sampling(value: JsonObject) { + this.sampling = value + } + + /** + * Indicates that the client supports sampling from an LLM with custom configuration. + * + * Example: + * ```kotlin + * capabilities { + * sampling { + * put("temperature", JsonPrimitive(0.7)) + * } + * } + * ``` + * + * @param block Lambda for building the sampling configuration + */ + public fun sampling(block: JsonObjectBuilder.() -> Unit): Unit = sampling(buildJsonObject(block)) + + /** + * Indicates that the client supports listing roots. + * + * Example with listChanged notification: + * ```kotlin + * capabilities { + * roots(listChanged = true) + * } + * ``` + * + * Example without listChanged: + * ```kotlin + * capabilities { + * roots() + * } + * ``` + * + * @param listChanged Whether the client will emit notifications when the list of roots changes + */ + public fun roots(listChanged: Boolean? = null) { + this.roots = ClientCapabilities.Roots(listChanged) + } + + /** + * Indicates that the client supports elicitation from the server. + * + * Use [ClientCapabilities.elicitation] for default empty configuration. + * + * Example: + * ```kotlin + * capabilities { + * elicitation(ClientCapabilities.elicitation) + * } + * ``` + * + * @param value The elicitation capability configuration + */ + public fun elicitation(value: JsonObject) { + this.elicitation = value + } + + /** + * Indicates that the client supports elicitation from the server with custom configuration. + * + * Example: + * ```kotlin + * capabilities { + * elicitation { + * put("mode", JsonPrimitive("interactive")) + * } + * } + * ``` + * + * @param block Lambda for building the elicitation configuration + */ + public fun elicitation(block: JsonObjectBuilder.() -> Unit): Unit = elicitation(buildJsonObject(block)) + + /** + * Defines experimental, non-standard capabilities that the client supports. + * + * Example: + * ```kotlin + * capabilities { + * experimental(buildJsonObject { + * put("customFeature", JsonPrimitive(true)) + * put("version", JsonPrimitive("1.0")) + * }) + * } + * ``` + * + * @param value The experimental capabilities configuration + */ + public fun experimental(value: JsonObject) { + this.experimental = value + } + + /** + * Defines experimental, non-standard capabilities that the client supports using a DSL builder. + * + * Example: + * ```kotlin + * capabilities { + * experimental { + * put("customFeature", JsonPrimitive(true)) + * put("beta", JsonObject(mapOf( + * "enabled" to JsonPrimitive(true), + * "version" to JsonPrimitive("2.0") + * ))) + * } + * } + * ``` + * + * @param block Lambda for building the experimental capabilities configuration + */ + public fun experimental(block: JsonObjectBuilder.() -> Unit): Unit = experimental(buildJsonObject(block)) + + @PublishedApi + internal fun build(): ClientCapabilities = ClientCapabilities( + sampling = sampling, + roots = roots, + elicitation = elicitation, + experimental = experimental, + ) +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.dsl.kt new file mode 100644 index 0000000..dbefd31 --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.dsl.kt @@ -0,0 +1,169 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +/** + * Creates a [CompleteRequest] using a type-safe DSL builder. + * + * ## Required + * - [argument][CompleteRequestBuilder.argument] - Sets the argument name and value to complete + * - [ref][CompleteRequestBuilder.ref] - Sets the reference to a prompt or resource template + * + * ## Optional + * - [context][CompleteRequestBuilder.context] - Adds additional context for the completion + * - [meta][CompleteRequestBuilder.meta] - Adds metadata to the request + * + * Example with [PromptReference]: + * ```kotlin + * val request = buildCompleteRequest { + * argument("query", "user input") + * ref(PromptReference("searchPrompt")) + * } + * ``` + * + * Example with [ResourceTemplateReference]: + * ```kotlin + * val request = buildCompleteRequest { + * argument("path", "/users/123") + * ref(ResourceTemplateReference("file:///{path}")) + * context { + * put("userId", "123") + * put("role", "admin") + * } + * } + * ``` + * + * @param block Configuration lambda for setting up the completion request + * @return A configured [CompleteRequest] instance + * @see CompleteRequestBuilder + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +internal inline fun buildCompleteRequest(block: CompleteRequestBuilder.() -> Unit): CompleteRequest { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return CompleteRequestBuilder().apply(block).build() +} + +/** + * DSL builder for constructing [CompleteRequest] instances. + * + * This builder provides methods to configure completion requests for prompts or resource templates. + * Both [argument] and [ref] are required; [context] is optional. + * + * @see buildCompleteRequest + */ +@McpDsl +public class CompleteRequestBuilder @PublishedApi internal constructor() : RequestBuilder() { + private var arg: CompleteRequestParams.Argument? = null + private var ref: Reference? = null + private var ctx: CompleteRequestParams.Context? = null + + /** + * Sets the argument for completion. + * + * This method specifies the name and value of the argument to be completed. + * This is a required field and must be called exactly once. + * + * Example: + * ```kotlin + * completeRequest { + * argument("query", "SELECT * FROM use") + * // ... other configuration + * } + * ``` + * + * @param name The name of the argument to complete + * @param value The partial or full value of the argument + */ + public fun argument(name: String, value: String) { + arg = CompleteRequestParams.Argument(name, value) + } + + /** + * Sets the reference to a prompt or resource template. + * + * This method specifies which prompt or resource template the completion request refers to. + * + * Example with prompt: + * ```kotlin + * completeRequest { + * ref(PromptReference("sqlQuery", "SQL Query Builder")) + * // ... other configuration + * } + * ``` + * + * Example with resource template: + * ```kotlin + * completeRequest { + * ref(ResourceTemplateReference("file:///{path}")) + * // ... other configuration + * } + * ``` + * + * @param value The [Reference] (either [PromptReference] or [ResourceTemplateReference]) + */ + public fun ref(value: Reference) { + ref = value + } + + /** + * Sets additional context for the completion request using a Map. + * + * This method allows providing additional key-value pairs that may be relevant + * for generating completions. This is an optional field. + * + * Example: + * ```kotlin + * completeRequest { + * context(mapOf("userId" to "123", "role" to "admin")) + * // ... other configuration + * } + * ``` + * + * @param arguments A map of context key-value pairs + */ + public fun context(arguments: Map) { + ctx = CompleteRequestParams.Context(arguments) + } + + /** + * Sets additional context for the completion request using a DSL builder. + * + * This method allows providing additional key-value pairs that may be relevant + * for generating completions using a type-safe builder syntax. This is an optional field. + * + * Example: + * ```kotlin + * completeRequest { + * context { + * put("userId", "123") + * put("role", "admin") + * put("environment", "production") + * } + * // ... other configuration + * } + * ``` + * + * @param block Lambda with receiver for building the context map + */ + public fun context(block: MutableMap.() -> Unit) { + ctx = CompleteRequestParams.Context(buildMap(block)) + } + + @PublishedApi + override fun build(): CompleteRequest { + val argument = requireNotNull(arg) { + "Missing required field 'argument(name, value)'. Example: argument(\"query\", \"user input\")" + } + + val reference = requireNotNull(ref) { + "Missing required field 'ref(Reference)'. Use ref(PromptReference(\"name\")) or ref(ResourceTemplateReference(\"uri\"))" + } + + val params = CompleteRequestParams(argument = argument, ref = reference, context = ctx, meta = meta) + return CompleteRequest(params) + } +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/content.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/content.dsl.kt new file mode 100644 index 0000000..e538af5 --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/content.dsl.kt @@ -0,0 +1,272 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonObjectBuilder +import kotlinx.serialization.json.buildJsonObject + +/** + * Base DSL builder for constructing [MediaContent] instances. + * + * This abstract class provides common functionality for all media content builders: + * - [annotations] - Optional metadata annotations + * - [meta] - Optional additional metadata + * + * Concrete implementations: + * - [TextContentBuilder] - For text content + * - [ImageContentBuilder] - For image content (base64-encoded) + * - [AudioContentBuilder] - For audio content (base64-encoded) + * + * @see TextContentBuilder + * @see ImageContentBuilder + * @see AudioContentBuilder + */ +@McpDsl +public abstract class MediaContentBuilder { + protected var annotations: Annotations? = null + protected var meta: JsonObject? = null + + /** + * Sets optional annotations for the content. + * + * Annotations provide additional metadata about the content such as audience, + * priority, and last modification time. + * + * Example: + * ```kotlin + * userText { + * text = "Hello" + * annotations(Annotations(priority = 0.8)) + * } + * ``` + * + * @param annotations The [Annotations] instance + */ + public fun annotations(annotations: Annotations) { + this.annotations = annotations + } + + /** + * Sets optional annotations for the content with individual parameters. + * + * Example with all parameters: + * ```kotlin + * userText { + * text = "Important update" + * annotations( + * audience = listOf(Role.User, Role.Assistant), + * priority = 0.8, + * lastModified = "2025-01-12T15:00:58Z" + * ) + * } + * ``` + * + * @param audience The intended audience for this content (list of [Role]) + * @param priority Priority hint for this content (0.0 to 1.0) + * @param lastModified ISO 8601 timestamp of the last modification + * @see Annotations + */ + public fun annotations(audience: List? = null, priority: Double? = null, lastModified: String? = null) { + this.annotations = Annotations(audience, priority, lastModified) + } + + /** + * Sets optional metadata for the content using a DSL builder. + * + * Example: + * ```kotlin + * userText { + * text = "Hello" + * meta { + * put("source", JsonPrimitive("user-input")) + * } + * } + * ``` + * + * @param block Lambda for building the metadata + */ + public fun meta(block: JsonObjectBuilder.() -> Unit) { + meta = buildJsonObject(block) + } + + @PublishedApi + internal abstract fun build(): MediaContent +} + +/** + * DSL builder for constructing [TextContent] instances. + * + * ## Required + * - [text] - The text content string + * + * ## Optional + * - [annotations] - Content annotations (inherited from [MediaContentBuilder]) + * - [meta] - Additional metadata (inherited from [MediaContentBuilder]) + * + * Example usage in sampling messages: + * ```kotlin + * createMessageRequest { + * maxTokens = 100 + * messages { + * userText { + * text = "What is the capital of France?" + * } + * } + * } + * ``` + * + * Example with annotations: + * ```kotlin + * userText { + * text = "Important message" + * annotations(Annotations(priority = 1.0)) + * } + * ``` + * + * @see TextContent + * @see MediaContentBuilder + */ +@McpDsl +public class TextContentBuilder @PublishedApi internal constructor() : MediaContentBuilder() { + /** + * The text content. This is a required field. + */ + public var text: String? = null + + override fun build(): TextContent { + val text = requireNotNull(text) { + "Missing required field 'text'. Example: text = \"Hello, world!\"" + } + + return TextContent(text = text, annotations = annotations, meta = meta) + } +} + +/** + * DSL builder for constructing [ImageContent] instances. + * + * ## Required + * - [data] - The base64-encoded image data + * - [mimeType] - The MIME type of the image (e.g., "image/png", "image/jpeg") + * + * ## Optional + * - [annotations] - Content annotations (inherited from [MediaContentBuilder]) + * - [meta] - Additional metadata (inherited from [MediaContentBuilder]) + * + * Example usage in sampling messages: + * ```kotlin + * createMessageRequest { + * maxTokens = 100 + * messages { + * userImage { + * data = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB..." + * mimeType = "image/png" + * } + * } + * } + * ``` + * + * Example with annotations: + * ```kotlin + * userImage { + * data = base64EncodedImageData + * mimeType = "image/jpeg" + * annotations(Annotations(priority = 0.9)) + * } + * ``` + * + * @see ImageContent + * @see MediaContentBuilder + */ +@McpDsl +public class ImageContentBuilder @PublishedApi internal constructor() : MediaContentBuilder() { + /** + * The base64-encoded image data. This is a required field. + */ + public var data: String? = null + + /** + * The MIME type of the image (e.g., "image/png", "image/jpeg"). This is a required field. + */ + public var mimeType: String? = null + + override fun build(): ImageContent { + val data = requireNotNull(data) { + "Missing required field 'data'. Provide base64-encoded image data" + } + val mime = requireNotNull(mimeType) { + "Missing required field 'mimeType'. Example: mimeType = \"image/png\"" + } + + return ImageContent( + data = data, + mimeType = mime, + annotations = annotations, + meta = meta, + ) + } +} + +/** + * DSL builder for constructing [AudioContent] instances. + * + * ## Required + * - [data] - The base64-encoded audio data + * - [mimeType] - The MIME type of the audio (e.g., "audio/wav", "audio/mpeg") + * + * ## Optional + * - [annotations] - Content annotations (inherited from [MediaContentBuilder]) + * - [meta] - Additional metadata (inherited from [MediaContentBuilder]) + * + * Example usage in sampling messages: + * ```kotlin + * createMessageRequest { + * maxTokens = 100 + * messages { + * userAudio { + * data = "UklGRiQAAABXQVZFZm10IBAAAAABAAEA..." + * mimeType = "audio/wav" + * } + * } + * } + * ``` + * + * Example with annotations: + * ```kotlin + * userAudio { + * data = base64EncodedAudioData + * mimeType = "audio/mpeg" + * annotations(Annotations(priority = 0.7)) + * } + * ``` + * + * @see AudioContent + * @see MediaContentBuilder + */ +@McpDsl +public class AudioContentBuilder @PublishedApi internal constructor() : MediaContentBuilder() { + /** + * The base64-encoded audio data. This is a required field. + */ + public var data: String? = null + + /** + * The MIME type of the audio (e.g., "audio/wav", "audio/mpeg"). This is a required field. + */ + public var mimeType: String? = null + + override fun build(): AudioContent { + val data = requireNotNull(data) { + "Missing required field 'data'. Provide base64-encoded audio data" + } + val mime = requireNotNull(mimeType) { + "Missing required field 'mimeType'. Example: mimeType = \"audio/wav\"" + } + + return AudioContent( + data = data, + mimeType = mime, + annotations = annotations, + meta = meta, + ) + } +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.dsl.kt new file mode 100644 index 0000000..a4823cf --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.dsl.kt @@ -0,0 +1,228 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonObjectBuilder +import kotlinx.serialization.json.buildJsonObject +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +/** + * Creates an [ElicitRequest] using a type-safe DSL builder. + * + * ## Required + * - [message][ElicitRequestBuilder.message] - The message to present to the user + * - [requestedSchema][ElicitRequestBuilder.requestedSchema] - Schema defining the structure of requested data + * + * ## Optional + * - [meta][ElicitRequestBuilder.meta] - Metadata for the request + * + * Example requesting user information: + * ```kotlin + * val request = buildElicitRequest { + * message = "Please provide your contact information" + * requestedSchema { + * properties { + * put("email", JsonObject(mapOf( + * "type" to JsonPrimitive("string"), + * "description" to JsonPrimitive("Your email address") + * ))) + * put("name", JsonObject(mapOf( + * "type" to JsonPrimitive("string") + * ))) + * } + * required = listOf("email") + * } + * } + * ``` + * + * Example with simple text input: + * ```kotlin + * val request = buildElicitRequest { + * message = "Enter a project name" + * requestedSchema { + * properties { + * put("projectName", JsonObject(mapOf( + * "type" to JsonPrimitive("string"), + * "description" to JsonPrimitive("Name for the new project") + * ))) + * } + * } + * } + * ``` + * + * @param block Configuration lambda for setting up the elicitation request + * @return A configured [ElicitRequest] instance + * @see ElicitRequestBuilder + * @see ElicitRequest + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +internal inline fun buildElicitRequest(block: ElicitRequestBuilder.() -> Unit): ElicitRequest { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return ElicitRequestBuilder().apply(block).build() +} + +/** + * DSL builder for constructing [ElicitRequest] instances. + * + * This builder is used to create requests that prompt users for structured input + * through forms or dialogs presented by the client. + * + * ## Required + * - [message] - The message to present to the user explaining what is being requested + * - [requestedSchema] - Schema defining the structure of the data to collect + * + * ## Optional + * - [meta] - Metadata for the request + * + * @see buildElicitRequest + * @see ElicitRequest + */ +@McpDsl +public class ElicitRequestBuilder @PublishedApi internal constructor() : RequestBuilder() { + /** + * The message to present to the user. This should clearly explain what information + * is being requested and why. This is a required field. + */ + public var message: String? = null + + private var requestedSchema: ElicitRequestParams.RequestedSchema? = null + + /** + * Sets the requested schema directly. + * + * Example: + * ```kotlin + * buildElicitRequest { + * message = "Enter details" + * requestedSchema(ElicitRequestParams.RequestedSchema( + * properties = buildJsonObject { + * put("name", JsonObject(mapOf("type" to JsonPrimitive("string")))) + * } + * )) + * } + * ``` + * + * @param schema The [ElicitRequestParams.RequestedSchema] instance + */ + public fun requestedSchema(schema: ElicitRequestParams.RequestedSchema) { + requestedSchema = schema + } + + /** + * Sets the requested schema using a DSL builder. + * + * This is the recommended way to define schemas. The schema defines the structure + * of data to be collected from the user, supporting only top-level primitive properties. + * + * Example: + * ```kotlin + * buildElicitRequest { + * message = "Configure settings" + * requestedSchema { + * properties { + * put("enabled", JsonObject(mapOf( + * "type" to JsonPrimitive("boolean"), + * "description" to JsonPrimitive("Enable feature") + * ))) + * put("apiKey", JsonObject(mapOf( + * "type" to JsonPrimitive("string") + * ))) + * } + * required = listOf("apiKey") + * } + * } + * ``` + * + * @param block Lambda for building the schema + * @see ElicitRequestedSchemaBuilder + */ + public fun requestedSchema(block: ElicitRequestedSchemaBuilder.() -> Unit) { + requestedSchema = ElicitRequestedSchemaBuilder().apply(block).build() + } + + @PublishedApi + override fun build(): ElicitRequest { + val message = requireNotNull(message) { + "Missing required field 'message'. Example: message = \"Please enter your name\"" + } + val requestedSchema = requireNotNull(requestedSchema) { + "Missing required field 'requestedSchema'. Use requestedSchema { properties { ... } }" + } + + val params = ElicitRequestParams(message = message, requestedSchema = requestedSchema, meta = meta) + return ElicitRequest(params) + } +} + +/** + * DSL builder for constructing [ElicitRequestParams.RequestedSchema] instances. + * + * Defines the JSON Schema structure for data to be collected from the user. + * Only supports top-level primitive properties. + * + * ## Required + * - [properties] - Schema definitions for each field + * + * ## Optional + * - [required] - List of required property names + * + * Example: + * ```kotlin + * requestedSchema { + * properties { + * put("username", JsonObject(mapOf( + * "type" to JsonPrimitive("string"), + * "description" to JsonPrimitive("Your username") + * ))) + * } + * required = listOf("username") + * } + * ``` + * + * @see ElicitRequestParams.RequestedSchema + */ +@McpDsl +public class ElicitRequestedSchemaBuilder @PublishedApi internal constructor() { + private var properties: JsonObject? = null + + /** + * List of required property names. If null, all fields are optional. + */ + public var required: List? = null + + /** + * Sets the schema properties directly. + * + * @param properties JsonObject containing property schemas + */ + public fun properties(properties: JsonObject) { + this.properties = properties + } + + /** + * Sets the schema properties using a DSL builder. + * + * Example: + * ```kotlin + * properties { + * put("email", JsonObject(mapOf( + * "type" to JsonPrimitive("string") + * ))) + * } + * ``` + * + * @param block Lambda for building the properties + */ + public fun properties(block: JsonObjectBuilder.() -> Unit): Unit = properties(buildJsonObject(block)) + + @PublishedApi + internal fun build(): ElicitRequestParams.RequestedSchema { + val properties = requireNotNull(properties) { + "Missing required field 'properties'. Use properties { put(\"fieldName\", schema) }" + } + return ElicitRequestParams.RequestedSchema(properties, required) + } +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/initialize.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/initialize.dsl.kt new file mode 100644 index 0000000..345b036 --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/initialize.dsl.kt @@ -0,0 +1,206 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +/** + * Creates an [InitializeRequest] using a type-safe DSL builder. + * + * ## Required + * - [protocolVersion][InitializeRequestBuilder.protocolVersion] - MCP protocol version + * - [capabilities][InitializeRequestBuilder.capabilities] - Client capabilities + * - [info][InitializeRequestBuilder.info] - Client implementation information + * + * ## Optional + * - [meta][InitializeRequestBuilder.meta] - Metadata for the request + * + * Example: + * ```kotlin + * val request = buildInitializeRequest { + * protocolVersion = "2024-11-05" + * capabilities { + * sampling(ClientCapabilities.sampling) + * roots(listChanged = true) + * } + * info("MyClient", "1.0.0") + * } + * ``` + * + * Example with full client info: + * ```kotlin + * val request = buildInitializeRequest { + * protocolVersion = "2024-11-05" + * capabilities { + * sampling(ClientCapabilities.sampling) + * experimental { + * put("feature", JsonPrimitive(true)) + * } + * } + * info( + * name = "MyAdvancedClient", + * version = "2.0.0", + * title = "Advanced MCP Client", + * websiteUrl = "https://example.com" + * ) + * } + * ``` + * + * @param block Configuration lambda for setting up the initialize request + * @return A configured [InitializeRequest] instance + * @see InitializeRequestBuilder + * @see InitializeRequest + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +internal inline fun buildInitializeRequest(block: InitializeRequestBuilder.() -> Unit): InitializeRequest { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return InitializeRequestBuilder().apply(block).build() +} + +/** + * DSL builder for constructing [InitializeRequest] instances. + * + * This builder creates the initial handshake request to establish + * an MCP protocol connection between client and server. + * + * ## Required + * - [protocolVersion] - MCP protocol version string + * - [capabilities] - Client capabilities configuration + * - [info] - Client implementation details + * + * ## Optional + * - [meta] - Metadata for the request + * + * @see buildInitializeRequest + * @see InitializeRequest + */ +@McpDsl +public class InitializeRequestBuilder @PublishedApi internal constructor() : RequestBuilder() { + /** + * The MCP protocol version. This is a required field. + * + * Example: `protocolVersion = "2024-11-05"` + */ + public var protocolVersion: String? = null + + private var capabilities: ClientCapabilities? = null + private var info: Implementation? = null + + /** + * Sets client capabilities directly. + * + * Example: + * ```kotlin + * capabilities(ClientCapabilities( + * sampling = ClientCapabilities.sampling, + * roots = ClientCapabilities.Roots(listChanged = true) + * )) + * ``` + * + * @param value The [ClientCapabilities] instance + */ + public fun capabilities(value: ClientCapabilities) { + capabilities = value + } + + /** + * Sets client capabilities using a DSL builder. + * + * This is the recommended way to configure capabilities. + * + * Example: + * ```kotlin + * capabilities { + * sampling(ClientCapabilities.sampling) + * roots(listChanged = true) + * elicitation(ClientCapabilities.elicitation) + * } + * ``` + * + * @param block Lambda for building capabilities + * @see ClientCapabilitiesBuilder + */ + public fun capabilities(block: ClientCapabilitiesBuilder.() -> Unit) { + capabilities = ClientCapabilitiesBuilder().apply(block).build() + } + + /** + * Sets client implementation info directly. + * + * Example: + * ```kotlin + * info(Implementation( + * name = "MyClient", + * version = "1.0.0" + * )) + * ``` + * + * @param value The [Implementation] instance + */ + public fun info(value: Implementation) { + info = value + } + + /** + * Sets client implementation info using individual parameters. + * + * This is the recommended way to configure client info. + * + * Example with required fields only: + * ```kotlin + * info("MyClient", "1.0.0") + * ``` + * + * Example with all fields: + * ```kotlin + * info( + * name = "MyClient", + * version = "2.0.0", + * title = "My MCP Client", + * websiteUrl = "https://example.com", + * icons = listOf(Icon( + * src = "https://example.com/icon.png", + * mimeType = "image/png" + * )) + * ) + * ``` + * + * @param name Client implementation name + * @param version Client version string + * @param title Optional human-readable title + * @param websiteUrl Optional URL to client website + * @param icons Optional list of client icons + */ + public fun info( + name: String, + version: String, + title: String? = null, + websiteUrl: String? = null, + icons: List? = null, + ) { + info = Implementation(name = name, version = version, title = title, websiteUrl = websiteUrl, icons = icons) + } + + @PublishedApi + override fun build(): InitializeRequest { + val version = requireNotNull(protocolVersion) { + "Missing required field 'protocolVersion'. Example: protocolVersion = \"2024-11-05\"" + } + val capabilities = requireNotNull(capabilities) { + "Missing required field 'capabilities'. Use capabilities { ... }" + } + val info = requireNotNull(info) { + "Missing required field 'info'. Use info(\"clientName\", \"1.0.0\")" + } + + val params = InitializeRequestParams( + protocolVersion = version, + capabilities = capabilities, + clientInfo = info, + meta = meta, + ) + return InitializeRequest(params = params) + } +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/logging.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/logging.dsl.kt new file mode 100644 index 0000000..883d2cc --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/logging.dsl.kt @@ -0,0 +1,87 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +/** + * Creates a [SetLevelRequest] using a type-safe DSL builder. + * + * ## Required + * - [loggingLevel][SetLevelRequestBuilder.loggingLevel] - The logging level to set + * + * ## Optional + * - [meta][SetLevelRequestBuilder.meta] - Metadata for the request + * + * Example setting info level: + * ```kotlin + * val request = buildSetLevelRequest { + * loggingLevel = LoggingLevel.Info + * } + * ``` + * + * Example setting debug level: + * ```kotlin + * val request = buildSetLevelRequest { + * loggingLevel = LoggingLevel.Debug + * } + * ``` + * + * @param block Configuration lambda for setting up the logging level request + * @return A configured [SetLevelRequest] instance + * @see SetLevelRequestBuilder + * @see SetLevelRequest + * @see LoggingLevel + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +internal inline fun buildSetLevelRequest(block: SetLevelRequestBuilder.() -> Unit): SetLevelRequest { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return SetLevelRequestBuilder().apply(block).build() +} + +/** + * DSL builder for constructing [SetLevelRequest] instances. + * + * This builder creates requests to set the logging level for the server. + * + * ## Required + * - [loggingLevel] - The logging level to set + * + * ## Optional + * - [meta] - Metadata for the request + * + * @see buildSetLevelRequest + * @see SetLevelRequest + * @see LoggingLevel + */ +@McpDsl +public class SetLevelRequestBuilder @PublishedApi internal constructor() : RequestBuilder() { + /** + * The logging level to set. This is a required field. + * + * Available levels (from least to most severe): + * - [LoggingLevel.Debug] + * - [LoggingLevel.Info] + * - [LoggingLevel.Notice] + * - [LoggingLevel.Warning] + * - [LoggingLevel.Error] + * - [LoggingLevel.Critical] + * - [LoggingLevel.Alert] + * - [LoggingLevel.Emergency] + * + * Example: `loggingLevel = LoggingLevel.Warning` + */ + public var loggingLevel: LoggingLevel? = null + + @PublishedApi + override fun build(): SetLevelRequest { + val level = requireNotNull(loggingLevel) { + "Missing required field 'loggingLevel'. Example: loggingLevel = LoggingLevel.Info" + } + + val params = SetLevelRequestParams(level = level, meta = meta) + return SetLevelRequest(params = params) + } +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/pingRequest.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/pingRequest.dsl.kt new file mode 100644 index 0000000..2f5ac33 --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/pingRequest.dsl.kt @@ -0,0 +1,59 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +/** + * Creates a [PingRequest] using a type-safe DSL builder. + * + * ## Optional + * - [meta][PingRequestBuilder.meta] - Metadata for the request + * + * Example with no parameters: + * ```kotlin + * val request = buildPingRequest { } + * ``` + * + * Example with metadata: + * ```kotlin + * val request = buildPingRequest { + * meta { + * put("timestamp", JsonPrimitive(System.currentTimeMillis())) + * } + * } + * ``` + * + * @param block Configuration lambda for setting up the ping request + * @return A configured [PingRequest] instance + * @see PingRequestBuilder + * @see PingRequest + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +internal inline fun buildPingRequest(block: PingRequestBuilder.() -> Unit): PingRequest { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return PingRequestBuilder().apply(block).build() +} + +/** + * DSL builder for constructing [PingRequest] instances. + * + * This builder creates ping requests to check connection status with the server. + * All fields are optional. + * + * ## Optional + * - [meta] - Metadata for the request + * + * @see buildPingRequest + * @see PingRequest + */ +@McpDsl +public class PingRequestBuilder @PublishedApi internal constructor() : RequestBuilder() { + @PublishedApi + override fun build(): PingRequest { + val params = meta?.let { BaseRequestParams(meta = it) } + return PingRequest(params) + } +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.dsl.kt new file mode 100644 index 0000000..02eae95 --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.dsl.kt @@ -0,0 +1,145 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +/** + * Creates a [GetPromptRequest] using a type-safe DSL builder. + * + * ## Required + * - [name][GetPromptRequestBuilder.name] - The name of the prompt to retrieve + * + * ## Optional + * - [arguments][GetPromptRequestBuilder.arguments] - Arguments to pass to the prompt + * - [meta][GetPromptRequestBuilder.meta] - Metadata for the request + * + * Example without arguments: + * ```kotlin + * val request = buildGetPromptRequest { + * name = "greeting" + * } + * ``` + * + * Example with arguments: + * ```kotlin + * val request = buildGetPromptRequest { + * name = "userProfile" + * arguments = mapOf( + * "userId" to "123", + * "includeDetails" to "true" + * ) + * } + * ``` + * + * @param block Configuration lambda for setting up the get prompt request + * @return A configured [GetPromptRequest] instance + * @see GetPromptRequestBuilder + * @see GetPromptRequest + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +internal inline fun buildGetPromptRequest(block: GetPromptRequestBuilder.() -> Unit): GetPromptRequest { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return GetPromptRequestBuilder().apply(block).build() +} + +/** + * DSL builder for constructing [GetPromptRequest] instances. + * + * This builder retrieves a specific prompt by name, optionally with arguments. + * + * ## Required + * - [name] - The name of the prompt to retrieve + * + * ## Optional + * - [arguments] - Arguments to pass to the prompt + * - [meta] - Metadata for the request + * + * @see buildGetPromptRequest + * @see GetPromptRequest + */ +@McpDsl +public class GetPromptRequestBuilder @PublishedApi internal constructor() : RequestBuilder() { + /** + * The name of the prompt to retrieve. This is a required field. + * + * Example: `name = "greeting"` + */ + public var name: String? = null + + /** + * Optional arguments to pass to the prompt. + * + * Example: + * ```kotlin + * arguments = mapOf("userId" to "123", "lang" to "en") + * ``` + */ + public var arguments: Map? = null + + @PublishedApi + override fun build(): GetPromptRequest { + val name = requireNotNull(name) { + "Missing required field 'name'. Example: name = \"promptName\"" + } + + val params = GetPromptRequestParams(name = name, arguments = arguments, meta = meta) + return GetPromptRequest(params) + } +} + +/** + * Creates a [ListPromptsRequest] using a type-safe DSL builder. + * + * ## Optional + * - [cursor][ListPromptsRequestBuilder.cursor] - Pagination cursor for fetching next page + * - [meta][ListPromptsRequestBuilder.meta] - Metadata for the request + * + * Example without pagination: + * ```kotlin + * val request = buildListPromptsRequest { } + * ``` + * + * Example with pagination: + * ```kotlin + * val request = buildListPromptsRequest { + * cursor = "eyJwYWdlIjogMn0=" + * } + * ``` + * + * @param block Configuration lambda for setting up the list prompts request + * @return A configured [ListPromptsRequest] instance + * @see ListPromptsRequestBuilder + * @see ListPromptsRequest + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +internal inline fun buildListPromptsRequest(block: ListPromptsRequestBuilder.() -> Unit): ListPromptsRequest { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return ListPromptsRequestBuilder().apply(block).build() +} + +/** + * DSL builder for constructing [ListPromptsRequest] instances. + * + * This builder retrieves a list of available prompts, with optional pagination support. + * All fields are optional. + * + * ## Optional + * - [cursor] - Pagination cursor (inherited from [PaginatedRequestBuilder]) + * - [meta] - Metadata for the request (inherited from [RequestBuilder]) + * + * @see buildListPromptsRequest + * @see ListPromptsRequest + * @see PaginatedRequestBuilder + */ +@McpDsl +public class ListPromptsRequestBuilder @PublishedApi internal constructor() : PaginatedRequestBuilder() { + @PublishedApi + override fun build(): ListPromptsRequest { + val params = paginatedRequestParams(cursor = cursor, meta = meta) + return ListPromptsRequest(params) + } +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.dsl.kt new file mode 100644 index 0000000..3e2a150 --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.dsl.kt @@ -0,0 +1,208 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import kotlinx.serialization.json.JsonArrayBuilder +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonNull +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonObjectBuilder +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.buildJsonArray +import kotlinx.serialization.json.buildJsonObject + +/** + * Base DSL builder for constructing MCP request instances. + * + * This abstract class provides common functionality for all request builders, + * including optional metadata support. + * + * All concrete request builder classes extend this base to inherit [meta] functionality. + * + * @see RequestMetaBuilder + * @see PaginatedRequestBuilder + */ +@McpDsl +public abstract class RequestBuilder { + protected var meta: RequestMeta? = null + + /** + * Sets request metadata using a DSL builder. + * + * Metadata can include progress tokens and custom fields for tracking requests. + * + * Example: + * ```kotlin + * initializeRequest { + * protocolVersion = "2024-11-05" + * meta { + * progressToken("progress-123") + * put("customField", "value") + * } + * // ... other configuration + * } + * ``` + * + * @param builderAction Lambda for building request metadata + * @see RequestMetaBuilder + */ + public fun meta(builderAction: RequestMetaBuilder.() -> Unit) { + meta = RequestMetaBuilder().apply(builderAction).build() + } + + internal abstract fun build(): Request +} + +/** + * DSL builder for constructing [RequestMeta] instances. + * + * This builder creates metadata for MCP requests, supporting progress tokens + * and custom key-value pairs. + * + * Example: + * ```kotlin + * meta { + * progressToken("progress-123") + * put("requestId", "req-456") + * put("priority", 1) + * putJsonObject("context") { + * put("source", "api") + * } + * } + * ``` + * + * @see RequestMeta + * @see RequestBuilder.meta + */ +@McpDsl +public class RequestMetaBuilder internal constructor() { + private val content: MutableMap = linkedMapOf() + + /** + * Sets the progress token as a string. + * + * Progress tokens are used for out-of-band progress notifications. + * + * Example: `progressToken("progress-123")` + */ + public fun progressToken(value: String) { + content["progressToken"] = JsonPrimitive(value) + } + + /** + * Sets the progress token as a `Long`. + * + * Example: `progressToken(123L)` + */ + public fun progressToken(value: Long) { + content["progressToken"] = JsonPrimitive(value) + } + + /** + * Sets the progress token as an Int. + * + * Example: `progressToken(123)` + */ + public fun progressToken(value: Int) { + content["progressToken"] = JsonPrimitive(value) + } + + /** + * Adds a custom metadata field with a JsonElement value. + * + * @param key The metadata field name + * @param value The JsonElement value + * @return The previous value associated with the key, or null + */ + public fun put(key: String, value: JsonElement): JsonElement? = content.put(key, value) + + /** + * Adds a custom metadata field with a String value. + * + * Example: `put("requestId", "req-456")` + */ + public fun put(key: String, value: String): JsonElement? = put(key, JsonPrimitive(value)) + + /** + * Adds a custom metadata field with a Number value. + * + * Example: `put("priority", 1)` + */ + public fun put(key: String, value: Number): JsonElement? = put(key, JsonPrimitive(value)) + + /** + * Adds a custom metadata field with a Boolean value. + * + * Example: `put("urgent", true)` + */ + public fun put(key: String, value: Boolean): JsonElement? = put(key, JsonPrimitive(value)) + + /** + * Adds a custom metadata field with a null value. + * + * Example: `put("optionalField", null)` + */ + @Suppress("UNUSED_PARAMETER") + public fun put(key: String, value: Nothing?): JsonElement? = put(key, JsonNull) + + /** + * Adds a custom metadata field with a JsonObject value using a DSL builder. + * + * Example: + * ```kotlin + * putJsonObject("context") { + * put("source", "api") + * put("version", 2) + * } + * ``` + * + * @param key The metadata field name + * @param builderAction Lambda for building the JsonObject + */ + public fun putJsonObject(key: String, builderAction: JsonObjectBuilder.() -> Unit): JsonElement? = + put(key, buildJsonObject(builderAction)) + + /** + * Adds a custom metadata field with a JsonArray value using a DSL builder. + * + * Example: + * ```kotlin + * putJsonArray("tags") { + * add("important") + * add("urgent") + * } + * ``` + * + * @param key The metadata field name + * @param builderAction Lambda for building the JsonArray + */ + public fun putJsonArray(key: String, builderAction: JsonArrayBuilder.() -> Unit): JsonElement? = + put(key, buildJsonArray(builderAction)) + + internal fun build(): RequestMeta = RequestMeta(JsonObject(content)) +} + +/** + * Base DSL builder for constructing paginated MCP request instances. + * + * This abstract class extends [RequestBuilder] and adds pagination support + * through an optional cursor field. + * + * Example: + * ```kotlin + * listPromptsRequest { + * cursor = "eyJwYWdlIjogMn0=" + * } + * ``` + * + * @see RequestBuilder + */ +@McpDsl +public abstract class PaginatedRequestBuilder : RequestBuilder() { + /** + * Optional pagination cursor for fetching the next page of results. + * + * The cursor value is typically obtained from the previous page's response. + * + * Example: `cursor = "eyJwYWdlIjogMn0="` + */ + public var cursor: String? = null +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.dsl.kt new file mode 100644 index 0000000..db40cd7 --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.dsl.kt @@ -0,0 +1,302 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +/** + * Creates a [ListResourcesRequest] using a type-safe DSL builder. + * + * ## Optional + * - [cursor][ListResourcesRequestBuilder.cursor] - Pagination cursor for fetching next page + * - [meta][ListResourcesRequestBuilder.meta] - Metadata for the request + * + * Example without pagination: + * ```kotlin + * val request = buildListResourcesRequest { } + * ``` + * + * Example with pagination: + * ```kotlin + * val request = buildListResourcesRequest { + * cursor = "eyJwYWdlIjogMn0=" + * } + * ``` + * + * @param block Configuration lambda for setting up the list resources request + * @return A configured [ListResourcesRequest] instance + * @see ListResourcesRequestBuilder + * @see ListResourcesRequest + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +internal inline fun buildListResourcesRequest(block: ListResourcesRequestBuilder.() -> Unit): ListResourcesRequest { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return ListResourcesRequestBuilder().apply(block).build() +} + +/** + * DSL builder for constructing [ListResourcesRequest] instances. + * + * This builder retrieves a list of available resources, with optional pagination support. + * All fields are optional. + * + * ## Optional + * - [cursor] - Pagination cursor (inherited from [PaginatedRequestBuilder]) + * - [meta] - Metadata for the request (inherited from [RequestBuilder]) + * + * @see buildListResourcesRequest + * @see ListResourcesRequest + * @see PaginatedRequestBuilder + */ +@McpDsl +public class ListResourcesRequestBuilder @PublishedApi internal constructor() : PaginatedRequestBuilder() { + @PublishedApi + override fun build(): ListResourcesRequest { + val params = paginatedRequestParams(cursor = cursor, meta = meta) + return ListResourcesRequest(params) + } +} + +/** + * Creates a [ReadResourceRequest] using a type-safe DSL builder. + * + * ## Required + * - [uri][ReadResourceRequestBuilder.uri] - The URI of the resource to read + * + * ## Optional + * - [meta][ReadResourceRequestBuilder.meta] - Metadata for the request + * + * Example: + * ```kotlin + * val request = buildReadResourceRequest { + * uri = "file:///path/to/resource.txt" + * } + * ``` + * + * @param block Configuration lambda for setting up the read resource request + * @return A configured [ReadResourceRequest] instance + * @see ReadResourceRequestBuilder + * @see ReadResourceRequest + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +internal inline fun buildReadResourceRequest(block: ReadResourceRequestBuilder.() -> Unit): ReadResourceRequest { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return ReadResourceRequestBuilder().apply(block).build() +} + +/** + * DSL builder for constructing [ReadResourceRequest] instances. + * + * This builder reads the contents of a specific resource by URI. + * + * ## Required + * - [uri] - The URI of the resource to read + * + * ## Optional + * - [meta] - Metadata for the request + * + * @see buildReadResourceRequest + * @see ReadResourceRequest + */ +@McpDsl +public class ReadResourceRequestBuilder @PublishedApi internal constructor() : RequestBuilder() { + /** + * The URI of the resource to read. This is a required field. + * + * Example: `uri = "file:///path/to/resource.txt"` + */ + public var uri: String? = null + + @PublishedApi + override fun build(): ReadResourceRequest { + val uri = requireNotNull(uri) { + "Missing required field 'uri'. Example: uri = \"file:///path/to/resource.txt\"" + } + + val params = ReadResourceRequestParams(uri = uri, meta = meta) + return ReadResourceRequest(params) + } +} + +/** + * Creates a [SubscribeRequest] using a type-safe DSL builder. + * + * ## Required + * - [uri][SubscribeRequestBuilder.uri] - The URI of the resource to subscribe to + * + * ## Optional + * - [meta][SubscribeRequestBuilder.meta] - Metadata for the request + * + * Example: + * ```kotlin + * val request = buildSubscribeRequest { + * uri = "file:///path/to/resource.txt" + * } + * ``` + * + * @param block Configuration lambda for setting up the subscribe request + * @return A configured [SubscribeRequest] instance + * @see SubscribeRequestBuilder + * @see SubscribeRequest + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +internal inline fun buildSubscribeRequest(block: SubscribeRequestBuilder.() -> Unit): SubscribeRequest { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return SubscribeRequestBuilder().apply(block).build() +} + +/** + * DSL builder for constructing [SubscribeRequest] instances. + * + * This builder subscribes to updates for a specific resource by URI. + * + * ## Required + * - [uri] - The URI of the resource to subscribe to + * + * ## Optional + * - [meta] - Metadata for the request + * + * @see buildSubscribeRequest + * @see SubscribeRequest + */ +@McpDsl +public class SubscribeRequestBuilder @PublishedApi internal constructor() : RequestBuilder() { + /** + * The URI of the resource to subscribe to. This is a required field. + * + * Example: `uri = "file:///path/to/resource.txt"` + */ + public var uri: String? = null + + @PublishedApi + override fun build(): SubscribeRequest { + val uri = requireNotNull(uri) { + "Missing required field 'uri'. Example: uri = \"file:///path/to/resource.txt\"" + } + + val params = SubscribeRequestParams(uri = uri, meta = meta) + return SubscribeRequest(params) + } +} + +/** + * Creates an [UnsubscribeRequest] using a type-safe DSL builder. + * + * ## Required + * - [uri][UnsubscribeRequestBuilder.uri] - The URI of the resource to unsubscribe from + * + * ## Optional + * - [meta][UnsubscribeRequestBuilder.meta] - Metadata for the request + * + * Example: + * ```kotlin + * val request = buildUnsubscribeRequest { + * uri = "file:///path/to/resource.txt" + * } + * ``` + * + * @param block Configuration lambda for setting up the unsubscribe request + * @return A configured [UnsubscribeRequest] instance + * @see UnsubscribeRequestBuilder + * @see UnsubscribeRequest + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +internal inline fun buildUnsubscribeRequest(block: UnsubscribeRequestBuilder.() -> Unit): UnsubscribeRequest { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return UnsubscribeRequestBuilder().apply(block).build() +} + +/** + * DSL builder for constructing [UnsubscribeRequest] instances. + * + * This builder unsubscribes from updates for a specific resource by URI. + * + * ## Required + * - [uri] - The URI of the resource to unsubscribe from + * + * ## Optional + * - [meta] - Metadata for the request + * + * @see buildUnsubscribeRequest + * @see UnsubscribeRequest + */ +@McpDsl +public class UnsubscribeRequestBuilder @PublishedApi internal constructor() : RequestBuilder() { + /** + * The URI of the resource to unsubscribe from. This is a required field. + * + * Example: `uri = "file:///path/to/resource.txt"` + */ + public var uri: String? = null + + @PublishedApi + override fun build(): UnsubscribeRequest { + val uri = requireNotNull(uri) { + "Missing required field 'uri'. Example: uri = \"file:///path/to/resource.txt\"" + } + + val params = UnsubscribeRequestParams(uri = uri, meta = meta) + return UnsubscribeRequest(params) + } +} + +/** + * Creates a [ListResourceTemplatesRequest] using a type-safe DSL builder. + * + * ## Optional + * - [cursor][ListResourceTemplatesRequestBuilder.cursor] - Pagination cursor for fetching next page + * - [meta][ListResourceTemplatesRequestBuilder.meta] - Metadata for the request + * + * Example without pagination: + * ```kotlin + * val request = buildListResourceTemplatesRequest { } + * ``` + * + * Example with pagination: + * ```kotlin + * val request = buildListResourceTemplatesRequest { + * cursor = "eyJwYWdlIjogMn0=" + * } + * ``` + * + * @param block Configuration lambda for setting up the list resource templates request + * @return A configured [ListResourceTemplatesRequest] instance + * @see ListResourceTemplatesRequestBuilder + * @see ListResourceTemplatesRequest + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +internal inline fun buildListResourceTemplatesRequest( + block: ListResourceTemplatesRequestBuilder.() -> Unit, +): ListResourceTemplatesRequest { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return ListResourceTemplatesRequestBuilder().apply(block).build() +} + +/** + * DSL builder for constructing [ListResourceTemplatesRequest] instances. + * + * This builder retrieves a list of available resource templates, with optional pagination support. + * All fields are optional. + * + * ## Optional + * - [cursor] - Pagination cursor (inherited from [PaginatedRequestBuilder]) + * - [meta] - Metadata for the request (inherited from [RequestBuilder]) + * + * @see buildListResourceTemplatesRequest + * @see ListResourceTemplatesRequest + * @see PaginatedRequestBuilder + */ +@McpDsl +public class ListResourceTemplatesRequestBuilder @PublishedApi internal constructor() : PaginatedRequestBuilder() { + @PublishedApi + override fun build(): ListResourceTemplatesRequest { + val params = paginatedRequestParams(cursor = cursor, meta = meta) + return ListResourceTemplatesRequest(params) + } +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.dsl.kt new file mode 100644 index 0000000..a73f218 --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.dsl.kt @@ -0,0 +1,59 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +/** + * Creates a [ListRootsRequest] using a type-safe DSL builder. + * + * ## Optional + * - [meta][ListRootsRequestBuilder.meta] - Metadata for the request + * + * Example with no parameters: + * ```kotlin + * val request = buildListRootsRequest { } + * ``` + * + * Example with metadata: + * ```kotlin + * val request = buildListRootsRequest { + * meta { + * put("context", "initialization") + * } + * } + * ``` + * + * @param block Configuration lambda for setting up the list roots request + * @return A configured [ListRootsRequest] instance + * @see ListRootsRequestBuilder + * @see ListRootsRequest + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +internal inline fun buildListRootsRequest(block: ListRootsRequestBuilder.() -> Unit): ListRootsRequest { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return ListRootsRequestBuilder().apply(block).build() +} + +/** + * DSL builder for constructing [ListRootsRequest] instances. + * + * This builder retrieves the list of root URIs provided by the client. + * All fields are optional. + * + * ## Optional + * - [meta] - Metadata for the request + * + * @see buildListRootsRequest + * @see ListRootsRequest + */ +@McpDsl +public class ListRootsRequestBuilder @PublishedApi internal constructor() : RequestBuilder() { + @PublishedApi + override fun build(): ListRootsRequest { + val params = meta?.let { BaseRequestParams(meta = it) } + return ListRootsRequest(params) + } +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.dsl.kt new file mode 100644 index 0000000..8e87304 --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.dsl.kt @@ -0,0 +1,394 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonObjectBuilder +import kotlinx.serialization.json.buildJsonObject +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +/** + * Creates a [CreateMessageRequest] using a type-safe DSL builder. + * + * ## Required + * - [maxTokens][CreateMessageRequestBuilder.maxTokens] - Maximum number of tokens to generate + * - [messages][CreateMessageRequestBuilder.messages] - List of conversation messages + * + * ## Optional + * - [systemPrompt][CreateMessageRequestBuilder.systemPrompt] - System-level instructions + * - [context][CreateMessageRequestBuilder.context] - Context inclusion settings + * - [temperature][CreateMessageRequestBuilder.temperature] - Sampling temperature + * - [stopSequences][CreateMessageRequestBuilder.stopSequences] - Sequences that stop generation + * - [preferences][CreateMessageRequestBuilder.preferences] - Model selection preferences + * - [metadata][CreateMessageRequestBuilder.metadata] - Additional metadata + * - [meta][CreateMessageRequestBuilder.meta] - Request metadata + * + * Example: + * ```kotlin + * val request = buildCreateMessageRequest { + * maxTokens = 1000 + * systemPrompt = "You are a helpful assistant" + * messages { + * user { "What is the capital of France?" } + * assistant { "The capital of France is Paris." } + * user { "What about Germany?" } + * } + * } + * ``` + * + * Example with preferences: + * ```kotlin + * val request = buildCreateMessageRequest { + * maxTokens = 500 + * temperature = 0.7 + * preferences( + * hints = listOf("claude-3-sonnet"), + * intelligence = 0.8 + * ) + * messages { + * user { "Explain quantum computing" } + * } + * } + * ``` + * + * @param block Configuration lambda for setting up the create message request + * @return A configured [CreateMessageRequest] instance + * @see CreateMessageRequestBuilder + * @see CreateMessageRequest + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +internal inline fun buildCreateMessageRequest(block: CreateMessageRequestBuilder.() -> Unit): CreateMessageRequest { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return CreateMessageRequestBuilder().apply(block).build() +} + +/** + * DSL builder for constructing [CreateMessageRequest] instances. + * + * This builder creates LLM sampling requests with conversation history. + * + * ## Required + * - [maxTokens] - Maximum number of tokens to generate + * - [messages] - List of conversation messages + * + * ## Optional + * - [systemPrompt] - System-level instructions + * - [context] - Context inclusion settings + * - [temperature] - Sampling temperature (0.0-1.0) + * - [stopSequences] - Sequences that stop generation + * - [preferences] - Model selection preferences + * - [metadata] - Additional metadata + * - [meta] - Request metadata + * + * @see buildCreateMessageRequest + * @see CreateMessageRequest + */ +@McpDsl +public class CreateMessageRequestBuilder @PublishedApi internal constructor() : RequestBuilder() { + /** Maximum number of tokens to generate. This is a required field. */ + public var maxTokens: Int? = null + + /** Optional system-level instructions for the model. */ + public var systemPrompt: String? = null + + /** Optional context inclusion settings. */ + public var context: IncludeContext? = null + + /** Optional sampling temperature (0.0-1.0). Lower values are more deterministic. */ + public var temperature: Double? = null + + /** Optional sequences that will stop generation when encountered. */ + public var stopSequences: List? = null + + private var messages: List? = null + private var preferences: ModelPreferences? = null + private var metadata: JsonObject? = null + + /** + * Sets messages directly from a list. + * + * Example: + * ```kotlin + * messages(listOf( + * SamplingMessage(Role.User, TextContent("Hello")), + * SamplingMessage(Role.Assistant, TextContent("Hi!")) + * )) + * ``` + */ + public fun messages(messages: List) { + this.messages = messages + } + + /** + * Sets messages using a DSL builder. + * + * This is the recommended way to define conversation messages. + * + * Example: + * ```kotlin + * messages { + * user { "What is 2+2?" } + * assistant { "2+2 equals 4." } + * user { "What about 3+3?" } + * } + * ``` + */ + public fun messages(block: SamplingMessageBuilder.() -> Unit) { + this.messages = SamplingMessageBuilder().apply(block).build() + } + + /** + * Sets model preferences directly. + */ + public fun preferences(value: ModelPreferences) { + this.preferences = value + } + + /** + * Sets model selection preferences using individual parameters. + * + * Example: + * ```kotlin + * preferences( + * hints = listOf("claude-3-sonnet"), + * intelligence = 0.9, + * speed = 0.5 + * ) + * ``` + * + * @param hints Model name hints for selection + * @param cost Cost optimization priority (0.0-1.0) + * @param speed Speed optimization priority (0.0-1.0) + * @param intelligence Intelligence optimization priority (0.0-1.0) + */ + public fun preferences( + hints: List? = null, + cost: Double? = null, + speed: Double? = null, + intelligence: Double? = null, + ) { + this.preferences = ModelPreferences( + hints = hints?.map { ModelHint(it) }, + costPriority = cost, + speedPriority = speed, + intelligencePriority = intelligence, + ) + } + + /** + * Sets additional metadata using a DSL builder. + * + * Example: + * ```kotlin + * metadata { + * put("userId", "123") + * put("sessionId", "abc") + * } + * ``` + */ + public fun metadata(block: JsonObjectBuilder.() -> Unit) { + this.metadata = buildJsonObject(block) + } + + @PublishedApi + override fun build(): CreateMessageRequest { + val maxTokens = requireNotNull(maxTokens) { + "Missing required field 'maxTokens'. Example: maxTokens = 1000" + } + val messages = requireNotNull(messages) { + "Missing required field 'messages'. Use messages { user { \"text\" } }" + } + + val params = CreateMessageRequestParams( + maxTokens = maxTokens, + messages = messages, + modelPreferences = preferences, + systemPrompt = systemPrompt, + includeContext = context, + temperature = temperature, + stopSequences = stopSequences, + metadata = metadata, + meta = meta, + ) + return CreateMessageRequest(params) + } +} + +/** + * DSL builder for constructing lists of [SamplingMessage] instances. + * + * This builder creates a conversation history for LLM sampling requests. + * + * Example: + * ```kotlin + * messages { + * user { "Hello!" } + * assistant { "Hi! How can I help?" } + * user { "What's 2+2?" } + * } + * ``` + * + * @see SamplingMessage + * @see CreateMessageRequestBuilder.messages + */ +@McpDsl +public class SamplingMessageBuilder @PublishedApi internal constructor() { + private val messages = mutableListOf() + + /** + * Adds a user message with the specified content. + * + * Example: + * ```kotlin + * user(TextContent("Hello")) + * ``` + */ + public fun user(content: MediaContent) { + messages.add(SamplingMessage(Role.User, content)) + } + + /** + * Adds an assistant message with the specified content. + * + * Example: + * ```kotlin + * assistant(TextContent("Hi there!")) + * ``` + */ + public fun assistant(content: MediaContent) { + messages.add(SamplingMessage(Role.Assistant, content)) + } + + @PublishedApi + internal fun build(): List = messages +} + +/** + * Adds a user message with simple text content. + * + * Example: + * ```kotlin + * messages { + * user { "What is the weather today?" } + * } + * ``` + */ +@ExperimentalMcpApi +public fun SamplingMessageBuilder.user(block: () -> String): Unit = this.user(TextContent(text = block())) + +/** + * Adds a user message with text content using a DSL builder. + * + * Example: + * ```kotlin + * messages { + * userText { + * text = "Important message" + * annotations(priority = 1.0) + * } + * } + * ``` + */ +@ExperimentalMcpApi +public fun SamplingMessageBuilder.userText(block: TextContentBuilder.() -> Unit): Unit = + this.user(TextContentBuilder().apply(block).build()) + +/** + * Adds a user message with image content. + * + * Example: + * ```kotlin + * messages { + * userImage { + * data = base64ImageData + * mimeType = "image/png" + * } + * } + * ``` + */ +@ExperimentalMcpApi +public fun SamplingMessageBuilder.userImage(block: ImageContentBuilder.() -> Unit): Unit = + this.user(ImageContentBuilder().apply(block).build()) + +/** + * Adds a user message with audio content. + * + * Example: + * ```kotlin + * messages { + * userAudio { + * data = base64AudioData + * mimeType = "audio/wav" + * } + * } + * ``` + */ +@ExperimentalMcpApi +public fun SamplingMessageBuilder.userAudio(block: AudioContentBuilder.() -> Unit): Unit = + this.user(AudioContentBuilder().apply(block).build()) + +/** + * Adds an assistant message with simple text content. + * + * Example: + * ```kotlin + * messages { + * assistant { "The weather is sunny today." } + * } + * ``` + */ +@ExperimentalMcpApi +public fun SamplingMessageBuilder.assistant(block: () -> String): Unit = this.assistant(TextContent(text = block())) + +/** + * Adds an assistant message with text content using a DSL builder. + * + * Example: + * ```kotlin + * messages { + * assistantText { + * text = "Here's the answer" + * annotations(priority = 0.8) + * } + * } + * ``` + */ +@ExperimentalMcpApi +public fun SamplingMessageBuilder.assistantText(block: TextContentBuilder.() -> Unit): Unit = + this.assistant(TextContentBuilder().apply(block).build()) + +/** + * Adds an assistant message with image content. + * + * Example: + * ```kotlin + * messages { + * assistantImage { + * data = generatedImageData + * mimeType = "image/jpeg" + * } + * } + * ``` + */ +@ExperimentalMcpApi +public fun SamplingMessageBuilder.assistantImage(block: ImageContentBuilder.() -> Unit): Unit = + this.assistant(ImageContentBuilder().apply(block).build()) + +/** + * Adds an assistant message with audio content. + * + * Example: + * ```kotlin + * messages { + * assistantAudio { + * data = generatedAudioData + * mimeType = "audio/mpeg" + * } + * } + * ``` + */ +@ExperimentalMcpApi +public fun SamplingMessageBuilder.assistantAudio(block: AudioContentBuilder.() -> Unit): Unit = + this.assistant(AudioContentBuilder().apply(block).build()) diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.dsl.kt new file mode 100644 index 0000000..9a5adf1 --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.dsl.kt @@ -0,0 +1,172 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonObjectBuilder +import kotlinx.serialization.json.buildJsonObject +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +/** + * Creates a [CallToolRequest] using a type-safe DSL builder. + * + * ## Required + * - [name][CallToolRequestBuilder.name] - The name of the tool to call + * + * ## Optional + * - [arguments][CallToolRequestBuilder.arguments] - Arguments to pass to the tool + * - [meta][CallToolRequestBuilder.meta] - Metadata for the request + * + * Example without arguments: + * ```kotlin + * val request = buildCallToolRequest { + * name = "getCurrentTime" + * } + * ``` + * + * Example with arguments: + * ```kotlin + * val request = buildCallToolRequest { + * name = "searchDatabase" + * arguments { + * put("query", "users") + * put("limit", 10) + * } + * } + * ``` + * + * @param block Configuration lambda for setting up the call tool request + * @return A configured [CallToolRequest] instance + * @see CallToolRequestBuilder + * @see CallToolRequest + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +internal inline fun buildCallToolRequest(block: CallToolRequestBuilder.() -> Unit): CallToolRequest { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return CallToolRequestBuilder().apply(block).build() +} + +/** + * DSL builder for constructing [CallToolRequest] instances. + * + * This builder invokes a specific tool by name with optional arguments. + * + * ## Required + * - [name] - The name of the tool to call + * + * ## Optional + * - [arguments] - Arguments to pass to the tool + * - [meta] - Metadata for the request + * + * @see buildCallToolRequest + * @see CallToolRequest + */ +@McpDsl +public class CallToolRequestBuilder @PublishedApi internal constructor() : RequestBuilder() { + /** + * The name of the tool to call. This is a required field. + * + * Example: `name = "getCurrentTime"` + */ + public var name: String? = null + + private var arguments: JsonObject? = null + + /** + * Sets tool arguments directly from a JsonObject. + * + * Example: + * ```kotlin + * arguments(buildJsonObject { + * put("query", "users") + * }) + * ``` + */ + public fun arguments(arguments: JsonObject) { + this.arguments = arguments + } + + /** + * Sets tool arguments using a DSL builder. + * + * This is the recommended way to provide tool arguments. + * + * Example: + * ```kotlin + * arguments { + * put("query", "SELECT * FROM users") + * put("limit", 100) + * put("includeDeleted", false) + * } + * ``` + * + * @param block Lambda for building the arguments JsonObject + */ + public fun arguments(block: JsonObjectBuilder.() -> Unit): Unit = arguments(buildJsonObject(block)) + + @PublishedApi + override fun build(): CallToolRequest { + val name = requireNotNull(name) { + "Missing required field 'name'. Example: name = \"toolName\"" + } + + val params = CallToolRequestParams(name = name, arguments = arguments, meta = meta) + return CallToolRequest(params) + } +} + +/** + * Creates a [ListToolsRequest] using a type-safe DSL builder. + * + * ## Optional + * - [cursor][ListToolsRequestBuilder.cursor] - Pagination cursor for fetching next page + * - [meta][ListToolsRequestBuilder.meta] - Metadata for the request + * + * Example without pagination: + * ```kotlin + * val request = buildListToolsRequest { } + * ``` + * + * Example with pagination: + * ```kotlin + * val request = buildListToolsRequest { + * cursor = "eyJwYWdlIjogMn0=" + * } + * ``` + * + * @param block Configuration lambda for setting up the list tools request + * @return A configured [ListToolsRequest] instance + * @see ListToolsRequestBuilder + * @see ListToolsRequest + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +internal inline fun buildListToolsRequest(block: ListToolsRequestBuilder.() -> Unit): ListToolsRequest { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return ListToolsRequestBuilder().apply(block).build() +} + +/** + * DSL builder for constructing [ListToolsRequest] instances. + * + * This builder retrieves a list of available tools, with optional pagination support. + * All fields are optional. + * + * ## Optional + * - [cursor] - Pagination cursor (inherited from [PaginatedRequestBuilder]) + * - [meta] - Metadata for the request (inherited from [RequestBuilder]) + * + * @see buildListToolsRequest + * @see ListToolsRequest + * @see PaginatedRequestBuilder + */ +@McpDsl +public class ListToolsRequestBuilder @PublishedApi internal constructor() : PaginatedRequestBuilder() { + @PublishedApi + override fun build(): ListToolsRequest { + val params = paginatedRequestParams(cursor = cursor, meta = meta) + return ListToolsRequest(params) + } +} From 2b9dfd946622248c02b5696556f11d2d30391b79 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Thu, 20 Nov 2025 20:17:15 +0100 Subject: [PATCH 077/449] Add options in `Transport.send` method (#408) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for `TransportSendOptions` in `Transport.send` method ## Motivation and Context This change extends the `Transport.send` method signature to support advanced transport features like resumption tokens and progress callbacks. This is needed to enable: - Long-running request resumption after reconnection - Request-response association tracking - Progress token handling in transport layer needed for #235 ## How Has This Been Tested? All transport tests passed ## Breaking Changes Yes, it’s necessary to update the custom Transport implementations. For users who rely on the transports provided by the sdk, there’s no breaking change ## Types of changes - [x] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- kotlin-sdk-client/api/kotlin-sdk-client.api | 6 +- .../kotlin/sdk/client/SSEClientTransport.kt | 3 +- .../kotlin/sdk/client/StdioClientTransport.kt | 3 +- .../client/StreamableHttpClientTransport.kt | 5 +- .../kotlin/sdk/client/MockTransport.kt | 3 +- .../sdk/client/OldSchemaMockTransport.kt | 3 +- kotlin-sdk-core/api/kotlin-sdk-core.api | 43 +++++++--- .../kotlin/sdk/shared/Protocol.kt | 81 +++++++++++++------ .../kotlin/sdk/shared/Transport.kt | 6 +- .../kotlin/sdk/shared/TransportSendOptions.kt | 50 ++++++++++++ .../sdk/shared/WebSocketMcpTransport.kt | 2 +- .../kotlin/sdk/shared/ProtocolTest.kt | 2 +- kotlin-sdk-server/api/kotlin-sdk-server.api | 4 +- .../kotlin/sdk/server/SSEServerTransport.kt | 3 +- .../kotlin/sdk/server/StdioServerTransport.kt | 3 +- .../kotlin/sdk/client/ClientTest.kt | 13 +-- .../kotlin/sdk/client/OldSchemaClientTest.kt | 9 ++- .../kotlin/sdk/shared/InMemoryTransport.kt | 2 +- .../sdk/shared/OldSchemaInMemoryTransport.kt | 2 +- .../sse/KotlinServerForTsClientSse.kt | 3 +- .../OldSchemaKotlinServerForTsClientSse.kt | 3 +- 21 files changed, 186 insertions(+), 63 deletions(-) create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/TransportSendOptions.kt diff --git a/kotlin-sdk-client/api/kotlin-sdk-client.api b/kotlin-sdk-client/api/kotlin-sdk-client.api index e071e81..9186512 100644 --- a/kotlin-sdk-client/api/kotlin-sdk-client.api +++ b/kotlin-sdk-client/api/kotlin-sdk-client.api @@ -66,14 +66,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/client/SseClientTransport public synthetic fun (Lio/ktor/client/HttpClient;Ljava/lang/String;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun (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 (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; } @@ -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; diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt index d858c1d..0f1353b 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt @@ -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 @@ -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!" } diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt index 6e272eb..547079f 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt @@ -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 @@ -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") } diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt index a402874..bcd1a7c 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt @@ -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 @@ -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) } /** diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockTransport.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockTransport.kt index 860ed14..d7a48ef 100644 --- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockTransport.kt +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockTransport.kt @@ -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 @@ -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 } diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaMockTransport.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaMockTransport.kt index da813c7..1a0db70 100644 --- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaMockTransport.kt +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaMockTransport.kt @@ -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 @@ -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 } diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index def7e8b..2887083 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -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 @@ -476,13 +477,13 @@ public final class io/modelcontextprotocol/kotlin/sdk/shared/RequestHandlerExtra public fun ()V } -public final class io/modelcontextprotocol/kotlin/sdk/shared/RequestOptions { - public synthetic fun (Lkotlin/jvm/functions/Function1;JILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun (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 (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 (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 @@ -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 ()V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V + public synthetic fun (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 ()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; } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt index db8b6ef..7dd04bc 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt @@ -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 - /** - * 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, -) + 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 + } + + 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. @@ -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) } @@ -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) { @@ -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) } /** diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Transport.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Transport.kt index aa698b4..52f1438 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Transport.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Transport.kt @@ -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. diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/TransportSendOptions.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/TransportSendOptions.kt new file mode 100644 index 0000000..c56df04 --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/TransportSendOptions.kt @@ -0,0 +1,50 @@ +package io.modelcontextprotocol.kotlin.sdk.shared + +import io.modelcontextprotocol.kotlin.sdk.types.RequestId + +/** + * Options for sending a JSON-RPC message through transport. + * + * @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. + */ +public open class TransportSendOptions( + public val relatedRequestId: RequestId? = null, + public val resumptionToken: String? = null, + public val onResumptionToken: ((String) -> Unit)? = null, +) { + public operator fun component1(): RequestId? = relatedRequestId + public operator fun component2(): String? = resumptionToken + public operator fun component3(): ((String) -> Unit)? = onResumptionToken + + public open fun copy( + relatedRequestId: RequestId? = this.relatedRequestId, + resumptionToken: String? = this.resumptionToken, + onResumptionToken: ((String) -> Unit)? = this.onResumptionToken, + ): TransportSendOptions = TransportSendOptions(relatedRequestId, resumptionToken, onResumptionToken) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + + other as TransportSendOptions + + return relatedRequestId == other.relatedRequestId && + resumptionToken == other.resumptionToken && + onResumptionToken == other.onResumptionToken + } + + override fun hashCode(): Int { + var result = relatedRequestId?.hashCode() ?: 0 + result = 31 * result + (resumptionToken?.hashCode() ?: 0) + result = 31 * result + (onResumptionToken?.hashCode() ?: 0) + return result + } + + override fun toString(): String = + "TransportSendOptions(relatedRequestId=$relatedRequestId, resumptionToken=$resumptionToken, onResumptionToken=$onResumptionToken)" +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt index 4b20926..392efdc 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt @@ -90,7 +90,7 @@ public abstract class WebSocketMcpTransport : AbstractTransport() { } } - override suspend fun send(message: JSONRPCMessage) { + override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { logger.debug { "Sending message" } if (!initialized.load()) { error("Not connected") diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolTest.kt index f53366c..142c2af 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolTest.kt @@ -166,7 +166,7 @@ private class RecordingTransport : Transport { override suspend fun start() {} - override suspend fun send(message: JSONRPCMessage) { + override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { sentMessages.send(message) } diff --git a/kotlin-sdk-server/api/kotlin-sdk-server.api b/kotlin-sdk-server/api/kotlin-sdk-server.api index 11ac161..8016d3b 100644 --- a/kotlin-sdk-server/api/kotlin-sdk-server.api +++ b/kotlin-sdk-server/api/kotlin-sdk-server.api @@ -136,14 +136,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/server/SseServerTransport public final fun getSessionId ()Ljava/lang/String; public final fun handleMessage (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun handlePostMessage (Lio/ktor/server/application/ApplicationCall;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/server/StdioServerTransport : io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport { public fun (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; } diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt index 2e1a285..c662464 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt @@ -9,6 +9,7 @@ import io.ktor.server.request.receiveText import io.ktor.server.response.respondText import io.ktor.server.sse.ServerSSESession 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.InternalCoroutinesApi @@ -119,7 +120,7 @@ public class SseServerTransport(private val endpoint: String, private val sessio _onClose.invoke() } - override suspend fun send(message: JSONRPCMessage) { + override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { if (!initialized.load()) { error("Not connected") } diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport.kt index b65aecd..092d0a6 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport.kt @@ -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.CoroutineScope @@ -145,7 +146,7 @@ public class StdioServerTransport(private val inputStream: Source, outputStream: } } - override suspend fun send(message: JSONRPCMessage) { + override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { writeChannel.send(message) } } diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt index b4fbcb7..0726fa8 100644 --- a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt +++ b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt @@ -5,6 +5,7 @@ import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions import io.modelcontextprotocol.kotlin.sdk.server.ServerSession import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport +import io.modelcontextprotocol.kotlin.sdk.shared.TransportSendOptions import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageResult @@ -63,7 +64,7 @@ class ClientTest { val clientTransport = object : AbstractTransport() { override suspend fun start() {} - override suspend fun send(message: JSONRPCMessage) { + override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { if (message !is JSONRPCRequest) return initialised = true val result = InitializeResult( @@ -109,7 +110,7 @@ class ClientTest { val clientTransport = object : AbstractTransport() { override suspend fun start() {} - override suspend fun send(message: JSONRPCMessage) { + override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { if (message !is JSONRPCRequest) return check(message.method == Method.Defined.Initialize.value) @@ -158,7 +159,7 @@ class ClientTest { val clientTransport = object : AbstractTransport() { override suspend fun start() {} - override suspend fun send(message: JSONRPCMessage) { + override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { if (message !is JSONRPCRequest) return check(message.method == Method.Defined.Initialize.value) @@ -205,7 +206,7 @@ class ClientTest { val failingTransport = object : AbstractTransport() { override suspend fun start() {} - override suspend fun send(message: JSONRPCMessage) { + override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { if (message !is JSONRPCRequest) return check(message.method == Method.Defined.Initialize.value) throw IllegalStateException("Test error") @@ -239,7 +240,7 @@ class ClientTest { val failingTransport = object : AbstractTransport() { override suspend fun start() {} - override suspend fun send(message: JSONRPCMessage) { + override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { if (message !is JSONRPCRequest) return check(message.method == Method.Defined.Initialize.value) throw McpException( @@ -277,7 +278,7 @@ class ClientTest { val failingTransport = object : AbstractTransport() { override suspend fun start() {} - override suspend fun send(message: JSONRPCMessage) { + override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { if (message !is JSONRPCRequest) return check(message.method == Method.Defined.Initialize.value) throw StreamableHttpError( diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaClientTest.kt b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaClientTest.kt index 4c3c752..f9ef4f3 100644 --- a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaClientTest.kt +++ b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaClientTest.kt @@ -31,6 +31,7 @@ import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions import io.modelcontextprotocol.kotlin.sdk.server.ServerSession import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport +import io.modelcontextprotocol.kotlin.sdk.shared.TransportSendOptions import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.cancel @@ -57,7 +58,7 @@ class OldSchemaClientTest { val clientTransport = object : AbstractTransport() { override suspend fun start() {} - override suspend fun send(message: JSONRPCMessage) { + override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { if (message !is JSONRPCRequest) return initialised = true val result = InitializeResult( @@ -103,7 +104,7 @@ class OldSchemaClientTest { val clientTransport = object : AbstractTransport() { override suspend fun start() {} - override suspend fun send(message: JSONRPCMessage) { + override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { if (message !is JSONRPCRequest) return check(message.method == Method.Defined.Initialize.value) @@ -152,7 +153,7 @@ class OldSchemaClientTest { val clientTransport = object : AbstractTransport() { override suspend fun start() {} - override suspend fun send(message: JSONRPCMessage) { + override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { if (message !is JSONRPCRequest) return check(message.method == Method.Defined.Initialize.value) @@ -199,7 +200,7 @@ class OldSchemaClientTest { val failingTransport = object : AbstractTransport() { override suspend fun start() {} - override suspend fun send(message: JSONRPCMessage) { + override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { if (message !is JSONRPCRequest) return check(message.method == Method.Defined.Initialize.value) throw IllegalStateException("Test error") diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/InMemoryTransport.kt b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/InMemoryTransport.kt index 5c5fdd7..eabdf28 100644 --- a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/InMemoryTransport.kt +++ b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/InMemoryTransport.kt @@ -39,7 +39,7 @@ class InMemoryTransport : AbstractTransport() { _onClose.invoke() } - override suspend fun send(message: JSONRPCMessage) { + override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { val other = otherTransport ?: throw IllegalStateException("Not connected") other._onMessage.invoke(message) diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaInMemoryTransport.kt b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaInMemoryTransport.kt index e788918..9a0d4d3 100644 --- a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaInMemoryTransport.kt +++ b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaInMemoryTransport.kt @@ -39,7 +39,7 @@ class OldSchemaInMemoryTransport : AbstractTransport() { _onClose.invoke() } - override suspend fun send(message: JSONRPCMessage) { + override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { val other = otherTransport ?: throw IllegalStateException("Not connected") other._onMessage.invoke(message) diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt index 3aa36bc..d0056cb 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt @@ -20,6 +20,7 @@ import io.ktor.server.routing.routing import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport +import io.modelcontextprotocol.kotlin.sdk.shared.TransportSendOptions import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult import io.modelcontextprotocol.kotlin.sdk.types.Implementation @@ -427,7 +428,7 @@ class HttpServerTransport(private val sessionId: String) : AbstractTransport() { logger.debug { "Starting HTTP server transport for session: $sessionId" } } - override suspend fun send(message: JSONRPCMessage) { + override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { logger.info { "Sending message: $message" } if (message is JSONRPCResponse) { diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinServerForTsClientSse.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinServerForTsClientSse.kt index d9a5221..08dcad7 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinServerForTsClientSse.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinServerForTsClientSse.kt @@ -39,6 +39,7 @@ import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport import io.modelcontextprotocol.kotlin.sdk.shared.McpJson +import io.modelcontextprotocol.kotlin.sdk.shared.TransportSendOptions import io.modelcontextprotocol.kotlin.sdk.types.RPCError import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CompletableDeferred @@ -428,7 +429,7 @@ class OldSchemaHttpServerTransport(private val sessionId: String) : AbstractTran logger.debug { "Starting HTTP server transport for session: $sessionId" } } - override suspend fun send(message: JSONRPCMessage) { + override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { logger.info { "Sending message: $message" } if (message is JSONRPCResponse) { From 6dd9c80335d3e26781c970d95ab175d57530d43d Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Thu, 20 Nov 2025 22:06:21 +0200 Subject: [PATCH 078/449] Add convenience constructors to JSONRPCRequest (#336) - Add convenience constructors to JSONRPCRequest - Make UUID string a default JSONRPCRequest ## Motivation and Context To create request objects more easily ## How Has This Been Tested? Unit tests ## Breaking Changes No, this is a new API ## 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 - [ ] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed ## Additional context --- .../StreamableHttpClientTransportTest.kt | 5 +-- kotlin-sdk-core/api/kotlin-sdk-core.api | 6 +++ .../kotlin/sdk/types/jsonRpc.kt | 43 +++++++++++++++---- .../kotlin/sdk/types/JsonRpcTest.kt | 32 ++++++++++++++ 4 files changed, 75 insertions(+), 11 deletions(-) diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransportTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransportTest.kt index fea1de1..9968ceb 100644 --- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransportTest.kt +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransportTest.kt @@ -17,7 +17,6 @@ import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest import io.modelcontextprotocol.kotlin.sdk.types.McpJson -import io.modelcontextprotocol.kotlin.sdk.types.RequestId import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.test.runTest @@ -50,7 +49,7 @@ class StreamableHttpClientTransportTest { @Test fun testSendJsonRpcMessage() = runTest { val message = JSONRPCRequest( - id = RequestId.StringId("test-id"), + id = "test-id", method = "test", params = buildJsonObject { }, ) @@ -78,7 +77,7 @@ class StreamableHttpClientTransportTest { @Test fun testStoreSessionId() = runTest { val initMessage = JSONRPCRequest( - id = RequestId.StringId("test-id"), + id = "test-id", method = "initialize", params = buildJsonObject { put( diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index 2887083..8694e2d 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -2118,8 +2118,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/JSONRPCNotification$ public final class io/modelcontextprotocol/kotlin/sdk/types/JSONRPCRequest : io/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCRequest$Companion; + public fun (JLjava/lang/String;Lkotlinx/serialization/json/JsonElement;)V + public synthetic fun (JLjava/lang/String;Lkotlinx/serialization/json/JsonElement;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Ljava/lang/String;)V public fun (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;)V public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;)V + public fun (Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonElement;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 ()Lkotlinx/serialization/json/JsonElement; diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt index 6622900..8a580eb 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt @@ -8,15 +8,14 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.decodeFromJsonElement import kotlinx.serialization.json.encodeToJsonElement -import kotlin.concurrent.atomics.AtomicLong import kotlin.concurrent.atomics.ExperimentalAtomicApi -import kotlin.concurrent.atomics.incrementAndFetch import kotlin.jvm.JvmInline +import kotlin.jvm.JvmOverloads +import kotlin.uuid.ExperimentalUuidApi +import kotlin.uuid.Uuid public const val JSONRPC_VERSION: String = "2.0" -private val REQUEST_MESSAGE_ID: AtomicLong = AtomicLong(0L) - public fun RequestId(value: String): RequestId = RequestId.StringId(value) public fun RequestId(value: Long): RequestId = RequestId.NumberId(value) @@ -94,23 +93,50 @@ public sealed interface JSONRPCMessage { * A request that expects a response. * * Requests are identified by a unique [id] and specify a [method] to invoke. - * The server or client (depending on direction) must respond with either a + * The server or client (depending on a direction) must respond with either a * [JSONRPCResponse] or [JSONRPCError] that has the same [id]. * * @property jsonrpc Always "2.0" to indicate JSON-RPC 2.0 protocol. * @property id A unique identifier for this request. The response will include the same ID. - * Can be a string or number. + * Can be a string or number. UUID string is used by default. * @property method The name of the method to invoke (e.g., "tools/list", "resources/read"). * @property params Optional parameters for the method. Structure depends on the specific method. */ @Serializable -public data class JSONRPCRequest( - val id: RequestId = RequestId(REQUEST_MESSAGE_ID.incrementAndFetch()), +@OptIn(ExperimentalUuidApi::class) +public data class JSONRPCRequest @JvmOverloads public constructor( + val id: RequestId = RequestId(Uuid.random().toHexString()), val method: String, val params: JsonElement? = null, ) : JSONRPCMessage { @EncodeDefault override val jsonrpc: String = JSONRPC_VERSION + + /** + * Secondary constructor for creating a `JSONRPCRequest` using a string-based ID. + * + * @param id The string ID for the request. + * @param method The method name for the request. + * @param params The parameters for the request as a JSON element. Defaults to `null`. + */ + public constructor( + id: String, + method: String, + params: JsonElement? = null, + ) : this(id = RequestId.StringId(id), method = method, params = params) + + /** + * Constructs a JSON-RPC request using a numerical ID, method name, and optional parameters. + * + * @param id The numerical ID for the request. + * @param method The method name for the request. + * @param params The parameters for the request as a JSON element. Defaults to `null`. + */ + public constructor( + id: Long, + method: String, + params: JsonElement? = null, + ) : this(id = RequestId.NumberId(id), method = method, params = params) } // ============================================================================ @@ -207,6 +233,7 @@ public data class RPCError(val code: Int, val message: String, val data: JsonEle public const val REQUEST_TIMEOUT: Int = -32001 // Standard JSON-RPC 2.0 error codes + /** Invalid JSON was received */ public const val PARSE_ERROR: Int = -32700 diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/JsonRpcTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/JsonRpcTest.kt index 9b97032..d8d2058 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/JsonRpcTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/JsonRpcTest.kt @@ -1,6 +1,8 @@ package io.modelcontextprotocol.kotlin.sdk.types import io.kotest.assertions.json.shouldEqualJson +import io.kotest.matchers.shouldBe +import io.kotest.matchers.types.shouldBeSameInstanceAs import kotlinx.serialization.json.boolean import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.int @@ -398,4 +400,34 @@ class JsonRpcTest { } """.trimIndent() } + + @Test + fun `should create JSONRPCRequest with string ID`() { + val params = buildJsonObject { + put("foo", "bar") + } + val request = JSONRPCRequest( + id = "req-42", + method = "notifications/log", + params = params, + ) + request.id shouldBe RequestId("req-42") + request.method shouldBe "notifications/log" + request.params shouldBeSameInstanceAs params + } + + @Test + fun `should create JSONRPCRequest with numeric ID`() { + val params = buildJsonObject { + put("foo", "bar") + } + val request = JSONRPCRequest( + id = 42, + method = "notifications/log", + params = params, + ) + request.id shouldBe RequestId(42) + request.method shouldBe "notifications/log" + request.params shouldBeSameInstanceAs params + } } From e119a825a66a72fe9b6cb43f7a9592e122c70483 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Thu, 20 Nov 2025 23:32:56 +0100 Subject: [PATCH 079/449] Prepare release version 0.8.0 (#424) bump mcp kotlin-sdk to 0.8.0 ## 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 18d5b8f..dc8d2b4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,4 +14,4 @@ kotlin.mpp.enableCInteropCommonization=true kotlin.native.ignoreDisabledTargets=true group=io.modelcontextprotocol -version=0.8.0-SNAPSHOT +version=0.8.0 From e5abc3fb98205644e7545102209bbc6c11f8f903 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:42:28 +0200 Subject: [PATCH 080/449] Upgrade project version and `kotlin-sdk` dependency to 0.8.0 in samples. (#425) ## Motivation and Context Prepare for next develoment version ## How Has This Been Tested? Regression tests ## Breaking Changes ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [x] CI 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 ## Additional context --- gradle.properties | 2 +- .../gradle/libs.versions.toml | 2 +- .../gradle/libs.versions.toml | 2 +- samples/notebooks/McpClient.ipynb | 138 +++++++++--------- .../gradle/libs.versions.toml | 2 +- 5 files changed, 73 insertions(+), 73 deletions(-) diff --git a/gradle.properties b/gradle.properties index dc8d2b4..2e46c84 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,4 +14,4 @@ kotlin.mpp.enableCInteropCommonization=true kotlin.native.ignoreDisabledTargets=true group=io.modelcontextprotocol -version=0.8.0 +version=0.8.1-SNAPSHOT diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index ee688a2..d4e2f73 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -2,7 +2,7 @@ anthropic = "2.9.0" kotlin = "2.2.21" ktor = "3.2.3" -mcp-kotlin = "0.7.7" +mcp-kotlin = "0.8.0" shadow = "9.2.2" slf4j = "2.0.17" diff --git a/samples/kotlin-mcp-server/gradle/libs.versions.toml b/samples/kotlin-mcp-server/gradle/libs.versions.toml index af35f79..cbcd17d 100644 --- a/samples/kotlin-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-server/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] kotlin = "2.2.21" ktor = "3.2.3" -mcp-kotlin = "0.7.7" +mcp-kotlin = "0.8.0" slf4j = "2.0.17" shadow = "9.2.2" diff --git a/samples/notebooks/McpClient.ipynb b/samples/notebooks/McpClient.ipynb index 9b63771..2ec80e0 100644 --- a/samples/notebooks/McpClient.ipynb +++ b/samples/notebooks/McpClient.ipynb @@ -19,8 +19,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-11-14T14:12:47.844373Z", - "start_time": "2025-11-14T14:12:47.723693Z" + "end_time": "2025-11-21T09:59:29.552681Z", + "start_time": "2025-11-21T09:59:29.425830Z" } }, "cell_type": "code", @@ -30,18 +30,18 @@ "\n", "USE {\n", " dependencies(\n", - " \"io.modelcontextprotocol:kotlin-sdk-client-jvm:0.7.7\",\n", + " \"io.modelcontextprotocol:kotlin-sdk-client-jvm:0.8.0\",\n", " )\n", "}" ], "outputs": [], - "execution_count": 10 + "execution_count": 9 }, { "metadata": { "ExecuteTime": { - "end_time": "2025-11-14T14:12:47.859619Z", - "start_time": "2025-11-14T14:12:47.847619Z" + "end_time": "2025-11-21T09:59:29.570750Z", + "start_time": "2025-11-21T09:59:29.556538Z" } }, "cell_type": "code", @@ -63,7 +63,7 @@ "import io.ktor.client.plugins.sse.SSE" ], "outputs": [], - "execution_count": 11 + "execution_count": 10 }, { "metadata": {}, @@ -76,8 +76,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-11-14T14:12:47.907402Z", - "start_time": "2025-11-14T14:12:47.862414Z" + "end_time": "2025-11-21T09:59:29.621332Z", + "start_time": "2025-11-21T09:59:29.573344Z" } }, "cell_type": "code", @@ -95,7 +95,7 @@ "}\n" ], "outputs": [], - "execution_count": 12 + "execution_count": 11 }, { "metadata": {}, @@ -105,8 +105,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-11-14T14:12:47.939057Z", - "start_time": "2025-11-14T14:12:47.910220Z" + "end_time": "2025-11-21T09:59:29.649304Z", + "start_time": "2025-11-21T09:59:29.624152Z" } }, "cell_type": "code", @@ -120,7 +120,7 @@ ")" ], "outputs": [], - "execution_count": 13 + "execution_count": 12 }, { "metadata": {}, @@ -130,8 +130,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-11-14T14:12:48.846736Z", - "start_time": "2025-11-14T14:12:47.941357Z" + "end_time": "2025-11-21T09:59:30.691125Z", + "start_time": "2025-11-21T09:59:29.652092Z" } }, "cell_type": "code", @@ -158,11 +158,11 @@ "-> Accept: application/json, text/event-stream\n", "-> Accept-Charset: UTF-8\n", "CONTENT HEADERS\n", - "-> Content-Length: 200\n", + "-> Content-Length: 191\n", "-> Content-Type: application/json\n", "BODY Content-Type: application/json\n", "BODY START\n", - "{\"id\":5,\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2025-03-26\",\"capabilities\":{\"experimental\":{},\"sampling\":{}},\"clientInfo\":{\"name\":\"my-client\",\"version\":\"1.0.0\"},\"_meta\":{}},\"jsonrpc\":\"2.0\"}\n", + "{\"id\":\"7eed280ffa794c2fa0ae38447d64d516\",\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2025-06-18\",\"capabilities\":{},\"clientInfo\":{\"name\":\"my-client\",\"version\":\"1.0.0\"}},\"jsonrpc\":\"2.0\"}\n", "BODY END" ] }, @@ -183,10 +183,10 @@ "-> Cache-Control: no-cache, no-store\n", "-> Connection: keep-alive\n", "-> Content-Encoding: identity\n", - "-> Content-Length: 1914\n", + "-> Content-Length: 1947\n", "-> Content-Type: text/event-stream\n", - "-> Date: Fri, 14 Nov 2025 14:12:48 GMT\n", - "-> Expires: Fri, 14 Nov 2025 14:12:48 GMT\n", + "-> Date: Fri, 21 Nov 2025 09:59:30 GMT\n", + "-> Expires: Fri, 21 Nov 2025 09:59:30 GMT\n", "-> Mcp-Session-Id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n", "-> Request-Context: appId=cid-v1:ccd19013-8f20-4877-8d3f-84a0107e7119\n", "-> Set-Cookie: ASLBSA=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; Path=/; Secure; HttpOnly;; ASLBSACORS=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; SameSite=none; Path=/; Secure; HttpOnly;\n", @@ -195,11 +195,11 @@ "-> X-Powered-By: ASP.NET\n", "-> nel: {\"report_to\":\"network-errors\",\"max_age\":604800,\"success_fraction\":0.01,\"failure_fraction\":1.0}\n", "-> report-to: {\"group\":\"network-errors\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://mdec.nelreports.net/api/report?cat=mdocs\"}]}\n", - "-> x-azure-ref: 20251114T141248Z-169b4b8d65848xdthC1AMS8x840000000xfg00000000arwf\n", + "-> x-azure-ref: 20251121T095929Z-15dc879c854ch4blhC1FRA4w400000001650000000006yfy\n", "BODY Content-Type: text/event-stream\n", "BODY START\n", "event: message\n", - "data: {\"result\":{\"protocolVersion\":\"2025-03-26\",\"capabilities\":{\"logging\":{},\"prompts\":{},\"resources\":{},\"tools\":{\"listChanged\":true}},\"serverInfo\":{\"name\":\"Microsoft Learn MCP Server\",\"version\":\"1.0.0\"},\"instructions\":\"# Microsoft Learn MCP Server\\r\\n\\r\\nThis server gives structured access to official Microsoft and Azure documentation via three tools:\\r\\n\\r\\n## Tools\\r\\n\\r\\n### microsoft_docs_search\\r\\nSearch official documentation and return up to 10 concise, high-quality content chunks (max 500 tokens each), including title, URL, and excerpt.\\r\\n\\r\\n- Use first to get a quick, reliable overview\\r\\n- Ideal for grounding answers in Microsoft knowledge\\r\\n\\r\\n### microsoft_code_sample_search\\r\\nSearch for code snippets and examples in official Microsoft Learn documentation and return up to 20 relevant, high-quality code samples.\\r\\n\\r\\n- Use when you need to provide sample Microsoft/Azure related code in your answers. \\r\\n- Ideal for generating code snippets or practical implementation examples. \\r\\n- Optional parameter \\u0060language\\u0060 can filter results. \\r\\n\\r\\n### microsoft_docs_fetch\\r\\nFetch and convert full Microsoft documentation pages to markdown.\\r\\n\\r\\n- Use after search when you need full content from a specific URL\\r\\n- Required for detailed tutorials, troubleshooting, prerequisites, code samples, or when search results are incomplete or outdated\\r\\n\\r\\n## Workflow\\r\\n\\r\\n1. Use \\u0060microsoft_docs_search\\u0060 to find relevant documents.\\r\\n2. If you need code examples or practical snippets, use \\u0060microsoft_code_sample_search\\u0060.\\r\\n3. If deeper or complete information is needed, use \\u0060microsoft_docs_fetch\\u0060.\\r\\n\\r\\n**Search gives breadth. Code Sample Search gives practical examples. Fetch gives depth.**\\r\\n\\r\\nAll content comes from Microsoft Learn or official sources, returned in clean markdown format.\"},\"id\":5,\"jsonrpc\":\"2.0\"}\n", + "data: {\"result\":{\"protocolVersion\":\"2025-06-18\",\"capabilities\":{\"logging\":{},\"prompts\":{},\"resources\":{},\"tools\":{\"listChanged\":true}},\"serverInfo\":{\"name\":\"Microsoft Learn MCP Server\",\"version\":\"1.0.0\"},\"instructions\":\"# Microsoft Learn MCP Server\\r\\n\\r\\nThis server gives structured access to official Microsoft and Azure documentation via three tools:\\r\\n\\r\\n## Tools\\r\\n\\r\\n### microsoft_docs_search\\r\\nSearch official documentation and return up to 10 concise, high-quality content chunks (max 500 tokens each), including title, URL, and excerpt.\\r\\n\\r\\n- Use first to get a quick, reliable overview\\r\\n- Ideal for grounding answers in Microsoft knowledge\\r\\n\\r\\n### microsoft_code_sample_search\\r\\nSearch for code snippets and examples in official Microsoft Learn documentation and return up to 20 relevant, high-quality code samples.\\r\\n\\r\\n- Use when you need to provide sample Microsoft/Azure related code in your answers. \\r\\n- Ideal for generating code snippets or practical implementation examples. \\r\\n- Optional parameter \\u0060language\\u0060 can filter results. \\r\\n\\r\\n### microsoft_docs_fetch\\r\\nFetch and convert full Microsoft documentation pages to markdown.\\r\\n\\r\\n- Use after search when you need full content from a specific URL\\r\\n- Required for detailed tutorials, troubleshooting, prerequisites, code samples, or when search results are incomplete or outdated\\r\\n\\r\\n## Workflow\\r\\n\\r\\n1. Use \\u0060microsoft_docs_search\\u0060 to find relevant documents.\\r\\n2. If you need code examples or practical snippets, use \\u0060microsoft_code_sample_search\\u0060.\\r\\n3. If deeper or complete information is needed, use \\u0060microsoft_docs_fetch\\u0060.\\r\\n\\r\\n**Search gives breadth. Code Sample Search gives practical examples. Fetch gives depth.**\\r\\n\\r\\nAll content comes from Microsoft Learn or official sources, returned in clean markdown format.\"},\"id\":\"7eed280ffa794c2fa0ae38447d64d516\",\"jsonrpc\":\"2.0\"}\n", "\n", "\n", "BODY END" @@ -221,11 +221,11 @@ "-> Accept-Charset: UTF-8\n", "-> mcp-session-id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n", "CONTENT HEADERS\n", - "-> Content-Length: 76\n", + "-> Content-Length: 54\n", "-> Content-Type: application/json\n", "BODY Content-Type: application/json\n", "BODY START\n", - "{\"method\":\"notifications/initialized\",\"params\":{\"_meta\":{}},\"jsonrpc\":\"2.0\"}\n", + "{\"method\":\"notifications/initialized\",\"jsonrpc\":\"2.0\"}\n", "BODY END" ] }, @@ -247,8 +247,8 @@ "-> Connection: keep-alive\n", "-> Content-Encoding: identity\n", "-> Content-Length: 0\n", - "-> Date: Fri, 14 Nov 2025 14:12:48 GMT\n", - "-> Expires: Fri, 14 Nov 2025 14:12:48 GMT\n", + "-> Date: Fri, 21 Nov 2025 09:59:30 GMT\n", + "-> Expires: Fri, 21 Nov 2025 09:59:30 GMT\n", "-> Mcp-Session-Id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n", "-> Request-Context: appId=cid-v1:ccd19013-8f20-4877-8d3f-84a0107e7119\n", "-> Set-Cookie: ASLBSA=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; Path=/; Secure; HttpOnly;; ASLBSACORS=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; SameSite=none; Path=/; Secure; HttpOnly;\n", @@ -257,7 +257,7 @@ "-> X-Powered-By: ASP.NET\n", "-> nel: {\"report_to\":\"network-errors\",\"max_age\":604800,\"success_fraction\":0.01,\"failure_fraction\":1.0}\n", "-> report-to: {\"group\":\"network-errors\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://mdec.nelreports.net/api/report?cat=mdocs\"}]}\n", - "-> x-azure-ref: 20251114T141248Z-169b4b8d6588t6fthC1AMSxncg00000018v000000000ruuv\n", + "-> x-azure-ref: 20251121T095930Z-16f77b7fcffgtcqwhC1FRAd56w00000001h000000000hyt7\n", "BODY Content-Type: null\n", "BODY START\n", "\n", @@ -299,12 +299,12 @@ "name": "stdout", "output_type": "stream", "text": [ - "✅ Server version: Implementation(name=Microsoft Learn MCP Server, version=1.0.0)\n", - "✅ Server capabilities: ServerCapabilities(experimental={}, sampling={}, logging={}, prompts=Prompts(listChanged=null), resources=Resources(subscribe=null, listChanged=null), tools=Tools(listChanged=true))\n" + "✅ Server version: Implementation(name=Microsoft Learn MCP Server, version=1.0.0, title=null, websiteUrl=null, icons=null)\n", + "✅ Server capabilities: ServerCapabilities(tools=Tools(listChanged=true), resources=Resources(listChanged=null, subscribe=null), prompts=Prompts(listChanged=null), logging={}, completions=null, experimental=null)\n" ] } ], - "execution_count": 14 + "execution_count": 13 }, { "metadata": {}, @@ -314,8 +314,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-11-14T14:12:49.148355Z", - "start_time": "2025-11-14T14:12:48.859801Z" + "end_time": "2025-11-21T09:59:31.051698Z", + "start_time": "2025-11-21T09:59:30.701471Z" } }, "cell_type": "code", @@ -335,11 +335,11 @@ "-> Accept-Charset: UTF-8\n", "-> mcp-session-id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n", "CONTENT HEADERS\n", - "-> Content-Length: 52\n", + "-> Content-Length: 73\n", "-> Content-Type: application/json\n", "BODY Content-Type: application/json\n", "BODY START\n", - "{\"id\":6,\"method\":\"ping\",\"params\":{},\"jsonrpc\":\"2.0\"}\n", + "{\"id\":\"e9a2ae55b3a74fc2aeb1c3baef8e550b\",\"method\":\"ping\",\"jsonrpc\":\"2.0\"}\n", "BODY END" ] }, @@ -360,10 +360,10 @@ "-> Cache-Control: no-cache, no-store\n", "-> Connection: keep-alive\n", "-> Content-Encoding: identity\n", - "-> Content-Length: 59\n", + "-> Content-Length: 92\n", "-> Content-Type: text/event-stream\n", - "-> Date: Fri, 14 Nov 2025 14:12:49 GMT\n", - "-> Expires: Fri, 14 Nov 2025 14:12:49 GMT\n", + "-> Date: Fri, 21 Nov 2025 09:59:31 GMT\n", + "-> Expires: Fri, 21 Nov 2025 09:59:31 GMT\n", "-> Mcp-Session-Id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n", "-> Request-Context: appId=cid-v1:ccd19013-8f20-4877-8d3f-84a0107e7119\n", "-> Set-Cookie: ASLBSA=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; Path=/; Secure; HttpOnly;; ASLBSACORS=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; SameSite=none; Path=/; Secure; HttpOnly;\n", @@ -372,11 +372,11 @@ "-> X-Powered-By: ASP.NET\n", "-> nel: {\"report_to\":\"network-errors\",\"max_age\":604800,\"success_fraction\":0.01,\"failure_fraction\":1.0}\n", "-> report-to: {\"group\":\"network-errors\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://mdec.nelreports.net/api/report?cat=mdocs\"}]}\n", - "-> x-azure-ref: 20251114T141249Z-169b4b8d6588pb5bhC1AMS2m0w00000017n000000000w3kt\n", + "-> x-azure-ref: 20251121T095930Z-15dc879c854ch4blhC1FRA4w400000001650000000006ymf\n", "BODY Content-Type: text/event-stream\n", "BODY START\n", "event: message\n", - "data: {\"result\":{},\"id\":6,\"jsonrpc\":\"2.0\"}\n", + "data: {\"result\":{},\"id\":\"e9a2ae55b3a74fc2aeb1c3baef8e550b\",\"jsonrpc\":\"2.0\"}\n", "\n", "\n", "BODY END" @@ -391,15 +391,15 @@ { "data": { "text/plain": [ - "EmptyRequestResult(_meta={})" + "EmptyResult(meta=null)" ] }, - "execution_count": 15, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], - "execution_count": 15 + "execution_count": 14 }, { "metadata": {}, @@ -409,8 +409,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-11-14T14:12:49.511356Z", - "start_time": "2025-11-14T14:12:49.201984Z" + "end_time": "2025-11-21T09:59:31.459319Z", + "start_time": "2025-11-21T09:59:31.088064Z" } }, "cell_type": "code", @@ -434,11 +434,11 @@ "-> Accept-Charset: UTF-8\n", "-> mcp-session-id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n", "CONTENT HEADERS\n", - "-> Content-Length: 68\n", + "-> Content-Length: 79\n", "-> Content-Type: application/json\n", "BODY Content-Type: application/json\n", "BODY START\n", - "{\"id\":7,\"method\":\"tools/list\",\"params\":{\"_meta\":{}},\"jsonrpc\":\"2.0\"}\n", + "{\"id\":\"c3bd8dfe53c14365a926b9b49de4697e\",\"method\":\"tools/list\",\"jsonrpc\":\"2.0\"}\n", "BODY END" ] }, @@ -459,10 +459,10 @@ "-> Cache-Control: no-cache, no-store\n", "-> Connection: keep-alive\n", "-> Content-Encoding: identity\n", - "-> Content-Length: 4462\n", + "-> Content-Length: 4495\n", "-> Content-Type: text/event-stream\n", - "-> Date: Fri, 14 Nov 2025 14:12:49 GMT\n", - "-> Expires: Fri, 14 Nov 2025 14:12:49 GMT\n", + "-> Date: Fri, 21 Nov 2025 09:59:31 GMT\n", + "-> Expires: Fri, 21 Nov 2025 09:59:31 GMT\n", "-> Mcp-Session-Id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n", "-> Request-Context: appId=cid-v1:ccd19013-8f20-4877-8d3f-84a0107e7119\n", "-> Set-Cookie: ASLBSA=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; Path=/; Secure; HttpOnly;; ASLBSACORS=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; SameSite=none; Path=/; Secure; HttpOnly;\n", @@ -471,11 +471,11 @@ "-> X-Powered-By: ASP.NET\n", "-> nel: {\"report_to\":\"network-errors\",\"max_age\":604800,\"success_fraction\":0.01,\"failure_fraction\":1.0}\n", "-> report-to: {\"group\":\"network-errors\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://mdec.nelreports.net/api/report?cat=mdocs\"}]}\n", - "-> x-azure-ref: 20251114T141249Z-169b4b8d6588t6fthC1AMSxncg00000018v000000000ruxq\n", + "-> x-azure-ref: 20251121T095931Z-15dc879c8542qpsrhC1FRAgxhg0000000uwg000000002y7h\n", "BODY Content-Type: text/event-stream\n", "BODY START\n", "event: message\n", - "data: {\"result\":{\"tools\":[{\"name\":\"microsoft_docs_search\",\"title\":\"Microsoft Docs Search\",\"description\":\"Search official Microsoft/Azure documentation to find the most relevant and trustworthy content for a user\\u0027s query. This tool returns up to 10 high-quality content chunks (each max 500 tokens), extracted from Microsoft Learn and other official sources. Each result includes the article title, URL, and a self-contained content excerpt optimized for fast retrieval and reasoning. Always use this tool to quickly ground your answers in accurate, first-party Microsoft/Azure knowledge.\\n\\nThe \\u0060question\\u0060 parameter is no longer used, use \\u0060query\\u0060 instead.\\n\\n## Follow-up Pattern\\nTo ensure completeness, use microsoft_docs_fetch when high-value pages are identified by search. The fetch tool complements search by providing the full detail. This is a required step for comprehensive results.\",\"inputSchema\":{\"type\":\"object\",\"properties\":{\"query\":{\"description\":\"a query or topic about Microsoft/Azure products, services, platforms, developer tools, frameworks, or APIs\",\"type\":\"string\",\"default\":null},\"question\":{\"description\":\"this parameter is no longer used, use query instead.\",\"type\":\"string\",\"default\":null}}},\"annotations\":{\"title\":\"Microsoft Docs Search\",\"destructiveHint\":false,\"idempotentHint\":true,\"readOnlyHint\":true}},{\"name\":\"microsoft_code_sample_search\",\"title\":\"Microsoft Code Sample Search\",\"description\":\"Search for code snippets and examples in official Microsoft Learn documentation. This tool retrieves relevant code samples from Microsoft documentation pages providing developers with practical implementation examples and best practices for Microsoft/Azure products and services related coding tasks. This tool will help you use the **LATEST OFFICIAL** code snippets to empower coding capabilities.\\n\\n## When to Use This Tool\\n- When you are going to provide sample Microsoft/Azure related code snippets in your answers.\\n- When you are **generating any Microsoft/Azure related code**.\\n\\n## Usage Pattern\\nInput a descriptive query, or SDK/class/method name to retrieve related code samples. The optional parameter \\u0060language\\u0060 can help to filter results.\\n\\nEligible values for \\u0060language\\u0060 parameter include: csharp javascript typescript python powershell azurecli al sql java kusto cpp go rust ruby php\",\"inputSchema\":{\"type\":\"object\",\"properties\":{\"query\":{\"description\":\"a descriptive query, SDK name, method name or code snippet related to Microsoft/Azure products, services, platforms, developer tools, frameworks, APIs or SDKs\",\"type\":\"string\"},\"language\":{\"description\":\"Optional parameter specifying the programming language of code snippets to retrieve. Can significantly improve search quality if provided. Eligible values: csharp javascript typescript python powershell azurecli al sql java kusto cpp go rust ruby php\",\"type\":\"string\",\"default\":null}},\"required\":[\"query\"]},\"annotations\":{\"title\":\"Microsoft Code Sample Search\",\"destructiveHint\":false,\"idempotentHint\":true,\"readOnlyHint\":true}},{\"name\":\"microsoft_docs_fetch\",\"title\":\"Microsoft Docs Fetch\",\"description\":\"Fetch and convert a Microsoft Learn documentation page to markdown format. This tool retrieves the latest complete content of Microsoft documentation pages including Azure, .NET, Microsoft 365, and other Microsoft technologies.\\n\\n## When to Use This Tool\\n- When search results provide incomplete information or truncated content\\n- When you need complete step-by-step procedures or tutorials\\n- When you need troubleshooting sections, prerequisites, or detailed explanations\\n- When search results reference a specific page that seems highly relevant\\n- For comprehensive guides that require full context\\n\\n## Usage Pattern\\nUse this tool AFTER microsoft_docs_search when you identify specific high-value pages that need complete content. The search tool gives you an overview; this tool gives you the complete picture.\\n\\n## URL Requirements\\n- The URL must be a valid link from the microsoft.com domain.\\n\\n## Output Format\\nmarkdown with headings, code blocks, tables, and links preserved.\",\"inputSchema\":{\"type\":\"object\",\"properties\":{\"url\":{\"description\":\"URL of the Microsoft documentation page to read\",\"type\":\"string\"}},\"required\":[\"url\"]},\"annotations\":{\"title\":\"Microsoft Docs Fetch\",\"destructiveHint\":false,\"idempotentHint\":true,\"readOnlyHint\":true}}]},\"id\":7,\"jsonrpc\":\"2.0\"}\n", + "data: {\"result\":{\"tools\":[{\"name\":\"microsoft_docs_search\",\"title\":\"Microsoft Docs Search\",\"description\":\"Search official Microsoft/Azure documentation to find the most relevant and trustworthy content for a user\\u0027s query. This tool returns up to 10 high-quality content chunks (each max 500 tokens), extracted from Microsoft Learn and other official sources. Each result includes the article title, URL, and a self-contained content excerpt optimized for fast retrieval and reasoning. Always use this tool to quickly ground your answers in accurate, first-party Microsoft/Azure knowledge.\\n\\nThe \\u0060question\\u0060 parameter is no longer used, use \\u0060query\\u0060 instead.\\n\\n## Follow-up Pattern\\nTo ensure completeness, use microsoft_docs_fetch when high-value pages are identified by search. The fetch tool complements search by providing the full detail. This is a required step for comprehensive results.\",\"inputSchema\":{\"type\":\"object\",\"properties\":{\"query\":{\"description\":\"a query or topic about Microsoft/Azure products, services, platforms, developer tools, frameworks, or APIs\",\"type\":\"string\",\"default\":null},\"question\":{\"description\":\"this parameter is no longer used, use query instead.\",\"type\":\"string\",\"default\":null}}},\"annotations\":{\"title\":\"Microsoft Docs Search\",\"destructiveHint\":false,\"idempotentHint\":true,\"readOnlyHint\":true}},{\"name\":\"microsoft_code_sample_search\",\"title\":\"Microsoft Code Sample Search\",\"description\":\"Search for code snippets and examples in official Microsoft Learn documentation. This tool retrieves relevant code samples from Microsoft documentation pages providing developers with practical implementation examples and best practices for Microsoft/Azure products and services related coding tasks. This tool will help you use the **LATEST OFFICIAL** code snippets to empower coding capabilities.\\n\\n## When to Use This Tool\\n- When you are going to provide sample Microsoft/Azure related code snippets in your answers.\\n- When you are **generating any Microsoft/Azure related code**.\\n\\n## Usage Pattern\\nInput a descriptive query, or SDK/class/method name to retrieve related code samples. The optional parameter \\u0060language\\u0060 can help to filter results.\\n\\nEligible values for \\u0060language\\u0060 parameter include: csharp javascript typescript python powershell azurecli al sql java kusto cpp go rust ruby php\",\"inputSchema\":{\"type\":\"object\",\"properties\":{\"query\":{\"description\":\"a descriptive query, SDK name, method name or code snippet related to Microsoft/Azure products, services, platforms, developer tools, frameworks, APIs or SDKs\",\"type\":\"string\"},\"language\":{\"description\":\"Optional parameter specifying the programming language of code snippets to retrieve. Can significantly improve search quality if provided. Eligible values: csharp javascript typescript python powershell azurecli al sql java kusto cpp go rust ruby php\",\"type\":\"string\",\"default\":null}},\"required\":[\"query\"]},\"annotations\":{\"title\":\"Microsoft Code Sample Search\",\"destructiveHint\":false,\"idempotentHint\":true,\"readOnlyHint\":true}},{\"name\":\"microsoft_docs_fetch\",\"title\":\"Microsoft Docs Fetch\",\"description\":\"Fetch and convert a Microsoft Learn documentation page to markdown format. This tool retrieves the latest complete content of Microsoft documentation pages including Azure, .NET, Microsoft 365, and other Microsoft technologies.\\n\\n## When to Use This Tool\\n- When search results provide incomplete information or truncated content\\n- When you need complete step-by-step procedures or tutorials\\n- When you need troubleshooting sections, prerequisites, or detailed explanations\\n- When search results reference a specific page that seems highly relevant\\n- For comprehensive guides that require full context\\n\\n## Usage Pattern\\nUse this tool AFTER microsoft_docs_search when you identify specific high-value pages that need complete content. The search tool gives you an overview; this tool gives you the complete picture.\\n\\n## URL Requirements\\n- The URL must be a valid link from the microsoft.com domain.\\n\\n## Output Format\\nmarkdown with headings, code blocks, tables, and links preserved.\",\"inputSchema\":{\"type\":\"object\",\"properties\":{\"url\":{\"description\":\"URL of the Microsoft documentation page to read\",\"type\":\"string\"}},\"required\":[\"url\"]},\"annotations\":{\"title\":\"Microsoft Docs Fetch\",\"destructiveHint\":false,\"idempotentHint\":true,\"readOnlyHint\":true}}]},\"id\":\"c3bd8dfe53c14365a926b9b49de4697e\",\"jsonrpc\":\"2.0\"}\n", "\n", "\n", "BODY END" @@ -490,12 +490,12 @@ { "data": { "text/plain": [ - "Tool(name=microsoft_docs_search, title=Microsoft Docs Search, description=Search official Microsoft/Azure documentation to find the most relevant and trustworthy content for a user's query. This tool returns up to 10 high-quality content chunks (each max 500 tokens), extracted from Microsoft Learn and other official sources. Each result includes the article title, URL, and a self-contained content excerpt optimized for fast retrieval and reasoning. Always use this tool to quickly ground your answers in accurate, first-party Microsoft/Azure knowledge.\n", + "Tool(name=microsoft_docs_search, inputSchema=ToolSchema(properties={\"query\":{\"description\":\"a query or topic about Microsoft/Azure products, services, platforms, developer tools, frameworks, or APIs\",\"type\":\"string\",\"default\":null},\"question\":{\"description\":\"this parameter is no longer used, use query instead.\",\"type\":\"string\",\"default\":null}}, required=null), description=Search official Microsoft/Azure documentation to find the most relevant and trustworthy content for a user's query. This tool returns up to 10 high-quality content chunks (each max 500 tokens), extracted from Microsoft Learn and other official sources. Each result includes the article title, URL, and a self-contained content excerpt optimized for fast retrieval and reasoning. Always use this tool to quickly ground your answers in accurate, first-party Microsoft/Azure knowledge.\n", "\n", "The `question` parameter is no longer used, use `query` instead.\n", "\n", "## Follow-up Pattern\n", - "To ensure completeness, use microsoft_docs_fetch when high-value pages are identified by search. The fetch tool complements search by providing the full detail. This is a required step for comprehensive results., inputSchema=Input(properties={\"query\":{\"description\":\"a query or topic about Microsoft/Azure products, services, platforms, developer tools, frameworks, or APIs\",\"type\":\"string\",\"default\":null},\"question\":{\"description\":\"this parameter is no longer used, use query instead.\",\"type\":\"string\",\"default\":null}}, required=null), outputSchema=null, annotations=ToolAnnotations(title=Microsoft Docs Search, readOnlyHint=true, destructiveHint=false, idempotentHint=true, openWorldHint=true), _meta={})" + "To ensure completeness, use microsoft_docs_fetch when high-value pages are identified by search. The fetch tool complements search by providing the full detail. This is a required step for comprehensive results., outputSchema=null, title=Microsoft Docs Search, annotations=ToolAnnotations(title=Microsoft Docs Search, readOnlyHint=true, destructiveHint=false, idempotentHint=true, openWorldHint=null), icons=null, meta=null)" ] }, "metadata": {}, @@ -507,7 +507,7 @@ { "data": { "text/plain": [ - "Tool(name=microsoft_code_sample_search, title=Microsoft Code Sample Search, description=Search for code snippets and examples in official Microsoft Learn documentation. This tool retrieves relevant code samples from Microsoft documentation pages providing developers with practical implementation examples and best practices for Microsoft/Azure products and services related coding tasks. This tool will help you use the **LATEST OFFICIAL** code snippets to empower coding capabilities.\n", + "Tool(name=microsoft_code_sample_search, inputSchema=ToolSchema(properties={\"query\":{\"description\":\"a descriptive query, SDK name, method name or code snippet related to Microsoft/Azure products, services, platforms, developer tools, frameworks, APIs or SDKs\",\"type\":\"string\"},\"language\":{\"description\":\"Optional parameter specifying the programming language of code snippets to retrieve. Can significantly improve search quality if provided. Eligible values: csharp javascript typescript python powershell azurecli al sql java kusto cpp go rust ruby php\",\"type\":\"string\",\"default\":null}}, required=[query]), description=Search for code snippets and examples in official Microsoft Learn documentation. This tool retrieves relevant code samples from Microsoft documentation pages providing developers with practical implementation examples and best practices for Microsoft/Azure products and services related coding tasks. This tool will help you use the **LATEST OFFICIAL** code snippets to empower coding capabilities.\n", "\n", "## When to Use This Tool\n", "- When you are going to provide sample Microsoft/Azure related code snippets in your answers.\n", @@ -516,7 +516,7 @@ "## Usage Pattern\n", "Input a descriptive query, or SDK/class/method name to retrieve related code samples. The optional parameter `language` can help to filter results.\n", "\n", - "Eligible values for `language` parameter include: csharp javascript typescript python powershell azurecli al sql java kusto cpp go rust ruby php, inputSchema=Input(properties={\"query\":{\"description\":\"a descriptive query, SDK name, method name or code snippet related to Microsoft/Azure products, services, platforms, developer tools, frameworks, APIs or SDKs\",\"type\":\"string\"},\"language\":{\"description\":\"Optional parameter specifying the programming language of code snippets to retrieve. Can significantly improve search quality if provided. Eligible values: csharp javascript typescript python powershell azurecli al sql java kusto cpp go rust ruby php\",\"type\":\"string\",\"default\":null}}, required=[query]), outputSchema=null, annotations=ToolAnnotations(title=Microsoft Code Sample Search, readOnlyHint=true, destructiveHint=false, idempotentHint=true, openWorldHint=true), _meta={})" + "Eligible values for `language` parameter include: csharp javascript typescript python powershell azurecli al sql java kusto cpp go rust ruby php, outputSchema=null, title=Microsoft Code Sample Search, annotations=ToolAnnotations(title=Microsoft Code Sample Search, readOnlyHint=true, destructiveHint=false, idempotentHint=true, openWorldHint=null), icons=null, meta=null)" ] }, "metadata": {}, @@ -528,7 +528,7 @@ { "data": { "text/plain": [ - "Tool(name=microsoft_docs_fetch, title=Microsoft Docs Fetch, description=Fetch and convert a Microsoft Learn documentation page to markdown format. This tool retrieves the latest complete content of Microsoft documentation pages including Azure, .NET, Microsoft 365, and other Microsoft technologies.\n", + "Tool(name=microsoft_docs_fetch, inputSchema=ToolSchema(properties={\"url\":{\"description\":\"URL of the Microsoft documentation page to read\",\"type\":\"string\"}}, required=[url]), description=Fetch and convert a Microsoft Learn documentation page to markdown format. This tool retrieves the latest complete content of Microsoft documentation pages including Azure, .NET, Microsoft 365, and other Microsoft technologies.\n", "\n", "## When to Use This Tool\n", "- When search results provide incomplete information or truncated content\n", @@ -544,7 +544,7 @@ "- The URL must be a valid link from the microsoft.com domain.\n", "\n", "## Output Format\n", - "markdown with headings, code blocks, tables, and links preserved., inputSchema=Input(properties={\"url\":{\"description\":\"URL of the Microsoft documentation page to read\",\"type\":\"string\"}}, required=[url]), outputSchema=null, annotations=ToolAnnotations(title=Microsoft Docs Fetch, readOnlyHint=true, destructiveHint=false, idempotentHint=true, openWorldHint=true), _meta={})" + "markdown with headings, code blocks, tables, and links preserved., outputSchema=null, title=Microsoft Docs Fetch, annotations=ToolAnnotations(title=Microsoft Docs Fetch, readOnlyHint=true, destructiveHint=false, idempotentHint=true, openWorldHint=null), icons=null, meta=null)" ] }, "metadata": {}, @@ -554,7 +554,7 @@ } } ], - "execution_count": 16 + "execution_count": 15 }, { "metadata": {}, @@ -564,8 +564,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-11-14T14:12:50.568880Z", - "start_time": "2025-11-14T14:12:49.524596Z" + "end_time": "2025-11-21T09:59:32.450109Z", + "start_time": "2025-11-21T09:59:31.467604Z" } }, "cell_type": "code", @@ -593,11 +593,11 @@ "-> Accept-Charset: UTF-8\n", "-> mcp-session-id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n", "CONTENT HEADERS\n", - "-> Content-Length: 129\n", + "-> Content-Length: 162\n", "-> Content-Type: application/json\n", "BODY Content-Type: application/json\n", "BODY START\n", - "{\"id\":8,\"method\":\"tools/call\",\"params\":{\"name\":\"microsoft_docs_search\",\"arguments\":{\"query\":\"Linux\"},\"_meta\":{}},\"jsonrpc\":\"2.0\"}\n", + "{\"id\":\"ffda9f6266ce4bfa8f086d23d03c025a\",\"method\":\"tools/call\",\"params\":{\"name\":\"microsoft_docs_search\",\"arguments\":{\"query\":\"Linux\"},\"_meta\":{}},\"jsonrpc\":\"2.0\"}\n", "BODY END" ] }, @@ -618,10 +618,10 @@ "-> Cache-Control: no-cache, no-store\n", "-> Connection: keep-alive\n", "-> Content-Encoding: identity\n", - "-> Content-Length: 22458\n", + "-> Content-Length: 22491\n", "-> Content-Type: text/event-stream\n", - "-> Date: Fri, 14 Nov 2025 14:12:50 GMT\n", - "-> Expires: Fri, 14 Nov 2025 14:12:50 GMT\n", + "-> Date: Fri, 21 Nov 2025 09:59:32 GMT\n", + "-> Expires: Fri, 21 Nov 2025 09:59:32 GMT\n", "-> Mcp-Session-Id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n", "-> Request-Context: appId=cid-v1:ccd19013-8f20-4877-8d3f-84a0107e7119\n", "-> Set-Cookie: ASLBSA=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; Path=/; Secure; HttpOnly;; ASLBSACORS=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; SameSite=none; Path=/; Secure; HttpOnly;\n", @@ -630,11 +630,11 @@ "-> X-Powered-By: ASP.NET\n", "-> nel: {\"report_to\":\"network-errors\",\"max_age\":604800,\"success_fraction\":0.01,\"failure_fraction\":1.0}\n", "-> report-to: {\"group\":\"network-errors\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://mdec.nelreports.net/api/report?cat=mdocs\"}]}\n", - "-> x-azure-ref: 20251114T141249Z-169b4b8d6588pb5bhC1AMS2m0w00000017n000000000w3q8\n", + "-> x-azure-ref: 20251121T095931Z-15dc879c854mpd4vhC1FRAa2us00000001mg00000000dt67\n", "BODY Content-Type: text/event-stream\n", "BODY START\n", "event: message\n", - "data: {\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"[{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\n## Step 2 - Choose a Linux distribution\\\\nThe different versions of Linux are called \\\\u0022distributions\\\\u0022 (sometimes shortened to \\\\u201Cdistros\\\\u201D). So there isn\\\\u2019t actually a way to just \\\\u201Cinstall Linux\\\\u201D, you first need to choose which distribution that uses the Linux kernel you want to download and install. There are over 600 active Linux distributions, such as Ubuntu, Debian, Kali Linux, openSUSE, and more. There are a number of factors to consider in choosing a distribution, such as:\\\\n### Required Linux experience\\\\nIf you\\\\u2019re new to Linux, you may want to start with Ubuntu. This is one of the most common starting places for Linux users due to the level of support and ease of use. Ubuntu offers a desktop-version and a server-version depending on how you plan to use it. The specific tasks that the operating system will be responsible for may help you determine which distribution to use. Arch Linux is a popular choice for those who want a highly customizable, do-it-yourself approach that is still stable and well-supported by a large user base. It is a much more complicated place to start, but can help you to get a better understanding of how Linux works due to the amount of custom configuration.\\\\n### System requirements\\\\nA minimal, or \\\\u0022lightweight\\\\u0022, distribution means a small footprint, with lower memory and processing speed requirements. Alpine Linux is a popular option where users will find most features disabled or not installed by default, but still has everything you need for a containerized app. Security needs: Some Linux users may have specific security concerns to consider. Kali Linux is an example of a distro designed for security tasks, such as Penetration Testing, Security Research, Computer Forensics and Reverse Engineering.\\\\n### Business and Enterprise requirements\\\\nBusiness, corporate, or academic environments may have unique concerns from consumer environments. For example Red Hat Enterprise Linux (RHEL), SUSE Linux Enterprise Server (SLES) and Oracle Linux offer 24/7 global support and subscription-based service. CentOS is another popular enterprise distro that is community-supported but still compatible with RHEL.\\\\n### Community support\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/linux/install#step-2---choose-a-linux-distribution\\u0022},{\\u0022title\\u0022:\\u0022New to Linux resources for SQL users\\u0022,\\u0022content\\u0022:\\u0022# New to Linux resources for SQL users\\\\n**Applies to:**[SQL Server](https://learn.microsoft.com/en-us/sql/sql-server/sql-docs-navigation-guide?view=sql-server-ver17#applies-to) - Linux\\\\nThis article is intended to provide a learning path through an introduction of Linux concepts. Use the sections in the article as a guided learning path to familiarize yourself with the Linux environment.\\\\nThis isn\\\\u0027t intended to be a comprehensive list but instead provides the bare minimum you need to help you administer and navigate your SQL Server on Linux environment. See the [full list of tutorials](https://www.linux.org/forums/linux-beginner-tutorials.123/) to learn more.\\\\n## What is Linux?\\\\nThe [What is Linux](https://www.linux.org/threads/what-is-linux.4106/) module introduces the history of Linux as an operating system. The module explains *the Kernel* and where Linux is today. This tutorial serves as an introduction to Linux, helping you get started.\\\\n## Select a distribution\\\\nAfter you know the history of Linux, decide which [distribution of Linux](https://www.linux.org/threads/selecting-a-linux-distribution.4117/) best suits your business needs. [SQL Server is supported](https://learn.microsoft.com/en-us/sql/linux/sql-server-linux-release-notes-2019?view=sql-server-ver17#supported-platforms) on various distributions such as Red Hat Enterprise Linux (RHEL), SUSE Linux Enterprise Server (SLES), and Ubuntu.\\\\n## Get around directories\\\\nAfter you have chosen the appropriate distribution of Linux, familiarize yourself with [getting around Linux directories](https://www.linux.org/threads/getting-around-in-linux-directories.4120/).\\\\nThis module will help you answer the following questions:\\\\n1. How to navigate between different files\\\\n2. How to identify a file from a directory\\\\n3. Identify different directories\\\\n## Install new software\\\\nNext, learn how to [install new software](https://www.linux.org/threads/installing-new-software-debian-red-hat-slackware.4119/) on your new Linux operating system. This module details installing new software to the Debian and Red Hat Linux operating systems.\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/sql/linux/new-to-linux-resources?view=sql-server-ver17\\u0022},{\\u0022title\\u0022:\\u0022Part 1.2 - Basic operations on Linux\\u0022,\\u0022content\\u0022:\\u0022# Part 1.2 - Basic operations on Linux\\\\n## Run commands by using the \\\\u0022sudo\\\\u0022 prefix\\\\nInstead of becoming the root account user in your session, you can run commands as root by using the \\\\u0060sudo \\\\u003Ccommand\\\\u003E\\\\u0060 format. This approach is safer because it will run the given command only as a superuser.\\\\nFor example, if you run \\\\u0060restart\\\\u0060 while you\\\\u0027re not elevated to superuser status, the result is as follows.\\\\n![Screenshot of restart command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/restart-command.png)\\\\nTo run this command as a superuser instead, you can either change the session context to root (by running \\\\u0060sudo su\\\\u0060) or add the \\\\u0060sudo\\\\u0060 prefix, as follows:\\\\n\\\\u0060\\\\u0060\\\\u0060bash\\\\nsudo reboot\\\\n\\\\u0060\\\\u0060\\\\u0060\\\\nNow, the restart operation runs as expected by the virtual machine.\\\\n![Screenshot of reboot command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/reboot-command.png)\\\\n## Package managers\\\\nPackage managers are used to install, upgrade, and remove applications in Linux. There are several different package managers, such as DPKG (Debian Package Management System), RPM (Red Hat Package Manager), and Zypper for SUSE Package managers themselves are low-level. Therefore, it might be difficult to manage software directly by using the package manager. Fortunately, there are wrappers for these tools that make it easier to install and uninstall software. For example, [yum](https://wikipedia.org/wiki/Yum_%28software%29) is a wrapper around RPM (Red Hat Package Manager) and [APT](https://wikipedia.org/wiki/APT_%28software%29) is a wrapper around DPKG (Debian Package Management System).\\\\nThis tutorial assumes that you\\\\u0027re running the [Ubuntu package management system](https://ubuntu.com/server/docs/package-management). This system is derived from the same system that\\\\u0027s used by the Debian GNU/Linux distribution. Therefore, we can use APT to install the software.\\\\nDepending on your Linux choice, you might have to use other tools. For example, you can use yum to install or uninstall software if you\\\\u0027re running Red Hat.\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-by-using-the-sudo-prefix\\u0022},{\\u0022title\\u0022:\\u0022Part 1.2 - Basic operations on Linux\\u0022,\\u0022content\\u0022:\\u0022# Part 1.2 - Basic operations on Linux\\\\n## Run commands as a superuser\\\\nThe superuser is the most privileged user account. It has root (unrestricted) access to all files and folders, and complete control over the operation of the computer. The system administrator uses this account for system maintenance.\\\\nOn UNIX-like systems, the conventional name of the superuser is \\\\u0022root\\\\u0022. The root user can do tasks that are otherwise restricted for standard users, including the following ones:\\\\n1. Changing system directories\\\\n2. Changing user privileges\\\\n3. Changing file ownership\\\\n4. System-related tasks such as restarts\\\\n5. Installation of certain applications\\\\nThere are several ways to run commands as the root account user. One is to use the \\\\u0060sudo su\\\\u0060 command. In this command, \\\\u0060sudo\\\\u0060 is short for \\\\u0060superuser do\\\\u0060, and \\\\u0060su\\\\u0060 is an abbreviation for \\\\u0022switch user.\\\\u0022 You can also use the \\\\u0060su\\\\u0060 command to switch between standard users.\\\\nImportant\\\\nAfter you become the root account user, everything you do runs in root context. Therefore, you should act carefully because the root account has access to the entire system.\\\\nTo demonstrate a permissions issue, create a text file in a special folder that\\\\u0027s named */etc*.\\\\nThe */etc/* folder is where system configuration files are located. This folder protects its contents from being changed by a standard user.\\\\nRun the following command:\\\\n\\\\u0060\\\\u0060\\\\u0060bash\\\\necho hello world \\\\u003E /etc/helloworld.txt\\\\n\\\\u0060\\\\u0060\\\\u0060\\\\nThe \\\\u0060echo\\\\u0060 command writes all the text that follows it to the output. The angle bracket (\\\\u0060\\\\u003E\\\\u0060) tells the system to send the output to the */etc/helloworld.txt* file instead of the console. This behavior is similar to how Windows works.\\\\nNote\\\\nLinux is case-sensitive: *Helloworld.txt*, *helloworld.txt* and *helloworld.Txt* are different files.\\\\nYou don\\\\u0027t have to provide a file name extension. The name \\\\u0022helloworld\\\\u0022 by itself is perfectly valid. (The \\\\u0022txt\\\\u0022 extension is used here only as an example.)\\\\nWhen you run this command, you receive a **permission denied** error message.\\\\n![Screenshot of permission denied message.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/permission-denied-message.png)\\\\nThe operation fails because the */etc* folder is special folder that a standard user can\\\\u0027t change. Verify the user by running the \\\\u0060id\\\\u0060 command.\\\\n![Screenshot of I D command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/id-command.png)\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-as-a-superuser\\u0022},{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\n## Step 1 - Choose a method to install Linux\\\\nThe choice of which method to use for installing Linux depends on your needs and preferences.\\\\n1. **New to using Linux?** We recommend starting with Windows Subsystem for Linux (WSL) as it\\\\u0027s the easiest way to get up and running.\\\\n2. **Work in a business environment with more complex needs related to scale or security?** We recommend running Linux as a Virtual Machine (VM) in the cloud and checking out the support that Azure has to offer. This also applies if you want to run Linux as a server.\\\\n3. **Only want to run Linux as your primary operating system?** If you are good with a slightly more complicated install process and don\\\\u0027t need access to Windows tools (like Outlook, Teams, Word, PowerPoint, etc), you can run Linux on bare metal to access the full potential of your hardware without any overhead from virtualization or emulation.\\\\nLearn more about these options below.\\\\n### Windows Subsystem for Linux (WSL)\\\\nAvailable to those with a Windows machine, this is the most simple way to install Linux. Just run the Linux install command: \\\\u0060wsl --install\\\\u0060 to install the Ubuntu distribution.\\\\nThis method uses virtualization to integrate the Windows operating system (OS) with the Linux OS (running on an actual Linux kernel). You can add as many different Linux distributions as you please and run your favorite Linux tools, including GUI apps, alongside your favorite Windows tools. You can also mix and match Bash and PowerShell commands in the same command line. A Linux file system hosts your project files, while a separate Windows file systems hosts any developer tools, such as VS Code, that you\\\\u0027d like to work on the files with. This method of running Linux is highly efficient and performant. Once you choose a Linux distribution or opt to use the default Ubuntu distro, follow the instructions to Use the install Linux command with Windows Subsystem for Linux or learn more in the [WSL documentation](https://learn.microsoft.com/en-us/windows/wsl).\\\\n### Create a Virtual Machine (VM)\\\\nAvailable to everyone regardless if you\\\\u0027re using a Windows or MacOS-based machine, but will require either an Azure account, if creating a VM in the cloud, or virtualization software, if creating a VM directly on your machine (locally).\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/linux/install#step-1---choose-a-method-to-install-linux\\u0022},{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\nLinux is an operating system, similar to Windows, but with many different versions due to the nature of being open source and fully customizable. To install Linux, you must choose an install method and choose a Linux distribution.\\\\nTo install Linux:\\\\n1. Choose an install method: Windows Subsystem for Linux (WSL), Bare metal Linux; or create a Virtual Machine (VM) to run Linux locally or in the cloud.\\\\n2. Choose a Linux distribution: Ubuntu, Debian, Kali Linux, openSUSE, etc.\\\\n3. Follow the steps for your preferred install method:\\\\n3.1. Use the install Linux command with Windows Subsystem for Linux (WSL)\\\\n3.2. Create a Linux Virtual Machine (VM) in the cloud\\\\n3.3. Create a Linux Virtual Machine (VM) on your local machine\\\\n3.4. Create a bootable USB to install bare-metal Linux\\\\n4. After installing Linux: Get familiar with your distribution\\\\u0027s package manager, update and upgrade the packages available, and get familiar with the other [Linux resources at Microsoft](https://learn.microsoft.com/en-us/linux/), such as training courses, Linux-versions of popular tools, news, and Open Source events.\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/linux/install\\u0022},{\\u0022title\\u0022:\\u0022Guide for changing your dev environment from Mac to Windows\\u0022,\\u0022content\\u0022:\\u0022# Guide for changing your dev environment from Mac to Windows\\\\n## Command-line shells and terminals\\\\nYou can now install Windows Subsystem for Linux (WSL) to support running a Linux shell within Windows. This means that you can run **bash**, with whichever specific Linux distribution you choose, integrated right inside Windows. Using WSL provides the kind of environment most familiar to Mac users. For example, you use **ls** to list the files in a current directory, not **dir** as you would with the traditional Windows Cmd Shell. To learn about installing and using WSL, see the [Windows Subsystem for Linux Installation Guide](https://learn.microsoft.com/en-us/windows/wsl/install). Linux distributions that you can install on Windows with WSL include:\\\\n1. [Ubuntu 20.04 LTS](https://www.microsoft.com/store/apps/9n6svws3rx71)\\\\n2. [Kali Linux](https://www.microsoft.com/store/apps/9PKR34TNCV07)\\\\n3. [Debian GNU/Linux](https://www.microsoft.com/store/apps/9MSVKQC78PK6)\\\\n4. [openSUSE Leap 15.1](https://www.microsoft.com/store/apps/9NJFZK00FGKV)\\\\n5. [SUSE Linux Enterprise Server 15 SP1](https://www.microsoft.com/store/apps/9PN498VPMF3Z)\\\\nJust to name a few. Find more in the [WSL install docs](https://learn.microsoft.com/en-us/windows/wsl/install-win10#install-your-linux-distribution-of-choice) and install them directly from the [Microsoft Store](https://aka.ms/wslstore).\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/windows/dev-environment/mac-to-windows#command-line-shells-and-terminals\\u0022},{\\u0022title\\u0022:\\u0022Supported Linux operating system versions\\u0022,\\u0022content\\u0022:\\u0022# Supported Linux operating system versions\\\\n## Universal Linux (Debian package)\\\\nSupported versions:\\\\n1. Debian 8, 9, 10, 11, and 12\\\\n2. Ubuntu 16.04, 18.04, 20.04, 22.04 and 24.04\\\\nNote\\\\n1. Ubuntu 22.04 is supported from Operations Manager 2022 UR1 and later when you apply [this hotfix](https://support.microsoft.com/topic/system-center-operations-manager-2022-now-has-openssl3-0-integration-kb-5024286-331bd221-10f9-42d5-bc06-775eaabe3081).\\\\n2. Ubuntu 24.04 is supported from Operations Manager 2022 UR2 and later.\\\\n| Required package | Description | Minimum version| \\\\n| --- | --- | --- |\\\\n| libc6 | C Standard shared library | 2.24-11 |\\\\n| OpenSSL | OpenSSL Libraries; Secure Network Communications Protocol | 1.0, 1.1 or 3.0 and later |\\\\n| PAM | Pluggable Authentication Modules | 1.1.8-3.1 |\\\\n\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/system-center/scom/plan-supported-crossplat-linux-os?view=sc-om-2025#universal-linux-debian-package\\u0022},{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\n## Step 3 - Follow install method instructions\\\\n### Install Linux with Windows Subsystem for Linux\\\\nTo install Linux on a Windows PC, use the install Linux command.\\\\n1. Open PowerShell or Windows Command Prompt in *administrator mode* by right-clicking and selecting \\\\u0022Run as administrator\\\\u0022.\\\\n2. Enter the Linux install command: \\\\u0060wsl --install\\\\u0060.\\\\n3. Restart your machine.\\\\n#### Install Linux command options\\\\nBy default, the install command will use the Ubuntu distribution of Linux. You can see the other distributions that are available in the Microsoft Store by entering the command: \\\\u0060wsl -l -o\\\\u0060. If you\\\\u0027d like to install a distribution other than Ubuntu, just add \\\\u0060-d\\\\u0060 to the command with the name of the distribution you\\\\u0027d like to install:\\\\n\\\\u0060\\\\u0060\\\\u0060powershell\\\\nwsl --install -d \\\\u003CDistro Name\\\\u003E\\\\n\\\\u0060\\\\u0060\\\\u0060\\\\nIf the distribution you want to install isn\\\\u0027t available in the Microsoft Store, see the Windows Subsystem for Linux (WSL) documentation on how to [Import any Linux distribution to use with WSL](https://learn.microsoft.com/en-us/windows/wsl/use-custom-distro).\\\\nFor more information on how to use VS Code, Git, databases, Docker, GUI apps, GPU acceleration, NodesJS, USB devices, or mount a disk, build a custom distribution, manage disk space, or set up WSL for your enterprise business, see the [WSL documentation](https://learn.microsoft.com/en-us/windows/wsl/).\\\\n### Create a Linux Virtual Machine in the cloud\\\\nTo create a Linux Virtual Machine (VM) that is hosted in the cloud on Azure, log-in to the Azure Portal and select the \\\\u201CCreate a Virtual Machine\\\\u201D service. Then follow the instructions for creating a user name, project, instance, SSH key, ports, and copying the IP address.\\\\nAzure supports a variety of options for setting up Linux VMs in the cloud. For a quickstart guide on how to set up a Linux VM, choose your preferred method:\\\\n1. [Azure Portal](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-portal?tabs=ubuntu\\\\u0026source=docs)\\\\n2. [Azure CLI](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-cli)\\\\n3. [Azure PowerShell](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-powershell)\\\\n4. [Terraform](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-terraform)\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/linux/install#step-3---follow-install-method-instructions\\u0022},{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\n## Step 1 - Choose a method to install Linux\\\\nYou are responsible for managing the virtual machine when using a hypervisor, including allocating resources like memory and disk space, and ensuring that it is secure and up-to-date. This requires more technical expertise than some of the other options and may not be as scalable or fault-tolerant.\\\\nOnce you\\\\u0027ve chosen a distribution and decided whether you want to run the VM locally, using a hypervisor, or in the cloud, using Azure, follow the instructions to Create a Linux Virtual Machine in the Cloud or Create a Linux Virtual Machine locally using a Hypervisor\\\\n### Bare metal Linux\\\\nBare metal Linux just means that Linux is running directly on the device hardware. This install method requires you to create a bootable USB drive by downloading an iso install file from the site hosting your chosen Linux distribution. You will need to use a Windows computer (or any desktop device with an existing OS) to create this drive.\\\\nMany users choose the traditional method of installing bare metal Linux on a device that is also running Windows and using the **\\\\u0022dual boot\\\\u0022** method. To boot Linux and Windows, you need to partition your hard drive to create separate spaces for both the Linux and Windows OS. The performance speed between WSL, virtual machines, and bare metal Linux has become so close that few developers choose this method due to the overhead of needing to restart (reboot) your device any time you want to switch between the operating systems. If you choose the bare metal Linux install route, you may also need to deal with potential driver issues or hardware compatibility problems that may arise with Linux on some devices.\\\\nYou can find a list of [Popular Linux Distributions available for download on Linux.org](https://linux.org/pages/download/). Once you\\\\u0027ve chosen a distro, follow the instructions to Create a bootable USB to install bare-metal Linux.\\\\nWarning\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/linux/install#step-1---choose-a-method-to-install-linux\\u0022}]\"}]},\"id\":8,\"jsonrpc\":\"2.0\"}\n", + "data: {\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"[{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\n## Step 2 - Choose a Linux distribution\\\\nThe different versions of Linux are called \\\\u0022distributions\\\\u0022 (sometimes shortened to \\\\u201Cdistros\\\\u201D). So there isn\\\\u2019t actually a way to just \\\\u201Cinstall Linux\\\\u201D, you first need to choose which distribution that uses the Linux kernel you want to download and install. There are over 600 active Linux distributions, such as Ubuntu, Debian, Kali Linux, openSUSE, and more. There are a number of factors to consider in choosing a distribution, such as:\\\\n### Required Linux experience\\\\nIf you\\\\u2019re new to Linux, you may want to start with Ubuntu. This is one of the most common starting places for Linux users due to the level of support and ease of use. Ubuntu offers a desktop-version and a server-version depending on how you plan to use it. The specific tasks that the operating system will be responsible for may help you determine which distribution to use. Arch Linux is a popular choice for those who want a highly customizable, do-it-yourself approach that is still stable and well-supported by a large user base. It is a much more complicated place to start, but can help you to get a better understanding of how Linux works due to the amount of custom configuration.\\\\n### System requirements\\\\nA minimal, or \\\\u0022lightweight\\\\u0022, distribution means a small footprint, with lower memory and processing speed requirements. Alpine Linux is a popular option where users will find most features disabled or not installed by default, but still has everything you need for a containerized app. Security needs: Some Linux users may have specific security concerns to consider. Kali Linux is an example of a distro designed for security tasks, such as Penetration Testing, Security Research, Computer Forensics and Reverse Engineering.\\\\n### Business and Enterprise requirements\\\\nBusiness, corporate, or academic environments may have unique concerns from consumer environments. For example Red Hat Enterprise Linux (RHEL), SUSE Linux Enterprise Server (SLES) and Oracle Linux offer 24/7 global support and subscription-based service. CentOS is another popular enterprise distro that is community-supported but still compatible with RHEL.\\\\n### Community support\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/linux/install#step-2---choose-a-linux-distribution\\u0022},{\\u0022title\\u0022:\\u0022New to Linux resources for SQL users\\u0022,\\u0022content\\u0022:\\u0022# New to Linux resources for SQL users\\\\n**Applies to:**[SQL Server](https://learn.microsoft.com/en-us/sql/sql-server/sql-docs-navigation-guide?view=sql-server-ver17#applies-to) - Linux\\\\nThis article is intended to provide a learning path through an introduction of Linux concepts. Use the sections in the article as a guided learning path to familiarize yourself with the Linux environment.\\\\nThis isn\\\\u0027t intended to be a comprehensive list but instead provides the bare minimum you need to help you administer and navigate your SQL Server on Linux environment. See the [full list of tutorials](https://www.linux.org/forums/linux-beginner-tutorials.123/) to learn more.\\\\n## What is Linux?\\\\nThe [What is Linux](https://www.linux.org/threads/what-is-linux.4106/) module introduces the history of Linux as an operating system. The module explains *the Kernel* and where Linux is today. This tutorial serves as an introduction to Linux, helping you get started.\\\\n## Select a distribution\\\\nAfter you know the history of Linux, decide which [distribution of Linux](https://www.linux.org/threads/selecting-a-linux-distribution.4117/) best suits your business needs. [SQL Server is supported](https://learn.microsoft.com/en-us/sql/linux/sql-server-linux-release-notes-2019?view=sql-server-ver17#supported-platforms) on various distributions such as Red Hat Enterprise Linux (RHEL), SUSE Linux Enterprise Server (SLES), and Ubuntu.\\\\n## Get around directories\\\\nAfter you have chosen the appropriate distribution of Linux, familiarize yourself with [getting around Linux directories](https://www.linux.org/threads/getting-around-in-linux-directories.4120/).\\\\nThis module will help you answer the following questions:\\\\n1. How to navigate between different files\\\\n2. How to identify a file from a directory\\\\n3. Identify different directories\\\\n## Install new software\\\\nNext, learn how to [install new software](https://www.linux.org/threads/installing-new-software-debian-red-hat-slackware.4119/) on your new Linux operating system. This module details installing new software to the Debian and Red Hat Linux operating systems.\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/sql/linux/new-to-linux-resources?view=sql-server-ver17\\u0022},{\\u0022title\\u0022:\\u0022Part 1.2 - Basic operations on Linux\\u0022,\\u0022content\\u0022:\\u0022# Part 1.2 - Basic operations on Linux\\\\n## Run commands by using the \\\\u0022sudo\\\\u0022 prefix\\\\nInstead of becoming the root account user in your session, you can run commands as root by using the \\\\u0060sudo \\\\u003Ccommand\\\\u003E\\\\u0060 format. This approach is safer because it will run the given command only as a superuser.\\\\nFor example, if you run \\\\u0060restart\\\\u0060 while you\\\\u0027re not elevated to superuser status, the result is as follows.\\\\n![Screenshot of restart command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/restart-command.png)\\\\nTo run this command as a superuser instead, you can either change the session context to root (by running \\\\u0060sudo su\\\\u0060) or add the \\\\u0060sudo\\\\u0060 prefix, as follows:\\\\n\\\\u0060\\\\u0060\\\\u0060bash\\\\nsudo reboot\\\\n\\\\u0060\\\\u0060\\\\u0060\\\\nNow, the restart operation runs as expected by the virtual machine.\\\\n![Screenshot of reboot command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/reboot-command.png)\\\\n## Package managers\\\\nPackage managers are used to install, upgrade, and remove applications in Linux. There are several different package managers, such as DPKG (Debian Package Management System), RPM (Red Hat Package Manager), and Zypper for SUSE Package managers themselves are low-level. Therefore, it might be difficult to manage software directly by using the package manager. Fortunately, there are wrappers for these tools that make it easier to install and uninstall software. For example, [yum](https://wikipedia.org/wiki/Yum_%28software%29) is a wrapper around RPM (Red Hat Package Manager) and [APT](https://wikipedia.org/wiki/APT_%28software%29) is a wrapper around DPKG (Debian Package Management System).\\\\nThis tutorial assumes that you\\\\u0027re running the [Ubuntu package management system](https://ubuntu.com/server/docs/package-management). This system is derived from the same system that\\\\u0027s used by the Debian GNU/Linux distribution. Therefore, we can use APT to install the software.\\\\nDepending on your Linux choice, you might have to use other tools. For example, you can use yum to install or uninstall software if you\\\\u0027re running Red Hat.\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-by-using-the-sudo-prefix\\u0022},{\\u0022title\\u0022:\\u0022Part 1.2 - Basic operations on Linux\\u0022,\\u0022content\\u0022:\\u0022# Part 1.2 - Basic operations on Linux\\\\n## Run commands as a superuser\\\\nThe superuser is the most privileged user account. It has root (unrestricted) access to all files and folders, and complete control over the operation of the computer. The system administrator uses this account for system maintenance.\\\\nOn UNIX-like systems, the conventional name of the superuser is \\\\u0022root\\\\u0022. The root user can do tasks that are otherwise restricted for standard users, including the following ones:\\\\n1. Changing system directories\\\\n2. Changing user privileges\\\\n3. Changing file ownership\\\\n4. System-related tasks such as restarts\\\\n5. Installation of certain applications\\\\nThere are several ways to run commands as the root account user. One is to use the \\\\u0060sudo su\\\\u0060 command. In this command, \\\\u0060sudo\\\\u0060 is short for \\\\u0060superuser do\\\\u0060, and \\\\u0060su\\\\u0060 is an abbreviation for \\\\u0022switch user.\\\\u0022 You can also use the \\\\u0060su\\\\u0060 command to switch between standard users.\\\\nImportant\\\\nAfter you become the root account user, everything you do runs in root context. Therefore, you should act carefully because the root account has access to the entire system.\\\\nTo demonstrate a permissions issue, create a text file in a special folder that\\\\u0027s named */etc*.\\\\nThe */etc/* folder is where system configuration files are located. This folder protects its contents from being changed by a standard user.\\\\nRun the following command:\\\\n\\\\u0060\\\\u0060\\\\u0060bash\\\\necho hello world \\\\u003E /etc/helloworld.txt\\\\n\\\\u0060\\\\u0060\\\\u0060\\\\nThe \\\\u0060echo\\\\u0060 command writes all the text that follows it to the output. The angle bracket (\\\\u0060\\\\u003E\\\\u0060) tells the system to send the output to the */etc/helloworld.txt* file instead of the console. This behavior is similar to how Windows works.\\\\nNote\\\\nLinux is case-sensitive: *Helloworld.txt*, *helloworld.txt* and *helloworld.Txt* are different files.\\\\nYou don\\\\u0027t have to provide a file name extension. The name \\\\u0022helloworld\\\\u0022 by itself is perfectly valid. (The \\\\u0022txt\\\\u0022 extension is used here only as an example.)\\\\nWhen you run this command, you receive a **permission denied** error message.\\\\n![Screenshot of permission denied message.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/permission-denied-message.png)\\\\nThe operation fails because the */etc* folder is special folder that a standard user can\\\\u0027t change. Verify the user by running the \\\\u0060id\\\\u0060 command.\\\\n![Screenshot of I D command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/id-command.png)\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-as-a-superuser\\u0022},{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\n## Step 1 - Choose a method to install Linux\\\\nThe choice of which method to use for installing Linux depends on your needs and preferences.\\\\n1. **New to using Linux?** We recommend starting with Windows Subsystem for Linux (WSL) as it\\\\u0027s the easiest way to get up and running.\\\\n2. **Work in a business environment with more complex needs related to scale or security?** We recommend running Linux as a Virtual Machine (VM) in the cloud and checking out the support that Azure has to offer. This also applies if you want to run Linux as a server.\\\\n3. **Only want to run Linux as your primary operating system?** If you are good with a slightly more complicated install process and don\\\\u0027t need access to Windows tools (like Outlook, Teams, Word, PowerPoint, etc), you can run Linux on bare metal to access the full potential of your hardware without any overhead from virtualization or emulation.\\\\nLearn more about these options below.\\\\n### Windows Subsystem for Linux (WSL)\\\\nAvailable to those with a Windows machine, this is the most simple way to install Linux. Just run the Linux install command: \\\\u0060wsl --install\\\\u0060 to install the Ubuntu distribution.\\\\nThis method uses virtualization to integrate the Windows operating system (OS) with the Linux OS (running on an actual Linux kernel). You can add as many different Linux distributions as you please and run your favorite Linux tools, including GUI apps, alongside your favorite Windows tools. You can also mix and match Bash and PowerShell commands in the same command line. A Linux file system hosts your project files, while a separate Windows file systems hosts any developer tools, such as VS Code, that you\\\\u0027d like to work on the files with. This method of running Linux is highly efficient and performant. Once you choose a Linux distribution or opt to use the default Ubuntu distro, follow the instructions to Use the install Linux command with Windows Subsystem for Linux or learn more in the [WSL documentation](https://learn.microsoft.com/en-us/windows/wsl).\\\\n### Create a Virtual Machine (VM)\\\\nAvailable to everyone regardless if you\\\\u0027re using a Windows or MacOS-based machine, but will require either an Azure account, if creating a VM in the cloud, or virtualization software, if creating a VM directly on your machine (locally).\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/linux/install#step-1---choose-a-method-to-install-linux\\u0022},{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\nLinux is an operating system, similar to Windows, but with many different versions due to the nature of being open source and fully customizable. To install Linux, you must choose an install method and choose a Linux distribution.\\\\nTo install Linux:\\\\n1. Choose an install method: Windows Subsystem for Linux (WSL), Bare metal Linux; or create a Virtual Machine (VM) to run Linux locally or in the cloud.\\\\n2. Choose a Linux distribution: Ubuntu, Debian, Kali Linux, openSUSE, etc.\\\\n3. Follow the steps for your preferred install method:\\\\n3.1. Use the install Linux command with Windows Subsystem for Linux (WSL)\\\\n3.2. Create a Linux Virtual Machine (VM) in the cloud\\\\n3.3. Create a Linux Virtual Machine (VM) on your local machine\\\\n3.4. Create a bootable USB to install bare-metal Linux\\\\n4. After installing Linux: Get familiar with your distribution\\\\u0027s package manager, update and upgrade the packages available, and get familiar with the other [Linux resources at Microsoft](https://learn.microsoft.com/en-us/linux/), such as training courses, Linux-versions of popular tools, news, and Open Source events.\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/linux/install\\u0022},{\\u0022title\\u0022:\\u0022Guide for changing your dev environment from Mac to Windows\\u0022,\\u0022content\\u0022:\\u0022# Guide for changing your dev environment from Mac to Windows\\\\n## Command-line shells and terminals\\\\nYou can now install Windows Subsystem for Linux (WSL) to support running a Linux shell within Windows. This means that you can run **bash**, with whichever specific Linux distribution you choose, integrated right inside Windows. Using WSL provides the kind of environment most familiar to Mac users. For example, you use **ls** to list the files in a current directory, not **dir** as you would with the traditional Windows Cmd Shell. To learn about installing and using WSL, see the [Windows Subsystem for Linux Installation Guide](https://learn.microsoft.com/en-us/windows/wsl/install). Linux distributions that you can install on Windows with WSL include:\\\\n1. [Ubuntu 20.04 LTS](https://www.microsoft.com/store/apps/9n6svws3rx71)\\\\n2. [Kali Linux](https://www.microsoft.com/store/apps/9PKR34TNCV07)\\\\n3. [Debian GNU/Linux](https://www.microsoft.com/store/apps/9MSVKQC78PK6)\\\\n4. [openSUSE Leap 15.1](https://www.microsoft.com/store/apps/9NJFZK00FGKV)\\\\n5. [SUSE Linux Enterprise Server 15 SP1](https://www.microsoft.com/store/apps/9PN498VPMF3Z)\\\\nJust to name a few. Find more in the [WSL install docs](https://learn.microsoft.com/en-us/windows/wsl/install-win10#install-your-linux-distribution-of-choice) and install them directly from the [Microsoft Store](https://aka.ms/wslstore).\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/windows/dev-environment/mac-to-windows#command-line-shells-and-terminals\\u0022},{\\u0022title\\u0022:\\u0022Supported Linux operating system versions\\u0022,\\u0022content\\u0022:\\u0022# Supported Linux operating system versions\\\\n## Universal Linux (Debian package)\\\\nSupported versions:\\\\n1. Debian 8, 9, 10, 11, and 12\\\\n2. Ubuntu 16.04, 18.04, 20.04, 22.04 and 24.04\\\\nNote\\\\n1. Ubuntu 22.04 is supported from Operations Manager 2022 UR1 and later when you apply [this hotfix](https://support.microsoft.com/topic/system-center-operations-manager-2022-now-has-openssl3-0-integration-kb-5024286-331bd221-10f9-42d5-bc06-775eaabe3081).\\\\n2. Ubuntu 24.04 is supported from Operations Manager 2022 UR2 and later.\\\\n| Required package | Description | Minimum version| \\\\n| --- | --- | --- |\\\\n| libc6 | C Standard shared library | 2.24-11 |\\\\n| OpenSSL | OpenSSL Libraries; Secure Network Communications Protocol | 1.0, 1.1 or 3.0 and later |\\\\n| PAM | Pluggable Authentication Modules | 1.1.8-3.1 |\\\\n\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/system-center/scom/plan-supported-crossplat-linux-os?view=sc-om-2025#universal-linux-debian-package\\u0022},{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\n## Step 3 - Follow install method instructions\\\\n### Install Linux with Windows Subsystem for Linux\\\\nTo install Linux on a Windows PC, use the install Linux command.\\\\n1. Open PowerShell or Windows Command Prompt in *administrator mode* by right-clicking and selecting \\\\u0022Run as administrator\\\\u0022.\\\\n2. Enter the Linux install command: \\\\u0060wsl --install\\\\u0060.\\\\n3. Restart your machine.\\\\n#### Install Linux command options\\\\nBy default, the install command will use the Ubuntu distribution of Linux. You can see the other distributions that are available in the Microsoft Store by entering the command: \\\\u0060wsl -l -o\\\\u0060. If you\\\\u0027d like to install a distribution other than Ubuntu, just add \\\\u0060-d\\\\u0060 to the command with the name of the distribution you\\\\u0027d like to install:\\\\n\\\\u0060\\\\u0060\\\\u0060powershell\\\\nwsl --install -d \\\\u003CDistro Name\\\\u003E\\\\n\\\\u0060\\\\u0060\\\\u0060\\\\nIf the distribution you want to install isn\\\\u0027t available in the Microsoft Store, see the Windows Subsystem for Linux (WSL) documentation on how to [Import any Linux distribution to use with WSL](https://learn.microsoft.com/en-us/windows/wsl/use-custom-distro).\\\\nFor more information on how to use VS Code, Git, databases, Docker, GUI apps, GPU acceleration, NodesJS, USB devices, or mount a disk, build a custom distribution, manage disk space, or set up WSL for your enterprise business, see the [WSL documentation](https://learn.microsoft.com/en-us/windows/wsl/).\\\\n### Create a Linux Virtual Machine in the cloud\\\\nTo create a Linux Virtual Machine (VM) that is hosted in the cloud on Azure, log-in to the Azure Portal and select the \\\\u201CCreate a Virtual Machine\\\\u201D service. Then follow the instructions for creating a user name, project, instance, SSH key, ports, and copying the IP address.\\\\nAzure supports a variety of options for setting up Linux VMs in the cloud. For a quickstart guide on how to set up a Linux VM, choose your preferred method:\\\\n1. [Azure Portal](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-portal?tabs=ubuntu\\\\u0026source=docs)\\\\n2. [Azure CLI](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-cli)\\\\n3. [Azure PowerShell](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-powershell)\\\\n4. [Terraform](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-terraform)\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/linux/install#step-3---follow-install-method-instructions\\u0022},{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\n## Step 1 - Choose a method to install Linux\\\\nYou are responsible for managing the virtual machine when using a hypervisor, including allocating resources like memory and disk space, and ensuring that it is secure and up-to-date. This requires more technical expertise than some of the other options and may not be as scalable or fault-tolerant.\\\\nOnce you\\\\u0027ve chosen a distribution and decided whether you want to run the VM locally, using a hypervisor, or in the cloud, using Azure, follow the instructions to Create a Linux Virtual Machine in the Cloud or Create a Linux Virtual Machine locally using a Hypervisor\\\\n### Bare metal Linux\\\\nBare metal Linux just means that Linux is running directly on the device hardware. This install method requires you to create a bootable USB drive by downloading an iso install file from the site hosting your chosen Linux distribution. You will need to use a Windows computer (or any desktop device with an existing OS) to create this drive.\\\\nMany users choose the traditional method of installing bare metal Linux on a device that is also running Windows and using the **\\\\u0022dual boot\\\\u0022** method. To boot Linux and Windows, you need to partition your hard drive to create separate spaces for both the Linux and Windows OS. The performance speed between WSL, virtual machines, and bare metal Linux has become so close that few developers choose this method due to the overhead of needing to restart (reboot) your device any time you want to switch between the operating systems. If you choose the bare metal Linux install route, you may also need to deal with potential driver issues or hardware compatibility problems that may arise with Linux on some devices.\\\\nYou can find a list of [Popular Linux Distributions available for download on Linux.org](https://linux.org/pages/download/). Once you\\\\u0027ve chosen a distro, follow the instructions to Create a bootable USB to install bare-metal Linux.\\\\nWarning\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/linux/install#step-1---choose-a-method-to-install-linux\\u0022}]\"}]},\"id\":\"ffda9f6266ce4bfa8f086d23d03c025a\",\"jsonrpc\":\"2.0\"}\n", "\n", "\n", "BODY END" @@ -650,11 +650,11 @@ "name": "stdout", "output_type": "stream", "text": [ - "TextContent(text=[{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\n## Step 2 - Choose a Linux distribution\\nThe different versions of Linux are called \\u0022distributions\\u0022 (sometimes shortened to \\u201Cdistros\\u201D). So there isn\\u2019t actually a way to just \\u201Cinstall Linux\\u201D, you first need to choose which distribution that uses the Linux kernel you want to download and install. There are over 600 active Linux distributions, such as Ubuntu, Debian, Kali Linux, openSUSE, and more. There are a number of factors to consider in choosing a distribution, such as:\\n### Required Linux experience\\nIf you\\u2019re new to Linux, you may want to start with Ubuntu. This is one of the most common starting places for Linux users due to the level of support and ease of use. Ubuntu offers a desktop-version and a server-version depending on how you plan to use it. The specific tasks that the operating system will be responsible for may help you determine which distribution to use. Arch Linux is a popular choice for those who want a highly customizable, do-it-yourself approach that is still stable and well-supported by a large user base. It is a much more complicated place to start, but can help you to get a better understanding of how Linux works due to the amount of custom configuration.\\n### System requirements\\nA minimal, or \\u0022lightweight\\u0022, distribution means a small footprint, with lower memory and processing speed requirements. Alpine Linux is a popular option where users will find most features disabled or not installed by default, but still has everything you need for a containerized app. Security needs: Some Linux users may have specific security concerns to consider. Kali Linux is an example of a distro designed for security tasks, such as Penetration Testing, Security Research, Computer Forensics and Reverse Engineering.\\n### Business and Enterprise requirements\\nBusiness, corporate, or academic environments may have unique concerns from consumer environments. For example Red Hat Enterprise Linux (RHEL), SUSE Linux Enterprise Server (SLES) and Oracle Linux offer 24/7 global support and subscription-based service. CentOS is another popular enterprise distro that is community-supported but still compatible with RHEL.\\n### Community support\",\"contentUrl\":\"https://learn.microsoft.com/en-us/linux/install#step-2---choose-a-linux-distribution\"},{\"title\":\"New to Linux resources for SQL users\",\"content\":\"# New to Linux resources for SQL users\\n**Applies to:**[SQL Server](https://learn.microsoft.com/en-us/sql/sql-server/sql-docs-navigation-guide?view=sql-server-ver17#applies-to) - Linux\\nThis article is intended to provide a learning path through an introduction of Linux concepts. Use the sections in the article as a guided learning path to familiarize yourself with the Linux environment.\\nThis isn\\u0027t intended to be a comprehensive list but instead provides the bare minimum you need to help you administer and navigate your SQL Server on Linux environment. See the [full list of tutorials](https://www.linux.org/forums/linux-beginner-tutorials.123/) to learn more.\\n## What is Linux?\\nThe [What is Linux](https://www.linux.org/threads/what-is-linux.4106/) module introduces the history of Linux as an operating system. The module explains *the Kernel* and where Linux is today. This tutorial serves as an introduction to Linux, helping you get started.\\n## Select a distribution\\nAfter you know the history of Linux, decide which [distribution of Linux](https://www.linux.org/threads/selecting-a-linux-distribution.4117/) best suits your business needs. [SQL Server is supported](https://learn.microsoft.com/en-us/sql/linux/sql-server-linux-release-notes-2019?view=sql-server-ver17#supported-platforms) on various distributions such as Red Hat Enterprise Linux (RHEL), SUSE Linux Enterprise Server (SLES), and Ubuntu.\\n## Get around directories\\nAfter you have chosen the appropriate distribution of Linux, familiarize yourself with [getting around Linux directories](https://www.linux.org/threads/getting-around-in-linux-directories.4120/).\\nThis module will help you answer the following questions:\\n1. How to navigate between different files\\n2. How to identify a file from a directory\\n3. Identify different directories\\n## Install new software\\nNext, learn how to [install new software](https://www.linux.org/threads/installing-new-software-debian-red-hat-slackware.4119/) on your new Linux operating system. This module details installing new software to the Debian and Red Hat Linux operating systems.\",\"contentUrl\":\"https://learn.microsoft.com/en-us/sql/linux/new-to-linux-resources?view=sql-server-ver17\"},{\"title\":\"Part 1.2 - Basic operations on Linux\",\"content\":\"# Part 1.2 - Basic operations on Linux\\n## Run commands by using the \\u0022sudo\\u0022 prefix\\nInstead of becoming the root account user in your session, you can run commands as root by using the \\u0060sudo \\u003Ccommand\\u003E\\u0060 format. This approach is safer because it will run the given command only as a superuser.\\nFor example, if you run \\u0060restart\\u0060 while you\\u0027re not elevated to superuser status, the result is as follows.\\n![Screenshot of restart command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/restart-command.png)\\nTo run this command as a superuser instead, you can either change the session context to root (by running \\u0060sudo su\\u0060) or add the \\u0060sudo\\u0060 prefix, as follows:\\n\\u0060\\u0060\\u0060bash\\nsudo reboot\\n\\u0060\\u0060\\u0060\\nNow, the restart operation runs as expected by the virtual machine.\\n![Screenshot of reboot command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/reboot-command.png)\\n## Package managers\\nPackage managers are used to install, upgrade, and remove applications in Linux. There are several different package managers, such as DPKG (Debian Package Management System), RPM (Red Hat Package Manager), and Zypper for SUSE Package managers themselves are low-level. Therefore, it might be difficult to manage software directly by using the package manager. Fortunately, there are wrappers for these tools that make it easier to install and uninstall software. For example, [yum](https://wikipedia.org/wiki/Yum_%28software%29) is a wrapper around RPM (Red Hat Package Manager) and [APT](https://wikipedia.org/wiki/APT_%28software%29) is a wrapper around DPKG (Debian Package Management System).\\nThis tutorial assumes that you\\u0027re running the [Ubuntu package management system](https://ubuntu.com/server/docs/package-management). This system is derived from the same system that\\u0027s used by the Debian GNU/Linux distribution. Therefore, we can use APT to install the software.\\nDepending on your Linux choice, you might have to use other tools. For example, you can use yum to install or uninstall software if you\\u0027re running Red Hat.\",\"contentUrl\":\"https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-by-using-the-sudo-prefix\"},{\"title\":\"Part 1.2 - Basic operations on Linux\",\"content\":\"# Part 1.2 - Basic operations on Linux\\n## Run commands as a superuser\\nThe superuser is the most privileged user account. It has root (unrestricted) access to all files and folders, and complete control over the operation of the computer. The system administrator uses this account for system maintenance.\\nOn UNIX-like systems, the conventional name of the superuser is \\u0022root\\u0022. The root user can do tasks that are otherwise restricted for standard users, including the following ones:\\n1. Changing system directories\\n2. Changing user privileges\\n3. Changing file ownership\\n4. System-related tasks such as restarts\\n5. Installation of certain applications\\nThere are several ways to run commands as the root account user. One is to use the \\u0060sudo su\\u0060 command. In this command, \\u0060sudo\\u0060 is short for \\u0060superuser do\\u0060, and \\u0060su\\u0060 is an abbreviation for \\u0022switch user.\\u0022 You can also use the \\u0060su\\u0060 command to switch between standard users.\\nImportant\\nAfter you become the root account user, everything you do runs in root context. Therefore, you should act carefully because the root account has access to the entire system.\\nTo demonstrate a permissions issue, create a text file in a special folder that\\u0027s named */etc*.\\nThe */etc/* folder is where system configuration files are located. This folder protects its contents from being changed by a standard user.\\nRun the following command:\\n\\u0060\\u0060\\u0060bash\\necho hello world \\u003E /etc/helloworld.txt\\n\\u0060\\u0060\\u0060\\nThe \\u0060echo\\u0060 command writes all the text that follows it to the output. The angle bracket (\\u0060\\u003E\\u0060) tells the system to send the output to the */etc/helloworld.txt* file instead of the console. This behavior is similar to how Windows works.\\nNote\\nLinux is case-sensitive: *Helloworld.txt*, *helloworld.txt* and *helloworld.Txt* are different files.\\nYou don\\u0027t have to provide a file name extension. The name \\u0022helloworld\\u0022 by itself is perfectly valid. (The \\u0022txt\\u0022 extension is used here only as an example.)\\nWhen you run this command, you receive a **permission denied** error message.\\n![Screenshot of permission denied message.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/permission-denied-message.png)\\nThe operation fails because the */etc* folder is special folder that a standard user can\\u0027t change. Verify the user by running the \\u0060id\\u0060 command.\\n![Screenshot of I D command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/id-command.png)\",\"contentUrl\":\"https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-as-a-superuser\"},{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\n## Step 1 - Choose a method to install Linux\\nThe choice of which method to use for installing Linux depends on your needs and preferences.\\n1. **New to using Linux?** We recommend starting with Windows Subsystem for Linux (WSL) as it\\u0027s the easiest way to get up and running.\\n2. **Work in a business environment with more complex needs related to scale or security?** We recommend running Linux as a Virtual Machine (VM) in the cloud and checking out the support that Azure has to offer. This also applies if you want to run Linux as a server.\\n3. **Only want to run Linux as your primary operating system?** If you are good with a slightly more complicated install process and don\\u0027t need access to Windows tools (like Outlook, Teams, Word, PowerPoint, etc), you can run Linux on bare metal to access the full potential of your hardware without any overhead from virtualization or emulation.\\nLearn more about these options below.\\n### Windows Subsystem for Linux (WSL)\\nAvailable to those with a Windows machine, this is the most simple way to install Linux. Just run the Linux install command: \\u0060wsl --install\\u0060 to install the Ubuntu distribution.\\nThis method uses virtualization to integrate the Windows operating system (OS) with the Linux OS (running on an actual Linux kernel). You can add as many different Linux distributions as you please and run your favorite Linux tools, including GUI apps, alongside your favorite Windows tools. You can also mix and match Bash and PowerShell commands in the same command line. A Linux file system hosts your project files, while a separate Windows file systems hosts any developer tools, such as VS Code, that you\\u0027d like to work on the files with. This method of running Linux is highly efficient and performant. Once you choose a Linux distribution or opt to use the default Ubuntu distro, follow the instructions to Use the install Linux command with Windows Subsystem for Linux or learn more in the [WSL documentation](https://learn.microsoft.com/en-us/windows/wsl).\\n### Create a Virtual Machine (VM)\\nAvailable to everyone regardless if you\\u0027re using a Windows or MacOS-based machine, but will require either an Azure account, if creating a VM in the cloud, or virtualization software, if creating a VM directly on your machine (locally).\",\"contentUrl\":\"https://learn.microsoft.com/en-us/linux/install#step-1---choose-a-method-to-install-linux\"},{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\nLinux is an operating system, similar to Windows, but with many different versions due to the nature of being open source and fully customizable. To install Linux, you must choose an install method and choose a Linux distribution.\\nTo install Linux:\\n1. Choose an install method: Windows Subsystem for Linux (WSL), Bare metal Linux; or create a Virtual Machine (VM) to run Linux locally or in the cloud.\\n2. Choose a Linux distribution: Ubuntu, Debian, Kali Linux, openSUSE, etc.\\n3. Follow the steps for your preferred install method:\\n3.1. Use the install Linux command with Windows Subsystem for Linux (WSL)\\n3.2. Create a Linux Virtual Machine (VM) in the cloud\\n3.3. Create a Linux Virtual Machine (VM) on your local machine\\n3.4. Create a bootable USB to install bare-metal Linux\\n4. After installing Linux: Get familiar with your distribution\\u0027s package manager, update and upgrade the packages available, and get familiar with the other [Linux resources at Microsoft](https://learn.microsoft.com/en-us/linux/), such as training courses, Linux-versions of popular tools, news, and Open Source events.\",\"contentUrl\":\"https://learn.microsoft.com/en-us/linux/install\"},{\"title\":\"Guide for changing your dev environment from Mac to Windows\",\"content\":\"# Guide for changing your dev environment from Mac to Windows\\n## Command-line shells and terminals\\nYou can now install Windows Subsystem for Linux (WSL) to support running a Linux shell within Windows. This means that you can run **bash**, with whichever specific Linux distribution you choose, integrated right inside Windows. Using WSL provides the kind of environment most familiar to Mac users. For example, you use **ls** to list the files in a current directory, not **dir** as you would with the traditional Windows Cmd Shell. To learn about installing and using WSL, see the [Windows Subsystem for Linux Installation Guide](https://learn.microsoft.com/en-us/windows/wsl/install). Linux distributions that you can install on Windows with WSL include:\\n1. [Ubuntu 20.04 LTS](https://www.microsoft.com/store/apps/9n6svws3rx71)\\n2. [Kali Linux](https://www.microsoft.com/store/apps/9PKR34TNCV07)\\n3. [Debian GNU/Linux](https://www.microsoft.com/store/apps/9MSVKQC78PK6)\\n4. [openSUSE Leap 15.1](https://www.microsoft.com/store/apps/9NJFZK00FGKV)\\n5. [SUSE Linux Enterprise Server 15 SP1](https://www.microsoft.com/store/apps/9PN498VPMF3Z)\\nJust to name a few. Find more in the [WSL install docs](https://learn.microsoft.com/en-us/windows/wsl/install-win10#install-your-linux-distribution-of-choice) and install them directly from the [Microsoft Store](https://aka.ms/wslstore).\",\"contentUrl\":\"https://learn.microsoft.com/en-us/windows/dev-environment/mac-to-windows#command-line-shells-and-terminals\"},{\"title\":\"Supported Linux operating system versions\",\"content\":\"# Supported Linux operating system versions\\n## Universal Linux (Debian package)\\nSupported versions:\\n1. Debian 8, 9, 10, 11, and 12\\n2. Ubuntu 16.04, 18.04, 20.04, 22.04 and 24.04\\nNote\\n1. Ubuntu 22.04 is supported from Operations Manager 2022 UR1 and later when you apply [this hotfix](https://support.microsoft.com/topic/system-center-operations-manager-2022-now-has-openssl3-0-integration-kb-5024286-331bd221-10f9-42d5-bc06-775eaabe3081).\\n2. Ubuntu 24.04 is supported from Operations Manager 2022 UR2 and later.\\n| Required package | Description | Minimum version| \\n| --- | --- | --- |\\n| libc6 | C Standard shared library | 2.24-11 |\\n| OpenSSL | OpenSSL Libraries; Secure Network Communications Protocol | 1.0, 1.1 or 3.0 and later |\\n| PAM | Pluggable Authentication Modules | 1.1.8-3.1 |\\n\",\"contentUrl\":\"https://learn.microsoft.com/en-us/system-center/scom/plan-supported-crossplat-linux-os?view=sc-om-2025#universal-linux-debian-package\"},{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\n## Step 3 - Follow install method instructions\\n### Install Linux with Windows Subsystem for Linux\\nTo install Linux on a Windows PC, use the install Linux command.\\n1. Open PowerShell or Windows Command Prompt in *administrator mode* by right-clicking and selecting \\u0022Run as administrator\\u0022.\\n2. Enter the Linux install command: \\u0060wsl --install\\u0060.\\n3. Restart your machine.\\n#### Install Linux command options\\nBy default, the install command will use the Ubuntu distribution of Linux. You can see the other distributions that are available in the Microsoft Store by entering the command: \\u0060wsl -l -o\\u0060. If you\\u0027d like to install a distribution other than Ubuntu, just add \\u0060-d\\u0060 to the command with the name of the distribution you\\u0027d like to install:\\n\\u0060\\u0060\\u0060powershell\\nwsl --install -d \\u003CDistro Name\\u003E\\n\\u0060\\u0060\\u0060\\nIf the distribution you want to install isn\\u0027t available in the Microsoft Store, see the Windows Subsystem for Linux (WSL) documentation on how to [Import any Linux distribution to use with WSL](https://learn.microsoft.com/en-us/windows/wsl/use-custom-distro).\\nFor more information on how to use VS Code, Git, databases, Docker, GUI apps, GPU acceleration, NodesJS, USB devices, or mount a disk, build a custom distribution, manage disk space, or set up WSL for your enterprise business, see the [WSL documentation](https://learn.microsoft.com/en-us/windows/wsl/).\\n### Create a Linux Virtual Machine in the cloud\\nTo create a Linux Virtual Machine (VM) that is hosted in the cloud on Azure, log-in to the Azure Portal and select the \\u201CCreate a Virtual Machine\\u201D service. Then follow the instructions for creating a user name, project, instance, SSH key, ports, and copying the IP address.\\nAzure supports a variety of options for setting up Linux VMs in the cloud. For a quickstart guide on how to set up a Linux VM, choose your preferred method:\\n1. [Azure Portal](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-portal?tabs=ubuntu\\u0026source=docs)\\n2. [Azure CLI](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-cli)\\n3. [Azure PowerShell](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-powershell)\\n4. [Terraform](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-terraform)\",\"contentUrl\":\"https://learn.microsoft.com/en-us/linux/install#step-3---follow-install-method-instructions\"},{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\n## Step 1 - Choose a method to install Linux\\nYou are responsible for managing the virtual machine when using a hypervisor, including allocating resources like memory and disk space, and ensuring that it is secure and up-to-date. This requires more technical expertise than some of the other options and may not be as scalable or fault-tolerant.\\nOnce you\\u0027ve chosen a distribution and decided whether you want to run the VM locally, using a hypervisor, or in the cloud, using Azure, follow the instructions to Create a Linux Virtual Machine in the Cloud or Create a Linux Virtual Machine locally using a Hypervisor\\n### Bare metal Linux\\nBare metal Linux just means that Linux is running directly on the device hardware. This install method requires you to create a bootable USB drive by downloading an iso install file from the site hosting your chosen Linux distribution. You will need to use a Windows computer (or any desktop device with an existing OS) to create this drive.\\nMany users choose the traditional method of installing bare metal Linux on a device that is also running Windows and using the **\\u0022dual boot\\u0022** method. To boot Linux and Windows, you need to partition your hard drive to create separate spaces for both the Linux and Windows OS. The performance speed between WSL, virtual machines, and bare metal Linux has become so close that few developers choose this method due to the overhead of needing to restart (reboot) your device any time you want to switch between the operating systems. If you choose the bare metal Linux install route, you may also need to deal with potential driver issues or hardware compatibility problems that may arise with Linux on some devices.\\nYou can find a list of [Popular Linux Distributions available for download on Linux.org](https://linux.org/pages/download/). Once you\\u0027ve chosen a distro, follow the instructions to Create a bootable USB to install bare-metal Linux.\\nWarning\",\"contentUrl\":\"https://learn.microsoft.com/en-us/linux/install#step-1---choose-a-method-to-install-linux\"}], annotations=null)\n" + "TextContent(text=[{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\n## Step 2 - Choose a Linux distribution\\nThe different versions of Linux are called \\u0022distributions\\u0022 (sometimes shortened to \\u201Cdistros\\u201D). So there isn\\u2019t actually a way to just \\u201Cinstall Linux\\u201D, you first need to choose which distribution that uses the Linux kernel you want to download and install. There are over 600 active Linux distributions, such as Ubuntu, Debian, Kali Linux, openSUSE, and more. There are a number of factors to consider in choosing a distribution, such as:\\n### Required Linux experience\\nIf you\\u2019re new to Linux, you may want to start with Ubuntu. This is one of the most common starting places for Linux users due to the level of support and ease of use. Ubuntu offers a desktop-version and a server-version depending on how you plan to use it. The specific tasks that the operating system will be responsible for may help you determine which distribution to use. Arch Linux is a popular choice for those who want a highly customizable, do-it-yourself approach that is still stable and well-supported by a large user base. It is a much more complicated place to start, but can help you to get a better understanding of how Linux works due to the amount of custom configuration.\\n### System requirements\\nA minimal, or \\u0022lightweight\\u0022, distribution means a small footprint, with lower memory and processing speed requirements. Alpine Linux is a popular option where users will find most features disabled or not installed by default, but still has everything you need for a containerized app. Security needs: Some Linux users may have specific security concerns to consider. Kali Linux is an example of a distro designed for security tasks, such as Penetration Testing, Security Research, Computer Forensics and Reverse Engineering.\\n### Business and Enterprise requirements\\nBusiness, corporate, or academic environments may have unique concerns from consumer environments. For example Red Hat Enterprise Linux (RHEL), SUSE Linux Enterprise Server (SLES) and Oracle Linux offer 24/7 global support and subscription-based service. CentOS is another popular enterprise distro that is community-supported but still compatible with RHEL.\\n### Community support\",\"contentUrl\":\"https://learn.microsoft.com/en-us/linux/install#step-2---choose-a-linux-distribution\"},{\"title\":\"New to Linux resources for SQL users\",\"content\":\"# New to Linux resources for SQL users\\n**Applies to:**[SQL Server](https://learn.microsoft.com/en-us/sql/sql-server/sql-docs-navigation-guide?view=sql-server-ver17#applies-to) - Linux\\nThis article is intended to provide a learning path through an introduction of Linux concepts. Use the sections in the article as a guided learning path to familiarize yourself with the Linux environment.\\nThis isn\\u0027t intended to be a comprehensive list but instead provides the bare minimum you need to help you administer and navigate your SQL Server on Linux environment. See the [full list of tutorials](https://www.linux.org/forums/linux-beginner-tutorials.123/) to learn more.\\n## What is Linux?\\nThe [What is Linux](https://www.linux.org/threads/what-is-linux.4106/) module introduces the history of Linux as an operating system. The module explains *the Kernel* and where Linux is today. This tutorial serves as an introduction to Linux, helping you get started.\\n## Select a distribution\\nAfter you know the history of Linux, decide which [distribution of Linux](https://www.linux.org/threads/selecting-a-linux-distribution.4117/) best suits your business needs. [SQL Server is supported](https://learn.microsoft.com/en-us/sql/linux/sql-server-linux-release-notes-2019?view=sql-server-ver17#supported-platforms) on various distributions such as Red Hat Enterprise Linux (RHEL), SUSE Linux Enterprise Server (SLES), and Ubuntu.\\n## Get around directories\\nAfter you have chosen the appropriate distribution of Linux, familiarize yourself with [getting around Linux directories](https://www.linux.org/threads/getting-around-in-linux-directories.4120/).\\nThis module will help you answer the following questions:\\n1. How to navigate between different files\\n2. How to identify a file from a directory\\n3. Identify different directories\\n## Install new software\\nNext, learn how to [install new software](https://www.linux.org/threads/installing-new-software-debian-red-hat-slackware.4119/) on your new Linux operating system. This module details installing new software to the Debian and Red Hat Linux operating systems.\",\"contentUrl\":\"https://learn.microsoft.com/en-us/sql/linux/new-to-linux-resources?view=sql-server-ver17\"},{\"title\":\"Part 1.2 - Basic operations on Linux\",\"content\":\"# Part 1.2 - Basic operations on Linux\\n## Run commands by using the \\u0022sudo\\u0022 prefix\\nInstead of becoming the root account user in your session, you can run commands as root by using the \\u0060sudo \\u003Ccommand\\u003E\\u0060 format. This approach is safer because it will run the given command only as a superuser.\\nFor example, if you run \\u0060restart\\u0060 while you\\u0027re not elevated to superuser status, the result is as follows.\\n![Screenshot of restart command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/restart-command.png)\\nTo run this command as a superuser instead, you can either change the session context to root (by running \\u0060sudo su\\u0060) or add the \\u0060sudo\\u0060 prefix, as follows:\\n\\u0060\\u0060\\u0060bash\\nsudo reboot\\n\\u0060\\u0060\\u0060\\nNow, the restart operation runs as expected by the virtual machine.\\n![Screenshot of reboot command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/reboot-command.png)\\n## Package managers\\nPackage managers are used to install, upgrade, and remove applications in Linux. There are several different package managers, such as DPKG (Debian Package Management System), RPM (Red Hat Package Manager), and Zypper for SUSE Package managers themselves are low-level. Therefore, it might be difficult to manage software directly by using the package manager. Fortunately, there are wrappers for these tools that make it easier to install and uninstall software. For example, [yum](https://wikipedia.org/wiki/Yum_%28software%29) is a wrapper around RPM (Red Hat Package Manager) and [APT](https://wikipedia.org/wiki/APT_%28software%29) is a wrapper around DPKG (Debian Package Management System).\\nThis tutorial assumes that you\\u0027re running the [Ubuntu package management system](https://ubuntu.com/server/docs/package-management). This system is derived from the same system that\\u0027s used by the Debian GNU/Linux distribution. Therefore, we can use APT to install the software.\\nDepending on your Linux choice, you might have to use other tools. For example, you can use yum to install or uninstall software if you\\u0027re running Red Hat.\",\"contentUrl\":\"https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-by-using-the-sudo-prefix\"},{\"title\":\"Part 1.2 - Basic operations on Linux\",\"content\":\"# Part 1.2 - Basic operations on Linux\\n## Run commands as a superuser\\nThe superuser is the most privileged user account. It has root (unrestricted) access to all files and folders, and complete control over the operation of the computer. The system administrator uses this account for system maintenance.\\nOn UNIX-like systems, the conventional name of the superuser is \\u0022root\\u0022. The root user can do tasks that are otherwise restricted for standard users, including the following ones:\\n1. Changing system directories\\n2. Changing user privileges\\n3. Changing file ownership\\n4. System-related tasks such as restarts\\n5. Installation of certain applications\\nThere are several ways to run commands as the root account user. One is to use the \\u0060sudo su\\u0060 command. In this command, \\u0060sudo\\u0060 is short for \\u0060superuser do\\u0060, and \\u0060su\\u0060 is an abbreviation for \\u0022switch user.\\u0022 You can also use the \\u0060su\\u0060 command to switch between standard users.\\nImportant\\nAfter you become the root account user, everything you do runs in root context. Therefore, you should act carefully because the root account has access to the entire system.\\nTo demonstrate a permissions issue, create a text file in a special folder that\\u0027s named */etc*.\\nThe */etc/* folder is where system configuration files are located. This folder protects its contents from being changed by a standard user.\\nRun the following command:\\n\\u0060\\u0060\\u0060bash\\necho hello world \\u003E /etc/helloworld.txt\\n\\u0060\\u0060\\u0060\\nThe \\u0060echo\\u0060 command writes all the text that follows it to the output. The angle bracket (\\u0060\\u003E\\u0060) tells the system to send the output to the */etc/helloworld.txt* file instead of the console. This behavior is similar to how Windows works.\\nNote\\nLinux is case-sensitive: *Helloworld.txt*, *helloworld.txt* and *helloworld.Txt* are different files.\\nYou don\\u0027t have to provide a file name extension. The name \\u0022helloworld\\u0022 by itself is perfectly valid. (The \\u0022txt\\u0022 extension is used here only as an example.)\\nWhen you run this command, you receive a **permission denied** error message.\\n![Screenshot of permission denied message.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/permission-denied-message.png)\\nThe operation fails because the */etc* folder is special folder that a standard user can\\u0027t change. Verify the user by running the \\u0060id\\u0060 command.\\n![Screenshot of I D command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/id-command.png)\",\"contentUrl\":\"https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-as-a-superuser\"},{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\n## Step 1 - Choose a method to install Linux\\nThe choice of which method to use for installing Linux depends on your needs and preferences.\\n1. **New to using Linux?** We recommend starting with Windows Subsystem for Linux (WSL) as it\\u0027s the easiest way to get up and running.\\n2. **Work in a business environment with more complex needs related to scale or security?** We recommend running Linux as a Virtual Machine (VM) in the cloud and checking out the support that Azure has to offer. This also applies if you want to run Linux as a server.\\n3. **Only want to run Linux as your primary operating system?** If you are good with a slightly more complicated install process and don\\u0027t need access to Windows tools (like Outlook, Teams, Word, PowerPoint, etc), you can run Linux on bare metal to access the full potential of your hardware without any overhead from virtualization or emulation.\\nLearn more about these options below.\\n### Windows Subsystem for Linux (WSL)\\nAvailable to those with a Windows machine, this is the most simple way to install Linux. Just run the Linux install command: \\u0060wsl --install\\u0060 to install the Ubuntu distribution.\\nThis method uses virtualization to integrate the Windows operating system (OS) with the Linux OS (running on an actual Linux kernel). You can add as many different Linux distributions as you please and run your favorite Linux tools, including GUI apps, alongside your favorite Windows tools. You can also mix and match Bash and PowerShell commands in the same command line. A Linux file system hosts your project files, while a separate Windows file systems hosts any developer tools, such as VS Code, that you\\u0027d like to work on the files with. This method of running Linux is highly efficient and performant. Once you choose a Linux distribution or opt to use the default Ubuntu distro, follow the instructions to Use the install Linux command with Windows Subsystem for Linux or learn more in the [WSL documentation](https://learn.microsoft.com/en-us/windows/wsl).\\n### Create a Virtual Machine (VM)\\nAvailable to everyone regardless if you\\u0027re using a Windows or MacOS-based machine, but will require either an Azure account, if creating a VM in the cloud, or virtualization software, if creating a VM directly on your machine (locally).\",\"contentUrl\":\"https://learn.microsoft.com/en-us/linux/install#step-1---choose-a-method-to-install-linux\"},{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\nLinux is an operating system, similar to Windows, but with many different versions due to the nature of being open source and fully customizable. To install Linux, you must choose an install method and choose a Linux distribution.\\nTo install Linux:\\n1. Choose an install method: Windows Subsystem for Linux (WSL), Bare metal Linux; or create a Virtual Machine (VM) to run Linux locally or in the cloud.\\n2. Choose a Linux distribution: Ubuntu, Debian, Kali Linux, openSUSE, etc.\\n3. Follow the steps for your preferred install method:\\n3.1. Use the install Linux command with Windows Subsystem for Linux (WSL)\\n3.2. Create a Linux Virtual Machine (VM) in the cloud\\n3.3. Create a Linux Virtual Machine (VM) on your local machine\\n3.4. Create a bootable USB to install bare-metal Linux\\n4. After installing Linux: Get familiar with your distribution\\u0027s package manager, update and upgrade the packages available, and get familiar with the other [Linux resources at Microsoft](https://learn.microsoft.com/en-us/linux/), such as training courses, Linux-versions of popular tools, news, and Open Source events.\",\"contentUrl\":\"https://learn.microsoft.com/en-us/linux/install\"},{\"title\":\"Guide for changing your dev environment from Mac to Windows\",\"content\":\"# Guide for changing your dev environment from Mac to Windows\\n## Command-line shells and terminals\\nYou can now install Windows Subsystem for Linux (WSL) to support running a Linux shell within Windows. This means that you can run **bash**, with whichever specific Linux distribution you choose, integrated right inside Windows. Using WSL provides the kind of environment most familiar to Mac users. For example, you use **ls** to list the files in a current directory, not **dir** as you would with the traditional Windows Cmd Shell. To learn about installing and using WSL, see the [Windows Subsystem for Linux Installation Guide](https://learn.microsoft.com/en-us/windows/wsl/install). Linux distributions that you can install on Windows with WSL include:\\n1. [Ubuntu 20.04 LTS](https://www.microsoft.com/store/apps/9n6svws3rx71)\\n2. [Kali Linux](https://www.microsoft.com/store/apps/9PKR34TNCV07)\\n3. [Debian GNU/Linux](https://www.microsoft.com/store/apps/9MSVKQC78PK6)\\n4. [openSUSE Leap 15.1](https://www.microsoft.com/store/apps/9NJFZK00FGKV)\\n5. [SUSE Linux Enterprise Server 15 SP1](https://www.microsoft.com/store/apps/9PN498VPMF3Z)\\nJust to name a few. Find more in the [WSL install docs](https://learn.microsoft.com/en-us/windows/wsl/install-win10#install-your-linux-distribution-of-choice) and install them directly from the [Microsoft Store](https://aka.ms/wslstore).\",\"contentUrl\":\"https://learn.microsoft.com/en-us/windows/dev-environment/mac-to-windows#command-line-shells-and-terminals\"},{\"title\":\"Supported Linux operating system versions\",\"content\":\"# Supported Linux operating system versions\\n## Universal Linux (Debian package)\\nSupported versions:\\n1. Debian 8, 9, 10, 11, and 12\\n2. Ubuntu 16.04, 18.04, 20.04, 22.04 and 24.04\\nNote\\n1. Ubuntu 22.04 is supported from Operations Manager 2022 UR1 and later when you apply [this hotfix](https://support.microsoft.com/topic/system-center-operations-manager-2022-now-has-openssl3-0-integration-kb-5024286-331bd221-10f9-42d5-bc06-775eaabe3081).\\n2. Ubuntu 24.04 is supported from Operations Manager 2022 UR2 and later.\\n| Required package | Description | Minimum version| \\n| --- | --- | --- |\\n| libc6 | C Standard shared library | 2.24-11 |\\n| OpenSSL | OpenSSL Libraries; Secure Network Communications Protocol | 1.0, 1.1 or 3.0 and later |\\n| PAM | Pluggable Authentication Modules | 1.1.8-3.1 |\\n\",\"contentUrl\":\"https://learn.microsoft.com/en-us/system-center/scom/plan-supported-crossplat-linux-os?view=sc-om-2025#universal-linux-debian-package\"},{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\n## Step 3 - Follow install method instructions\\n### Install Linux with Windows Subsystem for Linux\\nTo install Linux on a Windows PC, use the install Linux command.\\n1. Open PowerShell or Windows Command Prompt in *administrator mode* by right-clicking and selecting \\u0022Run as administrator\\u0022.\\n2. Enter the Linux install command: \\u0060wsl --install\\u0060.\\n3. Restart your machine.\\n#### Install Linux command options\\nBy default, the install command will use the Ubuntu distribution of Linux. You can see the other distributions that are available in the Microsoft Store by entering the command: \\u0060wsl -l -o\\u0060. If you\\u0027d like to install a distribution other than Ubuntu, just add \\u0060-d\\u0060 to the command with the name of the distribution you\\u0027d like to install:\\n\\u0060\\u0060\\u0060powershell\\nwsl --install -d \\u003CDistro Name\\u003E\\n\\u0060\\u0060\\u0060\\nIf the distribution you want to install isn\\u0027t available in the Microsoft Store, see the Windows Subsystem for Linux (WSL) documentation on how to [Import any Linux distribution to use with WSL](https://learn.microsoft.com/en-us/windows/wsl/use-custom-distro).\\nFor more information on how to use VS Code, Git, databases, Docker, GUI apps, GPU acceleration, NodesJS, USB devices, or mount a disk, build a custom distribution, manage disk space, or set up WSL for your enterprise business, see the [WSL documentation](https://learn.microsoft.com/en-us/windows/wsl/).\\n### Create a Linux Virtual Machine in the cloud\\nTo create a Linux Virtual Machine (VM) that is hosted in the cloud on Azure, log-in to the Azure Portal and select the \\u201CCreate a Virtual Machine\\u201D service. Then follow the instructions for creating a user name, project, instance, SSH key, ports, and copying the IP address.\\nAzure supports a variety of options for setting up Linux VMs in the cloud. For a quickstart guide on how to set up a Linux VM, choose your preferred method:\\n1. [Azure Portal](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-portal?tabs=ubuntu\\u0026source=docs)\\n2. [Azure CLI](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-cli)\\n3. [Azure PowerShell](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-powershell)\\n4. [Terraform](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-terraform)\",\"contentUrl\":\"https://learn.microsoft.com/en-us/linux/install#step-3---follow-install-method-instructions\"},{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\n## Step 1 - Choose a method to install Linux\\nYou are responsible for managing the virtual machine when using a hypervisor, including allocating resources like memory and disk space, and ensuring that it is secure and up-to-date. This requires more technical expertise than some of the other options and may not be as scalable or fault-tolerant.\\nOnce you\\u0027ve chosen a distribution and decided whether you want to run the VM locally, using a hypervisor, or in the cloud, using Azure, follow the instructions to Create a Linux Virtual Machine in the Cloud or Create a Linux Virtual Machine locally using a Hypervisor\\n### Bare metal Linux\\nBare metal Linux just means that Linux is running directly on the device hardware. This install method requires you to create a bootable USB drive by downloading an iso install file from the site hosting your chosen Linux distribution. You will need to use a Windows computer (or any desktop device with an existing OS) to create this drive.\\nMany users choose the traditional method of installing bare metal Linux on a device that is also running Windows and using the **\\u0022dual boot\\u0022** method. To boot Linux and Windows, you need to partition your hard drive to create separate spaces for both the Linux and Windows OS. The performance speed between WSL, virtual machines, and bare metal Linux has become so close that few developers choose this method due to the overhead of needing to restart (reboot) your device any time you want to switch between the operating systems. If you choose the bare metal Linux install route, you may also need to deal with potential driver issues or hardware compatibility problems that may arise with Linux on some devices.\\nYou can find a list of [Popular Linux Distributions available for download on Linux.org](https://linux.org/pages/download/). Once you\\u0027ve chosen a distro, follow the instructions to Create a bootable USB to install bare-metal Linux.\\nWarning\",\"contentUrl\":\"https://learn.microsoft.com/en-us/linux/install#step-1---choose-a-method-to-install-linux\"}], annotations=null, meta=null)\n" ] } ], - "execution_count": 17 + "execution_count": 16 } ], "metadata": { diff --git a/samples/weather-stdio-server/gradle/libs.versions.toml b/samples/weather-stdio-server/gradle/libs.versions.toml index 7136d03..d525461 100644 --- a/samples/weather-stdio-server/gradle/libs.versions.toml +++ b/samples/weather-stdio-server/gradle/libs.versions.toml @@ -4,7 +4,7 @@ coroutines = "1.10.2" kotlin = "2.2.21" ktor = "3.2.3" logging = "7.0.13" -mcp-kotlin = "0.7.7" +mcp-kotlin = "0.8.0" shadow = "9.2.2" slf4j = "2.0.17" From a0b4ef021d2018a103789f252a1616a3fbf40762 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 13:10:20 +0100 Subject: [PATCH 081/449] Bump actions/checkout from 5 to 6 (#429) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
Release notes

Sourced from actions/checkout's releases.

v6.0.0

What's Changed

Full Changelog: https://github.com/actions/checkout/compare/v5.0.0...v6.0.0

v6-beta

What's Changed

Updated persist-credentials to store the credentials under $RUNNER_TEMP instead of directly in the local git config.

This requires a minimum Actions Runner version of v2.329.0 to access the persisted credentials for Docker container action scenarios.

v5.0.1

What's Changed

Full Changelog: https://github.com/actions/checkout/compare/v5...v5.0.1

Changelog

Sourced from actions/checkout's changelog.

Changelog

V6.0.0

V5.0.1

V5.0.0

V4.3.1

V4.3.0

v4.2.2

v4.2.1

v4.2.0

v4.1.7

v4.1.6

v4.1.5

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=5&new-version=6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/apidocs.yaml | 2 +- .github/workflows/build.yml | 2 +- .github/workflows/codeql.yml | 2 +- .github/workflows/gradle-publish.yml | 2 +- .github/workflows/samples.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/apidocs.yaml b/.github/workflows/apidocs.yaml index c2b86e8..a22acb2 100644 --- a/.github/workflows/apidocs.yaml +++ b/.github/workflows/apidocs.yaml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: submodules: true # Fetch Hugo themes (true OR recursive) fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fd848b4..9eed688 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,7 +20,7 @@ jobs: env: JAVA_OPTS: "-Xmx8g -Dfile.encoding=UTF-8 -Djava.awt.headless=true -Dkotlin.daemon.jvm.options=-Xmx6g" steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up JDK 21 uses: actions/setup-java@v5 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 329aa59..1a7789d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -29,7 +29,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml index bdc64fa..d9b2801 100644 --- a/.github/workflows/gradle-publish.yml +++ b/.github/workflows/gradle-publish.yml @@ -23,7 +23,7 @@ jobs: packages: write steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up JDK 21 uses: actions/setup-java@v5 with: diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index 7ef265d..472110d 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -31,7 +31,7 @@ jobs: env: JAVA_OPTS: "-Xmx8g -Dfile.encoding=UTF-8 -Djava.awt.headless=true -Dkotlin.daemon.jvm.options=-Xmx6g" steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up JDK 21 uses: actions/setup-java@v5 From e443fea8cbf4d8d17b8fd5b602a6659c2eba0c14 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 13:25:53 +0100 Subject: [PATCH 082/449] Bump org.jetbrains.kotlinx:kotlinx-io-core from 0.8.0 to 0.8.1 (#432) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [org.jetbrains.kotlinx:kotlinx-io-core](https://github.com/Kotlin/kotlinx-io) from 0.8.0 to 0.8.1.
Release notes

Sourced from org.jetbrains.kotlinx:kotlinx-io-core's releases.

v0.8.1

This release addresses an issue with Wasm/JS, where the require function will no longer be available starting from Kotlin 2.3.20 (and it was never guaranteed to be available in Wasm/JS).

The release also includes some infrastructure changes.

Bugfixes

  • Fixed the issue with the require-function that Wasm/JS-projects migrating to future versions of Kotlin might face #481

Thanks to @​alex28sh, @​sargunv, and @​iseki0 for contributions!

Changelog

Sourced from org.jetbrains.kotlinx:kotlinx-io-core's changelog.

0.8.1

Published 21 November 2025

This release addresses an issue with Wasm/JS, where the require function will no longer be available starting from Kotlin 2.3.20 (and it was never guaranteed to be available in Wasm/JS).

The release also includes some infrastructure changes.

Bugfixes

  • Fixed the issue with the require-function that Wasm/JS-projects migrating to future versions of Kotlin might face #481
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.jetbrains.kotlinx:kotlinx-io-core&package-manager=gradle&previous-version=0.8.0&new-version=0.8.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9c57db3..9a8a182 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,7 +14,7 @@ openapi-generator = "7.17.0" serialization = "1.9.0" collections-immutable = "0.4.0" coroutines = "1.10.2" -kotlinx-io = "0.8.0" +kotlinx-io = "0.8.1" ktor = "3.2.3" logging = "7.0.13" slf4j = "2.0.17" From fbbb43027338b01033b17cd06ffa7cb3fc819035 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 20:48:08 +0200 Subject: [PATCH 083/449] Bump dev.mokksy:mokksy from 0.6.1 to 0.6.2 (#431) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [dev.mokksy:mokksy](https://github.com/mokksy/ai-mocks) from 0.6.1 to 0.6.2.
Release notes

Sourced from dev.mokksy:mokksy's releases.

0.6.2

What's Changed

Dependency updates

Full Changelog: https://github.com/mokksy/ai-mocks/compare/v0.6.1...v0.6.2

Commits
  • 5c0701b 0.6.2
  • fb95af2 fix(docs): Update KDocs, fix links and cross-references (#476)
  • 9b0e18b fix(deps): update dependency org.springframework.ai:spring-ai-bom to v1.1.0 (...
  • 93d57be chore(deps): update actions/checkout action to v6 (#475)
  • 8d91053 chore(deps): update dependency org.openrewrite.rewrite to v7.21.0 (#474)
  • f526626 Add JSON Schema DSL for type-safe schema construction (#472)
  • 8ce39c5 fix(deps): update dependency org.springframework:spring-framework-bom to v6.2...
  • 8a01226 chore(deps): update dependency com.diffplug.spotless to v8.1.0 (#470)
  • 88d9483 chore(deps): update dependency com.diffplug.spotless:spotless-plugin-gradle t...
  • cc0922b fix(deps): update dependency com.google.genai:google-genai to v1.28.0 (#469)
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=dev.mokksy:mokksy&package-manager=gradle&previous-version=0.6.1&new-version=0.6.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9a8a182..9ad38f9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -20,7 +20,7 @@ logging = "7.0.13" slf4j = "2.0.17" kotest = "6.0.4" awaitility = "4.3.0" -mokksy = "0.6.1" +mokksy = "0.6.2" [libraries] # Plugins From e584cfe8824dbf40e7610c9b11739aff0be59d9b Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Thu, 27 Nov 2025 18:12:14 +0200 Subject: [PATCH 084/449] fix(sse-client): Skip SSE in StreamableHttpClientTransport when data is empty (#433) Refactor StreamableHttpClientTransport to skip SSE when the data field is empty. ## Motivation and Context TypeScript SSE Server started returning Server-Sent Events with only id and empty data field (Heartbeat/Checkpoint), followed by an event with data, e.g.: ``` id: d85c6bb0-0fa2-4828-81ad-3e951131aea5_1764226144689_7qb3iojc data: event: message id: d85c6bb0-0fa2-4828-81ad-3e951131aea5_1764226144690_sodh20lo data: {"result":{"protocolVersion":"2025-06-18","capabilities":{"logging":{},"tools":{"listChanged":true},"prompts":{"listChanged":true},"completions":{},"resources":{"listChanged":true}},"serverInfo":{"name":"simple-streamable-http-server","version":"1.0.0"}},"jsonrpc":"2.0","id":"7ce065b0678f49e5b04ce5a0fcc7d518"} ``` Such empty events might be used by the server as **Heartbeat** or **Checkpoint** events. The client should retain the updated Last-Event-ID and disregard the payload. It is essential to handle such responses to ensure compatibility with updated TypeScript SDK. ## How Has This Been Tested? - Added integration test - Regression tests are green now ## Breaking Changes No ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 ## Additional context --- .../client/StreamableHttpClientTransport.kt | 21 +- .../AbstractStreamableHttpClientTest.kt | 2 +- .../kotlin/sdk/client/OldSchemaMockMcp.kt | 236 ------------------ .../sdk/client/StreamableHttpClientTest.kt | 57 +++++ 4 files changed, 71 insertions(+), 245 deletions(-) delete mode 100644 kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaMockMcp.kt diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt index bcd1a7c..e635f87 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt @@ -42,8 +42,6 @@ import kotlin.concurrent.atomics.AtomicBoolean import kotlin.concurrent.atomics.ExperimentalAtomicApi import kotlin.time.Duration -private val logger = KotlinLogging.logger {} - private const val MCP_SESSION_ID_HEADER = "mcp-session-id" private const val MCP_PROTOCOL_VERSION_HEADER = "mcp-protocol-version" private const val MCP_RESUMPTION_TOKEN_HEADER = "Last-Event-ID" @@ -67,6 +65,10 @@ public class StreamableHttpClientTransport( private val requestBuilder: HttpRequestBuilder.() -> Unit = {}, ) : AbstractTransport() { + private companion object { + private val logger = KotlinLogging.logger {} + } + public var sessionId: String? = null private set public var protocolVersion: String? = null @@ -316,11 +318,14 @@ public class StreamableHttpClientTransport( var id: String? = null var eventName: String? = null - suspend fun dispatch(data: String) { + suspend fun dispatch(id: String?, eventName: String?, data: String) { id?.let { lastEventId = it onResumptionToken?.invoke(it) } + if (data.isBlank()) { + return + } if (eventName == null || eventName == "message") { runCatching { McpJson.decodeFromString(data) } .onSuccess { msg -> @@ -335,16 +340,16 @@ public class StreamableHttpClientTransport( throw it } } - // reset - id = null - eventName = null - sb.clear() } while (!channel.isClosedForRead) { val line = channel.readUTF8Line() ?: break if (line.isEmpty()) { - dispatch(sb.toString()) + dispatch(id = id, eventName = eventName, data = sb.toString()) + // reset + id = null + eventName = null + sb.clear() continue } when { diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractStreamableHttpClientTest.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractStreamableHttpClientTest.kt index 6f1e30f..77ba553 100644 --- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractStreamableHttpClientTest.kt +++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractStreamableHttpClientTest.kt @@ -12,7 +12,7 @@ import org.junit.jupiter.api.TestInstance internal abstract class AbstractStreamableHttpClientTest { // start mokksy on random port - protected val mockMcp: OldSchemaMockMcp = OldSchemaMockMcp(verbose = true) + protected val mockMcp: MockMcp = MockMcp(verbose = true) @AfterEach fun afterEach() { diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaMockMcp.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaMockMcp.kt deleted file mode 100644 index 5d1a144..0000000 --- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaMockMcp.kt +++ /dev/null @@ -1,236 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.client - -import dev.mokksy.mokksy.BuildingStep -import dev.mokksy.mokksy.Mokksy -import dev.mokksy.mokksy.StubConfiguration -import io.ktor.http.ContentType -import io.ktor.http.HttpMethod -import io.ktor.http.HttpStatusCode -import io.ktor.sse.ServerSentEvent -import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest -import io.modelcontextprotocol.kotlin.sdk.types.RequestId -import kotlinx.coroutines.flow.Flow -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.contentOrNull -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive -import kotlinx.serialization.json.putJsonObject - -/** - * High-level helper for simulating an MCP server over Streaming HTTP transport with Server-Sent Events (SSE), - * built on top of an HTTP server using the [Mokksy](https://mokksy.dev) library. - * - * Provides test utilities to mock server behavior based on specific request conditions. - * - * @param verbose Whether to print detailed logs. Defaults to `false`. - * @author Konstantin Pavlov - */ -internal class OldSchemaMockMcp(verbose: Boolean = false) { - - private val mokksy: Mokksy = Mokksy(verbose = verbose) - - fun checkForUnmatchedRequests() { - mokksy.checkForUnmatchedRequests() - } - - val url = "${mokksy.baseUrl()}/mcp" - - @Suppress("LongParameterList") - fun onInitialize( - clientName: String? = null, - sessionId: String, - protocolVersion: String = "2025-03-26", - serverName: String = "Mock MCP Server", - serverVersion: String = "1.0.0", - capabilities: JsonObject = buildJsonObject { - putJsonObject("tools") { - put("listChanged", JsonPrimitive(false)) - } - }, - ) { - val predicates = if (clientName != null) { - arrayOf<(JSONRPCRequest?) -> Boolean>({ - it?.params?.jsonObject - ?.get("clientInfo")?.jsonObject - ?.get("name")?.jsonPrimitive - ?.contentOrNull == clientName - }) - } else { - emptyArray() - } - - handleWithResult( - jsonRpcMethod = "initialize", - sessionId = sessionId, - bodyPredicates = predicates, - // language=json - result = """ - { - "capabilities": $capabilities, - "protocolVersion": "$protocolVersion", - "serverInfo": { - "name": "$serverName", - "version": "$serverVersion" - }, - "_meta": { - "foo": "bar" - } - } - """.trimIndent(), - ) - } - - fun onJSONRPCRequest( - httpMethod: HttpMethod = HttpMethod.Post, - jsonRpcMethod: String, - expectedSessionId: String? = null, - vararg bodyPredicates: (JSONRPCRequest) -> Boolean, - ): BuildingStep = mokksy.method( - configuration = StubConfiguration(removeAfterMatch = true), - httpMethod = httpMethod, - requestType = JSONRPCRequest::class, - ) { - path("/mcp") - expectedSessionId?.let { - containsHeader(MCP_SESSION_ID_HEADER, it) - } - bodyMatchesPredicate( - description = "JSON-RPC version is '2.0'", - predicate = - { - it!!.jsonrpc == "2.0" - }, - ) - bodyMatchesPredicate( - description = "JSON-RPC Method should be '$jsonRpcMethod'", - predicate = - { - it!!.method == jsonRpcMethod - }, - ) - bodyPredicates.forEach { predicate -> - bodyMatchesPredicate(predicate = { predicate.invoke(it!!) }) - } - } - - @Suppress("LongParameterList") - fun handleWithResult( - httpMethod: HttpMethod = HttpMethod.Post, - jsonRpcMethod: String, - expectedSessionId: String? = null, - sessionId: String, - contentType: ContentType = ContentType.Application.Json, - statusCode: HttpStatusCode = HttpStatusCode.OK, - vararg bodyPredicates: (JSONRPCRequest) -> Boolean, - result: () -> JsonObject, - ) { - onJSONRPCRequest( - httpMethod = httpMethod, - jsonRpcMethod = jsonRpcMethod, - expectedSessionId = expectedSessionId, - bodyPredicates = bodyPredicates, - ) respondsWith { - val requestId = when (request.body.id) { - is RequestId.NumberId -> (request.body.id as RequestId.NumberId).value.toString() - is RequestId.StringId -> "\"${(request.body.id as RequestId.StringId).value}\"" - } - val resultObject = result!!.invoke() - // language=json - body = """ - { - "jsonrpc": "2.0", - "id": $requestId, - "result": $resultObject - } - """.trimIndent() - this.contentType = contentType - headers += MCP_SESSION_ID_HEADER to sessionId - httpStatus = statusCode - } - } - - @Suppress("LongParameterList") - fun handleWithResult( - httpMethod: HttpMethod = HttpMethod.Post, - jsonRpcMethod: String, - expectedSessionId: String? = null, - sessionId: String, - contentType: ContentType = ContentType.Application.Json, - statusCode: HttpStatusCode = HttpStatusCode.OK, - vararg bodyPredicates: (JSONRPCRequest) -> Boolean, - result: String, - ) { - handleWithResult( - httpMethod = httpMethod, - jsonRpcMethod = jsonRpcMethod, - expectedSessionId = expectedSessionId, - sessionId = sessionId, - contentType = contentType, - statusCode = statusCode, - bodyPredicates = bodyPredicates, - result = { - Json.parseToJsonElement(result).jsonObject - }, - ) - } - - @Suppress("LongParameterList") - fun handleJSONRPCRequest( - httpMethod: HttpMethod = HttpMethod.Post, - jsonRpcMethod: String, - expectedSessionId: String? = null, - sessionId: String, - contentType: ContentType = ContentType.Application.Json, - statusCode: HttpStatusCode = HttpStatusCode.OK, - vararg bodyPredicates: (JSONRPCRequest?) -> Boolean, - bodyBuilder: () -> String = { "" }, - ) { - onJSONRPCRequest( - httpMethod = httpMethod, - jsonRpcMethod = jsonRpcMethod, - expectedSessionId = expectedSessionId, - bodyPredicates = bodyPredicates, - ) respondsWith { - body = bodyBuilder.invoke() - this.contentType = contentType - headers += MCP_SESSION_ID_HEADER to sessionId - httpStatus = statusCode - } - } - - fun onSubscribe(httpMethod: HttpMethod = HttpMethod.Post, sessionId: String): BuildingStep = mokksy.method( - httpMethod = httpMethod, - name = "MCP GETs", - requestType = Any::class, - ) { - path("/mcp") - containsHeader(MCP_SESSION_ID_HEADER, sessionId) - containsHeader("Accept", "application/json,text/event-stream") - containsHeader("Cache-Control", "no-store") - } - - fun handleSubscribeWithGet(sessionId: String, block: () -> Flow) { - onSubscribe( - httpMethod = HttpMethod.Get, - sessionId = sessionId, - ) respondsWithSseStream { - headers += MCP_SESSION_ID_HEADER to sessionId - this.flow = block.invoke() - } - } - - fun mockUnsubscribeRequest(sessionId: String) { - mokksy.delete( - configuration = StubConfiguration(removeAfterMatch = true), - requestType = JSONRPCRequest::class, - ) { - path("/mcp") - containsHeader(MCP_SESSION_ID_HEADER, sessionId) - } respondsWith { - body = null - } - } -} diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt index 37f5a30..06d485f 100644 --- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt +++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt @@ -11,7 +11,9 @@ import io.modelcontextprotocol.kotlin.sdk.types.Implementation import io.modelcontextprotocol.kotlin.sdk.types.Tool import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.put @@ -28,6 +30,61 @@ import kotlin.uuid.Uuid @Suppress("LongMethod") internal class StreamableHttpClientTest : AbstractStreamableHttpClientTest() { + @Test + fun `Should skip empty SSE`(): Unit = runBlocking { + val client = Client( + clientInfo = Implementation( + name = "client1", + version = "1.0.0", + ), + options = ClientOptions( + capabilities = ClientCapabilities(), + ), + ) + val sessionId = Uuid.random().toString() + + mockMcp.onJSONRPCRequest( + httpMethod = HttpMethod.Post, + jsonRpcMethod = "initialize", + ).respondsWithStream { + headers += MCP_SESSION_ID_HEADER to sessionId + flow = flowOf( + "id: ${Uuid.random()}\n", + "data:\n", // empty data + "\n", + "id: ${Uuid.random()}\n", + "data: \t \n", // tabs and spaces + "\n", + "id: ${Uuid.random()}\n", + "event: message\n", + // multiline data + "data: {\n", + "data: \"result\":{\n" + + "data: \"protocolVersion\":\"2025-06-18\",\n" + + "data: \"capabilities\":{},\n" + + "data: \"serverInfo\":{\"name\":\"simple-streamable-http-server\",\"version\":\"1.0.0\"}\n" + + "data: },\n" + + "data: \"jsonrpc\":\"2.0\",\n" + + "data: \"id\":\"7ce065b0678f49e5b04ce5a0fcc7d518\"\n" + + "data: }\n", + "\n", + ) + } + + mockMcp.handleJSONRPCRequest( + jsonRpcMethod = "notifications/initialized", + expectedSessionId = sessionId, + sessionId = sessionId, + statusCode = HttpStatusCode.Accepted, + ) + + mockMcp.handleSubscribeWithGet(sessionId) { + emptyFlow() + } + + connect(client) + } + @Test fun `test streamableHttpClient`() = runBlocking { val client = Client( From 29d89abf0290b40f3f4e774e1617564f0e13e75b Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Thu, 27 Nov 2025 18:35:47 +0200 Subject: [PATCH 085/449] Upgrade SDK to 0.8.0 in Weather STDIO server sample (#428) Upgrade SDK version to 0.8.0 in Weather STDIO server sample ## Motivation and Context Keep samples up to date ## How Has This Been Tested? Locally + CI ## Breaking Changes ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [x] Refactoring ## 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 ## Additional context --- .../sample/server/McpWeatherServer.kt | 17 +++++++++-------- .../sample/client/ClientStdio.kt | 16 ++++++++++------ 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt b/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt index 181431e..f08c436 100644 --- a/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt +++ b/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt @@ -8,14 +8,14 @@ import io.ktor.http.contentType import io.ktor.http.headers import io.ktor.serialization.kotlinx.json.json import io.ktor.utils.io.streams.asInput -import io.modelcontextprotocol.kotlin.sdk.CallToolResult -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities -import io.modelcontextprotocol.kotlin.sdk.TextContent -import io.modelcontextprotocol.kotlin.sdk.Tool import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport +import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema import kotlinx.coroutines.Job import kotlinx.coroutines.runBlocking import kotlinx.io.asSink @@ -28,7 +28,8 @@ import kotlinx.serialization.json.put import kotlinx.serialization.json.putJsonObject /** - * Starts an MCP server that provides weather-related tools for fetching active weather alerts by state and weather forecasts by latitude/longitude. + * Starts an MCP server that provides weather-related tools for fetching active + * weather alerts by state and weather forecasts by latitude/longitude. */ fun runMcpServer() { // Base URL for the Weather API @@ -72,7 +73,7 @@ fun runMcpServer() { description = """ Get weather alerts for a US state. Input is Two-letter US state code (e.g. CA, NY) """.trimIndent(), - inputSchema = Tool.Input( + inputSchema = ToolSchema( properties = buildJsonObject { putJsonObject("state") { put("type", "string") @@ -97,7 +98,7 @@ fun runMcpServer() { description = """ Get weather forecast for a specific latitude/longitude """.trimIndent(), - inputSchema = Tool.Input( + inputSchema = ToolSchema( properties = buildJsonObject { putJsonObject("latitude") { put("type", "number") diff --git a/samples/weather-stdio-server/src/test/kotlin/io/modelcontextprotocol/sample/client/ClientStdio.kt b/samples/weather-stdio-server/src/test/kotlin/io/modelcontextprotocol/sample/client/ClientStdio.kt index 480bc1c..5934ee2 100644 --- a/samples/weather-stdio-server/src/test/kotlin/io/modelcontextprotocol/sample/client/ClientStdio.kt +++ b/samples/weather-stdio-server/src/test/kotlin/io/modelcontextprotocol/sample/client/ClientStdio.kt @@ -1,16 +1,20 @@ package io.modelcontextprotocol.sample.client -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.TextContent import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.TextContent import kotlinx.coroutines.runBlocking import kotlinx.io.asSink import kotlinx.io.asSource import kotlinx.io.buffered fun main(): Unit = runBlocking { - val process = ProcessBuilder("java", "-jar", "./build/libs/weather-stdio-server-0.1.0-all.jar") + val process = ProcessBuilder( + "java", + "-jar", + "${System.getProperty("user.dir")}/build/libs/weather-stdio-server-0.1.0-all.jar", + ).redirectErrorStream(true) .start() val transport = StdioClientTransport( @@ -34,15 +38,15 @@ fun main(): Unit = runBlocking { "latitude" to 38.5816, "longitude" to -121.4944, ), - )?.content?.map { if (it is TextContent) it.text else it.toString() } + ).content.map { if (it is TextContent) it.text else it.toString() } - println("Weather Forcast: ${weatherForecastResult?.joinToString(separator = "\n", prefix = "\n", postfix = "\n")}") + println("Weather Forecast: ${weatherForecastResult.joinToString(separator = "\n", prefix = "\n", postfix = "\n")}") val alertResult = client.callTool( name = "get_alert", arguments = mapOf("state" to "TX"), - )?.content?.map { if (it is TextContent) it.text else it.toString() } + ).content.map { if (it is TextContent) it.text else it.toString() } println("Alert Response = $alertResult") From f04ccfd1626eb290da412e5f487ddd4b33144c6a Mon Sep 17 00:00:00 2001 From: Sergey Karpov Date: Mon, 1 Dec 2025 18:02:34 +0200 Subject: [PATCH 086/449] Add MCP conformance test coverage (#435) ## Motivation and Context 1. The currently failing checks are commented out in `ConformanceTest`, see the TODOs. 2. I added detailed logging for Gradle task, Server, and Client to allow easier output reading when viewing tests results outside of IDE. 3. As a future improvement, instead of hard-coding scenarios, we can get them with `npx @modelcontextprotocol/conformance list` and run all of them. 4. The conformance framework writes its result into `kotlin-sdk-test/results` directory and contains JSONs like the one below: ```json [ { "id": "mcp-client-initialization", "name": "MCPClientInitialization", "description": "Validates that MCP client properly initializes with server", "status": "SUCCESS", "timestamp": "2025-11-28T14:38:36.718Z", "specReferences": [ { "id": "MCP-Lifecycle", "url": "https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle" } ], "details": { "protocolVersionSent": "2025-06-18", "expectedSpecVersion": "2025-06-18", "versionMatch": true, "clientName": "kotlin-conformance-client", "clientVersion": "1.0.0" } }, { "id": "server-info", "name": "ServerInfo", "description": "Test server info returned to client", "status": "INFO", "timestamp": "2025-11-28T14:38:36.718Z", "specReferences": [ { "id": "MCP-Lifecycle", "url": "https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle" } ], "details": { "serverName": "test-server", "serverVersion": "1.0.0" } } ] ``` As another future improvement, we can do somethig with these JSON files, like putting them into a job summary on GitHub. 5. The client test also generated stderr.txt file in `kotlin-sdk-test/results/initialize-.......` which contains errors because it tries to call a tool. I haven't investigated if it's just the way I wrote the client or related to `StreamableHttpClientTransport`. 6. The server is the simplified version of the existing server for Kotlin<--->Typescript tests. If conformance tests extend significantly, we can either combine the server implementations or ditch the integration tests. ## How Has This Been Tested? ## Breaking Changes ## 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 - [ ] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed ## Additional context --------- Co-authored-by: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> --- .github/workflows/build.yml | 1 + .github/workflows/conformance.yml | 50 +++ .github/workflows/gradle-publish.yml | 2 +- .gitignore | 3 + conformance-test/build.gradle.kts | 38 ++ .../sdk/conformance/ConformanceClient.kt | 85 ++++ .../sdk/conformance/ConformanceServer.kt | 422 ++++++++++++++++++ .../kotlin/sdk/conformance/ConformanceTest.kt | 328 ++++++++++++++ .../conformance/WebSocketConformanceClient.kt | 104 +++++ settings.gradle.kts | 1 + 10 files changed, 1033 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/conformance.yml create mode 100644 conformance-test/build.gradle.kts create mode 100644 conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceClient.kt create mode 100644 conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceServer.kt create mode 100644 conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTest.kt create mode 100644 conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/WebSocketConformanceClient.kt diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9eed688..719f9c7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,6 +41,7 @@ jobs: clean \ ktlintCheck \ build \ + -x :conformance-test:test \ koverLog koverHtmlReport \ publishToMavenLocal \ -Pversion=0.0.1-SNAPSHOT diff --git a/.github/workflows/conformance.yml b/.github/workflows/conformance.yml new file mode 100644 index 0000000..c736ad3 --- /dev/null +++ b/.github/workflows/conformance.yml @@ -0,0 +1,50 @@ +name: Conformance Tests + +on: + workflow_dispatch: + pull_request: + branches: [ main ] + push: + branches: [ main ] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + # Cancel only when the run is NOT on `main` branch + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +jobs: + run-conformance: + runs-on: macos-latest-xlarge + name: Run Conformance Tests + timeout-minutes: 20 + env: + JAVA_OPTS: "-Xmx8g -Dfile.encoding=UTF-8 -Djava.awt.headless=true -Dkotlin.daemon.jvm.options=-Xmx6g" + steps: + - uses: actions/checkout@v6 + + - name: Set up JDK 21 + uses: actions/setup-java@v5 + with: + java-version: '21' + distribution: 'temurin' + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '24.11.1' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + with: + add-job-summary: 'always' + cache-read-only: true + + - name: Run Conformance Tests + run: ./gradlew --no-daemon :conformance-test:test + + - name: Upload Conformance Results + if: ${{ !cancelled() }} + uses: actions/upload-artifact@v5 + with: + name: conformance-results + path: conformance-test/results/ diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml index d9b2801..bb81dbe 100644 --- a/.github/workflows/gradle-publish.yml +++ b/.github/workflows/gradle-publish.yml @@ -34,7 +34,7 @@ jobs: uses: gradle/actions/setup-gradle@v5 - name: Clean Build with Gradle - run: ./gradlew clean build + run: ./gradlew clean build -x :conformance-test:test - name: Publish to Maven Central Portal id: publish diff --git a/.gitignore b/.gitignore index ea62a95..a8c18dd 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,6 @@ dist ### SWE agents ### .claude/ .junie/ + +### Conformance test results ### +conformance-test/results/ diff --git a/conformance-test/build.gradle.kts b/conformance-test/build.gradle.kts new file mode 100644 index 0000000..66e57d1 --- /dev/null +++ b/conformance-test/build.gradle.kts @@ -0,0 +1,38 @@ +import org.gradle.api.tasks.testing.logging.TestExceptionFormat + +plugins { + kotlin("jvm") +} + +dependencies { + testImplementation(project(":kotlin-sdk")) + testImplementation(kotlin("test")) + testImplementation(libs.kotlin.logging) + testImplementation(libs.ktor.client.cio) + testImplementation(libs.ktor.server.cio) + testImplementation(libs.ktor.server.websockets) + testRuntimeOnly(libs.slf4j.simple) +} + +tasks.test { + useJUnitPlatform() + + testLogging { + events("passed", "skipped", "failed") + showStandardStreams = true + showExceptions = true + showCauses = true + showStackTraces = true + exceptionFormat = TestExceptionFormat.FULL + } + + doFirst { + systemProperty("test.classpath", classpath.asPath) + + println("\n" + "=".repeat(60)) + println("MCP CONFORMANCE TESTS") + println("=".repeat(60)) + println("These tests validate compliance with the MCP specification.") + println("=".repeat(60) + "\n") + } +} diff --git a/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceClient.kt b/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceClient.kt new file mode 100644 index 0000000..09ea50b --- /dev/null +++ b/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceClient.kt @@ -0,0 +1,85 @@ +package io.modelcontextprotocol.kotlin.sdk.conformance + +import io.github.oshai.kotlinlogging.KotlinLogging +import io.ktor.client.HttpClient +import io.ktor.client.engine.cio.CIO +import io.ktor.client.plugins.sse.SSE +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.client.StreamableHttpClientTransport +import io.modelcontextprotocol.kotlin.sdk.shared.Transport +import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest +import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.buildJsonObject + +private val logger = KotlinLogging.logger {} + +fun main(args: Array) { + require(args.isNotEmpty()) { + "Server URL must be provided as an argument" + } + + val serverUrl = args.last() + logger.info { "Connecting to test server at: $serverUrl" } + + val httpClient = HttpClient(CIO) { + install(SSE) + } + val transport: Transport = StreamableHttpClientTransport(httpClient, serverUrl) + + val client = Client( + clientInfo = Implementation( + name = "kotlin-conformance-client", + version = "1.0.0", + ), + ) + + var exitCode = 0 + + runBlocking { + try { + client.connect(transport) + logger.info { "✅ Connected to server successfully" } + + try { + val tools = client.listTools() + logger.info { "Available tools: ${tools.tools.map { it.name }}" } + + if (tools.tools.isNotEmpty()) { + val toolName = tools.tools.first().name + logger.info { "Calling tool: $toolName" } + + val result = client.callTool( + CallToolRequest( + params = CallToolRequestParams( + name = toolName, + arguments = buildJsonObject { + put("input", JsonPrimitive("test")) + }, + ), + ), + ) + logger.info { "Tool result: ${result.content}" } + } + } catch (e: Exception) { + logger.debug(e) { "Error during tool operations (may be expected for some scenarios)" } + } + + logger.info { "✅ Client operations completed successfully" } + } catch (e: Exception) { + logger.error(e) { "❌ Client failed" } + exitCode = 1 + } finally { + try { + transport.close() + } catch (e: Exception) { + logger.warn(e) { "Error closing transport" } + } + httpClient.close() + } + } + + kotlin.system.exitProcess(exitCode) +} diff --git a/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceServer.kt b/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceServer.kt new file mode 100644 index 0000000..01bdd40 --- /dev/null +++ b/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceServer.kt @@ -0,0 +1,422 @@ +package io.modelcontextprotocol.kotlin.sdk.conformance + +import io.github.oshai.kotlinlogging.KotlinLogging +import io.ktor.http.ContentType +import io.ktor.http.HttpStatusCode +import io.ktor.server.application.ApplicationCall +import io.ktor.server.application.install +import io.ktor.server.cio.CIO +import io.ktor.server.engine.embeddedServer +import io.ktor.server.request.header +import io.ktor.server.request.receiveText +import io.ktor.server.response.header +import io.ktor.server.response.respond +import io.ktor.server.response.respondText +import io.ktor.server.response.respondTextWriter +import io.ktor.server.routing.delete +import io.ktor.server.routing.get +import io.ktor.server.routing.post +import io.ktor.server.routing.routing +import io.ktor.server.websocket.WebSockets +import io.ktor.server.websocket.webSocket +import io.modelcontextprotocol.kotlin.sdk.server.Server +import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions +import io.modelcontextprotocol.kotlin.sdk.server.WebSocketMcpServerTransport +import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport +import io.modelcontextprotocol.kotlin.sdk.shared.TransportSendOptions +import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCError +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse +import io.modelcontextprotocol.kotlin.sdk.types.McpJson +import io.modelcontextprotocol.kotlin.sdk.types.PromptArgument +import io.modelcontextprotocol.kotlin.sdk.types.PromptMessage +import io.modelcontextprotocol.kotlin.sdk.types.RPCError +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult +import io.modelcontextprotocol.kotlin.sdk.types.RequestId +import io.modelcontextprotocol.kotlin.sdk.types.Role +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents +import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.launch +import kotlinx.coroutines.withTimeoutOrNull +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonNull +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.contentOrNull +import kotlinx.serialization.json.decodeFromJsonElement +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import java.util.UUID +import java.util.concurrent.ConcurrentHashMap + +private val logger = KotlinLogging.logger {} +private val serverTransports = ConcurrentHashMap() +private val jsonFormat = Json { ignoreUnknownKeys = true } + +private const val SESSION_CREATION_TIMEOUT_MS = 2000L +private const val REQUEST_TIMEOUT_MS = 10_000L +private const val MESSAGE_QUEUE_CAPACITY = 256 + +private fun isInitializeRequest(json: JsonElement): Boolean = + json is JsonObject && json["method"]?.jsonPrimitive?.contentOrNull == "initialize" + +fun main(args: Array) { + val port = args.getOrNull(0)?.toIntOrNull() ?: 3000 + + logger.info { "Starting MCP Conformance Server on port $port" } + + embeddedServer(CIO, port = port, host = "127.0.0.1") { + install(WebSockets) + + routing { + webSocket("/ws") { + logger.info { "WebSocket connection established" } + val transport = WebSocketMcpServerTransport(this) + val server = createConformanceServer() + + try { + server.createSession(transport) + } catch (e: Exception) { + logger.error(e) { "Error in WebSocket session" } + throw e + } + } + + get("/mcp") { + val sessionId = call.request.header("mcp-session-id") + ?: run { + call.respond(HttpStatusCode.BadRequest, "Missing mcp-session-id header") + return@get + } + val transport = serverTransports[sessionId] + ?: run { + call.respond(HttpStatusCode.BadRequest, "Invalid mcp-session-id") + return@get + } + transport.stream(call) + } + + post("/mcp") { + val sessionId = call.request.header("mcp-session-id") + val requestBody = call.receiveText() + + logger.debug { "Received request with sessionId: $sessionId" } + logger.trace { "Request body: $requestBody" } + + val jsonElement = try { + jsonFormat.parseToJsonElement(requestBody) + } catch (e: Exception) { + logger.error(e) { "Failed to parse request body as JSON" } + call.respond( + HttpStatusCode.BadRequest, + jsonFormat.encodeToString( + JsonObject.serializer(), + buildJsonObject { + put("jsonrpc", "2.0") + put( + "error", + buildJsonObject { + put("code", -32700) + put("message", "Parse error: ${e.message}") + }, + ) + put("id", JsonNull) + }, + ), + ) + return@post + } + + val transport = sessionId?.let { serverTransports[it] } + if (transport != null) { + logger.debug { "Using existing transport for session: $sessionId" } + transport.handleRequest(call, jsonElement) + } else { + if (isInitializeRequest(jsonElement)) { + val newSessionId = UUID.randomUUID().toString() + logger.info { "Creating new session with ID: $newSessionId" } + + val newTransport = HttpServerTransport(newSessionId) + serverTransports[newSessionId] = newTransport + + val mcpServer = createConformanceServer() + call.response.header("mcp-session-id", newSessionId) + + val sessionReady = CompletableDeferred() + CoroutineScope(Dispatchers.IO).launch { + try { + mcpServer.createSession(newTransport) + sessionReady.complete(Unit) + } catch (e: Exception) { + logger.error(e) { "Failed to create session" } + serverTransports.remove(newSessionId) + newTransport.close() + sessionReady.completeExceptionally(e) + } + } + + val sessionCreated = withTimeoutOrNull(SESSION_CREATION_TIMEOUT_MS) { + sessionReady.await() + } + + if (sessionCreated == null) { + logger.error { "Session creation timed out" } + serverTransports.remove(newSessionId) + call.respond( + HttpStatusCode.InternalServerError, + jsonFormat.encodeToString( + JsonObject.serializer(), + buildJsonObject { + put("jsonrpc", "2.0") + put( + "error", + buildJsonObject { + put("code", -32000) + put("message", "Session creation timed out") + }, + ) + put("id", JsonNull) + }, + ), + ) + return@post + } + + newTransport.handleRequest(call, jsonElement) + } else { + logger.warn { "Invalid request: no session ID or not an initialization request" } + call.respond( + HttpStatusCode.BadRequest, + jsonFormat.encodeToString( + JsonObject.serializer(), + buildJsonObject { + put("jsonrpc", "2.0") + put( + "error", + buildJsonObject { + put("code", -32000) + put("message", "Bad Request: No valid session ID provided") + }, + ) + put("id", JsonNull) + }, + ), + ) + } + } + } + + delete("/mcp") { + val sessionId = call.request.header("mcp-session-id") + val transport = sessionId?.let { serverTransports[it] } + if (transport != null) { + logger.info { "Terminating session: $sessionId" } + serverTransports.remove(sessionId) + transport.close() + call.respond(HttpStatusCode.OK) + } else { + logger.warn { "Invalid session termination request: $sessionId" } + call.respond(HttpStatusCode.BadRequest, "Invalid or missing session ID") + } + } + } + }.start(wait = true) +} + +private fun createConformanceServer(): Server { + val server = Server( + Implementation( + name = "kotlin-conformance-server", + version = "1.0.0", + ), + ServerOptions( + capabilities = ServerCapabilities( + tools = ServerCapabilities.Tools(listChanged = true), + resources = ServerCapabilities.Resources(subscribe = true, listChanged = true), + prompts = ServerCapabilities.Prompts(listChanged = true), + ), + ), + ) + + server.addTool( + name = "test-tool", + description = "A test tool for conformance testing", + inputSchema = ToolSchema( + properties = buildJsonObject { + put( + "input", + buildJsonObject { + put("type", "string") + put("description", "Test input parameter") + }, + ) + }, + required = listOf("input"), + ), + ) { request -> + val input = (request.params.arguments?.get("input") as? JsonPrimitive)?.content ?: "no input" + CallToolResult( + content = listOf(TextContent("Tool executed with input: $input")), + ) + } + + server.addResource( + uri = "test://test-resource", + name = "Test Resource", + description = "A test resource for conformance testing", + mimeType = "text/plain", + ) { request -> + ReadResourceResult( + contents = listOf( + TextResourceContents("Test resource content", request.params.uri, "text/plain"), + ), + ) + } + + server.addPrompt( + name = "test-prompt", + description = "A test prompt for conformance testing", + arguments = listOf( + PromptArgument( + name = "arg", + description = "Test argument", + required = false, + ), + ), + ) { + GetPromptResult( + messages = listOf( + PromptMessage( + role = Role.User, + content = TextContent("Test prompt content"), + ), + ), + description = "Test prompt description", + ) + } + + return server +} + +private class HttpServerTransport(private val sessionId: String) : AbstractTransport() { + private val logger = KotlinLogging.logger {} + private val pendingResponses = ConcurrentHashMap>() + private val messageQueue = Channel(MESSAGE_QUEUE_CAPACITY) + + suspend fun stream(call: ApplicationCall) { + logger.debug { "Starting SSE stream for session $sessionId" } + call.response.apply { + header("Cache-Control", "no-cache") + header("Connection", "keep-alive") + } + call.respondTextWriter(ContentType.Text.EventStream) { + try { + while (true) { + val msg = messageQueue.receiveCatching().getOrNull() ?: break + write("event: message\ndata: ${McpJson.encodeToString(msg)}\n\n") + flush() + } + } catch (e: Exception) { + logger.warn(e) { "SSE stream terminated for session $sessionId" } + } finally { + logger.debug { "SSE stream closed for session $sessionId" } + } + } + } + + suspend fun handleRequest(call: ApplicationCall, requestBody: JsonElement) { + try { + val message = McpJson.decodeFromJsonElement(requestBody) + logger.debug { "Handling ${message::class.simpleName}: $requestBody" } + + when (message) { + is JSONRPCRequest -> { + val idKey = when (val id = message.id) { + is RequestId.NumberId -> id.value.toString() + is RequestId.StringId -> id.value + } + val responseDeferred = CompletableDeferred() + pendingResponses[idKey] = responseDeferred + + _onMessage.invoke(message) + + val response = withTimeoutOrNull(REQUEST_TIMEOUT_MS) { responseDeferred.await() } + if (response != null) { + call.respondText(McpJson.encodeToString(response), ContentType.Application.Json) + } else { + pendingResponses.remove(idKey) + logger.warn { "Timeout for request $idKey" } + call.respondText( + McpJson.encodeToString( + JSONRPCError( + message.id, + RPCError(RPCError.ErrorCode.REQUEST_TIMEOUT, "Request timed out"), + ), + ), + ContentType.Application.Json, + ) + } + } + + else -> call.respond(HttpStatusCode.Accepted) + } + } catch (e: CancellationException) { + throw e + } catch (e: Exception) { + logger.error(e) { "Error handling request" } + if (!call.response.isCommitted) { + call.respondText( + McpJson.encodeToString( + JSONRPCError( + RequestId(0), + RPCError(RPCError.ErrorCode.INTERNAL_ERROR, "Internal error: ${e.message}"), + ), + ), + ContentType.Application.Json, + HttpStatusCode.InternalServerError, + ) + } + } + } + + override suspend fun start() { + logger.debug { "Started transport for session $sessionId" } + } + + override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { + when (message) { + is JSONRPCResponse -> { + val idKey = when (val id = message.id) { + is RequestId.NumberId -> id.value.toString() + is RequestId.StringId -> id.value + } + pendingResponses.remove(idKey)?.complete(message) ?: run { + logger.warn { "No pending response for ID $idKey, queueing" } + messageQueue.send(message) + } + } + + else -> messageQueue.send(message) + } + } + + override suspend fun close() { + logger.debug { "Closing transport for session $sessionId" } + messageQueue.close() + pendingResponses.clear() + _onClose.invoke() + } +} diff --git a/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTest.kt b/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTest.kt new file mode 100644 index 0000000..4137943 --- /dev/null +++ b/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTest.kt @@ -0,0 +1,328 @@ +package io.modelcontextprotocol.kotlin.sdk.conformance + +import io.github.oshai.kotlinlogging.KotlinLogging +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.DynamicTest +import org.junit.jupiter.api.TestFactory +import org.junit.jupiter.api.TestInstance +import java.io.BufferedReader +import java.io.InputStreamReader +import java.lang.management.ManagementFactory +import java.net.HttpURLConnection +import java.net.ServerSocket +import java.net.URI +import java.util.concurrent.TimeUnit +import kotlin.properties.Delegates + +private val logger = KotlinLogging.logger {} + +enum class TransportType { + SSE, + WEBSOCKET, +} + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class ConformanceTest { + + private var serverProcess: Process? = null + private var serverPort: Int by Delegates.notNull() + private val serverErrorOutput = mutableListOf() + private val maxErrorLines = 500 + + companion object { + private val SERVER_SCENARIOS = listOf( + "server-initialize", + "tools-list", + "tools-call-simple-text", + "resources-list", + "prompts-list", + // TODO: Fix + // - resources-read-text + // - prompts-get-simple + ) + + private val CLIENT_SCENARIOS = listOf( + "initialize", + // TODO: Fix + // "tools-call", + ) + + private val SERVER_TRANSPORT_TYPES = listOf( + TransportType.SSE, + // TODO: Fix +// TransportType.WEBSOCKET, + ) + + private val CLIENT_TRANSPORT_TYPES = listOf( + TransportType.SSE, + TransportType.WEBSOCKET, + ) + + private const val DEFAULT_TEST_TIMEOUT_SECONDS = 30L + private const val DEFAULT_SERVER_STARTUP_TIMEOUT_SECONDS = 10 + private const val INITIAL_BACKOFF_MS = 50L + private const val MAX_BACKOFF_MS = 500L + private const val BACKOFF_MULTIPLIER = 1.5 + private const val CONNECTION_TIMEOUT_MS = 500 + private const val GRACEFUL_SHUTDOWN_SECONDS = 5L + private const val FORCE_SHUTDOWN_SECONDS = 2L + + private fun findFreePort(): Int = ServerSocket(0).use { it.localPort } + + private fun getRuntimeClasspath(): String = ManagementFactory.getRuntimeMXBean().classPath + + private fun getTestClasspath(): String = System.getProperty("test.classpath") ?: getRuntimeClasspath() + + private fun waitForServerReady( + url: String, + timeoutSeconds: Int = DEFAULT_SERVER_STARTUP_TIMEOUT_SECONDS, + ): Boolean { + val deadline = System.currentTimeMillis() + (timeoutSeconds * 1000) + var lastError: Exception? = null + var backoffMs = INITIAL_BACKOFF_MS + + while (System.currentTimeMillis() < deadline) { + try { + val connection = URI(url).toURL().openConnection() as HttpURLConnection + connection.requestMethod = "GET" + connection.connectTimeout = CONNECTION_TIMEOUT_MS + connection.readTimeout = CONNECTION_TIMEOUT_MS + connection.connect() + + val responseCode = connection.responseCode + connection.disconnect() + logger.debug { "Server responded with code: $responseCode" } + return true + } catch (e: Exception) { + lastError = e + Thread.sleep(backoffMs) + backoffMs = (backoffMs * BACKOFF_MULTIPLIER).toLong().coerceAtMost(MAX_BACKOFF_MS) + } + } + + logger.error { "Server did not start within $timeoutSeconds seconds. Last error: ${lastError?.message}" } + return false + } + } + + @BeforeAll + fun startServer() { + serverPort = findFreePort() + val serverUrl = "http://127.0.0.1:$serverPort/mcp" + + logger.info { "Starting conformance test server on port $serverPort" } + + val processBuilder = ProcessBuilder( + "java", + "-cp", + getRuntimeClasspath(), + "io.modelcontextprotocol.kotlin.sdk.conformance.ConformanceServerKt", + serverPort.toString(), + ) + + val process = processBuilder.start() + serverProcess = process + + // capture stderr in the background + Thread { + try { + BufferedReader(InputStreamReader(process.errorStream)).use { reader -> + reader.lineSequence().forEach { line -> + synchronized(serverErrorOutput) { + if (serverErrorOutput.size >= maxErrorLines) { + serverErrorOutput.removeAt(0) + } + serverErrorOutput.add(line) + } + logger.debug { "Server stderr: $line" } + } + } + } catch (e: Exception) { + logger.trace(e) { "Error reading server stderr" } + } + }.apply { + name = "server-stderr-reader" + isDaemon = true + }.start() + + logger.info { "Waiting for server to start..." } + val serverReady = waitForServerReady(serverUrl) + + if (!serverReady) { + val errorInfo = synchronized(serverErrorOutput) { + if (serverErrorOutput.isNotEmpty()) { + "\n\nServer error output:\n${serverErrorOutput.joinToString("\n")}" + } else { + "" + } + } + serverProcess?.destroyForcibly() + throw IllegalStateException( + "Server failed to start within $DEFAULT_SERVER_STARTUP_TIMEOUT_SECONDS seconds. " + + "Check if port $serverPort is available.$errorInfo", + ) + } + + logger.info { "Server started successfully at $serverUrl" } + } + + @AfterAll + fun stopServer() { + serverProcess?.also { process -> + logger.info { "Stopping conformance test server (PID: ${process.pid()})" } + + try { + process.destroy() + val terminated = process.waitFor(GRACEFUL_SHUTDOWN_SECONDS, TimeUnit.SECONDS) + + if (!terminated) { + logger.warn { "Server did not terminate gracefully, forcing shutdown..." } + process.destroyForcibly() + process.waitFor(FORCE_SHUTDOWN_SECONDS, TimeUnit.SECONDS) + } else { + logger.info { "Server stopped gracefully" } + } + } catch (e: Exception) { + logger.error(e) { "Error stopping server process" } + } finally { + serverProcess = null + } + } ?: logger.debug { "No server process to stop" } + } + + @TestFactory + fun `MCP Server Conformance Tests`(): List = SERVER_TRANSPORT_TYPES.flatMap { transportType -> + SERVER_SCENARIOS.map { scenario -> + DynamicTest.dynamicTest("Server [$transportType]: $scenario") { + runServerConformanceTest(scenario, transportType) + } + } + } + + @TestFactory + fun `MCP Client Conformance Tests`(): List = CLIENT_TRANSPORT_TYPES.flatMap { transportType -> + CLIENT_SCENARIOS.map { scenario -> + DynamicTest.dynamicTest("Client [$transportType]: $scenario") { + runClientConformanceTest(scenario, transportType) + } + } + } + + private fun runServerConformanceTest(scenario: String, transportType: TransportType) { + val processBuilder = when (transportType) { + TransportType.SSE -> { + val serverUrl = "http://127.0.0.1:$serverPort/mcp" + ProcessBuilder( + "npx", + "@modelcontextprotocol/conformance", + "server", + "--url", + serverUrl, + "--scenario", + scenario, + ).apply { + inheritIO() + } + } + + TransportType.WEBSOCKET -> { + val serverUrl = "ws://127.0.0.1:$serverPort/ws" + ProcessBuilder( + "npx", + "@modelcontextprotocol/conformance", + "server", + "--url", + serverUrl, + "--scenario", + scenario, + ).apply { + inheritIO() + } + } + } + + runConformanceTest("server", scenario, processBuilder, transportType) + } + + private fun runClientConformanceTest(scenario: String, transportType: TransportType) { + val testClasspath = getTestClasspath() + + val clientCommand = when (transportType) { + TransportType.SSE -> { + val serverUrl = "http://127.0.0.1:$serverPort/mcp" + listOf( + "java", + "-cp", + testClasspath, + "io.modelcontextprotocol.kotlin.sdk.conformance.ConformanceClientKt", + serverUrl, + ) + } + + TransportType.WEBSOCKET -> { + val serverUrl = "ws://127.0.0.1:$serverPort/ws" + listOf( + "java", + "-cp", + testClasspath, + "io.modelcontextprotocol.kotlin.sdk.conformance.WebSocketConformanceClientKt", + serverUrl, + ) + } + } + + val processBuilder = ProcessBuilder( + "npx", + "@modelcontextprotocol/conformance", + "client", + "--command", + clientCommand.joinToString(" "), + "--scenario", + scenario, + ).apply { + inheritIO() + } + + runConformanceTest("client", scenario, processBuilder, transportType) + } + + private fun runConformanceTest( + type: String, + scenario: String, + processBuilder: ProcessBuilder, + transportType: TransportType, + ) { + val capitalizedType = type.replaceFirstChar { it.uppercase() } + logger.info { "Running $type conformance test [$transportType]: $scenario" } + + val timeoutSeconds = System.getenv("CONFORMANCE_TEST_TIMEOUT_SECONDS")?.toLongOrNull() + ?: DEFAULT_TEST_TIMEOUT_SECONDS + + val process = processBuilder.start() + val completed = process.waitFor(timeoutSeconds, TimeUnit.SECONDS) + + if (!completed) { + logger.error { + "$capitalizedType conformance test [$transportType] '$scenario' timed out after $timeoutSeconds seconds" + } + process.destroyForcibly() + throw AssertionError( + "❌ $capitalizedType conformance test [$transportType] '$scenario' timed out after $timeoutSeconds seconds", + ) + } + + when (val exitCode = process.exitValue()) { + 0 -> logger.info { "✅ $capitalizedType conformance test [$transportType] '$scenario' passed!" } + + else -> { + logger.error { + "$capitalizedType conformance test [$transportType] '$scenario' failed with exit code: $exitCode" + } + throw AssertionError( + "❌ $capitalizedType conformance test [$transportType] '$scenario' failed (exit code: $exitCode). Check test output above for details.", + ) + } + } + } +} diff --git a/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/WebSocketConformanceClient.kt b/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/WebSocketConformanceClient.kt new file mode 100644 index 0000000..f112319 --- /dev/null +++ b/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/WebSocketConformanceClient.kt @@ -0,0 +1,104 @@ +package io.modelcontextprotocol.kotlin.sdk.conformance + +import io.github.oshai.kotlinlogging.KotlinLogging +import io.ktor.client.HttpClient +import io.ktor.client.engine.cio.CIO +import io.ktor.client.plugins.websocket.WebSockets +import io.ktor.client.plugins.websocket.webSocket +import io.ktor.websocket.WebSocketSession +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.shared.MCP_SUBPROTOCOL +import io.modelcontextprotocol.kotlin.sdk.shared.WebSocketMcpTransport +import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest +import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.buildJsonObject + +private val logger = KotlinLogging.logger {} + +class WebSocketClientTransport(override val session: WebSocketSession) : WebSocketMcpTransport() { + override suspend fun initializeSession() { + logger.debug { "WebSocket client session initialized" } + } +} + +fun main(args: Array) { + require(args.isNotEmpty()) { + "Server WebSocket URL must be provided as an argument" + } + + val serverUrl = args.last() + logger.info { "Connecting to WebSocket test server at: $serverUrl" } + + val httpClient = HttpClient(CIO) { + install(WebSockets) + } + + var exitCode = 0 + + runBlocking { + try { + httpClient.webSocket(serverUrl, request = { + headers.append("Sec-WebSocket-Protocol", MCP_SUBPROTOCOL) + }) { + val transport = WebSocketClientTransport(this) + + val client = Client( + clientInfo = Implementation( + name = "kotlin-conformance-client-websocket", + version = "1.0.0", + ), + ) + + try { + client.connect(transport) + logger.info { "✅ Connected to server successfully" } + + try { + val tools = client.listTools() + logger.info { "Available tools: ${tools.tools.map { it.name }}" } + + if (tools.tools.isNotEmpty()) { + val toolName = tools.tools.first().name + logger.info { "Calling tool: $toolName" } + + val result = client.callTool( + CallToolRequest( + params = CallToolRequestParams( + name = toolName, + arguments = buildJsonObject { + put("input", JsonPrimitive("test")) + }, + ), + ), + ) + logger.info { "Tool result: ${result.content}" } + } + } catch (e: Exception) { + logger.debug(e) { "Error during tool operations (may be expected for some scenarios)" } + } + + logger.info { "✅ Client operations completed successfully" } + } catch (e: Exception) { + logger.error(e) { "❌ Client failed" } + exitCode = 1 + } finally { + try { + transport.close() + } catch (e: Exception) { + logger.warn(e) { "Error closing transport" } + } + } + } + } catch (e: Exception) { + logger.error(e) { "❌ WebSocket connection failed" } + exitCode = 1 + } finally { + httpClient.close() + } + } + + kotlin.system.exitProcess(exitCode) +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 368bff3..d7ec54f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -23,4 +23,5 @@ include( ":kotlin-sdk-server", ":kotlin-sdk", ":kotlin-sdk-test", + ":conformance-test", ) From e9658c261aab3a6e92fdda2a159c30afe9ffff55 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 20:10:29 +0200 Subject: [PATCH 087/449] Bump kotest from 6.0.4 to 6.0.7 (#439) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `kotest` from 6.0.4 to 6.0.7. Updates `io.kotest:kotest-assertions-core` from 6.0.4 to 6.0.7
Release notes

Sourced from io.kotest:kotest-assertions-core's releases.

v6.0.7

What's Changed

Full Changelog: https://github.com/kotest/kotest/compare/v6.0.6...v6.0.7

v6.0.6

What's Changed

Full Changelog: https://github.com/kotest/kotest/compare/v6.0.5...v6.0.6

v6.0.5

What's Changed

... (truncated)

Commits
  • 3923053 Update dependency io.mockk:mockk to v1.14.6 (#5237)
  • 014c93b Update dependency io.projectreactor.tools:blockhound to v1.0.15.RELEASE (#5238)
  • a656dfa Update dependency io.github.pdvrieze.xmlutil:serialization to v0.91.3 (#5236)
  • 521bd24 Update dependency com.google.jimfs:jimfs to v1.3.1 (#5235)
  • 07905bf Update default FQN config test to fix flakey test
  • d25eef2 Revert "Removing deprecated EqualityMatcherResult"
  • b430cf3 Removing deprecated EqualityMatcherResult
  • 15fde9d Revert "Removing ComparisonMatcherResult"
  • e376772 Make mingwX64 always included in native conventions instead of OS-based con...
  • 2b666d8 Removing ComparisonMatcherResult
  • Additional commits viewable in compare view

Updates `io.kotest:kotest-assertions-json` from 6.0.4 to 6.0.7
Release notes

Sourced from io.kotest:kotest-assertions-json's releases.

v6.0.7

What's Changed

Full Changelog: https://github.com/kotest/kotest/compare/v6.0.6...v6.0.7

v6.0.6

What's Changed

Full Changelog: https://github.com/kotest/kotest/compare/v6.0.5...v6.0.6

v6.0.5

What's Changed

... (truncated)

Commits
  • 3923053 Update dependency io.mockk:mockk to v1.14.6 (#5237)
  • 014c93b Update dependency io.projectreactor.tools:blockhound to v1.0.15.RELEASE (#5238)
  • a656dfa Update dependency io.github.pdvrieze.xmlutil:serialization to v0.91.3 (#5236)
  • 521bd24 Update dependency com.google.jimfs:jimfs to v1.3.1 (#5235)
  • 07905bf Update default FQN config test to fix flakey test
  • d25eef2 Revert "Removing deprecated EqualityMatcherResult"
  • b430cf3 Removing deprecated EqualityMatcherResult
  • 15fde9d Revert "Removing ComparisonMatcherResult"
  • e376772 Make mingwX64 always included in native conventions instead of OS-based con...
  • 2b666d8 Removing ComparisonMatcherResult
  • Additional commits viewable in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9ad38f9..8cdc69a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,7 +18,7 @@ kotlinx-io = "0.8.1" ktor = "3.2.3" logging = "7.0.13" slf4j = "2.0.17" -kotest = "6.0.4" +kotest = "6.0.7" awaitility = "4.3.0" mokksy = "0.6.2" From cc9b50ff760704c31839dd37e4e3a4f8b3307d3c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 21:42:25 +0200 Subject: [PATCH 088/449] Bump org.jetbrains.kotlinx:kotlinx-io-core from 0.8.1 to 0.8.2 (#440) Bumps [org.jetbrains.kotlinx:kotlinx-io-core](https://github.com/Kotlin/kotlinx-io) from 0.8.1 to 0.8.2.
Release notes

Sourced from org.jetbrains.kotlinx:kotlinx-io-core's releases.

v0.8.2

Bugfixes

  • Fixed the issue with Webpack not being able to process module import introduced in 0.8.1 #483
Changelog

Sourced from org.jetbrains.kotlinx:kotlinx-io-core's changelog.

0.8.2

Published 24 November 2025

Bugfixes

  • Fixed the issue with Webpack not being able to process module import introduced in 0.8.1 #483
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.jetbrains.kotlinx:kotlinx-io-core&package-manager=gradle&previous-version=0.8.1&new-version=0.8.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8cdc69a..12d8b37 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,7 +14,7 @@ openapi-generator = "7.17.0" serialization = "1.9.0" collections-immutable = "0.4.0" coroutines = "1.10.2" -kotlinx-io = "0.8.1" +kotlinx-io = "0.8.2" ktor = "3.2.3" logging = "7.0.13" slf4j = "2.0.17" From 4645ebfd17286e3028470e9e0d227f7efe494433 Mon Sep 17 00:00:00 2001 From: Maria Tigina <31625351+tiginamaria@users.noreply.github.com> Date: Wed, 3 Dec 2025 16:38:33 +0100 Subject: [PATCH 089/449] Server feature list changed and resource updated notifications (#441) ## Motivation and Context Implemented thread-safe notification service following eventual consistency principle. Fixes https://github.com/modelcontextprotocol/kotlin-sdk/issues/249 Contracts: * If the client connection and feature update happened in the same thread, the timestamps of the events will be consequent ``` server.connect(client) server.add(feature) // client will receive the notification ``` ``` server.add(feature) server.connect(client) // client will NOT receive the notification ``` * If the client connection and feature update happened in the different threads, both options can be possible The flow of the notification is explained in the diagram: image Documentation references: https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#list-changed-notification https://modelcontextprotocol.io/specification/2025-06-18/server/tools#list-changed-notification https://modelcontextprotocol.io/specification/2025-06-18/server/resources#list-changed-notification https://modelcontextprotocol.io/specification/2025-06-18/server/resources#subscriptions Questions: * I haven't found the subscription confirmed response message content docs, how it should look like? ## How Has This Been Tested? Multiple tests with various notification scenarios were added ## Breaking Changes ## 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed ## Additional context --- kotlin-sdk-server/api/kotlin-sdk-server.api | 4 + .../sdk/server/{Features.kt => Feature.kt} | 0 .../sdk/server/FeatureNotificationService.kt | 373 ++++++++++++++++++ .../kotlin/sdk/server/FeatureRegistry.kt | 86 ++-- .../kotlin/sdk/server/Server.kt | 129 +++++- .../sdk/integration/InMemoryTransportTest.kt | 96 +++++ .../server/ServerPromptsNotificationTest.kt | 116 ++++++ .../kotlin/sdk/server/ServerPromptsTest.kt | 48 ++- ...erverResourcesNotificationSubscribeTest.kt | 186 +++++++++ .../server/ServerResourcesNotificationTest.kt | 150 +++++++ .../kotlin/sdk/server/ServerResourcesTest.kt | 4 +- .../sdk/server/ServerToolsNotificationTest.kt | 100 +++++ .../kotlin/sdk/server/ServerToolsTest.kt | 75 +++- 13 files changed, 1307 insertions(+), 60 deletions(-) rename kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/{Features.kt => Feature.kt} (100%) create mode 100644 kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/FeatureNotificationService.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsNotificationTest.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationSubscribeTest.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationTest.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsNotificationTest.kt diff --git a/kotlin-sdk-server/api/kotlin-sdk-server.api b/kotlin-sdk-server/api/kotlin-sdk-server.api index 8016d3b..e5183f0 100644 --- a/kotlin-sdk-server/api/kotlin-sdk-server.api +++ b/kotlin-sdk-server/api/kotlin-sdk-server.api @@ -82,6 +82,8 @@ public class io/modelcontextprotocol/kotlin/sdk/server/Server { public final fun onConnect (Lkotlin/jvm/functions/Function0;)V public final fun onInitialized (Lkotlin/jvm/functions/Function0;)V public final fun ping (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun removeNotificationHandler (Lio/modelcontextprotocol/kotlin/sdk/types/Method;)V + public final fun removeNotificationHandler (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Method;)V public final fun removePrompt (Ljava/lang/String;)Z public final fun removePrompts (Ljava/util/List;)I public final fun removeResource (Ljava/lang/String;)Z @@ -93,6 +95,8 @@ public class io/modelcontextprotocol/kotlin/sdk/server/Server { public final fun sendResourceListChanged (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun sendResourceUpdated (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotification;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun sendToolListChanged (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun setNotificationHandler (Lio/modelcontextprotocol/kotlin/sdk/types/Method;Lkotlin/jvm/functions/Function1;)V + public final fun setNotificationHandler (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Method;Lkotlin/jvm/functions/Function1;)V } public final class io/modelcontextprotocol/kotlin/sdk/server/ServerOptions : io/modelcontextprotocol/kotlin/sdk/shared/ProtocolOptions { diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Features.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Feature.kt similarity index 100% rename from kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Features.kt rename to kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Feature.kt diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/FeatureNotificationService.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/FeatureNotificationService.kt new file mode 100644 index 0000000..397e6e1 --- /dev/null +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/FeatureNotificationService.kt @@ -0,0 +1,373 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.github.oshai.kotlinlogging.KotlinLogging +import io.modelcontextprotocol.kotlin.sdk.types.Notification +import io.modelcontextprotocol.kotlin.sdk.types.PromptListChangedNotification +import io.modelcontextprotocol.kotlin.sdk.types.ResourceListChangedNotification +import io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotification +import io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotificationParams +import io.modelcontextprotocol.kotlin.sdk.types.ServerNotification +import io.modelcontextprotocol.kotlin.sdk.types.ToolListChangedNotification +import kotlinx.atomicfu.atomic +import kotlinx.atomicfu.getAndUpdate +import kotlinx.collections.immutable.persistentMapOf +import kotlinx.collections.immutable.persistentSetOf +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.takeWhile +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.launch +import kotlin.time.Clock +import kotlin.time.ExperimentalTime + +/** + * Represents an event for the notification service. + * + * @property timestamp A timestamp for the event. + */ +private sealed class NotificationEvent(open val timestamp: Long) + +/** + * Represents an event for a notification. + * + * @property notification The notification associated with the event. + */ +private class SendEvent(override val timestamp: Long, val notification: Notification) : NotificationEvent(timestamp) + +/** Represents an event marking the end of notification processing. */ +private class EndEvent(override val timestamp: Long) : NotificationEvent(timestamp) + +/** + * Represents a job that handles session-specific notifications, processing events + * and delivering relevant notifications to the associated session. + * + * This class listens to a stream of notification events and processes them + * based on the event type and the resource subscriptions associated with the session. + * It allows subscribing to or unsubscribing from specific resource keys for granular + * notification handling. The job can also be canceled to stop processing further events. + * Notification with timestamps older than the starting timestamp are skipped. + */ +private class SessionNotificationJob { + private val job: Job + private val resourceSubscriptions = atomic(persistentMapOf()) + private val logger = KotlinLogging.logger {} + + /** + * Constructor for the SessionNotificationJob, responsible for processing notification events + * and dispatching appropriate notifications to the provided server session. The job operates + * within the given coroutine scope and begins handling events starting from the specified + * timestamp. + * + * @param session The server session where notifications will be dispatched. + * @param scope The coroutine scope in which this job operates. + * @param events A shared flow of notification events that the job listens to. + * @param fromTimestamp The timestamp from which the job starts processing events. + */ + constructor( + session: ServerSession, + scope: CoroutineScope, + events: SharedFlow, + fromTimestamp: Long, + ) { + logger.info { "Starting notification job from timestamp $fromTimestamp for sessionId: ${session.sessionId} " } + job = scope.launch { + events.takeWhile { it !is EndEvent }.collect { event -> + when (event) { + is SendEvent -> { + handleSendNotificationEvent(event, session, fromTimestamp) + } + + else -> { + logger.warn { "Skipping event: $event" } + } + } + } + } + } + + /** + * Handles sending a notification event to a specific server session. + * + * @param event The notification event to be processed. + * @param session The server session to which the notification should be sent. + * @param fromTimestamp The timestamp to filter events. + * Notifications with timestamps older than this value are skipped. + */ + private suspend fun handleSendNotificationEvent(event: SendEvent, session: ServerSession, fromTimestamp: Long) { + if (event.timestamp < fromTimestamp) { + logger.info { + "Skipping event with id: ${event.timestamp} as it is older than startingEventId $fromTimestamp: $event" + } + return + } + when (val notification = event.notification) { + is PromptListChangedNotification, + is ResourceListChangedNotification, + is ToolListChangedNotification, + -> { + logger.info { "Sending list changed notification for sessionId: ${session.sessionId}" } + session.notification(notification) + } + + is ResourceUpdatedNotification -> { + resourceSubscriptions.value[notification.params.uri]?.let { resourceFromTimestamp -> + if (event.timestamp >= resourceFromTimestamp) { + logger.info { + "Sending resource updated notification for resource ${notification.params.uri} " + + "to sessionId: ${session.sessionId}" + } + session.notification(notification) + } else { + logger.info { + "Skipping resource updated notification for resource ${notification.params.uri} " + + "as it is older than subscription timestamp $resourceFromTimestamp" + } + } + } ?: { + logger.info { + "No subscription for resource ${notification.params.uri}. " + + "Skipping notification: $notification" + } + } + } + + else -> { + logger.warn { "Skipping notification: $notification" } + } + } + } + + /** + * Subscribes to a resource identified by the given feature key. + * + * @param resourceKey The key representing the resource to subscribe to. + * @param timestamp The timestamp of the subscription. + */ + fun subscribe(resourceKey: FeatureKey, timestamp: Long) { + resourceSubscriptions.getAndUpdate { it.put(resourceKey, timestamp) } + } + + /** + * Unsubscribes from a resource identified by the given feature key. + * + * @param resourceKey The key representing the resource to unsubscribe from. + */ + fun unsubscribe(resourceKey: FeatureKey) { + resourceSubscriptions.getAndUpdate { it.remove(resourceKey) } + } + + /** + * Waits for the notification service to complete its operations. + */ + suspend fun join() { + job.join() + } + + /** + * Cancels the notification service job. + */ + fun cancel() { + job.cancel() + } +} + +/** + * Service responsible for managing and emitting notifications related to feature changes. + * + * This service facilitates notification subscriptions for different sessions and supports managing + * listeners for feature-related events. Notifications include changes in tool lists, prompt lists, + * resource lists, and updates to specific resources. + * + * This class operates on a background coroutine scope to handle notifications asynchronously. + * It maintains jobs associated with sessions and features for controlling active subscriptions. + * + * Key Responsibilities: + * - Emit notifications for various feature-related events. + * - Provide listeners for handling feature change events. + * - Allow clients to subscribe or unsubscribe from specific notifications. + * + * Notifications managed: + * - Tool list change notifications. + * - Prompt list change notifications. + * - Resource list change notifications. + * - Resource updates pertaining to specific resources. + */ +internal class FeatureNotificationService( + notificationBufferCapacity: Int = Channel.UNLIMITED, + @OptIn(ExperimentalTime::class) + private val clock: Clock = Clock.System, +) { + /** Coroutine scope used to handle asynchronous notifications. */ + private val notificationScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) + + /** Shared flow used to emit events within the feature notification service. */ + private val notificationEvents = MutableSharedFlow( + extraBufferCapacity = notificationBufferCapacity, + onBufferOverflow = BufferOverflow.SUSPEND, + ) + + /** Active emit jobs. */ + private val emitJobs = atomic(persistentSetOf()) + + /** Notification jobs associated with sessions. */ + private val sessionNotificationJobs = atomic(persistentMapOf()) + + private val logger = KotlinLogging.logger {} + + private fun featureListener(notificationProvider: (FeatureKey) -> ServerNotification): FeatureListener = + object : FeatureListener { + override fun onFeatureUpdated(featureKey: FeatureKey) { + val notification = notificationProvider(featureKey) + logger.debug { "Emitting notification: ${notification.method.value}" } + emit(notification) + } + } + + /** Listener for tool feature events. */ + internal val toolListChangedListener: FeatureListener = featureListener { ToolListChangedNotification() } + + /** Listener for prompt feature events. */ + internal val promptListChangedListener: FeatureListener = featureListener { PromptListChangedNotification() } + + /** Listener for resource feature events. */ + internal val resourceListChangedListener: FeatureListener = featureListener { ResourceListChangedNotification() } + + /** Listener for resource update events. */ + internal val resourceUpdatedListener: FeatureListener = + featureListener { ResourceUpdatedNotification(ResourceUpdatedNotificationParams(uri = it)) } + + /** + * Subscribes session to list changed notifications for all features and resource update notifications. + * For each session the job is created and stored until the [unsubscribeSession] method is called. + * In case of session already subscribed to list changed notifications, the method will skip the subscription and + * continue to send notification using the existing job. + * + * @param session The session to subscribe. + */ + internal fun subscribeSession(session: ServerSession) { + logger.info { "Subscribing session for notifications sessionId: ${session.sessionId}" } + + val timestamp = getCurrentTimestamp() + + sessionNotificationJobs.getAndUpdate { + if (it.containsKey(session.sessionId)) { + logger.info { "Session already subscribed: ${session.sessionId}" } + return@getAndUpdate it + } else { + it.put( + session.sessionId, + SessionNotificationJob( + session = session, + scope = notificationScope, + events = notificationEvents, + // Save the first event id to process, as notification can be emitted after the subscription + fromTimestamp = timestamp, + ), + ) + } + } + + logger.info { "Subscribed session for notifications sessionId: ${session.sessionId}" } + } + + /** + * Unsubscribes a session from list changed notifications for all features. + * Cancels and removes the job associated with the given session's notifications. + * + * @param session The session to unsubscribe from list changed notifications. + */ + internal fun unsubscribeSession(session: ServerSession) { + logger.info { "Unsubscribing from list changed notifications for sessionId: ${session.sessionId}" } + sessionNotificationJobs.getAndUpdate { + it[session.sessionId]?.cancel() + it.remove(session.sessionId) + } + logger.info { "Unsubscribed from list changed notifications for sessionId: ${session.sessionId}" } + } + + /** + * Subscribes a session to notifications for resource updates pertaining to the given resource key. + * + * @param session The session to subscribe. + * @param resourceKey The resource key to subscribe to. + */ + internal fun subscribeToResourceUpdate(session: ServerSession, resourceKey: FeatureKey) { + logger.info { "Subscribing to resource $resourceKey update notifications for sessionId: ${session.sessionId}" } + // Set starting event id for resources notifications to skip events emitted before the subscription + sessionNotificationJobs.value[session.sessionId]?.subscribe(resourceKey, getCurrentTimestamp()) + logger.info { "Subscribed to resource $resourceKey update notifications for sessionId: ${session.sessionId}" } + } + + /** + * Unsubscribes a session from notifications for resource updates pertaining to the given resource key. + * + * @param session The session to unsubscribe from. + * @param resourceKey The resource key to unsubscribe from. + */ + internal fun unsubscribeFromResourceUpdate(session: ServerSession, resourceKey: FeatureKey) { + logger.info { + "Unsubscribing from resource $resourceKey update notifications for sessionId: ${session.sessionId}" + } + sessionNotificationJobs.value[session.sessionId]?.unsubscribe(resourceKey) + logger.info { + "Unsubscribed from resource $resourceKey update notifications for sessionId: ${session.sessionId}" + } + } + + /** Emits a notification to all active sessions. */ + private fun emit(notification: Notification) { + // Create a timestamp before emit to ensure notifications are processed in order + val timestamp = getCurrentTimestamp() + + logger.info { "Emitting notification $timestamp: $notification" } + + // Launching emit lazily to put it to the jobs queue before the completion + val job = notificationScope.launch(start = CoroutineStart.LAZY) { + logger.debug { "Actually emitting notification $timestamp: $notification" } + notificationEvents.emit(SendEvent(timestamp, notification)) + logger.debug { "Notification emitted $timestamp: $notification" } + } + + // Add job to set before starting + emitJobs.getAndUpdate { it.add(job) } + + // Register completion + job.invokeOnCompletion { + emitJobs.getAndUpdate { it.remove(job) } + } + + // Start the job after it's safely added + job.start() + } + + /** Returns the current timestamp in milliseconds. */ + @OptIn(ExperimentalTime::class) + private fun getCurrentTimestamp(): Long = clock.now().toEpochMilliseconds() + + suspend fun close() { + logger.info { "Closing feature notification service" } + + // Making sure all emitting jobs are completed + emitJobs.value.joinAll() + + // Emitting end event to complete all session notification jobs + notificationScope.launch { + logger.info { "Emitting end event" } + notificationEvents.emit(EndEvent(getCurrentTimestamp())) + logger.info { "End event emitted" } + }.join() + + // Making sure all session notification jobs are completed (after receiving the end event) + sessionNotificationJobs.value.values.forEach { it.join() } + // Cancelling notification scope to stop processing further events + notificationScope.cancel() + } +} diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/FeatureRegistry.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/FeatureRegistry.kt index 1749ef6..0ed6558 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/FeatureRegistry.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/FeatureRegistry.kt @@ -5,9 +5,17 @@ import kotlinx.atomicfu.atomic import kotlinx.atomicfu.getAndUpdate import kotlinx.atomicfu.update import kotlinx.collections.immutable.minus +import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.toPersistentSet +/** + * A listener interface for receiving notifications about feature changes in registry. + */ +internal interface FeatureListener { + fun onFeatureUpdated(featureKey: String) +} + /** * A generic registry for managing features of a specified type. This class provides thread-safe * operations for adding, removing, and retrieving features from the registry. @@ -33,6 +41,16 @@ internal class FeatureRegistry(private val featureType: String) { internal val values: Map get() = registry.value + private val listeners = atomic(persistentListOf()) + + internal fun addListener(listener: FeatureListener) { + listeners.update { it.add(listener) } + } + + internal fun removeListener(listener: FeatureListener) { + listeners.update { it.remove(listener) } + } + /** * Adds the specified feature to the registry. * @@ -40,8 +58,11 @@ internal class FeatureRegistry(private val featureType: String) { */ internal fun add(feature: T) { logger.info { "Adding $featureType: \"${feature.key}\"" } - registry.update { current -> current.put(feature.key, feature) } + val oldMap = registry.getAndUpdate { current -> current.put(feature.key, feature) } + val oldFeature = oldMap[feature.key] + logger.info { "Added $featureType: \"${feature.key}\"" } + notifyFeatureUpdated(oldFeature, feature) } /** @@ -52,8 +73,13 @@ internal class FeatureRegistry(private val featureType: String) { */ internal fun addAll(features: List) { logger.info { "Adding ${featureType}s: ${features.size}" } - registry.update { current -> current.putAll(features.associateBy { it.key }) } + val oldMap = registry.getAndUpdate { current -> current.putAll(features.associateBy { it.key }) } + logger.info { "Added ${featureType}s: ${features.size}" } + for (feature in features) { + val oldFeature = oldMap[feature.key] + notifyFeatureUpdated(oldFeature, feature) + } } /** @@ -66,15 +92,17 @@ internal class FeatureRegistry(private val featureType: String) { logger.info { "Removing $featureType: \"$key\"" } val oldMap = registry.getAndUpdate { current -> current.remove(key) } - val removed = key in oldMap - logger.info { - if (removed) { - "Removed $featureType: \"$key\"" - } else { - "$featureType not found: \"$key\"" - } + val removedFeature = oldMap[key] + val removed = removedFeature != null + + if (removed) { + logger.info { "Removed $featureType: \"$key\"" } + notifyFeatureUpdated(removedFeature, null) + } else { + logger.info { "$featureType not found: \"$key\"" } } - return key in oldMap + + return removed } /** @@ -87,13 +115,16 @@ internal class FeatureRegistry(private val featureType: String) { logger.info { "Removing ${featureType}s: ${keys.size}" } val oldMap = registry.getAndUpdate { current -> current - keys.toPersistentSet() } - val removedCount = keys.count { it in oldMap } - logger.info { - if (removedCount > 0) { - "Removed ${featureType}s: $removedCount" - } else { - "No $featureType were removed" - } + val removedFeatures = keys.mapNotNull { oldMap[it] } + val removedCount = removedFeatures.size + + if (removedCount > 0) { + logger.info { "Removed ${featureType}s: $removedCount" } + } else { + logger.info { "No $featureType were removed" } + } + removedFeatures.forEach { + notifyFeatureUpdated(it, null) } return removedCount @@ -108,13 +139,22 @@ internal class FeatureRegistry(private val featureType: String) { internal fun get(key: FeatureKey): T? { logger.info { "Getting $featureType: \"$key\"" } val feature = registry.value[key] - logger.info { - if (feature != null) { - "Got $featureType: \"$key\"" - } else { - "$featureType not found: \"$key\"" - } + if (feature != null) { + logger.info { "Got $featureType: \"$key\"" } + } else { + logger.info { "$featureType not found: \"$key\"" } } + return feature } + + private fun notifyFeatureUpdated(oldFeature: T?, newFeature: T?) { + val featureKey = (oldFeature?.key ?: newFeature?.key) ?: run { + logger.error { "Notification should have feature key, but none found" } + return + } + + logger.info { "Notifying listeners on feature update" } + listeners.value.forEach { it.onFeatureUpdated(featureKey) } + } } diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt index 7fa7a98..ef52d72 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt @@ -26,6 +26,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.ListToolsRequest import io.modelcontextprotocol.kotlin.sdk.types.ListToolsResult import io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotification import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.Notification import io.modelcontextprotocol.kotlin.sdk.types.Prompt import io.modelcontextprotocol.kotlin.sdk.types.PromptArgument import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest @@ -33,12 +34,16 @@ import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult import io.modelcontextprotocol.kotlin.sdk.types.Resource import io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotification import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.SubscribeRequest import io.modelcontextprotocol.kotlin.sdk.types.TextContent import io.modelcontextprotocol.kotlin.sdk.types.Tool import io.modelcontextprotocol.kotlin.sdk.types.ToolAnnotations import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema +import io.modelcontextprotocol.kotlin.sdk.types.UnsubscribeRequest import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Deferred import kotlinx.serialization.json.JsonObject +import kotlin.time.ExperimentalTime private val logger = KotlinLogging.logger {} @@ -58,6 +63,11 @@ public class ServerOptions(public val capabilities: ServerCapabilities, enforceS * You can register tools, prompts, and resources using [addTool], [addPrompt], and [addResource]. * The server will then automatically handle listing and retrieval requests from the client. * + * In case the server supports feature list notification or resource substitution, + * the server will automatically send notifications for all connected clients. + * Currently, after subscription to a resource, the server will NOT send the subscription confirmation + * as this response schema is not defined in the protocol. + * * @param serverInfo Information about this server implementation (name, version). * @param options Configuration options for the server. * @param instructionsProvider Optional provider for instructions from the server to the client about how to use @@ -86,14 +96,6 @@ public open class Server( block: Server.() -> Unit = {}, ) : this(serverInfo, options, { instructions }, block) - private val sessionRegistry = ServerSessionRegistry() - - /** - * Provides a snapshot of all sessions currently registered in the server - */ - public val sessions: Map - get() = sessionRegistry.sessions - @Suppress("ktlint:standard:backing-property-naming") private var _onInitialized: (() -> Unit) = {} @@ -103,14 +105,51 @@ public open class Server( @Suppress("ktlint:standard:backing-property-naming") private var _onClose: () -> Unit = {} - private val toolRegistry = FeatureRegistry("Tool") - private val promptRegistry = FeatureRegistry("Prompt") - private val resourceRegistry = FeatureRegistry("Resource") + @OptIn(ExperimentalTime::class) + private val notificationService = FeatureNotificationService() + private val sessionRegistry = ServerSessionRegistry() + + private val toolRegistry = FeatureRegistry("Tool").apply { + if (options.capabilities.tools?.listChanged ?: false) { + addListener(notificationService.toolListChangedListener) + } + } + private val promptRegistry = FeatureRegistry("Prompt").apply { + if (options.capabilities.prompts?.listChanged ?: false) { + addListener(notificationService.promptListChangedListener) + } + } + private val resourceRegistry = FeatureRegistry("Resource").apply { + if (options.capabilities.resources?.listChanged ?: false) { + addListener(notificationService.resourceListChangedListener) + } + if (options.capabilities.resources?.subscribe ?: false) { + addListener(notificationService.resourceUpdatedListener) + } + } + + /** + * Provides a snapshot of all sessions currently registered in the server + */ + public val sessions: Map + get() = sessionRegistry.sessions + + /** + * Provides a snapshot of all tools currently registered in the server + */ public val tools: Map get() = toolRegistry.values + + /** + * Provides a snapshot of all prompts currently registered in the server + */ public val prompts: Map get() = promptRegistry.values + + /** + * Provides a snapshot of all resources currently registered in the server + */ public val resources: Map get() = resourceRegistry.values @@ -120,6 +159,7 @@ public open class Server( public suspend fun close() { logger.debug { "Closing MCP server" } + notificationService.close() sessions.forEach { (sessionId, session) -> logger.info { "Closing session $sessionId" } session.close() @@ -182,17 +222,32 @@ public open class Server( session.setRequestHandler(Method.Defined.ResourcesTemplatesList) { _, _ -> handleListResourceTemplates() } + if (options.capabilities.resources?.subscribe ?: false) { + session.setRequestHandler(Method.Defined.ResourcesSubscribe) { request, _ -> + handleSubscribeResources(session, request) + // Does not return any confirmation as the structure is not stated in the protocol + null + } + session.setRequestHandler(Method.Defined.ResourcesUnsubscribe) { request, _ -> + handleUnsubscribeResources(session, request) + // Does not return any confirmation as the structure is not stated in the protocol + null + } + } } // Register cleanup handler to remove session from list when it closes session.onClose { logger.debug { "Removing closed session from active sessions list" } + notificationService.unsubscribeSession(session) sessionRegistry.removeSession(session.sessionId) } + logger.debug { "Server session connecting to transport" } session.connect(transport) logger.debug { "Server session successfully connected to transport" } sessionRegistry.addSession(session) + notificationService.subscribeSession(session) _onConnect() return session @@ -484,7 +539,25 @@ public open class Server( } // --- Internal Handlers --- - private suspend fun handleListTools(): ListToolsResult { + private fun handleSubscribeResources(session: ServerSession, request: SubscribeRequest) { + if (options.capabilities.resources?.subscribe ?: false) { + logger.debug { "Subscribing to resources" } + notificationService.subscribeToResourceUpdate(session, request.params.uri) + } else { + logger.debug { "Failed to subscribe to resources: Server does not support resources capability" } + } + } + + private fun handleUnsubscribeResources(session: ServerSession, request: UnsubscribeRequest) { + if (options.capabilities.resources?.subscribe ?: false) { + logger.debug { "Unsubscribing from resources" } + notificationService.unsubscribeFromResourceUpdate(session, request.params.uri) + } else { + logger.debug { "Failed to unsubscribe from resources: Server does not support resources capability" } + } + } + + private fun handleListTools(): ListToolsResult { val toolList = tools.values.map { it.tool } return ListToolsResult(tools = toolList, nextCursor = null) } @@ -518,7 +591,7 @@ public open class Server( } } - private suspend fun handleListPrompts(): ListPromptsResult { + private fun handleListPrompts(): ListPromptsResult { logger.debug { "Handling list prompts request" } return ListPromptsResult(prompts = prompts.values.map { it.prompt }) } @@ -534,7 +607,7 @@ public open class Server( return prompt.messageProvider(request) } - private suspend fun handleListResources(): ListResourcesResult { + private fun handleListResources(): ListResourcesResult { logger.debug { "Handling list resources request" } return ListResourcesResult(resources = resources.values.map { it.resource }) } @@ -550,7 +623,7 @@ public open class Server( return resource.readHandler(request) } - private suspend fun handleListResourceTemplates(): ListResourceTemplatesResult { + private fun handleListResourceTemplates(): ListResourceTemplatesResult { // If you have resource templates, return them here. For now, return empty. return ListResourceTemplatesResult(listOf()) } @@ -675,4 +748,30 @@ public open class Server( } } // End the ServerSession redirection section + + // Start the notification handling section + public fun setNotificationHandler(method: Method, handler: (notification: T) -> Deferred) { + sessions.forEach { (_, session) -> + session.setNotificationHandler(method, handler) + } + } + + public fun removeNotificationHandler(method: Method) { + sessions.forEach { (_, session) -> + session.removeNotificationHandler(method) + } + } + + public fun setNotificationHandler( + sessionId: String, + method: Method, + handler: (notification: T) -> Deferred, + ) { + sessionRegistry.getSessionOrNull(sessionId)?.setNotificationHandler(method, handler) + } + + public fun removeNotificationHandler(sessionId: String, method: Method) { + sessionRegistry.getSessionOrNull(sessionId)?.removeNotificationHandler(method) + } + // End the notification handling section } diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/InMemoryTransportTest.kt b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/InMemoryTransportTest.kt index d13f584..83e5618 100644 --- a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/InMemoryTransportTest.kt +++ b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/InMemoryTransportTest.kt @@ -1,8 +1,14 @@ package io.modelcontextprotocol.kotlin.sdk.integration import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport +import io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams import io.modelcontextprotocol.kotlin.sdk.types.InitializedNotification import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.PromptListChangedNotification +import io.modelcontextprotocol.kotlin.sdk.types.ResourceListChangedNotification +import io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotification +import io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotificationParams +import io.modelcontextprotocol.kotlin.sdk.types.ToolListChangedNotification import io.modelcontextprotocol.kotlin.sdk.types.toJSON import kotlinx.coroutines.test.runTest import kotlin.test.BeforeTest @@ -107,4 +113,94 @@ class InMemoryTransportTest { serverTransport.start() assertEquals(message, receivedMessage) } + + @Test + fun `should send ToolListChangedNotification from server to client`() = runTest { + val notification = ToolListChangedNotification( + BaseNotificationParams(), + ) + + var receivedMessage: JSONRPCMessage? = null + clientTransport.onMessage { msg -> + receivedMessage = msg + } + + val rpcMessage = notification.toJSON() + serverTransport.send(rpcMessage) + assertEquals(rpcMessage, receivedMessage) + } + + @Test + fun `should send PromptListChangedNotification from server to client`() = runTest { + val notification = PromptListChangedNotification( + BaseNotificationParams(), + ) + + var receivedMessage: JSONRPCMessage? = null + clientTransport.onMessage { msg -> + receivedMessage = msg + } + + val rpcMessage = notification.toJSON() + serverTransport.send(rpcMessage) + assertEquals(rpcMessage, receivedMessage) + } + + @Test + fun `should send ResourceListChangedNotification from server to client`() = runTest { + val notification = ResourceListChangedNotification( + BaseNotificationParams(), + ) + + var receivedMessage: JSONRPCMessage? = null + clientTransport.onMessage { msg -> + receivedMessage = msg + } + + val rpcMessage = notification.toJSON() + serverTransport.send(rpcMessage) + assertEquals(rpcMessage, receivedMessage) + } + + @Test + fun `should send ResourceUpdatedNotification from server to client`() = runTest { + val notification = ResourceUpdatedNotification( + ResourceUpdatedNotificationParams( + uri = "file:///workspace/data.json", + ), + ) + + var receivedMessage: JSONRPCMessage? = null + clientTransport.onMessage { msg -> + receivedMessage = msg + } + + val rpcMessage = notification.toJSON() + serverTransport.send(rpcMessage) + assertEquals(rpcMessage, receivedMessage) + } + + @Test + fun `should handle multiple notifications in sequence`() = runTest { + val notifications = listOf( + ToolListChangedNotification(), + PromptListChangedNotification(), + ResourceListChangedNotification(), + ResourceUpdatedNotification(ResourceUpdatedNotificationParams(uri = "file:///workspace/data.json")), + ) + + val receivedMessages = mutableListOf() + clientTransport.onMessage { msg -> + receivedMessages.add(msg) + } + + notifications.forEach { notification -> + serverTransport.send(notification.toJSON()) + } + + assertEquals(notifications.size, receivedMessages.size) + notifications.forEachIndexed { index, notification -> + assertEquals(notification.toJSON(), receivedMessages[index]) + } + } } diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsNotificationTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsNotificationTest.kt new file mode 100644 index 0000000..e2cfe29 --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsNotificationTest.kt @@ -0,0 +1,116 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult +import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.Prompt +import io.modelcontextprotocol.kotlin.sdk.types.PromptListChangedNotification +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.test.runTest +import org.awaitility.kotlin.await +import org.awaitility.kotlin.untilAsserted +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class ServerPromptsNotificationTest : AbstractServerFeaturesTest() { + + override fun getServerCapabilities(): ServerCapabilities = ServerCapabilities( + prompts = ServerCapabilities.Prompts(true), + ) + + @Test + fun `addPrompt should send notification`() = runTest { + // Configure notification handler + var promptListChangedNotificationReceived = false + client.setNotificationHandler(Method.Defined.NotificationsPromptsListChanged) { + promptListChangedNotificationReceived = true + CompletableDeferred(Unit) + } + + // Add a prompt + val testPrompt = Prompt("test-prompt", "Test Prompt", null) + server.addPrompt(testPrompt) { + GetPromptResult( + description = "Test prompt description", + messages = listOf(), + ) + } + + // Remove the prompt + val result = server.removePrompt(testPrompt.name) + + // Verify the prompt was removed + assertTrue(result, "Prompt should be removed successfully") + + // Verify that the notification was sent + await untilAsserted { + assertTrue(promptListChangedNotificationReceived, "Notification should be sent when prompt is added") + } + } + + @Test + fun `removePrompts should remove multiple prompts and send two notifications`() = runTest { + // Configure notification handler + var promptListChangedNotificationReceivedCount = 0 + client.setNotificationHandler(Method.Defined.NotificationsPromptsListChanged) { + promptListChangedNotificationReceivedCount += 1 + CompletableDeferred(Unit) + } + + // Add prompts + val testPrompt1 = Prompt("test-prompt-1", "Test Prompt 1", null) + val testPrompt2 = Prompt("test-prompt-2", "Test Prompt 2", null) + + server.addPrompt(testPrompt1) { + GetPromptResult( + description = "Test prompt description 1", + messages = listOf(), + ) + } + server.addPrompt(testPrompt2) { + GetPromptResult( + description = "Test prompt description 2", + messages = listOf(), + ) + } + + // Remove the prompts + val result = server.removePrompts(listOf(testPrompt1.name, testPrompt2.name)) + + // Verify the prompts were removed + assertEquals(2, result, "Both prompts should be removed") + + // Verify that the notifications were sent twice + await untilAsserted { + assertEquals( + 4, + promptListChangedNotificationReceivedCount, + "Two notifications should be sent when prompts are added and two when removed", + ) + } + } + + @Test + fun `notification should not be send when removed prompt does not exists`() = runTest { + // Track notifications + var promptListChangedNotificationReceived = false + client.setNotificationHandler(Method.Defined.NotificationsPromptsListChanged) { + promptListChangedNotificationReceived = true + CompletableDeferred(Unit) + } + + // Try to remove a non-existent prompt + val result = server.removePrompt("non-existent-prompt") + + // Verify the result + assertFalse(result, "Removing non-existent prompt should return false") + await untilAsserted { + assertFalse( + promptListChangedNotificationReceived, + "No notification should be sent when prompt doesn't exist", + ) + } + } +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsTest.kt index 60de0ca..5f49d04 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsTest.kt @@ -17,11 +17,18 @@ import kotlin.test.assertTrue class ServerPromptsTest : AbstractServerFeaturesTest() { override fun getServerCapabilities(): ServerCapabilities = ServerCapabilities( - prompts = ServerCapabilities.Prompts(false), + prompts = ServerCapabilities.Prompts(null), ) @Test - fun `removePrompt should remove a prompt`() = runTest { + fun `removePrompt should remove a prompt and do not send notification`() = runTest { + // Configure notification handler + var promptListChangedNotificationReceived = false + client.setNotificationHandler(Method.Defined.NotificationsPromptsListChanged) { + promptListChangedNotificationReceived = true + CompletableDeferred(Unit) + } + // Add a prompt val testPrompt = Prompt("test-prompt", "Test Prompt", null) server.addPrompt(testPrompt) { @@ -36,13 +43,30 @@ class ServerPromptsTest : AbstractServerFeaturesTest() { // Verify the prompt was removed assertTrue(result, "Prompt should be removed successfully") + + // Verify that the notification was not sent + assertFalse( + promptListChangedNotificationReceived, + "No notification should be sent when prompts capability is not supported", + ) } @Test - fun `removePrompts should remove multiple prompts and send notification`() = runTest { + fun `removePrompts should remove multiple prompts`() = runTest { + // Configure notification handler + var promptListChangedNotificationReceived = false + client.setNotificationHandler(Method.Defined.NotificationsPromptsListChanged) { + promptListChangedNotificationReceived = true + CompletableDeferred(Unit) + } + // Add prompts val testPrompt1 = Prompt("test-prompt-1", "Test Prompt 1", null) val testPrompt2 = Prompt("test-prompt-2", "Test Prompt 2", null) + client.setNotificationHandler(Method.Defined.NotificationsPromptsListChanged) { + throw IllegalStateException("Notification should not be sent") + } + server.addPrompt(testPrompt1) { GetPromptResult( description = "Test prompt description 1", @@ -61,6 +85,12 @@ class ServerPromptsTest : AbstractServerFeaturesTest() { // Verify the prompts were removed assertEquals(2, result, "Both prompts should be removed") + + // Verify that the notification was not sent + assertFalse( + promptListChangedNotificationReceived, + "No notification should be sent when prompts capability is not supported", + ) } @Test @@ -82,6 +112,12 @@ class ServerPromptsTest : AbstractServerFeaturesTest() { @Test fun `removePrompt should throw when prompts capability is not supported`() = runTest { + var promptListChangedNotificationReceived = false + client.setNotificationHandler(Method.Defined.NotificationsPromptsListChanged) { + promptListChangedNotificationReceived = true + CompletableDeferred(Unit) + } + // Create server without prompts capability val serverOptions = ServerOptions( capabilities = ServerCapabilities(), @@ -96,5 +132,11 @@ class ServerPromptsTest : AbstractServerFeaturesTest() { server.removePrompt("test-prompt") } assertEquals("Server does not support prompts capability.", exception.message) + + // Verify that the notification was not sent + assertFalse( + promptListChangedNotificationReceived, + "No notification should be sent when prompts capability is not supported", + ) } } diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationSubscribeTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationSubscribeTest.kt new file mode 100644 index 0000000..6284450 --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationSubscribeTest.kt @@ -0,0 +1,186 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult +import io.modelcontextprotocol.kotlin.sdk.types.ResourceListChangedNotification +import io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotification +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.SubscribeRequest +import io.modelcontextprotocol.kotlin.sdk.types.SubscribeRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.test.runTest +import org.awaitility.kotlin.await +import org.awaitility.kotlin.untilAsserted +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class ServerResourcesNotificationSubscribeTest : AbstractServerFeaturesTest() { + + override fun getServerCapabilities(): ServerCapabilities = ServerCapabilities( + resources = ServerCapabilities.Resources(null, true), + ) + + @Test + fun `removeResource should send resource update notification`() = runTest { + val notifications = mutableListOf() + client.setNotificationHandler(Method.Defined.NotificationsResourcesUpdated) { + notifications.add(it) + CompletableDeferred(Unit) + } + + // Add resources + val testResourceUri1 = "test://resource1" + val testResourceUri2 = "test://resource2" + + server.addResource( + uri = testResourceUri1, + name = "Test Resource 1", + description = "A test resource 1", + mimeType = "text/plain", + ) { + ReadResourceResult( + contents = listOf( + TextResourceContents( + text = "Test resource content 1", + uri = testResourceUri1, + mimeType = "text/plain", + ), + ), + ) + } + + client.subscribeResource(SubscribeRequest(SubscribeRequestParams(uri = testResourceUri1))) + + server.addResource( + uri = testResourceUri2, + name = "Test Resource 2", + description = "A test resource 2", + mimeType = "text/plain", + ) { + ReadResourceResult( + contents = listOf( + TextResourceContents( + text = "Test resource content 2", + uri = testResourceUri2, + mimeType = "text/plain", + ), + ), + ) + } + + // Remove the resource + val result = server.removeResource(testResourceUri1) + + // Verify the resource was removed + assertTrue(result, "Resource should be removed successfully") + + // Verify that the notification was sent + await untilAsserted { + assertEquals(1, notifications.size, "Notification should be sent when resource 1 was deleted") + } + assertEquals(testResourceUri1, notifications[0].params.uri, "Notification should contain the resource 1 URI") + } + + @Test + fun `removeResource for two resources should send two separate notifications`() = runTest { + val notifications = mutableListOf() + client.setNotificationHandler(Method.Defined.NotificationsResourcesUpdated) { + notifications.add(it) + CompletableDeferred(Unit) + } + + // Add resources + val testResourceUri1 = "test://resource1" + val testResourceUri2 = "test://resource2" + + server.addResource( + uri = testResourceUri1, + name = "Test Resource 1", + description = "A test resource 1", + mimeType = "text/plain", + ) { + ReadResourceResult( + contents = listOf( + TextResourceContents( + text = "Test resource content 1", + uri = testResourceUri1, + mimeType = "text/plain", + ), + ), + ) + } + + server.addResource( + uri = testResourceUri2, + name = "Test Resource 2", + description = "A test resource 2", + mimeType = "text/plain", + ) { + ReadResourceResult( + contents = listOf( + TextResourceContents( + text = "Test resource content 2", + uri = testResourceUri2, + mimeType = "text/plain", + ), + ), + ) + } + + client.subscribeResource(SubscribeRequest(SubscribeRequestParams(uri = testResourceUri1))) + client.subscribeResource(SubscribeRequest(SubscribeRequestParams(uri = testResourceUri2))) + + // Remove the resource + val result1 = server.removeResource(testResourceUri1) + val result2 = server.removeResource(testResourceUri2) + + // Verify the resource was removed + assertTrue(result1, "Resource 1 should be removed successfully") + assertTrue(result2, "Resource 2 should be removed successfully") + + println(notifications.map { it.params.uri }) + // Verify that the notification was sent + await untilAsserted { + assertEquals( + 2, + notifications.size, + "Notification should be sent when resource 1 and resource 2 was deleted", + ) + } + + val deletedResources = listOf(notifications[0].params.uri, notifications[1].params.uri) + assertTrue( + deletedResources.contains(testResourceUri1), + "Notification should contain the removed resource 1 URI", + ) + assertTrue( + deletedResources.contains(testResourceUri2), + "Notification should contain the removed resource 2 URI", + ) + } + + @Test + fun `notification should not be send when removed resource does not exists`() = runTest { + // Track notifications + var resourceListChangedNotificationReceived = false + client.setNotificationHandler( + Method.Defined.NotificationsResourcesListChanged, + ) { + resourceListChangedNotificationReceived = true + CompletableDeferred(Unit) + } + + // Try to remove a non-existent resource + val result = server.removeResource("non-existent-resource") + + // Verify the result + assertFalse(result, "Removing non-existent resource should return false") + assertFalse( + resourceListChangedNotificationReceived, + "No notification should be sent when resource doesn't exist", + ) + } +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationTest.kt new file mode 100644 index 0000000..52a4261 --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationTest.kt @@ -0,0 +1,150 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult +import io.modelcontextprotocol.kotlin.sdk.types.ResourceListChangedNotification +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.test.runTest +import org.awaitility.kotlin.await +import org.awaitility.kotlin.untilAsserted +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class ServerResourcesNotificationTest : AbstractServerFeaturesTest() { + + override fun getServerCapabilities(): ServerCapabilities = ServerCapabilities( + resources = ServerCapabilities.Resources(true, null), + ) + + @Test + fun `addResource should send notification`() = runTest { + // Configure notification handler + var resourceListChangedNotificationReceived = false + client.setNotificationHandler( + Method.Defined.NotificationsResourcesListChanged, + ) { + resourceListChangedNotificationReceived = true + CompletableDeferred(Unit) + } + + // Add a resource + val testResourceUri = "test://resource" + server.addResource( + uri = testResourceUri, + name = "Test Resource", + description = "A test resource", + mimeType = "text/plain", + ) { + ReadResourceResult( + contents = listOf( + TextResourceContents( + text = "Test resource content", + uri = testResourceUri, + mimeType = "text/plain", + ), + ), + ) + } + + // Remove the resource + val result = server.removeResource(testResourceUri) + + // Verify the resource was removed + assertTrue(result, "Resource should be removed successfully") + + // Verify that the notification was sent + await untilAsserted { + assertTrue(resourceListChangedNotificationReceived, "Notification should be sent when resource is added") + } + } + + @Test + fun `removeResources should remove multiple resources and send two notifications`() = runTest { + // Configure notification handler + var resourceListChangedNotificationReceivedCount = 0 + client.setNotificationHandler( + Method.Defined.NotificationsResourcesListChanged, + ) { + resourceListChangedNotificationReceivedCount += 1 + CompletableDeferred(Unit) + } + + // Add resources + val testResourceUri1 = "test://resource1" + val testResourceUri2 = "test://resource2" + + server.addResource( + uri = testResourceUri1, + name = "Test Resource 1", + description = "A test resource 1", + mimeType = "text/plain", + ) { + ReadResourceResult( + contents = listOf( + TextResourceContents( + text = "Test resource content 1", + uri = testResourceUri1, + mimeType = "text/plain", + ), + ), + ) + } + server.addResource( + uri = testResourceUri2, + name = "Test Resource 2", + description = "A test resource 2", + mimeType = "text/plain", + ) { + ReadResourceResult( + contents = listOf( + TextResourceContents( + text = "Test resource content 2", + uri = testResourceUri2, + mimeType = "text/plain", + ), + ), + ) + } + + // Remove the resources + val result = server.removeResources(listOf(testResourceUri1, testResourceUri2)) + + // Verify the resources were removed + assertEquals(2, result, "Both resources should be removed") + + // Verify that the notifications were sent twice + await untilAsserted { + assertEquals( + 4, + resourceListChangedNotificationReceivedCount, + "Two notifications should be sent when resources are added and two when removed", + ) + } + } + + @Test + fun `notification should not be send when removed resource does not exists`() = runTest { + // Track notifications + var resourceListChangedNotificationReceived = false + client.setNotificationHandler( + Method.Defined.NotificationsResourcesListChanged, + ) { + resourceListChangedNotificationReceived = true + CompletableDeferred(Unit) + } + + // Try to remove a non-existent resource + val result = server.removeResource("non-existent-resource") + + // Verify the result + assertFalse(result, "Removing non-existent resource should return false") + assertFalse( + resourceListChangedNotificationReceived, + "No notification should be sent when resource doesn't exist", + ) + } +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesTest.kt index ad73c83..fafc3c8 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesTest.kt @@ -21,7 +21,7 @@ class ServerResourcesTest : AbstractServerFeaturesTest() { ) @Test - fun `removeResource should remove a resource and send notification`() = runTest { + fun `removeResource should remove a resource`() = runTest { // Add a resource val testResourceUri = "test://resource" server.addResource( @@ -49,7 +49,7 @@ class ServerResourcesTest : AbstractServerFeaturesTest() { } @Test - fun `removeResources should remove multiple resources and send notification`() = runTest { + fun `removeResources should remove multiple resources`() = runTest { // Add resources val testResourceUri1 = "test://resource1" val testResourceUri2 = "test://resource2" diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsNotificationTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsNotificationTest.kt new file mode 100644 index 0000000..5892532 --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsNotificationTest.kt @@ -0,0 +1,100 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult +import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.sdk.types.ToolListChangedNotification +import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.test.runTest +import org.awaitility.kotlin.await +import org.awaitility.kotlin.untilAsserted +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class ServerToolsNotificationTest : AbstractServerFeaturesTest() { + + override fun getServerCapabilities(): ServerCapabilities = ServerCapabilities( + tools = ServerCapabilities.Tools(true), + ) + + @Test + fun `addTool should send notification`() = runTest { + // Configure notification handler + var toolListChangedNotificationReceived = false + client.setNotificationHandler(Method.Defined.NotificationsToolsListChanged) { + toolListChangedNotificationReceived = true + CompletableDeferred(Unit) + } + + // Add a tool + server.addTool("test-tool", "Test Tool", ToolSchema()) { + CallToolResult(listOf(TextContent("Test result"))) + } + + // Remove the tool + val result = server.removeTool("test-tool") + + // Verify the tool was removed + assertTrue(result, "Tool should be removed successfully") + + // Verify that the notification was sent + await untilAsserted { + assertTrue(toolListChangedNotificationReceived, "Notification should be sent when tool is added") + } + } + + @Test + fun `removeTools should remove multiple tools and send two notifications`() = runTest { + // Configure notification handler + var toolListChangedNotificationReceivedCount = 0 + client.setNotificationHandler(Method.Defined.NotificationsToolsListChanged) { + toolListChangedNotificationReceivedCount += 1 + CompletableDeferred(Unit) + } + + // Add tools + server.addTool("test-tool-1", "Test Tool 1") { + CallToolResult(listOf(TextContent("Test result 1"))) + } + server.addTool("test-tool-2", "Test Tool 2") { + CallToolResult(listOf(TextContent("Test result 2"))) + } + + // Remove the tools + val result = server.removeTools(listOf("test-tool-1", "test-tool-2")) + // Verify the tools were removed + assertEquals(2, result, "Both tools should be removed") + + // Verify that the notifications were sent twice + await untilAsserted { + assertEquals( + 4, + toolListChangedNotificationReceivedCount, + "Two notifications should be sent when tools are added and two when removed", + ) + } + } + + @Test + fun `notification should not be send when removed tool does not exists`() = runTest { + // Track notifications + var toolListChangedNotificationReceived = false + client.setNotificationHandler(Method.Defined.NotificationsToolsListChanged) { + toolListChangedNotificationReceived = true + CompletableDeferred(Unit) + } + + // Try to remove a non-existent tool + val result = server.removeTool("non-existent-tool") + // Close the server to stop processing further events and flush notifications + server.close() + + // Verify the result + assertFalse(result, "Removing non-existent tool should return false") + assertFalse(toolListChangedNotificationReceived, "No notification should be sent when tool doesn't exist") + } +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsTest.kt index eae2eb2..c2e8201 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsTest.kt @@ -22,7 +22,14 @@ class ServerToolsTest : AbstractServerFeaturesTest() { ) @Test - fun `removeTool should remove a tool`() = runTest { + fun `removeTool should remove a tool and do not send notification`() = runTest { + // Configure notification handler + var toolListChangedNotificationReceived = false + client.setNotificationHandler(Method.Defined.NotificationsToolsListChanged) { + toolListChangedNotificationReceived = true + CompletableDeferred(Unit) + } + // Add a tool server.addTool("test-tool", "Test Tool", ToolSchema()) { CallToolResult(listOf(TextContent("Test result"))) @@ -33,6 +40,45 @@ class ServerToolsTest : AbstractServerFeaturesTest() { // Verify the tool was removed assertTrue(result, "Tool should be removed successfully") + + // Verify that the notification was not sent + assertFalse( + toolListChangedNotificationReceived, + "No notification should be sent when tools capability is not supported", + ) + } + + @Test + fun `removeTools should remove multiple tools`() = runTest { + // Configure notification handler + var toolListChangedNotificationReceived = false + client.setNotificationHandler(Method.Defined.NotificationsToolsListChanged) { + toolListChangedNotificationReceived = true + CompletableDeferred(Unit) + } + + // Add tools + server.addTool("test-tool-1", "Test Tool 1") { + CallToolResult(listOf(TextContent("Test result 1"))) + } + server.addTool("test-tool-2", "Test Tool 2") { + CallToolResult(listOf(TextContent("Test result 2"))) + } + client.setNotificationHandler(Method.Defined.NotificationsToolsListChanged) { + throw IllegalStateException("Notification should not be sent") + } + + // Remove the tools + val result = server.removeTools(listOf("test-tool-1", "test-tool-2")) + + // Verify the tools were removed + assertEquals(2, result, "Both tools should be removed") + + // Verify that the notification was not sent + assertFalse( + toolListChangedNotificationReceived, + "No notification should be sent when tools capability is not supported", + ) } @Test @@ -54,6 +100,12 @@ class ServerToolsTest : AbstractServerFeaturesTest() { @Test fun `removeTool should throw when tools capability is not supported`() = runTest { + var toolListChangedNotificationReceived = false + client.setNotificationHandler(Method.Defined.NotificationsToolsListChanged) { + toolListChangedNotificationReceived = true + CompletableDeferred(Unit) + } + // Create server without tools capability val serverOptions = ServerOptions( capabilities = ServerCapabilities(), @@ -68,22 +120,11 @@ class ServerToolsTest : AbstractServerFeaturesTest() { server.removeTool("test-tool") } assertEquals("Server does not support tools capability.", exception.message) - } - @Test - fun `removeTools should remove multiple tools`() = runTest { - // Add tools - server.addTool("test-tool-1", "Test Tool 1") { - CallToolResult(listOf(TextContent("Test result 1"))) - } - server.addTool("test-tool-2", "Test Tool 2") { - CallToolResult(listOf(TextContent("Test result 2"))) - } - - // Remove the tools - val result = server.removeTools(listOf("test-tool-1", "test-tool-2")) - - // Verify the tools were removed - assertEquals(2, result, "Both tools should be removed") + // Verify that the notification was not sent + assertFalse( + toolListChangedNotificationReceived, + "No notification should be sent when tools capability is not supported", + ) } } From 191b76863dddd3679f3ffebed288b4867eba79e7 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Thu, 4 Dec 2025 09:18:32 +0200 Subject: [PATCH 090/449] #107 Monitor STDERR and refactor `StdioClientTransport` to use flows (#442) --- .github/workflows/build.yml | 12 +- gradle/libs.versions.toml | 10 +- kotlin-sdk-client/api/kotlin-sdk-client.api | 16 + kotlin-sdk-client/build.gradle.kts | 8 +- .../kotlin/sdk/client/StdioClientTransport.kt | 351 ++++++++++++++---- .../client/StreamableHttpClientTransport.kt | 1 + .../AbstractClientTransportLifecycleTest.kt | 86 +++++ .../StdioClientTransportLifecycleTest.kt | 26 ++ .../StreamableHttpClientTransportTest.kt | 4 +- ...reamingHttpClientTransportLifecycleTest.kt | 43 +++ .../StdioClientTransportErrorHandlingTest.kt | 98 +++++ .../kotlin/sdk/shared/AbstractTransport.kt | 2 +- .../kotlin/sdk/shared/BaseTransportTest.kt | 4 + .../sdk/client/StdioClientTransportTest.kt | 81 +++- .../KotlinClientTsServerEdgeCasesTestStdio.kt | 5 +- 15 files changed, 660 insertions(+), 87 deletions(-) create mode 100644 kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractClientTransportLifecycleTest.kt create mode 100644 kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/stdio/StdioClientTransportLifecycleTest.kt rename kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/{ => streamable/http}/StreamableHttpClientTransportTest.kt (98%) create mode 100644 kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamingHttpClientTransportLifecycleTest.kt create mode 100644 kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/stdio/StdioClientTransportErrorHandlingTest.kt diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 719f9c7..e05c06a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,6 +12,10 @@ concurrency: # Cancel only when the run is NOT on `main` branch cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} +permissions: + checks: write + pull-requests: write # only required if `comment: true` was enabled + jobs: validate-pr: runs-on: macos-latest-xlarge @@ -70,12 +74,16 @@ jobs: uses: mikepenz/action-junit-report@v6 if: ${{ !cancelled() }} # always run even if the previous step fails with: - report_paths: '**/test-results/**/TEST-*.xml' + annotate_only: true + comment: true detailed_summary: true flaky_summary: true + group_suite: true include_empty_in_summary: false include_time_in_summary: true - annotate_only: true + report_paths: '**/test-results/**/TEST-*.xml' + truncate_stack_traces: false + updateComment: true - name: Disable Auto-Merge on Fail if: failure() && github.event_name == 'pull_request' diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 12d8b37..0b31051 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,16 +11,17 @@ binaryCompatibilityValidatorPlugin = "0.18.1" openapi-generator = "7.17.0" # libraries version -serialization = "1.9.0" +awaitility = "4.3.0" collections-immutable = "0.4.0" coroutines = "1.10.2" +kotest = "6.0.7" kotlinx-io = "0.8.2" ktor = "3.2.3" logging = "7.0.13" -slf4j = "2.0.17" -kotest = "6.0.7" -awaitility = "4.3.0" +mockk = "1.14.6" mokksy = "0.6.2" +serialization = "1.9.0" +slf4j = "2.0.17" [libraries] # Plugins @@ -53,6 +54,7 @@ kotest-assertions-json = { group = "io.kotest", name = "kotest-assertions-json", kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines" } ktor-client-mock = { group = "io.ktor", name = "ktor-client-mock", version.ref = "ktor" } ktor-server-test-host = { group = "io.ktor", name = "ktor-server-test-host", version.ref = "ktor" } +mockk = { module = "io.mockk:mockk", version.ref = "mockk" } mokksy = { group = "dev.mokksy", name = "mokksy", version.ref = "mokksy" } netty-bom = { group = "io.netty", name = "netty-bom", version.ref = "netty" } slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" } diff --git a/kotlin-sdk-client/api/kotlin-sdk-client.api b/kotlin-sdk-client/api/kotlin-sdk-client.api index 9186512..0609482 100644 --- a/kotlin-sdk-client/api/kotlin-sdk-client.api +++ b/kotlin-sdk-client/api/kotlin-sdk-client.api @@ -71,12 +71,28 @@ public final class io/modelcontextprotocol/kotlin/sdk/client/SseClientTransport } public final class io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport : io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport { + public static final field BUFFER_SIZE J public fun (Lkotlinx/io/Source;Lkotlinx/io/Sink;)V + public fun (Lkotlinx/io/Source;Lkotlinx/io/Sink;Lkotlinx/io/Source;)V + public fun (Lkotlinx/io/Source;Lkotlinx/io/Sink;Lkotlinx/io/Source;Lkotlinx/coroutines/channels/Channel;)V + public fun (Lkotlinx/io/Source;Lkotlinx/io/Sink;Lkotlinx/io/Source;Lkotlinx/coroutines/channels/Channel;Lkotlin/jvm/functions/Function1;)V + public synthetic fun (Lkotlinx/io/Source;Lkotlinx/io/Sink;Lkotlinx/io/Source;Lkotlinx/coroutines/channels/Channel;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun close (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$StderrSeverity : java/lang/Enum { + public static final field DEBUG Lio/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport$StderrSeverity; + public static final field FATAL Lio/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport$StderrSeverity; + public static final field IGNORE Lio/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport$StderrSeverity; + public static final field INFO Lio/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport$StderrSeverity; + public static final field WARNING Lio/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport$StderrSeverity; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport$StderrSeverity; + public static fun values ()[Lio/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport$StderrSeverity; +} + public final class io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport : io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport { public synthetic fun (Lio/ktor/client/HttpClient;Ljava/lang/String;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun (Lio/ktor/client/HttpClient;Ljava/lang/String;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V diff --git a/kotlin-sdk-client/build.gradle.kts b/kotlin-sdk-client/build.gradle.kts index 473c32b..4755ac7 100644 --- a/kotlin-sdk-client/build.gradle.kts +++ b/kotlin-sdk-client/build.gradle.kts @@ -41,18 +41,20 @@ kotlin { commonTest { dependencies { implementation(kotlin("test")) - implementation(libs.ktor.client.mock) - implementation(libs.ktor.server.websockets) + implementation(libs.kotest.assertions.core) implementation(libs.kotlinx.coroutines.test) implementation(libs.ktor.client.logging) + implementation(libs.ktor.client.mock) + implementation(libs.ktor.server.websockets) } } jvmTest { dependencies { - implementation(libs.mokksy) implementation(libs.awaitility) implementation(libs.ktor.client.apache5) + implementation(libs.mockk) + implementation(libs.mokksy) implementation(dependencies.platform(libs.netty.bom)) runtimeOnly(libs.slf4j.simple) } diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt index 547079f..b6aa827 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt @@ -1,135 +1,348 @@ package io.modelcontextprotocol.kotlin.sdk.client import io.github.oshai.kotlinlogging.KotlinLogging +import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport.StderrSeverity.DEBUG +import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport.StderrSeverity.FATAL +import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport.StderrSeverity.IGNORE +import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport.StderrSeverity.INFO +import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport.StderrSeverity.WARNING 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 io.modelcontextprotocol.kotlin.sdk.types.McpException +import io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.CONNECTION_CLOSED +import io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.INTERNAL_ERROR import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.ClosedSendChannelException +import kotlinx.coroutines.channels.ProducerScope import kotlinx.coroutines.channels.consumeEach +import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.isActive import kotlinx.coroutines.launch +import kotlinx.coroutines.yield import kotlinx.io.Buffer +import kotlinx.io.IOException import kotlinx.io.Sink import kotlinx.io.Source import kotlinx.io.buffered import kotlinx.io.readByteArray import kotlinx.io.writeString +import kotlinx.serialization.SerializationException import kotlin.concurrent.atomics.AtomicBoolean import kotlin.concurrent.atomics.ExperimentalAtomicApi import kotlin.coroutines.CoroutineContext +import kotlin.jvm.JvmOverloads /** - * A transport implementation for JSON-RPC communication that leverages standard input and output streams. + * A transport implementation for JSON-RPC communication over standard I/O streams. * - * This class reads from an input stream to process incoming JSON-RPC messages and writes JSON-RPC messages - * to an output stream. + * Reads JSON-RPC messages from [input] and writes messages to [output]. Optionally monitors + * [error] stream for stderr output with configurable severity handling. + * + * ## Usage Example + * ```kotlin + * val process = ProcessBuilder("mcp-server").start() + * + * val transport = StdioClientTransport( + * input = process.inputStream.asSource().buffered(), + * output = process.outputStream.asSink().buffered(), + * error = process.errorStream.asSource().buffered() + * ) { stderrLine -> + * when { + * stderrLine.contains("error", ignoreCase = true) -> StderrSeverity.FATAL + * stderrLine.contains("warning", ignoreCase = true) -> StderrSeverity.WARNING + * else -> StderrSeverity.INFO + * } + * } + * + * transport.start() + * ``` * * @param input The input stream where messages are received. * @param output The output stream where messages are sent. + * @param error Optional error stream for stderr monitoring. + * @param sendChannel Channel for outbound messages. Default: buffered channel (capacity 64). + * @param classifyStderr Callback to classify stderr lines. Return [StderrSeverity.FATAL] to fail transport, + * or [StderrSeverity.WARNING] / [StderrSeverity.INFO] / [StderrSeverity.DEBUG] + * to log, or [StderrSeverity.IGNORE] to discard. + * Default value: [StderrSeverity.DEBUG]. + * @see MCP Specification */ @OptIn(ExperimentalAtomicApi::class) -public class StdioClientTransport(private val input: Source, private val output: Sink) : AbstractTransport() { - private val logger = KotlinLogging.logger {} - private val ioCoroutineContext: CoroutineContext = IODispatcher - private val scope by lazy { - CoroutineScope(ioCoroutineContext + SupervisorJob()) +public class StdioClientTransport @JvmOverloads public constructor( + private val input: Source, + private val output: Sink, + private val error: Source? = null, + private val sendChannel: Channel = Channel(Channel.BUFFERED), + private val classifyStderr: (String) -> StderrSeverity = { DEBUG }, +) : AbstractTransport() { + + private companion object { + /** + * Buffer size for I/O operations. + * 8KB is optimal for most systems (matches default page size). + */ + const val BUFFER_SIZE = 8 * 1024L + + private val logger = KotlinLogging.logger {} } - private var job: Job? = null - private val initialized: AtomicBoolean = AtomicBoolean(false) - private val sendChannel = Channel(Channel.UNLIMITED) - private val readBuffer = ReadBuffer() + + /** + * Severity classification for stderr messages. + * + * - [FATAL]: Calls error handler and terminates transport. + * - [WARNING]: Logs at WARN level, transport continues. + * - [INFO]: Logs at INFO level, transport continues. + * - [DEBUG]: Logs at DEBUG level, transport continues. + * - [IGNORE]: Discards message silently, transport continues. + */ + public enum class StderrSeverity { FATAL, WARNING, INFO, DEBUG, IGNORE } + + private val ioCoroutineContext: CoroutineContext = IODispatcher + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) + private val initialized = AtomicBoolean(false) + private val onCloseCalled = AtomicBoolean(false) override suspend fun start() { if (!initialized.compareAndSet(expectedValue = false, newValue = true)) { error("StdioClientTransport already started!") } - logger.debug { "Starting StdioClientTransport..." } - val outputStream = output.buffered() + // Producers run on IODispatcher for I/O + // Collector runs on Default dispatcher for message handling + scope.launch(CoroutineName("StdioClientTransport.IO#${hashCode()}")) { + var writeJob: Job? = null + val mainScope = this + try { + // Explicitly use ioCoroutineContext for I/O operations + writeJob = launch(ioCoroutineContext) { + logger.debug { "Write coroutine started." } + output.buffered().use { sink -> + sendChannel.consumeEach { message -> + sendOutboundMessage(message, sink, mainScope) + yield() // Giving other coroutines a chance to run + } + } + } - job = scope.launch(CoroutineName("StdioClientTransport.IO#${hashCode()}")) { - val readJob = launch { - logger.debug { "Read coroutine started." } - try { - input.use { - while (isActive) { - val buffer = Buffer() - val bytesRead = input.readAtMostTo(buffer, 8192) - if (bytesRead == -1L) break - if (bytesRead > 0L) { - readBuffer.append(buffer.readByteArray()) - processReadBuffer() + val eventsFlow = channelFlow { + launch(ioCoroutineContext) { + logger.debug { "Read stdin coroutine started." } + val readBuffer = ReadBuffer() // parses bytes into JSONRPCMessage + readSource(stream = ProcessStream.Stdin, source = input, channel = this@channelFlow) { bytes -> + readBuffer.append(bytes) + do { + val msg = readBuffer.readMessage() + msg?.let { send(Event.JsonRpc(msg)) } + } while (msg != null) + } + }.invokeOnCompletion { + logger.debug(it) { "Read stdin coroutine finished." } + } + + error?.let { source -> + launch(ioCoroutineContext) { + logger.debug { "Read stderr coroutine started." } + readSource( + stream = ProcessStream.Stderr, + source = source, + channel = this@channelFlow, + ) { bytes -> + val str = bytes.decodeToString() + send(Event.StderrEvent(str)) } } } - } catch (e: Exception) { - _onError.invoke(e) - logger.error(e) { "Error reading from input stream" } } - } - val writeJob = launch { - logger.debug { "Write coroutine started." } - try { - sendChannel.consumeEach { message -> - val json = serializeMessage(message) - outputStream.writeString(json) - outputStream.flush() - } - } catch (e: Throwable) { - if (isActive) { - _onError.invoke(e) - logger.error(e) { "Error writing to output stream" } - } - } finally { - output.close() - } - } + // Collect events on handlerCoroutineContext (Dispatchers.Default from parent scope) + // No flowOn necessary - collection runs in parent launch context + eventsFlow + .collect { event -> + when (event) { + is Event.JsonRpc -> { + handleJSONRPCMessage(event.message) + } - readJob.join() - writeJob.cancelAndJoin() - _onClose.invoke() + is Event.StderrEvent -> { + val errorSeverity = classifyStderr(event.message) + when (errorSeverity) { + FATAL -> { + runCatching { + _onError( + McpException(INTERNAL_ERROR, "Message in StdErr: ${event.message}"), + ) + } + stopProcessing("Fatal STDERR message received") + } + + WARNING -> { + logger.warn { "STDERR message received: ${event.message}" } + } + + INFO -> { + logger.info { "STDERR message received: ${event.message}" } + } + + DEBUG -> { + logger.debug { "STDERR message received: ${event.message}" } + } + + IGNORE -> { + // do nothing + } + } + } + + is Event.EOFEvent -> { + if (event.stream == ProcessStream.Stdin) { + stopProcessing("EOF in ${event.stream}") + } + } + + is Event.IOErrorEvent -> { + runCatching { _onError(event.cause) } + stopProcessing("IO Error", event.cause) + } + } + } + } finally { + // Wait for write job to complete before closing, matching old implementation + writeJob?.cancelAndJoin() + logger.debug { "Transport coroutine completed, calling onClose" } + callOnCloseOnce() + } } } override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { - if (!initialized.load()) { - error("Transport not started") + check(initialized.load()) { "Transport is not started" } + check(!onCloseCalled.load()) { "Transport is closed" } + @Suppress("TooGenericExceptionCaught", "SwallowedException") + try { + sendChannel.send(message) + } catch (e: ClosedSendChannelException) { + logger.debug(e) { "Cannot send message: transport is closed" } + throw McpException(CONNECTION_CLOSED, "Transport is closed") + } catch (e: McpException) { + logger.debug(e) { "Error while sending message: ${e.message}" } + throw e + } catch (e: Exception) { + logger.error(e) { "Error while sending message: ${e.message}" } + throw McpException(INTERNAL_ERROR, "Error while sending message: ${e.message}") } - - sendChannel.send(message) } override suspend fun close() { if (!initialized.compareAndSet(expectedValue = true, newValue = false)) { - error("Transport is already closed") + return // Already closed } - job?.cancelAndJoin() - input.close() - output.close() - readBuffer.clear() - sendChannel.close() - _onClose.invoke() + scope.stopProcessing("Closed") + scope.coroutineContext[Job]?.join() // Wait for all coroutines to complete } - private suspend fun processReadBuffer() { - while (true) { - val msg = readBuffer.readMessage() ?: break - try { - _onMessage.invoke(msg) - } catch (e: Throwable) { - _onError.invoke(e) - logger.error(e) { "Error processing message." } + private fun callOnCloseOnce() { + if (onCloseCalled.compareAndSet(expectedValue = false, newValue = true)) { + runCatching { _onClose() } + } + } + + private fun sendOutboundMessage(message: JSONRPCMessage, sink: Sink, mainScope: CoroutineScope) { + try { + val json = serializeMessage(message) + sink.writeString(json) + sink.flush() + } catch (e: SerializationException) { + logger.warn(e) { "Can't serialize message" } + runCatching { _onError(McpException(INTERNAL_ERROR, "Serialization error")) } + mainScope.stopProcessing("Can't serialize message", e) + } catch (e: IOException) { + logger.warn(e) { "Can't send message" } + runCatching { _onError(McpException(CONNECTION_CLOSED, "Can't send message. Connection closed")) } + mainScope.stopProcessing("Write I/O failed", e) + } + } + + private suspend fun handleJSONRPCMessage(msg: JSONRPCMessage) { + @Suppress("TooGenericExceptionCaught") + try { + _onMessage.invoke(msg) + } catch (e: Throwable) { + logger.error(e) { "Error processing message." } + runCatching { _onError.invoke(e) } + } + } + + private fun CoroutineScope.stopProcessing(reason: String, cause: Throwable? = null) { + sendChannel.close() // Stop accepting new messages + callOnCloseOnce() + cancel(reason, cause) // cancel current coroutine context + } + + private suspend fun CoroutineScope.readSource( + stream: ProcessStream, + source: Source, + channel: ProducerScope, + bytesConsumer: suspend (ByteArray) -> Unit, + ) { + val buffer = Buffer() + try { + source.use { source -> + while (isActive) { + val bytesRead = source.readAtMostTo(buffer, BUFFER_SIZE) + if (bytesRead == -1L) { + logger.debug { "EOF reached in $stream" } + channel.send(Event.EOFEvent(stream)) + break + } + + if (bytesRead > 0L) { + val bytes = buffer.readByteArray() + buffer.clear() + bytesConsumer.invoke(bytes) + } + + yield() // Giving other coroutines a chance to run + } } + } catch (exception: IOException) { + logger.debug(exception) { "IOException while reading stream" } + channel.send(Event.IOErrorEvent(stream, exception)) + } finally { + buffer.clear() + } + } + + private enum class ProcessStream { Stdin, Stderr, Stdout } + + /** + * Represents an event in the communication process. + * + * Events are a sealed hierarchy of different types of communication signals + * between processes. These events are used to manage and interpret information + * or errors generated during the operation of the associated transport. + */ + private sealed interface Event { + + data class JsonRpc(val message: JSONRPCMessage) : Event + + data class StderrEvent(val message: String) : Event + + data class EOFEvent(val stream: ProcessStream) : Event + + data class IOErrorEvent(val stream: ProcessStream, val cause: Throwable) : Event { + override fun toString(): String = "IOErrorEvent(stream=$stream, cause=${cause.message})" } } } diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt index e635f87..a171217 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt @@ -105,6 +105,7 @@ public class StreamableHttpClientTransport( resumptionToken: String?, onResumptionToken: ((String) -> Unit)? = null, ) { + check(initialized.load()) { "Transport is not started" } logger.debug { "Client sending message via POST to $url: ${McpJson.encodeToString(message)}" } // If we have a resumption token, reconnect the SSE stream with it diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractClientTransportLifecycleTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractClientTransportLifecycleTest.kt new file mode 100644 index 0000000..8afc81f --- /dev/null +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractClientTransportLifecycleTest.kt @@ -0,0 +1,86 @@ +package io.modelcontextprotocol.kotlin.sdk.client + +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain +import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport +import io.modelcontextprotocol.kotlin.sdk.types.PingRequest +import io.modelcontextprotocol.kotlin.sdk.types.toJSON +import kotlinx.coroutines.delay +import kotlinx.coroutines.test.runTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.time.Duration.Companion.milliseconds + +abstract class AbstractClientTransportLifecycleTest { + + protected lateinit var transport: T + + @BeforeTest + fun beforeEach() { + transport = createTransport() + } + + @Test + fun `should throw when started twice`() = runTest { + transport.start() + + val exception = shouldThrow { + transport.start() + } + exception.message shouldContain "already started" + } + + @Test + fun `should be idempotent when closed twice`() = runTest { + val transport = createTransport() + + transport.start() + transport.close() + + // Second close should not throw + transport.close() + } + + @Test + fun `should throw when sending before start`() = runTest { + val transport = createTransport() + + val exception = shouldThrow { + transport.send(PingRequest().toJSON()) + } + exception.message shouldContain "not started" + } + + @Test + fun `should throw when sending after close`() = runTest { + val transport = createTransport() + + transport.start() + delay(50.milliseconds) + transport.close() + + shouldThrow { + transport.send(PingRequest().toJSON()) + } + } + + @Test + fun `should call onClose exactly once`() = runTest { + val transport = createTransport() + + var closeCallCount = 0 + transport.onClose { closeCallCount++ } + + transport.start() + delay(50.milliseconds) + + // Multiple close attempts + transport.close() + transport.close() + + closeCallCount shouldBe 1 + } + + protected abstract fun createTransport(): T +} diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/stdio/StdioClientTransportLifecycleTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/stdio/StdioClientTransportLifecycleTest.kt new file mode 100644 index 0000000..6354242 --- /dev/null +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/stdio/StdioClientTransportLifecycleTest.kt @@ -0,0 +1,26 @@ +package io.modelcontextprotocol.kotlin.sdk.client.stdio + +import io.modelcontextprotocol.kotlin.sdk.client.AbstractClientTransportLifecycleTest +import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport +import kotlinx.io.Buffer +import kotlin.test.Ignore +import kotlin.test.Test + +class StdioClientTransportLifecycleTest : AbstractClientTransportLifecycleTest() { + + /** + * Dummy method to make IDE treat this class as a test + */ + @Test + @Ignore + fun dummyTest() = Unit + + override fun createTransport(): StdioClientTransport { + val inputBuffer = Buffer() + val outputBuffer = Buffer() + return StdioClientTransport( + input = inputBuffer, + output = outputBuffer, + ) + } +} diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransportTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt similarity index 98% rename from kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransportTest.kt rename to kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt index 9968ceb..5cf96b6 100644 --- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransportTest.kt +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt @@ -1,4 +1,4 @@ -package io.modelcontextprotocol.kotlin.sdk.client +package io.modelcontextprotocol.kotlin.sdk.client.streamable.http import io.ktor.client.HttpClient import io.ktor.client.engine.mock.MockEngine @@ -12,6 +12,8 @@ import io.ktor.http.HttpStatusCode import io.ktor.http.content.TextContent import io.ktor.http.headersOf import io.ktor.utils.io.ByteReadChannel +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.client.StreamableHttpClientTransport import io.modelcontextprotocol.kotlin.sdk.types.Implementation import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamingHttpClientTransportLifecycleTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamingHttpClientTransportLifecycleTest.kt new file mode 100644 index 0000000..f8ecec5 --- /dev/null +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamingHttpClientTransportLifecycleTest.kt @@ -0,0 +1,43 @@ +package io.modelcontextprotocol.kotlin.sdk.client.streamable.http + +import io.ktor.client.HttpClient +import io.ktor.client.engine.mock.MockEngine +import io.ktor.client.engine.mock.respond +import io.ktor.client.plugins.sse.SSE +import io.ktor.http.ContentType +import io.ktor.http.HttpHeaders +import io.ktor.http.HttpStatusCode +import io.ktor.http.headersOf +import io.modelcontextprotocol.kotlin.sdk.client.AbstractClientTransportLifecycleTest +import io.modelcontextprotocol.kotlin.sdk.client.StreamableHttpClientTransport +import kotlin.test.Ignore +import kotlin.test.Test +import kotlin.time.Duration.Companion.seconds + +class StreamingHttpClientTransportLifecycleTest : + AbstractClientTransportLifecycleTest() { + + /** + * Dummy method to make IDE treat this class as a test + */ + @Test + @Ignore + fun dummyTest() = Unit + + override fun createTransport(): StreamableHttpClientTransport { + val mockEngine = MockEngine { + respond( + "this is not valid json", + status = HttpStatusCode.OK, + headers = headersOf(HttpHeaders.ContentType, ContentType.Application.Json.toString()), + ) + } + val httpClient = HttpClient(mockEngine) { + install(SSE) { + reconnectionTime = 1.seconds + } + } + + return StreamableHttpClientTransport(httpClient, url = "http://localhost:8080/mcp") + } +} diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/stdio/StdioClientTransportErrorHandlingTest.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/stdio/StdioClientTransportErrorHandlingTest.kt new file mode 100644 index 0000000..ba3bb2a --- /dev/null +++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/stdio/StdioClientTransportErrorHandlingTest.kt @@ -0,0 +1,98 @@ +package io.modelcontextprotocol.kotlin.sdk.client.stdio + +import io.kotest.matchers.booleans.shouldBeFalse +import io.kotest.matchers.shouldBe +import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport +import kotlinx.coroutines.delay +import kotlinx.coroutines.test.runTest +import kotlinx.io.Buffer +import kotlinx.io.writeString +import kotlin.concurrent.atomics.AtomicBoolean +import kotlin.concurrent.atomics.ExperimentalAtomicApi +import kotlin.test.Test +import kotlin.time.Duration.Companion.milliseconds + +/** + * Tests for StdioClientTransport error handling: EOF, IO errors, and edge cases. + */ +class StdioClientTransportErrorHandlingTest { + + private lateinit var transport: StdioClientTransport + + @OptIn(ExperimentalAtomicApi::class) + @Test + fun `should continue on stderr EOF`() = runTest { + val stderrBuffer = Buffer() + // Empty stderr = immediate EOF + + val inputBuffer = Buffer() + inputBuffer.writeString("""data: {"jsonrpc":"2.0","method":"ping","id":1}\n\n""") + val outputBuffer = Buffer() + + transport = StdioClientTransport( + input = inputBuffer, + output = outputBuffer, + error = stderrBuffer, + ) + + val closeCalled = AtomicBoolean(false) + transport.onClose { closeCalled.store(true) } + + transport.start() + delay(200.milliseconds) + + // Stderr EOF should not close transport + closeCalled.load() shouldBe false + + transport.close() + closeCalled.load() shouldBe true + } + + @Test + fun `should call onClose exactly once on error scenarios`() = runTest { + val stderrBuffer = Buffer() + stderrBuffer.write("FATAL: critical error\n".encodeToByteArray()) + + val inputBuffer = Buffer() + val outputBuffer = Buffer() + + var closeCallCount = 0 + + transport = StdioClientTransport( + input = inputBuffer, + output = outputBuffer, + error = stderrBuffer, + classifyStderr = { StdioClientTransport.StderrSeverity.FATAL }, + ) + + transport.onClose { closeCallCount++ } + + transport.start() + delay(100.milliseconds) + + // Explicit close after error already closed it + transport.close() + + closeCallCount shouldBe 1 + } + + @Test + fun `should handle empty input gracefully`() = runTest { + val inputBuffer = Buffer() + val outputBuffer = Buffer() + + transport = StdioClientTransport( + input = inputBuffer, + output = outputBuffer, + ) + + var errorCalled = false + transport.onError { errorCalled = true } + + transport.start() + delay(100.milliseconds) + + // Empty input should close cleanly without error + errorCalled.shouldBeFalse() + } +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport.kt index 55e09da..cbab7ae 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport.kt @@ -1,6 +1,6 @@ package io.modelcontextprotocol.kotlin.sdk.shared -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage import kotlinx.coroutines.CompletableDeferred /** diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/BaseTransportTest.kt b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/BaseTransportTest.kt index 1929fac..5de5fd2 100644 --- a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/BaseTransportTest.kt +++ b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/BaseTransportTest.kt @@ -5,10 +5,12 @@ import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage import io.modelcontextprotocol.kotlin.sdk.types.PingRequest import io.modelcontextprotocol.kotlin.sdk.types.toJSON import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.delay import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue import kotlin.test.fail +import kotlin.time.Duration.Companion.seconds abstract class BaseTransportTest { @@ -21,6 +23,8 @@ abstract class BaseTransportTest { transport.onClose { didClose = true } transport.start() + delay(1.seconds) + assertFalse(didClose, "Transport should not be closed immediately after start") transport.close() diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt index f0c7aed..fa2f032 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt @@ -1,13 +1,68 @@ package io.modelcontextprotocol.kotlin.sdk.client import io.modelcontextprotocol.kotlin.sdk.shared.BaseTransportTest +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.McpException +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import kotlinx.io.asSink import kotlinx.io.asSource import kotlinx.io.buffered import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Timeout +import org.junit.jupiter.api.assertThrows +import java.util.concurrent.TimeUnit +import kotlin.concurrent.atomics.AtomicBoolean +import kotlin.concurrent.atomics.ExperimentalAtomicApi +import kotlin.test.assertFalse +import kotlin.test.assertTrue +import kotlin.test.fail +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds +@Timeout(30, unit = TimeUnit.SECONDS) class StdioClientTransportTest : BaseTransportTest() { + + @Test + fun `handle stdio error`(): Unit = runBlocking { + val processBuilder = if (System.getProperty("os.name").lowercase().contains("win")) { + ProcessBuilder("cmd", "/c", "pause 1 && echo simulated error 1>&2 && exit 1") + } else { + ProcessBuilder("sh", "-c", "sleep 1 && echo 'simulated error' >&2 && exit 1") + } + + val process = processBuilder.start() + + val stdin = process.inputStream.asSource().buffered() + val stdout = process.outputStream.asSink().buffered() + val stderr = process.errorStream.asSource().buffered() + + val transport = StdioClientTransport( + input = stdin, + output = stdout, + error = stderr, + ) { + println("💥Ah-oh!, error: \"$it\"") + StdioClientTransport.StderrSeverity.FATAL + } + + val client = Client( + clientInfo = Implementation( + name = "test-client", + version = "1.0", + ), + ) + + // The error in stderr should cause connecting to fail + assertThrows { + client.connect(transport) + } + + process.destroyForcibly() + } + + @OptIn(ExperimentalAtomicApi::class) @Test fun `should start then close cleanly`() = runTest { // Run process "/usr/bin/tee" @@ -16,15 +71,33 @@ class StdioClientTransportTest : BaseTransportTest() { val input = process.inputStream.asSource().buffered() val output = process.outputStream.asSink().buffered() + val error = process.errorStream.asSource().buffered() val transport = StdioClientTransport( input = input, output = output, + error = error, ) - testTransportOpenClose(transport) + transport.onError { error -> + fail("Unexpected error: $error") + } - process.destroy() + val didClose = AtomicBoolean(false) + transport.onClose { didClose.store(true) } + + transport.start() + delay(1.seconds) + + assertFalse(didClose.load(), "Transport should not be closed immediately after start") + + // Destroy process BEFORE close() to unblock stdin reader + process.destroyForcibly() + delay(100.milliseconds) // Give time for EOF to propagate + + transport.close() + + assertTrue(didClose.load(), "Transport should be closed after close() call") } @Test @@ -43,7 +116,7 @@ class StdioClientTransportTest : BaseTransportTest() { testTransportRead(transport) process.waitFor() - process.destroy() + process.destroyForcibly() } @Test @@ -63,6 +136,6 @@ class StdioClientTransportTest : BaseTransportTest() { testTransportRead(transport) process.waitFor() - process.destroy() + process.destroyForcibly() } } diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt index fcab03c..2bdd361 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt @@ -89,7 +89,7 @@ class KotlinClientTsServerEdgeCasesTestStdio : TsTestBase() { } @Test - @Timeout(60, unit = TimeUnit.SECONDS) + @Timeout(30, unit = TimeUnit.SECONDS) fun testConcurrentRequestsOverStdio(): Unit = runBlocking(Dispatchers.IO) { withClientStdio { client: Client, _ -> val concurrentCount = 5 @@ -102,8 +102,7 @@ class KotlinClientTsServerEdgeCasesTestStdio : TsTestBase() { val result = client.callTool("greet", arguments) assertNotNull(result, "Tool call result should not be null for client $i") - val callResult = result - val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent + val textContent = result.content.firstOrNull { it is TextContent } as? TextContent assertNotNull(textContent, "Text content should be present for client $i") textContent.text From bb8de718d93d7cb4511c808092b06637518e7d83 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Thu, 4 Dec 2025 10:37:02 +0200 Subject: [PATCH 091/449] Update samples to use new schema (#443) --- .../sample/client/MCPClient.kt | 12 +++++---- .../sample/server/server.kt | 24 ++++++++--------- .../test/kotlin/SseServerIntegrationTest.kt | 26 +++++++++++-------- 3 files changed, 33 insertions(+), 29 deletions(-) diff --git a/samples/kotlin-mcp-client/src/main/kotlin/io/modelcontextprotocol/sample/client/MCPClient.kt b/samples/kotlin-mcp-client/src/main/kotlin/io/modelcontextprotocol/sample/client/MCPClient.kt index 0a0817e..8360316 100644 --- a/samples/kotlin-mcp-client/src/main/kotlin/io/modelcontextprotocol/sample/client/MCPClient.kt +++ b/samples/kotlin-mcp-client/src/main/kotlin/io/modelcontextprotocol/sample/client/MCPClient.kt @@ -9,12 +9,13 @@ import com.anthropic.models.messages.Tool import com.anthropic.models.messages.ToolUnion import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.databind.ObjectMapper -import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.TextContent import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport +import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.TextContent import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.yield import kotlinx.io.asSink import kotlinx.io.asSource import kotlinx.io.buffered @@ -137,8 +138,8 @@ class MCPClient : AutoCloseable { "type": "tool_result", "tool_name": $toolName, "result": ${ - result?.content?.joinToString("\n") { - (it as TextContent).text ?: "" + result.content.joinToString("\n") { + (it as TextContent).text } } """.trimIndent(), @@ -173,6 +174,7 @@ class MCPClient : AutoCloseable { if (message.lowercase() == "quit") break val response = processQuery(message) println("\n$response") + yield() } } diff --git a/samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/server.kt b/samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/server.kt index a593377..e707743 100644 --- a/samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/server.kt +++ b/samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/server.kt @@ -14,23 +14,22 @@ import io.ktor.server.routing.routing import io.ktor.server.sse.SSE import io.ktor.server.sse.sse import io.ktor.util.collections.ConcurrentMap -import io.modelcontextprotocol.kotlin.sdk.CallToolResult -import io.modelcontextprotocol.kotlin.sdk.GetPromptResult -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.PromptArgument -import io.modelcontextprotocol.kotlin.sdk.PromptMessage -import io.modelcontextprotocol.kotlin.sdk.ReadResourceResult -import io.modelcontextprotocol.kotlin.sdk.Role -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities -import io.modelcontextprotocol.kotlin.sdk.TextContent -import io.modelcontextprotocol.kotlin.sdk.TextResourceContents -import io.modelcontextprotocol.kotlin.sdk.Tool import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions import io.modelcontextprotocol.kotlin.sdk.server.ServerSession import io.modelcontextprotocol.kotlin.sdk.server.SseServerTransport import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport import io.modelcontextprotocol.kotlin.sdk.server.mcp +import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.PromptArgument +import io.modelcontextprotocol.kotlin.sdk.types.PromptMessage +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult +import io.modelcontextprotocol.kotlin.sdk.types.Role +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents import kotlinx.coroutines.Job import kotlinx.coroutines.runBlocking import kotlinx.io.asSink @@ -66,7 +65,7 @@ fun configureServer(): Server { GetPromptResult( messages = listOf( PromptMessage( - role = Role.user, + role = Role.User, content = TextContent( "Develop a kotlin project named ${request.arguments?.get("Project Name")}", ), @@ -80,7 +79,6 @@ fun configureServer(): Server { server.addTool( name = "kotlin-sdk-tool", description = "A test tool", - inputSchema = Tool.Input(), ) { _ -> CallToolResult( content = listOf(TextContent("Hello, world!")), diff --git a/samples/kotlin-mcp-server/src/test/kotlin/SseServerIntegrationTest.kt b/samples/kotlin-mcp-server/src/test/kotlin/SseServerIntegrationTest.kt index 7f8b6a9..9cbedda 100644 --- a/samples/kotlin-mcp-server/src/test/kotlin/SseServerIntegrationTest.kt +++ b/samples/kotlin-mcp-server/src/test/kotlin/SseServerIntegrationTest.kt @@ -1,13 +1,14 @@ -import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject -import io.modelcontextprotocol.kotlin.sdk.ReadResourceRequest -import io.modelcontextprotocol.kotlin.sdk.TextContent -import io.modelcontextprotocol.kotlin.sdk.TextResourceContents import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents import kotlinx.coroutines.runBlocking import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs import kotlin.test.assertNotNull +import kotlin.test.assertNull import kotlin.test.assertTrue class SseServerIntegrationTest { @@ -20,7 +21,7 @@ class SseServerIntegrationTest { val listToolsResult = client.listTools() // then - assertEquals(expected = EmptyJsonObject, actual = listToolsResult._meta) + assertNull(listToolsResult.meta) val tools = listToolsResult.tools assertEquals(actual = tools.size, expected = 1) @@ -33,7 +34,7 @@ class SseServerIntegrationTest { val listPromptsResult = client.listPrompts() // then - assertEquals(expected = EmptyJsonObject, actual = listPromptsResult._meta) + assertNull(listPromptsResult.meta) val prompts = listPromptsResult.prompts @@ -45,7 +46,7 @@ class SseServerIntegrationTest { val listResourcesResult = client.listResources() // then - assertEquals(expected = EmptyJsonObject, actual = listResourcesResult._meta) + assertNull(listResourcesResult.meta) val resources = listResourcesResult.resources assertEquals(expected = listOf("Web Search"), actual = resources.map { it.name }) @@ -55,11 +56,11 @@ class SseServerIntegrationTest { fun `should get resource`(): Unit = runBlocking { val testResourceUri = "https://search.com/" val getResourcesResult = client.readResource( - ReadResourceRequest(uri = testResourceUri), + ReadResourceRequest(ReadResourceRequestParams(uri = testResourceUri)), ) // then - assertEquals(expected = EmptyJsonObject, actual = getResourcesResult._meta) + assertEquals(expected = null, actual = getResourcesResult.meta) val contents = getResourcesResult.contents assertEquals(expected = 1, actual = contents.size) assertTrue { @@ -72,11 +73,14 @@ class SseServerIntegrationTest { @Test fun `should call tool`(): Unit = runBlocking { // when - val toolResult = client.callTool("kotlin-sdk-tool", EmptyJsonObject) + val toolResult = client.callTool( + name = "kotlin-sdk-tool", + arguments = emptyMap(), + ) // then assertNotNull(toolResult) - assertEquals(expected = EmptyJsonObject, actual = toolResult._meta) + assertNull(toolResult.meta) val content = toolResult.content.single() assertIs(content, "Tool result should be a text content") From 6ec6d3c52fcad6ef391b39654ad7d6850ca27d87 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Thu, 4 Dec 2025 15:09:54 +0200 Subject: [PATCH 092/449] Better exception handling in StdioClientTransport (#444) # Better exception handling in StdioClientTransport Refactor `McpException` for improved handling with convenience constructors and enhanced exception wrapping logic in `StdioClientTransport`. Update tests to use `kotest` matchers and add JUnit parameterized tests for exception handling. ## Motivation and Context #442 follow up ## How Has This Been Tested? ## Breaking Changes ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update ## Checklist - [ ] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [ ] My code follows the repository's style guidelines - [ ] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed ## Additional context --- gradle/libs.versions.toml | 2 + kotlin-sdk-client/build.gradle.kts | 1 + .../kotlin/sdk/client/StdioClientTransport.kt | 23 ++++-- .../StdioClientTransportErrorHandlingTest.kt | 78 +++++++++++++++++++ kotlin-sdk-core/api/kotlin-sdk-core.api | 5 +- .../kotlin/sdk/types/McpException.kt | 17 ++-- .../kotlin/AbstractPromptIntegrationTest.kt | 10 ++- 7 files changed, 121 insertions(+), 15 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0b31051..c1d6773 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,6 +22,7 @@ mockk = "1.14.6" mokksy = "0.6.2" serialization = "1.9.0" slf4j = "2.0.17" +junit="6.0.1" [libraries] # Plugins @@ -58,6 +59,7 @@ mockk = { module = "io.mockk:mockk", version.ref = "mockk" } mokksy = { group = "dev.mokksy", name = "mokksy", version.ref = "mokksy" } netty-bom = { group = "io.netty", name = "netty-bom", version.ref = "netty" } slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" } +junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit" } # Samples ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio", version.ref = "ktor" } diff --git a/kotlin-sdk-client/build.gradle.kts b/kotlin-sdk-client/build.gradle.kts index 4755ac7..36a3ed8 100644 --- a/kotlin-sdk-client/build.gradle.kts +++ b/kotlin-sdk-client/build.gradle.kts @@ -54,6 +54,7 @@ kotlin { implementation(libs.awaitility) implementation(libs.ktor.client.apache5) implementation(libs.mockk) + implementation(libs.junit.jupiter.params) implementation(libs.mokksy) implementation(dependencies.platform(libs.netty.bom)) runtimeOnly(libs.slf4j.simple) diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt index b6aa827..66d43f7 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt @@ -15,6 +15,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage import io.modelcontextprotocol.kotlin.sdk.types.McpException import io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.CONNECTION_CLOSED import io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.INTERNAL_ERROR +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -71,12 +72,14 @@ import kotlin.jvm.JvmOverloads * @param input The input stream where messages are received. * @param output The output stream where messages are sent. * @param error Optional error stream for stderr monitoring. - * @param sendChannel Channel for outbound messages. Default: buffered channel (capacity 64). + * @param sendChannel Channel for outbound messages. Default: buffered channel + * (implementation-default capacity). * @param classifyStderr Callback to classify stderr lines. Return [StderrSeverity.FATAL] to fail transport, * or [StderrSeverity.WARNING] / [StderrSeverity.INFO] / [StderrSeverity.DEBUG] * to log, or [StderrSeverity.IGNORE] to discard. * Default value: [StderrSeverity.DEBUG]. - * @see MCP Specification + * @see MCP Specification + * @see [Channel.BUFFERED] */ @OptIn(ExperimentalAtomicApi::class) public class StdioClientTransport @JvmOverloads public constructor( @@ -232,15 +235,25 @@ public class StdioClientTransport @JvmOverloads public constructor( @Suppress("TooGenericExceptionCaught", "SwallowedException") try { sendChannel.send(message) + } catch (e: CancellationException) { + throw e // MUST rethrow immediately - don't log, don't wrap } catch (e: ClosedSendChannelException) { logger.debug(e) { "Cannot send message: transport is closed" } - throw McpException(CONNECTION_CLOSED, "Transport is closed") + throw McpException( + code = CONNECTION_CLOSED, + message = "Transport is closed", + cause = e, + ) } catch (e: McpException) { logger.debug(e) { "Error while sending message: ${e.message}" } throw e - } catch (e: Exception) { + } catch (e: Throwable) { logger.error(e) { "Error while sending message: ${e.message}" } - throw McpException(INTERNAL_ERROR, "Error while sending message: ${e.message}") + throw McpException( + code = INTERNAL_ERROR, + message = "Error while sending message: ${e.message}", + cause = e, + ) } } diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/stdio/StdioClientTransportErrorHandlingTest.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/stdio/StdioClientTransportErrorHandlingTest.kt index ba3bb2a..43a8b04 100644 --- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/stdio/StdioClientTransportErrorHandlingTest.kt +++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/stdio/StdioClientTransportErrorHandlingTest.kt @@ -1,12 +1,28 @@ package io.modelcontextprotocol.kotlin.sdk.client.stdio +import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.booleans.shouldBeFalse import io.kotest.matchers.shouldBe +import io.kotest.matchers.types.shouldBeInstanceOf +import io.kotest.matchers.types.shouldBeSameInstanceAs +import io.mockk.coEvery +import io.mockk.mockk import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest +import io.modelcontextprotocol.kotlin.sdk.types.McpException +import io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.ClosedSendChannelException import kotlinx.coroutines.delay import kotlinx.coroutines.test.runTest import kotlinx.io.Buffer import kotlinx.io.writeString +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import java.util.stream.Stream import kotlin.concurrent.atomics.AtomicBoolean import kotlin.concurrent.atomics.ExperimentalAtomicApi import kotlin.test.Test @@ -95,4 +111,66 @@ class StdioClientTransportErrorHandlingTest { // Empty input should close cleanly without error errorCalled.shouldBeFalse() } + + companion object { + @JvmStatic + fun exceptions(): Stream = Stream.of( + Arguments.of( + CancellationException(), + false, // should not wrap, propagate + null, + ), + Arguments.of( + McpException(-1, "dummy"), + false, // should not wrap, propagate + null, + ), + Arguments.of( + ClosedSendChannelException("dummy"), + true, // should wrap in McpException + ErrorCode.CONNECTION_CLOSED, + ), + Arguments.of( + Exception(), + true, + ErrorCode.INTERNAL_ERROR, + ), + Arguments.of( + OutOfMemoryError(), + true, + ErrorCode.INTERNAL_ERROR, + ), + + ) + } + + @ParameterizedTest + @MethodSource("exceptions") + fun `Send should handle exceptions`(throwable: Throwable, shouldWrap: Boolean, expectedCode: Int?) = runTest { + val sendChannel: Channel = mockk(relaxed = true) + + transport = StdioClientTransport( + input = Buffer(), + output = Buffer(), + sendChannel = sendChannel, + ) + + coEvery { sendChannel.send(any()) } throws throwable + + transport.start() + + // Cancel the coroutine while it's suspended in send() + val exception = shouldThrow { + transport.send(JSONRPCRequest(id = "test-1", method = "test/method")) + } + + if (shouldWrap) { + exception.shouldBeInstanceOf { + it.cause shouldBeSameInstanceAs throwable + it.code shouldBe expectedCode + } + } else { + exception shouldBeSameInstanceAs throwable + } + } } diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index 8694e2d..49b88cb 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -2654,11 +2654,12 @@ public abstract interface annotation class io/modelcontextprotocol/kotlin/sdk/ty } public final class io/modelcontextprotocol/kotlin/sdk/types/McpException : java/lang/Exception { + public fun (ILjava/lang/String;)V public fun (ILjava/lang/String;Lkotlinx/serialization/json/JsonElement;)V - public synthetic fun (ILjava/lang/String;Lkotlinx/serialization/json/JsonElement;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (ILjava/lang/String;Lkotlinx/serialization/json/JsonElement;Ljava/lang/Throwable;)V + public synthetic fun (ILjava/lang/String;Lkotlinx/serialization/json/JsonElement;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getCode ()I public final fun getData ()Lkotlinx/serialization/json/JsonElement; - public fun getMessage ()Ljava/lang/String; } public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/MediaContent : io/modelcontextprotocol/kotlin/sdk/types/ContentBlock { diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/McpException.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/McpException.kt index fb6e1ee..f63f4a9 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/McpException.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/McpException.kt @@ -1,14 +1,19 @@ package io.modelcontextprotocol.kotlin.sdk.types import kotlinx.serialization.json.JsonElement +import kotlin.jvm.JvmOverloads /** * Represents an error specific to the MCP protocol. * - * @property code The error code. - * @property message The error message. - * @property data Additional error data as a JSON object. + * @property code The MCP/JSON‑RPC error code. + * @property data Optional additional error payload as a JSON element; `null` when not provided. + * @param message The error message. + * @param cause The original cause. */ -public class McpException(public val code: Int, message: String, public val data: JsonElement? = null) : Exception() { - override val message: String = "MCP error $code: $message" -} +public class McpException @JvmOverloads public constructor( + public val code: Int, + message: String, + public val data: JsonElement? = null, + cause: Throwable? = null, +) : Exception("MCP error $code: $message", cause) diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt index b66ac7b..fbaca72 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt @@ -1,5 +1,7 @@ package io.modelcontextprotocol.kotlin.sdk.integration.kotlin +import io.kotest.assertions.withClue +import io.kotest.matchers.string.shouldContain import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequestParams import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult @@ -391,7 +393,9 @@ abstract class AbstractPromptIntegrationTest : KotlinTestBase() { } } - assertTrue(exception.message.contains("requiredArg2"), "Exception should mention the missing argument") + withClue("Exception should mention the missing argument") { + exception.message shouldContain "requiredArg2" + } // test with no args val exception2 = assertThrows { @@ -407,7 +411,9 @@ abstract class AbstractPromptIntegrationTest : KotlinTestBase() { } } - assertTrue(exception2.message.contains("requiredArg"), "Exception should mention a missing required argument") + withClue("Exception should mention a missing required argument") { + exception2.message shouldContain "requiredArg" + } // test with all required args val result = client.getPrompt( From 98a9ac0b9307d4d063991d4ff28de6a7718dc55d Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:35:17 +0200 Subject: [PATCH 093/449] Prepare for release version 0.8.1 (#445) # Prepare for release version 0.8.1 Bump mcp kotlin-sdk to 0.8.1 ## How Has This Been Tested? Regression tests on CI ## Breaking Changes ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 2e46c84..b594654 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,4 +14,4 @@ kotlin.mpp.enableCInteropCommonization=true kotlin.native.ignoreDisabledTargets=true group=io.modelcontextprotocol -version=0.8.1-SNAPSHOT +version=0.8.1 From 2dd468832379cef07c696c94aa0dca8bfd732c95 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Thu, 4 Dec 2025 18:22:48 +0200 Subject: [PATCH 094/449] Prepare for next development iteration (#447) # Prepare for next development iteration Bump mcp kotlin-sdk to 0.8.1 ## How Has This Been Tested? Regression ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [x] CI update ## Checklist - [ ] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [ ] My code follows the repository's style guidelines - [ ] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed ## Additional context --- gradle.properties | 2 +- .../gradle/libs.versions.toml | 2 +- .../gradle/libs.versions.toml | 2 +- samples/notebooks/McpClient.ipynb | 88 +++++++++---------- .../gradle/libs.versions.toml | 2 +- 5 files changed, 48 insertions(+), 48 deletions(-) diff --git a/gradle.properties b/gradle.properties index b594654..7182771 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,4 +14,4 @@ kotlin.mpp.enableCInteropCommonization=true kotlin.native.ignoreDisabledTargets=true group=io.modelcontextprotocol -version=0.8.1 +version=0.8.2-SNAPSHOT diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index d4e2f73..fa8b4e9 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -2,7 +2,7 @@ anthropic = "2.9.0" kotlin = "2.2.21" ktor = "3.2.3" -mcp-kotlin = "0.8.0" +mcp-kotlin = "0.8.1" shadow = "9.2.2" slf4j = "2.0.17" diff --git a/samples/kotlin-mcp-server/gradle/libs.versions.toml b/samples/kotlin-mcp-server/gradle/libs.versions.toml index cbcd17d..0ce6d58 100644 --- a/samples/kotlin-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-server/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] kotlin = "2.2.21" ktor = "3.2.3" -mcp-kotlin = "0.8.0" +mcp-kotlin = "0.8.1" slf4j = "2.0.17" shadow = "9.2.2" diff --git a/samples/notebooks/McpClient.ipynb b/samples/notebooks/McpClient.ipynb index 2ec80e0..583039d 100644 --- a/samples/notebooks/McpClient.ipynb +++ b/samples/notebooks/McpClient.ipynb @@ -19,8 +19,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-11-21T09:59:29.552681Z", - "start_time": "2025-11-21T09:59:29.425830Z" + "end_time": "2025-12-04T16:08:57.281786Z", + "start_time": "2025-12-04T16:08:57.126434Z" } }, "cell_type": "code", @@ -30,7 +30,7 @@ "\n", "USE {\n", " dependencies(\n", - " \"io.modelcontextprotocol:kotlin-sdk-client-jvm:0.8.0\",\n", + " \"io.modelcontextprotocol:kotlin-sdk-client-jvm:0.8.1\",\n", " )\n", "}" ], @@ -40,16 +40,16 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-11-21T09:59:29.570750Z", - "start_time": "2025-11-21T09:59:29.556538Z" + "end_time": "2025-12-04T16:08:57.298752Z", + "start_time": "2025-12-04T16:08:57.285099Z" } }, "cell_type": "code", "source": [ "import kotlinx.coroutines.runBlocking\n", "import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject\n", - "import io.modelcontextprotocol.kotlin.sdk.Implementation\n", - "import io.modelcontextprotocol.kotlin.sdk.TextContent\n", + "import io.modelcontextprotocol.kotlin.sdk.types.Implementation\n", + "import io.modelcontextprotocol.kotlin.sdk.types.TextContent\n", "import io.modelcontextprotocol.kotlin.sdk.client.Client\n", "import io.modelcontextprotocol.kotlin.sdk.client.ClientOptions\n", "import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport\n", @@ -76,8 +76,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-11-21T09:59:29.621332Z", - "start_time": "2025-11-21T09:59:29.573344Z" + "end_time": "2025-12-04T16:08:57.346926Z", + "start_time": "2025-12-04T16:08:57.301809Z" } }, "cell_type": "code", @@ -105,8 +105,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-11-21T09:59:29.649304Z", - "start_time": "2025-11-21T09:59:29.624152Z" + "end_time": "2025-12-04T16:08:57.375128Z", + "start_time": "2025-12-04T16:08:57.350611Z" } }, "cell_type": "code", @@ -130,8 +130,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-11-21T09:59:30.691125Z", - "start_time": "2025-11-21T09:59:29.652092Z" + "end_time": "2025-12-04T16:08:58.199710Z", + "start_time": "2025-12-04T16:08:57.379661Z" } }, "cell_type": "code", @@ -162,7 +162,7 @@ "-> Content-Type: application/json\n", "BODY Content-Type: application/json\n", "BODY START\n", - "{\"id\":\"7eed280ffa794c2fa0ae38447d64d516\",\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2025-06-18\",\"capabilities\":{},\"clientInfo\":{\"name\":\"my-client\",\"version\":\"1.0.0\"}},\"jsonrpc\":\"2.0\"}\n", + "{\"id\":\"26884bad1c9b4ea0b9c4470f893b60d3\",\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2025-06-18\",\"capabilities\":{},\"clientInfo\":{\"name\":\"my-client\",\"version\":\"1.0.0\"}},\"jsonrpc\":\"2.0\"}\n", "BODY END" ] }, @@ -185,8 +185,8 @@ "-> Content-Encoding: identity\n", "-> Content-Length: 1947\n", "-> Content-Type: text/event-stream\n", - "-> Date: Fri, 21 Nov 2025 09:59:30 GMT\n", - "-> Expires: Fri, 21 Nov 2025 09:59:30 GMT\n", + "-> Date: Thu, 04 Dec 2025 16:08:57 GMT\n", + "-> Expires: Thu, 04 Dec 2025 16:08:57 GMT\n", "-> Mcp-Session-Id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n", "-> Request-Context: appId=cid-v1:ccd19013-8f20-4877-8d3f-84a0107e7119\n", "-> Set-Cookie: ASLBSA=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; Path=/; Secure; HttpOnly;; ASLBSACORS=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; SameSite=none; Path=/; Secure; HttpOnly;\n", @@ -195,11 +195,11 @@ "-> X-Powered-By: ASP.NET\n", "-> nel: {\"report_to\":\"network-errors\",\"max_age\":604800,\"success_fraction\":0.01,\"failure_fraction\":1.0}\n", "-> report-to: {\"group\":\"network-errors\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://mdec.nelreports.net/api/report?cat=mdocs\"}]}\n", - "-> x-azure-ref: 20251121T095929Z-15dc879c854ch4blhC1FRA4w400000001650000000006yfy\n", + "-> x-azure-ref: 20251204T160857Z-r1cfc47d796grb7bhC1STOs2rc0000002u20000000002xwb\n", "BODY Content-Type: text/event-stream\n", "BODY START\n", "event: message\n", - "data: {\"result\":{\"protocolVersion\":\"2025-06-18\",\"capabilities\":{\"logging\":{},\"prompts\":{},\"resources\":{},\"tools\":{\"listChanged\":true}},\"serverInfo\":{\"name\":\"Microsoft Learn MCP Server\",\"version\":\"1.0.0\"},\"instructions\":\"# Microsoft Learn MCP Server\\r\\n\\r\\nThis server gives structured access to official Microsoft and Azure documentation via three tools:\\r\\n\\r\\n## Tools\\r\\n\\r\\n### microsoft_docs_search\\r\\nSearch official documentation and return up to 10 concise, high-quality content chunks (max 500 tokens each), including title, URL, and excerpt.\\r\\n\\r\\n- Use first to get a quick, reliable overview\\r\\n- Ideal for grounding answers in Microsoft knowledge\\r\\n\\r\\n### microsoft_code_sample_search\\r\\nSearch for code snippets and examples in official Microsoft Learn documentation and return up to 20 relevant, high-quality code samples.\\r\\n\\r\\n- Use when you need to provide sample Microsoft/Azure related code in your answers. \\r\\n- Ideal for generating code snippets or practical implementation examples. \\r\\n- Optional parameter \\u0060language\\u0060 can filter results. \\r\\n\\r\\n### microsoft_docs_fetch\\r\\nFetch and convert full Microsoft documentation pages to markdown.\\r\\n\\r\\n- Use after search when you need full content from a specific URL\\r\\n- Required for detailed tutorials, troubleshooting, prerequisites, code samples, or when search results are incomplete or outdated\\r\\n\\r\\n## Workflow\\r\\n\\r\\n1. Use \\u0060microsoft_docs_search\\u0060 to find relevant documents.\\r\\n2. If you need code examples or practical snippets, use \\u0060microsoft_code_sample_search\\u0060.\\r\\n3. If deeper or complete information is needed, use \\u0060microsoft_docs_fetch\\u0060.\\r\\n\\r\\n**Search gives breadth. Code Sample Search gives practical examples. Fetch gives depth.**\\r\\n\\r\\nAll content comes from Microsoft Learn or official sources, returned in clean markdown format.\"},\"id\":\"7eed280ffa794c2fa0ae38447d64d516\",\"jsonrpc\":\"2.0\"}\n", + "data: {\"result\":{\"protocolVersion\":\"2025-06-18\",\"capabilities\":{\"logging\":{},\"prompts\":{},\"resources\":{},\"tools\":{\"listChanged\":true}},\"serverInfo\":{\"name\":\"Microsoft Learn MCP Server\",\"version\":\"1.0.0\"},\"instructions\":\"# Microsoft Learn MCP Server\\r\\n\\r\\nThis server gives structured access to official Microsoft and Azure documentation via three tools:\\r\\n\\r\\n## Tools\\r\\n\\r\\n### microsoft_docs_search\\r\\nSearch official documentation and return up to 10 concise, high-quality content chunks (max 500 tokens each), including title, URL, and excerpt.\\r\\n\\r\\n- Use first to get a quick, reliable overview\\r\\n- Ideal for grounding answers in Microsoft knowledge\\r\\n\\r\\n### microsoft_code_sample_search\\r\\nSearch for code snippets and examples in official Microsoft Learn documentation and return up to 20 relevant, high-quality code samples.\\r\\n\\r\\n- Use when you need to provide sample Microsoft/Azure related code in your answers. \\r\\n- Ideal for generating code snippets or practical implementation examples. \\r\\n- Optional parameter \\u0060language\\u0060 can filter results. \\r\\n\\r\\n### microsoft_docs_fetch\\r\\nFetch and convert full Microsoft documentation pages to markdown.\\r\\n\\r\\n- Use after search when you need full content from a specific URL\\r\\n- Required for detailed tutorials, troubleshooting, prerequisites, code samples, or when search results are incomplete or outdated\\r\\n\\r\\n## Workflow\\r\\n\\r\\n1. Use \\u0060microsoft_docs_search\\u0060 to find relevant documents.\\r\\n2. If you need code examples or practical snippets, use \\u0060microsoft_code_sample_search\\u0060.\\r\\n3. If deeper or complete information is needed, use \\u0060microsoft_docs_fetch\\u0060.\\r\\n\\r\\n**Search gives breadth. Code Sample Search gives practical examples. Fetch gives depth.**\\r\\n\\r\\nAll content comes from Microsoft Learn or official sources, returned in clean markdown format.\"},\"id\":\"26884bad1c9b4ea0b9c4470f893b60d3\",\"jsonrpc\":\"2.0\"}\n", "\n", "\n", "BODY END" @@ -247,8 +247,8 @@ "-> Connection: keep-alive\n", "-> Content-Encoding: identity\n", "-> Content-Length: 0\n", - "-> Date: Fri, 21 Nov 2025 09:59:30 GMT\n", - "-> Expires: Fri, 21 Nov 2025 09:59:30 GMT\n", + "-> Date: Thu, 04 Dec 2025 16:08:57 GMT\n", + "-> Expires: Thu, 04 Dec 2025 16:08:57 GMT\n", "-> Mcp-Session-Id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n", "-> Request-Context: appId=cid-v1:ccd19013-8f20-4877-8d3f-84a0107e7119\n", "-> Set-Cookie: ASLBSA=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; Path=/; Secure; HttpOnly;; ASLBSACORS=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; SameSite=none; Path=/; Secure; HttpOnly;\n", @@ -257,7 +257,7 @@ "-> X-Powered-By: ASP.NET\n", "-> nel: {\"report_to\":\"network-errors\",\"max_age\":604800,\"success_fraction\":0.01,\"failure_fraction\":1.0}\n", "-> report-to: {\"group\":\"network-errors\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://mdec.nelreports.net/api/report?cat=mdocs\"}]}\n", - "-> x-azure-ref: 20251121T095930Z-16f77b7fcffgtcqwhC1FRAd56w00000001h000000000hyt7\n", + "-> x-azure-ref: 20251204T160857Z-r1cfc47d796r8qqwhC1STO91g00000001p6g000000002rdg\n", "BODY Content-Type: null\n", "BODY START\n", "\n", @@ -314,8 +314,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-11-21T09:59:31.051698Z", - "start_time": "2025-11-21T09:59:30.701471Z" + "end_time": "2025-12-04T16:08:58.507109Z", + "start_time": "2025-12-04T16:08:58.206412Z" } }, "cell_type": "code", @@ -339,7 +339,7 @@ "-> Content-Type: application/json\n", "BODY Content-Type: application/json\n", "BODY START\n", - "{\"id\":\"e9a2ae55b3a74fc2aeb1c3baef8e550b\",\"method\":\"ping\",\"jsonrpc\":\"2.0\"}\n", + "{\"id\":\"caf63f9816a44766a926944329e987d5\",\"method\":\"ping\",\"jsonrpc\":\"2.0\"}\n", "BODY END" ] }, @@ -362,8 +362,8 @@ "-> Content-Encoding: identity\n", "-> Content-Length: 92\n", "-> Content-Type: text/event-stream\n", - "-> Date: Fri, 21 Nov 2025 09:59:31 GMT\n", - "-> Expires: Fri, 21 Nov 2025 09:59:31 GMT\n", + "-> Date: Thu, 04 Dec 2025 16:08:58 GMT\n", + "-> Expires: Thu, 04 Dec 2025 16:08:58 GMT\n", "-> Mcp-Session-Id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n", "-> Request-Context: appId=cid-v1:ccd19013-8f20-4877-8d3f-84a0107e7119\n", "-> Set-Cookie: ASLBSA=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; Path=/; Secure; HttpOnly;; ASLBSACORS=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; SameSite=none; Path=/; Secure; HttpOnly;\n", @@ -372,11 +372,11 @@ "-> X-Powered-By: ASP.NET\n", "-> nel: {\"report_to\":\"network-errors\",\"max_age\":604800,\"success_fraction\":0.01,\"failure_fraction\":1.0}\n", "-> report-to: {\"group\":\"network-errors\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://mdec.nelreports.net/api/report?cat=mdocs\"}]}\n", - "-> x-azure-ref: 20251121T095930Z-15dc879c854ch4blhC1FRA4w400000001650000000006ymf\n", + "-> x-azure-ref: 20251204T160858Z-r195dfd6579k9c28hC1STOn4n80000000nk0000000006rvx\n", "BODY Content-Type: text/event-stream\n", "BODY START\n", "event: message\n", - "data: {\"result\":{},\"id\":\"e9a2ae55b3a74fc2aeb1c3baef8e550b\",\"jsonrpc\":\"2.0\"}\n", + "data: {\"result\":{},\"id\":\"caf63f9816a44766a926944329e987d5\",\"jsonrpc\":\"2.0\"}\n", "\n", "\n", "BODY END" @@ -409,8 +409,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-11-21T09:59:31.459319Z", - "start_time": "2025-11-21T09:59:31.088064Z" + "end_time": "2025-12-04T16:08:58.818668Z", + "start_time": "2025-12-04T16:08:58.530569Z" } }, "cell_type": "code", @@ -438,7 +438,7 @@ "-> Content-Type: application/json\n", "BODY Content-Type: application/json\n", "BODY START\n", - "{\"id\":\"c3bd8dfe53c14365a926b9b49de4697e\",\"method\":\"tools/list\",\"jsonrpc\":\"2.0\"}\n", + "{\"id\":\"1c46ee9521954378958971e267d543fc\",\"method\":\"tools/list\",\"jsonrpc\":\"2.0\"}\n", "BODY END" ] }, @@ -461,8 +461,8 @@ "-> Content-Encoding: identity\n", "-> Content-Length: 4495\n", "-> Content-Type: text/event-stream\n", - "-> Date: Fri, 21 Nov 2025 09:59:31 GMT\n", - "-> Expires: Fri, 21 Nov 2025 09:59:31 GMT\n", + "-> Date: Thu, 04 Dec 2025 16:08:58 GMT\n", + "-> Expires: Thu, 04 Dec 2025 16:08:58 GMT\n", "-> Mcp-Session-Id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n", "-> Request-Context: appId=cid-v1:ccd19013-8f20-4877-8d3f-84a0107e7119\n", "-> Set-Cookie: ASLBSA=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; Path=/; Secure; HttpOnly;; ASLBSACORS=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; SameSite=none; Path=/; Secure; HttpOnly;\n", @@ -471,11 +471,11 @@ "-> X-Powered-By: ASP.NET\n", "-> nel: {\"report_to\":\"network-errors\",\"max_age\":604800,\"success_fraction\":0.01,\"failure_fraction\":1.0}\n", "-> report-to: {\"group\":\"network-errors\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://mdec.nelreports.net/api/report?cat=mdocs\"}]}\n", - "-> x-azure-ref: 20251121T095931Z-15dc879c8542qpsrhC1FRAgxhg0000000uwg000000002y7h\n", + "-> x-azure-ref: 20251204T160858Z-r195dfd6579k9c28hC1STOn4n80000000nk0000000006rw8\n", "BODY Content-Type: text/event-stream\n", "BODY START\n", "event: message\n", - "data: {\"result\":{\"tools\":[{\"name\":\"microsoft_docs_search\",\"title\":\"Microsoft Docs Search\",\"description\":\"Search official Microsoft/Azure documentation to find the most relevant and trustworthy content for a user\\u0027s query. This tool returns up to 10 high-quality content chunks (each max 500 tokens), extracted from Microsoft Learn and other official sources. Each result includes the article title, URL, and a self-contained content excerpt optimized for fast retrieval and reasoning. Always use this tool to quickly ground your answers in accurate, first-party Microsoft/Azure knowledge.\\n\\nThe \\u0060question\\u0060 parameter is no longer used, use \\u0060query\\u0060 instead.\\n\\n## Follow-up Pattern\\nTo ensure completeness, use microsoft_docs_fetch when high-value pages are identified by search. The fetch tool complements search by providing the full detail. This is a required step for comprehensive results.\",\"inputSchema\":{\"type\":\"object\",\"properties\":{\"query\":{\"description\":\"a query or topic about Microsoft/Azure products, services, platforms, developer tools, frameworks, or APIs\",\"type\":\"string\",\"default\":null},\"question\":{\"description\":\"this parameter is no longer used, use query instead.\",\"type\":\"string\",\"default\":null}}},\"annotations\":{\"title\":\"Microsoft Docs Search\",\"destructiveHint\":false,\"idempotentHint\":true,\"readOnlyHint\":true}},{\"name\":\"microsoft_code_sample_search\",\"title\":\"Microsoft Code Sample Search\",\"description\":\"Search for code snippets and examples in official Microsoft Learn documentation. This tool retrieves relevant code samples from Microsoft documentation pages providing developers with practical implementation examples and best practices for Microsoft/Azure products and services related coding tasks. This tool will help you use the **LATEST OFFICIAL** code snippets to empower coding capabilities.\\n\\n## When to Use This Tool\\n- When you are going to provide sample Microsoft/Azure related code snippets in your answers.\\n- When you are **generating any Microsoft/Azure related code**.\\n\\n## Usage Pattern\\nInput a descriptive query, or SDK/class/method name to retrieve related code samples. The optional parameter \\u0060language\\u0060 can help to filter results.\\n\\nEligible values for \\u0060language\\u0060 parameter include: csharp javascript typescript python powershell azurecli al sql java kusto cpp go rust ruby php\",\"inputSchema\":{\"type\":\"object\",\"properties\":{\"query\":{\"description\":\"a descriptive query, SDK name, method name or code snippet related to Microsoft/Azure products, services, platforms, developer tools, frameworks, APIs or SDKs\",\"type\":\"string\"},\"language\":{\"description\":\"Optional parameter specifying the programming language of code snippets to retrieve. Can significantly improve search quality if provided. Eligible values: csharp javascript typescript python powershell azurecli al sql java kusto cpp go rust ruby php\",\"type\":\"string\",\"default\":null}},\"required\":[\"query\"]},\"annotations\":{\"title\":\"Microsoft Code Sample Search\",\"destructiveHint\":false,\"idempotentHint\":true,\"readOnlyHint\":true}},{\"name\":\"microsoft_docs_fetch\",\"title\":\"Microsoft Docs Fetch\",\"description\":\"Fetch and convert a Microsoft Learn documentation page to markdown format. This tool retrieves the latest complete content of Microsoft documentation pages including Azure, .NET, Microsoft 365, and other Microsoft technologies.\\n\\n## When to Use This Tool\\n- When search results provide incomplete information or truncated content\\n- When you need complete step-by-step procedures or tutorials\\n- When you need troubleshooting sections, prerequisites, or detailed explanations\\n- When search results reference a specific page that seems highly relevant\\n- For comprehensive guides that require full context\\n\\n## Usage Pattern\\nUse this tool AFTER microsoft_docs_search when you identify specific high-value pages that need complete content. The search tool gives you an overview; this tool gives you the complete picture.\\n\\n## URL Requirements\\n- The URL must be a valid link from the microsoft.com domain.\\n\\n## Output Format\\nmarkdown with headings, code blocks, tables, and links preserved.\",\"inputSchema\":{\"type\":\"object\",\"properties\":{\"url\":{\"description\":\"URL of the Microsoft documentation page to read\",\"type\":\"string\"}},\"required\":[\"url\"]},\"annotations\":{\"title\":\"Microsoft Docs Fetch\",\"destructiveHint\":false,\"idempotentHint\":true,\"readOnlyHint\":true}}]},\"id\":\"c3bd8dfe53c14365a926b9b49de4697e\",\"jsonrpc\":\"2.0\"}\n", + "data: {\"result\":{\"tools\":[{\"name\":\"microsoft_docs_search\",\"title\":\"Microsoft Docs Search\",\"description\":\"Search official Microsoft/Azure documentation to find the most relevant and trustworthy content for a user\\u0027s query. This tool returns up to 10 high-quality content chunks (each max 500 tokens), extracted from Microsoft Learn and other official sources. Each result includes the article title, URL, and a self-contained content excerpt optimized for fast retrieval and reasoning. Always use this tool to quickly ground your answers in accurate, first-party Microsoft/Azure knowledge.\\n\\nThe \\u0060question\\u0060 parameter is no longer used, use \\u0060query\\u0060 instead.\\n\\n## Follow-up Pattern\\nTo ensure completeness, use microsoft_docs_fetch when high-value pages are identified by search. The fetch tool complements search by providing the full detail. This is a required step for comprehensive results.\",\"inputSchema\":{\"type\":\"object\",\"properties\":{\"query\":{\"description\":\"a query or topic about Microsoft/Azure products, services, platforms, developer tools, frameworks, or APIs\",\"type\":\"string\",\"default\":null},\"question\":{\"description\":\"this parameter is no longer used, use query instead.\",\"type\":\"string\",\"default\":null}}},\"annotations\":{\"title\":\"Microsoft Docs Search\",\"destructiveHint\":false,\"idempotentHint\":true,\"readOnlyHint\":true}},{\"name\":\"microsoft_code_sample_search\",\"title\":\"Microsoft Code Sample Search\",\"description\":\"Search for code snippets and examples in official Microsoft Learn documentation. This tool retrieves relevant code samples from Microsoft documentation pages providing developers with practical implementation examples and best practices for Microsoft/Azure products and services related coding tasks. This tool will help you use the **LATEST OFFICIAL** code snippets to empower coding capabilities.\\n\\n## When to Use This Tool\\n- When you are going to provide sample Microsoft/Azure related code snippets in your answers.\\n- When you are **generating any Microsoft/Azure related code**.\\n\\n## Usage Pattern\\nInput a descriptive query, or SDK/class/method name to retrieve related code samples. The optional parameter \\u0060language\\u0060 can help to filter results.\\n\\nEligible values for \\u0060language\\u0060 parameter include: csharp javascript typescript python powershell azurecli al sql java kusto cpp go rust ruby php\",\"inputSchema\":{\"type\":\"object\",\"properties\":{\"query\":{\"description\":\"a descriptive query, SDK name, method name or code snippet related to Microsoft/Azure products, services, platforms, developer tools, frameworks, APIs or SDKs\",\"type\":\"string\"},\"language\":{\"description\":\"Optional parameter specifying the programming language of code snippets to retrieve. Can significantly improve search quality if provided. Eligible values: csharp javascript typescript python powershell azurecli al sql java kusto cpp go rust ruby php\",\"type\":\"string\",\"default\":null}},\"required\":[\"query\"]},\"annotations\":{\"title\":\"Microsoft Code Sample Search\",\"destructiveHint\":false,\"idempotentHint\":true,\"readOnlyHint\":true}},{\"name\":\"microsoft_docs_fetch\",\"title\":\"Microsoft Docs Fetch\",\"description\":\"Fetch and convert a Microsoft Learn documentation page to markdown format. This tool retrieves the latest complete content of Microsoft documentation pages including Azure, .NET, Microsoft 365, and other Microsoft technologies.\\n\\n## When to Use This Tool\\n- When search results provide incomplete information or truncated content\\n- When you need complete step-by-step procedures or tutorials\\n- When you need troubleshooting sections, prerequisites, or detailed explanations\\n- When search results reference a specific page that seems highly relevant\\n- For comprehensive guides that require full context\\n\\n## Usage Pattern\\nUse this tool AFTER microsoft_docs_search when you identify specific high-value pages that need complete content. The search tool gives you an overview; this tool gives you the complete picture.\\n\\n## URL Requirements\\n- The URL must be a valid link from the microsoft.com domain.\\n\\n## Output Format\\nmarkdown with headings, code blocks, tables, and links preserved.\",\"inputSchema\":{\"type\":\"object\",\"properties\":{\"url\":{\"description\":\"URL of the Microsoft documentation page to read\",\"type\":\"string\"}},\"required\":[\"url\"]},\"annotations\":{\"title\":\"Microsoft Docs Fetch\",\"destructiveHint\":false,\"idempotentHint\":true,\"readOnlyHint\":true}}]},\"id\":\"1c46ee9521954378958971e267d543fc\",\"jsonrpc\":\"2.0\"}\n", "\n", "\n", "BODY END" @@ -564,8 +564,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-11-21T09:59:32.450109Z", - "start_time": "2025-11-21T09:59:31.467604Z" + "end_time": "2025-12-04T16:08:59.912312Z", + "start_time": "2025-12-04T16:08:58.839853Z" } }, "cell_type": "code", @@ -597,7 +597,7 @@ "-> Content-Type: application/json\n", "BODY Content-Type: application/json\n", "BODY START\n", - "{\"id\":\"ffda9f6266ce4bfa8f086d23d03c025a\",\"method\":\"tools/call\",\"params\":{\"name\":\"microsoft_docs_search\",\"arguments\":{\"query\":\"Linux\"},\"_meta\":{}},\"jsonrpc\":\"2.0\"}\n", + "{\"id\":\"c4d77709f5314c9e9f097f08dd756438\",\"method\":\"tools/call\",\"params\":{\"name\":\"microsoft_docs_search\",\"arguments\":{\"query\":\"Linux\"},\"_meta\":{}},\"jsonrpc\":\"2.0\"}\n", "BODY END" ] }, @@ -618,10 +618,10 @@ "-> Cache-Control: no-cache, no-store\n", "-> Connection: keep-alive\n", "-> Content-Encoding: identity\n", - "-> Content-Length: 22491\n", + "-> Content-Length: 24086\n", "-> Content-Type: text/event-stream\n", - "-> Date: Fri, 21 Nov 2025 09:59:32 GMT\n", - "-> Expires: Fri, 21 Nov 2025 09:59:32 GMT\n", + "-> Date: Thu, 04 Dec 2025 16:08:59 GMT\n", + "-> Expires: Thu, 04 Dec 2025 16:08:59 GMT\n", "-> Mcp-Session-Id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n", "-> Request-Context: appId=cid-v1:ccd19013-8f20-4877-8d3f-84a0107e7119\n", "-> Set-Cookie: ASLBSA=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; Path=/; Secure; HttpOnly;; ASLBSACORS=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; SameSite=none; Path=/; Secure; HttpOnly;\n", @@ -630,11 +630,11 @@ "-> X-Powered-By: ASP.NET\n", "-> nel: {\"report_to\":\"network-errors\",\"max_age\":604800,\"success_fraction\":0.01,\"failure_fraction\":1.0}\n", "-> report-to: {\"group\":\"network-errors\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://mdec.nelreports.net/api/report?cat=mdocs\"}]}\n", - "-> x-azure-ref: 20251121T095931Z-15dc879c854mpd4vhC1FRAa2us00000001mg00000000dt67\n", + "-> x-azure-ref: 20251204T160859Z-r195dfd6579k9c28hC1STOn4n80000000nk0000000006rws\n", "BODY Content-Type: text/event-stream\n", "BODY START\n", "event: message\n", - "data: {\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"[{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\n## Step 2 - Choose a Linux distribution\\\\nThe different versions of Linux are called \\\\u0022distributions\\\\u0022 (sometimes shortened to \\\\u201Cdistros\\\\u201D). So there isn\\\\u2019t actually a way to just \\\\u201Cinstall Linux\\\\u201D, you first need to choose which distribution that uses the Linux kernel you want to download and install. There are over 600 active Linux distributions, such as Ubuntu, Debian, Kali Linux, openSUSE, and more. There are a number of factors to consider in choosing a distribution, such as:\\\\n### Required Linux experience\\\\nIf you\\\\u2019re new to Linux, you may want to start with Ubuntu. This is one of the most common starting places for Linux users due to the level of support and ease of use. Ubuntu offers a desktop-version and a server-version depending on how you plan to use it. The specific tasks that the operating system will be responsible for may help you determine which distribution to use. Arch Linux is a popular choice for those who want a highly customizable, do-it-yourself approach that is still stable and well-supported by a large user base. It is a much more complicated place to start, but can help you to get a better understanding of how Linux works due to the amount of custom configuration.\\\\n### System requirements\\\\nA minimal, or \\\\u0022lightweight\\\\u0022, distribution means a small footprint, with lower memory and processing speed requirements. Alpine Linux is a popular option where users will find most features disabled or not installed by default, but still has everything you need for a containerized app. Security needs: Some Linux users may have specific security concerns to consider. Kali Linux is an example of a distro designed for security tasks, such as Penetration Testing, Security Research, Computer Forensics and Reverse Engineering.\\\\n### Business and Enterprise requirements\\\\nBusiness, corporate, or academic environments may have unique concerns from consumer environments. For example Red Hat Enterprise Linux (RHEL), SUSE Linux Enterprise Server (SLES) and Oracle Linux offer 24/7 global support and subscription-based service. CentOS is another popular enterprise distro that is community-supported but still compatible with RHEL.\\\\n### Community support\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/linux/install#step-2---choose-a-linux-distribution\\u0022},{\\u0022title\\u0022:\\u0022New to Linux resources for SQL users\\u0022,\\u0022content\\u0022:\\u0022# New to Linux resources for SQL users\\\\n**Applies to:**[SQL Server](https://learn.microsoft.com/en-us/sql/sql-server/sql-docs-navigation-guide?view=sql-server-ver17#applies-to) - Linux\\\\nThis article is intended to provide a learning path through an introduction of Linux concepts. Use the sections in the article as a guided learning path to familiarize yourself with the Linux environment.\\\\nThis isn\\\\u0027t intended to be a comprehensive list but instead provides the bare minimum you need to help you administer and navigate your SQL Server on Linux environment. See the [full list of tutorials](https://www.linux.org/forums/linux-beginner-tutorials.123/) to learn more.\\\\n## What is Linux?\\\\nThe [What is Linux](https://www.linux.org/threads/what-is-linux.4106/) module introduces the history of Linux as an operating system. The module explains *the Kernel* and where Linux is today. This tutorial serves as an introduction to Linux, helping you get started.\\\\n## Select a distribution\\\\nAfter you know the history of Linux, decide which [distribution of Linux](https://www.linux.org/threads/selecting-a-linux-distribution.4117/) best suits your business needs. [SQL Server is supported](https://learn.microsoft.com/en-us/sql/linux/sql-server-linux-release-notes-2019?view=sql-server-ver17#supported-platforms) on various distributions such as Red Hat Enterprise Linux (RHEL), SUSE Linux Enterprise Server (SLES), and Ubuntu.\\\\n## Get around directories\\\\nAfter you have chosen the appropriate distribution of Linux, familiarize yourself with [getting around Linux directories](https://www.linux.org/threads/getting-around-in-linux-directories.4120/).\\\\nThis module will help you answer the following questions:\\\\n1. How to navigate between different files\\\\n2. How to identify a file from a directory\\\\n3. Identify different directories\\\\n## Install new software\\\\nNext, learn how to [install new software](https://www.linux.org/threads/installing-new-software-debian-red-hat-slackware.4119/) on your new Linux operating system. This module details installing new software to the Debian and Red Hat Linux operating systems.\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/sql/linux/new-to-linux-resources?view=sql-server-ver17\\u0022},{\\u0022title\\u0022:\\u0022Part 1.2 - Basic operations on Linux\\u0022,\\u0022content\\u0022:\\u0022# Part 1.2 - Basic operations on Linux\\\\n## Run commands by using the \\\\u0022sudo\\\\u0022 prefix\\\\nInstead of becoming the root account user in your session, you can run commands as root by using the \\\\u0060sudo \\\\u003Ccommand\\\\u003E\\\\u0060 format. This approach is safer because it will run the given command only as a superuser.\\\\nFor example, if you run \\\\u0060restart\\\\u0060 while you\\\\u0027re not elevated to superuser status, the result is as follows.\\\\n![Screenshot of restart command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/restart-command.png)\\\\nTo run this command as a superuser instead, you can either change the session context to root (by running \\\\u0060sudo su\\\\u0060) or add the \\\\u0060sudo\\\\u0060 prefix, as follows:\\\\n\\\\u0060\\\\u0060\\\\u0060bash\\\\nsudo reboot\\\\n\\\\u0060\\\\u0060\\\\u0060\\\\nNow, the restart operation runs as expected by the virtual machine.\\\\n![Screenshot of reboot command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/reboot-command.png)\\\\n## Package managers\\\\nPackage managers are used to install, upgrade, and remove applications in Linux. There are several different package managers, such as DPKG (Debian Package Management System), RPM (Red Hat Package Manager), and Zypper for SUSE Package managers themselves are low-level. Therefore, it might be difficult to manage software directly by using the package manager. Fortunately, there are wrappers for these tools that make it easier to install and uninstall software. For example, [yum](https://wikipedia.org/wiki/Yum_%28software%29) is a wrapper around RPM (Red Hat Package Manager) and [APT](https://wikipedia.org/wiki/APT_%28software%29) is a wrapper around DPKG (Debian Package Management System).\\\\nThis tutorial assumes that you\\\\u0027re running the [Ubuntu package management system](https://ubuntu.com/server/docs/package-management). This system is derived from the same system that\\\\u0027s used by the Debian GNU/Linux distribution. Therefore, we can use APT to install the software.\\\\nDepending on your Linux choice, you might have to use other tools. For example, you can use yum to install or uninstall software if you\\\\u0027re running Red Hat.\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-by-using-the-sudo-prefix\\u0022},{\\u0022title\\u0022:\\u0022Part 1.2 - Basic operations on Linux\\u0022,\\u0022content\\u0022:\\u0022# Part 1.2 - Basic operations on Linux\\\\n## Run commands as a superuser\\\\nThe superuser is the most privileged user account. It has root (unrestricted) access to all files and folders, and complete control over the operation of the computer. The system administrator uses this account for system maintenance.\\\\nOn UNIX-like systems, the conventional name of the superuser is \\\\u0022root\\\\u0022. The root user can do tasks that are otherwise restricted for standard users, including the following ones:\\\\n1. Changing system directories\\\\n2. Changing user privileges\\\\n3. Changing file ownership\\\\n4. System-related tasks such as restarts\\\\n5. Installation of certain applications\\\\nThere are several ways to run commands as the root account user. One is to use the \\\\u0060sudo su\\\\u0060 command. In this command, \\\\u0060sudo\\\\u0060 is short for \\\\u0060superuser do\\\\u0060, and \\\\u0060su\\\\u0060 is an abbreviation for \\\\u0022switch user.\\\\u0022 You can also use the \\\\u0060su\\\\u0060 command to switch between standard users.\\\\nImportant\\\\nAfter you become the root account user, everything you do runs in root context. Therefore, you should act carefully because the root account has access to the entire system.\\\\nTo demonstrate a permissions issue, create a text file in a special folder that\\\\u0027s named */etc*.\\\\nThe */etc/* folder is where system configuration files are located. This folder protects its contents from being changed by a standard user.\\\\nRun the following command:\\\\n\\\\u0060\\\\u0060\\\\u0060bash\\\\necho hello world \\\\u003E /etc/helloworld.txt\\\\n\\\\u0060\\\\u0060\\\\u0060\\\\nThe \\\\u0060echo\\\\u0060 command writes all the text that follows it to the output. The angle bracket (\\\\u0060\\\\u003E\\\\u0060) tells the system to send the output to the */etc/helloworld.txt* file instead of the console. This behavior is similar to how Windows works.\\\\nNote\\\\nLinux is case-sensitive: *Helloworld.txt*, *helloworld.txt* and *helloworld.Txt* are different files.\\\\nYou don\\\\u0027t have to provide a file name extension. The name \\\\u0022helloworld\\\\u0022 by itself is perfectly valid. (The \\\\u0022txt\\\\u0022 extension is used here only as an example.)\\\\nWhen you run this command, you receive a **permission denied** error message.\\\\n![Screenshot of permission denied message.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/permission-denied-message.png)\\\\nThe operation fails because the */etc* folder is special folder that a standard user can\\\\u0027t change. Verify the user by running the \\\\u0060id\\\\u0060 command.\\\\n![Screenshot of I D command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/id-command.png)\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-as-a-superuser\\u0022},{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\n## Step 1 - Choose a method to install Linux\\\\nThe choice of which method to use for installing Linux depends on your needs and preferences.\\\\n1. **New to using Linux?** We recommend starting with Windows Subsystem for Linux (WSL) as it\\\\u0027s the easiest way to get up and running.\\\\n2. **Work in a business environment with more complex needs related to scale or security?** We recommend running Linux as a Virtual Machine (VM) in the cloud and checking out the support that Azure has to offer. This also applies if you want to run Linux as a server.\\\\n3. **Only want to run Linux as your primary operating system?** If you are good with a slightly more complicated install process and don\\\\u0027t need access to Windows tools (like Outlook, Teams, Word, PowerPoint, etc), you can run Linux on bare metal to access the full potential of your hardware without any overhead from virtualization or emulation.\\\\nLearn more about these options below.\\\\n### Windows Subsystem for Linux (WSL)\\\\nAvailable to those with a Windows machine, this is the most simple way to install Linux. Just run the Linux install command: \\\\u0060wsl --install\\\\u0060 to install the Ubuntu distribution.\\\\nThis method uses virtualization to integrate the Windows operating system (OS) with the Linux OS (running on an actual Linux kernel). You can add as many different Linux distributions as you please and run your favorite Linux tools, including GUI apps, alongside your favorite Windows tools. You can also mix and match Bash and PowerShell commands in the same command line. A Linux file system hosts your project files, while a separate Windows file systems hosts any developer tools, such as VS Code, that you\\\\u0027d like to work on the files with. This method of running Linux is highly efficient and performant. Once you choose a Linux distribution or opt to use the default Ubuntu distro, follow the instructions to Use the install Linux command with Windows Subsystem for Linux or learn more in the [WSL documentation](https://learn.microsoft.com/en-us/windows/wsl).\\\\n### Create a Virtual Machine (VM)\\\\nAvailable to everyone regardless if you\\\\u0027re using a Windows or MacOS-based machine, but will require either an Azure account, if creating a VM in the cloud, or virtualization software, if creating a VM directly on your machine (locally).\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/linux/install#step-1---choose-a-method-to-install-linux\\u0022},{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\nLinux is an operating system, similar to Windows, but with many different versions due to the nature of being open source and fully customizable. To install Linux, you must choose an install method and choose a Linux distribution.\\\\nTo install Linux:\\\\n1. Choose an install method: Windows Subsystem for Linux (WSL), Bare metal Linux; or create a Virtual Machine (VM) to run Linux locally or in the cloud.\\\\n2. Choose a Linux distribution: Ubuntu, Debian, Kali Linux, openSUSE, etc.\\\\n3. Follow the steps for your preferred install method:\\\\n3.1. Use the install Linux command with Windows Subsystem for Linux (WSL)\\\\n3.2. Create a Linux Virtual Machine (VM) in the cloud\\\\n3.3. Create a Linux Virtual Machine (VM) on your local machine\\\\n3.4. Create a bootable USB to install bare-metal Linux\\\\n4. After installing Linux: Get familiar with your distribution\\\\u0027s package manager, update and upgrade the packages available, and get familiar with the other [Linux resources at Microsoft](https://learn.microsoft.com/en-us/linux/), such as training courses, Linux-versions of popular tools, news, and Open Source events.\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/linux/install\\u0022},{\\u0022title\\u0022:\\u0022Guide for changing your dev environment from Mac to Windows\\u0022,\\u0022content\\u0022:\\u0022# Guide for changing your dev environment from Mac to Windows\\\\n## Command-line shells and terminals\\\\nYou can now install Windows Subsystem for Linux (WSL) to support running a Linux shell within Windows. This means that you can run **bash**, with whichever specific Linux distribution you choose, integrated right inside Windows. Using WSL provides the kind of environment most familiar to Mac users. For example, you use **ls** to list the files in a current directory, not **dir** as you would with the traditional Windows Cmd Shell. To learn about installing and using WSL, see the [Windows Subsystem for Linux Installation Guide](https://learn.microsoft.com/en-us/windows/wsl/install). Linux distributions that you can install on Windows with WSL include:\\\\n1. [Ubuntu 20.04 LTS](https://www.microsoft.com/store/apps/9n6svws3rx71)\\\\n2. [Kali Linux](https://www.microsoft.com/store/apps/9PKR34TNCV07)\\\\n3. [Debian GNU/Linux](https://www.microsoft.com/store/apps/9MSVKQC78PK6)\\\\n4. [openSUSE Leap 15.1](https://www.microsoft.com/store/apps/9NJFZK00FGKV)\\\\n5. [SUSE Linux Enterprise Server 15 SP1](https://www.microsoft.com/store/apps/9PN498VPMF3Z)\\\\nJust to name a few. Find more in the [WSL install docs](https://learn.microsoft.com/en-us/windows/wsl/install-win10#install-your-linux-distribution-of-choice) and install them directly from the [Microsoft Store](https://aka.ms/wslstore).\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/windows/dev-environment/mac-to-windows#command-line-shells-and-terminals\\u0022},{\\u0022title\\u0022:\\u0022Supported Linux operating system versions\\u0022,\\u0022content\\u0022:\\u0022# Supported Linux operating system versions\\\\n## Universal Linux (Debian package)\\\\nSupported versions:\\\\n1. Debian 8, 9, 10, 11, and 12\\\\n2. Ubuntu 16.04, 18.04, 20.04, 22.04 and 24.04\\\\nNote\\\\n1. Ubuntu 22.04 is supported from Operations Manager 2022 UR1 and later when you apply [this hotfix](https://support.microsoft.com/topic/system-center-operations-manager-2022-now-has-openssl3-0-integration-kb-5024286-331bd221-10f9-42d5-bc06-775eaabe3081).\\\\n2. Ubuntu 24.04 is supported from Operations Manager 2022 UR2 and later.\\\\n| Required package | Description | Minimum version| \\\\n| --- | --- | --- |\\\\n| libc6 | C Standard shared library | 2.24-11 |\\\\n| OpenSSL | OpenSSL Libraries; Secure Network Communications Protocol | 1.0, 1.1 or 3.0 and later |\\\\n| PAM | Pluggable Authentication Modules | 1.1.8-3.1 |\\\\n\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/system-center/scom/plan-supported-crossplat-linux-os?view=sc-om-2025#universal-linux-debian-package\\u0022},{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\n## Step 3 - Follow install method instructions\\\\n### Install Linux with Windows Subsystem for Linux\\\\nTo install Linux on a Windows PC, use the install Linux command.\\\\n1. Open PowerShell or Windows Command Prompt in *administrator mode* by right-clicking and selecting \\\\u0022Run as administrator\\\\u0022.\\\\n2. Enter the Linux install command: \\\\u0060wsl --install\\\\u0060.\\\\n3. Restart your machine.\\\\n#### Install Linux command options\\\\nBy default, the install command will use the Ubuntu distribution of Linux. You can see the other distributions that are available in the Microsoft Store by entering the command: \\\\u0060wsl -l -o\\\\u0060. If you\\\\u0027d like to install a distribution other than Ubuntu, just add \\\\u0060-d\\\\u0060 to the command with the name of the distribution you\\\\u0027d like to install:\\\\n\\\\u0060\\\\u0060\\\\u0060powershell\\\\nwsl --install -d \\\\u003CDistro Name\\\\u003E\\\\n\\\\u0060\\\\u0060\\\\u0060\\\\nIf the distribution you want to install isn\\\\u0027t available in the Microsoft Store, see the Windows Subsystem for Linux (WSL) documentation on how to [Import any Linux distribution to use with WSL](https://learn.microsoft.com/en-us/windows/wsl/use-custom-distro).\\\\nFor more information on how to use VS Code, Git, databases, Docker, GUI apps, GPU acceleration, NodesJS, USB devices, or mount a disk, build a custom distribution, manage disk space, or set up WSL for your enterprise business, see the [WSL documentation](https://learn.microsoft.com/en-us/windows/wsl/).\\\\n### Create a Linux Virtual Machine in the cloud\\\\nTo create a Linux Virtual Machine (VM) that is hosted in the cloud on Azure, log-in to the Azure Portal and select the \\\\u201CCreate a Virtual Machine\\\\u201D service. Then follow the instructions for creating a user name, project, instance, SSH key, ports, and copying the IP address.\\\\nAzure supports a variety of options for setting up Linux VMs in the cloud. For a quickstart guide on how to set up a Linux VM, choose your preferred method:\\\\n1. [Azure Portal](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-portal?tabs=ubuntu\\\\u0026source=docs)\\\\n2. [Azure CLI](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-cli)\\\\n3. [Azure PowerShell](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-powershell)\\\\n4. [Terraform](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-terraform)\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/linux/install#step-3---follow-install-method-instructions\\u0022},{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\n## Step 1 - Choose a method to install Linux\\\\nYou are responsible for managing the virtual machine when using a hypervisor, including allocating resources like memory and disk space, and ensuring that it is secure and up-to-date. This requires more technical expertise than some of the other options and may not be as scalable or fault-tolerant.\\\\nOnce you\\\\u0027ve chosen a distribution and decided whether you want to run the VM locally, using a hypervisor, or in the cloud, using Azure, follow the instructions to Create a Linux Virtual Machine in the Cloud or Create a Linux Virtual Machine locally using a Hypervisor\\\\n### Bare metal Linux\\\\nBare metal Linux just means that Linux is running directly on the device hardware. This install method requires you to create a bootable USB drive by downloading an iso install file from the site hosting your chosen Linux distribution. You will need to use a Windows computer (or any desktop device with an existing OS) to create this drive.\\\\nMany users choose the traditional method of installing bare metal Linux on a device that is also running Windows and using the **\\\\u0022dual boot\\\\u0022** method. To boot Linux and Windows, you need to partition your hard drive to create separate spaces for both the Linux and Windows OS. The performance speed between WSL, virtual machines, and bare metal Linux has become so close that few developers choose this method due to the overhead of needing to restart (reboot) your device any time you want to switch between the operating systems. If you choose the bare metal Linux install route, you may also need to deal with potential driver issues or hardware compatibility problems that may arise with Linux on some devices.\\\\nYou can find a list of [Popular Linux Distributions available for download on Linux.org](https://linux.org/pages/download/). Once you\\\\u0027ve chosen a distro, follow the instructions to Create a bootable USB to install bare-metal Linux.\\\\nWarning\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/linux/install#step-1---choose-a-method-to-install-linux\\u0022}]\"}]},\"id\":\"ffda9f6266ce4bfa8f086d23d03c025a\",\"jsonrpc\":\"2.0\"}\n", + "data: {\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"[{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\n## Step 2 - Choose a Linux distribution\\\\nThe different versions of Linux are called \\\\u0022distributions\\\\u0022 (sometimes shortened to \\\\u201Cdistros\\\\u201D). So there isn\\\\u2019t actually a way to just \\\\u201Cinstall Linux\\\\u201D, you first need to choose which distribution that uses the Linux kernel you want to download and install. There are over 600 active Linux distributions, such as Ubuntu, Debian, Kali Linux, openSUSE, and more. There are a number of factors to consider in choosing a distribution, such as:\\\\n### Required Linux experience\\\\nIf you\\\\u2019re new to Linux, you may want to start with Ubuntu. This is one of the most common starting places for Linux users due to the level of support and ease of use. Ubuntu offers a desktop-version and a server-version depending on how you plan to use it. The specific tasks that the operating system will be responsible for may help you determine which distribution to use. Arch Linux is a popular choice for those who want a highly customizable, do-it-yourself approach that is still stable and well-supported by a large user base. It is a much more complicated place to start, but can help you to get a better understanding of how Linux works due to the amount of custom configuration.\\\\n### System requirements\\\\nA minimal, or \\\\u0022lightweight\\\\u0022, distribution means a small footprint, with lower memory and processing speed requirements. Alpine Linux is a popular option where users will find most features disabled or not installed by default, but still has everything you need for a containerized app. Security needs: Some Linux users may have specific security concerns to consider. Kali Linux is an example of a distro designed for security tasks, such as Penetration Testing, Security Research, Computer Forensics and Reverse Engineering.\\\\n### Business and Enterprise requirements\\\\nBusiness, corporate, or academic environments may have unique concerns from consumer environments. For example Red Hat Enterprise Linux (RHEL), SUSE Linux Enterprise Server (SLES) and Oracle Linux offer 24/7 global support and subscription-based service. CentOS is another popular enterprise distro that is community-supported but still compatible with RHEL.\\\\n### Community support\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/linux/install#step-2---choose-a-linux-distribution\\u0022},{\\u0022title\\u0022:\\u0022New to Linux resources for SQL users\\u0022,\\u0022content\\u0022:\\u0022# New to Linux resources for SQL users\\\\n**Applies to:**[SQL Server](https://learn.microsoft.com/en-us/sql/sql-server/sql-docs-navigation-guide?view=sql-server-ver17#applies-to) - Linux\\\\nThis article is intended to provide a learning path through an introduction of Linux concepts. Use the sections in the article as a guided learning path to familiarize yourself with the Linux environment.\\\\nThis isn\\\\u0027t intended to be a comprehensive list but instead provides the bare minimum you need to help you administer and navigate your SQL Server on Linux environment. See the [full list of tutorials](https://www.linux.org/forums/linux-beginner-tutorials.123/) to learn more.\\\\n## What is Linux?\\\\nThe [What is Linux](https://www.linux.org/threads/what-is-linux.4106/) module introduces the history of Linux as an operating system. The module explains *the Kernel* and where Linux is today. This tutorial serves as an introduction to Linux, helping you get started.\\\\n## Select a distribution\\\\nAfter you know the history of Linux, decide which [distribution of Linux](https://www.linux.org/threads/selecting-a-linux-distribution.4117/) best suits your business needs. [SQL Server is supported](https://learn.microsoft.com/en-us/sql/linux/sql-server-linux-release-notes-2019?view=sql-server-ver17#supported-platforms) on various distributions such as Red Hat Enterprise Linux (RHEL), SUSE Linux Enterprise Server (SLES), and Ubuntu.\\\\n## Get around directories\\\\nAfter you have chosen the appropriate distribution of Linux, familiarize yourself with [getting around Linux directories](https://www.linux.org/threads/getting-around-in-linux-directories.4120/).\\\\nThis module will help you answer the following questions:\\\\n1. How to navigate between different files\\\\n2. How to identify a file from a directory\\\\n3. Identify different directories\\\\n## Install new software\\\\nNext, learn how to [install new software](https://www.linux.org/threads/installing-new-software-debian-red-hat-slackware.4119/) on your new Linux operating system. This module details installing new software to the Debian and Red Hat Linux operating systems.\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/sql/linux/new-to-linux-resources?view=sql-server-ver17\\u0022},{\\u0022title\\u0022:\\u0022Part 1.2 - Basic operations on Linux\\u0022,\\u0022content\\u0022:\\u0022# Part 1.2 - Basic operations on Linux\\\\n## Run commands by using the \\\\u0022sudo\\\\u0022 prefix\\\\nInstead of becoming the root account user in your session, you can run commands as root by using the \\\\u0060sudo \\\\u003Ccommand\\\\u003E\\\\u0060 format. This approach is safer because it will run the given command only as a superuser.\\\\nFor example, if you run \\\\u0060restart\\\\u0060 while you\\\\u0027re not elevated to superuser status, the result is as follows.\\\\n![Screenshot of restart command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/restart-command.png)\\\\nTo run this command as a superuser instead, you can either change the session context to root (by running \\\\u0060sudo su\\\\u0060) or add the \\\\u0060sudo\\\\u0060 prefix, as follows:\\\\n\\\\u0060\\\\u0060\\\\u0060bash\\\\nsudo reboot\\\\n\\\\u0060\\\\u0060\\\\u0060\\\\nNow, the restart operation runs as expected by the virtual machine.\\\\n![Screenshot of reboot command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/reboot-command.png)\\\\n## Package managers\\\\nPackage managers are used to install, upgrade, and remove applications in Linux. There are several different package managers, such as DPKG (Debian Package Management System), RPM (Red Hat Package Manager), and Zypper for SUSE Package managers themselves are low-level. Therefore, it might be difficult to manage software directly by using the package manager. Fortunately, there are wrappers for these tools that make it easier to install and uninstall software. For example, [yum](https://wikipedia.org/wiki/Yum_%28software%29) is a wrapper around RPM (Red Hat Package Manager) and [APT](https://wikipedia.org/wiki/APT_%28software%29) is a wrapper around DPKG (Debian Package Management System).\\\\nThis tutorial assumes that you\\\\u0027re running the [Ubuntu package management system](https://ubuntu.com/server/docs/package-management). This system is derived from the same system that\\\\u0027s used by the Debian GNU/Linux distribution. Therefore, we can use APT to install the software.\\\\nDepending on your Linux choice, you might have to use other tools. For example, you can use yum to install or uninstall software if you\\\\u0027re running Red Hat.\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-by-using-the-sudo-prefix\\u0022},{\\u0022title\\u0022:\\u0022Part 1.2 - Basic operations on Linux\\u0022,\\u0022content\\u0022:\\u0022# Part 1.2 - Basic operations on Linux\\\\n## Run commands as a superuser\\\\nThe superuser is the most privileged user account. It has root (unrestricted) access to all files and folders, and complete control over the operation of the computer. The system administrator uses this account for system maintenance.\\\\nOn UNIX-like systems, the conventional name of the superuser is \\\\u0022root\\\\u0022. The root user can do tasks that are otherwise restricted for standard users, including the following ones:\\\\n1. Changing system directories\\\\n2. Changing user privileges\\\\n3. Changing file ownership\\\\n4. System-related tasks such as restarts\\\\n5. Installation of certain applications\\\\nThere are several ways to run commands as the root account user. One is to use the \\\\u0060sudo su\\\\u0060 command. In this command, \\\\u0060sudo\\\\u0060 is short for \\\\u0060superuser do\\\\u0060, and \\\\u0060su\\\\u0060 is an abbreviation for \\\\u0022switch user.\\\\u0022 You can also use the \\\\u0060su\\\\u0060 command to switch between standard users.\\\\nImportant\\\\nAfter you become the root account user, everything you do runs in root context. Therefore, you should act carefully because the root account has access to the entire system.\\\\nTo demonstrate a permissions issue, create a text file in a special folder that\\\\u0027s named */etc*.\\\\nThe */etc/* folder is where system configuration files are located. This folder protects its contents from being changed by a standard user.\\\\nRun the following command:\\\\n\\\\u0060\\\\u0060\\\\u0060bash\\\\necho hello world \\\\u003E /etc/helloworld.txt\\\\n\\\\u0060\\\\u0060\\\\u0060\\\\nThe \\\\u0060echo\\\\u0060 command writes all the text that follows it to the output. The angle bracket (\\\\u0060\\\\u003E\\\\u0060) tells the system to send the output to the */etc/helloworld.txt* file instead of the console. This behavior is similar to how Windows works.\\\\nNote\\\\nLinux is case-sensitive: *Helloworld.txt*, *helloworld.txt* and *helloworld.Txt* are different files.\\\\nYou don\\\\u0027t have to provide a file name extension. The name \\\\u0022helloworld\\\\u0022 by itself is perfectly valid. (The \\\\u0022txt\\\\u0022 extension is used here only as an example.)\\\\nWhen you run this command, you receive a **permission denied** error message.\\\\n![Screenshot of permission denied message.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/permission-denied-message.png)\\\\nThe operation fails because the */etc* folder is special folder that a standard user can\\\\u0027t change. Verify the user by running the \\\\u0060id\\\\u0060 command.\\\\n![Screenshot of I D command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/id-command.png)\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-as-a-superuser\\u0022},{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\n## Step 1 - Choose a method to install Linux\\\\nThe choice of which method to use for installing Linux depends on your needs and preferences.\\\\n1. **New to using Linux?** We recommend starting with Windows Subsystem for Linux (WSL) as it\\\\u0027s the easiest way to get up and running.\\\\n2. **Work in a business environment with more complex needs related to scale or security?** We recommend running Linux as a Virtual Machine (VM) in the cloud and checking out the support that Azure has to offer. This also applies if you want to run Linux as a server.\\\\n3. **Only want to run Linux as your primary operating system?** If you are good with a slightly more complicated install process and don\\\\u0027t need access to Windows tools (like Outlook, Teams, Word, PowerPoint, etc), you can run Linux on bare metal to access the full potential of your hardware without any overhead from virtualization or emulation.\\\\nLearn more about these options below.\\\\n### Windows Subsystem for Linux (WSL)\\\\nAvailable to those with a Windows machine, this is the most simple way to install Linux. Just run the Linux install command: \\\\u0060wsl --install\\\\u0060 to install the Ubuntu distribution.\\\\nThis method uses virtualization to integrate the Windows operating system (OS) with the Linux OS (running on an actual Linux kernel). You can add as many different Linux distributions as you please and run your favorite Linux tools, including GUI apps, alongside your favorite Windows tools. You can also mix and match Bash and PowerShell commands in the same command line. A Linux file system hosts your project files, while a separate Windows file systems hosts any developer tools, such as VS Code, that you\\\\u0027d like to work on the files with. This method of running Linux is highly efficient and performant. Once you choose a Linux distribution or opt to use the default Ubuntu distro, follow the instructions to Use the install Linux command with Windows Subsystem for Linux or learn more in the [WSL documentation](https://learn.microsoft.com/en-us/windows/wsl).\\\\n![WSL install command screenshot](https://learn.microsoft.com/en-us/linux/images/wsl-install-command-screenshot.png)\\\\n### Create a Virtual Machine (VM)\\\\nAvailable to everyone regardless if you\\\\u0027re using a Windows or MacOS-based machine, but will require either an Azure account, if creating a VM in the cloud, or virtualization software, if creating a VM directly on your machine (locally).\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/linux/install#step-1---choose-a-method-to-install-linux\\u0022},{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\nLinux is an operating system, similar to Windows, but with many different versions due to the nature of being open source and fully customizable. To install Linux, you must choose an install method and choose a Linux distribution.\\\\nTo install Linux:\\\\n1. Choose an install method: Windows Subsystem for Linux (WSL), Bare metal Linux; or create a Virtual Machine (VM) to run Linux locally or in the cloud.\\\\n2. Choose a Linux distribution: Ubuntu, Debian, Kali Linux, openSUSE, etc.\\\\n3. Follow the steps for your preferred install method:\\\\n3.1. Use the install Linux command with Windows Subsystem for Linux (WSL)\\\\n3.2. Create a Linux Virtual Machine (VM) in the cloud\\\\n3.3. Create a Linux Virtual Machine (VM) on your local machine\\\\n3.4. Create a bootable USB to install bare-metal Linux\\\\n4. After installing Linux: Get familiar with your distribution\\\\u0027s package manager, update and upgrade the packages available, and get familiar with the other [Linux resources at Microsoft](https://learn.microsoft.com/en-us/linux/), such as training courses, Linux-versions of popular tools, news, and Open Source events.\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/linux/install\\u0022},{\\u0022title\\u0022:\\u0022Guide for changing your dev environment from Mac to Windows\\u0022,\\u0022content\\u0022:\\u0022# Guide for changing your dev environment from Mac to Windows\\\\n## Command-line shells and terminals\\\\nYou can now install Windows Subsystem for Linux (WSL) to support running a Linux shell within Windows. This means that you can run **bash**, with whichever specific Linux distribution you choose, integrated right inside Windows. Using WSL provides the kind of environment most familiar to Mac users. For example, you use **ls** to list the files in a current directory, not **dir** as you would with the traditional Windows Cmd Shell. To learn about installing and using WSL, see the [Windows Subsystem for Linux Installation Guide](https://learn.microsoft.com/en-us/windows/wsl/install). Linux distributions that you can install on Windows with WSL include:\\\\n1. [Ubuntu 20.04 LTS](https://www.microsoft.com/store/apps/9n6svws3rx71)\\\\n2. [Kali Linux](https://www.microsoft.com/store/apps/9PKR34TNCV07)\\\\n3. [Debian GNU/Linux](https://www.microsoft.com/store/apps/9MSVKQC78PK6)\\\\n4. [openSUSE Leap 15.1](https://www.microsoft.com/store/apps/9NJFZK00FGKV)\\\\n5. [SUSE Linux Enterprise Server 15 SP1](https://www.microsoft.com/store/apps/9PN498VPMF3Z)\\\\nJust to name a few. Find more in the [WSL install docs](https://learn.microsoft.com/en-us/windows/wsl/install-win10#install-your-linux-distribution-of-choice) and install them directly from the [Microsoft Store](https://aka.ms/wslstore).\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/windows/dev-environment/mac-to-windows#command-line-shells-and-terminals\\u0022},{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\n## Step 3 - Follow install method instructions\\\\n### Install Linux with Windows Subsystem for Linux\\\\nTo install Linux on a Windows PC, use the install Linux command.\\\\n1. Open PowerShell or Windows Command Prompt in *administrator mode* by right-clicking and selecting \\\\u0022Run as administrator\\\\u0022.\\\\n2. Enter the Linux install command: \\\\u0060wsl --install\\\\u0060.\\\\n3. Restart your machine.\\\\n![WSL Linux Install List Command screenshot](https://learn.microsoft.com/en-us/linux/images/wsl-install-list-screenshot.png)\\\\n#### Install Linux command options\\\\nBy default, the install command will use the Ubuntu distribution of Linux. You can see the other distributions that are available in the Microsoft Store by entering the command: \\\\u0060wsl -l -o\\\\u0060. If you\\\\u0027d like to install a distribution other than Ubuntu, just add \\\\u0060-d\\\\u0060 to the command with the name of the distribution you\\\\u0027d like to install:\\\\n\\\\u0060\\\\u0060\\\\u0060powershell\\\\nwsl --install -d \\\\u003CDistro Name\\\\u003E\\\\n\\\\u0060\\\\u0060\\\\u0060\\\\nIf the distribution you want to install isn\\\\u0027t available in the Microsoft Store, see the Windows Subsystem for Linux (WSL) documentation on how to [Import any Linux distribution to use with WSL](https://learn.microsoft.com/en-us/windows/wsl/use-custom-distro).\\\\nFor more information on how to use VS Code, Git, databases, Docker, GUI apps, GPU acceleration, NodesJS, USB devices, or mount a disk, build a custom distribution, manage disk space, or set up WSL for your enterprise business, see the [WSL documentation](https://learn.microsoft.com/en-us/windows/wsl/).\\\\n### Create a Linux Virtual Machine in the cloud\\\\nTo create a Linux Virtual Machine (VM) that is hosted in the cloud on Azure, log-in to the Azure Portal and select the \\\\u201CCreate a Virtual Machine\\\\u201D service. Then follow the instructions for creating a user name, project, instance, SSH key, ports, and copying the IP address.\\\\nAzure supports a variety of options for setting up Linux VMs in the cloud. For a quickstart guide on how to set up a Linux VM, choose your preferred method:\\\\n1. [Azure Portal](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-portal?tabs=ubuntu\\\\u0026source=docs)\\\\n2. [Azure CLI](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-cli)\\\\n3. [Azure PowerShell](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-powershell)\\\\n4. [Terraform](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-terraform)\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/linux/install#step-3---follow-install-method-instructions\\u0022},{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\n## Step 2 - Choose a Linux distribution\\\\nA large user community that consistently contributes to the distribution, fixing bugs, adding features, helping with questions, etc. is another good consideration. Ubuntu is one of the most popular distos. openSUSE has been around longer than most other distributions and offers in addition to the stable version (Leap) a rolling version (Tumbleweed). Many other distros rapidly grow popular as the needs and preferences of the user community change. The Linux.org site has resources to help you assess what\\\\u0027s available, well-supported, or growing in popularity.\\\\n![Screenshot of the list of distributions on Linux.org](https://learn.microsoft.com/en-us/linux/images/linux-distributions-list-screenshot.png)\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/linux/install#step-2---choose-a-linux-distribution\\u0022},{\\u0022title\\u0022:\\u0022Part 1.2 - Basic operations on Linux\\u0022,\\u0022content\\u0022:\\u0022# Part 1.2 - Basic operations on Linux\\\\n## Run commands as a superuser\\\\nTo become the root account user, run the \\\\u0060sudo su\\\\u0060 command.\\\\n![Screenshot of sudo su command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/sudo-su-command.png)\\\\nYou should notice two things: When you become root, the dollar sign character (\\\\u0060$\\\\u0060) becomes a pound sign character (\\\\u0060#\\\\u0060). Also, the username is changed to **root**. If you run the same \\\\u0060id\\\\u0060 command again, you see that the user and group ID of root are **0**.\\\\n![Screenshot of root command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/root-command.png)\\\\nImportant\\\\nNow you have elevated yourself to the \\\\u0022superuser\\\\u0022 role in your session, you have complete access to the system. Keep in mind that this is a potentially dangerous situation, and you should exercise caution as you continue.\\\\nNow, run the same command one more time:\\\\n\\\\u0060\\\\u0060\\\\u0060bash\\\\necho hello world \\\\u003E /etc/helloworld.txt\\\\n\\\\u0060\\\\u0060\\\\u0060\\\\nThis time, you don\\\\u0027t receive any error message. To verify if the file is created, run the following command:\\\\n\\\\u0060\\\\u0060\\\\u0060bash\\\\nll /etc/hello*\\\\n\\\\u0060\\\\u0060\\\\u0060\\\\nThe \\\\u0060ll\\\\u0060 command lists the files and subfolders that are in a specified folder. In this example, the \\\\u0060/etc/hello*\\\\u0060 parameter value limits the command output to the files or folders whose name begins in \\\\u0060hello\\\\u0060.\\\\n![Screenshot of hello command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/hello-command.png)\\\\nThe output shows that the file was created. What if we want to examine the content of the file? The \\\\u0060cat\\\\u0060 command helps here. To see the content, run \\\\u0060cat /etc/helloworld.txt\\\\u0060.\\\\n![Screenshot of cat command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/cat-command.png)\\\\nThe \\\\u0060cat\\\\u0060 command reads files sequentially, and writes their content to the standard output. Therefore, it writes \\\\u0022hello world\\\\u0022 in the console.\\\\nTo prevent mistakes that could damage the system while you\\\\u0027re elevated to root status, it\\\\u0027s a good practice to exit the root session and return to your standard user session to avoid some dangerous operations. To do this, run \\\\u0060exit\\\\u0060. You can see that the pound sign reverts to a dollar sign, and the username is displayed as your standard user.\\\\n![Screenshot of exit command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/exit-command.png)\\\\nFor additional practice, run the same \\\\u0060ll\\\\u0060 and \\\\u0060cat\\\\u0060 commands within your standard user context. You\\\\u0027ll see that you can\\\\u0027t create the file in the */etc/* folder, but can you list the file and read the contents.\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-as-a-superuser\\u0022}]\"}]},\"id\":\"c4d77709f5314c9e9f097f08dd756438\",\"jsonrpc\":\"2.0\"}\n", "\n", "\n", "BODY END" @@ -650,7 +650,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "TextContent(text=[{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\n## Step 2 - Choose a Linux distribution\\nThe different versions of Linux are called \\u0022distributions\\u0022 (sometimes shortened to \\u201Cdistros\\u201D). So there isn\\u2019t actually a way to just \\u201Cinstall Linux\\u201D, you first need to choose which distribution that uses the Linux kernel you want to download and install. There are over 600 active Linux distributions, such as Ubuntu, Debian, Kali Linux, openSUSE, and more. There are a number of factors to consider in choosing a distribution, such as:\\n### Required Linux experience\\nIf you\\u2019re new to Linux, you may want to start with Ubuntu. This is one of the most common starting places for Linux users due to the level of support and ease of use. Ubuntu offers a desktop-version and a server-version depending on how you plan to use it. The specific tasks that the operating system will be responsible for may help you determine which distribution to use. Arch Linux is a popular choice for those who want a highly customizable, do-it-yourself approach that is still stable and well-supported by a large user base. It is a much more complicated place to start, but can help you to get a better understanding of how Linux works due to the amount of custom configuration.\\n### System requirements\\nA minimal, or \\u0022lightweight\\u0022, distribution means a small footprint, with lower memory and processing speed requirements. Alpine Linux is a popular option where users will find most features disabled or not installed by default, but still has everything you need for a containerized app. Security needs: Some Linux users may have specific security concerns to consider. Kali Linux is an example of a distro designed for security tasks, such as Penetration Testing, Security Research, Computer Forensics and Reverse Engineering.\\n### Business and Enterprise requirements\\nBusiness, corporate, or academic environments may have unique concerns from consumer environments. For example Red Hat Enterprise Linux (RHEL), SUSE Linux Enterprise Server (SLES) and Oracle Linux offer 24/7 global support and subscription-based service. CentOS is another popular enterprise distro that is community-supported but still compatible with RHEL.\\n### Community support\",\"contentUrl\":\"https://learn.microsoft.com/en-us/linux/install#step-2---choose-a-linux-distribution\"},{\"title\":\"New to Linux resources for SQL users\",\"content\":\"# New to Linux resources for SQL users\\n**Applies to:**[SQL Server](https://learn.microsoft.com/en-us/sql/sql-server/sql-docs-navigation-guide?view=sql-server-ver17#applies-to) - Linux\\nThis article is intended to provide a learning path through an introduction of Linux concepts. Use the sections in the article as a guided learning path to familiarize yourself with the Linux environment.\\nThis isn\\u0027t intended to be a comprehensive list but instead provides the bare minimum you need to help you administer and navigate your SQL Server on Linux environment. See the [full list of tutorials](https://www.linux.org/forums/linux-beginner-tutorials.123/) to learn more.\\n## What is Linux?\\nThe [What is Linux](https://www.linux.org/threads/what-is-linux.4106/) module introduces the history of Linux as an operating system. The module explains *the Kernel* and where Linux is today. This tutorial serves as an introduction to Linux, helping you get started.\\n## Select a distribution\\nAfter you know the history of Linux, decide which [distribution of Linux](https://www.linux.org/threads/selecting-a-linux-distribution.4117/) best suits your business needs. [SQL Server is supported](https://learn.microsoft.com/en-us/sql/linux/sql-server-linux-release-notes-2019?view=sql-server-ver17#supported-platforms) on various distributions such as Red Hat Enterprise Linux (RHEL), SUSE Linux Enterprise Server (SLES), and Ubuntu.\\n## Get around directories\\nAfter you have chosen the appropriate distribution of Linux, familiarize yourself with [getting around Linux directories](https://www.linux.org/threads/getting-around-in-linux-directories.4120/).\\nThis module will help you answer the following questions:\\n1. How to navigate between different files\\n2. How to identify a file from a directory\\n3. Identify different directories\\n## Install new software\\nNext, learn how to [install new software](https://www.linux.org/threads/installing-new-software-debian-red-hat-slackware.4119/) on your new Linux operating system. This module details installing new software to the Debian and Red Hat Linux operating systems.\",\"contentUrl\":\"https://learn.microsoft.com/en-us/sql/linux/new-to-linux-resources?view=sql-server-ver17\"},{\"title\":\"Part 1.2 - Basic operations on Linux\",\"content\":\"# Part 1.2 - Basic operations on Linux\\n## Run commands by using the \\u0022sudo\\u0022 prefix\\nInstead of becoming the root account user in your session, you can run commands as root by using the \\u0060sudo \\u003Ccommand\\u003E\\u0060 format. This approach is safer because it will run the given command only as a superuser.\\nFor example, if you run \\u0060restart\\u0060 while you\\u0027re not elevated to superuser status, the result is as follows.\\n![Screenshot of restart command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/restart-command.png)\\nTo run this command as a superuser instead, you can either change the session context to root (by running \\u0060sudo su\\u0060) or add the \\u0060sudo\\u0060 prefix, as follows:\\n\\u0060\\u0060\\u0060bash\\nsudo reboot\\n\\u0060\\u0060\\u0060\\nNow, the restart operation runs as expected by the virtual machine.\\n![Screenshot of reboot command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/reboot-command.png)\\n## Package managers\\nPackage managers are used to install, upgrade, and remove applications in Linux. There are several different package managers, such as DPKG (Debian Package Management System), RPM (Red Hat Package Manager), and Zypper for SUSE Package managers themselves are low-level. Therefore, it might be difficult to manage software directly by using the package manager. Fortunately, there are wrappers for these tools that make it easier to install and uninstall software. For example, [yum](https://wikipedia.org/wiki/Yum_%28software%29) is a wrapper around RPM (Red Hat Package Manager) and [APT](https://wikipedia.org/wiki/APT_%28software%29) is a wrapper around DPKG (Debian Package Management System).\\nThis tutorial assumes that you\\u0027re running the [Ubuntu package management system](https://ubuntu.com/server/docs/package-management). This system is derived from the same system that\\u0027s used by the Debian GNU/Linux distribution. Therefore, we can use APT to install the software.\\nDepending on your Linux choice, you might have to use other tools. For example, you can use yum to install or uninstall software if you\\u0027re running Red Hat.\",\"contentUrl\":\"https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-by-using-the-sudo-prefix\"},{\"title\":\"Part 1.2 - Basic operations on Linux\",\"content\":\"# Part 1.2 - Basic operations on Linux\\n## Run commands as a superuser\\nThe superuser is the most privileged user account. It has root (unrestricted) access to all files and folders, and complete control over the operation of the computer. The system administrator uses this account for system maintenance.\\nOn UNIX-like systems, the conventional name of the superuser is \\u0022root\\u0022. The root user can do tasks that are otherwise restricted for standard users, including the following ones:\\n1. Changing system directories\\n2. Changing user privileges\\n3. Changing file ownership\\n4. System-related tasks such as restarts\\n5. Installation of certain applications\\nThere are several ways to run commands as the root account user. One is to use the \\u0060sudo su\\u0060 command. In this command, \\u0060sudo\\u0060 is short for \\u0060superuser do\\u0060, and \\u0060su\\u0060 is an abbreviation for \\u0022switch user.\\u0022 You can also use the \\u0060su\\u0060 command to switch between standard users.\\nImportant\\nAfter you become the root account user, everything you do runs in root context. Therefore, you should act carefully because the root account has access to the entire system.\\nTo demonstrate a permissions issue, create a text file in a special folder that\\u0027s named */etc*.\\nThe */etc/* folder is where system configuration files are located. This folder protects its contents from being changed by a standard user.\\nRun the following command:\\n\\u0060\\u0060\\u0060bash\\necho hello world \\u003E /etc/helloworld.txt\\n\\u0060\\u0060\\u0060\\nThe \\u0060echo\\u0060 command writes all the text that follows it to the output. The angle bracket (\\u0060\\u003E\\u0060) tells the system to send the output to the */etc/helloworld.txt* file instead of the console. This behavior is similar to how Windows works.\\nNote\\nLinux is case-sensitive: *Helloworld.txt*, *helloworld.txt* and *helloworld.Txt* are different files.\\nYou don\\u0027t have to provide a file name extension. The name \\u0022helloworld\\u0022 by itself is perfectly valid. (The \\u0022txt\\u0022 extension is used here only as an example.)\\nWhen you run this command, you receive a **permission denied** error message.\\n![Screenshot of permission denied message.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/permission-denied-message.png)\\nThe operation fails because the */etc* folder is special folder that a standard user can\\u0027t change. Verify the user by running the \\u0060id\\u0060 command.\\n![Screenshot of I D command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/id-command.png)\",\"contentUrl\":\"https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-as-a-superuser\"},{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\n## Step 1 - Choose a method to install Linux\\nThe choice of which method to use for installing Linux depends on your needs and preferences.\\n1. **New to using Linux?** We recommend starting with Windows Subsystem for Linux (WSL) as it\\u0027s the easiest way to get up and running.\\n2. **Work in a business environment with more complex needs related to scale or security?** We recommend running Linux as a Virtual Machine (VM) in the cloud and checking out the support that Azure has to offer. This also applies if you want to run Linux as a server.\\n3. **Only want to run Linux as your primary operating system?** If you are good with a slightly more complicated install process and don\\u0027t need access to Windows tools (like Outlook, Teams, Word, PowerPoint, etc), you can run Linux on bare metal to access the full potential of your hardware without any overhead from virtualization or emulation.\\nLearn more about these options below.\\n### Windows Subsystem for Linux (WSL)\\nAvailable to those with a Windows machine, this is the most simple way to install Linux. Just run the Linux install command: \\u0060wsl --install\\u0060 to install the Ubuntu distribution.\\nThis method uses virtualization to integrate the Windows operating system (OS) with the Linux OS (running on an actual Linux kernel). You can add as many different Linux distributions as you please and run your favorite Linux tools, including GUI apps, alongside your favorite Windows tools. You can also mix and match Bash and PowerShell commands in the same command line. A Linux file system hosts your project files, while a separate Windows file systems hosts any developer tools, such as VS Code, that you\\u0027d like to work on the files with. This method of running Linux is highly efficient and performant. Once you choose a Linux distribution or opt to use the default Ubuntu distro, follow the instructions to Use the install Linux command with Windows Subsystem for Linux or learn more in the [WSL documentation](https://learn.microsoft.com/en-us/windows/wsl).\\n### Create a Virtual Machine (VM)\\nAvailable to everyone regardless if you\\u0027re using a Windows or MacOS-based machine, but will require either an Azure account, if creating a VM in the cloud, or virtualization software, if creating a VM directly on your machine (locally).\",\"contentUrl\":\"https://learn.microsoft.com/en-us/linux/install#step-1---choose-a-method-to-install-linux\"},{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\nLinux is an operating system, similar to Windows, but with many different versions due to the nature of being open source and fully customizable. To install Linux, you must choose an install method and choose a Linux distribution.\\nTo install Linux:\\n1. Choose an install method: Windows Subsystem for Linux (WSL), Bare metal Linux; or create a Virtual Machine (VM) to run Linux locally or in the cloud.\\n2. Choose a Linux distribution: Ubuntu, Debian, Kali Linux, openSUSE, etc.\\n3. Follow the steps for your preferred install method:\\n3.1. Use the install Linux command with Windows Subsystem for Linux (WSL)\\n3.2. Create a Linux Virtual Machine (VM) in the cloud\\n3.3. Create a Linux Virtual Machine (VM) on your local machine\\n3.4. Create a bootable USB to install bare-metal Linux\\n4. After installing Linux: Get familiar with your distribution\\u0027s package manager, update and upgrade the packages available, and get familiar with the other [Linux resources at Microsoft](https://learn.microsoft.com/en-us/linux/), such as training courses, Linux-versions of popular tools, news, and Open Source events.\",\"contentUrl\":\"https://learn.microsoft.com/en-us/linux/install\"},{\"title\":\"Guide for changing your dev environment from Mac to Windows\",\"content\":\"# Guide for changing your dev environment from Mac to Windows\\n## Command-line shells and terminals\\nYou can now install Windows Subsystem for Linux (WSL) to support running a Linux shell within Windows. This means that you can run **bash**, with whichever specific Linux distribution you choose, integrated right inside Windows. Using WSL provides the kind of environment most familiar to Mac users. For example, you use **ls** to list the files in a current directory, not **dir** as you would with the traditional Windows Cmd Shell. To learn about installing and using WSL, see the [Windows Subsystem for Linux Installation Guide](https://learn.microsoft.com/en-us/windows/wsl/install). Linux distributions that you can install on Windows with WSL include:\\n1. [Ubuntu 20.04 LTS](https://www.microsoft.com/store/apps/9n6svws3rx71)\\n2. [Kali Linux](https://www.microsoft.com/store/apps/9PKR34TNCV07)\\n3. [Debian GNU/Linux](https://www.microsoft.com/store/apps/9MSVKQC78PK6)\\n4. [openSUSE Leap 15.1](https://www.microsoft.com/store/apps/9NJFZK00FGKV)\\n5. [SUSE Linux Enterprise Server 15 SP1](https://www.microsoft.com/store/apps/9PN498VPMF3Z)\\nJust to name a few. Find more in the [WSL install docs](https://learn.microsoft.com/en-us/windows/wsl/install-win10#install-your-linux-distribution-of-choice) and install them directly from the [Microsoft Store](https://aka.ms/wslstore).\",\"contentUrl\":\"https://learn.microsoft.com/en-us/windows/dev-environment/mac-to-windows#command-line-shells-and-terminals\"},{\"title\":\"Supported Linux operating system versions\",\"content\":\"# Supported Linux operating system versions\\n## Universal Linux (Debian package)\\nSupported versions:\\n1. Debian 8, 9, 10, 11, and 12\\n2. Ubuntu 16.04, 18.04, 20.04, 22.04 and 24.04\\nNote\\n1. Ubuntu 22.04 is supported from Operations Manager 2022 UR1 and later when you apply [this hotfix](https://support.microsoft.com/topic/system-center-operations-manager-2022-now-has-openssl3-0-integration-kb-5024286-331bd221-10f9-42d5-bc06-775eaabe3081).\\n2. Ubuntu 24.04 is supported from Operations Manager 2022 UR2 and later.\\n| Required package | Description | Minimum version| \\n| --- | --- | --- |\\n| libc6 | C Standard shared library | 2.24-11 |\\n| OpenSSL | OpenSSL Libraries; Secure Network Communications Protocol | 1.0, 1.1 or 3.0 and later |\\n| PAM | Pluggable Authentication Modules | 1.1.8-3.1 |\\n\",\"contentUrl\":\"https://learn.microsoft.com/en-us/system-center/scom/plan-supported-crossplat-linux-os?view=sc-om-2025#universal-linux-debian-package\"},{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\n## Step 3 - Follow install method instructions\\n### Install Linux with Windows Subsystem for Linux\\nTo install Linux on a Windows PC, use the install Linux command.\\n1. Open PowerShell or Windows Command Prompt in *administrator mode* by right-clicking and selecting \\u0022Run as administrator\\u0022.\\n2. Enter the Linux install command: \\u0060wsl --install\\u0060.\\n3. Restart your machine.\\n#### Install Linux command options\\nBy default, the install command will use the Ubuntu distribution of Linux. You can see the other distributions that are available in the Microsoft Store by entering the command: \\u0060wsl -l -o\\u0060. If you\\u0027d like to install a distribution other than Ubuntu, just add \\u0060-d\\u0060 to the command with the name of the distribution you\\u0027d like to install:\\n\\u0060\\u0060\\u0060powershell\\nwsl --install -d \\u003CDistro Name\\u003E\\n\\u0060\\u0060\\u0060\\nIf the distribution you want to install isn\\u0027t available in the Microsoft Store, see the Windows Subsystem for Linux (WSL) documentation on how to [Import any Linux distribution to use with WSL](https://learn.microsoft.com/en-us/windows/wsl/use-custom-distro).\\nFor more information on how to use VS Code, Git, databases, Docker, GUI apps, GPU acceleration, NodesJS, USB devices, or mount a disk, build a custom distribution, manage disk space, or set up WSL for your enterprise business, see the [WSL documentation](https://learn.microsoft.com/en-us/windows/wsl/).\\n### Create a Linux Virtual Machine in the cloud\\nTo create a Linux Virtual Machine (VM) that is hosted in the cloud on Azure, log-in to the Azure Portal and select the \\u201CCreate a Virtual Machine\\u201D service. Then follow the instructions for creating a user name, project, instance, SSH key, ports, and copying the IP address.\\nAzure supports a variety of options for setting up Linux VMs in the cloud. For a quickstart guide on how to set up a Linux VM, choose your preferred method:\\n1. [Azure Portal](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-portal?tabs=ubuntu\\u0026source=docs)\\n2. [Azure CLI](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-cli)\\n3. [Azure PowerShell](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-powershell)\\n4. [Terraform](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-terraform)\",\"contentUrl\":\"https://learn.microsoft.com/en-us/linux/install#step-3---follow-install-method-instructions\"},{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\n## Step 1 - Choose a method to install Linux\\nYou are responsible for managing the virtual machine when using a hypervisor, including allocating resources like memory and disk space, and ensuring that it is secure and up-to-date. This requires more technical expertise than some of the other options and may not be as scalable or fault-tolerant.\\nOnce you\\u0027ve chosen a distribution and decided whether you want to run the VM locally, using a hypervisor, or in the cloud, using Azure, follow the instructions to Create a Linux Virtual Machine in the Cloud or Create a Linux Virtual Machine locally using a Hypervisor\\n### Bare metal Linux\\nBare metal Linux just means that Linux is running directly on the device hardware. This install method requires you to create a bootable USB drive by downloading an iso install file from the site hosting your chosen Linux distribution. You will need to use a Windows computer (or any desktop device with an existing OS) to create this drive.\\nMany users choose the traditional method of installing bare metal Linux on a device that is also running Windows and using the **\\u0022dual boot\\u0022** method. To boot Linux and Windows, you need to partition your hard drive to create separate spaces for both the Linux and Windows OS. The performance speed between WSL, virtual machines, and bare metal Linux has become so close that few developers choose this method due to the overhead of needing to restart (reboot) your device any time you want to switch between the operating systems. If you choose the bare metal Linux install route, you may also need to deal with potential driver issues or hardware compatibility problems that may arise with Linux on some devices.\\nYou can find a list of [Popular Linux Distributions available for download on Linux.org](https://linux.org/pages/download/). Once you\\u0027ve chosen a distro, follow the instructions to Create a bootable USB to install bare-metal Linux.\\nWarning\",\"contentUrl\":\"https://learn.microsoft.com/en-us/linux/install#step-1---choose-a-method-to-install-linux\"}], annotations=null, meta=null)\n" + "TextContent(text=[{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\n## Step 2 - Choose a Linux distribution\\nThe different versions of Linux are called \\u0022distributions\\u0022 (sometimes shortened to \\u201Cdistros\\u201D). So there isn\\u2019t actually a way to just \\u201Cinstall Linux\\u201D, you first need to choose which distribution that uses the Linux kernel you want to download and install. There are over 600 active Linux distributions, such as Ubuntu, Debian, Kali Linux, openSUSE, and more. There are a number of factors to consider in choosing a distribution, such as:\\n### Required Linux experience\\nIf you\\u2019re new to Linux, you may want to start with Ubuntu. This is one of the most common starting places for Linux users due to the level of support and ease of use. Ubuntu offers a desktop-version and a server-version depending on how you plan to use it. The specific tasks that the operating system will be responsible for may help you determine which distribution to use. Arch Linux is a popular choice for those who want a highly customizable, do-it-yourself approach that is still stable and well-supported by a large user base. It is a much more complicated place to start, but can help you to get a better understanding of how Linux works due to the amount of custom configuration.\\n### System requirements\\nA minimal, or \\u0022lightweight\\u0022, distribution means a small footprint, with lower memory and processing speed requirements. Alpine Linux is a popular option where users will find most features disabled or not installed by default, but still has everything you need for a containerized app. Security needs: Some Linux users may have specific security concerns to consider. Kali Linux is an example of a distro designed for security tasks, such as Penetration Testing, Security Research, Computer Forensics and Reverse Engineering.\\n### Business and Enterprise requirements\\nBusiness, corporate, or academic environments may have unique concerns from consumer environments. For example Red Hat Enterprise Linux (RHEL), SUSE Linux Enterprise Server (SLES) and Oracle Linux offer 24/7 global support and subscription-based service. CentOS is another popular enterprise distro that is community-supported but still compatible with RHEL.\\n### Community support\",\"contentUrl\":\"https://learn.microsoft.com/en-us/linux/install#step-2---choose-a-linux-distribution\"},{\"title\":\"New to Linux resources for SQL users\",\"content\":\"# New to Linux resources for SQL users\\n**Applies to:**[SQL Server](https://learn.microsoft.com/en-us/sql/sql-server/sql-docs-navigation-guide?view=sql-server-ver17#applies-to) - Linux\\nThis article is intended to provide a learning path through an introduction of Linux concepts. Use the sections in the article as a guided learning path to familiarize yourself with the Linux environment.\\nThis isn\\u0027t intended to be a comprehensive list but instead provides the bare minimum you need to help you administer and navigate your SQL Server on Linux environment. See the [full list of tutorials](https://www.linux.org/forums/linux-beginner-tutorials.123/) to learn more.\\n## What is Linux?\\nThe [What is Linux](https://www.linux.org/threads/what-is-linux.4106/) module introduces the history of Linux as an operating system. The module explains *the Kernel* and where Linux is today. This tutorial serves as an introduction to Linux, helping you get started.\\n## Select a distribution\\nAfter you know the history of Linux, decide which [distribution of Linux](https://www.linux.org/threads/selecting-a-linux-distribution.4117/) best suits your business needs. [SQL Server is supported](https://learn.microsoft.com/en-us/sql/linux/sql-server-linux-release-notes-2019?view=sql-server-ver17#supported-platforms) on various distributions such as Red Hat Enterprise Linux (RHEL), SUSE Linux Enterprise Server (SLES), and Ubuntu.\\n## Get around directories\\nAfter you have chosen the appropriate distribution of Linux, familiarize yourself with [getting around Linux directories](https://www.linux.org/threads/getting-around-in-linux-directories.4120/).\\nThis module will help you answer the following questions:\\n1. How to navigate between different files\\n2. How to identify a file from a directory\\n3. Identify different directories\\n## Install new software\\nNext, learn how to [install new software](https://www.linux.org/threads/installing-new-software-debian-red-hat-slackware.4119/) on your new Linux operating system. This module details installing new software to the Debian and Red Hat Linux operating systems.\",\"contentUrl\":\"https://learn.microsoft.com/en-us/sql/linux/new-to-linux-resources?view=sql-server-ver17\"},{\"title\":\"Part 1.2 - Basic operations on Linux\",\"content\":\"# Part 1.2 - Basic operations on Linux\\n## Run commands by using the \\u0022sudo\\u0022 prefix\\nInstead of becoming the root account user in your session, you can run commands as root by using the \\u0060sudo \\u003Ccommand\\u003E\\u0060 format. This approach is safer because it will run the given command only as a superuser.\\nFor example, if you run \\u0060restart\\u0060 while you\\u0027re not elevated to superuser status, the result is as follows.\\n![Screenshot of restart command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/restart-command.png)\\nTo run this command as a superuser instead, you can either change the session context to root (by running \\u0060sudo su\\u0060) or add the \\u0060sudo\\u0060 prefix, as follows:\\n\\u0060\\u0060\\u0060bash\\nsudo reboot\\n\\u0060\\u0060\\u0060\\nNow, the restart operation runs as expected by the virtual machine.\\n![Screenshot of reboot command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/reboot-command.png)\\n## Package managers\\nPackage managers are used to install, upgrade, and remove applications in Linux. There are several different package managers, such as DPKG (Debian Package Management System), RPM (Red Hat Package Manager), and Zypper for SUSE Package managers themselves are low-level. Therefore, it might be difficult to manage software directly by using the package manager. Fortunately, there are wrappers for these tools that make it easier to install and uninstall software. For example, [yum](https://wikipedia.org/wiki/Yum_%28software%29) is a wrapper around RPM (Red Hat Package Manager) and [APT](https://wikipedia.org/wiki/APT_%28software%29) is a wrapper around DPKG (Debian Package Management System).\\nThis tutorial assumes that you\\u0027re running the [Ubuntu package management system](https://ubuntu.com/server/docs/package-management). This system is derived from the same system that\\u0027s used by the Debian GNU/Linux distribution. Therefore, we can use APT to install the software.\\nDepending on your Linux choice, you might have to use other tools. For example, you can use yum to install or uninstall software if you\\u0027re running Red Hat.\",\"contentUrl\":\"https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-by-using-the-sudo-prefix\"},{\"title\":\"Part 1.2 - Basic operations on Linux\",\"content\":\"# Part 1.2 - Basic operations on Linux\\n## Run commands as a superuser\\nThe superuser is the most privileged user account. It has root (unrestricted) access to all files and folders, and complete control over the operation of the computer. The system administrator uses this account for system maintenance.\\nOn UNIX-like systems, the conventional name of the superuser is \\u0022root\\u0022. The root user can do tasks that are otherwise restricted for standard users, including the following ones:\\n1. Changing system directories\\n2. Changing user privileges\\n3. Changing file ownership\\n4. System-related tasks such as restarts\\n5. Installation of certain applications\\nThere are several ways to run commands as the root account user. One is to use the \\u0060sudo su\\u0060 command. In this command, \\u0060sudo\\u0060 is short for \\u0060superuser do\\u0060, and \\u0060su\\u0060 is an abbreviation for \\u0022switch user.\\u0022 You can also use the \\u0060su\\u0060 command to switch between standard users.\\nImportant\\nAfter you become the root account user, everything you do runs in root context. Therefore, you should act carefully because the root account has access to the entire system.\\nTo demonstrate a permissions issue, create a text file in a special folder that\\u0027s named */etc*.\\nThe */etc/* folder is where system configuration files are located. This folder protects its contents from being changed by a standard user.\\nRun the following command:\\n\\u0060\\u0060\\u0060bash\\necho hello world \\u003E /etc/helloworld.txt\\n\\u0060\\u0060\\u0060\\nThe \\u0060echo\\u0060 command writes all the text that follows it to the output. The angle bracket (\\u0060\\u003E\\u0060) tells the system to send the output to the */etc/helloworld.txt* file instead of the console. This behavior is similar to how Windows works.\\nNote\\nLinux is case-sensitive: *Helloworld.txt*, *helloworld.txt* and *helloworld.Txt* are different files.\\nYou don\\u0027t have to provide a file name extension. The name \\u0022helloworld\\u0022 by itself is perfectly valid. (The \\u0022txt\\u0022 extension is used here only as an example.)\\nWhen you run this command, you receive a **permission denied** error message.\\n![Screenshot of permission denied message.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/permission-denied-message.png)\\nThe operation fails because the */etc* folder is special folder that a standard user can\\u0027t change. Verify the user by running the \\u0060id\\u0060 command.\\n![Screenshot of I D command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/id-command.png)\",\"contentUrl\":\"https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-as-a-superuser\"},{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\n## Step 1 - Choose a method to install Linux\\nThe choice of which method to use for installing Linux depends on your needs and preferences.\\n1. **New to using Linux?** We recommend starting with Windows Subsystem for Linux (WSL) as it\\u0027s the easiest way to get up and running.\\n2. **Work in a business environment with more complex needs related to scale or security?** We recommend running Linux as a Virtual Machine (VM) in the cloud and checking out the support that Azure has to offer. This also applies if you want to run Linux as a server.\\n3. **Only want to run Linux as your primary operating system?** If you are good with a slightly more complicated install process and don\\u0027t need access to Windows tools (like Outlook, Teams, Word, PowerPoint, etc), you can run Linux on bare metal to access the full potential of your hardware without any overhead from virtualization or emulation.\\nLearn more about these options below.\\n### Windows Subsystem for Linux (WSL)\\nAvailable to those with a Windows machine, this is the most simple way to install Linux. Just run the Linux install command: \\u0060wsl --install\\u0060 to install the Ubuntu distribution.\\nThis method uses virtualization to integrate the Windows operating system (OS) with the Linux OS (running on an actual Linux kernel). You can add as many different Linux distributions as you please and run your favorite Linux tools, including GUI apps, alongside your favorite Windows tools. You can also mix and match Bash and PowerShell commands in the same command line. A Linux file system hosts your project files, while a separate Windows file systems hosts any developer tools, such as VS Code, that you\\u0027d like to work on the files with. This method of running Linux is highly efficient and performant. Once you choose a Linux distribution or opt to use the default Ubuntu distro, follow the instructions to Use the install Linux command with Windows Subsystem for Linux or learn more in the [WSL documentation](https://learn.microsoft.com/en-us/windows/wsl).\\n![WSL install command screenshot](https://learn.microsoft.com/en-us/linux/images/wsl-install-command-screenshot.png)\\n### Create a Virtual Machine (VM)\\nAvailable to everyone regardless if you\\u0027re using a Windows or MacOS-based machine, but will require either an Azure account, if creating a VM in the cloud, or virtualization software, if creating a VM directly on your machine (locally).\",\"contentUrl\":\"https://learn.microsoft.com/en-us/linux/install#step-1---choose-a-method-to-install-linux\"},{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\nLinux is an operating system, similar to Windows, but with many different versions due to the nature of being open source and fully customizable. To install Linux, you must choose an install method and choose a Linux distribution.\\nTo install Linux:\\n1. Choose an install method: Windows Subsystem for Linux (WSL), Bare metal Linux; or create a Virtual Machine (VM) to run Linux locally or in the cloud.\\n2. Choose a Linux distribution: Ubuntu, Debian, Kali Linux, openSUSE, etc.\\n3. Follow the steps for your preferred install method:\\n3.1. Use the install Linux command with Windows Subsystem for Linux (WSL)\\n3.2. Create a Linux Virtual Machine (VM) in the cloud\\n3.3. Create a Linux Virtual Machine (VM) on your local machine\\n3.4. Create a bootable USB to install bare-metal Linux\\n4. After installing Linux: Get familiar with your distribution\\u0027s package manager, update and upgrade the packages available, and get familiar with the other [Linux resources at Microsoft](https://learn.microsoft.com/en-us/linux/), such as training courses, Linux-versions of popular tools, news, and Open Source events.\",\"contentUrl\":\"https://learn.microsoft.com/en-us/linux/install\"},{\"title\":\"Guide for changing your dev environment from Mac to Windows\",\"content\":\"# Guide for changing your dev environment from Mac to Windows\\n## Command-line shells and terminals\\nYou can now install Windows Subsystem for Linux (WSL) to support running a Linux shell within Windows. This means that you can run **bash**, with whichever specific Linux distribution you choose, integrated right inside Windows. Using WSL provides the kind of environment most familiar to Mac users. For example, you use **ls** to list the files in a current directory, not **dir** as you would with the traditional Windows Cmd Shell. To learn about installing and using WSL, see the [Windows Subsystem for Linux Installation Guide](https://learn.microsoft.com/en-us/windows/wsl/install). Linux distributions that you can install on Windows with WSL include:\\n1. [Ubuntu 20.04 LTS](https://www.microsoft.com/store/apps/9n6svws3rx71)\\n2. [Kali Linux](https://www.microsoft.com/store/apps/9PKR34TNCV07)\\n3. [Debian GNU/Linux](https://www.microsoft.com/store/apps/9MSVKQC78PK6)\\n4. [openSUSE Leap 15.1](https://www.microsoft.com/store/apps/9NJFZK00FGKV)\\n5. [SUSE Linux Enterprise Server 15 SP1](https://www.microsoft.com/store/apps/9PN498VPMF3Z)\\nJust to name a few. Find more in the [WSL install docs](https://learn.microsoft.com/en-us/windows/wsl/install-win10#install-your-linux-distribution-of-choice) and install them directly from the [Microsoft Store](https://aka.ms/wslstore).\",\"contentUrl\":\"https://learn.microsoft.com/en-us/windows/dev-environment/mac-to-windows#command-line-shells-and-terminals\"},{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\n## Step 3 - Follow install method instructions\\n### Install Linux with Windows Subsystem for Linux\\nTo install Linux on a Windows PC, use the install Linux command.\\n1. Open PowerShell or Windows Command Prompt in *administrator mode* by right-clicking and selecting \\u0022Run as administrator\\u0022.\\n2. Enter the Linux install command: \\u0060wsl --install\\u0060.\\n3. Restart your machine.\\n![WSL Linux Install List Command screenshot](https://learn.microsoft.com/en-us/linux/images/wsl-install-list-screenshot.png)\\n#### Install Linux command options\\nBy default, the install command will use the Ubuntu distribution of Linux. You can see the other distributions that are available in the Microsoft Store by entering the command: \\u0060wsl -l -o\\u0060. If you\\u0027d like to install a distribution other than Ubuntu, just add \\u0060-d\\u0060 to the command with the name of the distribution you\\u0027d like to install:\\n\\u0060\\u0060\\u0060powershell\\nwsl --install -d \\u003CDistro Name\\u003E\\n\\u0060\\u0060\\u0060\\nIf the distribution you want to install isn\\u0027t available in the Microsoft Store, see the Windows Subsystem for Linux (WSL) documentation on how to [Import any Linux distribution to use with WSL](https://learn.microsoft.com/en-us/windows/wsl/use-custom-distro).\\nFor more information on how to use VS Code, Git, databases, Docker, GUI apps, GPU acceleration, NodesJS, USB devices, or mount a disk, build a custom distribution, manage disk space, or set up WSL for your enterprise business, see the [WSL documentation](https://learn.microsoft.com/en-us/windows/wsl/).\\n### Create a Linux Virtual Machine in the cloud\\nTo create a Linux Virtual Machine (VM) that is hosted in the cloud on Azure, log-in to the Azure Portal and select the \\u201CCreate a Virtual Machine\\u201D service. Then follow the instructions for creating a user name, project, instance, SSH key, ports, and copying the IP address.\\nAzure supports a variety of options for setting up Linux VMs in the cloud. For a quickstart guide on how to set up a Linux VM, choose your preferred method:\\n1. [Azure Portal](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-portal?tabs=ubuntu\\u0026source=docs)\\n2. [Azure CLI](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-cli)\\n3. [Azure PowerShell](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-powershell)\\n4. [Terraform](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-terraform)\",\"contentUrl\":\"https://learn.microsoft.com/en-us/linux/install#step-3---follow-install-method-instructions\"},{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\n## Step 2 - Choose a Linux distribution\\nA large user community that consistently contributes to the distribution, fixing bugs, adding features, helping with questions, etc. is another good consideration. Ubuntu is one of the most popular distos. openSUSE has been around longer than most other distributions and offers in addition to the stable version (Leap) a rolling version (Tumbleweed). Many other distros rapidly grow popular as the needs and preferences of the user community change. The Linux.org site has resources to help you assess what\\u0027s available, well-supported, or growing in popularity.\\n![Screenshot of the list of distributions on Linux.org](https://learn.microsoft.com/en-us/linux/images/linux-distributions-list-screenshot.png)\",\"contentUrl\":\"https://learn.microsoft.com/en-us/linux/install#step-2---choose-a-linux-distribution\"},{\"title\":\"Part 1.2 - Basic operations on Linux\",\"content\":\"# Part 1.2 - Basic operations on Linux\\n## Run commands as a superuser\\nTo become the root account user, run the \\u0060sudo su\\u0060 command.\\n![Screenshot of sudo su command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/sudo-su-command.png)\\nYou should notice two things: When you become root, the dollar sign character (\\u0060$\\u0060) becomes a pound sign character (\\u0060#\\u0060). Also, the username is changed to **root**. If you run the same \\u0060id\\u0060 command again, you see that the user and group ID of root are **0**.\\n![Screenshot of root command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/root-command.png)\\nImportant\\nNow you have elevated yourself to the \\u0022superuser\\u0022 role in your session, you have complete access to the system. Keep in mind that this is a potentially dangerous situation, and you should exercise caution as you continue.\\nNow, run the same command one more time:\\n\\u0060\\u0060\\u0060bash\\necho hello world \\u003E /etc/helloworld.txt\\n\\u0060\\u0060\\u0060\\nThis time, you don\\u0027t receive any error message. To verify if the file is created, run the following command:\\n\\u0060\\u0060\\u0060bash\\nll /etc/hello*\\n\\u0060\\u0060\\u0060\\nThe \\u0060ll\\u0060 command lists the files and subfolders that are in a specified folder. In this example, the \\u0060/etc/hello*\\u0060 parameter value limits the command output to the files or folders whose name begins in \\u0060hello\\u0060.\\n![Screenshot of hello command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/hello-command.png)\\nThe output shows that the file was created. What if we want to examine the content of the file? The \\u0060cat\\u0060 command helps here. To see the content, run \\u0060cat /etc/helloworld.txt\\u0060.\\n![Screenshot of cat command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/cat-command.png)\\nThe \\u0060cat\\u0060 command reads files sequentially, and writes their content to the standard output. Therefore, it writes \\u0022hello world\\u0022 in the console.\\nTo prevent mistakes that could damage the system while you\\u0027re elevated to root status, it\\u0027s a good practice to exit the root session and return to your standard user session to avoid some dangerous operations. To do this, run \\u0060exit\\u0060. You can see that the pound sign reverts to a dollar sign, and the username is displayed as your standard user.\\n![Screenshot of exit command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/exit-command.png)\\nFor additional practice, run the same \\u0060ll\\u0060 and \\u0060cat\\u0060 commands within your standard user context. You\\u0027ll see that you can\\u0027t create the file in the */etc/* folder, but can you list the file and read the contents.\",\"contentUrl\":\"https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-as-a-superuser\"}], annotations=null, meta=null)\n" ] } ], diff --git a/samples/weather-stdio-server/gradle/libs.versions.toml b/samples/weather-stdio-server/gradle/libs.versions.toml index d525461..6056b94 100644 --- a/samples/weather-stdio-server/gradle/libs.versions.toml +++ b/samples/weather-stdio-server/gradle/libs.versions.toml @@ -4,7 +4,7 @@ coroutines = "1.10.2" kotlin = "2.2.21" ktor = "3.2.3" logging = "7.0.13" -mcp-kotlin = "0.8.0" +mcp-kotlin = "0.8.1" shadow = "9.2.2" slf4j = "2.0.17" From 10a0b67204dc0aa6790770b0ebcdc3c766f33297 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Fri, 5 Dec 2025 18:51:19 +0100 Subject: [PATCH 095/449] [WIP] StreamableHttpServerTransport implementation (#449) # [WIP] StreamableHttpServerTransport implementation - Implement StreamableHttpServerTransport for MCP spec - Support both SSE streaming and direct HTTP JSON responses - Add event store interface for resumability > [!IMPORTANT] > The work is in progress. StreamableHttpServerTransport is not production-ready yet!!! Based on #235 ## How Has This Been Tested? ## 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- gradle/libs.versions.toml | 3 + kotlin-sdk-core/api/kotlin-sdk-core.api | 10 + .../kotlin/sdk/shared/Protocol.kt | 2 + .../kotlin/sdk/types/common.kt | 4 +- .../kotlin/sdk/types/jsonRpc.kt | 7 +- .../kotlin/sdk/types/serializers.kt | 1 + .../kotlin/sdk/types/JsonRpcTest.kt | 12 + kotlin-sdk-server/api/kotlin-sdk-server.api | 25 + kotlin-sdk-server/build.gradle.kts | 7 + .../kotlin/sdk/server/EventStore.kt | 33 + .../server/StreamableHttpServerTransport.kt | 654 ++++++++++++++++++ .../StreamableHttpServerTransportTest.kt | 269 +++++++ 12 files changed, 1024 insertions(+), 3 deletions(-) create mode 100644 kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/EventStore.kt create mode 100644 kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt create mode 100644 kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c1d6773..0cbc5f0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -44,6 +44,9 @@ kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx- ktor-client-apache5 = { group = "io.ktor", name = "ktor-client-apache5", version.ref = "ktor" } ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" } ktor-client-logging = { group = "io.ktor", name = "ktor-client-logging", version.ref = "ktor" } +ktor-server-content-negotiation = { group = "io.ktor", name = "ktor-server-content-negotiation", version.ref = "ktor" } +ktor-client-content-negotiation = { group = "io.ktor", name = "ktor-client-content-negotiation", version.ref = "ktor" } +ktor-serialization = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" } ktor-server-core = { group = "io.ktor", name = "ktor-server-core", version.ref = "ktor" } ktor-server-sse = { group = "io.ktor", name = "ktor-server-sse", version.ref = "ktor" } ktor-server-websockets = { group = "io.ktor", name = "ktor-server-websockets", version.ref = "ktor" } diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index 49b88cb..8fff88b 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -988,6 +988,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ClientResult$Default } public final class io/modelcontextprotocol/kotlin/sdk/types/CommonKt { + public static final field DEFAULT_NEGOTIATED_PROTOCOL_VERSION Ljava/lang/String; public static final field LATEST_PROTOCOL_VERSION Ljava/lang/String; public static final fun ProgressToken (J)Lio/modelcontextprotocol/kotlin/sdk/types/RequestId; public static final fun ProgressToken (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/RequestId; @@ -2046,6 +2047,15 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/InitializedNotificat public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/JSONRPCEmptyMessage : io/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCEmptyMessage; + public fun equals (Ljava/lang/Object;)Z + public fun getJsonrpc ()Ljava/lang/String; + public fun hashCode ()I + public final fun serializer ()Lkotlinx/serialization/KSerializer; + public fun toString ()Ljava/lang/String; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/JSONRPCError : io/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCError$Companion; public fun (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Lio/modelcontextprotocol/kotlin/sdk/types/RPCError;)V diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt index 7dd04bc..358b565 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt @@ -4,6 +4,7 @@ import io.github.oshai.kotlinlogging.KotlinLogging import io.modelcontextprotocol.kotlin.sdk.types.CancelledNotification import io.modelcontextprotocol.kotlin.sdk.types.CancelledNotificationParams import io.modelcontextprotocol.kotlin.sdk.types.EmptyResult +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCEmptyMessage import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCError import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest @@ -249,6 +250,7 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio is JSONRPCRequest -> onRequest(message) is JSONRPCNotification -> onNotification(message) is JSONRPCError -> onResponse(null, message) + is JSONRPCEmptyMessage -> Unit } } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt index f715be7..e22dab7 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt @@ -1,7 +1,5 @@ package io.modelcontextprotocol.kotlin.sdk.types -import io.modelcontextprotocol.kotlin.sdk.types.Icon.Theme.Dark -import io.modelcontextprotocol.kotlin.sdk.types.Icon.Theme.Light import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonObject @@ -12,6 +10,8 @@ import kotlinx.serialization.json.JsonObject public const val LATEST_PROTOCOL_VERSION: String = "2025-06-18" +public const val DEFAULT_NEGOTIATED_PROTOCOL_VERSION: String = "2025-03-26" + public val SUPPORTED_PROTOCOL_VERSIONS: List = listOf( LATEST_PROTOCOL_VERSION, "2025-03-26", diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt index 8a580eb..50fd02f 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt @@ -85,6 +85,11 @@ public sealed interface JSONRPCMessage { public val jsonrpc: String } +@Serializable +public data object JSONRPCEmptyMessage : JSONRPCMessage { + override val jsonrpc: String = JSONRPC_VERSION +} + // ============================================================================ // JSONRPCRequest // ============================================================================ @@ -197,7 +202,7 @@ public data class JSONRPCResponse(val id: RequestId, val result: RequestResult = * @property error Details about the error that occurred, including error code and message. */ @Serializable -public data class JSONRPCError(val id: RequestId, val error: RPCError) : JSONRPCMessage { +public data class JSONRPCError(val id: RequestId?, val error: RPCError) : JSONRPCMessage { @EncodeDefault override val jsonrpc: String = JSONRPC_VERSION } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt index 721a2aa..a00b26b 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt @@ -384,6 +384,7 @@ internal object JSONRPCMessagePolymorphicSerializer : "result" in jsonObject -> JSONRPCResponse.serializer() "method" in jsonObject && "id" in jsonObject -> JSONRPCRequest.serializer() "method" in jsonObject -> JSONRPCNotification.serializer() + jsonObject.isEmpty() || jsonObject.keys == setOf("jsonrpc") -> JSONRPCEmptyMessage.serializer() else -> throw SerializationException("Invalid JSONRPCMessage type: ${jsonObject.keys}") } } diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/JsonRpcTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/JsonRpcTest.kt index d8d2058..ce02cc0 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/JsonRpcTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/JsonRpcTest.kt @@ -430,4 +430,16 @@ class JsonRpcTest { request.method shouldBe "notifications/log" request.params shouldBeSameInstanceAs params } + + @Test + fun `should deserialize JSONRPCEmptyMessage`() { + val json = """ + { + "jsonrpc": "2.0" + } + """.trimIndent() + + val message = McpJson.decodeFromString(json) + message shouldBeSameInstanceAs JSONRPCEmptyMessage + } } diff --git a/kotlin-sdk-server/api/kotlin-sdk-server.api b/kotlin-sdk-server/api/kotlin-sdk-server.api index e5183f0..53c971b 100644 --- a/kotlin-sdk-server/api/kotlin-sdk-server.api +++ b/kotlin-sdk-server/api/kotlin-sdk-server.api @@ -1,3 +1,9 @@ +public abstract interface class io/modelcontextprotocol/kotlin/sdk/server/EventStore { + public abstract fun getStreamIdForEventId (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun replayEventsAfter (Ljava/lang/String;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun storeEvent (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + public final class io/modelcontextprotocol/kotlin/sdk/server/KtorServerKt { public static final fun mcp (Lio/ktor/server/application/Application;Lkotlin/jvm/functions/Function1;)V public static final fun mcp (Lio/ktor/server/routing/Routing;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V @@ -151,6 +157,25 @@ public final class io/modelcontextprotocol/kotlin/sdk/server/StdioServerTranspor public fun start (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } +public final class io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport : io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport { + public static final field STANDALONE_SSE_STREAM_ID Ljava/lang/String; + public fun ()V + public fun (ZZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Ljava/lang/Long;)V + public synthetic fun (ZZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Ljava/lang/Long;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun closeSseStream (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun getSessionId ()Ljava/lang/String; + public final fun handleDeleteRequest (Lio/ktor/server/application/ApplicationCall;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun handleGetRequest (Lio/ktor/server/sse/ServerSSESession;Lio/ktor/server/application/ApplicationCall;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun handlePostRequest (Lio/ktor/server/sse/ServerSSESession;Lio/ktor/server/application/ApplicationCall;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun handleRequest (Lio/ktor/server/sse/ServerSSESession;Lio/ktor/server/application/ApplicationCall;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 final fun setOnSessionClosed (Lkotlin/jvm/functions/Function1;)V + public final fun setOnSessionInitialized (Lkotlin/jvm/functions/Function1;)V + public final fun setSessionIdGenerator (Lkotlin/jvm/functions/Function0;)V + public fun start (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + public final class io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensionsKt { public static final fun mcpWebSocket (Lio/ktor/server/application/Application;Ljava/lang/String;Lkotlin/jvm/functions/Function0;)V public static final fun mcpWebSocket (Lio/ktor/server/application/Application;Lkotlin/jvm/functions/Function0;)V diff --git a/kotlin-sdk-server/build.gradle.kts b/kotlin-sdk-server/build.gradle.kts index 64a2749..e51a643 100644 --- a/kotlin-sdk-server/build.gradle.kts +++ b/kotlin-sdk-server/build.gradle.kts @@ -26,6 +26,13 @@ kotlin { jvmTest { dependencies { + implementation(libs.ktor.client.logging) + implementation(libs.ktor.server.content.negotiation) + implementation(libs.ktor.client.content.negotiation) + implementation(libs.ktor.serialization) + implementation(libs.ktor.server.test.host) + implementation(libs.kotest.assertions.core) + implementation(libs.kotest.assertions.json) runtimeOnly(libs.slf4j.simple) } } diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/EventStore.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/EventStore.kt new file mode 100644 index 0000000..ff9abbf --- /dev/null +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/EventStore.kt @@ -0,0 +1,33 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage + +/** + * Interface for resumability support via event storage + */ +public interface EventStore { + /** + * Stores an event for later retrieval + * @param streamId ID of the stream the event belongs to + * @param message The JSON-RPC message to store + * @returns The generated event ID for the stored event + */ + public suspend fun storeEvent(streamId: String, message: JSONRPCMessage): String + + /** + * Replays events after the specified event ID + * @param lastEventId The last event ID that was received + * @param sender Function to send events + * @return The stream ID for the replayed events + */ + public suspend fun replayEventsAfter( + lastEventId: String, + sender: suspend (eventId: String, message: JSONRPCMessage) -> Unit, + ): String + + /** + * Returns the stream ID associated with [eventId], or null if the event is unknown. + * Default implementation is a no-op which disables extra validation during replay. + */ + public suspend fun getStreamIdForEventId(eventId: String): String? +} diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt new file mode 100644 index 0000000..56bcead --- /dev/null +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt @@ -0,0 +1,654 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.ktor.http.ContentType +import io.ktor.http.HttpHeaders +import io.ktor.http.HttpMethod +import io.ktor.http.HttpStatusCode +import io.ktor.server.application.ApplicationCall +import io.ktor.server.request.contentType +import io.ktor.server.request.header +import io.ktor.server.request.httpMethod +import io.ktor.server.request.receiveText +import io.ktor.server.response.header +import io.ktor.server.response.respond +import io.ktor.server.response.respondNullable +import io.ktor.server.sse.ServerSSESession +import io.ktor.util.collections.ConcurrentMap +import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport +import io.modelcontextprotocol.kotlin.sdk.shared.TransportSendOptions +import io.modelcontextprotocol.kotlin.sdk.types.DEFAULT_NEGOTIATED_PROTOCOL_VERSION +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCEmptyMessage +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCError +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse +import io.modelcontextprotocol.kotlin.sdk.types.McpJson +import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.RPCError +import io.modelcontextprotocol.kotlin.sdk.types.RequestId +import io.modelcontextprotocol.kotlin.sdk.types.SUPPORTED_PROTOCOL_VERSIONS +import kotlinx.coroutines.job +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.decodeFromJsonElement +import kotlin.concurrent.atomics.AtomicBoolean +import kotlin.concurrent.atomics.ExperimentalAtomicApi +import kotlin.uuid.ExperimentalUuidApi +import kotlin.uuid.Uuid + +internal const val MCP_SESSION_ID_HEADER = "mcp-session-id" +private const val MCP_PROTOCOL_VERSION_HEADER = "mcp-protocol-version" +private const val MCP_RESUMPTION_TOKEN_HEADER = "Last-Event-ID" +private const val MAXIMUM_MESSAGE_SIZE = 4 * 1024 * 1024 // 4 MB + +/** + * A holder for an active request call. + * If enableJsonResponse is true, session is null. + * Otherwise, session is not null. + */ +private data class SessionContext(val session: ServerSSESession?, val call: ApplicationCall) + +/** + * Server transport for Streamable HTTP: this implements the MCP Streamable HTTP transport specification. + * It supports both SSE streaming and direct HTTP responses. + * + * In stateful mode: + * - Session ID is generated and included in response headers + * - Session ID is always included in initialization responses + * - Requests with invalid session IDs are rejected with 404 Not Found + * - Non-initialization requests without a session ID are rejected with 400 Bad Request + * - State is maintained in-memory (connections, message history) + * + * In stateless mode: + * - No Session ID is included in any responses + * - No session validation is performed + * + * @param enableJsonResponse If true, the server will return JSON responses instead of starting an SSE stream. + * This can be useful for simple request/response scenarios without streaming. + * Default is false (SSE streams are preferred). + * @param enableDnsRebindingProtection Enable DNS rebinding protection (requires allowedHosts and/or allowedOrigins to be configured). + * Default is false for backwards compatibility. + * @param allowedHosts List of allowed host header values for DNS rebinding protection. + * If not specified, host validation is disabled. + * @param allowedOrigins List of allowed origin header values for DNS rebinding protection. + * If not specified, origin validation is disabled. + * @param eventStore Event store for resumability support + * If provided, resumability will be enabled, allowing clients to reconnect and resume messages + * @param retryIntervalMillis Retry interval (in milliseconds) advertised via SSE priming events to hint the client when to reconnect. + * Applies only when an [eventStore] is configured. Defaults to `null` (no retry hint). + */ +@OptIn(ExperimentalUuidApi::class, ExperimentalAtomicApi::class) +public class StreamableHttpServerTransport( + private val enableJsonResponse: Boolean = false, + private val enableDnsRebindingProtection: Boolean = false, + private val allowedHosts: List? = null, + private val allowedOrigins: List? = null, + private val eventStore: EventStore? = null, + private val retryIntervalMillis: Long? = null, +) : AbstractTransport() { + public var sessionId: String? = null + private set + + private var sessionIdGenerator: (() -> String)? = { Uuid.random().toString() } + private var onSessionInitialized: ((sessionId: String) -> Unit)? = null + private var onSessionClosed: ((sessionId: String) -> Unit)? = null + + private val started: AtomicBoolean = AtomicBoolean(false) + private val initialized: AtomicBoolean = AtomicBoolean(false) + + private val streamsMapping: ConcurrentMap = ConcurrentMap() + private val requestToStreamMapping: ConcurrentMap = ConcurrentMap() + private val requestToResponseMapping: ConcurrentMap = ConcurrentMap() + + private val sessionMutex = Mutex() + private val streamMutex = Mutex() + + private companion object { + const val STANDALONE_SSE_STREAM_ID = "_GET_stream" + } + + /** + * Function that generates a session ID for the transport. + * The session ID SHOULD be globally unique and cryptographically secure + * (e.g., a securely generated UUID, a JWT, or a cryptographic hash) + * + * Set undefined to disable session management. + */ + public fun setSessionIdGenerator(block: (() -> String)?) { + sessionIdGenerator = block + } + + /** + * A callback for session initialization events + * This is called when the server initializes a new session. + * Useful in cases when you need to register multiple mcp sessions + * and need to keep track of them. + */ + public fun setOnSessionInitialized(block: ((String) -> Unit)?) { + onSessionInitialized = block + } + + /** + * A callback for session close events + * This is called when the server closes a session due to a DELETE request. + * Useful in cases when you need to clean up resources associated with the session. + * Note that this is different from the transport closing, if you are handling + * HTTP requests from multiple nodes you might want to close each + * StreamableHTTPServerTransport after a request is completed while still keeping the + * session open/running. + */ + public fun setOnSessionClosed(block: ((String) -> Unit)?) { + onSessionClosed = block + } + + override suspend fun start() { + check(started.compareAndSet(expectedValue = false, newValue = true)) { + "StreamableHttpServerTransport already started! If using Server class, note that connect() calls start() automatically." + } + } + + override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { + val responseRequestId: RequestId? = when (message) { + is JSONRPCResponse -> message.id + is JSONRPCError -> message.id + else -> null + } + val routingRequestId = responseRequestId ?: options?.relatedRequestId + + // Standalone SSE stream + if (routingRequestId == null) { + require(message !is JSONRPCResponse && message !is JSONRPCError) { + "Cannot send a response on a standalone SSE stream unless resuming a previous client request" + } + val standaloneStream = streamsMapping[STANDALONE_SSE_STREAM_ID] ?: return + emitOnStream(STANDALONE_SSE_STREAM_ID, standaloneStream.session, message) + return + } + + val streamId = requestToStreamMapping[routingRequestId] + ?: error("No connection established for request id $routingRequestId") + val activeStream = streamsMapping[streamId] + + if (!enableJsonResponse) { + activeStream?.let { stream -> + emitOnStream(streamId, stream.session, message) + } + } + + val isTerminated = message is JSONRPCResponse || message is JSONRPCError + if (!isTerminated) return + + requestToResponseMapping[responseRequestId!!] = message + val relatedIds = requestToStreamMapping.filterValues { it == streamId }.keys + + if (relatedIds.any { it !in requestToResponseMapping }) return + + streamMutex.withLock { + if (activeStream == null) error("No connection established for request ID: $routingRequestId") + + if (enableJsonResponse) { + activeStream.call.response.header(HttpHeaders.ContentType, ContentType.Application.Json.toString()) + sessionId?.let { activeStream.call.response.header(MCP_SESSION_ID_HEADER, it) } + val responses = relatedIds.mapNotNull { requestToResponseMapping[it] } + val payload = if (responses.size == 1) { + responses.first() + } else { + responses + } + activeStream.call.respond(payload) + } else { + activeStream.session?.close() + } + + // Clean up + relatedIds.forEach { requestId -> + requestToResponseMapping.remove(requestId) + requestToStreamMapping.remove(requestId) + } + } + } + + override suspend fun close() { + streamMutex.withLock { + streamsMapping.values.forEach { + try { + it.session?.close() + } catch (_: Exception) { + } + } + streamsMapping.clear() + requestToStreamMapping.clear() + requestToResponseMapping.clear() + _onClose() + } + } + + /** + * Handles an incoming HTTP request, whether GET, POST or DELETE + */ + public suspend fun handleRequest(session: ServerSSESession?, call: ApplicationCall) { + validateHeaders(call)?.let { reason -> + call.reject(HttpStatusCode.Forbidden, RPCError.ErrorCode.CONNECTION_CLOSED, reason) + _onError(Error(reason)) + return + } + + when (call.request.httpMethod) { + HttpMethod.Post -> handlePostRequest(session, call) + + HttpMethod.Get -> handleGetRequest(session, call) + + HttpMethod.Delete -> handleDeleteRequest(call) + + else -> call.run { + response.header(HttpHeaders.Allow, "GET, POST, DELETE") + reject(HttpStatusCode.MethodNotAllowed, RPCError.ErrorCode.CONNECTION_CLOSED, "Method not allowed.") + } + } + } + + /** + * Handles POST requests containing JSON-RPC messages + */ + public suspend fun handlePostRequest(session: ServerSSESession?, call: ApplicationCall) { + try { + if (!enableJsonResponse && session == null) error("Server session can't be null with json response") + + val acceptHeader = call.request.header(HttpHeaders.Accept) + val isAcceptEventStream = acceptHeader.accepts(ContentType.Text.EventStream) + val isAcceptJson = acceptHeader.accepts(ContentType.Application.Json) + + if (!isAcceptEventStream || !isAcceptJson) { + call.reject( + HttpStatusCode.NotAcceptable, + RPCError.ErrorCode.CONNECTION_CLOSED, + "Not Acceptable: Client must accept both application/json and text/event-stream", + ) + return + } + + if (!call.request.contentType().match(ContentType.Application.Json)) { + call.reject( + HttpStatusCode.UnsupportedMediaType, + RPCError.ErrorCode.CONNECTION_CLOSED, + "Unsupported Media Type: Content-Type must be application/json", + ) + return + } + + val messages = parseBody(call) ?: return + val isInitializationRequest = messages.any { + it is JSONRPCRequest && it.method == Method.Defined.Initialize.value + } + + if (isInitializationRequest) { + if (initialized.load() && sessionId != null) { + call.reject( + HttpStatusCode.BadRequest, + RPCError.ErrorCode.INVALID_REQUEST, + "Invalid Request: Server already initialized", + ) + return + } + if (messages.size > 1) { + call.reject( + HttpStatusCode.BadRequest, + RPCError.ErrorCode.INVALID_REQUEST, + "Invalid Request: Only one initialization request is allowed", + ) + return + } + + sessionMutex.withLock { + if (sessionId != null) return@withLock + sessionId = sessionIdGenerator?.invoke() + initialized.store(true) + sessionId?.let { onSessionInitialized?.invoke(it) } + } + } else { + if (!validateSession(call) || !validateProtocolVersion(call)) return + } + + val hasRequest = messages.any { it is JSONRPCRequest } + if (!hasRequest) { + call.respondNullable(status = HttpStatusCode.Accepted, message = null) + messages.forEach { message -> _onMessage(message) } + return + } + + val streamId = Uuid.random().toString() + if (!enableJsonResponse) { + call.appendSseHeaders() + flushSse(session) // flush headers immediately + maybeSendPrimingEvent(streamId, session) + } + + streamMutex.withLock { + streamsMapping[streamId] = SessionContext(session, call) + messages.filterIsInstance().forEach { requestToStreamMapping[it.id] = streamId } + } + call.coroutineContext.job.invokeOnCompletion { streamsMapping.remove(streamId) } + + messages.forEach { message -> _onMessage(message) } + } catch (e: Exception) { + call.reject( + HttpStatusCode.BadRequest, + RPCError.ErrorCode.PARSE_ERROR, + "Parse error: ${e.message}", + ) + _onError(e) + } + } + + public suspend fun handleGetRequest(session: ServerSSESession?, call: ApplicationCall) { + if (enableJsonResponse) { + call.reject( + HttpStatusCode.MethodNotAllowed, + RPCError.ErrorCode.CONNECTION_CLOSED, + "Method not allowed.", + ) + return + } + val sseSession = session ?: error("Server session can't be null for streaming GET requests") + + val acceptHeader = call.request.header(HttpHeaders.Accept) + if (!acceptHeader.accepts(ContentType.Text.EventStream)) { + call.reject( + HttpStatusCode.NotAcceptable, + RPCError.ErrorCode.CONNECTION_CLOSED, + "Not Acceptable: Client must accept text/event-stream", + ) + return + } + + if (!validateSession(call) || !validateProtocolVersion(call)) return + + eventStore?.let { store -> + call.request.header(MCP_RESUMPTION_TOKEN_HEADER)?.let { lastEventId -> + replayEvents(store, lastEventId, sseSession) + return + } + } + + if (STANDALONE_SSE_STREAM_ID in streamsMapping) { + call.reject( + HttpStatusCode.Conflict, + RPCError.ErrorCode.CONNECTION_CLOSED, + "Conflict: Only one SSE stream is allowed per session", + ) + return + } + + call.appendSseHeaders() + flushSse(sseSession) // flush headers immediately + streamsMapping[STANDALONE_SSE_STREAM_ID] = SessionContext(sseSession, call) + maybeSendPrimingEvent(STANDALONE_SSE_STREAM_ID, sseSession) + sseSession.coroutineContext.job.invokeOnCompletion { + streamsMapping.remove(STANDALONE_SSE_STREAM_ID) + } + } + + public suspend fun handleDeleteRequest(call: ApplicationCall) { + if (!validateSession(call) || !validateProtocolVersion(call)) return + sessionId?.let { onSessionClosed?.invoke(it) } + close() + call.respondNullable(status = HttpStatusCode.OK, message = null) + } + + /** + * Closes the SSE stream associated with the given [requestId], prompting the client to reconnect. + * Useful for implementing polling behavior for long-running operations. + */ + public suspend fun closeSseStream(requestId: RequestId) { + if (enableJsonResponse) return + val streamId = requestToStreamMapping[requestId] ?: return + val sessionContext = streamsMapping[streamId] ?: return + + try { + sessionContext.session?.close() + } catch (e: Exception) { + _onError(e) + } finally { + streamsMapping.remove(streamId) + } + } + + private suspend fun replayEvents(store: EventStore, lastEventId: String, session: ServerSSESession) { + val call: ApplicationCall = session.call + + try { + var lookupSupported = true + val lookupStreamId = try { + store.getStreamIdForEventId(lastEventId) + } catch (_: NotImplementedError) { + lookupSupported = false + null + } catch (_: UnsupportedOperationException) { + lookupSupported = false + null + } + + if (lookupSupported) { + val streamId = lookupStreamId + ?: run { + call.reject( + HttpStatusCode.BadRequest, + RPCError.ErrorCode.CONNECTION_CLOSED, + "Invalid event ID format", + ) + return + } + + if (streamId in streamsMapping) { + call.reject( + HttpStatusCode.Conflict, + RPCError.ErrorCode.CONNECTION_CLOSED, + "Conflict: Stream already has an active connection", + ) + return + } + } + + call.appendSseHeaders() + flushSse(session) // flush headers immediately + + val streamId = store.replayEventsAfter(lastEventId) { eventId, message -> + try { + session.send( + event = "message", + id = eventId, + data = McpJson.encodeToString(message), + ) + } catch (e: Exception) { + _onError(IllegalStateException("Failed to replay event: ${e.message}", e)) + } + } + + streamsMapping[streamId] = SessionContext(session, call) + + session.coroutineContext.job.invokeOnCompletion { throwable -> + streamsMapping.remove(streamId) + throwable?.let { _onError(it) } + } + } catch (e: Exception) { + _onError(e) + } + } + + private suspend fun validateSession(call: ApplicationCall): Boolean { + if (sessionIdGenerator == null) return true + + if (!initialized.load()) { + call.reject( + HttpStatusCode.BadRequest, + RPCError.ErrorCode.CONNECTION_CLOSED, + "Bad Request: Server not initialized", + ) + return false + } + + val sessionHeaderValues = call.request.headers.getAll(MCP_SESSION_ID_HEADER) + + if (sessionHeaderValues.isNullOrEmpty()) { + call.reject( + HttpStatusCode.BadRequest, + RPCError.ErrorCode.CONNECTION_CLOSED, + "Bad Request: Mcp-Session-Id header is required", + ) + return false + } + + if (sessionHeaderValues.size > 1) { + call.reject( + HttpStatusCode.BadRequest, + RPCError.ErrorCode.CONNECTION_CLOSED, + "Bad Request: Mcp-Session-Id header must be a single value", + ) + return false + } + + val headerId = sessionHeaderValues.single() + + return when (headerId) { + sessionId -> true + + else -> { + call.reject( + HttpStatusCode.NotFound, + -32001, + "Session not found", + ) + false + } + } + } + + private suspend fun validateProtocolVersion(call: ApplicationCall): Boolean { + val protocolVersions = call.request.headers.getAll(MCP_PROTOCOL_VERSION_HEADER) + val version = protocolVersions?.lastOrNull() ?: DEFAULT_NEGOTIATED_PROTOCOL_VERSION + + return when (version) { + !in SUPPORTED_PROTOCOL_VERSIONS -> { + call.reject( + HttpStatusCode.BadRequest, + RPCError.ErrorCode.CONNECTION_CLOSED, + "Bad Request: Unsupported protocol version (supported versions: ${ + SUPPORTED_PROTOCOL_VERSIONS.joinToString( + ", ", + ) + })", + ) + false + } + + else -> true + } + } + + private fun validateHeaders(call: ApplicationCall): String? { + if (!enableDnsRebindingProtection) return null + + allowedHosts?.let { hosts -> + val hostHeader = call.request.headers[HttpHeaders.Host]?.lowercase() + val allowedHostsLowercase = hosts.map { it.lowercase() } + + if (hostHeader == null || hostHeader !in allowedHostsLowercase) { + return "Invalid Host header: $hostHeader" + } + } + + allowedOrigins?.let { origins -> + val originHeader = call.request.headers[HttpHeaders.Origin]?.lowercase() + val allowedOriginsLowercase = origins.map { it.lowercase() } + + if (originHeader == null || originHeader !in allowedOriginsLowercase) { + return "Invalid Origin header: $originHeader" + } + } + + return null + } + + private suspend fun flushSse(session: ServerSSESession?) { + try { + session?.send(data = "") + } catch (e: Exception) { + _onError(e) + } + } + + private suspend fun parseBody(call: ApplicationCall): List? { + val contentLength = call.request.header(HttpHeaders.ContentLength)?.toIntOrNull() ?: 0 + if (contentLength > MAXIMUM_MESSAGE_SIZE) { + call.reject( + HttpStatusCode.PayloadTooLarge, + RPCError.ErrorCode.INVALID_REQUEST, + "Invalid Request: message size exceeds maximum of ${MAXIMUM_MESSAGE_SIZE / (1024 * 1024)} MB", + ) + return null + } + + val body = call.receiveText() + if (body.length > MAXIMUM_MESSAGE_SIZE) { + call.reject( + HttpStatusCode.PayloadTooLarge, + RPCError.ErrorCode.INVALID_REQUEST, + "Invalid Request: message size exceeds maximum of ${MAXIMUM_MESSAGE_SIZE / (1024 * 1024)} MB", + ) + return null + } + + return when (val element = McpJson.parseToJsonElement(body)) { + is JsonObject -> listOf(McpJson.decodeFromJsonElement(element)) + + is JsonArray -> McpJson.decodeFromJsonElement>(element) + + else -> { + call.reject( + HttpStatusCode.BadRequest, + RPCError.ErrorCode.INVALID_REQUEST, + "Invalid Request: unable to parse JSON body", + ) + null + } + } + } + + private fun String?.accepts(mime: ContentType): Boolean = + this?.lowercase()?.contains(mime.toString().lowercase()) == true + + private suspend fun emitOnStream(streamId: String, session: ServerSSESession?, message: JSONRPCMessage) { + val eventId = eventStore?.storeEvent(streamId, message) + try { + session?.send(event = "message", id = eventId, data = McpJson.encodeToString(message)) + } catch (_: Exception) { + streamsMapping.remove(streamId) + } + } + + private suspend fun maybeSendPrimingEvent(streamId: String, session: ServerSSESession?) { + val store = eventStore ?: return + val sseSession = session ?: return + try { + val primingEventId = store.storeEvent(streamId, JSONRPCEmptyMessage) + sseSession.send(id = primingEventId, retry = retryIntervalMillis, data = "") + } catch (e: Exception) { + _onError(e) + } + } + + private fun ApplicationCall.appendSseHeaders() { + this.response.headers.append(HttpHeaders.ContentType, ContentType.Text.EventStream.toString()) + this.response.headers.append(HttpHeaders.CacheControl, "no-cache, no-transform") + this.response.headers.append(HttpHeaders.Connection, "keep-alive") + sessionId?.let { this.response.headers.append(MCP_SESSION_ID_HEADER, it) } + this.response.status(HttpStatusCode.OK) + } +} + +internal suspend fun ApplicationCall.reject(status: HttpStatusCode, code: Int, message: String) { + this.response.status(status) + this.respond(JSONRPCError(id = null, error = RPCError(code = code, message = message))) +} diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt new file mode 100644 index 0000000..968c057 --- /dev/null +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt @@ -0,0 +1,269 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.kotest.matchers.collections.shouldContain +import io.kotest.matchers.equals.shouldBeEqual +import io.kotest.matchers.shouldBe +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.plugins.logging.LogLevel +import io.ktor.client.plugins.logging.Logging +import io.ktor.client.request.HttpRequestBuilder +import io.ktor.client.request.header +import io.ktor.client.request.post +import io.ktor.client.request.setBody +import io.ktor.http.ContentType +import io.ktor.http.HttpHeaders +import io.ktor.http.HttpStatusCode +import io.ktor.http.contentType +import io.ktor.serialization.kotlinx.json.json +import io.ktor.server.application.install +import io.ktor.server.routing.post +import io.ktor.server.routing.routing +import io.ktor.server.testing.ApplicationTestBuilder +import io.ktor.server.testing.testApplication +import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.EmptyResult +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest +import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.InitializedNotification +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse +import io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION +import io.modelcontextprotocol.kotlin.sdk.types.ListResourcesResult +import io.modelcontextprotocol.kotlin.sdk.types.ListToolsResult +import io.modelcontextprotocol.kotlin.sdk.types.McpJson +import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.RequestId +import io.modelcontextprotocol.kotlin.sdk.types.Tool +import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema +import io.modelcontextprotocol.kotlin.sdk.types.toJSON +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import java.util.concurrent.atomic.AtomicBoolean +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation as ClientContentNegotiation +import io.ktor.server.plugins.contentnegotiation.ContentNegotiation as ServerContentNegotiation + +class StreamableHttpServerTransportTest { + private val path = "/transport" + + @Test + fun `POST without event-stream accept header is rejected`() = testApplication { + configTestServer() + + val client = createTestClient() + + val transport = StreamableHttpServerTransport(enableJsonResponse = true) + val onMessageCalled = AtomicBoolean(false) + transport.onMessage { + onMessageCalled.set(true) + } + + configureTransportEndpoint(transport) + + val payload = buildInitializeRequestPayload() + + val response = client.post(path) { + contentType(ContentType.Application.Json) + header(HttpHeaders.Accept, ContentType.Application.Json.toString()) + setBody(payload) + } + + assertEquals(HttpStatusCode.NotAcceptable, response.status) + assertFalse(onMessageCalled.get(), "Transport should not deliver messages when headers are invalid") + } + + @Test + fun `initialization request establishes session and returns json response`() = testApplication { + configTestServer() + + val client = createTestClient() + + val transport = StreamableHttpServerTransport(enableJsonResponse = true) + val expectedSessionId = "session-test-id" + transport.setSessionIdGenerator { expectedSessionId } + + var observedRequest: JSONRPCRequest? = null + transport.onMessage { message -> + if (message is JSONRPCRequest) { + observedRequest = message + transport.send(JSONRPCResponse(message.id, EmptyResult()), null) + } + } + + configureTransportEndpoint(transport) + + val payload = buildInitializeRequestPayload() + + val response = client.post(path) { + addStreamableHeaders() + setBody(payload) + } + + assertEquals(HttpStatusCode.OK, response.status) + assertEquals(expectedSessionId, response.headers[MCP_SESSION_ID_HEADER]) + val request = assertNotNull(observedRequest, "Initialization request should be forwarded") + + response.body() shouldBe JSONRPCResponse(request.id) + } + + @Test + fun `notifications only payload responds with 202 Accepted`() = testApplication { + configTestServer() + + val client = createTestClient() + + val transport = StreamableHttpServerTransport(enableJsonResponse = true) + val receivedMessages = mutableListOf() + transport.onMessage { message -> + if (message is JSONRPCRequest) { + transport.send(JSONRPCResponse(message.id, EmptyResult())) + } + receivedMessages.add(message) + } + + configureTransportEndpoint(transport) + + val initRequest = buildInitializeRequestPayload() + + val responseInit = client.post(path) { + addStreamableHeaders() + setBody(initRequest) + } + + val notificationPayload = encodeMessages( + listOf(InitializedNotification().toJSON()), + ) + + val response = client.post(path) { + addStreamableHeaders() + header("mcp-session-id", responseInit.headers[MCP_SESSION_ID_HEADER]) + setBody(notificationPayload) + } + + assertEquals(HttpStatusCode.Accepted, response.status) + receivedMessages shouldBeEqual listOf(initRequest, InitializedNotification().toJSON()) + } + + @Test + fun `batched requests wait for all responses before replying`() = testApplication { + configTestServer() + + val client = createTestClient(logging = true) + + val transport = StreamableHttpServerTransport(enableJsonResponse = true) + val firstRequest = JSONRPCRequest(id = RequestId("first"), method = Method.Defined.ToolsList.value) + val secondRequest = JSONRPCRequest(id = RequestId("second"), method = Method.Defined.ResourcesList.value) + + val firstResult = ListToolsResult( + tools = listOf( + Tool(name = "tool-1", inputSchema = ToolSchema()), + ), + meta = buildJsonObject { put("label", "first") }, + ) + val secondResult = ListResourcesResult( + resources = emptyList(), + meta = buildJsonObject { put("label", "second") }, + ) + + transport.onMessage { message -> + if (message is JSONRPCRequest) { + val result = when (message.id) { + firstRequest.id -> firstResult + secondRequest.id -> secondResult + else -> EmptyResult() + } + transport.send(JSONRPCResponse(message.id, result), null) + } + } + + configureTransportEndpoint(transport) + + val initRequest = buildInitializeRequestPayload() + + val responseInit = client.post(path) { + addStreamableHeaders() + setBody(initRequest) + } + + val payload = encodeMessages(listOf(firstRequest, secondRequest)) + + val response = client.post(path) { + addStreamableHeaders() + header("mcp-session-id", responseInit.headers[MCP_SESSION_ID_HEADER]) + setBody(payload) + } + + assertEquals(HttpStatusCode.OK, response.status) + + val responses = response.body>().map { it.result } + responses shouldContain (firstResult) + responses shouldContain (secondResult) + // TODO(check order) +// assertEquals(listOf(firstRequest.id, secondRequest.id), responses.map { it.id }) +// val firstMeta = (responses[0].result as EmptyResult).meta +// val secondMeta = (responses[1].result as EmptyResult).meta +// assertEquals("first", firstMeta?.get("label")?.jsonPrimitive?.content) +// assertEquals("second", secondMeta?.get("label")?.jsonPrimitive?.content) + } + + private fun ApplicationTestBuilder.configureTransportEndpoint(transport: StreamableHttpServerTransport) { + application { + routing { + post(path) { + transport.handlePostRequest(null, call) + } + } + } + } + + private fun HttpRequestBuilder.addStreamableHeaders() { + header( + HttpHeaders.Accept, + listOf(ContentType.Application.Json, ContentType.Text.EventStream).joinToString(", ") { + it.toString() + }, + ) + contentType(ContentType.Application.Json) + } + + private fun buildInitializeRequestPayload(): JSONRPCRequest { + val request = InitializeRequest( + InitializeRequestParams( + protocolVersion = LATEST_PROTOCOL_VERSION, + capabilities = ClientCapabilities(), + clientInfo = Implementation(name = "test-client", version = "1.0.0"), + ), + ).toJSON() + + return request + } + + private fun encodeMessages(messages: List): String = + McpJson.encodeToString(ListSerializer(JSONRPCMessage.serializer()), messages) + + private fun ApplicationTestBuilder.configTestServer() { + application { + install(ServerContentNegotiation) { + json(McpJson) + } + } + } + + private fun ApplicationTestBuilder.createTestClient(logging: Boolean = false): HttpClient = createClient { + install(ClientContentNegotiation) { + json(McpJson) + } + if (logging) { + install(Logging) { + level = LogLevel.ALL + } + } + } +} From 64f2d1864b1fa13b51710abb112eb46d8bc1bf88 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 12:10:26 +0200 Subject: [PATCH 096/449] Bump io.mockk:mockk from 1.14.6 to 1.14.7 (#455) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [io.mockk:mockk](https://github.com/mockk/mockk) from 1.14.6 to 1.14.7.
Release notes

Sourced from io.mockk:mockk's releases.

v1.14.7

What's Changed

New Contributors

Full Changelog: https://github.com/mockk/mockk/compare/1.14.6...1.14.7

Commits
  • 3b99349 Version bump
  • d0e14bb Merge pull request #1455 from mockk/copilot/remove-transitive-junit-dependency
  • 9372ca6 Merge pull request #1464 from mockk/copilot/fix-stackoverflow-error-mockk
  • 73736a6 Address code review feedback for parseParamTypes
  • 6866dd0 Merge pull request #1454 from nishatoma/add-strict-mocking-system-property
  • ea99f88 Merge pull request #1456 from mockk/copilot/fix-mockk-compatibility-issue
  • b7b72de Merge pull request #1457 from mockk/copilot/fix-inaccessibleobjectexception
  • 08d1d1d Address comments
  • 7681de2 Merge pull request #1465 from TWiStErRob/patch-2
  • 54e6154 Fix configuration option example for restricted classes
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=io.mockk:mockk&package-manager=gradle&previous-version=1.14.6&new-version=1.14.7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0cbc5f0..d5a19ee 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,7 +18,7 @@ kotest = "6.0.7" kotlinx-io = "0.8.2" ktor = "3.2.3" logging = "7.0.13" -mockk = "1.14.6" +mockk = "1.14.7" mokksy = "0.6.2" serialization = "1.9.0" slf4j = "2.0.17" From 1744e16d8679132cc6d33bca862ef0f2c62714a5 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Mon, 8 Dec 2025 13:57:08 +0200 Subject: [PATCH 097/449] Fix flaky StdioClientTransportErrorHandlingTest (#457) ## Changes Fix flaky StdioClientTransportErrorHandlingTest Use non-empty buffer for reliability ## How Has This Been Tested? Unit test ## Breaking Changes ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 ## Additional context --- .../StdioClientTransportErrorHandlingTest.kt | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/stdio/StdioClientTransportErrorHandlingTest.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/stdio/StdioClientTransportErrorHandlingTest.kt index 43a8b04..da18741 100644 --- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/stdio/StdioClientTransportErrorHandlingTest.kt +++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/stdio/StdioClientTransportErrorHandlingTest.kt @@ -41,8 +41,9 @@ class StdioClientTransportErrorHandlingTest { val stderrBuffer = Buffer() // Empty stderr = immediate EOF - val inputBuffer = Buffer() - inputBuffer.writeString("""data: {"jsonrpc":"2.0","method":"ping","id":1}\n\n""") + val inputBuffer = createNonEmptyBuffer { + """data: {"jsonrpc":"2.0","method":"ping","id":1}\n\n""" + } val outputBuffer = Buffer() transport = StdioClientTransport( @@ -66,8 +67,9 @@ class StdioClientTransportErrorHandlingTest { @Test fun `should call onClose exactly once on error scenarios`() = runTest { - val stderrBuffer = Buffer() - stderrBuffer.write("FATAL: critical error\n".encodeToByteArray()) + val stderrBuffer = createNonEmptyBuffer { + "FATAL: critical error\n" + } val inputBuffer = Buffer() val outputBuffer = Buffer() @@ -149,8 +151,9 @@ class StdioClientTransportErrorHandlingTest { fun `Send should handle exceptions`(throwable: Throwable, shouldWrap: Boolean, expectedCode: Int?) = runTest { val sendChannel: Channel = mockk(relaxed = true) + val stdin = createNonEmptyBuffer { "id: 1\ndata:\n" } transport = StdioClientTransport( - input = Buffer(), + input = stdin, output = Buffer(), sendChannel = sendChannel, ) @@ -173,4 +176,10 @@ class StdioClientTransportErrorHandlingTest { exception shouldBeSameInstanceAs throwable } } + + fun createNonEmptyBuffer(block: () -> String): Buffer { + val buffer = Buffer() + buffer.writeString(block()) + return buffer + } } From 274089a6da09590b00feec0494edff52333ab99d Mon Sep 17 00:00:00 2001 From: Maria Tigina <31625351+tiginamaria@users.noreply.github.com> Date: Mon, 8 Dec 2025 13:09:41 +0100 Subject: [PATCH 098/449] Fix tests for notifications (#446) ## Motivation and Context ## How Has This Been Tested? ## Breaking Changes ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update ## Checklist - [ ] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [ ] My code follows the repository's style guidelines - [ ] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed ## Additional context --- .../sdk/server/FeatureNotificationService.kt | 2 +- .../server/ServerPromptsNotificationTest.kt | 20 ++++++------- ...erverResourcesNotificationSubscribeTest.kt | 29 +++++++++---------- .../server/ServerResourcesNotificationTest.kt | 20 ++++++------- .../sdk/server/ServerToolsNotificationTest.kt | 18 ++++++------ 5 files changed, 43 insertions(+), 46 deletions(-) diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/FeatureNotificationService.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/FeatureNotificationService.kt index 397e6e1..d996b2d 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/FeatureNotificationService.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/FeatureNotificationService.kt @@ -348,7 +348,7 @@ internal class FeatureNotificationService( job.start() } - /** Returns the current timestamp in milliseconds. */ + /** Returns the current timestamp in milliseconds since Unix epoch. */ @OptIn(ExperimentalTime::class) private fun getCurrentTimestamp(): Long = clock.now().toEpochMilliseconds() diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsNotificationTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsNotificationTest.kt index e2cfe29..3674401 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsNotificationTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsNotificationTest.kt @@ -23,9 +23,9 @@ class ServerPromptsNotificationTest : AbstractServerFeaturesTest() { @Test fun `addPrompt should send notification`() = runTest { // Configure notification handler - var promptListChangedNotificationReceived = false + val notifications = mutableListOf() client.setNotificationHandler(Method.Defined.NotificationsPromptsListChanged) { - promptListChangedNotificationReceived = true + notifications.add(it) CompletableDeferred(Unit) } @@ -46,16 +46,16 @@ class ServerPromptsNotificationTest : AbstractServerFeaturesTest() { // Verify that the notification was sent await untilAsserted { - assertTrue(promptListChangedNotificationReceived, "Notification should be sent when prompt is added") + assertTrue(notifications.isNotEmpty(), "Notification should be sent when prompt is added") } } @Test fun `removePrompts should remove multiple prompts and send two notifications`() = runTest { // Configure notification handler - var promptListChangedNotificationReceivedCount = 0 + val notifications = mutableListOf() client.setNotificationHandler(Method.Defined.NotificationsPromptsListChanged) { - promptListChangedNotificationReceivedCount += 1 + notifications.add(it) CompletableDeferred(Unit) } @@ -86,7 +86,7 @@ class ServerPromptsNotificationTest : AbstractServerFeaturesTest() { await untilAsserted { assertEquals( 4, - promptListChangedNotificationReceivedCount, + notifications.size, "Two notifications should be sent when prompts are added and two when removed", ) } @@ -95,9 +95,9 @@ class ServerPromptsNotificationTest : AbstractServerFeaturesTest() { @Test fun `notification should not be send when removed prompt does not exists`() = runTest { // Track notifications - var promptListChangedNotificationReceived = false + val notifications = mutableListOf() client.setNotificationHandler(Method.Defined.NotificationsPromptsListChanged) { - promptListChangedNotificationReceived = true + notifications.add(it) CompletableDeferred(Unit) } @@ -107,8 +107,8 @@ class ServerPromptsNotificationTest : AbstractServerFeaturesTest() { // Verify the result assertFalse(result, "Removing non-existent prompt should return false") await untilAsserted { - assertFalse( - promptListChangedNotificationReceived, + assertTrue( + notifications.isEmpty(), "No notification should be sent when prompt doesn't exist", ) } diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationSubscribeTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationSubscribeTest.kt index 6284450..b596cc4 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationSubscribeTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationSubscribeTest.kt @@ -2,7 +2,6 @@ package io.modelcontextprotocol.kotlin.sdk.server import io.modelcontextprotocol.kotlin.sdk.types.Method import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult -import io.modelcontextprotocol.kotlin.sdk.types.ResourceListChangedNotification import io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotification import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.types.SubscribeRequest @@ -13,7 +12,6 @@ import kotlinx.coroutines.test.runTest import org.awaitility.kotlin.await import org.awaitility.kotlin.untilAsserted import org.junit.jupiter.api.Test -import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -52,8 +50,6 @@ class ServerResourcesNotificationSubscribeTest : AbstractServerFeaturesTest() { ) } - client.subscribeResource(SubscribeRequest(SubscribeRequestParams(uri = testResourceUri1))) - server.addResource( uri = testResourceUri2, name = "Test Resource 2", @@ -71,6 +67,8 @@ class ServerResourcesNotificationSubscribeTest : AbstractServerFeaturesTest() { ) } + client.subscribeResource(SubscribeRequest(SubscribeRequestParams(uri = testResourceUri1))) + // Remove the resource val result = server.removeResource(testResourceUri1) @@ -79,9 +77,11 @@ class ServerResourcesNotificationSubscribeTest : AbstractServerFeaturesTest() { // Verify that the notification was sent await untilAsserted { - assertEquals(1, notifications.size, "Notification should be sent when resource 1 was deleted") + assertTrue( + notifications.any { it.params.uri == testResourceUri1 }, + "Notification should be sent when resource 1 was deleted", + ) } - assertEquals(testResourceUri1, notifications[0].params.uri, "Notification should contain the resource 1 URI") } @Test @@ -144,9 +144,8 @@ class ServerResourcesNotificationSubscribeTest : AbstractServerFeaturesTest() { println(notifications.map { it.params.uri }) // Verify that the notification was sent await untilAsserted { - assertEquals( - 2, - notifications.size, + assertTrue( + notifications.any { it.params.uri == testResourceUri1 }, "Notification should be sent when resource 1 and resource 2 was deleted", ) } @@ -165,11 +164,9 @@ class ServerResourcesNotificationSubscribeTest : AbstractServerFeaturesTest() { @Test fun `notification should not be send when removed resource does not exists`() = runTest { // Track notifications - var resourceListChangedNotificationReceived = false - client.setNotificationHandler( - Method.Defined.NotificationsResourcesListChanged, - ) { - resourceListChangedNotificationReceived = true + val notifications = mutableListOf() + client.setNotificationHandler(Method.Defined.NotificationsResourcesUpdated) { + notifications.add(it) CompletableDeferred(Unit) } @@ -178,8 +175,8 @@ class ServerResourcesNotificationSubscribeTest : AbstractServerFeaturesTest() { // Verify the result assertFalse(result, "Removing non-existent resource should return false") - assertFalse( - resourceListChangedNotificationReceived, + assertTrue( + notifications.isEmpty(), "No notification should be sent when resource doesn't exist", ) } diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationTest.kt index 52a4261..6337479 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationTest.kt @@ -23,11 +23,11 @@ class ServerResourcesNotificationTest : AbstractServerFeaturesTest() { @Test fun `addResource should send notification`() = runTest { // Configure notification handler - var resourceListChangedNotificationReceived = false + val notifications = mutableListOf() client.setNotificationHandler( Method.Defined.NotificationsResourcesListChanged, ) { - resourceListChangedNotificationReceived = true + notifications.add(it) CompletableDeferred(Unit) } @@ -58,18 +58,18 @@ class ServerResourcesNotificationTest : AbstractServerFeaturesTest() { // Verify that the notification was sent await untilAsserted { - assertTrue(resourceListChangedNotificationReceived, "Notification should be sent when resource is added") + assertTrue(notifications.isNotEmpty(), "Notification should be sent when resource is added") } } @Test fun `removeResources should remove multiple resources and send two notifications`() = runTest { // Configure notification handler - var resourceListChangedNotificationReceivedCount = 0 + val notifications = mutableListOf() client.setNotificationHandler( Method.Defined.NotificationsResourcesListChanged, ) { - resourceListChangedNotificationReceivedCount += 1 + notifications.add(it) CompletableDeferred(Unit) } @@ -120,7 +120,7 @@ class ServerResourcesNotificationTest : AbstractServerFeaturesTest() { await untilAsserted { assertEquals( 4, - resourceListChangedNotificationReceivedCount, + notifications.size, "Two notifications should be sent when resources are added and two when removed", ) } @@ -129,11 +129,11 @@ class ServerResourcesNotificationTest : AbstractServerFeaturesTest() { @Test fun `notification should not be send when removed resource does not exists`() = runTest { // Track notifications - var resourceListChangedNotificationReceived = false + val notifications = mutableListOf() client.setNotificationHandler( Method.Defined.NotificationsResourcesListChanged, ) { - resourceListChangedNotificationReceived = true + notifications.add(it) CompletableDeferred(Unit) } @@ -142,8 +142,8 @@ class ServerResourcesNotificationTest : AbstractServerFeaturesTest() { // Verify the result assertFalse(result, "Removing non-existent resource should return false") - assertFalse( - resourceListChangedNotificationReceived, + assertTrue( + notifications.isEmpty(), "No notification should be sent when resource doesn't exist", ) } diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsNotificationTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsNotificationTest.kt index 5892532..4cda75f 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsNotificationTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsNotificationTest.kt @@ -24,9 +24,9 @@ class ServerToolsNotificationTest : AbstractServerFeaturesTest() { @Test fun `addTool should send notification`() = runTest { // Configure notification handler - var toolListChangedNotificationReceived = false + val notifications = mutableListOf() client.setNotificationHandler(Method.Defined.NotificationsToolsListChanged) { - toolListChangedNotificationReceived = true + notifications.add(it) CompletableDeferred(Unit) } @@ -43,16 +43,16 @@ class ServerToolsNotificationTest : AbstractServerFeaturesTest() { // Verify that the notification was sent await untilAsserted { - assertTrue(toolListChangedNotificationReceived, "Notification should be sent when tool is added") + assertTrue(notifications.isNotEmpty(), "Notification should be sent when tool is added") } } @Test fun `removeTools should remove multiple tools and send two notifications`() = runTest { // Configure notification handler - var toolListChangedNotificationReceivedCount = 0 + val notifications = mutableListOf() client.setNotificationHandler(Method.Defined.NotificationsToolsListChanged) { - toolListChangedNotificationReceivedCount += 1 + notifications.add(it) CompletableDeferred(Unit) } @@ -73,7 +73,7 @@ class ServerToolsNotificationTest : AbstractServerFeaturesTest() { await untilAsserted { assertEquals( 4, - toolListChangedNotificationReceivedCount, + notifications.size, "Two notifications should be sent when tools are added and two when removed", ) } @@ -82,9 +82,9 @@ class ServerToolsNotificationTest : AbstractServerFeaturesTest() { @Test fun `notification should not be send when removed tool does not exists`() = runTest { // Track notifications - var toolListChangedNotificationReceived = false + val notifications = mutableListOf() client.setNotificationHandler(Method.Defined.NotificationsToolsListChanged) { - toolListChangedNotificationReceived = true + notifications.add(it) CompletableDeferred(Unit) } @@ -95,6 +95,6 @@ class ServerToolsNotificationTest : AbstractServerFeaturesTest() { // Verify the result assertFalse(result, "Removing non-existent tool should return false") - assertFalse(toolListChangedNotificationReceived, "No notification should be sent when tool doesn't exist") + assertTrue(notifications.isEmpty(), "No notification should be sent when tool doesn't exist") } } From 0d3494b26a8a930fc406ebe3877d37fd24ad6aef Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Mon, 8 Dec 2025 14:20:35 +0200 Subject: [PATCH 099/449] Update GitHub Actions workflow (#452) Update GitHub Actions workflow - Rename `validate-pr` job to `build` - Update `comment` and `updateComment` parameters to be false on forks ## Motivation and Context mikepenz/action-junit-report GH Action can't create/update comments on forks ## How Has This Been Tested? CI ## Breaking Changes No ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 ## Additional context --- .github/workflows/build.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e05c06a..b2664ff 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,9 +17,9 @@ permissions: pull-requests: write # only required if `comment: true` was enabled jobs: - validate-pr: + build: runs-on: macos-latest-xlarge - name: Validate PR + name: Build timeout-minutes: 20 env: JAVA_OPTS: "-Xmx8g -Dfile.encoding=UTF-8 -Djava.awt.headless=true -Dkotlin.daemon.jvm.options=-Xmx6g" @@ -75,7 +75,7 @@ jobs: if: ${{ !cancelled() }} # always run even if the previous step fails with: annotate_only: true - comment: true + comment: ${{ github.repository == 'modelcontextprotocol/kotlin-sdk' }} detailed_summary: true flaky_summary: true group_suite: true @@ -83,7 +83,7 @@ jobs: include_time_in_summary: true report_paths: '**/test-results/**/TEST-*.xml' truncate_stack_traces: false - updateComment: true + updateComment: ${{ github.repository == 'modelcontextprotocol/kotlin-sdk' }} - name: Disable Auto-Merge on Fail if: failure() && github.event_name == 'pull_request' From 9653e306fe1ddeec5e50f46462c405cbab5b4b14 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Mon, 8 Dec 2025 17:25:28 +0200 Subject: [PATCH 100/449] Revert publishing junit-report comments (#458) # Revert publishing junit-report comments Build on Forks is broken now, because Junit-Reports plugin can't write comments. ## Motivation and Context Revert [this change](https://github.com/modelcontextprotocol/kotlin-sdk/pull/442/files#diff-5c3fa597431eda03ac3339ae6bf7f05e1a50d6fc7333679ec38e21b337cb6721) ## How Has This Been Tested? CI ## Breaking Changes No ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 --- .github/workflows/build.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b2664ff..99ac3b8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -75,7 +75,6 @@ jobs: if: ${{ !cancelled() }} # always run even if the previous step fails with: annotate_only: true - comment: ${{ github.repository == 'modelcontextprotocol/kotlin-sdk' }} detailed_summary: true flaky_summary: true group_suite: true @@ -83,7 +82,6 @@ jobs: include_time_in_summary: true report_paths: '**/test-results/**/TEST-*.xml' truncate_stack_traces: false - updateComment: ${{ github.repository == 'modelcontextprotocol/kotlin-sdk' }} - name: Disable Auto-Merge on Fail if: failure() && github.event_name == 'pull_request' From d539c9697fc4e82c91e37ac40b8170395f98943a Mon Sep 17 00:00:00 2001 From: Artem Bukhonov Date: Mon, 8 Dec 2025 17:10:39 +0100 Subject: [PATCH 101/449] Properly check content type. Should fix #450 (#451) ## Motivation and Context ## How Has This Been Tested? ## Breaking Changes ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update ## Checklist - [ ] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [ ] My code follows the repository's style guidelines - [ ] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed ## Additional context --- .../kotlin/sdk/server/SSEServerTransport.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt index c662464..1b33f39 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt @@ -80,7 +80,7 @@ public class SseServerTransport(private val endpoint: String, private val sessio val body = try { val ct = call.request.contentType() - if (ct != ContentType.Application.Json) { + if (!ct.match(ContentType.Application.Json)) { error("Unsupported content-type: $ct") } From e22bbb9649b2a1995e714f3b6843e650737bba62 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 22:21:12 +0200 Subject: [PATCH 102/449] Bump actions/setup-node from 4 to 6 (#453) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 6.
Release notes

Sourced from actions/setup-node's releases.

v6.0.0

What's Changed

Breaking Changes

Dependency Upgrades

Full Changelog: https://github.com/actions/setup-node/compare/v5...v6.0.0

v5.0.0

What's Changed

Breaking Changes

This update, introduces automatic caching when a valid packageManager field is present in your package.json. This aims to improve workflow performance and make dependency management more seamless. To disable this automatic caching, set package-manager-cache: false

steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
  with:
    package-manager-cache: false

Make sure your runner is on version v2.327.1 or later to ensure compatibility with this release. See Release Notes

Dependency Upgrades

New Contributors

Full Changelog: https://github.com/actions/setup-node/compare/v4...v5.0.0

v4.4.0

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/setup-node&package-manager=github_actions&previous-version=4&new-version=6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/conformance.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/conformance.yml b/.github/workflows/conformance.yml index c736ad3..a6a8405 100644 --- a/.github/workflows/conformance.yml +++ b/.github/workflows/conformance.yml @@ -29,7 +29,7 @@ jobs: distribution: 'temurin' - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: '24.11.1' From 363eab6327315a0ea8f9312c0ab3c1b5c8841820 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Mon, 8 Dec 2025 22:25:55 +0100 Subject: [PATCH 103/449] Fix kotlin-mcp-server sample (#459) --- .../sample/server/server.kt | 8 +++++-- .../src/test/kotlin/McpServerType.kt | 17 ++++++++++++++ .../test/kotlin/SseServerIntegrationTest.kt | 14 ++++++++++-- .../src/test/kotlin/TestEnvironment.kt | 22 +++++++------------ 4 files changed, 43 insertions(+), 18 deletions(-) create mode 100644 samples/kotlin-mcp-server/src/test/kotlin/McpServerType.kt diff --git a/samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/server.kt b/samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/server.kt index e707743..2a85c74 100644 --- a/samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/server.kt +++ b/samples/kotlin-mcp-server/src/main/kotlin/io/modelcontextprotocol/sample/server/server.kt @@ -31,6 +31,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.types.TextContent import io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents import kotlinx.coroutines.Job +import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.runBlocking import kotlinx.io.asSink import kotlinx.io.asSource @@ -102,13 +103,13 @@ fun configureServer(): Server { return server } -fun runSseMcpServerWithPlainConfiguration(port: Int, wait: Boolean = true) { +fun runSseMcpServerWithPlainConfiguration(port: Int, wait: Boolean = true): EmbeddedServer<*, *> { printBanner(port = port, path = "/sse") val serverSessions = ConcurrentMap() val server = configureServer() - embeddedServer(CIO, host = "127.0.0.1", port = port) { + val ktorServer = embeddedServer(CIO, host = "127.0.0.1", port = port) { installCors() install(SSE) routing { @@ -121,6 +122,7 @@ fun runSseMcpServerWithPlainConfiguration(port: Int, wait: Boolean = true) { println("Server session closed for: ${transport.sessionId}") serverSessions.remove(transport.sessionId) } + awaitCancellation() } post("/message") { val sessionId: String? = call.request.queryParameters["sessionId"] @@ -139,6 +141,8 @@ fun runSseMcpServerWithPlainConfiguration(port: Int, wait: Boolean = true) { } } }.start(wait = wait) + + return ktorServer } /** diff --git a/samples/kotlin-mcp-server/src/test/kotlin/McpServerType.kt b/samples/kotlin-mcp-server/src/test/kotlin/McpServerType.kt new file mode 100644 index 0000000..b39dc43 --- /dev/null +++ b/samples/kotlin-mcp-server/src/test/kotlin/McpServerType.kt @@ -0,0 +1,17 @@ +import io.ktor.server.engine.EmbeddedServer +import io.modelcontextprotocol.sample.server.runSseMcpServerUsingKtorPlugin +import io.modelcontextprotocol.sample.server.runSseMcpServerWithPlainConfiguration + +enum class McpServerType( + val sseEndpoint: String, + val serverFactory: (port: Int) -> EmbeddedServer<*, *> +) { + KTOR_PLUGIN( + sseEndpoint = "", + serverFactory = { port -> runSseMcpServerUsingKtorPlugin(port, wait = false) } + ), + PLAIN_CONFIGURATION( + sseEndpoint = "/sse", + serverFactory = { port -> runSseMcpServerWithPlainConfiguration(port, wait = false) } + ) +} diff --git a/samples/kotlin-mcp-server/src/test/kotlin/SseServerIntegrationTest.kt b/samples/kotlin-mcp-server/src/test/kotlin/SseServerIntegrationTest.kt index 9cbedda..2e091ba 100644 --- a/samples/kotlin-mcp-server/src/test/kotlin/SseServerIntegrationTest.kt +++ b/samples/kotlin-mcp-server/src/test/kotlin/SseServerIntegrationTest.kt @@ -11,9 +11,9 @@ import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.assertTrue -class SseServerIntegrationTest { +abstract class SseServerIntegrationTestBase { - private val client: Client = TestEnvironment.client + abstract val client: Client @Test fun `should list tools`(): Unit = runBlocking { @@ -88,3 +88,13 @@ class SseServerIntegrationTest { assertEquals(expected = "text", actual = "${content.type}".lowercase()) } } + +class SseServerKtorPluginIntegrationTest : SseServerIntegrationTestBase() { + private val testEnvironment = TestEnvironment(McpServerType.KTOR_PLUGIN) + override val client: Client = testEnvironment.client +} + +class SseServerPlainConfigurationIntegrationTest : SseServerIntegrationTestBase() { + private val testEnvironment = TestEnvironment(McpServerType.PLAIN_CONFIGURATION) + override val client: Client = testEnvironment.client +} diff --git a/samples/kotlin-mcp-server/src/test/kotlin/TestEnvironment.kt b/samples/kotlin-mcp-server/src/test/kotlin/TestEnvironment.kt index 6d457dd..71932c3 100644 --- a/samples/kotlin-mcp-server/src/test/kotlin/TestEnvironment.kt +++ b/samples/kotlin-mcp-server/src/test/kotlin/TestEnvironment.kt @@ -1,34 +1,34 @@ import io.ktor.client.HttpClient import io.ktor.client.engine.cio.CIO import io.ktor.client.plugins.sse.SSE -import io.modelcontextprotocol.kotlin.sdk.Implementation +import io.ktor.server.engine.EmbeddedServer import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.client.mcpSseTransport -import io.modelcontextprotocol.sample.server.runSseMcpServerUsingKtorPlugin +import io.modelcontextprotocol.kotlin.sdk.types.Implementation import kotlinx.coroutines.runBlocking import java.util.concurrent.TimeUnit -object TestEnvironment { +class TestEnvironment(private val serverConfig: McpServerType) { - val server = runSseMcpServerUsingKtorPlugin(0, wait = false) + val server: EmbeddedServer<*, *> = serverConfig.serverFactory(0) val client: Client init { client = runBlocking { val port = server.engine.resolvedConnectors().single().port - initClient(port) + initClient(port, serverConfig) } Runtime.getRuntime().addShutdownHook( Thread { - println("🏁 Shutting down server") + println("🏁 Shutting down server (${serverConfig.name})") server.stop(500, 700, TimeUnit.MILLISECONDS) println("☑️ Shutdown complete") }, ) } - private suspend fun initClient(port: Int): Client { + private suspend fun initClient(port: Int, config: McpServerType): Client { val client = Client( Implementation(name = "test-client", version = "0.1.0"), ) @@ -37,13 +37,7 @@ object TestEnvironment { install(SSE) } - // Create a transport wrapper that captures the session ID and received messages - val transport = httpClient.mcpSseTransport { - url { - this.host = "127.0.0.1" - this.port = port - } - } + val transport = httpClient.mcpSseTransport("http://127.0.0.1:$port/${config.sseEndpoint}") client.connect(transport) return client } From 0ba61060c29d2106ec2f32463eec1acf547b08c9 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Fri, 9 Jan 2026 21:00:14 +0200 Subject: [PATCH 104/449] Update README.md conformance tests badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7250499..ac2b73e 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![Maven Central](https://img.shields.io/maven-central/v/io.modelcontextprotocol/kotlin-sdk.svg?label=Maven%20Central)](https://central.sonatype.com/artifact/io.modelcontextprotocol/kotlin-sdk) [![Build](https://github.com/modelcontextprotocol/kotlin-sdk/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/modelcontextprotocol/kotlin-sdk/actions/workflows/build.yml) +[![Conformance Tests](https://github.com/modelcontextprotocol/kotlin-sdk/actions/workflows/conformance.yml/badge.svg)](https://github.com/modelcontextprotocol/kotlin-sdk/actions/workflows/conformance.yml) [![Kotlin](https://img.shields.io/badge/kotlin-2.2+-blueviolet.svg?logo=kotlin)](http://kotlinlang.org) [![Kotlin Multiplatform](https://img.shields.io/badge/Platforms-%20JVM%20%7C%20Wasm%2FJS%20%7C%20Native%20-blueviolet?logo=kotlin)](https://kotlinlang.org/docs/multiplatform.html) [![JVM](https://img.shields.io/badge/JVM-11+-red.svg?logo=jvm)](http://java.com) From 288d46cdde0ef643d99ea804b0ae20f4361603de Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Fri, 9 Jan 2026 22:48:31 +0200 Subject: [PATCH 105/449] fix: Update/Fix build workflow (#474) # Update/Fix build workflow - `macos-latest-xlarge` is no longer available. Switch to cheaper runner: `macos-latest-xlarge` -> `macos-latest` - Refactor build to run matrix job under Linux and macOS - Tune caching GH caching - Add junit and logger properties - Downgrade Junit to 5.14.2 - Extract `ProcessBuilder` creation logic into helper functions - Disable `StdioClientTransportTest` on windows temporary ## How Has This Been Tested? CI ## Breaking Changes Functionally - no. Outdated/broken TypeScript tests were disabled ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [x] CI Update ## Checklist - [ ] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [ ] My code follows the repository's style guidelines - [ ] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed ## Additional context GitHub actions workflows were blocked because the selected "macos-latest-xlarge" runner, which is not available for the free open-source plan. With "macos-latest" workflow works. --- .github/workflows/apidocs.yaml | 2 +- .github/workflows/build.yml | 162 +++++-- .github/workflows/codeql.yml | 28 +- .github/workflows/conformance.yml | 2 +- .github/workflows/gradle-publish.yml | 5 +- .github/workflows/samples.yml | 20 +- .../test/resources/simplelogger.properties | 8 + gradle/libs.versions.toml | 2 +- .../resources/junit-platform.properties | 6 + .../jvmTest/resources/simplelogger.properties | 8 + .../jvmTest/resources/simplelogger.properties | 8 + kotlin-sdk-test/build.gradle.kts | 1 + .../kotlin/sdk/shared/BaseTransportTest.kt | 2 +- .../sdk/client/StdioClientTransportTest.kt | 17 +- .../kotlin/sdk/client/processBuilders.kt | 15 + .../AbstractKotlinClientTsServerTest.kt | 2 + .../AbstractTsClientKotlinServerTest.kt | 2 + ...dSchemaAbstractKotlinClientTsServerTest.kt | 2 +- .../typescript/OldSchemaTsTestBase.kt | 455 ------------------ .../KotlinClientTsServerEdgeCasesTestSse.kt | 2 + .../sse/KotlinClientTsServerTestSse.kt | 2 + ...emaKotlinClientTsServerEdgeCasesTestSse.kt | 10 +- .../OldSchemaKotlinClientTsServerTestSse.kt | 6 +- .../sse/TsClientKotlinServerTestSse.kt | 2 + .../typescript/sse/TsEdgeCasesTestSse.kt | 2 + .../KotlinClientTsServerEdgeCasesTestStdio.kt | 2 + .../stdio/KotlinClientTsServerTestStdio.kt | 2 + ...aKotlinClientTsServerEdgeCasesTestStdio.kt | 10 +- .../OldSchemaKotlinClientTsServerTestStdio.kt | 6 +- .../stdio/TsClientKotlinServerTestStdio.kt | 2 + .../typescript/stdio/TsEdgeCasesTestStdio.kt | 2 + .../sdk/integration/utils/DisabledOnCI.kt | 6 + .../jvmTest/resources/simplelogger.properties | 8 + 33 files changed, 268 insertions(+), 541 deletions(-) create mode 100644 conformance-test/src/test/resources/simplelogger.properties create mode 100644 kotlin-sdk-core/src/jvmTest/resources/junit-platform.properties create mode 100644 kotlin-sdk-core/src/jvmTest/resources/simplelogger.properties create mode 100644 kotlin-sdk-server/src/jvmTest/resources/simplelogger.properties create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/processBuilders.kt delete mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/OldSchemaTsTestBase.kt create mode 100644 kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/utils/DisabledOnCI.kt create mode 100644 kotlin-sdk-test/src/jvmTest/resources/simplelogger.properties diff --git a/.github/workflows/apidocs.yaml b/.github/workflows/apidocs.yaml index a22acb2..603ab3a 100644 --- a/.github/workflows/apidocs.yaml +++ b/.github/workflows/apidocs.yaml @@ -1,4 +1,4 @@ -name: Build and Publish API Docs to GitHub Pages +name: Publish API Docs on: release: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 99ac3b8..fa07e46 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,69 +10,88 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} # Cancel only when the run is NOT on `main` branch - cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} permissions: checks: write pull-requests: write # only required if `comment: true` was enabled +env: + JAVA_VERSION: 21 + JAVA_DISTRIBUTION: temurin + JAVA_OPTS: "-Dfile.encoding=UTF-8 -Djava.awt.headless=true" + GRADLE_OPTS: "-Dorg.gradle.daemon=false -Dkotlin.compiler.execution.strategy=in-process" + jobs: build: - runs-on: macos-latest-xlarge - name: Build - timeout-minutes: 20 - env: - JAVA_OPTS: "-Xmx8g -Dfile.encoding=UTF-8 -Djava.awt.headless=true -Dkotlin.daemon.jvm.options=-Xmx6g" + runs-on: ${{ matrix.os }} + name: Build (${{ matrix.job-name }}) + timeout-minutes: 30 + permissions: + contents: read + + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + job-name: "Linux Tests" + gradle-tasks: "build ktlintCheck koverLog koverHtmlReport publishToMavenLocal -Pversion=1-SNAPSHOT" + max-workers: 3 + + - os: windows-latest + job-name: "Windows Tests" + gradle-tasks: "jvmTest" + max-workers: 3 + + - os: macos-latest + job-name: "iOS Simulator tests" + gradle-tasks: "linkIosSimulatorArm64 iosSimulatorArm64Test" + max-workers: 2 + steps: - uses: actions/checkout@v6 - - name: Set up JDK 21 + - name: Set up JDK ${{ env.JAVA_VERSION }} uses: actions/setup-java@v5 with: - java-version: '21' - distribution: 'temurin' + java-version: ${{ env.JAVA_VERSION }} + distribution: ${{ env.JAVA_DISTRIBUTION }} + + - name: Set up Node.js + if: matrix.os == 'ubuntu-latest' + uses: actions/setup-node@v6 + with: + node-version: 'lts/*' - name: Setup Gradle uses: gradle/actions/setup-gradle@v5 with: + validate-wrappers: true add-job-summary: 'always' - cache-read-only: true + cache-read-only: ${{ github.ref != 'refs/heads/main' }} + cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} + gradle-home-cache-includes: | + caches + notifications + sdks + .konan/** - - name: Build with Gradle + - name: ${{ matrix.job-name }} run: |- - ./gradlew --no-daemon \ - --rerun-tasks \ - clean \ - ktlintCheck \ - build \ - -x :conformance-test:test \ - koverLog koverHtmlReport \ - publishToMavenLocal \ - -Pversion=0.0.1-SNAPSHOT - - - name: Build Kotlin-MCP-Client Sample - working-directory: ./samples/kotlin-mcp-client - run: ./gradlew --no-daemon clean build -Pmcp.kotlin.overrideVersion=0.0.1-SNAPSHOT - - - name: Build Kotlin-MCP-Server Sample - working-directory: ./samples/kotlin-mcp-server - run: ./gradlew --no-daemon clean build -Pmcp.kotlin.overrideVersion=0.0.1-SNAPSHOT - - - name: Build Weather-Stdio-Server Sample - working-directory: ./samples/weather-stdio-server - run: ./gradlew --no-daemon clean build -Pmcp.kotlin.overrideVersion=0.0.1-SNAPSHOT + ./gradlew ${{ matrix.gradle-tasks }} -x :conformance-test:test --max-workers ${{ matrix.max-workers }} --continue --configure-on-demand --parallel - name: Upload Reports - if: ${{ !cancelled() }} - uses: actions/upload-artifact@v5 + if: always() + uses: actions/upload-artifact@v6 with: - name: reports + name: reports-build-${{ matrix.os }} path: | **/build/reports/ - name: Publish Test Report uses: mikepenz/action-junit-report@v6 - if: ${{ !cancelled() }} # always run even if the previous step fails + if: always() with: annotate_only: true detailed_summary: true @@ -83,9 +102,68 @@ jobs: report_paths: '**/test-results/**/TEST-*.xml' truncate_stack_traces: false - - name: Disable Auto-Merge on Fail - if: failure() && github.event_name == 'pull_request' - run: gh pr merge --disable-auto "$PR_URL" - env: - PR_URL: ${{github.event.pull_request.html_url}} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Archive Maven Local Repository + if: ${{ success() && matrix.os == 'ubuntu-latest' }} + run: | + cd ~/.m2/repository && zip -r $GITHUB_WORKSPACE/local-maven-repository.zip . + + - name: Upload Maven Local Repository + if: ${{ success() && matrix.os == 'ubuntu-latest' }} + uses: actions/upload-artifact@v4 + with: + name: local-maven-repository + path: local-maven-repository.zip + retention-days: 1 + + samples: + needs: [ build ] + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + sample: + - kotlin-mcp-client + - kotlin-mcp-server + - weather-stdio-server + + name: "Build Sample: ${{ matrix.sample }}" + timeout-minutes: 10 + env: + JAVA_OPTS: "-Xmx8g -Dfile.encoding=UTF-8 -Djava.awt.headless=true -Dkotlin.daemon.jvm.options=-Xmx6g" + steps: + - uses: actions/checkout@v6 + + - name: Set up JDK ${{ env.JAVA_VERSION }} + uses: actions/setup-java@v5 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: ${{ env.JAVA_DISTRIBUTION }} + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + with: + add-job-summary: 'always' + cache-read-only: true + + - name: Download Maven Local Repository + uses: actions/download-artifact@v7 + with: + name: local-maven-repository + path: . + + - name: Extract Maven Local Repository + run: | + mkdir -p ~/.m2/repository + unzip -o local-maven-repository.zip -d ~/.m2/repository + + - name: "Build Sample: ${{ matrix.sample }}" + working-directory: ./samples/${{ matrix.sample }} + run: ./gradlew build -Pmcp.kotlin.overrideVersion=1-SNAPSHOT + + - name: Upload Reports + if: ${{ !cancelled() }} + uses: actions/upload-artifact@v6 + with: + name: reports-samples-snapshot-${{ matrix.sample }} + path: | + **/build/reports/ diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 1a7789d..0daaf5f 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -8,10 +8,19 @@ on: schedule: - cron: '0 4 * * 0' +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +env: + JAVA_VERSION: 21 + JAVA_DISTRIBUTION: temurin + jobs: analyze: name: Analyze (${{ matrix.language }}) runs-on: ubuntu-latest + timeout-minutes: 30 permissions: # required for all workflows security-events: write @@ -24,6 +33,7 @@ jobs: contents: read strategy: + fail-fast: false matrix: language: [ java-kotlin ] @@ -33,10 +43,11 @@ jobs: with: fetch-depth: 0 - - uses: actions/setup-java@v5 + - name: Set up JDK ${{ env.JAVA_VERSION }} + uses: actions/setup-java@v5 with: - distribution: temurin - java-version: '21' + distribution: ${{ env.JAVA_DISTRIBUTION }} + java-version: ${{ env.JAVA_VERSION }} # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL @@ -45,16 +56,15 @@ jobs: languages: ${{ matrix.language }} build-mode: manual - - uses: actions/cache@v4 + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + validate-wrappers: true + cache-read-only: true - name: Build Kotlin sources run: | - ./gradlew \ + ./gradlew \ :kotlin-sdk-core:compileKotlinJvm \ :kotlin-sdk-client:compileKotlinJvm \ :kotlin-sdk-server:compileKotlinJvm \ diff --git a/.github/workflows/conformance.yml b/.github/workflows/conformance.yml index a6a8405..5aee68b 100644 --- a/.github/workflows/conformance.yml +++ b/.github/workflows/conformance.yml @@ -14,7 +14,7 @@ concurrency: jobs: run-conformance: - runs-on: macos-latest-xlarge + runs-on: macos-latest name: Run Conformance Tests timeout-minutes: 20 env: diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml index bb81dbe..43f4766 100644 --- a/.github/workflows/gradle-publish.yml +++ b/.github/workflows/gradle-publish.yml @@ -13,10 +13,11 @@ on: jobs: build: - runs-on: macos-latest-xlarge + runs-on: macos-latest environment: release env: - JAVA_OPTS: "-Xmx8g -Dfile.encoding=UTF-8 -Djava.awt.headless=true -Dkotlin.daemon.jvm.options=-Xmx6g" + JAVA_OPTS: "-Xmx4g -Dfile.encoding=UTF-8 -XX:MaxMetaspaceSize=768m -Djava.awt.headless=true" + GRADLE_OPTS: "-Dorg.gradle.daemon=false -Dkotlin.compiler.execution.strategy=in-process" permissions: contents: write diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index 472110d..199a0a0 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -16,43 +16,49 @@ concurrency: # Cancel only when the run is NOT on `main` branch cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} +env: + JAVA_VERSION: 21 + JAVA_DISTRIBUTION: temurin + jobs: build: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: sample: - kotlin-mcp-client - kotlin-mcp-server - weather-stdio-server - name: Build Sample + name: Build Sample (${{ matrix.sample }}) timeout-minutes: 10 env: JAVA_OPTS: "-Xmx8g -Dfile.encoding=UTF-8 -Djava.awt.headless=true -Dkotlin.daemon.jvm.options=-Xmx6g" steps: - uses: actions/checkout@v6 - - name: Set up JDK 21 + - name: Set up JDK ${{ env.JAVA_VERSION }} uses: actions/setup-java@v5 with: - java-version: 21 - distribution: 'temurin' + java-version: ${{ env.JAVA_VERSION }} + distribution: ${{ env.JAVA_DISTRIBUTION }} - name: Setup Gradle uses: gradle/actions/setup-gradle@v5 with: + validate-wrappers: true add-job-summary: 'always' - cache-read-only: true + cache-read-only: ${{ github.ref != 'refs/heads/main' }} - name: "Build Sample: ${{ matrix.sample }}" working-directory: ./samples/${{ matrix.sample }} - run: ./gradlew --no-daemon clean build + run: ./gradlew --no-daemon build - name: Upload Reports if: ${{ !cancelled() }} uses: actions/upload-artifact@v5 with: - name: reports-${{ matrix.sample }} + name: reports-samples-${{ matrix.sample }} path: | **/build/reports/ diff --git a/conformance-test/src/test/resources/simplelogger.properties b/conformance-test/src/test/resources/simplelogger.properties new file mode 100644 index 0000000..95fb646 --- /dev/null +++ b/conformance-test/src/test/resources/simplelogger.properties @@ -0,0 +1,8 @@ +# Level of logging for the ROOT logger: ERROR, WARN, INFO, DEBUG, TRACE (default is INFO) +org.slf4j.simpleLogger.defaultLogLevel=INFO +org.slf4j.simpleLogger.showThreadName=true +org.slf4j.simpleLogger.showDateTime=false + +# Log level for specific packages or classes +org.slf4j.simpleLogger.log.io.ktor.server=DEBUG +org.slf4j.simpleLogger.log.io.modelcontextprotocol=DEBUG diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d5a19ee..30f3356 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,7 +22,7 @@ mockk = "1.14.7" mokksy = "0.6.2" serialization = "1.9.0" slf4j = "2.0.17" -junit="6.0.1" +junit="5.14.2" [libraries] # Plugins diff --git a/kotlin-sdk-core/src/jvmTest/resources/junit-platform.properties b/kotlin-sdk-core/src/jvmTest/resources/junit-platform.properties new file mode 100644 index 0000000..9af7987 --- /dev/null +++ b/kotlin-sdk-core/src/jvmTest/resources/junit-platform.properties @@ -0,0 +1,6 @@ +## https://docs.junit.org/5.3.0-M1/user-guide/index.html#writing-tests-parallel-execution +junit.jupiter.execution.parallel.enabled=true +junit.jupiter.execution.parallel.config.strategy=dynamic +junit.jupiter.execution.parallel.mode.default=concurrent +junit.jupiter.execution.parallel.mode.classes.default=concurrent +junit.jupiter.execution.timeout.default=2m diff --git a/kotlin-sdk-core/src/jvmTest/resources/simplelogger.properties b/kotlin-sdk-core/src/jvmTest/resources/simplelogger.properties new file mode 100644 index 0000000..95fb646 --- /dev/null +++ b/kotlin-sdk-core/src/jvmTest/resources/simplelogger.properties @@ -0,0 +1,8 @@ +# Level of logging for the ROOT logger: ERROR, WARN, INFO, DEBUG, TRACE (default is INFO) +org.slf4j.simpleLogger.defaultLogLevel=INFO +org.slf4j.simpleLogger.showThreadName=true +org.slf4j.simpleLogger.showDateTime=false + +# Log level for specific packages or classes +org.slf4j.simpleLogger.log.io.ktor.server=DEBUG +org.slf4j.simpleLogger.log.io.modelcontextprotocol=DEBUG diff --git a/kotlin-sdk-server/src/jvmTest/resources/simplelogger.properties b/kotlin-sdk-server/src/jvmTest/resources/simplelogger.properties new file mode 100644 index 0000000..95fb646 --- /dev/null +++ b/kotlin-sdk-server/src/jvmTest/resources/simplelogger.properties @@ -0,0 +1,8 @@ +# Level of logging for the ROOT logger: ERROR, WARN, INFO, DEBUG, TRACE (default is INFO) +org.slf4j.simpleLogger.defaultLogLevel=INFO +org.slf4j.simpleLogger.showThreadName=true +org.slf4j.simpleLogger.showDateTime=false + +# Log level for specific packages or classes +org.slf4j.simpleLogger.log.io.ktor.server=DEBUG +org.slf4j.simpleLogger.log.io.modelcontextprotocol=DEBUG diff --git a/kotlin-sdk-test/build.gradle.kts b/kotlin-sdk-test/build.gradle.kts index 252d0e2..fce90be 100644 --- a/kotlin-sdk-test/build.gradle.kts +++ b/kotlin-sdk-test/build.gradle.kts @@ -26,6 +26,7 @@ kotlin { jvmTest { dependencies { implementation(libs.awaitility) + implementation(libs.junit.jupiter.params) runtimeOnly(libs.slf4j.simple) } } diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/BaseTransportTest.kt b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/BaseTransportTest.kt index 5de5fd2..d5849eb 100644 --- a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/BaseTransportTest.kt +++ b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/BaseTransportTest.kt @@ -37,7 +37,7 @@ abstract class BaseTransportTest { fail("Unexpected error: $error") } - val messages = listOf( + val messages = listOf( PingRequest().toJSON(), InitializedNotification().toJSON(), ) diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt index fa2f032..ecd21f3 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt @@ -12,6 +12,8 @@ import kotlinx.io.buffered import org.junit.jupiter.api.Test import org.junit.jupiter.api.Timeout import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.condition.DisabledOnOs +import org.junit.jupiter.api.condition.OS import java.util.concurrent.TimeUnit import kotlin.concurrent.atomics.AtomicBoolean import kotlin.concurrent.atomics.ExperimentalAtomicApi @@ -22,15 +24,12 @@ import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds @Timeout(30, unit = TimeUnit.SECONDS) +@DisabledOnOs(OS.WINDOWS) // TODO: fix running on windows class StdioClientTransportTest : BaseTransportTest() { @Test fun `handle stdio error`(): Unit = runBlocking { - val processBuilder = if (System.getProperty("os.name").lowercase().contains("win")) { - ProcessBuilder("cmd", "/c", "pause 1 && echo simulated error 1>&2 && exit 1") - } else { - ProcessBuilder("sh", "-c", "sleep 1 && echo 'simulated error' >&2 && exit 1") - } + val processBuilder = createSleepyProcessBuilder() val process = processBuilder.start() @@ -65,8 +64,8 @@ class StdioClientTransportTest : BaseTransportTest() { @OptIn(ExperimentalAtomicApi::class) @Test fun `should start then close cleanly`() = runTest { - // Run process "/usr/bin/tee" - val processBuilder = ProcessBuilder("/usr/bin/tee") + // Run loopback process + val processBuilder = createTeeProcessBuilder() val process = processBuilder.start() val input = process.inputStream.asSource().buffered() @@ -102,7 +101,7 @@ class StdioClientTransportTest : BaseTransportTest() { @Test fun `should read messages`() = runTest { - val processBuilder = ProcessBuilder("/usr/bin/tee") + val processBuilder = createTeeProcessBuilder() val process = processBuilder.start() val input = process.inputStream.asSource().buffered() @@ -121,7 +120,7 @@ class StdioClientTransportTest : BaseTransportTest() { @Test fun `should ignore first output messages`() = runTest { - val processBuilder = ProcessBuilder("/usr/bin/tee") + val processBuilder = createTeeProcessBuilder() val process = processBuilder.start() process.outputStream.write("Stdio server started".toByteArray()) diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/processBuilders.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/processBuilders.kt new file mode 100644 index 0000000..405f2b3 --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/processBuilders.kt @@ -0,0 +1,15 @@ +package io.modelcontextprotocol.kotlin.sdk.client + +private val isWindows = System.getProperty("os.name").lowercase().contains("win") + +fun createTeeProcessBuilder(): ProcessBuilder = if (isWindows) { + ProcessBuilder("cmd", "/c", "findstr", "^") +} else { + ProcessBuilder("tee") +} + +fun createSleepyProcessBuilder(): ProcessBuilder = if (isWindows) { + ProcessBuilder("cmd", "/c", "ping -n 2 127.0.0.1 > nul && echo simulated error 1>&2 && exit 1") +} else { + ProcessBuilder("sh", "-c", "sleep 1 && echo 'simulated error' >&2 && exit 1") +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractKotlinClientTsServerTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractKotlinClientTsServerTest.kt index 5d04512..02a4cda 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractKotlinClientTsServerTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractKotlinClientTsServerTest.kt @@ -1,6 +1,7 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.integration.utils.DisabledOnCI import io.modelcontextprotocol.kotlin.sdk.types.TextContent import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking @@ -11,6 +12,7 @@ import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue +@DisabledOnCI abstract class AbstractKotlinClientTsServerTest : TsTestBase() { protected abstract suspend fun useClient(block: suspend (Client) -> T): T diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractTsClientKotlinServerTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractTsClientKotlinServerTest.kt index e7950e7..2b77ed5 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractTsClientKotlinServerTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractTsClientKotlinServerTest.kt @@ -1,11 +1,13 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript +import io.modelcontextprotocol.kotlin.sdk.integration.utils.DisabledOnCI import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Test import org.junit.jupiter.api.Timeout import java.util.concurrent.TimeUnit import kotlin.test.assertTrue +@DisabledOnCI abstract class AbstractTsClientKotlinServerTest : TsTestBase() { protected open fun beforeServer() {} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/OldSchemaAbstractKotlinClientTsServerTest.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/OldSchemaAbstractKotlinClientTsServerTest.kt index 64e114f..5d0406c 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/OldSchemaAbstractKotlinClientTsServerTest.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/OldSchemaAbstractKotlinClientTsServerTest.kt @@ -11,7 +11,7 @@ import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue -abstract class OldSchemaAbstractKotlinClientTsServerTest : OldSchemaTsTestBase() { +abstract class OldSchemaAbstractKotlinClientTsServerTest : TsTestBase() { protected abstract suspend fun useClient(block: suspend (Client) -> T): T @Test diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/OldSchemaTsTestBase.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/OldSchemaTsTestBase.kt deleted file mode 100644 index 98d83d3..0000000 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/OldSchemaTsTestBase.kt +++ /dev/null @@ -1,455 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.typescript - -import io.ktor.client.HttpClient -import io.ktor.client.engine.cio.CIO -import io.ktor.client.plugins.sse.SSE -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport -import io.modelcontextprotocol.kotlin.sdk.client.mcpStreamableHttp -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse.KotlinServerForTsClient -import io.modelcontextprotocol.kotlin.sdk.integration.utils.Retry -import io.modelcontextprotocol.kotlin.sdk.server.Server -import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withTimeout -import kotlinx.io.Sink -import kotlinx.io.Source -import kotlinx.io.asSink -import kotlinx.io.asSource -import kotlinx.io.buffered -import org.awaitility.kotlin.await -import org.junit.jupiter.api.BeforeAll -import java.io.BufferedReader -import java.io.File -import java.io.InputStreamReader -import java.net.ServerSocket -import java.net.Socket -import java.util.concurrent.TimeUnit -import kotlin.io.path.createTempDirectory -import kotlin.time.Duration.Companion.seconds - -enum class OldSchemaTransportKind { SSE, STDIO, DEFAULT } - -@Retry(times = 3) -abstract class OldSchemaTsTestBase { - - protected open val transportKind: OldSchemaTransportKind = OldSchemaTransportKind.DEFAULT - - protected val projectRoot: File get() = File(System.getProperty("user.dir")) - protected val tsClientDir: File - get() { - val base = File( - projectRoot, - "src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript", - ) - - // Allow override via system property for CI: -Dts.transport=stdio|sse - val fromProp = System.getProperty("ts.transport")?.lowercase() - val overrideSubDir = when (fromProp) { - "stdio" -> "stdio" - "sse" -> "sse" - else -> null - } - - val subDirName = overrideSubDir ?: when (transportKind) { - OldSchemaTransportKind.STDIO -> "stdio" - OldSchemaTransportKind.SSE -> "sse" - OldSchemaTransportKind.DEFAULT -> null - } - if (subDirName != null) { - val sub = File(base, subDirName) - if (sub.exists()) return sub - } - return base - } - - companion object { - @JvmStatic - private val tempRootDir: File = createTempDirectory("typescript-sdk-").toFile().apply { deleteOnExit() } - - @JvmStatic - protected val sdkDir: File = File(tempRootDir, "typescript-sdk") - - @JvmStatic - @BeforeAll - fun setupTypeScriptSdk() { - println("Cloning TypeScript SDK repository") - - if (!sdkDir.exists()) { - val process = ProcessBuilder( - "git", - "clone", - "--depth", - "1", - "https://github.com/modelcontextprotocol/typescript-sdk.git", - sdkDir.absolutePath, - ) - .redirectErrorStream(true) - .start() - val exitCode = process.waitFor() - if (exitCode != 0) { - throw RuntimeException("Failed to clone TypeScript SDK repository: exit code $exitCode") - } - } - - println("Installing TypeScript SDK dependencies") - executeCommand("npm install", sdkDir, allowFailure = false, timeoutSeconds = null) - } - - @JvmStatic - protected fun killProcessOnPort(port: Int) { - val isWindows = System.getProperty("os.name").lowercase().contains("windows") - val killCommand = if (isWindows) { - "netstat -ano | findstr :$port | for /f \"tokens=5\" %a in ('more')" + - " do taskkill /F /PID %a 2>nul || echo No process found" - } else { - "lsof -ti:$port | xargs kill -9 2>/dev/null || true" - } - executeCommand(killCommand, File("."), allowFailure = true, timeoutSeconds = null) - } - - @JvmStatic - protected fun findFreePort(): Int { - ServerSocket(0).use { socket -> - return socket.localPort - } - } - - @JvmStatic - protected fun executeCommand( - command: String, - workingDir: File, - allowFailure: Boolean = false, - timeoutSeconds: Long? = null, - ): String { - if (!workingDir.exists()) { - if (!workingDir.mkdirs()) { - throw RuntimeException("Failed to create working directory: ${workingDir.absolutePath}") - } - } - - if (!workingDir.isDirectory || !workingDir.canRead()) { - throw RuntimeException("Working directory is not accessible: ${workingDir.absolutePath}") - } - - val isWindows = System.getProperty("os.name").lowercase().contains("windows") - val processBuilder = if (isWindows) { - ProcessBuilder() - .command("cmd.exe", "/c", "set TYPESCRIPT_SDK_DIR=${sdkDir.absolutePath} && $command") - } else { - ProcessBuilder() - .command("bash", "-c", "TYPESCRIPT_SDK_DIR='${sdkDir.absolutePath}' $command") - } - - val process = processBuilder - .directory(workingDir) - .redirectErrorStream(true) - .start() - - val output = StringBuilder() - BufferedReader(InputStreamReader(process.inputStream)).use { reader -> - var line: String? - while (reader.readLine().also { line = it } != null) { - println(line) - output.append(line).append("\n") - } - } - - if (timeoutSeconds == null) { - val exitCode = process.waitFor() - if (!allowFailure && exitCode != 0) { - throw RuntimeException( - "Command execution failed with exit code $exitCode: $command\n" + - "Working dir: ${workingDir.absolutePath}\nOutput:\n$output", - ) - } - } else { - process.waitFor(timeoutSeconds, TimeUnit.SECONDS) - } - - return output.toString() - } - } - - private fun waitForProcessTermination(process: Process, timeoutSeconds: Long): Boolean { - if (process.isAlive && !process.waitFor(timeoutSeconds, TimeUnit.SECONDS)) { - process.destroyForcibly() - process.waitFor(2, TimeUnit.SECONDS) - return false - } - return true - } - - private fun createProcessOutputReader(process: Process, prefix: String = "TS-SERVER"): Thread { - val outputReader = Thread { - try { - process.inputStream.bufferedReader().useLines { lines -> - for (line in lines) { - println("[$prefix] $line") - } - } - } catch (e: Exception) { - println("Warning: Error reading process output: ${e.message}") - } - } - outputReader.isDaemon = true - return outputReader - } - - private fun createProcessErrorReader(process: Process, prefix: String = "TS-SERVER"): Thread { - val errorReader = Thread { - try { - process.errorStream.bufferedReader().useLines { lines -> - for (line in lines) { - println("[$prefix][err] $line") - } - } - } catch (e: Exception) { - println("Warning: Error reading process error stream: ${e.message}") - } - } - errorReader.isDaemon = true - return errorReader - } - - protected fun waitForPort(host: String = "localhost", port: Int, timeoutSeconds: Long = 10): Boolean = try { - await.atMost(timeoutSeconds, TimeUnit.SECONDS) - .pollDelay(200, TimeUnit.MILLISECONDS) - .pollInterval(100, TimeUnit.MILLISECONDS) - .until { - try { - Socket(host, port).use { true } - } catch (_: Exception) { - false - } - } - true - } catch (_: Exception) { - false - } - - protected fun executeCommandAllowingFailure(command: String, workingDir: File, timeoutSeconds: Long = 20): String = - executeCommand(command, workingDir, allowFailure = true, timeoutSeconds = timeoutSeconds) - - protected fun startTypeScriptServer(port: Int): Process { - killProcessOnPort(port) - - if (!sdkDir.exists() || !sdkDir.isDirectory) { - throw IllegalStateException( - "TypeScript SDK directory does not exist or is not accessible: ${sdkDir.absolutePath}", - ) - } - - val isWindows = System.getProperty("os.name").lowercase().contains("windows") - val localServerPath = File(tsClientDir, "simpleStreamableHttp.ts").absolutePath - val processBuilder = if (isWindows) { - ProcessBuilder() - .command( - "cmd.exe", - "/c", - "set MCP_PORT=$port && set NODE_PATH=${sdkDir.absolutePath}\\node_modules && npx --prefix \"${sdkDir.absolutePath}\" tsx \"$localServerPath\"", - ) - } else { - ProcessBuilder() - .command( - "bash", - "-c", - "MCP_PORT=$port NODE_PATH='${sdkDir.absolutePath}/node_modules' npx --prefix '${sdkDir.absolutePath}' tsx \"$localServerPath\"", - ) - } - - processBuilder.environment()["TYPESCRIPT_SDK_DIR"] = sdkDir.absolutePath - - val process = processBuilder - .directory(tsClientDir) - .redirectErrorStream(true) - .start() - - createProcessOutputReader(process).start() - - if (!waitForPort(port = port, timeoutSeconds = 20)) { - throw IllegalStateException("TypeScript server did not become ready on localhost:$port within timeout") - } - return process - } - - protected fun stopProcess(process: Process, waitSeconds: Long = 3, name: String = "TypeScript server") { - process.destroy() - if (waitForProcessTermination(process, waitSeconds)) { - println("$name stopped gracefully") - } else { - println("$name did not stop gracefully, forced termination") - } - } - - // ===== SSE client helpers ===== - protected suspend fun newClient(serverUrl: String): Client = - HttpClient(CIO) { install(SSE) }.mcpStreamableHttp(serverUrl) - - protected suspend fun withClient(serverUrl: String, block: suspend (Client) -> T): T { - val client = newClient(serverUrl) - return try { - withTimeout(20.seconds) { block(client) } - } finally { - try { - withTimeout(3.seconds) { client.close() } - } catch (_: Exception) { - // ignore errors - } - } - } - - // ===== STDIO client + server helpers ===== - protected fun startTypeScriptServerStdio(): Process { - if (!sdkDir.exists() || !sdkDir.isDirectory) { - throw IllegalStateException( - "TypeScript SDK directory does not exist or is not accessible: ${sdkDir.absolutePath}", - ) - } - val isWindows = System.getProperty("os.name").lowercase().contains("windows") - val localServerPath = File(tsClientDir, "simpleStdio.ts").absolutePath - val processBuilder = if (isWindows) { - ProcessBuilder() - .command( - "cmd.exe", - "/c", - "set NODE_PATH=${sdkDir.absolutePath}\\node_modules && npx --prefix \"${sdkDir.absolutePath}\" tsx \"$localServerPath\"", - ) - } else { - ProcessBuilder() - .command( - "bash", - "-c", - "NODE_PATH='${sdkDir.absolutePath}/node_modules' npx --prefix '${sdkDir.absolutePath}' tsx \"$localServerPath\"", - ) - } - processBuilder.environment()["TYPESCRIPT_SDK_DIR"] = sdkDir.absolutePath - val process = processBuilder - .directory(tsClientDir) - .redirectErrorStream(false) - .start() - // For stdio transports, do NOT read from stdout (it's used for protocol). Read stderr for logs only. - createProcessErrorReader(process, prefix = "TS-SERVER-STDIO").start() - // Give the process a moment to start - await.atMost(2, TimeUnit.SECONDS) - .pollDelay(200, TimeUnit.MILLISECONDS) - .pollInterval(100, TimeUnit.MILLISECONDS) - .until { process.isAlive } - return process - } - - protected suspend fun newClientStdio(process: Process): Client { - val input: Source = process.inputStream.asSource().buffered() - val output: Sink = process.outputStream.asSink().buffered() - val transport = StdioClientTransport(input = input, output = output) - val client = Client(Implementation("test", "1.0")) - client.connect(transport) - return client - } - - protected suspend fun withClientStdio(block: suspend (Client, Process) -> T): T { - val proc = startTypeScriptServerStdio() - val client = newClientStdio(proc) - return try { - withTimeout(20.seconds) { block(client, proc) } - } finally { - try { - withTimeout(3.seconds) { client.close() } - } catch (_: Exception) { - } - try { - stopProcess(proc, name = "TypeScript stdio server") - } catch (_: Exception) { - } - } - } - - // ===== Helpers to run TypeScript client over STDIO against Kotlin server over STDIO ===== - protected fun runStdioClient(vararg args: String): String { - // Start Node stdio client (it will speak MCP over its stdout/stdin) - val isWindows = System.getProperty("os.name").lowercase().contains("windows") - val clientPath = File(tsClientDir, "myClient.ts").absolutePath - - val process = if (isWindows) { - ProcessBuilder() - .command( - "cmd.exe", - "/c", - ( - "set TYPESCRIPT_SDK_DIR=${sdkDir.absolutePath} && " + - "set NODE_PATH=${sdkDir.absolutePath}\\node_modules && " + - "npx --prefix \"${sdkDir.absolutePath}\" tsx \"$clientPath\" " + - args.joinToString(" ") - ), - ) - .directory(tsClientDir) - .redirectErrorStream(false) - .start() - } else { - ProcessBuilder() - .command( - "bash", - "-c", - ( - "TYPESCRIPT_SDK_DIR='${sdkDir.absolutePath}' " + - "NODE_PATH='${sdkDir.absolutePath}/node_modules' " + - "npx --prefix '${sdkDir.absolutePath}' tsx \"$clientPath\" " + - args.joinToString(" ") - ), - ) - .directory(tsClientDir) - .redirectErrorStream(false) - .start() - } - - // Create Kotlin server and attach stdio transport to the process streams - val server: Server = KotlinServerForTsClient().createMcpServer() - val transport = StdioServerTransport( - inputStream = process.inputStream.asSource().buffered(), - outputStream = process.outputStream.asSink().buffered(), - ) - - // Connect server in a background thread to avoid blocking - val serverThread = Thread { - try { - runBlocking { server.createSession(transport) } - } catch (e: Exception) { - println("[STDIO-SERVER] Error connecting: ${e.message}") - } - } - serverThread.isDaemon = true - serverThread.start() - - // Read ONLY stderr from client for human-readable output - val output = StringBuilder() - val errReader = Thread { - try { - process.errorStream.bufferedReader().useLines { lines -> - lines.forEach { line -> - println("[TS-CLIENT-STDIO][err] $line") - output.append(line).append('\n') - } - } - } catch (e: Exception) { - println("Warning: Error reading stdio client stderr: ${e.message}") - } - } - errReader.isDaemon = true - errReader.start() - - // Wait up to 25s for client to exit - val finished = process.waitFor(25, TimeUnit.SECONDS) - if (!finished) { - println("Stdio client did not finish in time; destroying") - process.destroyForcibly() - } - - try { - runBlocking { transport.close() } - } catch (_: Exception) { - } - - return output.toString() - } -} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerEdgeCasesTestSse.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerEdgeCasesTestSse.kt index 54c6e0f..ff00bf9 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerEdgeCasesTestSse.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerEdgeCasesTestSse.kt @@ -3,6 +3,7 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TsTestBase +import io.modelcontextprotocol.kotlin.sdk.integration.utils.DisabledOnCI import io.modelcontextprotocol.kotlin.sdk.types.TextContent import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async @@ -22,6 +23,7 @@ import kotlin.test.assertNotNull import kotlin.test.assertTrue import kotlin.time.Duration.Companion.seconds +@DisabledOnCI class KotlinClientTsServerEdgeCasesTestSse : TsTestBase() { override val transportKind = TransportKind.SSE diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerTestSse.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerTestSse.kt index 95a80f0..b9a7b5d 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerTestSse.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerTestSse.kt @@ -3,11 +3,13 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.integration.typescript.AbstractKotlinClientTsServerTest import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind +import io.modelcontextprotocol.kotlin.sdk.integration.utils.DisabledOnCI import kotlinx.coroutines.withTimeout import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import kotlin.time.Duration.Companion.seconds +@DisabledOnCI class KotlinClientTsServerTestSse : AbstractKotlinClientTsServerTest() { override val transportKind = TransportKind.SSE diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerEdgeCasesTestSse.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerEdgeCasesTestSse.kt index 5af315b..c2a4529 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerEdgeCasesTestSse.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerEdgeCasesTestSse.kt @@ -2,8 +2,9 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse import io.modelcontextprotocol.kotlin.sdk.TextContent import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.OldSchemaTransportKind -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.OldSchemaTsTestBase +import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind +import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TsTestBase +import io.modelcontextprotocol.kotlin.sdk.integration.utils.DisabledOnCI import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll @@ -22,9 +23,10 @@ import kotlin.test.assertNotNull import kotlin.test.assertTrue import kotlin.time.Duration.Companion.seconds -class OldSchemaKotlinClientTsServerEdgeCasesTestSse : OldSchemaTsTestBase() { +@DisabledOnCI +class OldSchemaKotlinClientTsServerEdgeCasesTestSse : TsTestBase() { - override val transportKind = OldSchemaTransportKind.SSE + override val transportKind = TransportKind.SSE private var port: Int = 0 private val host = "localhost" diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerTestSse.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerTestSse.kt index 39ac18c..e0990ee 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerTestSse.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerTestSse.kt @@ -2,15 +2,17 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.integration.typescript.OldSchemaAbstractKotlinClientTsServerTest -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.OldSchemaTransportKind +import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind +import io.modelcontextprotocol.kotlin.sdk.integration.utils.DisabledOnCI import kotlinx.coroutines.withTimeout import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import kotlin.time.Duration.Companion.seconds +@DisabledOnCI class OldSchemaKotlinClientTsServerTestSse : OldSchemaAbstractKotlinClientTsServerTest() { - override val transportKind = OldSchemaTransportKind.SSE + override val transportKind = TransportKind.SSE private var port: Int = 0 private val host = "localhost" diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsClientKotlinServerTestSse.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsClientKotlinServerTestSse.kt index edee427..e95db0e 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsClientKotlinServerTestSse.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsClientKotlinServerTestSse.kt @@ -2,9 +2,11 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse import io.modelcontextprotocol.kotlin.sdk.integration.typescript.AbstractTsClientKotlinServerTest import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind +import io.modelcontextprotocol.kotlin.sdk.integration.utils.DisabledOnCI import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach +@DisabledOnCI class TsClientKotlinServerTestSse : AbstractTsClientKotlinServerTest() { override val transportKind = TransportKind.SSE diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsEdgeCasesTestSse.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsEdgeCasesTestSse.kt index 65b003d..0ba11de 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsEdgeCasesTestSse.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsEdgeCasesTestSse.kt @@ -2,6 +2,7 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TsTestBase +import io.modelcontextprotocol.kotlin.sdk.integration.utils.DisabledOnCI import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope @@ -18,6 +19,7 @@ import kotlin.test.assertEquals import kotlin.test.assertTrue import kotlin.test.fail +@DisabledOnCI class TsEdgeCasesTestSse : TsTestBase() { override val transportKind = TransportKind.SSE diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt index 2bdd361..7abe0cb 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt @@ -3,6 +3,7 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.stdio import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TsTestBase +import io.modelcontextprotocol.kotlin.sdk.integration.utils.DisabledOnCI import io.modelcontextprotocol.kotlin.sdk.types.TextContent import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async @@ -18,6 +19,7 @@ import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue +@DisabledOnCI class KotlinClientTsServerEdgeCasesTestStdio : TsTestBase() { override val transportKind = TransportKind.STDIO diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerTestStdio.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerTestStdio.kt index 8273724..61f23c4 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerTestStdio.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerTestStdio.kt @@ -3,7 +3,9 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.stdio import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.integration.typescript.AbstractKotlinClientTsServerTest import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind +import io.modelcontextprotocol.kotlin.sdk.integration.utils.DisabledOnCI +@DisabledOnCI class KotlinClientTsServerTestStdio : AbstractKotlinClientTsServerTest() { override val transportKind = TransportKind.STDIO diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerEdgeCasesTestStdio.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerEdgeCasesTestStdio.kt index 3666622..ae2e09f 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerEdgeCasesTestStdio.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerEdgeCasesTestStdio.kt @@ -2,8 +2,9 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.stdio import io.modelcontextprotocol.kotlin.sdk.TextContent import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.OldSchemaTransportKind -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.OldSchemaTsTestBase +import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind +import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TsTestBase +import io.modelcontextprotocol.kotlin.sdk.integration.utils.DisabledOnCI import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll @@ -18,9 +19,10 @@ import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue -class OldSchemaKotlinClientTsServerEdgeCasesTestStdio : OldSchemaTsTestBase() { +@DisabledOnCI +class OldSchemaKotlinClientTsServerEdgeCasesTestStdio : TsTestBase() { - override val transportKind = OldSchemaTransportKind.STDIO + override val transportKind = TransportKind.STDIO @Test @Timeout(30, unit = TimeUnit.SECONDS) diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerTestStdio.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerTestStdio.kt index b1b5bae..7daac4a 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerTestStdio.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerTestStdio.kt @@ -2,11 +2,13 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.stdio import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.integration.typescript.OldSchemaAbstractKotlinClientTsServerTest -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.OldSchemaTransportKind +import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind +import io.modelcontextprotocol.kotlin.sdk.integration.utils.DisabledOnCI +@DisabledOnCI class OldSchemaKotlinClientTsServerTestStdio : OldSchemaAbstractKotlinClientTsServerTest() { - override val transportKind = OldSchemaTransportKind.STDIO + override val transportKind = TransportKind.STDIO override suspend fun useClient(block: suspend (Client) -> T): T = withClientStdio { client, proc -> try { diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/TsClientKotlinServerTestStdio.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/TsClientKotlinServerTestStdio.kt index abf821b..05f55bc 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/TsClientKotlinServerTestStdio.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/TsClientKotlinServerTestStdio.kt @@ -2,7 +2,9 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.stdio import io.modelcontextprotocol.kotlin.sdk.integration.typescript.AbstractTsClientKotlinServerTest import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind +import io.modelcontextprotocol.kotlin.sdk.integration.utils.DisabledOnCI +@DisabledOnCI class TsClientKotlinServerTestStdio : AbstractTsClientKotlinServerTest() { override val transportKind = TransportKind.STDIO override fun runClient(vararg args: String): String = runStdioClient(*args) diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/TsEdgeCasesTestStdio.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/TsEdgeCasesTestStdio.kt index dafda6d..cd1fa4d 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/TsEdgeCasesTestStdio.kt +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/TsEdgeCasesTestStdio.kt @@ -2,6 +2,7 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.stdio import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TsTestBase +import io.modelcontextprotocol.kotlin.sdk.integration.utils.DisabledOnCI import org.junit.jupiter.api.Test import org.junit.jupiter.api.Timeout import org.junit.jupiter.api.condition.EnabledOnOs @@ -11,6 +12,7 @@ import java.util.concurrent.TimeUnit import kotlin.test.assertEquals import kotlin.test.assertTrue +@DisabledOnCI class TsEdgeCasesTestStdio : TsTestBase() { override val transportKind: TransportKind = TransportKind.STDIO diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/utils/DisabledOnCI.kt b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/utils/DisabledOnCI.kt new file mode 100644 index 0000000..087d201 --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/utils/DisabledOnCI.kt @@ -0,0 +1,6 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.utils + +import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable + +@DisabledIfEnvironmentVariable(named = "CI", matches = "true") +annotation class DisabledOnCI diff --git a/kotlin-sdk-test/src/jvmTest/resources/simplelogger.properties b/kotlin-sdk-test/src/jvmTest/resources/simplelogger.properties new file mode 100644 index 0000000..95fb646 --- /dev/null +++ b/kotlin-sdk-test/src/jvmTest/resources/simplelogger.properties @@ -0,0 +1,8 @@ +# Level of logging for the ROOT logger: ERROR, WARN, INFO, DEBUG, TRACE (default is INFO) +org.slf4j.simpleLogger.defaultLogLevel=INFO +org.slf4j.simpleLogger.showThreadName=true +org.slf4j.simpleLogger.showDateTime=false + +# Log level for specific packages or classes +org.slf4j.simpleLogger.log.io.ktor.server=DEBUG +org.slf4j.simpleLogger.log.io.modelcontextprotocol=DEBUG From 960b38f923fd34ee4d336fff2b10bb7fe825b6e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Jan 2026 23:22:17 +0200 Subject: [PATCH 106/449] Bump io.github.oshai:kotlin-logging from 7.0.13 to 7.0.14 (#471) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [io.github.oshai:kotlin-logging](https://github.com/oshai/kotlin-logging) from 7.0.13 to 7.0.14.
Release notes

Sourced from io.github.oshai:kotlin-logging's releases.

7.0.14

What's Changed

New Contributors

Full Changelog: https://github.com/oshai/kotlin-logging/compare/7.0.13...7.0.14

Commits
  • e887dd0 bump version to 7.0.15
  • a1a8277 Change LogbackLogEvent to implement ILoggingEvent (#583)
  • 18d3337 Update README with new badges and contributor note
  • 8ed0b38 Bump ch.qos.logback:logback-classic from 1.5.18 to 1.5.19
  • d104a3e Bump gradle/actions from 4 to 5
  • 56a7383 Bump com.diffplug.spotless from 7.2.1 to 8.0.0
  • 22f8266 Bump actions/cache from 4.2.4 to 4.3.0
  • 88292cc Bump log4j from 2.25.1 to 2.25.2
  • 7ba3ab8 Bump org.mockito:mockito-core from 5.19.0 to 5.20.0
  • be06b6a Bump org.graalvm.sdk:nativeimage from 24.2.2 to 25.0.0
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=io.github.oshai:kotlin-logging&package-manager=gradle&previous-version=7.0.13&new-version=7.0.14)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 30f3356..46e47c2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,7 +17,7 @@ coroutines = "1.10.2" kotest = "6.0.7" kotlinx-io = "0.8.2" ktor = "3.2.3" -logging = "7.0.13" +logging = "7.0.14" mockk = "1.14.7" mokksy = "0.6.2" serialization = "1.9.0" From edc7f47a65bf156cd9d9197b6a30e225ca391062 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Jan 2026 23:23:05 +0200 Subject: [PATCH 107/449] Bump org.openapi.generator from 7.17.0 to 7.18.0 (#469) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [//]: # (dependabot-start) ⚠️ **Dependabot is rebasing this PR** ⚠️ Rebasing might not happen immediately, so don't worry if this takes some time. Note: if you make any changes to this PR yourself, they will take precedence over the rebase. --- [//]: # (dependabot-end) Bumps org.openapi.generator from 7.17.0 to 7.18.0. [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.openapi.generator&package-manager=gradle&previous-version=7.17.0&new-version=7.18.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 46e47c2..c426f02 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ kover = "0.9.3" netty = "4.2.7.Final" mavenPublish = "0.35.0" binaryCompatibilityValidatorPlugin = "0.18.1" -openapi-generator = "7.17.0" +openapi-generator = "7.18.0" # libraries version awaitility = "4.3.0" From fb69538129a2d57f6e3917ebd3c709272a904a76 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Jan 2026 23:44:27 +0200 Subject: [PATCH 108/449] Bump org.jetbrains.kotlinx.kover from 0.9.3 to 0.9.4 (#466) Bumps [org.jetbrains.kotlinx.kover](https://github.com/Kotlin/kotlinx-kover) from 0.9.3 to 0.9.4.
Release notes

Sourced from org.jetbrains.kotlinx.kover's releases.

0.9.4

Kover Gradle Plugin

Bugfixes

Changelog

Sourced from org.jetbrains.kotlinx.kover's changelog.

0.9.4 / 2025-12-09

Kover Gradle Plugin

Bugfixes

Commits
  • 34e42b9 Release 0.9.4
  • 298bf8b Added support for Android Gradle Plugin 9.0.0
  • 0ba9e9e Aligned filtering for JaCoCo reports with Kover reporting
  • See full diff in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.jetbrains.kotlinx.kover&package-manager=gradle&previous-version=0.9.3&new-version=0.9.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c426f02..c1492b4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ kotlin = "2.2.21" dokka = "2.1.0" atomicfu = "0.29.0" ktlint = "14.0.1" -kover = "0.9.3" +kover = "0.9.4" netty = "4.2.7.Final" mavenPublish = "0.35.0" binaryCompatibilityValidatorPlugin = "0.18.1" From 1e4d27ec2d348d4db2a8b26c7c8c82d8dd3e24d4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 10 Jan 2026 00:01:31 +0200 Subject: [PATCH 109/449] Bump io.netty:netty-bom from 4.2.7.Final to 4.2.9.Final (#468) Bumps [io.netty:netty-bom](https://github.com/netty/netty) from 4.2.7.Final to 4.2.9.Final.
Commits
  • a853a39 [maven-release-plugin] prepare release netty-4.2.9.Final
  • 6d29a4f Add missing publishing config for the bom module
  • ea911de Optimize HTTP startline validation (#16030)
  • d7108a3 LocalChannel: Reduce GC by re-using same Runnable (#16014)
  • 207afcb Fix MpscIntQueue bug (#16023)
  • 27bfd56 Fix HTTP startline validation (#16022)
  • 40ab418 [maven-release-plugin] prepare for next development iteration
  • e2d9d11 [maven-release-plugin] prepare release netty-4.2.8.Final
  • 2f2e437 Merge commit from fork
  • d011634 Build fixes to allow using the epoll native transport on Android (#16016)
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=io.netty:netty-bom&package-manager=gradle&previous-version=4.2.7.Final&new-version=4.2.9.Final)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c1492b4..fed638b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ dokka = "2.1.0" atomicfu = "0.29.0" ktlint = "14.0.1" kover = "0.9.4" -netty = "4.2.7.Final" +netty = "4.2.9.Final" mavenPublish = "0.35.0" binaryCompatibilityValidatorPlugin = "0.18.1" openapi-generator = "7.18.0" From 1e1c7e68716904df857e6b1850364e0c332923e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 10 Jan 2026 00:52:06 +0200 Subject: [PATCH 110/449] Bump actions/upload-artifact from 5 to 6 (#465) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
Release notes

Sourced from actions/upload-artifact's releases.

v6.0.0

v6 - What's new

[!IMPORTANT] actions/upload-artifact@v6 now runs on Node.js 24 (runs.using: node24) and requires a minimum Actions Runner version of 2.327.1. If you are using self-hosted runners, ensure they are updated before upgrading.

Node.js 24

This release updates the runtime to Node.js 24. v5 had preliminary support for Node.js 24, however this action was by default still running on Node.js 20. Now this action by default will run on Node.js 24.

What's Changed

Full Changelog: https://github.com/actions/upload-artifact/compare/v5.0.0...v6.0.0

Commits
  • b7c566a Merge pull request #745 from actions/upload-artifact-v6-release
  • e516bc8 docs: correct description of Node.js 24 support in README
  • ddc45ed docs: update README to correct action name for Node.js 24 support
  • 615b319 chore: release v6.0.0 for Node.js 24 support
  • 017748b Merge pull request #744 from actions/fix-storage-blob
  • 38d4c79 chore: rebuild dist
  • 7d27270 chore: add missing license cache files for @​actions/core, @​actions/io, and mi...
  • 5f643d3 chore: update license files for @​actions/artifact@​5.0.1 dependencies
  • 1df1684 chore: update package-lock.json with @​actions/artifact@​5.0.1
  • b5b1a91 fix: update @​actions/artifact to ^5.0.0 for Node.js 24 punycode fix
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/upload-artifact&package-manager=github_actions&previous-version=5&new-version=6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- .github/workflows/conformance.yml | 2 +- .github/workflows/samples.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fa07e46..1b6c741 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -109,7 +109,7 @@ jobs: - name: Upload Maven Local Repository if: ${{ success() && matrix.os == 'ubuntu-latest' }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: local-maven-repository path: local-maven-repository.zip diff --git a/.github/workflows/conformance.yml b/.github/workflows/conformance.yml index 5aee68b..4f09c1b 100644 --- a/.github/workflows/conformance.yml +++ b/.github/workflows/conformance.yml @@ -44,7 +44,7 @@ jobs: - name: Upload Conformance Results if: ${{ !cancelled() }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: conformance-results path: conformance-test/results/ diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index 199a0a0..7fac388 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -57,7 +57,7 @@ jobs: - name: Upload Reports if: ${{ !cancelled() }} - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: reports-samples-${{ matrix.sample }} path: | From 6fee82ff1aab6006f082b8845ececc39d259c853 Mon Sep 17 00:00:00 2001 From: Nathaniel Lombardi <75634623+nathaniellombardi-toast@users.noreply.github.com> Date: Fri, 9 Jan 2026 18:12:29 -0500 Subject: [PATCH 111/449] Mark DSL functions for types as public (#472) ## Motivation and Context The dsl builders in [this commit](https://github.com/modelcontextprotocol/kotlin-sdk/commit/74c93745cde9b746f8090115467e50d7acb607d3#diff-598edaa01d0daeeafab4c95b9802c5435dc8b6798147b4d34db08419c89fcaf1) were mistakenly marked internal and are therefore unusable. I have simply marked them as public. ## How Has This Been Tested? ## Breaking Changes No ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update ## Checklist - [ ] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [ ] 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 --------- Co-authored-by: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> --- AGENTS.md | 6 + kotlin-sdk-core/api/kotlin-sdk-core.api | 43 ++++ .../kotlin/sdk/types/completion.dsl.kt | 2 +- .../kotlin/sdk/types/elicitation.dsl.kt | 2 +- .../kotlin/sdk/types/initialize.dsl.kt | 2 +- .../kotlin/sdk/types/logging.dsl.kt | 2 +- .../kotlin/sdk/types/pingRequest.dsl.kt | 2 +- .../kotlin/sdk/types/prompts.dsl.kt | 4 +- .../kotlin/sdk/types/resources.dsl.kt | 10 +- .../kotlin/sdk/types/roots.dsl.kt | 2 +- .../kotlin/sdk/types/sampling.dsl.kt | 2 +- .../kotlin/sdk/types/tools.dsl.kt | 4 +- .../kotlin/sdk/types/dsl/CompletionDslTest.kt | 65 ++++++ .../sdk/types/dsl/ElicitationDslTest.kt | 84 ++++++++ .../kotlin/sdk/types/dsl/InitializeDslTest.kt | 138 ++++++++++++ .../kotlin/sdk/types/dsl/LoggingDslTest.kt | 27 +++ .../sdk/types/dsl/PingRequestDslTest.kt | 65 ++++++ .../kotlin/sdk/types/dsl/PromptsDslTest.kt | 47 ++++ .../kotlin/sdk/types/dsl/ResourcesDslTest.kt | 92 ++++++++ .../kotlin/sdk/types/dsl/RootsDslTest.kt | 19 ++ .../kotlin/sdk/types/dsl/SamplingDslTest.kt | 201 ++++++++++++++++++ .../kotlin/sdk/types/dsl/ToolsDslTest.kt | 67 ++++++ 22 files changed, 870 insertions(+), 16 deletions(-) create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CompletionDslTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ElicitationDslTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/InitializeDslTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/LoggingDslTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/PingRequestDslTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/PromptsDslTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ResourcesDslTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/RootsDslTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/SamplingDslTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ToolsDslTest.kt diff --git a/AGENTS.md b/AGENTS.md index 5b49a6d..29aacef 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -46,6 +46,12 @@ MCP Kotlin SDK — Kotlin Multiplatform implementation of the Model Context Prot - All tests for each module are located in `src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/` - Platform-specific tests go in `src/jvmTest/`, `src/jsTest/`, etc. - Use Kotest assertions (`shouldBe`, `shouldContain`, etc.) for readable test failures. +- For nullable objects with nested properties, prefer `shouldNotBeNull { ... }` blocks: + ```kotlin + content.annotations shouldNotBeNull { + priority shouldBe 1.0 + } + ``` - Use `shouldMatchJson` from Kotest for JSON validation. - Mock Ktor HTTP clients using `MockEngine` from `io.ktor:ktor-client-mock`. - Always add tests for new features or bug fixes, even if not explicitly requested. diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index 8fff88b..67acbdd 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -1195,6 +1195,10 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CompleteResult$Compl public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/Completion_dslKt { + public static final fun buildCompleteRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequest; +} + public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/ContentBlock : io/modelcontextprotocol/kotlin/sdk/types/WithMeta { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ContentBlock$Companion; public abstract fun getType ()Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; @@ -1584,6 +1588,10 @@ 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/Elicitation_dslKt { + public static final fun buildElicitRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequest; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/EmbeddedResource : io/modelcontextprotocol/kotlin/sdk/types/ContentBlock { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/EmbeddedResource$Companion; public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ResourceContents;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Lkotlinx/serialization/json/JsonObject;)V @@ -2016,6 +2024,10 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/InitializeResult$Com public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/Initialize_dslKt { + public static final fun buildInitializeRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequest; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/InitializedNotification : io/modelcontextprotocol/kotlin/sdk/types/ClientNotification { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/InitializedNotification$Companion; public fun ()V @@ -2660,6 +2672,10 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotifi public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/Logging_dslKt { + public static final fun buildSetLevelRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequest; +} + public abstract interface annotation class io/modelcontextprotocol/kotlin/sdk/types/McpDsl : java/lang/annotation/Annotation { } @@ -2957,6 +2973,10 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/PingRequestBuilder : public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; } +public final class io/modelcontextprotocol/kotlin/sdk/types/PingRequest_dslKt { + public static final fun buildPingRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/PingRequest; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/Progress { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Progress$Companion; public fun (DLjava/lang/Double;Ljava/lang/String;)V @@ -3211,6 +3231,11 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/PromptReference$Comp public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/Prompts_dslKt { + public static final fun buildGetPromptRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest; + public static final fun buildListPromptsRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/RPCError { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/RPCError$Companion; public fun (ILjava/lang/String;Lkotlinx/serialization/json/JsonElement;)V @@ -3809,6 +3834,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotif public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/Resources_dslKt { + public static final fun buildListResourceTemplatesRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesRequest; + public static final fun buildListResourcesRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest; + public static final fun buildReadResourceRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest; + public static final fun buildSubscribeRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest; + public static final fun buildUnsubscribeRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/Role : java/lang/Enum { public static final field Assistant Lio/modelcontextprotocol/kotlin/sdk/types/Role; public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Role$Companion; @@ -3886,6 +3919,10 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/RootsListChangedNoti public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/Roots_dslKt { + public static final fun buildListRootsRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/SamplingMessage { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessage$Companion; public fun (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;)V @@ -3927,6 +3964,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/Sampling_dslKt { public static final fun assistantAudio (Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageBuilder;Lkotlin/jvm/functions/Function1;)V public static final fun assistantImage (Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageBuilder;Lkotlin/jvm/functions/Function1;)V public static final fun assistantText (Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageBuilder;Lkotlin/jvm/functions/Function1;)V + public static final fun buildCreateMessageRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest; public static final fun user (Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageBuilder;Lkotlin/jvm/functions/Function0;)V public static final fun userAudio (Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageBuilder;Lkotlin/jvm/functions/Function1;)V public static final fun userImage (Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageBuilder;Lkotlin/jvm/functions/Function1;)V @@ -4490,6 +4528,11 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ToolsKt { public static synthetic fun success$default (Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult$Companion;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult; } +public final class io/modelcontextprotocol/kotlin/sdk/types/Tools_dslKt { + public static final fun buildCallToolRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequest; + public static final fun buildListToolsRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/UnknownResourceContents : io/modelcontextprotocol/kotlin/sdk/types/ResourceContents { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/UnknownResourceContents$Companion; public fun (Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.dsl.kt index dbefd31..bbcfe6c 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.dsl.kt @@ -42,7 +42,7 @@ import kotlin.contracts.contract */ @OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -internal inline fun buildCompleteRequest(block: CompleteRequestBuilder.() -> Unit): CompleteRequest { +public inline fun buildCompleteRequest(block: CompleteRequestBuilder.() -> Unit): CompleteRequest { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return CompleteRequestBuilder().apply(block).build() } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.dsl.kt index a4823cf..e398d98 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.dsl.kt @@ -59,7 +59,7 @@ import kotlin.contracts.contract */ @OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -internal inline fun buildElicitRequest(block: ElicitRequestBuilder.() -> Unit): ElicitRequest { +public inline fun buildElicitRequest(block: ElicitRequestBuilder.() -> Unit): ElicitRequest { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return ElicitRequestBuilder().apply(block).build() } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/initialize.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/initialize.dsl.kt index 345b036..2cb58b7 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/initialize.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/initialize.dsl.kt @@ -54,7 +54,7 @@ import kotlin.contracts.contract */ @OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -internal inline fun buildInitializeRequest(block: InitializeRequestBuilder.() -> Unit): InitializeRequest { +public inline fun buildInitializeRequest(block: InitializeRequestBuilder.() -> Unit): InitializeRequest { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return InitializeRequestBuilder().apply(block).build() } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/logging.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/logging.dsl.kt index 883d2cc..b65846c 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/logging.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/logging.dsl.kt @@ -36,7 +36,7 @@ import kotlin.contracts.contract */ @OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -internal inline fun buildSetLevelRequest(block: SetLevelRequestBuilder.() -> Unit): SetLevelRequest { +public inline fun buildSetLevelRequest(block: SetLevelRequestBuilder.() -> Unit): SetLevelRequest { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return SetLevelRequestBuilder().apply(block).build() } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/pingRequest.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/pingRequest.dsl.kt index 2f5ac33..1898636 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/pingRequest.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/pingRequest.dsl.kt @@ -32,7 +32,7 @@ import kotlin.contracts.contract */ @OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -internal inline fun buildPingRequest(block: PingRequestBuilder.() -> Unit): PingRequest { +public inline fun buildPingRequest(block: PingRequestBuilder.() -> Unit): PingRequest { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return PingRequestBuilder().apply(block).build() } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.dsl.kt index 02eae95..b5aaaa2 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.dsl.kt @@ -40,7 +40,7 @@ import kotlin.contracts.contract */ @OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -internal inline fun buildGetPromptRequest(block: GetPromptRequestBuilder.() -> Unit): GetPromptRequest { +public inline fun buildGetPromptRequest(block: GetPromptRequestBuilder.() -> Unit): GetPromptRequest { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return GetPromptRequestBuilder().apply(block).build() } @@ -116,7 +116,7 @@ public class GetPromptRequestBuilder @PublishedApi internal constructor() : Requ */ @OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -internal inline fun buildListPromptsRequest(block: ListPromptsRequestBuilder.() -> Unit): ListPromptsRequest { +public inline fun buildListPromptsRequest(block: ListPromptsRequestBuilder.() -> Unit): ListPromptsRequest { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return ListPromptsRequestBuilder().apply(block).build() } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.dsl.kt index db40cd7..ff2ddf5 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.dsl.kt @@ -31,7 +31,7 @@ import kotlin.contracts.contract */ @OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -internal inline fun buildListResourcesRequest(block: ListResourcesRequestBuilder.() -> Unit): ListResourcesRequest { +public inline fun buildListResourcesRequest(block: ListResourcesRequestBuilder.() -> Unit): ListResourcesRequest { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return ListResourcesRequestBuilder().apply(block).build() } @@ -82,7 +82,7 @@ public class ListResourcesRequestBuilder @PublishedApi internal constructor() : */ @OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -internal inline fun buildReadResourceRequest(block: ReadResourceRequestBuilder.() -> Unit): ReadResourceRequest { +public inline fun buildReadResourceRequest(block: ReadResourceRequestBuilder.() -> Unit): ReadResourceRequest { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return ReadResourceRequestBuilder().apply(block).build() } @@ -144,7 +144,7 @@ public class ReadResourceRequestBuilder @PublishedApi internal constructor() : R */ @OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -internal inline fun buildSubscribeRequest(block: SubscribeRequestBuilder.() -> Unit): SubscribeRequest { +public inline fun buildSubscribeRequest(block: SubscribeRequestBuilder.() -> Unit): SubscribeRequest { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return SubscribeRequestBuilder().apply(block).build() } @@ -206,7 +206,7 @@ public class SubscribeRequestBuilder @PublishedApi internal constructor() : Requ */ @OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -internal inline fun buildUnsubscribeRequest(block: UnsubscribeRequestBuilder.() -> Unit): UnsubscribeRequest { +public inline fun buildUnsubscribeRequest(block: UnsubscribeRequestBuilder.() -> Unit): UnsubscribeRequest { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return UnsubscribeRequestBuilder().apply(block).build() } @@ -271,7 +271,7 @@ public class UnsubscribeRequestBuilder @PublishedApi internal constructor() : Re */ @OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -internal inline fun buildListResourceTemplatesRequest( +public inline fun buildListResourceTemplatesRequest( block: ListResourceTemplatesRequestBuilder.() -> Unit, ): ListResourceTemplatesRequest { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.dsl.kt index a73f218..c1af201 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.dsl.kt @@ -32,7 +32,7 @@ import kotlin.contracts.contract */ @OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -internal inline fun buildListRootsRequest(block: ListRootsRequestBuilder.() -> Unit): ListRootsRequest { +public inline fun buildListRootsRequest(block: ListRootsRequestBuilder.() -> Unit): ListRootsRequest { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return ListRootsRequestBuilder().apply(block).build() } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.dsl.kt index 8e87304..1bc00e2 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.dsl.kt @@ -59,7 +59,7 @@ import kotlin.contracts.contract */ @OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -internal inline fun buildCreateMessageRequest(block: CreateMessageRequestBuilder.() -> Unit): CreateMessageRequest { +public inline fun buildCreateMessageRequest(block: CreateMessageRequestBuilder.() -> Unit): CreateMessageRequest { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return CreateMessageRequestBuilder().apply(block).build() } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.dsl.kt index 9a5adf1..a3a9be3 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.dsl.kt @@ -43,7 +43,7 @@ import kotlin.contracts.contract */ @OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -internal inline fun buildCallToolRequest(block: CallToolRequestBuilder.() -> Unit): CallToolRequest { +public inline fun buildCallToolRequest(block: CallToolRequestBuilder.() -> Unit): CallToolRequest { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return CallToolRequestBuilder().apply(block).build() } @@ -143,7 +143,7 @@ public class CallToolRequestBuilder @PublishedApi internal constructor() : Reque */ @OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -internal inline fun buildListToolsRequest(block: ListToolsRequestBuilder.() -> Unit): ListToolsRequest { +public inline fun buildListToolsRequest(block: ListToolsRequestBuilder.() -> Unit): ListToolsRequest { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return ListToolsRequestBuilder().apply(block).build() } diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CompletionDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CompletionDslTest.kt new file mode 100644 index 0000000..cbe40c9 --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CompletionDslTest.kt @@ -0,0 +1,65 @@ +package io.modelcontextprotocol.kotlin.sdk.types.dsl + +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.shouldBe +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import io.modelcontextprotocol.kotlin.sdk.types.PromptReference +import io.modelcontextprotocol.kotlin.sdk.types.ResourceTemplateReference +import io.modelcontextprotocol.kotlin.sdk.types.buildCompleteRequest +import kotlin.test.Test + +@OptIn(ExperimentalMcpApi::class) +class CompletionDslTest { + @Test + fun `buildCompleteRequest should build with prompt reference and context`() { + val request = buildCompleteRequest { + argument("query", "user input") + ref(PromptReference("searchPrompt")) + context { + put("userId", "123") + } + } + + request.params.argument.name shouldBe "query" + request.params.argument.value shouldBe "user input" + (request.params.ref as PromptReference).name shouldBe "searchPrompt" + request.params.context shouldNotBeNull { + arguments?.get("userId") shouldBe "123" + } + } + + @Test + fun `buildCompleteRequest should build with resource template reference and map context`() { + val request = buildCompleteRequest { + argument("path", "/users/123") + ref(ResourceTemplateReference("file:///{path}")) + context(mapOf("role" to "admin")) + } + + request.params.argument.name shouldBe "path" + request.params.argument.value shouldBe "/users/123" + (request.params.ref as ResourceTemplateReference).uri shouldBe "file:///{path}" + request.params.context shouldNotBeNull { + arguments?.get("role") shouldBe "admin" + } + } + + @Test + fun `buildCompleteRequest should throw if argument is missing`() { + shouldThrow { + buildCompleteRequest { + ref(PromptReference("name")) + } + } + } + + @Test + fun `buildCompleteRequest should throw if ref is missing`() { + shouldThrow { + buildCompleteRequest { + argument("name", "value") + } + } + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ElicitationDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ElicitationDslTest.kt new file mode 100644 index 0000000..e49e4d2 --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ElicitationDslTest.kt @@ -0,0 +1,84 @@ +package io.modelcontextprotocol.kotlin.sdk.types.dsl + +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.buildElicitRequest +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import kotlin.test.Test + +@OptIn(ExperimentalMcpApi::class) +class ElicitationDslTest { + @Test + fun `buildElicitRequest should build with all fields`() { + val request = buildElicitRequest { + message = "Provide info" + requestedSchema { + properties { + put("email", buildJsonObject { put("type", "string") }) + } + required = listOf("email") + } + } + + request.params.message shouldBe "Provide info" + request.params.requestedSchema.properties["email"] shouldBe buildJsonObject { put("type", "string") } + request.params.requestedSchema.required shouldBe listOf("email") + } + + @Test + fun `buildElicitRequest should support direct requestedSchema`() { + val schema = ElicitRequestParams.RequestedSchema( + properties = buildJsonObject { put("name", buildJsonObject { put("type", "string") }) }, + required = listOf("name"), + ) + val request = buildElicitRequest { + message = "Test" + requestedSchema(schema) + } + + request.params.requestedSchema shouldBe schema + } + + @Test + fun `ElicitRequestedSchemaBuilder should support direct properties assignment`() { + val props = buildJsonObject { put("key", "value") } + val request = buildElicitRequest { + message = "Test" + requestedSchema { + properties(props) + } + } + request.params.requestedSchema.properties shouldBe props + } + + @Test + fun `buildElicitRequest should throw if message is missing`() { + shouldThrow { + buildElicitRequest { + requestedSchema { properties { put("a", 1) } } + } + } + } + + @Test + fun `buildElicitRequest should throw if requestedSchema is missing`() { + shouldThrow { + buildElicitRequest { + message = "Test" + } + } + } + + @Test + fun `ElicitRequestedSchemaBuilder should throw if properties are missing`() { + shouldThrow { + buildElicitRequest { + message = "Test" + requestedSchema { } + } + } + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/InitializeDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/InitializeDslTest.kt new file mode 100644 index 0000000..cb35115 --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/InitializeDslTest.kt @@ -0,0 +1,138 @@ +package io.modelcontextprotocol.kotlin.sdk.types.dsl + +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.shouldBe +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.buildInitializeRequest +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import kotlin.test.Test + +@OptIn(ExperimentalMcpApi::class) +class InitializeDslTest { + @Test + fun `buildInitializeRequest should create request with all fields`() { + val request = buildInitializeRequest { + protocolVersion = "2024-11-05" + capabilities { + sampling { + put("maxTokens", 100) + } + roots(listChanged = true) + elicitation { + put("mode", "interactive") + } + experimental { + put("custom", true) + } + } + info( + name = "TestClient", + version = "1.0.0", + title = "Test Client", + websiteUrl = "https://example.com", + ) + } + + request.params.protocolVersion shouldBe "2024-11-05" + request.params.capabilities.shouldNotBeNull { + sampling?.get("maxTokens")?.jsonPrimitive?.int shouldBe 100 + roots?.listChanged shouldBe true + elicitation?.get("mode")?.jsonPrimitive?.content shouldBe "interactive" + experimental?.get("custom")?.jsonPrimitive?.content shouldBe "true" + } + request.params.clientInfo.shouldNotBeNull { + name shouldBe "TestClient" + version shouldBe "1.0.0" + title shouldBe "Test Client" + websiteUrl shouldBe "https://example.com" + } + } + + @Test + fun `buildInitializeRequest should support direct capabilities and info`() { + val capabilities = ClientCapabilities(roots = ClientCapabilities.Roots(listChanged = false)) + val info = Implementation(name = "Direct", version = "0.1") + + val request = buildInitializeRequest { + protocolVersion = "1.0" + capabilities(capabilities) + info(info) + } + + request.params.capabilities shouldBe capabilities + request.params.clientInfo shouldBe info + } + + @Test + fun `capabilities DSL should support direct JsonObject values`() { + val samplingObj = buildJsonObject { put("key", "value") } + val elicitationObj = buildJsonObject { put("key", "value") } + val experimentalObj = buildJsonObject { put("key", "value") } + + val request = buildInitializeRequest { + protocolVersion = "1.0" + capabilities { + sampling(samplingObj) + elicitation(elicitationObj) + experimental(experimentalObj) + } + info("Test", "1.0") + } + + request.params.capabilities.shouldNotBeNull { + sampling shouldBe samplingObj + elicitation shouldBe elicitationObj + experimental shouldBe experimentalObj + } + } + + @Test + fun `ClientCapabilitiesBuilder roots should support default arguments`() { + val request = buildInitializeRequest { + protocolVersion = "1.0" + capabilities { + roots() + } + info("Test", "1.0") + } + request.params.capabilities.roots.shouldNotBeNull { + listChanged shouldBe null + } + } + + @Test + fun `buildInitializeRequest should throw if protocolVersion is missing`() { + shouldThrow { + buildInitializeRequest { + capabilities { } + info("Test", "1.0") + } + } + } + + @Test + fun `buildInitializeRequest should throw if capabilities are missing`() { + shouldThrow { + buildInitializeRequest { + protocolVersion = "1.0" + info("Test", "1.0") + } + } + } + + @Test + fun `buildInitializeRequest should throw if info is missing`() { + shouldThrow { + buildInitializeRequest { + protocolVersion = "1.0" + capabilities { } + } + } + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/LoggingDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/LoggingDslTest.kt new file mode 100644 index 0000000..5b09b42 --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/LoggingDslTest.kt @@ -0,0 +1,27 @@ +package io.modelcontextprotocol.kotlin.sdk.types.dsl + +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel +import io.modelcontextprotocol.kotlin.sdk.types.buildSetLevelRequest +import kotlin.test.Test + +@OptIn(ExperimentalMcpApi::class) +class LoggingDslTest { + @Test + fun `buildSetLevelRequest should create request with given level`() { + val request = buildSetLevelRequest { + loggingLevel = LoggingLevel.Info + } + + request.params.level shouldBe LoggingLevel.Info + } + + @Test + fun `buildSetLevelRequest should throw if loggingLevel is missing`() { + shouldThrow { + buildSetLevelRequest { } + } + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/PingRequestDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/PingRequestDslTest.kt new file mode 100644 index 0000000..5a219ba --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/PingRequestDslTest.kt @@ -0,0 +1,65 @@ +package io.modelcontextprotocol.kotlin.sdk.types.dsl + +import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.shouldBe +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import io.modelcontextprotocol.kotlin.sdk.types.buildPingRequest +import kotlinx.serialization.json.add +import kotlinx.serialization.json.boolean +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import kotlin.test.Test + +@OptIn(ExperimentalMcpApi::class) +class PingRequestDslTest { + @Test + fun `buildPingRequest should create request with meta containing all field types`() { + val request = buildPingRequest { + meta { + progressToken("token-123") + put("string", "value") + put("number", 42) + put("boolean", true) + put("null", null) + putJsonObject("obj") { + put("key", "val") + } + putJsonArray("arr") { + add("item") + } + } + } + + request.params.shouldNotBeNull { + meta.shouldNotBeNull { + json["progressToken"]?.jsonPrimitive?.content shouldBe "token-123" + json["string"]?.jsonPrimitive?.content shouldBe "value" + json["number"]?.jsonPrimitive?.int shouldBe 42 + json["boolean"]?.jsonPrimitive?.boolean shouldBe true + json["null"] shouldBe kotlinx.serialization.json.JsonNull + json["obj"]?.shouldNotBeNull() + json["arr"]?.shouldNotBeNull() + } + } + } + + @Test + fun `RequestMeta DSL should support numeric progress tokens`() { + val requestInt = buildPingRequest { + meta { progressToken(123) } + } + requestInt.params?.meta?.json?.get("progressToken")?.jsonPrimitive?.int shouldBe 123 + + val requestLong = buildPingRequest { + meta { progressToken(456L) } + } + requestLong.params?.meta?.json?.get("progressToken")?.jsonPrimitive?.int shouldBe 456 + } + + @Test + fun `buildPingRequest should create request without params if meta is empty`() { + val request = buildPingRequest { } + request.params shouldBe null + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/PromptsDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/PromptsDslTest.kt new file mode 100644 index 0000000..937933e --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/PromptsDslTest.kt @@ -0,0 +1,47 @@ +package io.modelcontextprotocol.kotlin.sdk.types.dsl + +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.shouldBe +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import io.modelcontextprotocol.kotlin.sdk.types.buildGetPromptRequest +import io.modelcontextprotocol.kotlin.sdk.types.buildListPromptsRequest +import kotlin.test.Test + +@OptIn(ExperimentalMcpApi::class) +class PromptsDslTest { + @Test + fun `buildGetPromptRequest should create request with name and arguments`() { + val request = buildGetPromptRequest { + name = "test-prompt" + arguments = mapOf("key" to "value") + } + + request.params.name shouldBe "test-prompt" + request.params.arguments shouldBe mapOf("key" to "value") + } + + @Test + fun `buildListPromptsRequest should create request with cursor`() { + val request = buildListPromptsRequest { + cursor = "next-page" + } + + request.params shouldNotBeNull { + cursor shouldBe "next-page" + } + } + + @Test + fun `buildGetPromptRequest should throw if name is missing`() { + shouldThrow { + buildGetPromptRequest { } + } + } + + @Test + fun `buildListPromptsRequest should create request without params if empty`() { + val request = buildListPromptsRequest { } + request.params shouldBe null + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ResourcesDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ResourcesDslTest.kt new file mode 100644 index 0000000..13df670 --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ResourcesDslTest.kt @@ -0,0 +1,92 @@ +package io.modelcontextprotocol.kotlin.sdk.types.dsl + +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.shouldBe +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import io.modelcontextprotocol.kotlin.sdk.types.buildListResourceTemplatesRequest +import io.modelcontextprotocol.kotlin.sdk.types.buildListResourcesRequest +import io.modelcontextprotocol.kotlin.sdk.types.buildReadResourceRequest +import io.modelcontextprotocol.kotlin.sdk.types.buildSubscribeRequest +import io.modelcontextprotocol.kotlin.sdk.types.buildUnsubscribeRequest +import kotlin.test.Test + +@OptIn(ExperimentalMcpApi::class) +class ResourcesDslTest { + @Test + fun `buildListResourcesRequest should create request with cursor`() { + val request = buildListResourcesRequest { + cursor = "next" + } + request.params shouldNotBeNull { + cursor shouldBe "next" + } + } + + @Test + fun `buildReadResourceRequest should create request with uri`() { + val request = buildReadResourceRequest { + uri = "test://resource" + } + request.params.uri shouldBe "test://resource" + } + + @Test + fun `buildSubscribeRequest should create request with uri`() { + val request = buildSubscribeRequest { + uri = "test://resource" + } + request.params.uri shouldBe "test://resource" + } + + @Test + fun `buildUnsubscribeRequest should create request with uri`() { + val request = buildUnsubscribeRequest { + uri = "test://resource" + } + request.params.uri shouldBe "test://resource" + } + + @Test + fun `buildListResourceTemplatesRequest should create request with cursor`() { + val request = buildListResourceTemplatesRequest { + cursor = "template-cursor" + } + request.params shouldNotBeNull { + cursor shouldBe "template-cursor" + } + } + + @Test + fun `buildReadResourceRequest should throw if uri is missing`() { + shouldThrow { + buildReadResourceRequest { } + } + } + + @Test + fun `buildSubscribeRequest should throw if uri is missing`() { + shouldThrow { + buildSubscribeRequest { } + } + } + + @Test + fun `buildUnsubscribeRequest should throw if uri is missing`() { + shouldThrow { + buildUnsubscribeRequest { } + } + } + + @Test + fun `buildListResourcesRequest should create request without params if empty`() { + val request = buildListResourcesRequest { } + request.params shouldBe null + } + + @Test + fun `buildListResourceTemplatesRequest should create request without params if empty`() { + val request = buildListResourceTemplatesRequest { } + request.params shouldBe null + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/RootsDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/RootsDslTest.kt new file mode 100644 index 0000000..037649f --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/RootsDslTest.kt @@ -0,0 +1,19 @@ +package io.modelcontextprotocol.kotlin.sdk.types.dsl + +import io.kotest.matchers.nulls.shouldNotBeNull +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import io.modelcontextprotocol.kotlin.sdk.types.buildListRootsRequest +import kotlin.test.Test + +@OptIn(ExperimentalMcpApi::class) +class RootsDslTest { + @Test + fun `buildListRootsRequest should create request with meta`() { + val request = buildListRootsRequest { + meta { + put("test", "value") + } + } + request.params.shouldNotBeNull() + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/SamplingDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/SamplingDslTest.kt new file mode 100644 index 0000000..5b60cfb --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/SamplingDslTest.kt @@ -0,0 +1,201 @@ +package io.modelcontextprotocol.kotlin.sdk.types.dsl + +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.collections.shouldHaveSize +import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.shouldBe +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import io.modelcontextprotocol.kotlin.sdk.types.Annotations +import io.modelcontextprotocol.kotlin.sdk.types.AudioContent +import io.modelcontextprotocol.kotlin.sdk.types.ImageContent +import io.modelcontextprotocol.kotlin.sdk.types.IncludeContext +import io.modelcontextprotocol.kotlin.sdk.types.ModelPreferences +import io.modelcontextprotocol.kotlin.sdk.types.Role +import io.modelcontextprotocol.kotlin.sdk.types.SamplingMessage +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.sdk.types.assistant +import io.modelcontextprotocol.kotlin.sdk.types.assistantAudio +import io.modelcontextprotocol.kotlin.sdk.types.assistantImage +import io.modelcontextprotocol.kotlin.sdk.types.buildCreateMessageRequest +import io.modelcontextprotocol.kotlin.sdk.types.user +import io.modelcontextprotocol.kotlin.sdk.types.userAudio +import io.modelcontextprotocol.kotlin.sdk.types.userImage +import io.modelcontextprotocol.kotlin.sdk.types.userText +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import kotlin.test.Test + +@OptIn(ExperimentalMcpApi::class) +class SamplingDslTest { + @Test + fun `buildCreateMessageRequest should build with all fields`() { + val request = buildCreateMessageRequest { + maxTokens = 1000 + systemPrompt = "System prompt" + temperature = 0.5 + stopSequences = listOf("STOP") + messages { + user { "Hello" } + assistant { "Hi" } + userText { + text = "Text with annotations" + annotations( + audience = listOf(Role.User), + priority = 1.0, + lastModified = "2025-01-10T00:00:00Z", + ) + meta { + put("key", "value") + } + } + assistantImage { + data = "base64image" + mimeType = "image/png" + annotations(Annotations(priority = 0.5)) + } + assistantAudio { + data = "base64audio" + mimeType = "audio/wav" + } + } + preferences(hints = listOf("hint"), intelligence = 0.9) + metadata { + put("metaKey", "metaValue") + } + } + + request.params.maxTokens shouldBe 1000 + request.params.systemPrompt shouldBe "System prompt" + request.params.temperature shouldBe 0.5 + request.params.stopSequences shouldBe listOf("STOP") + request.params.messages shouldHaveSize 5 + + (request.params.messages[2].content as TextContent).shouldNotBeNull { + text shouldBe "Text with annotations" + annotations shouldNotBeNull { + audience shouldBe listOf(Role.User) + priority shouldBe 1.0 + lastModified shouldBe "2025-01-10T00:00:00Z" + } + meta?.get("key")?.jsonPrimitive?.content shouldBe "value" + } + + (request.params.messages[3].content as ImageContent).shouldNotBeNull { + data shouldBe "base64image" + mimeType shouldBe "image/png" + annotations?.priority shouldBe 0.5 + } + + (request.params.messages[4].content as AudioContent).shouldNotBeNull { + data shouldBe "base64audio" + mimeType shouldBe "audio/wav" + } + + request.params.modelPreferences shouldNotBeNull { + intelligencePriority shouldBe 0.9 + } + request.params.metadata shouldNotBeNull { + get("metaKey")?.jsonPrimitive?.content shouldBe "metaValue" + } + } + + @Test + fun `buildCreateMessageRequest should support direct assignments`() { + val messages = listOf(SamplingMessage(Role.User, TextContent("Hello"))) + val preferences = ModelPreferences(costPriority = 0.1) + val request = buildCreateMessageRequest { + maxTokens = 100 + messages(messages) + preferences(preferences) + context = IncludeContext.AllServers + } + + request.params.messages shouldBe messages + request.params.modelPreferences shouldBe preferences + request.params.includeContext shouldBe IncludeContext.AllServers + } + + @Test + fun `SamplingMessageBuilder should support direct content assignment`() { + val content = TextContent("Direct") + val request = buildCreateMessageRequest { + maxTokens = 100 + messages { + user(content) + assistant(content) + } + } + request.params.messages[0].content shouldBe content + request.params.messages[1].content shouldBe content + } + + @Test + fun `buildCreateMessageRequest should throw if maxTokens is missing`() { + shouldThrow { + buildCreateMessageRequest { + messages { user { "Hi" } } + } + } + } + + @Test + fun `buildCreateMessageRequest should throw if messages are missing`() { + shouldThrow { + buildCreateMessageRequest { + maxTokens = 100 + } + } + } + + @Test + fun `TextContentBuilder should throw if text is missing`() { + shouldThrow { + buildCreateMessageRequest { + maxTokens = 100 + messages { + userText { } + } + } + } + } + + @Test + fun `ImageContentBuilder should throw if data or mimeType is missing`() { + shouldThrow { + buildCreateMessageRequest { + maxTokens = 100 + messages { + userImage { data = "abc" } + } + } + } + shouldThrow { + buildCreateMessageRequest { + maxTokens = 100 + messages { + userImage { mimeType = "image/png" } + } + } + } + } + + @Test + fun `AudioContentBuilder should throw if data or mimeType is missing`() { + shouldThrow { + buildCreateMessageRequest { + maxTokens = 100 + messages { + userAudio { data = "abc" } + } + } + } + shouldThrow { + buildCreateMessageRequest { + maxTokens = 100 + messages { + userAudio { mimeType = "audio/wav" } + } + } + } + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ToolsDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ToolsDslTest.kt new file mode 100644 index 0000000..957f0d2 --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ToolsDslTest.kt @@ -0,0 +1,67 @@ +package io.modelcontextprotocol.kotlin.sdk.types.dsl + +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.shouldBe +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import io.modelcontextprotocol.kotlin.sdk.types.buildCallToolRequest +import io.modelcontextprotocol.kotlin.sdk.types.buildListToolsRequest +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import kotlin.test.Test + +@OptIn(ExperimentalMcpApi::class) +class ToolsDslTest { + @Test + fun `buildCallToolRequest should build with name and arguments`() { + val request = buildCallToolRequest { + name = "test-tool" + arguments { + put("key", "value") + put("count", 1) + } + } + + request.params.name shouldBe "test-tool" + request.params.arguments shouldNotBeNull { + get("key")?.jsonPrimitive?.content shouldBe "value" + get("count")?.jsonPrimitive?.int shouldBe 1 + } + } + + @Test + fun `buildListToolsRequest should build with cursor`() { + val request = buildListToolsRequest { + cursor = "tool-cursor" + } + + request.params shouldNotBeNull { + cursor shouldBe "tool-cursor" + } + } + + @Test + fun `buildCallToolRequest should support direct arguments assignment`() { + val args = buildJsonObject { put("key", "value") } + val request = buildCallToolRequest { + name = "test-tool" + arguments(args) + } + request.params.arguments shouldBe args + } + + @Test + fun `buildCallToolRequest should throw if name is missing`() { + shouldThrow { + buildCallToolRequest { } + } + } + + @Test + fun `buildListToolsRequest should build without params if empty`() { + val request = buildListToolsRequest { } + request.params shouldBe null + } +} From 29d34b8e4864245c63a151761666bcbfdb4d5f5a Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Sat, 10 Jan 2026 16:51:15 +0200 Subject: [PATCH 112/449] test: Refactor test utilities, fix conformance (#476) # Refactor test utilities, fix conformance - Add `test-utils` module to streamline test cases - Rename module `kotlin-sdk-test` -> `integration-test` - Move test utilities for managing processes, ports, and TypeScript execution logic - Make conformance tests work by specifying conformance package version (0.1.8) ## Motivation and Context Reuse test infrastructure code, resurrect conformance tests ## How Has This Been Tested? CI, locally ## Breaking Changes No ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [x] Tests 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 --- .github/workflows/codeql.yml | 3 +- build.gradle.kts | 2 +- conformance-test/build.gradle.kts | 1 + .../kotlin/sdk/conformance/ConformanceTest.kt | 8 +- .../build.gradle.kts | 4 +- .../kotlin/sdk/client/ClientTest.kt | 0 .../kotlin/sdk/client/OldSchemaClientTest.kt | 0 .../sdk/client/OldSchemaSseTransportTest.kt | 0 .../kotlin/sdk/client/SseTransportTest.kt | 0 .../sdk/client/WebSocketTransportTest.kt | 0 .../sdk/integration/InMemoryTransportTest.kt | 0 .../OldSchemaInMemoryTransportTest.kt | 0 .../kotlin/sdk/shared/BaseTransportTest.kt | 0 .../kotlin/sdk/shared/InMemoryTransport.kt | 0 .../sdk/shared/OldSchemaBaseTransportTest.kt | 0 .../sdk/shared/OldSchemaInMemoryTransport.kt | 0 .../sdk/client/StdioClientTransportTest.kt | 2 + .../kotlin/AbstractPromptIntegrationTest.kt | 0 .../kotlin/AbstractResourceIntegrationTest.kt | 0 .../kotlin/AbstractToolIntegrationTest.kt | 0 .../sdk/integration/kotlin/KotlinTestBase.kt | 2 +- .../OldSchemaAbstractPromptIntegrationTest.kt | 0 ...ldSchemaAbstractResourceIntegrationTest.kt | 0 .../OldSchemaAbstractToolIntegrationTest.kt | 0 .../kotlin/OldSchemaKotlinTestBase.kt | 2 +- .../sse/OldSchemaPromptIntegrationTestSse.kt | 0 .../sse/OldSchemaResourceIntegrationTestSse | 0 .../sse/OldSchemaToolIntegrationTestSse.kt | 0 .../kotlin/sse/PromptIntegrationTestSse.kt | 0 .../kotlin/sse/ResourceIntegrationTestSse.kt | 0 .../kotlin/sse/ToolIntegrationTestSse.kt | 0 .../OldSchemaPromptIntegrationTestStdio.kt | 0 .../OldSchemaResourceIntegrationTestStdio.kt | 0 .../OldSchemaToolIntegrationTestStdio.kt | 0 .../stdio/PromptIntegrationTestStdio.kt | 0 .../stdio/ResourceIntegrationTestStdio.kt | 0 .../kotlin/stdio/ToolIntegrationTestStdio.kt | 0 .../OldSchemaWebSocketIntegrationTest.kt | 0 .../websocket/WebSocketIntegrationTest.kt | 0 .../sse/AbstractSseIntegrationTest.kt | 0 .../OldSchemaAbstractSseIntegrationTest.kt | 0 .../sse/OldSchemaSseIntegrationTest.kt | 0 .../sdk/integration/sse/SseIntegrationTest.kt | 0 .../AbstractKotlinClientTsServerTest.kt | 2 +- .../AbstractTsClientKotlinServerTest.kt | 2 +- ...dSchemaAbstractKotlinClientTsServerTest.kt | 0 .../sdk/integration/typescript/TsTestBase.kt | 2 +- .../KotlinClientTsServerEdgeCasesTestSse.kt | 2 +- .../sse/KotlinClientTsServerTestSse.kt | 2 +- .../sse/KotlinServerForTsClientSse.kt | 0 ...emaKotlinClientTsServerEdgeCasesTestSse.kt | 2 +- .../OldSchemaKotlinClientTsServerTestSse.kt | 2 +- .../OldSchemaKotlinServerForTsClientSse.kt | 0 .../sse/TsClientKotlinServerTestSse.kt | 2 +- .../typescript/sse/TsEdgeCasesTestSse.kt | 2 +- .../integration/typescript/sse/myClient.ts | 0 .../typescript/sse/simpleStreamableHttp.ts | 0 .../KotlinClientTsServerEdgeCasesTestStdio.kt | 2 +- .../stdio/KotlinClientTsServerTestStdio.kt | 2 +- ...aKotlinClientTsServerEdgeCasesTestStdio.kt | 2 +- .../OldSchemaKotlinClientTsServerTestStdio.kt | 2 +- .../stdio/TsClientKotlinServerTestStdio.kt | 2 +- .../typescript/stdio/TsEdgeCasesTestStdio.kt | 2 +- .../integration/typescript/stdio/myClient.ts | 0 .../typescript/stdio/simpleStdio.ts | 4 +- .../sdk/server/AbstractServerFeaturesTest.kt | 0 .../OldSchemaAbstractServerFeaturesTest.kt | 0 .../server/OldSchemaServerInstructionsTest.kt | 0 .../sdk/server/OldSchemaServerPromptsTest.kt | 0 .../server/OldSchemaServerResourcesTest.kt | 0 .../sdk/server/OldSchemaServerToolsTest.kt | 0 .../sdk/server/ServerInstructionsTest.kt | 0 .../server/ServerPromptsNotificationTest.kt | 0 .../kotlin/sdk/server/ServerPromptsTest.kt | 0 ...erverResourcesNotificationSubscribeTest.kt | 0 .../server/ServerResourcesNotificationTest.kt | 0 .../kotlin/sdk/server/ServerResourcesTest.kt | 0 .../sdk/server/ServerToolsNotificationTest.kt | 0 .../kotlin/sdk/server/ServerToolsTest.kt | 0 .../jvmTest/resources/simplelogger.properties | 0 settings.gradle.kts | 3 +- test-utils/build.gradle.kts | 22 ++++ .../kotlin/test}/utils/DisabledOnCI.kt | 4 +- .../kotlin/test}/utils/Retry.kt | 9 +- .../kotlin/test/utils/TypeScriptRunner.kt | 62 ++++++++++ .../kotlin/test/utils/platform.kt | 3 + .../kotlin/test/utils}/processBuilders.kt | 8 +- .../kotlin/test/utils/processUtils.kt | 113 ++++++++++++++++++ 88 files changed, 243 insertions(+), 39 deletions(-) rename {kotlin-sdk-test => integration-test}/build.gradle.kts (84%) rename {kotlin-sdk-test => integration-test}/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaClientTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaSseTransportTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketTransportTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/InMemoryTransportTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/OldSchemaInMemoryTransportTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/BaseTransportTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/InMemoryTransport.kt (100%) rename {kotlin-sdk-test => integration-test}/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaBaseTransportTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaInMemoryTransport.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt (96%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/KotlinTestBase.kt (98%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractPromptIntegrationTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractResourceIntegrationTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractToolIntegrationTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaKotlinTestBase.kt (98%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaPromptIntegrationTestSse.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaResourceIntegrationTestSse (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaToolIntegrationTestSse.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/PromptIntegrationTestSse.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/ResourceIntegrationTestSse.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/ToolIntegrationTestSse.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaPromptIntegrationTestStdio.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaResourceIntegrationTestStdio.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaToolIntegrationTestStdio.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/PromptIntegrationTestStdio.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/ResourceIntegrationTestStdio.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/ToolIntegrationTestStdio.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/OldSchemaWebSocketIntegrationTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/WebSocketIntegrationTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/AbstractSseIntegrationTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaAbstractSseIntegrationTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaSseIntegrationTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/SseIntegrationTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractKotlinClientTsServerTest.kt (97%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractTsClientKotlinServerTest.kt (97%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/OldSchemaAbstractKotlinClientTsServerTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TsTestBase.kt (99%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerEdgeCasesTestSse.kt (99%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerTestSse.kt (95%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerEdgeCasesTestSse.kt (99%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerTestSse.kt (95%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinServerForTsClientSse.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsClientKotlinServerTestSse.kt (95%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsEdgeCasesTestSse.kt (99%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/myClient.ts (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/simpleStreamableHttp.ts (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt (98%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerTestStdio.kt (92%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerEdgeCasesTestStdio.kt (99%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerTestStdio.kt (92%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/TsClientKotlinServerTestStdio.kt (86%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/TsEdgeCasesTestStdio.kt (98%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/myClient.ts (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/simpleStdio.ts (97%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/AbstractServerFeaturesTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaAbstractServerFeaturesTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerInstructionsTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerPromptsTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerResourcesTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerToolsTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerInstructionsTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsNotificationTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationSubscribeTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsNotificationTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsTest.kt (100%) rename {kotlin-sdk-test => integration-test}/src/jvmTest/resources/simplelogger.properties (100%) create mode 100644 test-utils/build.gradle.kts rename {kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration => test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test}/utils/DisabledOnCI.kt (59%) rename {kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration => test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test}/utils/Retry.kt (91%) create mode 100644 test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/TypeScriptRunner.kt create mode 100644 test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/platform.kt rename {kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client => test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils}/processBuilders.kt (51%) create mode 100644 test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/processUtils.kt diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 0daaf5f..fba2019 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -69,7 +69,8 @@ jobs: :kotlin-sdk-client:compileKotlinJvm \ :kotlin-sdk-server:compileKotlinJvm \ :kotlin-sdk:compileKotlinJvm \ - :kotlin-sdk-test:compileKotlinJvm \ + :integration-test:compileKotlinJvm \ + :conformance-test:testClasses \ -Pkotlin.incremental=false \ --no-daemon --stacktrace --rerun-tasks diff --git a/build.gradle.kts b/build.gradle.kts index f7f3ef4..bcdf96f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,7 +12,7 @@ dependencies { kover(project(":kotlin-sdk-core")) kover(project(":kotlin-sdk-client")) kover(project(":kotlin-sdk-server")) - kover(project(":kotlin-sdk-test")) + "kover"(project(":integration-test")) } subprojects { diff --git a/conformance-test/build.gradle.kts b/conformance-test/build.gradle.kts index 66e57d1..04073cb 100644 --- a/conformance-test/build.gradle.kts +++ b/conformance-test/build.gradle.kts @@ -7,6 +7,7 @@ plugins { dependencies { testImplementation(project(":kotlin-sdk")) testImplementation(kotlin("test")) + testImplementation(project(":test-utils")) testImplementation(libs.kotlin.logging) testImplementation(libs.ktor.client.cio) testImplementation(libs.ktor.server.cio) diff --git a/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTest.kt b/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTest.kt index 4137943..b306375 100644 --- a/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTest.kt +++ b/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTest.kt @@ -17,6 +17,8 @@ import kotlin.properties.Delegates private val logger = KotlinLogging.logger {} +private const val CONFORMANCE_VERSION = "0.1.8" + enum class TransportType { SSE, WEBSOCKET, @@ -215,7 +217,7 @@ class ConformanceTest { val serverUrl = "http://127.0.0.1:$serverPort/mcp" ProcessBuilder( "npx", - "@modelcontextprotocol/conformance", + "@modelcontextprotocol/conformance@$CONFORMANCE_VERSION", "server", "--url", serverUrl, @@ -230,7 +232,7 @@ class ConformanceTest { val serverUrl = "ws://127.0.0.1:$serverPort/ws" ProcessBuilder( "npx", - "@modelcontextprotocol/conformance", + "@modelcontextprotocol/conformance@$CONFORMANCE_VERSION", "server", "--url", serverUrl, @@ -274,7 +276,7 @@ class ConformanceTest { val processBuilder = ProcessBuilder( "npx", - "@modelcontextprotocol/conformance", + "@modelcontextprotocol/conformance@$CONFORMANCE_VERSION", "client", "--command", clientCommand.joinToString(" "), diff --git a/kotlin-sdk-test/build.gradle.kts b/integration-test/build.gradle.kts similarity index 84% rename from kotlin-sdk-test/build.gradle.kts rename to integration-test/build.gradle.kts index fce90be..d5b8ef8 100644 --- a/kotlin-sdk-test/build.gradle.kts +++ b/integration-test/build.gradle.kts @@ -25,9 +25,7 @@ kotlin { } jvmTest { dependencies { - implementation(libs.awaitility) - implementation(libs.junit.jupiter.params) - runtimeOnly(libs.slf4j.simple) + implementation(project(":test-utils")) } } } diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt similarity index 100% rename from kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt rename to integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaClientTest.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaClientTest.kt similarity index 100% rename from kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaClientTest.kt rename to integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaClientTest.kt diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaSseTransportTest.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaSseTransportTest.kt similarity index 100% rename from kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaSseTransportTest.kt rename to integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaSseTransportTest.kt diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt similarity index 100% rename from kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt rename to integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketTransportTest.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketTransportTest.kt similarity index 100% rename from kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketTransportTest.kt rename to integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketTransportTest.kt diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/InMemoryTransportTest.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/InMemoryTransportTest.kt similarity index 100% rename from kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/InMemoryTransportTest.kt rename to integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/InMemoryTransportTest.kt diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/OldSchemaInMemoryTransportTest.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/OldSchemaInMemoryTransportTest.kt similarity index 100% rename from kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/OldSchemaInMemoryTransportTest.kt rename to integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/OldSchemaInMemoryTransportTest.kt diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/BaseTransportTest.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/BaseTransportTest.kt similarity index 100% rename from kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/BaseTransportTest.kt rename to integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/BaseTransportTest.kt diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/InMemoryTransport.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/InMemoryTransport.kt similarity index 100% rename from kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/InMemoryTransport.kt rename to integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/InMemoryTransport.kt diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaBaseTransportTest.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaBaseTransportTest.kt similarity index 100% rename from kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaBaseTransportTest.kt rename to integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaBaseTransportTest.kt diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaInMemoryTransport.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaInMemoryTransport.kt similarity index 100% rename from kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaInMemoryTransport.kt rename to integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaInMemoryTransport.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt similarity index 96% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt index ecd21f3..adea76b 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt @@ -3,6 +3,8 @@ package io.modelcontextprotocol.kotlin.sdk.client import io.modelcontextprotocol.kotlin.sdk.shared.BaseTransportTest import io.modelcontextprotocol.kotlin.sdk.types.Implementation import io.modelcontextprotocol.kotlin.sdk.types.McpException +import io.modelcontextprotocol.kotlin.test.utils.createSleepyProcessBuilder +import io.modelcontextprotocol.kotlin.test.utils.createTeeProcessBuilder import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/KotlinTestBase.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/KotlinTestBase.kt similarity index 98% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/KotlinTestBase.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/KotlinTestBase.kt index e409ec6..43b1117 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/KotlinTestBase.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/KotlinTestBase.kt @@ -10,13 +10,13 @@ import io.ktor.server.routing.routing import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.client.SseClientTransport import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport -import io.modelcontextprotocol.kotlin.sdk.integration.utils.Retry import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport import io.modelcontextprotocol.kotlin.sdk.server.mcp import io.modelcontextprotocol.kotlin.sdk.types.Implementation import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.test.utils.Retry import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeout import kotlinx.io.Sink diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractPromptIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractPromptIntegrationTest.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractPromptIntegrationTest.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractPromptIntegrationTest.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractResourceIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractResourceIntegrationTest.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractResourceIntegrationTest.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractResourceIntegrationTest.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractToolIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractToolIntegrationTest.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractToolIntegrationTest.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractToolIntegrationTest.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaKotlinTestBase.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaKotlinTestBase.kt similarity index 98% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaKotlinTestBase.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaKotlinTestBase.kt index c781fdf..241172e 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaKotlinTestBase.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaKotlinTestBase.kt @@ -11,11 +11,11 @@ import io.modelcontextprotocol.kotlin.sdk.Implementation import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.client.SseClientTransport import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport -import io.modelcontextprotocol.kotlin.sdk.integration.utils.Retry import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport import io.modelcontextprotocol.kotlin.sdk.server.mcp +import io.modelcontextprotocol.kotlin.test.utils.Retry import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeout import kotlinx.io.Sink diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaPromptIntegrationTestSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaPromptIntegrationTestSse.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaPromptIntegrationTestSse.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaPromptIntegrationTestSse.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaResourceIntegrationTestSse b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaResourceIntegrationTestSse similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaResourceIntegrationTestSse rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaResourceIntegrationTestSse diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaToolIntegrationTestSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaToolIntegrationTestSse.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaToolIntegrationTestSse.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaToolIntegrationTestSse.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/PromptIntegrationTestSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/PromptIntegrationTestSse.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/PromptIntegrationTestSse.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/PromptIntegrationTestSse.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/ResourceIntegrationTestSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/ResourceIntegrationTestSse.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/ResourceIntegrationTestSse.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/ResourceIntegrationTestSse.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/ToolIntegrationTestSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/ToolIntegrationTestSse.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/ToolIntegrationTestSse.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/ToolIntegrationTestSse.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaPromptIntegrationTestStdio.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaPromptIntegrationTestStdio.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaPromptIntegrationTestStdio.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaPromptIntegrationTestStdio.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaResourceIntegrationTestStdio.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaResourceIntegrationTestStdio.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaResourceIntegrationTestStdio.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaResourceIntegrationTestStdio.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaToolIntegrationTestStdio.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaToolIntegrationTestStdio.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaToolIntegrationTestStdio.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaToolIntegrationTestStdio.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/PromptIntegrationTestStdio.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/PromptIntegrationTestStdio.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/PromptIntegrationTestStdio.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/PromptIntegrationTestStdio.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/ResourceIntegrationTestStdio.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/ResourceIntegrationTestStdio.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/ResourceIntegrationTestStdio.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/ResourceIntegrationTestStdio.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/ToolIntegrationTestStdio.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/ToolIntegrationTestStdio.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/ToolIntegrationTestStdio.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/ToolIntegrationTestStdio.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/OldSchemaWebSocketIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/OldSchemaWebSocketIntegrationTest.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/OldSchemaWebSocketIntegrationTest.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/OldSchemaWebSocketIntegrationTest.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/WebSocketIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/WebSocketIntegrationTest.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/WebSocketIntegrationTest.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/WebSocketIntegrationTest.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/AbstractSseIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/AbstractSseIntegrationTest.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/AbstractSseIntegrationTest.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/AbstractSseIntegrationTest.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaAbstractSseIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaAbstractSseIntegrationTest.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaAbstractSseIntegrationTest.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaAbstractSseIntegrationTest.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaSseIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaSseIntegrationTest.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaSseIntegrationTest.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaSseIntegrationTest.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/SseIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/SseIntegrationTest.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/SseIntegrationTest.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/SseIntegrationTest.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractKotlinClientTsServerTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractKotlinClientTsServerTest.kt similarity index 97% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractKotlinClientTsServerTest.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractKotlinClientTsServerTest.kt index 02a4cda..37f7fcb 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractKotlinClientTsServerTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractKotlinClientTsServerTest.kt @@ -1,8 +1,8 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.integration.utils.DisabledOnCI import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.test.utils.DisabledOnCI import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Test diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractTsClientKotlinServerTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractTsClientKotlinServerTest.kt similarity index 97% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractTsClientKotlinServerTest.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractTsClientKotlinServerTest.kt index 2b77ed5..90088aa 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractTsClientKotlinServerTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractTsClientKotlinServerTest.kt @@ -1,6 +1,6 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript -import io.modelcontextprotocol.kotlin.sdk.integration.utils.DisabledOnCI +import io.modelcontextprotocol.kotlin.test.utils.DisabledOnCI import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Test import org.junit.jupiter.api.Timeout diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/OldSchemaAbstractKotlinClientTsServerTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/OldSchemaAbstractKotlinClientTsServerTest.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/OldSchemaAbstractKotlinClientTsServerTest.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/OldSchemaAbstractKotlinClientTsServerTest.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TsTestBase.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TsTestBase.kt similarity index 99% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TsTestBase.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TsTestBase.kt index 22af65a..af3c525 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TsTestBase.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TsTestBase.kt @@ -7,10 +7,10 @@ import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport import io.modelcontextprotocol.kotlin.sdk.client.mcpStreamableHttp import io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse.KotlinServerForTsClient -import io.modelcontextprotocol.kotlin.sdk.integration.utils.Retry import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.test.utils.Retry import kotlinx.coroutines.withTimeout import kotlinx.io.Sink import kotlinx.io.Source diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerEdgeCasesTestSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerEdgeCasesTestSse.kt similarity index 99% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerEdgeCasesTestSse.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerEdgeCasesTestSse.kt index ff00bf9..c3832b6 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerEdgeCasesTestSse.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerEdgeCasesTestSse.kt @@ -3,8 +3,8 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TsTestBase -import io.modelcontextprotocol.kotlin.sdk.integration.utils.DisabledOnCI import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.test.utils.DisabledOnCI import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerTestSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerTestSse.kt similarity index 95% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerTestSse.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerTestSse.kt index b9a7b5d..d38d765 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerTestSse.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerTestSse.kt @@ -3,7 +3,7 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.integration.typescript.AbstractKotlinClientTsServerTest import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind -import io.modelcontextprotocol.kotlin.sdk.integration.utils.DisabledOnCI +import io.modelcontextprotocol.kotlin.test.utils.DisabledOnCI import kotlinx.coroutines.withTimeout import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerEdgeCasesTestSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerEdgeCasesTestSse.kt similarity index 99% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerEdgeCasesTestSse.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerEdgeCasesTestSse.kt index c2a4529..ab62b0f 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerEdgeCasesTestSse.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerEdgeCasesTestSse.kt @@ -4,7 +4,7 @@ import io.modelcontextprotocol.kotlin.sdk.TextContent import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TsTestBase -import io.modelcontextprotocol.kotlin.sdk.integration.utils.DisabledOnCI +import io.modelcontextprotocol.kotlin.test.utils.DisabledOnCI import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerTestSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerTestSse.kt similarity index 95% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerTestSse.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerTestSse.kt index e0990ee..9fb9786 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerTestSse.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerTestSse.kt @@ -3,7 +3,7 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.integration.typescript.OldSchemaAbstractKotlinClientTsServerTest import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind -import io.modelcontextprotocol.kotlin.sdk.integration.utils.DisabledOnCI +import io.modelcontextprotocol.kotlin.test.utils.DisabledOnCI import kotlinx.coroutines.withTimeout import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinServerForTsClientSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinServerForTsClientSse.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinServerForTsClientSse.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinServerForTsClientSse.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsClientKotlinServerTestSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsClientKotlinServerTestSse.kt similarity index 95% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsClientKotlinServerTestSse.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsClientKotlinServerTestSse.kt index e95db0e..87123dd 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsClientKotlinServerTestSse.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsClientKotlinServerTestSse.kt @@ -2,7 +2,7 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse import io.modelcontextprotocol.kotlin.sdk.integration.typescript.AbstractTsClientKotlinServerTest import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind -import io.modelcontextprotocol.kotlin.sdk.integration.utils.DisabledOnCI +import io.modelcontextprotocol.kotlin.test.utils.DisabledOnCI import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsEdgeCasesTestSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsEdgeCasesTestSse.kt similarity index 99% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsEdgeCasesTestSse.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsEdgeCasesTestSse.kt index 0ba11de..eb372c2 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsEdgeCasesTestSse.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsEdgeCasesTestSse.kt @@ -2,7 +2,7 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TsTestBase -import io.modelcontextprotocol.kotlin.sdk.integration.utils.DisabledOnCI +import io.modelcontextprotocol.kotlin.test.utils.DisabledOnCI import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/myClient.ts b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/myClient.ts similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/myClient.ts rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/myClient.ts diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/simpleStreamableHttp.ts b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/simpleStreamableHttp.ts similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/simpleStreamableHttp.ts rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/simpleStreamableHttp.ts diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt similarity index 98% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt index 7abe0cb..5815d0d 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt @@ -3,8 +3,8 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.stdio import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TsTestBase -import io.modelcontextprotocol.kotlin.sdk.integration.utils.DisabledOnCI import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.test.utils.DisabledOnCI import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerTestStdio.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerTestStdio.kt similarity index 92% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerTestStdio.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerTestStdio.kt index 61f23c4..6f2fea3 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerTestStdio.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerTestStdio.kt @@ -3,7 +3,7 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.stdio import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.integration.typescript.AbstractKotlinClientTsServerTest import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind -import io.modelcontextprotocol.kotlin.sdk.integration.utils.DisabledOnCI +import io.modelcontextprotocol.kotlin.test.utils.DisabledOnCI @DisabledOnCI class KotlinClientTsServerTestStdio : AbstractKotlinClientTsServerTest() { diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerEdgeCasesTestStdio.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerEdgeCasesTestStdio.kt similarity index 99% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerEdgeCasesTestStdio.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerEdgeCasesTestStdio.kt index ae2e09f..3b74993 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerEdgeCasesTestStdio.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerEdgeCasesTestStdio.kt @@ -4,7 +4,7 @@ import io.modelcontextprotocol.kotlin.sdk.TextContent import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TsTestBase -import io.modelcontextprotocol.kotlin.sdk.integration.utils.DisabledOnCI +import io.modelcontextprotocol.kotlin.test.utils.DisabledOnCI import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerTestStdio.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerTestStdio.kt similarity index 92% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerTestStdio.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerTestStdio.kt index 7daac4a..7bbad51 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerTestStdio.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerTestStdio.kt @@ -3,7 +3,7 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.stdio import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.integration.typescript.OldSchemaAbstractKotlinClientTsServerTest import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind -import io.modelcontextprotocol.kotlin.sdk.integration.utils.DisabledOnCI +import io.modelcontextprotocol.kotlin.test.utils.DisabledOnCI @DisabledOnCI class OldSchemaKotlinClientTsServerTestStdio : OldSchemaAbstractKotlinClientTsServerTest() { diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/TsClientKotlinServerTestStdio.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/TsClientKotlinServerTestStdio.kt similarity index 86% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/TsClientKotlinServerTestStdio.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/TsClientKotlinServerTestStdio.kt index 05f55bc..0751c2e 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/TsClientKotlinServerTestStdio.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/TsClientKotlinServerTestStdio.kt @@ -2,7 +2,7 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.stdio import io.modelcontextprotocol.kotlin.sdk.integration.typescript.AbstractTsClientKotlinServerTest import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind -import io.modelcontextprotocol.kotlin.sdk.integration.utils.DisabledOnCI +import io.modelcontextprotocol.kotlin.test.utils.DisabledOnCI @DisabledOnCI class TsClientKotlinServerTestStdio : AbstractTsClientKotlinServerTest() { diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/TsEdgeCasesTestStdio.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/TsEdgeCasesTestStdio.kt similarity index 98% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/TsEdgeCasesTestStdio.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/TsEdgeCasesTestStdio.kt index cd1fa4d..9b51194 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/TsEdgeCasesTestStdio.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/TsEdgeCasesTestStdio.kt @@ -2,7 +2,7 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.stdio import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TsTestBase -import io.modelcontextprotocol.kotlin.sdk.integration.utils.DisabledOnCI +import io.modelcontextprotocol.kotlin.test.utils.DisabledOnCI import org.junit.jupiter.api.Test import org.junit.jupiter.api.Timeout import org.junit.jupiter.api.condition.EnabledOnOs diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/myClient.ts b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/myClient.ts similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/myClient.ts rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/myClient.ts diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/simpleStdio.ts b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/simpleStdio.ts similarity index 97% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/simpleStdio.ts rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/simpleStdio.ts index 2986309..8e5fc79 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/simpleStdio.ts +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/simpleStdio.ts @@ -1,7 +1,7 @@ // @ts-nocheck -import { z } from 'zod'; +import {z} from 'zod'; import path from 'node:path'; -import { pathToFileURL } from 'node:url'; +import {pathToFileURL} from 'node:url'; const SDK_DIR = process.env.TYPESCRIPT_SDK_DIR; if (!SDK_DIR) { diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/AbstractServerFeaturesTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/AbstractServerFeaturesTest.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/AbstractServerFeaturesTest.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/AbstractServerFeaturesTest.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaAbstractServerFeaturesTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaAbstractServerFeaturesTest.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaAbstractServerFeaturesTest.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaAbstractServerFeaturesTest.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerInstructionsTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerInstructionsTest.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerInstructionsTest.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerInstructionsTest.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerPromptsTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerPromptsTest.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerPromptsTest.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerPromptsTest.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerResourcesTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerResourcesTest.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerResourcesTest.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerResourcesTest.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerToolsTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerToolsTest.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerToolsTest.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerToolsTest.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerInstructionsTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerInstructionsTest.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerInstructionsTest.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerInstructionsTest.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsNotificationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsNotificationTest.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsNotificationTest.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsNotificationTest.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsTest.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsTest.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsTest.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationSubscribeTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationSubscribeTest.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationSubscribeTest.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationSubscribeTest.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationTest.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationTest.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationTest.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesTest.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesTest.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesTest.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsNotificationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsNotificationTest.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsNotificationTest.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsNotificationTest.kt diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsTest.kt similarity index 100% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsTest.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsTest.kt diff --git a/kotlin-sdk-test/src/jvmTest/resources/simplelogger.properties b/integration-test/src/jvmTest/resources/simplelogger.properties similarity index 100% rename from kotlin-sdk-test/src/jvmTest/resources/simplelogger.properties rename to integration-test/src/jvmTest/resources/simplelogger.properties diff --git a/settings.gradle.kts b/settings.gradle.kts index d7ec54f..9a646a1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -18,10 +18,11 @@ dependencyResolutionManagement { } include( + ":test-utils", ":kotlin-sdk-core", ":kotlin-sdk-client", ":kotlin-sdk-server", ":kotlin-sdk", - ":kotlin-sdk-test", + ":integration-test", ":conformance-test", ) diff --git a/test-utils/build.gradle.kts b/test-utils/build.gradle.kts new file mode 100644 index 0000000..ffffc3e --- /dev/null +++ b/test-utils/build.gradle.kts @@ -0,0 +1,22 @@ +plugins { + kotlin("jvm") +} + +kotlin { + explicitApi() + + dependencies { + api(kotlin("test")) + api(libs.kotest.assertions.core) + api(libs.kotest.assertions.json) + api(libs.kotlinx.coroutines.test) + api(libs.mockk) + api(libs.awaitility) + api(libs.junit.jupiter.params) + runtimeOnly(libs.slf4j.simple) + } + + tasks.withType { + useJUnitPlatform() + } +} diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/utils/DisabledOnCI.kt b/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/DisabledOnCI.kt similarity index 59% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/utils/DisabledOnCI.kt rename to test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/DisabledOnCI.kt index 087d201..5ff456b 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/utils/DisabledOnCI.kt +++ b/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/DisabledOnCI.kt @@ -1,6 +1,6 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.utils +package io.modelcontextprotocol.kotlin.test.utils import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable @DisabledIfEnvironmentVariable(named = "CI", matches = "true") -annotation class DisabledOnCI +public annotation class DisabledOnCI diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/utils/Retry.kt b/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/Retry.kt similarity index 91% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/utils/Retry.kt rename to test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/Retry.kt index 32f2053..e173f3f 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/utils/Retry.kt +++ b/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/Retry.kt @@ -1,4 +1,4 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.utils +package io.modelcontextprotocol.kotlin.test.utils import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.ExtensionContext @@ -7,15 +7,16 @@ import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation import org.junit.jupiter.api.extension.ReflectiveInvocationContext import org.opentest4j.TestAbortedException import java.lang.reflect.AnnotatedElement +import java.lang.reflect.InvocationTargetException import java.lang.reflect.Method import java.util.Optional @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) @ExtendWith(RetryExtension::class) -annotation class Retry(val times: Int = 3, val delayMs: Long = 1000) +public annotation class Retry(val times: Int = 3, val delayMs: Long = 1000) -class RetryExtension : InvocationInterceptor { +public class RetryExtension : InvocationInterceptor { override fun interceptTestMethod( invocation: Invocation, invocationContext: ReflectiveInvocationContext, @@ -65,7 +66,7 @@ class RetryExtension : InvocationInterceptor { return } catch (t: Throwable) { if (t is TestAbortedException) throw t - lastError = if (t is java.lang.reflect.InvocationTargetException) t.targetException ?: t else t + lastError = if (t is InvocationTargetException) t.targetException ?: t else t if (attempt == maxAttempts) { println( "[Retry] Giving up after $attempt attempts for ${ diff --git a/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/TypeScriptRunner.kt b/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/TypeScriptRunner.kt new file mode 100644 index 0000000..f68118e --- /dev/null +++ b/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/TypeScriptRunner.kt @@ -0,0 +1,62 @@ +package io.modelcontextprotocol.kotlin.test.utils + +import java.io.File + +/** + * Component for running arbitrary TypeScript files using npx tsx. + */ +public object TypeScriptRunner { + + /** + * Runs a TypeScript file using npx tsx. + * + * @param typescriptDir The working directory for the process + * @param scriptPath The path to the TypeScript file relative to [typescriptDir] or absolute + * @param env Environment variables for the process + * @param redirectErrorStream Whether to redirect error stream to output stream + * @param logPrefix Prefix for log lines + * @return The started Process + */ + public fun run( + typescriptDir: File, + scriptPath: String, + env: Map = emptyMap(), + redirectErrorStream: Boolean = false, + logPrefix: String = "TS-RUNNER", + ): Process { + val pb = ProcessBuilder("npx", "tsx", scriptPath) + pb.directory(typescriptDir) + env.forEach { (k, v) -> pb.environment()[k] = v } + pb.redirectErrorStream(redirectErrorStream) + + val proc = pb.start() + + if (redirectErrorStream) { + createProcessOutputReader(proc, logPrefix).start() + } else { + createProcessErrorReader(proc, logPrefix).start() + } + + return proc + } + + /** + * Installs npm dependencies in the specified TypeScript directory. + * + * @param typescriptDir The directory containing package.json + * @throws RuntimeException if npm install fails + */ + public fun installDependencies(typescriptDir: File) { + require(typescriptDir.isDirectory()) { "Type script directory does not exist" } + println("Installing TypeScript dependencies in ${typescriptDir.absolutePath}") + val pb = ProcessBuilder("npm", "install") + pb.directory(typescriptDir) + pb.redirectErrorStream(true) + val proc = pb.start() + proc.inputStream.bufferedReader().useLines { lines -> + lines.forEach { println("[NPM] $it") } + } + val exitCode = proc.waitFor() + require(exitCode == 0) { "npm install failed with exit code $exitCode" } + } +} diff --git a/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/platform.kt b/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/platform.kt new file mode 100644 index 0000000..fcdd1f4 --- /dev/null +++ b/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/platform.kt @@ -0,0 +1,3 @@ +package io.modelcontextprotocol.kotlin.test.utils + +public val isWindows: Boolean = System.getProperty("os.name").lowercase().contains("windows") diff --git a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/processBuilders.kt b/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/processBuilders.kt similarity index 51% rename from kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/processBuilders.kt rename to test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/processBuilders.kt index 405f2b3..3ded6a3 100644 --- a/kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/processBuilders.kt +++ b/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/processBuilders.kt @@ -1,14 +1,12 @@ -package io.modelcontextprotocol.kotlin.sdk.client +package io.modelcontextprotocol.kotlin.test.utils -private val isWindows = System.getProperty("os.name").lowercase().contains("win") - -fun createTeeProcessBuilder(): ProcessBuilder = if (isWindows) { +public fun createTeeProcessBuilder(): ProcessBuilder = if (isWindows) { ProcessBuilder("cmd", "/c", "findstr", "^") } else { ProcessBuilder("tee") } -fun createSleepyProcessBuilder(): ProcessBuilder = if (isWindows) { +public fun createSleepyProcessBuilder(): ProcessBuilder = if (isWindows) { ProcessBuilder("cmd", "/c", "ping -n 2 127.0.0.1 > nul && echo simulated error 1>&2 && exit 1") } else { ProcessBuilder("sh", "-c", "sleep 1 && echo 'simulated error' >&2 && exit 1") diff --git a/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/processUtils.kt b/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/processUtils.kt new file mode 100644 index 0000000..43e0a6c --- /dev/null +++ b/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/processUtils.kt @@ -0,0 +1,113 @@ +package io.modelcontextprotocol.kotlin.test.utils + +import org.awaitility.kotlin.await +import java.net.ServerSocket +import java.net.Socket +import java.util.concurrent.TimeUnit + +/** + * Kills any process currently using the specified port. + * + * @param port The port number to free up + */ +public fun killProcessOnPort(port: Int) { + val killCommand = if (isWindows) { + listOf( + "cmd.exe", + "/c", + "netstat -ano | findstr :$port | for /f \"tokens=5\" %a in ('more') do taskkill /F /PID %a 2>nul || echo No process found", + ) + } else { + listOf("bash", "-c", "lsof -ti:$port | xargs kill -9 2>/dev/null || true") + } + ProcessBuilder(killCommand).start().waitFor() +} + +/** + * Waits for a port to become available (indicating server is ready). + * + * @param host The hostname to check (default: "localhost") + * @param port The port number to check + * @param timeoutSeconds Maximum time to wait in seconds (default: 10) + * @return true if port became available, false if timeout occurred + */ +public fun waitForPort(host: String = "localhost", port: Int, timeoutSeconds: Long = 10): Boolean = try { + await.atMost(timeoutSeconds, TimeUnit.SECONDS) + .pollDelay(200, TimeUnit.MILLISECONDS) + .pollInterval(100, TimeUnit.MILLISECONDS) + .until { + try { + Socket(host, port).use { true } + } catch (_: Exception) { + false + } + } + true +} catch (_: Exception) { + false +} + +/** + * Stops the given process. + * + * Attempts graceful shutdown first, then forces termination if necessary. + */ +public fun stopProcess(process: Process) { + process.destroy() + if (!process.waitFor(1, TimeUnit.SECONDS)) { + process.destroyForcibly() + } +} + +/** + * Finds an available port by opening and closing a ServerSocket. + * + * @return An available port number + */ +public fun findFreePort(): Int { + ServerSocket(0).use { socket -> + return socket.localPort + } +} + +/** + * Creates a daemon thread that reads and logs process output. + * + * @param process The process to read from + * @param prefix Log prefix for output lines + * @return The created Thread (already started) + */ +public fun createProcessOutputReader(process: Process, prefix: String): Thread { + val thread = Thread { + try { + process.inputStream.bufferedReader().useLines { lines -> + lines.forEach { println("[$prefix] $it") } + } + } catch (e: Exception) { + println("Warning: Error reading process output: ${e.message}") + } + } + thread.isDaemon = true + return thread +} + +/** + * Creates a daemon thread that reads and logs process error output. + * + * @param process The process to read from + * @param prefix Log prefix for error lines + * @return The created Thread (already started) + */ +public fun createProcessErrorReader(process: Process, prefix: String): Thread { + val thread = Thread { + try { + process.errorStream.bufferedReader().useLines { lines -> + lines.forEach { println("[$prefix][err] $it") } + } + } catch (e: Exception) { + println("Warning: Error reading process error stream: ${e.message}") + } + } + thread.isDaemon = true + return thread +} From 658b370c875b06fcc5eddbeb4f7bf8d629947aa5 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Sat, 10 Jan 2026 20:46:51 +0200 Subject: [PATCH 113/449] refactor(test): add centralized TypeScript server factory for integration tests (#477) # refactor(test): add centralized TypeScript server factory for integration tests - Create TypeScriptServer class for managing TypeScript MCP servers - Add dedicated typescript/ directory with server infrastructure - Refactor TsTestBase to use TypeScriptServer for stdio transport - Extract TypeScript test tools (greet, multi-greet, prompts, resources) - Use npx tsx for direct execution (better performance than npm run) - Install TypeScript dependencies automatically in test setup The TypeScriptServer factory provides: - startSse(port): Start SSE transport server - startStdio(): Start STDIO transport server - stop(): Graceful server shutdown - installDependencies(): npm install automation ## Motivation and Context To resurrect typescript tests ## How Has This Been Tested? Locally, CI ## Breaking Changes ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [x] Test fix ## 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 ## Additional context #475 --- .github/workflows/build.yml | 14 +- .github/workflows/conformance.yml | 11 +- conformance-test/build.gradle.kts | 2 +- .../kotlin/sdk/conformance/ConformanceTest.kt | 4 +- .../AbstractKotlinClientTsServerTest.kt | 8 +- .../sdk/integration/typescript/TsTestBase.kt | 312 +-- .../typescript/TypeScriptServer.kt | 121 ++ .../KotlinClientTsServerEdgeCasesTestSse.kt | 24 +- .../sse/KotlinClientTsServerTestSse.kt | 29 +- ...emaKotlinClientTsServerEdgeCasesTestSse.kt | 24 +- .../OldSchemaKotlinClientTsServerTestSse.kt | 28 +- .../sse/TsClientKotlinServerTestSse.kt | 2 + .../typescript/sse/TsEdgeCasesTestSse.kt | 6 +- .../integration/typescript/sse/myClient.ts | 4 +- .../typescript/sse/simpleStreamableHttp.ts | 701 ------- .../KotlinClientTsServerEdgeCasesTestStdio.kt | 2 - .../stdio/KotlinClientTsServerTestStdio.kt | 2 - ...aKotlinClientTsServerEdgeCasesTestStdio.kt | 2 - .../OldSchemaKotlinClientTsServerTestStdio.kt | 2 - .../integration/typescript/stdio/myClient.ts | 2 +- .../typescript/stdio/simpleStdio.ts | 74 - .../src/jvmTest/typescript/package-lock.json | 1842 +++++++++++++++++ .../src/jvmTest/typescript/package.json | 26 + .../typescript/server/server-common.ts | 402 ++++ .../jvmTest/typescript/server/sse-server.ts | 300 +++ .../jvmTest/typescript/server/stdio-server.ts | 20 + .../kotlin/test/utils/TypeScriptRunner.kt | 5 +- 27 files changed, 2849 insertions(+), 1120 deletions(-) create mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TypeScriptServer.kt delete mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/simpleStreamableHttp.ts delete mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/simpleStdio.ts create mode 100644 integration-test/src/jvmTest/typescript/package-lock.json create mode 100644 integration-test/src/jvmTest/typescript/package.json create mode 100644 integration-test/src/jvmTest/typescript/server/server-common.ts create mode 100644 integration-test/src/jvmTest/typescript/server/sse-server.ts create mode 100644 integration-test/src/jvmTest/typescript/server/stdio-server.ts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1b6c741..2d336cd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,7 +41,7 @@ jobs: - os: windows-latest job-name: "Windows Tests" - gradle-tasks: "jvmTest" + gradle-tasks: "jvmTest -x :integration-test:jvmTest" max-workers: 3 - os: macos-latest @@ -64,6 +64,11 @@ jobs: with: node-version: 'lts/*' + - name: Set up Node Packages + if: matrix.os == 'ubuntu-latest' + working-directory: integration-test/src/jvmTest/typescript + run: npm ci + - name: Setup Gradle uses: gradle/actions/setup-gradle@v5 with: @@ -75,7 +80,7 @@ jobs: caches notifications sdks - .konan/** + ../.konan/** - name: ${{ matrix.job-name }} run: |- @@ -144,6 +149,11 @@ jobs: with: add-job-summary: 'always' cache-read-only: true + gradle-home-cache-includes: | + caches + notifications + sdks + ../.konan/** - name: Download Maven Local Repository uses: actions/download-artifact@v7 diff --git a/.github/workflows/conformance.yml b/.github/workflows/conformance.yml index 4f09c1b..7660be7 100644 --- a/.github/workflows/conformance.yml +++ b/.github/workflows/conformance.yml @@ -31,19 +31,24 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v6 with: - node-version: '24.11.1' + node-version: 'lts/*' - name: Setup Gradle uses: gradle/actions/setup-gradle@v5 with: add-job-summary: 'always' - cache-read-only: true + cache-read-only: ${{ github.ref != 'refs/heads/main' }} + gradle-home-cache-includes: | + caches + notifications + sdks + ../.konan/** - name: Run Conformance Tests run: ./gradlew --no-daemon :conformance-test:test - name: Upload Conformance Results - if: ${{ !cancelled() }} + if: always() uses: actions/upload-artifact@v6 with: name: conformance-results diff --git a/conformance-test/build.gradle.kts b/conformance-test/build.gradle.kts index 04073cb..1877d09 100644 --- a/conformance-test/build.gradle.kts +++ b/conformance-test/build.gradle.kts @@ -6,8 +6,8 @@ plugins { dependencies { testImplementation(project(":kotlin-sdk")) - testImplementation(kotlin("test")) testImplementation(project(":test-utils")) + testImplementation(kotlin("test")) testImplementation(libs.kotlin.logging) testImplementation(libs.ktor.client.cio) testImplementation(libs.ktor.server.cio) diff --git a/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTest.kt b/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTest.kt index b306375..47ca2e8 100644 --- a/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTest.kt +++ b/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTest.kt @@ -1,6 +1,7 @@ package io.modelcontextprotocol.kotlin.sdk.conformance import io.github.oshai.kotlinlogging.KotlinLogging +import io.modelcontextprotocol.kotlin.test.utils.findFreePort import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.DynamicTest @@ -10,7 +11,6 @@ import java.io.BufferedReader import java.io.InputStreamReader import java.lang.management.ManagementFactory import java.net.HttpURLConnection -import java.net.ServerSocket import java.net.URI import java.util.concurrent.TimeUnit import kotlin.properties.Delegates @@ -70,8 +70,6 @@ class ConformanceTest { private const val GRACEFUL_SHUTDOWN_SECONDS = 5L private const val FORCE_SHUTDOWN_SECONDS = 2L - private fun findFreePort(): Int = ServerSocket(0).use { it.localPort } - private fun getRuntimeClasspath(): String = ManagementFactory.getRuntimeMXBean().classPath private fun getTestClasspath(): String = System.getProperty("test.classpath") ?: getRuntimeClasspath() diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractKotlinClientTsServerTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractKotlinClientTsServerTest.kt index 37f7fcb..9a384f1 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractKotlinClientTsServerTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractKotlinClientTsServerTest.kt @@ -17,7 +17,7 @@ abstract class AbstractKotlinClientTsServerTest : TsTestBase() { protected abstract suspend fun useClient(block: suspend (Client) -> T): T @Test - @Timeout(30, unit = TimeUnit.SECONDS) + @Timeout(20, unit = TimeUnit.SECONDS) fun connectsAndPings() = runBlocking(Dispatchers.IO) { useClient { client -> assertNotNull(client, "Client should be initialized") @@ -30,7 +30,7 @@ abstract class AbstractKotlinClientTsServerTest : TsTestBase() { } @Test - @Timeout(30, unit = TimeUnit.SECONDS) + @Timeout(20, unit = TimeUnit.SECONDS) fun listsTools() = runBlocking(Dispatchers.IO) { useClient { client -> val result = client.listTools() @@ -44,7 +44,7 @@ abstract class AbstractKotlinClientTsServerTest : TsTestBase() { } @Test - @Timeout(30, unit = TimeUnit.SECONDS) + @Timeout(20, unit = TimeUnit.SECONDS) fun callGreet() = runBlocking(Dispatchers.IO) { useClient { client -> val testName = "TestUser" @@ -59,7 +59,7 @@ abstract class AbstractKotlinClientTsServerTest : TsTestBase() { } @Test - @Timeout(30, unit = TimeUnit.SECONDS) + @Timeout(20, unit = TimeUnit.SECONDS) fun multipleClients() = runBlocking(Dispatchers.IO) { useClient { client1 -> useClient { client2 -> diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TsTestBase.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TsTestBase.kt index af3c525..313480f 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TsTestBase.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TsTestBase.kt @@ -11,59 +11,71 @@ import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport import io.modelcontextprotocol.kotlin.sdk.types.Implementation import io.modelcontextprotocol.kotlin.test.utils.Retry +import io.modelcontextprotocol.kotlin.test.utils.TypeScriptRunner +import io.modelcontextprotocol.kotlin.test.utils.isWindows import kotlinx.coroutines.withTimeout import kotlinx.io.Sink import kotlinx.io.Source import kotlinx.io.asSink import kotlinx.io.asSource import kotlinx.io.buffered -import org.awaitility.kotlin.await import org.junit.jupiter.api.BeforeAll import java.io.BufferedReader import java.io.File import java.io.InputStreamReader -import java.net.ServerSocket -import java.net.Socket import java.util.concurrent.TimeUnit import kotlin.io.path.createTempDirectory import kotlin.time.Duration.Companion.seconds -enum class TransportKind { SSE, STDIO, DEFAULT } +enum class TransportKind { SSE, STDIO } @Retry(times = 3) abstract class TsTestBase { - protected open val transportKind: TransportKind = TransportKind.DEFAULT + protected abstract val transportKind: TransportKind - protected val projectRoot: File get() = File(System.getProperty("user.dir")) - protected val tsClientDir: File - get() { - val base = File( + companion object { + @JvmStatic + protected val projectRoot: File get() = File(System.getProperty("user.dir")) + + @JvmStatic + protected val tsClientDir: File + get() = File(projectRoot, "src/jvmTest/typescript") + + @JvmStatic + protected val tsSourceDir: File + get() = File( projectRoot, "src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript", ) - // Allow override via system property for CI: -Dts.transport=stdio|sse - val fromProp = System.getProperty("ts.transport")?.lowercase() - val overrideSubDir = when (fromProp) { - "stdio" -> "stdio" - "sse" -> "sse" - else -> null - } + @JvmStatic + private var sharedSseServer: Process? = null - val subDirName = overrideSubDir ?: when (transportKind) { - TransportKind.STDIO -> "stdio" - TransportKind.SSE -> "sse" - TransportKind.DEFAULT -> null + @JvmStatic + private var sharedSsePort: Int = 0 + + @JvmStatic + @Synchronized + protected fun getSharedSseUrl(): String { + if (sharedSseServer == null || !sharedSseServer!!.isAlive) { + sharedSsePort = io.modelcontextprotocol.kotlin.test.utils.findFreePort() + val server = TypeScriptServer(tsClientDir) + sharedSseServer = server.startSse(sharedSsePort) + println("Shared TypeScript SSE server started on port $sharedSsePort") + + Runtime.getRuntime().addShutdownHook( + Thread { + sharedSseServer?.let { + println("Stopping shared TypeScript SSE server") + io.modelcontextprotocol.kotlin.test.utils.stopProcess(it) + } + }, + ) } - if (subDirName != null) { - val sub = File(base, subDirName) - if (sub.exists()) return sub - } - return base + return "http://localhost:$sharedSsePort/mcp" } - companion object { @JvmStatic private val tempRootDir: File = createTempDirectory("typescript-sdk-").toFile().apply { deleteOnExit() } @@ -72,6 +84,7 @@ abstract class TsTestBase { @JvmStatic @BeforeAll + @Deprecated("Use setupTypeScriptSdkWithDependencies instead") fun setupTypeScriptSdk() { println("Cloning TypeScript SDK repository") @@ -87,32 +100,43 @@ abstract class TsTestBase { .redirectErrorStream(true) .start() val exitCode = process.waitFor() - if (exitCode != 0) { - throw RuntimeException("Failed to clone TypeScript SDK repository: exit code $exitCode") + require(exitCode == 0) { + "Failed to clone TypeScript SDK repository: exit code $exitCode" } } println("Installing TypeScript SDK dependencies") - executeCommand("npm install", sdkDir, allowFailure = false, timeoutSeconds = null) + val npmInstallResult = executeCommand("npm install", sdkDir, allowFailure = true, timeoutSeconds = null) + if (npmInstallResult.contains("EUNSUPPORTEDPROTOCOL") || npmInstallResult.contains("catalog:")) { + println("npm install failed due to catalog protocol. Trying pnpm...") + try { + // Try to use npx pnpm to ensure it's available + executeCommand("npx pnpm install", sdkDir, allowFailure = false, timeoutSeconds = null) + } catch (e: Exception) { + println("pnpm install failed or not found: ${e.message}") + println("Attempting to patch package.json to remove catalog: protocol and use npm") + patchPackageJson(sdkDir) + // Also patch other package.json files in the workspace if they exist + sdkDir.walk().filter { it.name == "package.json" }.forEach { patchPackageJson(it.parentFile) } + executeCommand("npm install", sdkDir, allowFailure = false, timeoutSeconds = null) + } + } } - @JvmStatic - protected fun killProcessOnPort(port: Int) { - val isWindows = System.getProperty("os.name").lowercase().contains("windows") - val killCommand = if (isWindows) { - "netstat -ano | findstr :$port | for /f \"tokens=5\" %a in ('more')" + - " do taskkill /F /PID %a 2>nul || echo No process found" - } else { - "lsof -ti:$port | xargs kill -9 2>/dev/null || true" - } - executeCommand(killCommand, File("."), allowFailure = true, timeoutSeconds = null) - } - - @JvmStatic - protected fun findFreePort(): Int { - ServerSocket(0).use { socket -> - return socket.localPort - } + @Deprecated("never patch package.json") + private fun patchPackageJson(dir: File) { + val packageJson = File(dir, "package.json") + if (!packageJson.exists()) return + var content = packageJson.readText() + // Replace "catalog:something" with "*" or a reasonable default if we knew it. + // Since we don't know the exact versions in the catalog, "*" is a risky but possible fallback for tests. + // However, a better approach is to use pnpm if available. + content = content.replace(Regex(""""catalog:[^"]*""""), "\"*\"") + // Also replace workspace: protocol if we are using npm fallback + content = content.replace(Regex(""""workspace:[^"]*""""), "\"*\"") + // And any other unsupported protocols that might be in the package.json + content = content.replace(Regex(""""link:[^"]*""""), "\"*\"") + packageJson.writeText(content) } @JvmStatic @@ -132,7 +156,6 @@ abstract class TsTestBase { throw RuntimeException("Working directory is not accessible: ${workingDir.absolutePath}") } - val isWindows = System.getProperty("os.name").lowercase().contains("windows") val processBuilder = if (isWindows) { ProcessBuilder() .command("cmd.exe", "/c", "set TYPESCRIPT_SDK_DIR=${sdkDir.absolutePath} && $command") @@ -157,11 +180,9 @@ abstract class TsTestBase { if (timeoutSeconds == null) { val exitCode = process.waitFor() - if (!allowFailure && exitCode != 0) { - throw RuntimeException( - "Command execution failed with exit code $exitCode: $command\n" + - "Working dir: ${workingDir.absolutePath}\nOutput:\n$output", - ) + require(allowFailure || exitCode == 0) { + "Command execution failed with exit code $exitCode: $command\n" + + "Working dir: ${workingDir.absolutePath}\nOutput:\n$output" } } else { process.waitFor(timeoutSeconds, TimeUnit.SECONDS) @@ -172,114 +193,24 @@ abstract class TsTestBase { } private fun waitForProcessTermination(process: Process, timeoutSeconds: Long): Boolean { - if (process.isAlive && !process.waitFor(timeoutSeconds, TimeUnit.SECONDS)) { - process.destroyForcibly() - process.waitFor(2, TimeUnit.SECONDS) - return false - } + io.modelcontextprotocol.kotlin.test.utils.stopProcess(process) return true } - private fun createProcessOutputReader(process: Process, prefix: String = "TS-SERVER"): Thread { - val outputReader = Thread { - try { - process.inputStream.bufferedReader().useLines { lines -> - for (line in lines) { - println("[$prefix] $line") - } - } - } catch (e: Exception) { - println("Warning: Error reading process output: ${e.message}") - } - } - outputReader.isDaemon = true - return outputReader - } - - private fun createProcessErrorReader(process: Process, prefix: String = "TS-SERVER"): Thread { - val errorReader = Thread { - try { - process.errorStream.bufferedReader().useLines { lines -> - for (line in lines) { - println("[$prefix][err] $line") - } - } - } catch (e: Exception) { - println("Warning: Error reading process error stream: ${e.message}") - } - } - errorReader.isDaemon = true - return errorReader - } - - protected fun waitForPort(host: String = "localhost", port: Int, timeoutSeconds: Long = 10): Boolean = try { - await.atMost(timeoutSeconds, TimeUnit.SECONDS) - .pollDelay(200, TimeUnit.MILLISECONDS) - .pollInterval(100, TimeUnit.MILLISECONDS) - .until { - try { - Socket(host, port).use { true } - } catch (_: Exception) { - false - } - } - true - } catch (_: Exception) { - false - } + protected fun waitForPort(host: String = "localhost", port: Int, timeoutSeconds: Long = 10): Boolean = + io.modelcontextprotocol.kotlin.test.utils.waitForPort(host, port, timeoutSeconds) protected fun executeCommandAllowingFailure(command: String, workingDir: File, timeoutSeconds: Long = 20): String = executeCommand(command, workingDir, allowFailure = true, timeoutSeconds = timeoutSeconds) protected fun startTypeScriptServer(port: Int): Process { - killProcessOnPort(port) - - if (!sdkDir.exists() || !sdkDir.isDirectory) { - throw IllegalStateException( - "TypeScript SDK directory does not exist or is not accessible: ${sdkDir.absolutePath}", - ) - } - - val isWindows = System.getProperty("os.name").lowercase().contains("windows") - val localServerPath = File(tsClientDir, "simpleStreamableHttp.ts").absolutePath - val processBuilder = if (isWindows) { - ProcessBuilder() - .command( - "cmd.exe", - "/c", - "set MCP_PORT=$port && set NODE_PATH=${sdkDir.absolutePath}\\node_modules && npx --prefix \"${sdkDir.absolutePath}\" tsx \"$localServerPath\"", - ) - } else { - ProcessBuilder() - .command( - "bash", - "-c", - "MCP_PORT=$port NODE_PATH='${sdkDir.absolutePath}/node_modules' npx --prefix '${sdkDir.absolutePath}' tsx \"$localServerPath\"", - ) - } - - processBuilder.environment()["TYPESCRIPT_SDK_DIR"] = sdkDir.absolutePath - - val process = processBuilder - .directory(tsClientDir) - .redirectErrorStream(true) - .start() - - createProcessOutputReader(process).start() - - if (!waitForPort(port = port, timeoutSeconds = 20)) { - throw IllegalStateException("TypeScript server did not become ready on localhost:$port within timeout") - } - return process + val server = TypeScriptServer(tsClientDir) + return server.startSse(port) } protected fun stopProcess(process: Process, waitSeconds: Long = 3, name: String = "TypeScript server") { - process.destroy() - if (waitForProcessTermination(process, waitSeconds)) { - println("$name stopped gracefully") - } else { - println("$name did not stop gracefully, forced termination") - } + io.modelcontextprotocol.kotlin.test.utils.stopProcess(process) + println("$name stopped") } // ===== SSE client helpers ===== @@ -301,41 +232,8 @@ abstract class TsTestBase { // ===== STDIO client + server helpers ===== protected fun startTypeScriptServerStdio(): Process { - if (!sdkDir.exists() || !sdkDir.isDirectory) { - throw IllegalStateException( - "TypeScript SDK directory does not exist or is not accessible: ${sdkDir.absolutePath}", - ) - } - val isWindows = System.getProperty("os.name").lowercase().contains("windows") - val localServerPath = File(tsClientDir, "simpleStdio.ts").absolutePath - val processBuilder = if (isWindows) { - ProcessBuilder() - .command( - "cmd.exe", - "/c", - "set NODE_PATH=${sdkDir.absolutePath}\\node_modules && npx --prefix \"${sdkDir.absolutePath}\" tsx \"$localServerPath\"", - ) - } else { - ProcessBuilder() - .command( - "bash", - "-c", - "NODE_PATH='${sdkDir.absolutePath}/node_modules' npx --prefix '${sdkDir.absolutePath}' tsx \"$localServerPath\"", - ) - } - processBuilder.environment()["TYPESCRIPT_SDK_DIR"] = sdkDir.absolutePath - val process = processBuilder - .directory(tsClientDir) - .redirectErrorStream(false) - .start() - // For stdio transports, do NOT read from stdout (it's used for protocol). Read stderr for logs only. - createProcessErrorReader(process, prefix = "TS-SERVER-STDIO").start() - // Give the process a moment to start - await.atMost(2, TimeUnit.SECONDS) - .pollDelay(200, TimeUnit.MILLISECONDS) - .pollInterval(100, TimeUnit.MILLISECONDS) - .until { process.isAlive } - return process + val server = TypeScriptServer(tsClientDir) + return server.startStdio() } protected suspend fun newClientStdio(process: Process): Client { @@ -367,40 +265,20 @@ abstract class TsTestBase { // ===== Helpers to run TypeScript client over STDIO against Kotlin server over STDIO ===== protected fun runStdioClient(vararg args: String): String { // Start Node stdio client (it will speak MCP over its stdout/stdin) - val isWindows = System.getProperty("os.name").lowercase().contains("windows") - val clientPath = File(tsClientDir, "myClient.ts").absolutePath - - val process = if (isWindows) { - ProcessBuilder() - .command( - "cmd.exe", - "/c", - ( - "set TYPESCRIPT_SDK_DIR=${sdkDir.absolutePath} && " + - "set NODE_PATH=${sdkDir.absolutePath}\\node_modules && " + - "npx --prefix \"${sdkDir.absolutePath}\" tsx \"$clientPath\" " + - args.joinToString(" ") - ), - ) - .directory(tsClientDir) - .redirectErrorStream(false) - .start() - } else { - ProcessBuilder() - .command( - "bash", - "-c", - ( - "TYPESCRIPT_SDK_DIR='${sdkDir.absolutePath}' " + - "NODE_PATH='${sdkDir.absolutePath}/node_modules' " + - "npx --prefix '${sdkDir.absolutePath}' tsx \"$clientPath\" " + - args.joinToString(" ") - ), - ) - .directory(tsClientDir) - .redirectErrorStream(false) - .start() + val subDirName = when (transportKind) { + TransportKind.STDIO -> "stdio" + TransportKind.SSE -> "sse" } + val clientPath = File(tsSourceDir, "$subDirName/myClient.ts").absolutePath + + val process = TypeScriptRunner.run( + typescriptDir = tsClientDir, + scriptPath = clientPath, + arguments = args.toList(), + env = mapOf("TYPESCRIPT_SDK_DIR" to sdkDir.absolutePath), + redirectErrorStream = false, + logPrefix = "TS-CLIENT-STDIO", + ) // Create Kotlin server and attach stdio transport to the process streams val server: Server = KotlinServerForTsClient().createMcpServer() diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TypeScriptServer.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TypeScriptServer.kt new file mode 100644 index 0000000..d926590 --- /dev/null +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TypeScriptServer.kt @@ -0,0 +1,121 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.typescript + +import io.modelcontextprotocol.kotlin.test.utils.TypeScriptRunner +import io.modelcontextprotocol.kotlin.test.utils.killProcessOnPort +import io.modelcontextprotocol.kotlin.test.utils.stopProcess +import io.modelcontextprotocol.kotlin.test.utils.waitForPort +import org.awaitility.kotlin.await +import java.io.File +import java.util.concurrent.TimeUnit + +/** + * Factory class for starting and managing TypeScript MCP servers for integration tests. + * + * This class provides methods to: + * - Start TypeScript servers using SSE or STDIO transports via npx tsx + * - Install npm dependencies in the TypeScript test directory + * - Manage server lifecycle (start/stop) + * - Handle port management and process cleanup + * + * The servers are started using npx tsx to execute TypeScript files directly: + * - `npx tsx server/stdio-server.ts` for STDIO transport + * - `npx tsx server/sse-server.ts` for SSE transport + * + * Usage: + * ```kotlin + * val typescriptDir = File("kotlin-sdk-test/src/jvmTest/typescript") + * val server = TypeScriptServer(typescriptDir) + * + * // Start SSE server + * val port = TypeScriptServer.findFreePort() + * val process = server.startSse(port) + * + * // Stop server + * server.stop() + * ``` + */ +class TypeScriptServer(private val typescriptDir: File) { + private var process: Process? = null + + /** + * Starts a TypeScript MCP server using SSE transport. + * + * @param port The port number to bind the SSE server to + * @return The started Process + * @throws IllegalStateException if the server fails to start within timeout + */ + fun startSse(port: Int): Process { + killProcessOnPort(port) + val serverPath = File(typescriptDir, "server/sse-server.ts").absolutePath + val proc = TypeScriptRunner.run( + typescriptDir = typescriptDir, + scriptPath = serverPath, + env = mapOf("MCP_PORT" to port.toString()), + redirectErrorStream = true, + logPrefix = "TS-SERVER-SSE", + ) + process = proc + + check(waitForPort(port = port, timeoutSeconds = 10)) { + proc.destroyForcibly() + "TypeScript SSE server did not become ready on localhost:$port within timeout" + } + return proc + } + + /** + * Starts a TypeScript MCP server using STDIO transport. + * + * @return The started Process + */ + fun startStdio(): Process { + val serverPath = File(typescriptDir, "server/stdio-server.ts").absolutePath + val proc = TypeScriptRunner.run( + typescriptDir = typescriptDir, + scriptPath = serverPath, + redirectErrorStream = false, + logPrefix = "TS-SERVER-STDIO", + ) + process = proc + + await.atMost(5, TimeUnit.SECONDS) + .pollDelay(200, TimeUnit.MILLISECONDS) + .pollInterval(100, TimeUnit.MILLISECONDS) + .until { proc.isAlive } + + return proc + } + + /** + * Stops the currently running server process. + * + * Attempts graceful shutdown first, then forces termination if necessary. + */ + fun stop() { + process?.let { + stopProcess(it) + } + process = null + } + + companion object { + /** + * Installs npm dependencies in the specified TypeScript directory. + * + * @param typescriptDir The directory containing package.json + * @throws RuntimeException if npm install fails + */ + @JvmStatic + fun installDependencies(typescriptDir: File) { + TypeScriptRunner.installDependencies(typescriptDir) + } + + /** + * Finds an available port by opening and closing a ServerSocket. + * + * @return An available port number + */ + @JvmStatic + fun findFreePort(): Int = io.modelcontextprotocol.kotlin.test.utils.findFreePort() + } +} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerEdgeCasesTestSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerEdgeCasesTestSse.kt index c3832b6..d434f89 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerEdgeCasesTestSse.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerEdgeCasesTestSse.kt @@ -4,7 +4,6 @@ import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TsTestBase import io.modelcontextprotocol.kotlin.sdk.types.TextContent -import io.modelcontextprotocol.kotlin.test.utils.DisabledOnCI import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll @@ -14,7 +13,6 @@ import kotlinx.coroutines.withTimeout import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.Timeout import java.util.concurrent.TimeUnit @@ -23,25 +21,14 @@ import kotlin.test.assertNotNull import kotlin.test.assertTrue import kotlin.time.Duration.Companion.seconds -@DisabledOnCI class KotlinClientTsServerEdgeCasesTestSse : TsTestBase() { override val transportKind = TransportKind.SSE - private var port: Int = 0 private val host = "localhost" - private lateinit var serverUrl: String + private val serverUrl: String by lazy { getSharedSseUrl() } private lateinit var client: Client - private lateinit var tsServerProcess: Process - - @BeforeEach - fun setUp() { - port = findFreePort() - serverUrl = "http://$host:$port/mcp" - tsServerProcess = startTypeScriptServer(port) - println("TypeScript server started on port $port") - } @AfterEach fun tearDown() { @@ -56,15 +43,6 @@ class KotlinClientTsServerEdgeCasesTestSse : TsTestBase() { println("Warning: Error during client close: ${e.message}") } } - - if (::tsServerProcess.isInitialized) { - try { - println("Stopping TypeScript server") - stopProcess(tsServerProcess) - } catch (e: Exception) { - println("Warning: Error during TypeScript server stop: ${e.message}") - } - } } @Test diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerTestSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerTestSse.kt index d38d765..2006285 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerTestSse.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerTestSse.kt @@ -3,41 +3,14 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.integration.typescript.AbstractKotlinClientTsServerTest import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind -import io.modelcontextprotocol.kotlin.test.utils.DisabledOnCI import kotlinx.coroutines.withTimeout -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.BeforeEach import kotlin.time.Duration.Companion.seconds -@DisabledOnCI class KotlinClientTsServerTestSse : AbstractKotlinClientTsServerTest() { - override val transportKind = TransportKind.SSE - private var port: Int = 0 private val host = "localhost" - private lateinit var serverUrl: String - private lateinit var tsServerProcess: Process - - @BeforeEach - fun setUpSse() { - port = findFreePort() - serverUrl = "http://$host:$port/mcp" - tsServerProcess = startTypeScriptServer(port) - println("TypeScript server started on port $port") - } - - @AfterEach - fun tearDownSse() { - if (::tsServerProcess.isInitialized) { - try { - println("Stopping TypeScript server") - stopProcess(tsServerProcess) - } catch (e: Exception) { - println("Warning: Error during TypeScript server stop: ${e.message}") - } - } - } + private val serverUrl: String by lazy { getSharedSseUrl() } override suspend fun useClient(block: suspend (Client) -> T): T = withClient(serverUrl) { client -> try { diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerEdgeCasesTestSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerEdgeCasesTestSse.kt index ab62b0f..f2f6009 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerEdgeCasesTestSse.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerEdgeCasesTestSse.kt @@ -4,7 +4,6 @@ import io.modelcontextprotocol.kotlin.sdk.TextContent import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TsTestBase -import io.modelcontextprotocol.kotlin.test.utils.DisabledOnCI import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll @@ -14,7 +13,6 @@ import kotlinx.coroutines.withTimeout import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.Timeout import java.util.concurrent.TimeUnit @@ -23,25 +21,14 @@ import kotlin.test.assertNotNull import kotlin.test.assertTrue import kotlin.time.Duration.Companion.seconds -@DisabledOnCI class OldSchemaKotlinClientTsServerEdgeCasesTestSse : TsTestBase() { override val transportKind = TransportKind.SSE - private var port: Int = 0 private val host = "localhost" - private lateinit var serverUrl: String + private val serverUrl: String by lazy { getSharedSseUrl() } private lateinit var client: Client - private lateinit var tsServerProcess: Process - - @BeforeEach - fun setUp() { - port = findFreePort() - serverUrl = "http://$host:$port/mcp" - tsServerProcess = startTypeScriptServer(port) - println("TypeScript server started on port $port") - } @AfterEach fun tearDown() { @@ -56,15 +43,6 @@ class OldSchemaKotlinClientTsServerEdgeCasesTestSse : TsTestBase() { println("Warning: Error during client close: ${e.message}") } } - - if (::tsServerProcess.isInitialized) { - try { - println("Stopping TypeScript server") - stopProcess(tsServerProcess) - } catch (e: Exception) { - println("Warning: Error during TypeScript server stop: ${e.message}") - } - } } @Test diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerTestSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerTestSse.kt index 9fb9786..f9415e1 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerTestSse.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerTestSse.kt @@ -3,41 +3,15 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.integration.typescript.OldSchemaAbstractKotlinClientTsServerTest import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind -import io.modelcontextprotocol.kotlin.test.utils.DisabledOnCI import kotlinx.coroutines.withTimeout -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.BeforeEach import kotlin.time.Duration.Companion.seconds -@DisabledOnCI class OldSchemaKotlinClientTsServerTestSse : OldSchemaAbstractKotlinClientTsServerTest() { override val transportKind = TransportKind.SSE - private var port: Int = 0 private val host = "localhost" - private lateinit var serverUrl: String - private lateinit var tsServerProcess: Process - - @BeforeEach - fun setUpSse() { - port = findFreePort() - serverUrl = "http://$host:$port/mcp" - tsServerProcess = startTypeScriptServer(port) - println("TypeScript server started on port $port") - } - - @AfterEach - fun tearDownSse() { - if (::tsServerProcess.isInitialized) { - try { - println("Stopping TypeScript server") - stopProcess(tsServerProcess) - } catch (e: Exception) { - println("Warning: Error during TypeScript server stop: ${e.message}") - } - } - } + private val serverUrl: String by lazy { getSharedSseUrl() } override suspend fun useClient(block: suspend (Client) -> T): T = withClient(serverUrl) { client -> try { diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsClientKotlinServerTestSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsClientKotlinServerTestSse.kt index 87123dd..e1c8302 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsClientKotlinServerTestSse.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsClientKotlinServerTestSse.kt @@ -3,6 +3,8 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse import io.modelcontextprotocol.kotlin.sdk.integration.typescript.AbstractTsClientKotlinServerTest import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind import io.modelcontextprotocol.kotlin.test.utils.DisabledOnCI +import io.modelcontextprotocol.kotlin.test.utils.findFreePort +import io.modelcontextprotocol.kotlin.test.utils.killProcessOnPort import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsEdgeCasesTestSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsEdgeCasesTestSse.kt index eb372c2..8023678 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsEdgeCasesTestSse.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsEdgeCasesTestSse.kt @@ -3,6 +3,8 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TsTestBase import io.modelcontextprotocol.kotlin.test.utils.DisabledOnCI +import io.modelcontextprotocol.kotlin.test.utils.findFreePort +import io.modelcontextprotocol.kotlin.test.utils.killProcessOnPort import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope @@ -35,8 +37,8 @@ class TsEdgeCasesTestSse : TsTestBase() { killProcessOnPort(port) httpServer = KotlinServerForTsClient() httpServer?.start(port) - if (!waitForPort(port = port)) { - throw IllegalStateException("Kotlin test server did not become ready on localhost:$port within timeout") + check(waitForPort(port = port)) { + "Kotlin test server did not become ready on localhost:$port within timeout" } println("Kotlin server started on port $port") } diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/myClient.ts b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/myClient.ts index 42a14f5..8a1a69c 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/myClient.ts +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/myClient.ts @@ -17,8 +17,8 @@ async function main() { const path = await import('path'); // @ts-ignore const { pathToFileURL } = await import('url'); - const clientUrl = pathToFileURL(path.join(sdkDir, 'src', 'client', 'index.ts')).href; - const streamUrl = pathToFileURL(path.join(sdkDir, 'src', 'client', 'streamableHttp.js')).href; + const clientUrl = pathToFileURL(path.join(sdkDir, 'packages', 'client', 'src', 'index.ts')).href; + const streamUrl = pathToFileURL(path.join(sdkDir, 'packages', 'client', 'src', 'streamableHttp.ts')).href; // @ts-ignore ({ Client } = await import(clientUrl)); // @ts-ignore diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/simpleStreamableHttp.ts b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/simpleStreamableHttp.ts deleted file mode 100644 index c750aed..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/simpleStreamableHttp.ts +++ /dev/null @@ -1,701 +0,0 @@ -// @ts-nocheck -import express, {Request, Response} from 'express'; -import {randomUUID} from 'node:crypto'; -import {z} from 'zod'; -import cors from 'cors'; -import path from 'node:path'; -import {pathToFileURL} from 'node:url'; - -const SDK_DIR = process.env.TYPESCRIPT_SDK_DIR; -if (!SDK_DIR) { - throw new Error('TYPESCRIPT_SDK_DIR environment variable is not set. It should point to the cloned typescript-sdk directory.'); -} - -async function importFromSdk(rel: string): Promise { - const full = path.resolve(SDK_DIR!, rel); - const url = pathToFileURL(full).href; - return (await import(url)) as unknown as T; -} - -async function main() { - const {McpServer} = await importFromSdk('src/server/mcp.ts'); - const {StreamableHTTPServerTransport} = await importFromSdk('src/server/streamableHttp.ts'); - const { - getOAuthProtectedResourceMetadataUrl, - mcpAuthMetadataRouter - } = await importFromSdk('src/server/auth/router.ts'); - const {requireBearerAuth} = await importFromSdk('src/server/auth/middleware/bearerAuth.ts'); - const { - isInitializeRequest, - } = await importFromSdk('src/types.ts'); - const {InMemoryEventStore} = await importFromSdk('src/examples/shared/inMemoryEventStore.ts'); - const {setupAuthServer} = await importFromSdk('src/examples/server/demoInMemoryOAuthProvider.ts'); - const {checkResourceAllowed} = await importFromSdk('src/shared/auth-utils.ts'); - - // Check for OAuth flag - const useOAuth = process.argv.includes('--oauth'); - const strictOAuth = process.argv.includes('--oauth-strict'); - -// Create an MCP server with implementation details - const getServer = () => { - const server = new McpServer({ - name: 'simple-streamable-http-server', - version: '1.0.0' - }, {capabilities: {logging: {}}}); - - // Register a simple tool that returns a greeting - server.registerTool( - 'greet', - { - title: 'Greeting Tool', // Display name for UI - description: 'A simple greeting tool', - inputSchema: { - name: z.string().describe('Name to greet'), - }, - }, - async ({name}): Promise => { - return { - content: [ - { - type: 'text', - text: `Hello, ${name}!`, - }, - ], - }; - } - ); - - // Register a tool that sends multiple greetings with notifications (with annotations) - server.tool( - 'multi-greet', - 'A tool that sends different greetings with delays between them', - { - name: z.string().describe('Name to greet'), - }, - { - title: 'Multiple Greeting Tool', - readOnlyHint: true, - openWorldHint: false - }, - async ({name}, extra): Promise => { - const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); - - await server.sendLoggingMessage({ - level: "debug", - data: `Starting multi-greet for ${name}` - }, extra.sessionId); - - await sleep(1000); // Wait 1 second before first greeting - - await server.sendLoggingMessage({ - level: "info", - data: `Sending first greeting to ${name}` - }, extra.sessionId); - - await sleep(1000); // Wait another second before second greeting - - await server.sendLoggingMessage({ - level: "info", - data: `Sending second greeting to ${name}` - }, extra.sessionId); - - return { - content: [ - { - type: 'text', - text: `Good morning, ${name}!`, - } - ], - }; - } - ); - // Register a tool that demonstrates elicitation (user input collection) - // This creates a closure that captures the server instance - server.tool( - 'collect-user-info', - 'A tool that collects user information through elicitation', - { - infoType: z.enum(['contact', 'preferences', 'feedback']).describe('Type of information to collect'), - }, - async ({infoType}): Promise => { - let message: string; - let requestedSchema: { - type: 'object'; - properties: Record; - required?: string[]; - }; - - switch (infoType) { - case 'contact': - message = 'Please provide your contact information'; - requestedSchema = { - type: 'object', - properties: { - name: { - type: 'string', - title: 'Full Name', - description: 'Your full name', - }, - email: { - type: 'string', - title: 'Email Address', - description: 'Your email address', - format: 'email', - }, - phone: { - type: 'string', - title: 'Phone Number', - description: 'Your phone number (optional)', - }, - }, - required: ['name', 'email'], - }; - break; - case 'preferences': - message = 'Please set your preferences'; - requestedSchema = { - type: 'object', - properties: { - theme: { - type: 'string', - title: 'Theme', - description: 'Choose your preferred theme', - enum: ['light', 'dark', 'auto'], - enumNames: ['Light', 'Dark', 'Auto'], - }, - notifications: { - type: 'boolean', - title: 'Enable Notifications', - description: 'Would you like to receive notifications?', - default: true, - }, - frequency: { - type: 'string', - title: 'Notification Frequency', - description: 'How often would you like notifications?', - enum: ['daily', 'weekly', 'monthly'], - enumNames: ['Daily', 'Weekly', 'Monthly'], - }, - }, - required: ['theme'], - }; - break; - case 'feedback': - message = 'Please provide your feedback'; - requestedSchema = { - type: 'object', - properties: { - rating: { - type: 'integer', - title: 'Rating', - description: 'Rate your experience (1-5)', - minimum: 1, - maximum: 5, - }, - comments: { - type: 'string', - title: 'Comments', - description: 'Additional comments (optional)', - maxLength: 500, - }, - recommend: { - type: 'boolean', - title: 'Would you recommend this?', - description: 'Would you recommend this to others?', - }, - }, - required: ['rating', 'recommend'], - }; - break; - default: - throw new Error(`Unknown info type: ${infoType}`); - } - - try { - // Use the underlying server instance to elicit input from the client - const result = await server.server.elicitInput({ - message, - requestedSchema, - }); - - if (result.action === 'accept') { - return { - content: [ - { - type: 'text', - text: `Thank you! Collected ${infoType} information: ${JSON.stringify(result.content, null, 2)}`, - }, - ], - }; - } else if (result.action === 'decline') { - return { - content: [ - { - type: 'text', - text: `No information was collected. User declined ${infoType} information request.`, - }, - ], - }; - } else { - return { - content: [ - { - type: 'text', - text: `Information collection was cancelled by the user.`, - }, - ], - }; - } - } catch (error) { - return { - content: [ - { - type: 'text', - text: `Error collecting ${infoType} information: ${error}`, - }, - ], - }; - } - } - ); - - // Register a simple prompt with title - server.registerPrompt( - 'greeting-template', - { - title: 'Greeting Template', // Display name for UI - description: 'A simple greeting prompt template', - argsSchema: { - name: z.string().describe('Name to include in greeting'), - }, - }, - async ({name}): Promise => { - return { - messages: [ - { - role: 'user', - content: { - type: 'text', - text: `Please greet ${name} in a friendly manner.`, - }, - }, - ], - }; - } - ); - - // Register a tool specifically for testing resumability - server.tool( - 'start-notification-stream', - 'Starts sending periodic notifications for testing resumability', - { - interval: z.number().describe('Interval in milliseconds between notifications').default(100), - count: z.number().describe('Number of notifications to send (0 for 100)').default(50), - }, - async ({interval, count}, extra): Promise => { - const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); - let counter = 0; - - while (count === 0 || counter < count) { - counter++; - try { - await server.sendLoggingMessage({ - level: "info", - data: `Periodic notification #${counter} at ${new Date().toISOString()}` - }, extra.sessionId); - } catch (error) { - console.error("Error sending notification:", error); - } - // Wait for the specified interval - await sleep(interval); - } - - return { - content: [ - { - type: 'text', - text: `Started sending periodic notifications every ${interval}ms`, - } - ], - }; - } - ); - - // Create a simple resource at a fixed URI - server.registerResource( - 'greeting-resource', - 'https://example.com/greetings/default', - { - title: 'Default Greeting', // Display name for UI - description: 'A simple greeting resource', - mimeType: 'text/plain' - }, - async (): Promise => { - return { - contents: [ - { - uri: 'https://example.com/greetings/default', - text: 'Hello, world!', - }, - ], - }; - } - ); - - // Create additional resources for ResourceLink demonstration - server.registerResource( - 'example-file-1', - 'file:///example/file1.txt', - { - title: 'Example File 1', - description: 'First example file for ResourceLink demonstration', - mimeType: 'text/plain' - }, - async (): Promise => { - return { - contents: [ - { - uri: 'file:///example/file1.txt', - text: 'This is the content of file 1', - }, - ], - }; - } - ); - - server.registerResource( - 'example-file-2', - 'file:///example/file2.txt', - { - title: 'Example File 2', - description: 'Second example file for ResourceLink demonstration', - mimeType: 'text/plain' - }, - async (): Promise => { - return { - contents: [ - { - uri: 'file:///example/file2.txt', - text: 'This is the content of file 2', - }, - ], - }; - } - ); - - // Register a tool that returns ResourceLinks - server.registerTool( - 'list-files', - { - title: 'List Files with ResourceLinks', - description: 'Returns a list of files as ResourceLinks without embedding their content', - inputSchema: { - includeDescriptions: z.boolean().optional().describe('Whether to include descriptions in the resource links'), - }, - }, - async ({includeDescriptions = true}): Promise => { - const resourceLinks: ResourceLink[] = [ - { - type: 'resource_link', - uri: 'https://example.com/greetings/default', - name: 'Default Greeting', - mimeType: 'text/plain', - ...(includeDescriptions && {description: 'A simple greeting resource'}) - }, - { - type: 'resource_link', - uri: 'file:///example/file1.txt', - name: 'Example File 1', - mimeType: 'text/plain', - ...(includeDescriptions && {description: 'First example file for ResourceLink demonstration'}) - }, - { - type: 'resource_link', - uri: 'file:///example/file2.txt', - name: 'Example File 2', - mimeType: 'text/plain', - ...(includeDescriptions && {description: 'Second example file for ResourceLink demonstration'}) - } - ]; - - return { - content: [ - { - type: 'text', - text: 'Here are the available files as resource links:', - }, - ...resourceLinks, - { - type: 'text', - text: '\nYou can read any of these resources using their URI.', - } - ], - }; - } - ); - - return server; - }; - - const MCP_PORT = process.env.MCP_PORT ? parseInt(process.env.MCP_PORT, 10) : 3000; - const AUTH_PORT = process.env.MCP_AUTH_PORT ? parseInt(process.env.MCP_AUTH_PORT, 10) : 3001; - - const app = express(); - app.use(express.json()); - -// Allow CORS all domains, expose the Mcp-Session-Id header - app.use(cors({ - origin: '*', // Allow all origins - exposedHeaders: ["Mcp-Session-Id"] - })); - -// Set up OAuth if enabled - let authMiddleware = null; - if (useOAuth) { - // Create auth middleware for MCP endpoints - const mcpServerUrl = new URL(`http://localhost:${MCP_PORT}/mcp`); - const authServerUrl = new URL(`http://localhost:${AUTH_PORT}`); - - const oauthMetadata: OAuthMetadata = setupAuthServer({ - authServerUrl, - mcpServerUrl, - strictResource: strictOAuth - }); - - const tokenVerifier = { - verifyAccessToken: async (token: string) => { - const endpoint = oauthMetadata.introspection_endpoint; - - if (!endpoint) { - throw new Error('No token verification endpoint available in metadata'); - } - - const response = await fetch(endpoint, { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - body: new URLSearchParams({ - token: token - }).toString() - }); - - - if (!response.ok) { - throw new Error(`Invalid or expired token: ${await response.text()}`); - } - - const data = await response.json(); - - if (strictOAuth) { - if (!data.aud) { - throw new Error(`Resource Indicator (RFC8707) missing`); - } - if (!checkResourceAllowed({requestedResource: data.aud, configuredResource: mcpServerUrl})) { - throw new Error(`Expected resource indicator ${mcpServerUrl}, got: ${data.aud}`); - } - } - - // Convert the response to AuthInfo format - return { - token, - clientId: data.client_id, - scopes: data.scope ? data.scope.split(' ') : [], - expiresAt: data.exp, - }; - } - } - // Add metadata routes to the main MCP server - app.use(mcpAuthMetadataRouter({ - oauthMetadata, - resourceServerUrl: mcpServerUrl, - scopesSupported: ['mcp:tools'], - resourceName: 'MCP Demo Server', - })); - - authMiddleware = requireBearerAuth({ - verifier: tokenVerifier, - requiredScopes: [], - resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(mcpServerUrl), - }); - } - -// Map to store transports by session ID - const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {}; - -// MCP POST endpoint with optional auth - const mcpPostHandler = async (req: Request, res: Response) => { - const sessionId = req.headers['mcp-session-id'] as string | undefined; - if (sessionId) { - console.log(`Received MCP request for session: ${sessionId}`); - } else { - console.log('Request body:', req.body); - } - - if (useOAuth && req.auth) { - console.log('Authenticated user:', req.auth); - } - try { - let transport: StreamableHTTPServerTransport; - if (sessionId && transports[sessionId]) { - // Reuse existing transport - transport = transports[sessionId]; - } else if (!sessionId && isInitializeRequest(req.body)) { - // New initialization request - const eventStore = new InMemoryEventStore(); - transport = new StreamableHTTPServerTransport({ - sessionIdGenerator: () => randomUUID(), - eventStore, // Enable resumability - onsessioninitialized: (sessionId) => { - // Store the transport by session ID when session is initialized - // This avoids race conditions where requests might come in before the session is stored - console.log(`Session initialized with ID: ${sessionId}`); - transports[sessionId] = transport; - } - }); - - // Set up onclose handler to clean up transport when closed - transport.onclose = () => { - const sid = transport.sessionId; - if (sid && transports[sid]) { - console.log(`Transport closed for session ${sid}, removing from transports map`); - delete transports[sid]; - } - }; - - // Connect the transport to the MCP server BEFORE handling the request - // so responses can flow back through the same transport - const server = getServer(); - await server.connect(transport); - - await transport.handleRequest(req, res, req.body); - return; // Already handled - } else { - // Invalid request - no session ID or not initialization request - res.status(400).json({ - jsonrpc: '2.0', - error: { - code: -32000, - message: 'Bad Request: No valid session ID provided', - }, - id: null, - }); - return; - } - - // Handle the request with existing transport - no need to reconnect - // The existing transport is already connected to the server - await transport.handleRequest(req, res, req.body); - } catch (error) { - console.error('Error handling MCP request:', error); - if (!res.headersSent) { - res.status(500).json({ - jsonrpc: '2.0', - error: { - code: -32603, - message: 'Internal server error', - }, - id: null, - }); - } - } - }; - -// Set up routes with conditional auth middleware - if (useOAuth && authMiddleware) { - app.post('/mcp', authMiddleware, mcpPostHandler); - } else { - app.post('/mcp', mcpPostHandler); - } - -// Handle GET requests for SSE streams (using built-in support from StreamableHTTP) - const mcpGetHandler = async (req: Request, res: Response) => { - const sessionId = req.headers['mcp-session-id'] as string | undefined; - if (!sessionId || !transports[sessionId]) { - res.status(400).send('Invalid or missing session ID'); - return; - } - - if (useOAuth && req.auth) { - console.log('Authenticated SSE connection from user:', req.auth); - } - - // Check for Last-Event-ID header for resumability - const lastEventId = req.headers['last-event-id'] as string | undefined; - if (lastEventId) { - console.log(`Client reconnecting with Last-Event-ID: ${lastEventId}`); - } else { - console.log(`Establishing new SSE stream for session ${sessionId}`); - } - - const transport = transports[sessionId]; - await transport.handleRequest(req, res); - }; - -// Set up GET route with conditional auth middleware - if (useOAuth && authMiddleware) { - app.get('/mcp', authMiddleware, mcpGetHandler); - } else { - app.get('/mcp', mcpGetHandler); - } - -// Handle DELETE requests for session termination (according to MCP spec) - const mcpDeleteHandler = async (req: Request, res: Response) => { - const sessionId = req.headers['mcp-session-id'] as string | undefined; - if (!sessionId || !transports[sessionId]) { - res.status(400).send('Invalid or missing session ID'); - return; - } - - console.log(`Received session termination request for session ${sessionId}`); - - try { - const transport = transports[sessionId]; - await transport.handleRequest(req, res); - } catch (error) { - console.error('Error handling session termination:', error); - if (!res.headersSent) { - res.status(500).send('Error processing session termination'); - } - } - }; - -// Set up DELETE route with conditional auth middleware - if (useOAuth && authMiddleware) { - app.delete('/mcp', authMiddleware, mcpDeleteHandler); - } else { - app.delete('/mcp', mcpDeleteHandler); - } - - app.listen(MCP_PORT, (error) => { - if (error) { - console.error('Failed to start server:', error); - process.exit(1); - } - console.log(`MCP Streamable HTTP Server listening on port ${MCP_PORT}`); - }); - -// Handle server shutdown - process.on('SIGINT', async () => { - console.log('Shutting down server...'); - - // Close all active transports to properly clean up resources - for (const sessionId in transports) { - try { - console.log(`Closing transport for session ${sessionId}`); - await transports[sessionId].close(); - delete transports[sessionId]; - } catch (error) { - console.error(`Error closing transport for session ${sessionId}:`, error); - } - } - console.log('Server shutdown complete'); - process.exit(0); - }); - -} - -main().catch((err) => { - console.error('Failed to start server:', err); - process.exit(1); -}); diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt index 5815d0d..2bdd361 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt @@ -4,7 +4,6 @@ import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TsTestBase import io.modelcontextprotocol.kotlin.sdk.types.TextContent -import io.modelcontextprotocol.kotlin.test.utils.DisabledOnCI import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll @@ -19,7 +18,6 @@ import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue -@DisabledOnCI class KotlinClientTsServerEdgeCasesTestStdio : TsTestBase() { override val transportKind = TransportKind.STDIO diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerTestStdio.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerTestStdio.kt index 6f2fea3..8273724 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerTestStdio.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerTestStdio.kt @@ -3,9 +3,7 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.stdio import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.integration.typescript.AbstractKotlinClientTsServerTest import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind -import io.modelcontextprotocol.kotlin.test.utils.DisabledOnCI -@DisabledOnCI class KotlinClientTsServerTestStdio : AbstractKotlinClientTsServerTest() { override val transportKind = TransportKind.STDIO diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerEdgeCasesTestStdio.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerEdgeCasesTestStdio.kt index 3b74993..0ed8676 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerEdgeCasesTestStdio.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerEdgeCasesTestStdio.kt @@ -4,7 +4,6 @@ import io.modelcontextprotocol.kotlin.sdk.TextContent import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TsTestBase -import io.modelcontextprotocol.kotlin.test.utils.DisabledOnCI import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll @@ -19,7 +18,6 @@ import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue -@DisabledOnCI class OldSchemaKotlinClientTsServerEdgeCasesTestStdio : TsTestBase() { override val transportKind = TransportKind.STDIO diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerTestStdio.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerTestStdio.kt index 7bbad51..e87a5c8 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerTestStdio.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerTestStdio.kt @@ -3,9 +3,7 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.stdio import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.integration.typescript.OldSchemaAbstractKotlinClientTsServerTest import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind -import io.modelcontextprotocol.kotlin.test.utils.DisabledOnCI -@DisabledOnCI class OldSchemaKotlinClientTsServerTestStdio : OldSchemaAbstractKotlinClientTsServerTest() { override val transportKind = TransportKind.STDIO diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/myClient.ts b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/myClient.ts index e4aae91..cce2354 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/myClient.ts +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/myClient.ts @@ -16,7 +16,7 @@ async function main() { const path = await import('path'); const { pathToFileURL } = await import('url'); - const clientUrl = pathToFileURL(path.join(sdkDir, 'src', 'client', 'index.ts')).href; + const clientUrl = pathToFileURL(path.join(sdkDir, 'packages', 'client', 'src', 'index.ts')).href; ({ Client } = await import(clientUrl)); if (!toolName) { diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/simpleStdio.ts b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/simpleStdio.ts deleted file mode 100644 index 8e5fc79..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/simpleStdio.ts +++ /dev/null @@ -1,74 +0,0 @@ -// @ts-nocheck -import {z} from 'zod'; -import path from 'node:path'; -import {pathToFileURL} from 'node:url'; - -const SDK_DIR = process.env.TYPESCRIPT_SDK_DIR; -if (!SDK_DIR) { - throw new Error('TYPESCRIPT_SDK_DIR environment variable is not set. It should point to the cloned typescript-sdk directory.'); -} - -async function importFromSdk(rel: string): Promise { - const full = path.resolve(SDK_DIR!, rel); - const url = pathToFileURL(full).href; - return await import(url); -} - -async function main() { - const { McpServer } = await importFromSdk('src/server/mcp.ts'); - const { StdioServerTransport } = await importFromSdk('src/server/stdio.ts'); - - const server = new McpServer({ - name: 'simple-stdio-server', - version: '1.0.0', - }, { capabilities: { logging: {} } }); - - // Simple tools mirroring ones from HTTP test server - server.registerTool('greet', { - title: 'Greeting Tool', - description: 'A simple greeting tool', - inputSchema: { name: z.string().describe('Name to greet') }, - }, async ({ name }): Promise => { - return { content: [{ type: 'text', text: `Hello, ${name}!` }] }; - }); - - server.tool('multi-greet', 'A tool that sends different greetings with delays between them', - { name: z.string().describe('Name to greet') }, - { title: 'Multiple Greeting Tool', readOnlyHint: true, openWorldHint: false }, - async ({ name }, extra): Promise => { - const sleep = (ms: number) => new Promise(r => setTimeout(r, ms)); - await server.sendLoggingMessage({ level: 'debug', data: `Starting multi-greet for ${name}` }, extra.sessionId); - await sleep(200); - await server.sendLoggingMessage({ level: 'info', data: `Sending first greeting to ${name}` }, extra.sessionId); - await sleep(200); - await server.sendLoggingMessage({ level: 'info', data: `Sending second greeting to ${name}` }, extra.sessionId); - return { content: [{ type: 'text', text: `Good morning, ${name}!` }] }; - } - ); - - server.registerPrompt('greeting-template', { - title: 'Greeting Template', - description: 'A simple greeting prompt template', - argsSchema: { name: z.string().describe('Name to include in greeting') }, - }, async ({ name }): Promise => { - return { - messages: [{ role: 'user', content: { type: 'text', text: `Please greet ${name} in a friendly manner.` } }], - }; - }); - - server.registerResource('greeting-resource', 'https://example.com/greetings/default', { - title: 'Default Greeting', - description: 'A simple greeting resource', - mimeType: 'text/plain', - }, async (): Promise => { - return { contents: [{ uri: 'https://example.com/greetings/default', text: 'Hello, world!' }] }; - }); - - const transport = new StdioServerTransport(); - await server.connect(transport); -} - -main().catch((err) => { - console.error('Failed to start stdio server:', err); - process.exit(1); -}); diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json new file mode 100644 index 0000000..0ad4874 --- /dev/null +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -0,0 +1,1842 @@ +{ + "name": "mcp-typescript-test-sdk", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mcp-typescript-test-sdk", + "version": "1.0.0", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.25.2", + "body-parser": "^2.2.2", + "express": "^5.2.1", + "uuid": "^13.0.0", + "zod": "^4.3.5" + }, + "devDependencies": { + "@eslint/js": "^9.39.2", + "@types/cors": "^2.8.19", + "@types/express": "^5.0.6", + "@types/node": "^24.10.6", + "@types/uuid": "^10.0.0", + "cors": "^2.8.5", + "tsx": "^4.21.0", + "typescript": "^5.9.3" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@hono/node-server": { + "version": "1.19.8", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.8.tgz", + "integrity": "sha512-0/g2lIOPzX8f3vzW1ggQgvG5mjtFBDBHFAzI5SFAi2DzSqS9luJwqg9T6O/gKYLi+inS7eNxBeIFkkghIPvrMA==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.25.2", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.25.2.tgz", + "integrity": "sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.7", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "jose": "^6.1.1", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", + "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.10.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.6.tgz", + "integrity": "sha512-B8h60xgJMR/xmgyX9fncRzEW9gCxoJjdenUhke2v1JGOd/V66KopmWrLPXi5oUI4VuiGK+d+HlXJjDRZMj21EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "peer": true, + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", + "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.3.tgz", + "integrity": "sha512-PmQi306+M/ct/m5s66Hrg+adPnkD5jiO6IjA7WhWw0gSBSo1EcRegwuI1deZ+wd5pzCGynCcn2DprnE4/yEV4w==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uuid": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", + "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/zod": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", + "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25 || ^4" + } + } + } +} diff --git a/integration-test/src/jvmTest/typescript/package.json b/integration-test/src/jvmTest/typescript/package.json new file mode 100644 index 0000000..0ec09d1 --- /dev/null +++ b/integration-test/src/jvmTest/typescript/package.json @@ -0,0 +1,26 @@ +{ + "name": "mcp-typescript-test-sdk", + "version": "1.0.0", + "private": true, + "scripts": { + "server:stdio": "tsx server/stdio-server.ts", + "server:sse": "tsx server/sse-server.ts" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.25.2", + "body-parser": "^2.2.2", + "express": "^5.2.1", + "uuid": "^13.0.0", + "zod": "^4.3.5" + }, + "devDependencies": { + "@eslint/js": "^9.39.2", + "@types/cors": "^2.8.19", + "@types/express": "^5.0.6", + "@types/node": "^24.10.6", + "@types/uuid": "^10.0.0", + "cors": "^2.8.5", + "tsx": "^4.21.0", + "typescript": "^5.9.3" + } +} diff --git a/integration-test/src/jvmTest/typescript/server/server-common.ts b/integration-test/src/jvmTest/typescript/server/server-common.ts new file mode 100644 index 0000000..6b233c5 --- /dev/null +++ b/integration-test/src/jvmTest/typescript/server/server-common.ts @@ -0,0 +1,402 @@ +import {McpServer} from '@modelcontextprotocol/sdk/server/mcp'; +import {z} from 'zod'; +import { + type CallToolResult, + type GetPromptResult, + type PrimitiveSchemaDefinition, + type ReadResourceResult, + type ResourceLink, +} from '@modelcontextprotocol/sdk/types'; + +export function registerTestUtils(server: McpServer) { + // Register a simple tool that returns a greeting + server.registerTool( + 'greet', + { + title: 'Greeting Tool', + description: 'A simple greeting tool', + inputSchema: { + name: z.string().describe('Name to greet'), + }, + }, + async ({name}): Promise => { + return { + content: [ + { + type: 'text', + text: `Hello, ${name}!`, + }, + ], + }; + } + ); + + // Register a tool that sends multiple greetings with notifications + server.tool( + 'multi-greet', + 'A tool that sends different greetings with delays between them', + { + name: z.string().describe('Name to greet'), + }, + { + title: 'Multiple Greeting Tool', + readOnlyHint: true, + openWorldHint: false + }, + async ({name}, extra): Promise => { + const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + + await server.sendLoggingMessage({ + level: "debug", + data: `Starting multi-greet for ${name}` + }, extra.sessionId); + + await sleep(200); + + await server.sendLoggingMessage({ + level: "info", + data: `Sending first greeting to ${name}` + }, extra.sessionId); + + await sleep(200); + + await server.sendLoggingMessage({ + level: "info", + data: `Sending second greeting to ${name}` + }, extra.sessionId); + + return { + content: [ + { + type: 'text', + text: `Good morning, ${name}!`, + } + ], + }; + } + ); + + // Register a tool that demonstrates elicitation (user input collection) + server.tool( + 'collect-user-info', + 'A tool that collects user information through elicitation', + { + infoType: z.enum(['contact', 'preferences', 'feedback']).describe('Type of information to collect'), + }, + async ({infoType}): Promise => { + let message: string; + let requestedSchema: { + type: 'object'; + properties: Record; + required?: string[]; + }; + + switch (infoType) { + case 'contact': + message = 'Please provide your contact information'; + requestedSchema = { + type: 'object', + properties: { + name: { + type: 'string', + title: 'Full Name', + description: 'Your full name', + }, + email: { + type: 'string', + title: 'Email Address', + description: 'Your email address', + format: 'email', + }, + phone: { + type: 'string', + title: 'Phone Number', + description: 'Your phone number (optional)', + }, + }, + required: ['name', 'email'], + }; + break; + case 'preferences': + message = 'Please set your preferences'; + requestedSchema = { + type: 'object', + properties: { + theme: { + type: 'string', + title: 'Theme', + description: 'Choose your preferred theme', + enum: ['light', 'dark', 'auto'], + enumNames: ['Light', 'Dark', 'Auto'], + }, + notifications: { + type: 'boolean', + title: 'Enable Notifications', + description: 'Would you like to receive notifications?', + default: true, + }, + frequency: { + type: 'string', + title: 'Notification Frequency', + description: 'How often would you like notifications?', + enum: ['daily', 'weekly', 'monthly'], + enumNames: ['Daily', 'Weekly', 'Monthly'], + }, + }, + required: ['theme'], + }; + break; + case 'feedback': + message = 'Please provide your feedback'; + requestedSchema = { + type: 'object', + properties: { + rating: { + type: 'integer', + title: 'Rating', + description: 'Rate your experience (1-5)', + minimum: 1, + maximum: 5, + }, + comments: { + type: 'string', + title: 'Comments', + description: 'Additional comments (optional)', + maxLength: 500, + }, + recommend: { + type: 'boolean', + title: 'Would you recommend this?', + description: 'Would you recommend this to others?', + }, + }, + required: ['rating', 'recommend'], + }; + break; + default: + throw new Error(`Unknown info type: ${infoType}`); + } + + try { + // Use the underlying server instance to elicit input from the client + const result = await server.server.elicitInput({ + message, + requestedSchema, + }); + + if (result.action === 'accept') { + return { + content: [ + { + type: 'text', + text: `Thank you! Collected ${infoType} information: ${JSON.stringify(result.content, null, 2)}`, + }, + ], + }; + } else if (result.action === 'decline') { + return { + content: [ + { + type: 'text', + text: `No information was collected. User declined ${infoType} information request.`, + }, + ], + }; + } else { + return { + content: [ + { + type: 'text', + text: `Information collection was cancelled by the user.`, + }, + ], + }; + } + } catch (error) { + return { + content: [ + { + type: 'text', + text: `Error collecting ${infoType} information: ${error}`, + }, + ], + }; + } + } + ); + + // Register a simple prompt + server.registerPrompt( + 'greeting-template', + { + title: 'Greeting Template', + description: 'A simple greeting prompt template', + argsSchema: { + name: z.string().describe('Name to include in greeting'), + }, + }, + async ({name}): Promise => { + return { + messages: [ + { + role: 'user', + content: { + type: 'text', + text: `Please greet ${name} in a friendly manner.`, + }, + }, + ], + }; + } + ); + + // Register a tool specifically for testing resumability + server.tool( + 'start-notification-stream', + 'Starts sending periodic notifications for testing resumability', + { + interval: z.number().describe('Interval in milliseconds between notifications').default(100), + count: z.number().describe('Number of notifications to send (0 for 100)').default(50), + }, + async ({interval, count}, extra): Promise => { + const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + let counter = 0; + + while (count === 0 || counter < count) { + counter++; + try { + await server.sendLoggingMessage({ + level: "info", + data: `Periodic notification #${counter} at ${new Date().toISOString()}` + }, extra.sessionId); + } catch (error) { + console.error("Error sending notification:", error); + } + // Wait for the specified interval + await sleep(interval); + } + + return { + content: [ + { + type: 'text', + text: `Started sending periodic notifications every ${interval}ms`, + } + ], + }; + } + ); + + // Create a simple resource at a fixed URI + server.registerResource( + 'greeting-resource', + 'https://example.com/greetings/default', + { + title: 'Default Greeting', + description: 'A simple greeting resource', + mimeType: 'text/plain' + }, + async (): Promise => { + return { + contents: [ + { + uri: 'https://example.com/greetings/default', + text: 'Hello, world!', + }, + ], + }; + } + ); + + // Create additional resources + server.registerResource( + 'example-file-1', + 'file:///example/file1.txt', + { + title: 'Example File 1', + description: 'First example file for ResourceLink demonstration', + mimeType: 'text/plain' + }, + async (): Promise => { + return { + contents: [ + { + uri: 'file:///example/file1.txt', + text: 'This is the content of file 1', + }, + ], + }; + } + ); + + server.registerResource( + 'example-file-2', + 'file:///example/file2.txt', + { + title: 'Example File 2', + description: 'Second example file for ResourceLink demonstration', + mimeType: 'text/plain' + }, + async (): Promise => { + return { + contents: [ + { + uri: 'file:///example/file2.txt', + text: 'This is the content of file 2', + }, + ], + }; + } + ); + + // Register a tool that returns ResourceLinks + server.registerTool( + 'list-files', + { + title: 'List Files with ResourceLinks', + description: 'Returns a list of files as ResourceLinks without embedding their content', + inputSchema: { + includeDescriptions: z.boolean().optional().describe('Whether to include descriptions in the resource links'), + }, + }, + async ({includeDescriptions = true}): Promise => { + const resourceLinks: ResourceLink[] = [ + { + type: 'resource_link', + uri: 'https://example.com/greetings/default', + name: 'Default Greeting', + mimeType: 'text/plain', + ...(includeDescriptions && {description: 'A simple greeting resource'}) + }, + { + type: 'resource_link', + uri: 'file:///example/file1.txt', + name: 'Example File 1', + mimeType: 'text/plain', + ...(includeDescriptions && {description: 'First example file for ResourceLink demonstration'}) + }, + { + type: 'resource_link', + uri: 'file:///example/file2.txt', + name: 'Example File 2', + mimeType: 'text/plain', + ...(includeDescriptions && {description: 'Second example file for ResourceLink demonstration'}) + } + ]; + + return { + content: [ + { + type: 'text', + text: 'Here are the available files as resource links:', + }, + ...resourceLinks, + { + type: 'text', + text: '\nYou can read any of these resources using their URI.', + } + ], + }; + } + ); +} diff --git a/integration-test/src/jvmTest/typescript/server/sse-server.ts b/integration-test/src/jvmTest/typescript/server/sse-server.ts new file mode 100644 index 0000000..48ea453 --- /dev/null +++ b/integration-test/src/jvmTest/typescript/server/sse-server.ts @@ -0,0 +1,300 @@ +// @ts-ignore +import express, {Request, Response} from 'express'; +import {randomUUID} from 'node:crypto'; +// @ts-ignore +import cors from 'cors'; +import {McpServer} from '@modelcontextprotocol/sdk/server/mcp'; +import {StreamableHTTPServerTransport,} from '@modelcontextprotocol/sdk/server/streamableHttp'; +import {registerTestUtils} from './server-common'; +import { + getOAuthProtectedResourceMetadataUrl, + mcpAuthMetadataRouter +} from '@modelcontextprotocol/sdk/server/auth/router'; +import {requireBearerAuth} from '@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth'; +import {isInitializeRequest,} from '@modelcontextprotocol/sdk/types'; +import {InMemoryEventStore} from '@modelcontextprotocol/sdk/examples/shared/inMemoryEventStore'; +import {setupAuthServer} from '@modelcontextprotocol/sdk/examples/server/demoInMemoryOAuthProvider'; +import {checkResourceAllowed} from '@modelcontextprotocol/sdk/shared/auth-utils'; +import {OAuthMetadata} from "@modelcontextprotocol/sdk/shared/auth"; + +// import {type OAuthMetadata} from '@modelcontextprotocol/sdk/auth.js'; + +async function main() { + // Check for OAuth flag + const useOAuth = process.argv.includes('--oauth'); + const strictOAuth = process.argv.includes('--oauth-strict'); + +// Create an MCP server with implementation details + const getServer = () => { + const server = new McpServer({ + name: 'simple-streamable-http-server', + version: '1.0.0' + }, {capabilities: {logging: {}}}); + + registerTestUtils(server); + + return server; + }; + + const MCP_PORT = process.env.MCP_PORT ? parseInt(process.env.MCP_PORT, 10) : 3000; + const AUTH_PORT = process.env.MCP_AUTH_PORT ? parseInt(process.env.MCP_AUTH_PORT, 10) : 3001; + + const app = express(); + app.use(express.json()); + +// Allow CORS all domains, expose the Mcp-Session-Id header + app.use(cors({ + origin: '*', // Allow all origins + exposedHeaders: ["Mcp-Session-Id"] + })); + +// Set up OAuth if enabled + let authMiddleware = null; + if (useOAuth) { + // Create auth middleware for MCP endpoints + const mcpServerUrl = new URL(`http://localhost:${MCP_PORT}/mcp`); + const authServerUrl = new URL(`http://localhost:${AUTH_PORT}`); + + const oauthMetadata: OAuthMetadata = setupAuthServer({ + authServerUrl, + mcpServerUrl, + strictResource: strictOAuth + }); + + const tokenVerifier = { + verifyAccessToken: async (token: string) => { + const endpoint = oauthMetadata.introspection_endpoint; + + if (!endpoint) { + throw new Error('No token verification endpoint available in metadata'); + } + + const response = await fetch(endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: new URLSearchParams({ + token: token + }).toString() + }); + + + if (!response.ok) { + throw new Error(`Invalid or expired token: ${await response.text()}`); + } + + const data = await response.json(); + + if (strictOAuth) { + if (!data.aud) { + throw new Error(`Resource Indicator (RFC8707) missing`); + } + if (!checkResourceAllowed({requestedResource: data.aud, configuredResource: mcpServerUrl})) { + throw new Error(`Expected resource indicator ${mcpServerUrl}, got: ${data.aud}`); + } + } + + // Convert the response to AuthInfo format + return { + token, + clientId: data.client_id, + scopes: data.scope ? data.scope.split(' ') : [], + expiresAt: data.exp, + }; + } + } + // Add metadata routes to the main MCP server + app.use(mcpAuthMetadataRouter({ + oauthMetadata, + resourceServerUrl: mcpServerUrl, + scopesSupported: ['mcp:tools'], + resourceName: 'MCP Demo Server', + })); + + authMiddleware = requireBearerAuth({ + verifier: tokenVerifier, + requiredScopes: [], + resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(mcpServerUrl), + }); + } + +// Map to store transports by session ID + const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {}; + +// MCP POST endpoint with optional auth + const mcpPostHandler = async (req: Request, res: Response) => { + const sessionId = req.headers['mcp-session-id'] as string | undefined; + if (sessionId) { + console.log(`Received MCP request for session: ${sessionId}`); + } else { + console.log('Request body:', req.body); + } + + if (useOAuth && req.auth) { + console.log('Authenticated user:', req.auth); + } + try { + let transport: StreamableHTTPServerTransport; + if (sessionId && transports[sessionId]) { + // Reuse existing transport + transport = transports[sessionId]; + } else if (!sessionId && isInitializeRequest(req.body)) { + // New initialization request + const eventStore = new InMemoryEventStore(); + transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: () => randomUUID(), + eventStore, // Enable resumability + onsessioninitialized: (sessionId) => { + // Store the transport by session ID when session is initialized + // This avoids race conditions where requests might come in before the session is stored + console.log(`Session initialized with ID: ${sessionId}`); + transports[sessionId] = transport; + } + }); + + // Set up onclose handler to clean up transport when closed + transport.onclose = () => { + const sid = transport.sessionId; + if (sid && transports[sid]) { + console.log(`Transport closed for session ${sid}, removing from transports map`); + delete transports[sid]; + } + }; + + // Connect the transport to the MCP server BEFORE handling the request + // so responses can flow back through the same transport + const server = getServer(); + await server.connect(transport); + + await transport.handleRequest(req, res, req.body); + return; // Already handled + } else { + // Invalid request - no session ID or not initialization request + res.status(400).json({ + jsonrpc: '2.0', + error: { + code: -32000, + message: 'Bad Request: No valid session ID provided', + }, + id: null, + }); + return; + } + + // Handle the request with existing transport - no need to reconnect + // The existing transport is already connected to the server + await transport.handleRequest(req, res, req.body); + } catch (error) { + console.error('Error handling MCP request:', error); + if (!res.headersSent) { + res.status(500).json({ + jsonrpc: '2.0', + error: { + code: -32603, + message: 'Internal server error', + }, + id: null, + }); + } + } + }; + +// Set up routes with conditional auth middleware + if (useOAuth && authMiddleware) { + app.post('/mcp', authMiddleware, mcpPostHandler); + } else { + app.post('/mcp', mcpPostHandler); + } + +// Handle GET requests for SSE streams (using built-in support from StreamableHTTP) + const mcpGetHandler = async (req: Request, res: Response) => { + const sessionId = req.headers['mcp-session-id'] as string | undefined; + if (!sessionId || !transports[sessionId]) { + res.status(400).send('Invalid or missing session ID'); + return; + } + + if (useOAuth && req.auth) { + console.log('Authenticated SSE connection from user:', req.auth); + } + + // Check for Last-Event-ID header for resumability + const lastEventId = req.headers['last-event-id'] as string | undefined; + if (lastEventId) { + console.log(`Client reconnecting with Last-Event-ID: ${lastEventId}`); + } else { + console.log(`Establishing new SSE stream for session ${sessionId}`); + } + + const transport = transports[sessionId]; + await transport.handleRequest(req, res); + }; + +// Set up GET route with conditional auth middleware + if (useOAuth && authMiddleware) { + app.get('/mcp', authMiddleware, mcpGetHandler); + } else { + app.get('/mcp', mcpGetHandler); + } + +// Handle DELETE requests for session termination (according to MCP spec) + const mcpDeleteHandler = async (req: Request, res: Response) => { + const sessionId = req.headers['mcp-session-id'] as string | undefined; + if (!sessionId || !transports[sessionId]) { + res.status(400).send('Invalid or missing session ID'); + return; + } + + console.log(`Received session termination request for session ${sessionId}`); + + try { + const transport = transports[sessionId]; + await transport.handleRequest(req, res); + } catch (error) { + console.error('Error handling session termination:', error); + if (!res.headersSent) { + res.status(500).send('Error processing session termination'); + } + } + }; + +// Set up DELETE route with conditional auth middleware + if (useOAuth && authMiddleware) { + app.delete('/mcp', authMiddleware, mcpDeleteHandler); + } else { + app.delete('/mcp', mcpDeleteHandler); + } + + app.listen(MCP_PORT, (error) => { + if (error) { + console.error('Failed to start server:', error); + process.exit(1); + } + console.log(`MCP Streamable HTTP Server listening on port ${MCP_PORT}`); + }); + +// Handle server shutdown + process.on('SIGINT', async () => { + console.log('Shutting down server...'); + + // Close all active transports to properly clean up resources + for (const sessionId in transports) { + try { + console.log(`Closing transport for session ${sessionId}`); + await transports[sessionId].close(); + delete transports[sessionId]; + } catch (error) { + console.error(`Error closing transport for session ${sessionId}:`, error); + } + } + console.log('Server shutdown complete'); + process.exit(0); + }); + +} + +main().catch((err) => { + console.error('Failed to start server:', err); + process.exit(1); +}); diff --git a/integration-test/src/jvmTest/typescript/server/stdio-server.ts b/integration-test/src/jvmTest/typescript/server/stdio-server.ts new file mode 100644 index 0000000..0d71eb8 --- /dev/null +++ b/integration-test/src/jvmTest/typescript/server/stdio-server.ts @@ -0,0 +1,20 @@ +import {McpServer} from '@modelcontextprotocol/sdk/server/mcp'; +import {StdioServerTransport} from '@modelcontextprotocol/sdk/server/stdio'; +import {registerTestUtils} from './server-common'; + +async function main() { + const server = new McpServer({ + name: 'simple-stdio-server', + version: '1.0.0', + }, {capabilities: {logging: {}}}); + + registerTestUtils(server); + + const transport = new StdioServerTransport(); + await server.connect(transport); +} + +main().catch((err) => { + console.error('Failed to start stdio server:', err); + process.exit(1); +}); diff --git a/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/TypeScriptRunner.kt b/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/TypeScriptRunner.kt index f68118e..cd1d674 100644 --- a/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/TypeScriptRunner.kt +++ b/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/TypeScriptRunner.kt @@ -20,11 +20,14 @@ public object TypeScriptRunner { public fun run( typescriptDir: File, scriptPath: String, + arguments: List = emptyList(), env: Map = emptyMap(), redirectErrorStream: Boolean = false, logPrefix: String = "TS-RUNNER", ): Process { - val pb = ProcessBuilder("npx", "tsx", scriptPath) + val command = mutableListOf("npx", "tsx", scriptPath) + command.addAll(arguments) + val pb = ProcessBuilder(command) pb.directory(typescriptDir) env.forEach { (k, v) -> pb.environment()[k] = v } pb.redirectErrorStream(redirectErrorStream) From 6ad9bf212277ab352774621d166ed23cbd9321cb Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Sun, 11 Jan 2026 09:38:13 +0200 Subject: [PATCH 114/449] fix(integration-test): refactor integration test infrastructure, fix and enable tests with ts-client (#478) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Refactor integration test infrastructure, fix and enable tests with ts-client - Refactor TypeScript integration test infrastructure to support parallel execution and real I/O operations. - Extract reusable TypeScriptClient component - Migrate some tests from `runTest` to `runBlocking` for tests that spawn actual processes and communicate over real network connections. ## Key Changes - Extract `TypeScriptClient` class for managing TS client processes - Move client scripts to integration-test/src/jvmTest/typescript/client/ - Migrate typescript: `myClient.ts`(2) → `sse-client.ts`, `stdio-client.ts` - Automate TypeScript dependency installation in static initializer - Enable JUnit parallel execution (dynamic strategy, concurrent mode) with test timeout of 2 minutes - Replace `mutableListOf` with `ConcurrentLinkedQueue` for thread-safe notification handling - Migrate integration tests from `runTest` to `runBlocking` **Rationale:** Integration tests that spawn real TypeScript processes, establish actual HTTP/SSE connections, and manage server lifecycles require real-time I/O behavior. The runTest framework's virtual time is designed for fast unit tests with simulated timing, not for integration scenarios where external processes and network communication must occur in real time. Parallel execution requires thread-safe data structures (ConcurrentLinkedQueue) to safely collect notifications from multiple concurrent test instances. - Add `EmbeddedServer.actualPort()` extension to get bound port - Standardize kotlin.time.Duration usage across test utilities - Update processUtils.stopProcess() to accept Duration parameter - Replace RuntimeException checks with idiomatic check() calls - Revert `--rerun-tasks` flag to Gradle workflows for reliable builds ## Motivation and Context Integration tests with typescript client are now broken. Enabling parallel test execution will force writing thread-safe independent tests and will highlight problems faster. ## How Has This Been Tested? CI ## Breaking Changes ## Types of changes - [x] CI Update - [x] Test Fix ## 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 ## Additional context Follow up for #477 --- .github/workflows/build.yml | 4 +- .github/workflows/gradle-publish.yml | 2 +- .../sdk/client/StdioClientTransportTest.kt | 3 +- .../OldSchemaWebSocketIntegrationTest.kt | 54 ++--- .../websocket/WebSocketIntegrationTest.kt | 52 ++--- .../sse/AbstractSseIntegrationTest.kt | 2 - .../OldSchemaAbstractSseIntegrationTest.kt | 4 +- .../sse/OldSchemaSseIntegrationTest.kt | 55 ++--- .../sdk/integration/sse/SseIntegrationTest.kt | 53 ++--- .../AbstractKotlinClientTsServerTest.kt | 2 - .../AbstractTsClientKotlinServerTest.kt | 15 +- .../sdk/integration/typescript/TsTestBase.kt | 139 +++-------- .../typescript/TypeScriptClient.kt | 69 ++++++ .../KotlinClientTsServerEdgeCasesTestSse.kt | 1 - .../sse/TsClientKotlinServerTestSse.kt | 22 +- .../typescript/sse/TsEdgeCasesTestSse.kt | 47 ++-- .../stdio/TsClientKotlinServerTestStdio.kt | 2 - .../typescript/stdio/TsEdgeCasesTestStdio.kt | 2 - ...erverResourcesNotificationSubscribeTest.kt | 219 +++++++----------- .../resources/junit-platform.properties | 6 + .../client/sse-client.ts} | 33 +-- .../client/stdio-client.ts} | 28 +-- .../src/jvmTest/typescript/package.json | 2 + test-utils/build.gradle.kts | 1 + .../kotlin/test/utils/TypeScriptRunner.kt | 104 +++++++-- .../kotlin/test/utils/ktor.kt | 17 ++ .../kotlin/test/utils/processUtils.kt | 6 +- 27 files changed, 461 insertions(+), 483 deletions(-) create mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TypeScriptClient.kt create mode 100644 integration-test/src/jvmTest/resources/junit-platform.properties rename integration-test/src/jvmTest/{kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/myClient.ts => typescript/client/sse-client.ts} (71%) rename integration-test/src/jvmTest/{kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/myClient.ts => typescript/client/stdio-client.ts} (84%) create mode 100644 test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/ktor.kt diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2d336cd..1922ffb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -84,7 +84,7 @@ jobs: - name: ${{ matrix.job-name }} run: |- - ./gradlew ${{ matrix.gradle-tasks }} -x :conformance-test:test --max-workers ${{ matrix.max-workers }} --continue --configure-on-demand --parallel + ./gradlew ${{ matrix.gradle-tasks }} -x :conformance-test:test --max-workers ${{ matrix.max-workers }} --continue --rerun-tasks --configure-on-demand --parallel - name: Upload Reports if: always() @@ -168,7 +168,7 @@ jobs: - name: "Build Sample: ${{ matrix.sample }}" working-directory: ./samples/${{ matrix.sample }} - run: ./gradlew build -Pmcp.kotlin.overrideVersion=1-SNAPSHOT + run: ./gradlew build -Pmcp.kotlin.overrideVersion=1-SNAPSHOT --rerun-tasks - name: Upload Reports if: ${{ !cancelled() }} diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml index 43f4766..a140872 100644 --- a/.github/workflows/gradle-publish.yml +++ b/.github/workflows/gradle-publish.yml @@ -35,7 +35,7 @@ jobs: uses: gradle/actions/setup-gradle@v5 - name: Clean Build with Gradle - run: ./gradlew clean build -x :conformance-test:test + run: ./gradlew clean build -x :conformance-test:test --rerun-tasks - name: Publish to Maven Central Portal id: publish diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt index adea76b..be06cff 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt @@ -5,6 +5,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.Implementation import io.modelcontextprotocol.kotlin.sdk.types.McpException import io.modelcontextprotocol.kotlin.test.utils.createSleepyProcessBuilder import io.modelcontextprotocol.kotlin.test.utils.createTeeProcessBuilder +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest @@ -30,7 +31,7 @@ import kotlin.time.Duration.Companion.seconds class StdioClientTransportTest : BaseTransportTest() { @Test - fun `handle stdio error`(): Unit = runBlocking { + fun `handle stdio error`(): Unit = runBlocking(Dispatchers.IO) { val processBuilder = createSleepyProcessBuilder() val process = processBuilder.start() diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/OldSchemaWebSocketIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/OldSchemaWebSocketIntegrationTest.kt index 0ac98e3..312e3be 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/OldSchemaWebSocketIntegrationTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/OldSchemaWebSocketIntegrationTest.kt @@ -1,3 +1,5 @@ +@file:Suppress("DEPRECATION") + package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.websocket import io.ktor.client.HttpClient @@ -21,11 +23,9 @@ import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions import io.modelcontextprotocol.kotlin.sdk.server.mcpWebSocket import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequestParams import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.test.runTest -import kotlinx.coroutines.withContext +import kotlinx.coroutines.runBlocking import kotlin.test.Test import kotlin.test.assertTrue -import kotlin.time.Duration.Companion.seconds import io.ktor.client.engine.cio.CIO as ClientCIO import io.ktor.client.plugins.websocket.WebSockets as ClientWebSocket import io.ktor.server.cio.CIO as ServerCIO @@ -34,16 +34,14 @@ import io.ktor.server.websocket.WebSockets as ServerWebSockets class OldSchemaWebSocketIntegrationTest { @Test - fun `client should be able to connect to websocket server 2`() = runTest(timeout = 5.seconds) { + fun `client should be able to connect to websocket server 2`() = runBlocking(Dispatchers.IO) { var server: EmbeddedServer? = null var client: Client? = null try { - withContext(Dispatchers.Default) { - server = initServer() - val port = server.engine.resolvedConnectors().first().port - client = initClient(serverPort = port) - } + server = initServer() + val port = server.engine.resolvedConnectors().first().port + client = initClient(serverPort = port) } finally { client?.close() server?.stop(1000, 2000) @@ -58,19 +56,17 @@ class OldSchemaWebSocketIntegrationTest { * 3. Observe that Client A receives a response related to it. */ @Test - fun `single websocket connection`() = runTest(timeout = 5.seconds) { + fun `single websocket connection`() = runBlocking(Dispatchers.IO) { var server: EmbeddedServer? = null var client: Client? = null try { - withContext(Dispatchers.Default) { - server = initServer() - val port = server.engine.resolvedConnectors().first().port - client = initClient("Client A", port) + server = initServer() + val port = server.engine.resolvedConnectors().first().port + client = initClient("Client A", port) - val promptA = getPrompt(client, "Client A") - assertTrue { "Client A" in promptA } - } + val promptA = getPrompt(client, "Client A") + assertTrue { "Client A" in promptA } } finally { client?.close() server?.stop(1000, 2000) @@ -86,26 +82,24 @@ class OldSchemaWebSocketIntegrationTest { * 4. Observe that Client B (connection #2) receives a response related to sessionId#1. */ @Test - fun `multiple websocket connections`() = runTest(timeout = 5.seconds) { + fun `multiple websocket connections`() = runBlocking(Dispatchers.IO) { var server: EmbeddedServer? = null var clientA: Client? = null var clientB: Client? = null try { - withContext(Dispatchers.Default) { - server = initServer() - val port = server.engine.resolvedConnectors().first().port - clientA = initClient("Client A", port) - clientB = initClient("Client B", port) + server = initServer() + val port = server.engine.resolvedConnectors().first().port + clientA = initClient("Client A", port) + clientB = initClient("Client B", port) - // Step 3: Send a prompt request from Client A - val promptA = getPrompt(clientA, "Client A") - // Step 4: Send a prompt request from Client B - val promptB = getPrompt(clientB, "Client B") + // Step 3: Send a prompt request from Client A + val promptA = getPrompt(clientA, "Client A") + // Step 4: Send a prompt request from Client B + val promptB = getPrompt(clientB, "Client B") - assertTrue { "Client A" in promptA } - assertTrue { "Client B" in promptB } - } + assertTrue { "Client A" in promptA } + assertTrue { "Client B" in promptB } } finally { clientA?.close() clientB?.close() diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/WebSocketIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/WebSocketIntegrationTest.kt index ab0d848..7d7898e 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/WebSocketIntegrationTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/WebSocketIntegrationTest.kt @@ -21,11 +21,9 @@ import io.modelcontextprotocol.kotlin.sdk.types.Role import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.types.TextContent import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.test.runTest -import kotlinx.coroutines.withContext +import kotlinx.coroutines.runBlocking import kotlin.test.Test import kotlin.test.assertTrue -import kotlin.time.Duration.Companion.seconds import io.ktor.client.engine.cio.CIO as ClientCIO import io.ktor.client.plugins.websocket.WebSockets as ClientWebSocket import io.ktor.server.cio.CIO as ServerCIO @@ -34,16 +32,14 @@ import io.ktor.server.websocket.WebSockets as ServerWebSockets class WebSocketIntegrationTest { @Test - fun `client should be able to connect to websocket server 2`() = runTest(timeout = 5.seconds) { + fun `client should be able to connect to websocket server 2`() = runBlocking(Dispatchers.IO) { var server: EmbeddedServer? = null var client: Client? = null try { - withContext(Dispatchers.Default) { - server = initServer() - val port = server.engine.resolvedConnectors().first().port - client = initClient(serverPort = port) - } + server = initServer() + val port = server.engine.resolvedConnectors().first().port + client = initClient(serverPort = port) } finally { client?.close() server?.stop(1000, 2000) @@ -58,19 +54,17 @@ class WebSocketIntegrationTest { * 3. Observe that Client A receives a response related to it. */ @Test - fun `single websocket connection`() = runTest(timeout = 5.seconds) { + fun `single websocket connection`() = runBlocking(Dispatchers.IO) { var server: EmbeddedServer? = null var client: Client? = null try { - withContext(Dispatchers.Default) { - server = initServer() - val port = server.engine.resolvedConnectors().first().port - client = initClient("Client A", port) + server = initServer() + val port = server.engine.resolvedConnectors().first().port + client = initClient("Client A", port) - val promptA = getPrompt(client, "Client A") - assertTrue { "Client A" in promptA } - } + val promptA = getPrompt(client, "Client A") + assertTrue { "Client A" in promptA } } finally { client?.close() server?.stop(1000, 2000) @@ -86,26 +80,24 @@ class WebSocketIntegrationTest { * 4. Observe that Client B (connection #2) receives a response related to sessionId#1. */ @Test - fun `multiple websocket connections`() = runTest(timeout = 5.seconds) { + fun `multiple websocket connections`() = runBlocking(Dispatchers.IO) { var server: EmbeddedServer? = null var clientA: Client? = null var clientB: Client? = null try { - withContext(Dispatchers.Default) { - server = initServer() - val port = server.engine.resolvedConnectors().first().port - clientA = initClient("Client A", port) - clientB = initClient("Client B", port) + server = initServer() + val port = server.engine.resolvedConnectors().first().port + clientA = initClient("Client A", port) + clientB = initClient("Client B", port) - // Step 3: Send a prompt request from Client A - val promptA = getPrompt(clientA, "Client A") - // Step 4: Send a prompt request from Client B - val promptB = getPrompt(clientB, "Client B") + // Step 3: Send a prompt request from Client A + val promptA = getPrompt(clientA, "Client A") + // Step 4: Send a prompt request from Client B + val promptB = getPrompt(clientB, "Client B") - assertTrue { "Client A" in promptA } - assertTrue { "Client B" in promptB } - } + assertTrue { "Client A" in promptA } + assertTrue { "Client B" in promptB } } finally { clientA?.close() clientB?.close() diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/AbstractSseIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/AbstractSseIntegrationTest.kt index 8f6aed6..2a2f381 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/AbstractSseIntegrationTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/AbstractSseIntegrationTest.kt @@ -25,8 +25,6 @@ import io.ktor.server.sse.SSE as ServerSSE open class AbstractSseIntegrationTest { - suspend fun EmbeddedServer<*, *>.actualPort() = engine.resolvedConnectors().single().port - suspend fun initTestClient(serverPort: Int, name: String? = null): Client { val client = Client( Implementation(name = name ?: DEFAULT_CLIENT_NAME, version = VERSION), diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaAbstractSseIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaAbstractSseIntegrationTest.kt index a0b3e19..82d51b9 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaAbstractSseIntegrationTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaAbstractSseIntegrationTest.kt @@ -1,3 +1,5 @@ +@file:Suppress("DEPRECATION") + package io.modelcontextprotocol.kotlin.sdk.integration.sse import io.ktor.client.HttpClient @@ -25,8 +27,6 @@ import io.ktor.server.sse.SSE as ServerSSE open class OldSchemaAbstractSseIntegrationTest { - suspend fun EmbeddedServer<*, *>.actualPort() = engine.resolvedConnectors().single().port - suspend fun initTestClient(serverPort: Int, name: String? = null): Client { val client = Client( Implementation(name = name ?: DEFAULT_CLIENT_NAME, version = VERSION), diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaSseIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaSseIntegrationTest.kt index a5cb133..97313bd 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaSseIntegrationTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaSseIntegrationTest.kt @@ -1,3 +1,5 @@ +@file:Suppress("DEPRECATION") + package io.modelcontextprotocol.kotlin.sdk.integration.sse import io.ktor.server.cio.CIOApplicationEngine @@ -6,26 +8,23 @@ import io.modelcontextprotocol.kotlin.sdk.GetPromptRequest import io.modelcontextprotocol.kotlin.sdk.TextContent import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequestParams +import io.modelcontextprotocol.kotlin.test.utils.actualPort import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.test.runTest -import kotlinx.coroutines.withContext +import kotlinx.coroutines.runBlocking import kotlin.test.Test import kotlin.test.assertTrue -import kotlin.time.Duration.Companion.seconds class OldSchemaSseIntegrationTest : OldSchemaAbstractSseIntegrationTest() { @Test - fun `client should be able to connect to sse server`() = runTest(timeout = 5.seconds) { + fun `client should be able to connect to sse server`() = runBlocking(Dispatchers.IO) { var server: EmbeddedServer? = null var client: Client? = null try { - withContext(Dispatchers.Default) { - server = initTestServer() - val port = server.engine.resolvedConnectors().single().port - client = initTestClient(serverPort = port) - } + server = initTestServer() + val port = server.actualPort() + client = initTestClient(serverPort = port) } finally { client?.close() server?.stopSuspend(1000, 2000) @@ -40,18 +39,16 @@ class OldSchemaSseIntegrationTest : OldSchemaAbstractSseIntegrationTest() { * 3. Observe that Client A receives a response related to it. */ @Test - fun `single sse connection`() = runTest(timeout = 5.seconds) { + fun `single sse connection`() = runBlocking(Dispatchers.IO) { var server: EmbeddedServer? = null var client: Client? = null try { - withContext(Dispatchers.Default) { - server = initTestServer() - val port = server.engine.resolvedConnectors().single().port - client = initTestClient(port, "Client A") + server = initTestServer() + val port = server.actualPort() + client = initTestClient(port, "Client A") - val promptA = getPrompt(client, "Client A") - assertTrue { "Client A" in promptA } - } + val promptA = getPrompt(client, "Client A") + assertTrue { "Client A" in promptA } } finally { client?.close() server?.stopSuspend(1000, 2000) @@ -67,27 +64,25 @@ class OldSchemaSseIntegrationTest : OldSchemaAbstractSseIntegrationTest() { * 4. Observe that Client B (connection #2) receives a response related to sessionId#1. */ @Test - fun `multiple sse connections`() = runTest(timeout = 5.seconds) { + fun `multiple sse connections`() = runBlocking(Dispatchers.IO) { var server: EmbeddedServer? = null var clientA: Client? = null var clientB: Client? = null try { - withContext(Dispatchers.Default) { - server = initTestServer() - val port = server.engine.resolvedConnectors().first().port + server = initTestServer() + val port = server.engine.resolvedConnectors().first().port - clientA = initTestClient(port, "Client A") - clientB = initTestClient(port, "Client B") + clientA = initTestClient(port, "Client A") + clientB = initTestClient(port, "Client B") - // Step 3: Send a prompt request from Client A - val promptA = getPrompt(clientA, "Client A") - // Step 4: Send a prompt request from Client B - val promptB = getPrompt(clientB, "Client B") + // Step 3: Send a prompt request from Client A + val promptA = getPrompt(clientA, "Client A") + // Step 4: Send a prompt request from Client B + val promptB = getPrompt(clientB, "Client B") - assertTrue { "Client A" in promptA } - assertTrue { "Client B" in promptB } - } + assertTrue { "Client A" in promptA } + assertTrue { "Client B" in promptB } } finally { clientA?.close() clientB?.close() diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/SseIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/SseIntegrationTest.kt index 0826eb2..8f0516e 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/SseIntegrationTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/SseIntegrationTest.kt @@ -6,26 +6,23 @@ import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequestParams import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.test.utils.actualPort import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.test.runTest -import kotlinx.coroutines.withContext +import kotlinx.coroutines.runBlocking import kotlin.test.Test import kotlin.test.assertTrue -import kotlin.time.Duration.Companion.seconds class SseIntegrationTest : AbstractSseIntegrationTest() { @Test - fun `client should be able to connect to sse server`() = runTest(timeout = 5.seconds) { + fun `client should be able to connect to sse server`() = runBlocking(Dispatchers.IO) { var server: EmbeddedServer? = null var client: Client? = null try { - withContext(Dispatchers.Default) { - server = initTestServer() - val port = server.engine.resolvedConnectors().single().port - client = initTestClient(serverPort = port) - } + server = initTestServer() + val port = server.actualPort() + client = initTestClient(serverPort = port) } finally { client?.close() server?.stopSuspend(1000, 2000) @@ -40,18 +37,16 @@ class SseIntegrationTest : AbstractSseIntegrationTest() { * 3. Observe that Client A receives a response related to it. */ @Test - fun `single sse connection`() = runTest(timeout = 5.seconds) { + fun `single sse connection`() = runBlocking(Dispatchers.IO) { var server: EmbeddedServer? = null var client: Client? = null try { - withContext(Dispatchers.Default) { - server = initTestServer() - val port = server.engine.resolvedConnectors().single().port - client = initTestClient(port, "Client A") + server = initTestServer() + val port = server.actualPort() + client = initTestClient(port, "Client A") - val promptA = getPrompt(client, "Client A") - assertTrue { "Client A" in promptA } - } + val promptA = getPrompt(client, "Client A") + assertTrue { "Client A" in promptA } } finally { client?.close() server?.stopSuspend(1000, 2000) @@ -67,27 +62,25 @@ class SseIntegrationTest : AbstractSseIntegrationTest() { * 4. Observe that Client B (connection #2) receives a response related to sessionId#1. */ @Test - fun `multiple sse connections`() = runTest(timeout = 5.seconds) { + fun `multiple sse connections`() = runBlocking(Dispatchers.IO) { var server: EmbeddedServer? = null var clientA: Client? = null var clientB: Client? = null try { - withContext(Dispatchers.Default) { - server = initTestServer() - val port = server.engine.resolvedConnectors().first().port + server = initTestServer() + val port = server.actualPort() - clientA = initTestClient(port, "Client A") - clientB = initTestClient(port, "Client B") + clientA = initTestClient(port, "Client A") + clientB = initTestClient(port, "Client B") - // Step 3: Send a prompt request from Client A - val promptA = getPrompt(clientA, "Client A") - // Step 4: Send a prompt request from Client B - val promptB = getPrompt(clientB, "Client B") + // Step 3: Send a prompt request from Client A + val promptA = getPrompt(clientA, "Client A") + // Step 4: Send a prompt request from Client B + val promptB = getPrompt(clientB, "Client B") - assertTrue { "Client A" in promptA } - assertTrue { "Client B" in promptB } - } + assertTrue { "Client A" in promptA } + assertTrue { "Client B" in promptB } } finally { clientA?.close() clientB?.close() diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractKotlinClientTsServerTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractKotlinClientTsServerTest.kt index 9a384f1..1ed3cf4 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractKotlinClientTsServerTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractKotlinClientTsServerTest.kt @@ -2,7 +2,6 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.types.TextContent -import io.modelcontextprotocol.kotlin.test.utils.DisabledOnCI import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Test @@ -12,7 +11,6 @@ import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue -@DisabledOnCI abstract class AbstractKotlinClientTsServerTest : TsTestBase() { protected abstract suspend fun useClient(block: suspend (Client) -> T): T diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractTsClientKotlinServerTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractTsClientKotlinServerTest.kt index 90088aa..7f71b6a 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractTsClientKotlinServerTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractTsClientKotlinServerTest.kt @@ -1,13 +1,14 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript -import io.modelcontextprotocol.kotlin.test.utils.DisabledOnCI +import io.kotest.assertions.nondeterministic.eventually +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Test import org.junit.jupiter.api.Timeout import java.util.concurrent.TimeUnit import kotlin.test.assertTrue +import kotlin.time.Duration.Companion.seconds -@DisabledOnCI abstract class AbstractTsClientKotlinServerTest : TsTestBase() { protected open fun beforeServer() {} @@ -25,8 +26,10 @@ abstract class AbstractTsClientKotlinServerTest : TsTestBase() { try { val testName = "TestUser" val out = runClient("greet", testName) - assertTrue(out.contains("Text content:"), "Output should contain the text content section.\n$out") - assertTrue(out.contains("Hello, $testName!"), "Tool response should contain the greeting.\n$out") + eventually(5.seconds) { + assertTrue(out.contains("Text content:"), "Output should contain the text content section.\n$out") + assertTrue(out.contains("Hello, $testName!"), "Tool response should contain the greeting.\n$out") + } assertTrue( out.contains("Structured content:"), "Output should contain the structured content section.\n$out", @@ -46,7 +49,7 @@ abstract class AbstractTsClientKotlinServerTest : TsTestBase() { @Test @Timeout(60, unit = TimeUnit.SECONDS) - fun notifications() = runTest { + fun notifications() = runBlocking { beforeServer() try { val name = "NotifUser" @@ -66,7 +69,7 @@ abstract class AbstractTsClientKotlinServerTest : TsTestBase() { @Test @Timeout(120, unit = TimeUnit.SECONDS) - fun multipleClientSequence() = runTest { + fun multipleClientSequence() = runBlocking { beforeServer() try { val out1 = runClient("greet", "FirstClient") diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TsTestBase.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TsTestBase.kt index 313480f..49632c4 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TsTestBase.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TsTestBase.kt @@ -11,7 +11,7 @@ import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport import io.modelcontextprotocol.kotlin.sdk.types.Implementation import io.modelcontextprotocol.kotlin.test.utils.Retry -import io.modelcontextprotocol.kotlin.test.utils.TypeScriptRunner +import io.modelcontextprotocol.kotlin.test.utils.TypeScriptRunner.installDependencies import io.modelcontextprotocol.kotlin.test.utils.isWindows import kotlinx.coroutines.withTimeout import kotlinx.io.Sink @@ -19,12 +19,11 @@ import kotlinx.io.Source import kotlinx.io.asSink import kotlinx.io.asSource import kotlinx.io.buffered -import org.junit.jupiter.api.BeforeAll import java.io.BufferedReader import java.io.File import java.io.InputStreamReader import java.util.concurrent.TimeUnit -import kotlin.io.path.createTempDirectory +import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds enum class TransportKind { SSE, STDIO } @@ -34,20 +33,14 @@ abstract class TsTestBase { protected abstract val transportKind: TransportKind + protected val tsClient: TypeScriptClient get() = TypeScriptClient(typescriptDir) + companion object { @JvmStatic - protected val projectRoot: File get() = File(System.getProperty("user.dir")) + protected val projectRoot = File(System.getProperty("user.dir")) @JvmStatic - protected val tsClientDir: File - get() = File(projectRoot, "src/jvmTest/typescript") - - @JvmStatic - protected val tsSourceDir: File - get() = File( - projectRoot, - "src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript", - ) + protected val typescriptDir = File(projectRoot, "src/jvmTest/typescript") @JvmStatic private var sharedSseServer: Process? = null @@ -55,12 +48,16 @@ abstract class TsTestBase { @JvmStatic private var sharedSsePort: Int = 0 + init { + installDependencies(typescriptDir) + } + @JvmStatic @Synchronized protected fun getSharedSseUrl(): String { if (sharedSseServer == null || !sharedSseServer!!.isAlive) { sharedSsePort = io.modelcontextprotocol.kotlin.test.utils.findFreePort() - val server = TypeScriptServer(tsClientDir) + val server = TypeScriptServer(typescriptDir) sharedSseServer = server.startSse(sharedSsePort) println("Shared TypeScript SSE server started on port $sharedSsePort") @@ -76,69 +73,6 @@ abstract class TsTestBase { return "http://localhost:$sharedSsePort/mcp" } - @JvmStatic - private val tempRootDir: File = createTempDirectory("typescript-sdk-").toFile().apply { deleteOnExit() } - - @JvmStatic - protected val sdkDir: File = File(tempRootDir, "typescript-sdk") - - @JvmStatic - @BeforeAll - @Deprecated("Use setupTypeScriptSdkWithDependencies instead") - fun setupTypeScriptSdk() { - println("Cloning TypeScript SDK repository") - - if (!sdkDir.exists()) { - val process = ProcessBuilder( - "git", - "clone", - "--depth", - "1", - "https://github.com/modelcontextprotocol/typescript-sdk.git", - sdkDir.absolutePath, - ) - .redirectErrorStream(true) - .start() - val exitCode = process.waitFor() - require(exitCode == 0) { - "Failed to clone TypeScript SDK repository: exit code $exitCode" - } - } - - println("Installing TypeScript SDK dependencies") - val npmInstallResult = executeCommand("npm install", sdkDir, allowFailure = true, timeoutSeconds = null) - if (npmInstallResult.contains("EUNSUPPORTEDPROTOCOL") || npmInstallResult.contains("catalog:")) { - println("npm install failed due to catalog protocol. Trying pnpm...") - try { - // Try to use npx pnpm to ensure it's available - executeCommand("npx pnpm install", sdkDir, allowFailure = false, timeoutSeconds = null) - } catch (e: Exception) { - println("pnpm install failed or not found: ${e.message}") - println("Attempting to patch package.json to remove catalog: protocol and use npm") - patchPackageJson(sdkDir) - // Also patch other package.json files in the workspace if they exist - sdkDir.walk().filter { it.name == "package.json" }.forEach { patchPackageJson(it.parentFile) } - executeCommand("npm install", sdkDir, allowFailure = false, timeoutSeconds = null) - } - } - } - - @Deprecated("never patch package.json") - private fun patchPackageJson(dir: File) { - val packageJson = File(dir, "package.json") - if (!packageJson.exists()) return - var content = packageJson.readText() - // Replace "catalog:something" with "*" or a reasonable default if we knew it. - // Since we don't know the exact versions in the catalog, "*" is a risky but possible fallback for tests. - // However, a better approach is to use pnpm if available. - content = content.replace(Regex(""""catalog:[^"]*""""), "\"*\"") - // Also replace workspace: protocol if we are using npm fallback - content = content.replace(Regex(""""workspace:[^"]*""""), "\"*\"") - // And any other unsupported protocols that might be in the package.json - content = content.replace(Regex(""""link:[^"]*""""), "\"*\"") - packageJson.writeText(content) - } - @JvmStatic protected fun executeCommand( command: String, @@ -147,21 +81,21 @@ abstract class TsTestBase { timeoutSeconds: Long? = null, ): String { if (!workingDir.exists()) { - if (!workingDir.mkdirs()) { - throw RuntimeException("Failed to create working directory: ${workingDir.absolutePath}") + check(workingDir.mkdirs()) { + "Failed to create working directory: ${workingDir.absolutePath}" } } - if (!workingDir.isDirectory || !workingDir.canRead()) { - throw RuntimeException("Working directory is not accessible: ${workingDir.absolutePath}") + check(workingDir.isDirectory && workingDir.canRead()) { + "Working directory is not accessible: ${workingDir.absolutePath}" } val processBuilder = if (isWindows) { ProcessBuilder() - .command("cmd.exe", "/c", "set TYPESCRIPT_SDK_DIR=${sdkDir.absolutePath} && $command") + .command("cmd.exe", "/c", command) } else { ProcessBuilder() - .command("bash", "-c", "TYPESCRIPT_SDK_DIR='${sdkDir.absolutePath}' $command") + .command("bash", "-c", command) } val process = processBuilder @@ -192,24 +126,18 @@ abstract class TsTestBase { } } - private fun waitForProcessTermination(process: Process, timeoutSeconds: Long): Boolean { - io.modelcontextprotocol.kotlin.test.utils.stopProcess(process) - return true - } - protected fun waitForPort(host: String = "localhost", port: Int, timeoutSeconds: Long = 10): Boolean = io.modelcontextprotocol.kotlin.test.utils.waitForPort(host, port, timeoutSeconds) protected fun executeCommandAllowingFailure(command: String, workingDir: File, timeoutSeconds: Long = 20): String = executeCommand(command, workingDir, allowFailure = true, timeoutSeconds = timeoutSeconds) - protected fun startTypeScriptServer(port: Int): Process { - val server = TypeScriptServer(tsClientDir) - return server.startSse(port) - } - - protected fun stopProcess(process: Process, waitSeconds: Long = 3, name: String = "TypeScript server") { - io.modelcontextprotocol.kotlin.test.utils.stopProcess(process) + protected fun stopProcess( + process: Process, + waitDuration: Duration = 3.seconds, + name: String = "TypeScript server", + ) { + io.modelcontextprotocol.kotlin.test.utils.stopProcess(process, waitDuration) println("$name stopped") } @@ -232,7 +160,7 @@ abstract class TsTestBase { // ===== STDIO client + server helpers ===== protected fun startTypeScriptServerStdio(): Process { - val server = TypeScriptServer(tsClientDir) + val server = TypeScriptServer(typescriptDir) return server.startStdio() } @@ -263,22 +191,9 @@ abstract class TsTestBase { } // ===== Helpers to run TypeScript client over STDIO against Kotlin server over STDIO ===== - protected fun runStdioClient(vararg args: String): String { + protected fun runStdioClient(vararg args: String): String = tsClient.use { client -> // Start Node stdio client (it will speak MCP over its stdout/stdin) - val subDirName = when (transportKind) { - TransportKind.STDIO -> "stdio" - TransportKind.SSE -> "sse" - } - val clientPath = File(tsSourceDir, "$subDirName/myClient.ts").absolutePath - - val process = TypeScriptRunner.run( - typescriptDir = tsClientDir, - scriptPath = clientPath, - arguments = args.toList(), - env = mapOf("TYPESCRIPT_SDK_DIR" to sdkDir.absolutePath), - redirectErrorStream = false, - logPrefix = "TS-CLIENT-STDIO", - ) + val process = client.startStdio(args.toList(), log = false) // Create Kotlin server and attach stdio transport to the process streams val server: Server = KotlinServerForTsClient().createMcpServer() @@ -327,6 +242,6 @@ abstract class TsTestBase { } catch (_: Exception) { } - return output.toString() + output.toString() } } diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TypeScriptClient.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TypeScriptClient.kt new file mode 100644 index 0000000..7664abf --- /dev/null +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TypeScriptClient.kt @@ -0,0 +1,69 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.typescript + +import io.modelcontextprotocol.kotlin.test.utils.TypeScriptRunner +import io.modelcontextprotocol.kotlin.test.utils.stopProcess +import java.io.File + +/** + * Component for starting and managing TypeScript MCP clients for integration tests. + * + * This class provides methods to start TypeScript clients using SSE or STDIO transports. + * The clients are started using npx tsx to execute TypeScript files directly. + */ +class TypeScriptClient(private val typescriptDir: File) : AutoCloseable { + private var process: Process? = null + + /** + * Starts a TypeScript MCP client using SSE transport. + * + * @param arguments Arguments to pass to the client (typically server URL, tool name, and tool args) + * @param log Whether to automatically log process output/error + * @return The started Process + */ + fun startSse(arguments: List, log: Boolean = true): Process { + val scriptPath = File(typescriptDir, "client/sse-client.ts").absolutePath + val proc = TypeScriptRunner.run( + typescriptDir = typescriptDir, + scriptPath = scriptPath, + arguments = arguments, + env = emptyMap(), + redirectErrorStream = false, + logPrefix = "TS-CLIENT-SSE", + log = log, + ) + process = proc + return proc + } + + /** + * Starts a TypeScript MCP client using STDIO transport. + * + * @param arguments Arguments to pass to the client (typically tool name and tool args) + * @param log Whether to automatically log process output/error + * @return The started Process + */ + fun startStdio(arguments: List, log: Boolean = true): Process { + val scriptPath = File(typescriptDir, "client/stdio-client.ts").absolutePath + val proc = TypeScriptRunner.run( + typescriptDir = typescriptDir, + scriptPath = scriptPath, + arguments = arguments, + env = emptyMap(), + redirectErrorStream = false, + logPrefix = "TS-CLIENT-STDIO", + log = log, + ) + process = proc + return proc + } + + /** + * Stops the currently running client process. + */ + override fun close() { + process?.let { + stopProcess(it) + } + process = null + } +} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerEdgeCasesTestSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerEdgeCasesTestSse.kt index d434f89..356fd30 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerEdgeCasesTestSse.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerEdgeCasesTestSse.kt @@ -25,7 +25,6 @@ class KotlinClientTsServerEdgeCasesTestSse : TsTestBase() { override val transportKind = TransportKind.SSE - private val host = "localhost" private val serverUrl: String by lazy { getSharedSseUrl() } private lateinit var client: Client diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsClientKotlinServerTestSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsClientKotlinServerTestSse.kt index e1c8302..dd43b70 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsClientKotlinServerTestSse.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsClientKotlinServerTestSse.kt @@ -2,13 +2,11 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse import io.modelcontextprotocol.kotlin.sdk.integration.typescript.AbstractTsClientKotlinServerTest import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind -import io.modelcontextprotocol.kotlin.test.utils.DisabledOnCI import io.modelcontextprotocol.kotlin.test.utils.findFreePort import io.modelcontextprotocol.kotlin.test.utils.killProcessOnPort import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach -@DisabledOnCI class TsClientKotlinServerTestSse : AbstractTsClientKotlinServerTest() { override val transportKind = TransportKind.SSE @@ -37,18 +35,16 @@ class TsClientKotlinServerTestSse : AbstractTsClientKotlinServerTest() { } } - override fun beforeServer() {} - override fun afterServer() {} - - override fun runClient(vararg args: String): String { - val cmd = buildString { - append("npx tsx myClient.ts ") - append(serverUrl) - if (args.isNotEmpty()) { - append(' ') - append(args.joinToString(" ")) + override fun runClient(vararg args: String): String = tsClient.use { client -> + val process = client.startSse(listOf(serverUrl) + args.toList(), log = true) + val output = StringBuilder() + process.inputStream.bufferedReader().useLines { lines -> + lines.forEach { line -> + println("[TS-CLIENT-SSE] $line") + output.append(line).append("\n") } } - return executeCommand(cmd, tsClientDir) + process.waitFor(25, java.util.concurrent.TimeUnit.SECONDS) + output.toString() } } diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsEdgeCasesTestSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsEdgeCasesTestSse.kt index 8023678..ee81ce2 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsEdgeCasesTestSse.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsEdgeCasesTestSse.kt @@ -2,18 +2,18 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TsTestBase -import io.modelcontextprotocol.kotlin.test.utils.DisabledOnCI import io.modelcontextprotocol.kotlin.test.utils.findFreePort import io.modelcontextprotocol.kotlin.test.utils.killProcessOnPort import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.Timeout -import org.junit.jupiter.api.condition.EnabledOnOs +import org.junit.jupiter.api.condition.DisabledOnOs import org.junit.jupiter.api.condition.OS import java.io.File import java.util.concurrent.TimeUnit @@ -21,7 +21,6 @@ import kotlin.test.assertEquals import kotlin.test.assertTrue import kotlin.test.fail -@DisabledOnCI class TsEdgeCasesTestSse : TsTestBase() { override val transportKind = TransportKind.SSE @@ -56,16 +55,16 @@ class TsEdgeCasesTestSse : TsTestBase() { @Test @Timeout(30, unit = TimeUnit.SECONDS) fun testInvalidURL() = runTest { - val nonExistentToolCommand = "npx tsx myClient.ts $serverUrl non-existent-tool" - val nonExistentToolOutput = executeCommandAllowingFailure(nonExistentToolCommand, tsClientDir) + val nonExistentToolCommand = "npx tsx client/sse-client.ts $serverUrl non-existent-tool" + val nonExistentToolOutput = executeCommandAllowingFailure(nonExistentToolCommand, typescriptDir) assertTrue( nonExistentToolOutput.contains("Tool \"non-existent-tool\" not found"), "Client should handle non-existent tool gracefully", ) - val invalidUrlCommand = "npx tsx myClient.ts http://localhost:${port + 1000}/mcp greet TestUser" - val invalidUrlOutput = executeCommandAllowingFailure(invalidUrlCommand, tsClientDir) + val invalidUrlCommand = "npx tsx client/sse-client.ts http://localhost:${port + 1000}/mcp greet TestUser" + val invalidUrlOutput = executeCommandAllowingFailure(invalidUrlCommand, typescriptDir) assertTrue( invalidUrlOutput.contains("Invalid URL") || @@ -85,8 +84,8 @@ class TsEdgeCasesTestSse : TsTestBase() { tempFile.deleteOnExit() val specialCharsContent = tempFile.readText() - val specialCharsCommand = "npx tsx myClient.ts $serverUrl greet \"$specialCharsContent\"" - val specialCharsOutput = executeCommand(specialCharsCommand, tsClientDir) + val specialCharsCommand = "npx tsx client/sse-client.ts $serverUrl greet \"$specialCharsContent\"" + val specialCharsOutput = executeCommand(specialCharsCommand, typescriptDir) assertTrue( specialCharsOutput.contains("Hello, $specialChars!"), @@ -100,8 +99,8 @@ class TsEdgeCasesTestSse : TsTestBase() { // skip on windows as it can't handle long commands @Test - @Timeout(30, unit = TimeUnit.SECONDS) - @EnabledOnOs(OS.MAC, OS.LINUX) + @Timeout(40, unit = TimeUnit.SECONDS) + @DisabledOnOs(OS.WINDOWS) fun testLargePayload() = runTest { val largeName = "A".repeat(10 * 1024) @@ -110,8 +109,8 @@ class TsEdgeCasesTestSse : TsTestBase() { tempFile.deleteOnExit() val largeNameContent = tempFile.readText() - val largePayloadCommand = "npx tsx myClient.ts $serverUrl greet \"$largeNameContent\"" - val largePayloadOutput = executeCommand(largePayloadCommand, tsClientDir) + val largePayloadCommand = "npx tsx client/sse-client.ts $serverUrl greet \"$largeNameContent\"" + val largePayloadOutput = executeCommand(largePayloadCommand, typescriptDir) tempFile.delete() @@ -126,8 +125,8 @@ class TsEdgeCasesTestSse : TsTestBase() { } @Test - @Timeout(60, unit = TimeUnit.SECONDS) - fun testComplexConcurrentRequests() = runTest { + @Timeout(30, unit = TimeUnit.SECONDS) + fun testComplexConcurrentRequests(): Unit = runBlocking { fun prettyFail( index: Int, command: String, @@ -158,18 +157,18 @@ class TsEdgeCasesTestSse : TsTestBase() { } val commands = listOf( - "npx tsx myClient.ts $serverUrl greet \"Client1\"", - "npx tsx myClient.ts $serverUrl multi-greet \"Client2\"", - "npx tsx myClient.ts $serverUrl greet \"Client3\"", - "npx tsx myClient.ts $serverUrl", - "npx tsx myClient.ts $serverUrl multi-greet \"Client5\"", + "npx tsx client/sse-client.ts $serverUrl greet \"Client1\"", + "npx tsx client/sse-client.ts $serverUrl multi-greet \"Client2\"", + "npx tsx client/sse-client.ts $serverUrl greet \"Client3\"", + "npx tsx client/sse-client.ts $serverUrl", + "npx tsx client/sse-client.ts $serverUrl multi-greet \"Client5\"", ) coroutineScope { val jobs = commands.mapIndexed { index, command -> async(kotlinx.coroutines.Dispatchers.IO) { println("Starting client $index") - val output = executeCommand(command, tsClientDir) + val output = executeCommand(command, typescriptDir) println("Client $index completed") assertContains( @@ -241,10 +240,10 @@ class TsEdgeCasesTestSse : TsTestBase() { @Test @Timeout(120, unit = TimeUnit.SECONDS) - fun testRapidSequentialRequests() = runTest { + fun testRapidSequentialRequests() = runBlocking { val outputs = (1..10).map { i -> - val command = "npx tsx myClient.ts $serverUrl greet \"RapidClient$i\"" - val output = executeCommand(command, tsClientDir) + val command = "npx tsx client/sse-client.ts $serverUrl greet \"RapidClient$i\"" + val output = executeCommand(command, typescriptDir) assertTrue( output.contains("Connected to server"), diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/TsClientKotlinServerTestStdio.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/TsClientKotlinServerTestStdio.kt index 0751c2e..abf821b 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/TsClientKotlinServerTestStdio.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/TsClientKotlinServerTestStdio.kt @@ -2,9 +2,7 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.stdio import io.modelcontextprotocol.kotlin.sdk.integration.typescript.AbstractTsClientKotlinServerTest import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind -import io.modelcontextprotocol.kotlin.test.utils.DisabledOnCI -@DisabledOnCI class TsClientKotlinServerTestStdio : AbstractTsClientKotlinServerTest() { override val transportKind = TransportKind.STDIO override fun runClient(vararg args: String): String = runStdioClient(*args) diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/TsEdgeCasesTestStdio.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/TsEdgeCasesTestStdio.kt index 9b51194..dafda6d 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/TsEdgeCasesTestStdio.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/TsEdgeCasesTestStdio.kt @@ -2,7 +2,6 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.stdio import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TsTestBase -import io.modelcontextprotocol.kotlin.test.utils.DisabledOnCI import org.junit.jupiter.api.Test import org.junit.jupiter.api.Timeout import org.junit.jupiter.api.condition.EnabledOnOs @@ -12,7 +11,6 @@ import java.util.concurrent.TimeUnit import kotlin.test.assertEquals import kotlin.test.assertTrue -@DisabledOnCI class TsEdgeCasesTestStdio : TsTestBase() { override val transportKind: TransportKind = TransportKind.STDIO diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationSubscribeTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationSubscribeTest.kt index b596cc4..1e102a7 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationSubscribeTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationSubscribeTest.kt @@ -1,6 +1,7 @@ package io.modelcontextprotocol.kotlin.sdk.server -import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.kotest.matchers.collections.shouldHaveSize +import io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsResourcesUpdated import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult import io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotification import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities @@ -8,13 +9,19 @@ import io.modelcontextprotocol.kotlin.sdk.types.SubscribeRequest import io.modelcontextprotocol.kotlin.sdk.types.SubscribeRequestParams import io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking import org.awaitility.kotlin.await import org.awaitility.kotlin.untilAsserted import org.junit.jupiter.api.Test +import java.util.concurrent.ConcurrentLinkedQueue import kotlin.test.assertFalse import kotlin.test.assertTrue +import kotlin.time.Duration.Companion.seconds +import kotlin.uuid.ExperimentalUuidApi +import kotlin.uuid.Uuid +@OptIn(ExperimentalUuidApi::class) class ServerResourcesNotificationSubscribeTest : AbstractServerFeaturesTest() { override fun getServerCapabilities(): ServerCapabilities = ServerCapabilities( @@ -22,159 +29,107 @@ class ServerResourcesNotificationSubscribeTest : AbstractServerFeaturesTest() { ) @Test - fun `removeResource should send resource update notification`() = runTest { - val notifications = mutableListOf() - client.setNotificationHandler(Method.Defined.NotificationsResourcesUpdated) { + fun `should send resource notifications`() = runBlocking { + val notifications = ConcurrentLinkedQueue() + + client.setNotificationHandler(NotificationsResourcesUpdated) { notifications.add(it) CompletableDeferred(Unit) } - // Add resources - val testResourceUri1 = "test://resource1" - val testResourceUri2 = "test://resource2" - - server.addResource( - uri = testResourceUri1, - name = "Test Resource 1", - description = "A test resource 1", - mimeType = "text/plain", - ) { - ReadResourceResult( - contents = listOf( - TextResourceContents( - text = "Test resource content 1", - uri = testResourceUri1, - mimeType = "text/plain", - ), - ), - ) - } - - server.addResource( - uri = testResourceUri2, - name = "Test Resource 2", - description = "A test resource 2", - mimeType = "text/plain", - ) { - ReadResourceResult( - contents = listOf( - TextResourceContents( - text = "Test resource content 2", - uri = testResourceUri2, - mimeType = "text/plain", - ), - ), - ) - } - - client.subscribeResource(SubscribeRequest(SubscribeRequestParams(uri = testResourceUri1))) - - // Remove the resource - val result = server.removeResource(testResourceUri1) - - // Verify the resource was removed - assertTrue(result, "Resource should be removed successfully") - - // Verify that the notification was sent - await untilAsserted { - assertTrue( - notifications.any { it.params.uri == testResourceUri1 }, - "Notification should be sent when resource 1 was deleted", - ) - } - } - - @Test - fun `removeResource for two resources should send two separate notifications`() = runTest { - val notifications = mutableListOf() - client.setNotificationHandler(Method.Defined.NotificationsResourcesUpdated) { - notifications.add(it) - CompletableDeferred(Unit) - } - - // Add resources - val testResourceUri1 = "test://resource1" - val testResourceUri2 = "test://resource2" - - server.addResource( - uri = testResourceUri1, - name = "Test Resource 1", - description = "A test resource 1", - mimeType = "text/plain", - ) { - ReadResourceResult( - contents = listOf( - TextResourceContents( - text = "Test resource content 1", - uri = testResourceUri1, - mimeType = "text/plain", - ), - ), - ) - } - - server.addResource( - uri = testResourceUri2, - name = "Test Resource 2", - description = "A test resource 2", - mimeType = "text/plain", - ) { - ReadResourceResult( - contents = listOf( - TextResourceContents( - text = "Test resource content 2", - uri = testResourceUri2, - mimeType = "text/plain", - ), - ), - ) - } + val testResourceUri1 = "test://resource1-${Uuid.random()}" + val testResourceUri2 = "test://resource2-${Uuid.random()}" client.subscribeResource(SubscribeRequest(SubscribeRequestParams(uri = testResourceUri1))) client.subscribeResource(SubscribeRequest(SubscribeRequestParams(uri = testResourceUri2))) - // Remove the resource - val result1 = server.removeResource(testResourceUri1) - val result2 = server.removeResource(testResourceUri2) - - // Verify the resource was removed - assertTrue(result1, "Resource 1 should be removed successfully") - assertTrue(result2, "Resource 2 should be removed successfully") - - println(notifications.map { it.params.uri }) - // Verify that the notification was sent - await untilAsserted { - assertTrue( - notifications.any { it.params.uri == testResourceUri1 }, - "Notification should be sent when resource 1 and resource 2 was deleted", + server.addResource( + uri = testResourceUri1, + name = "Test Resource 1", + description = "A test resource 1", + mimeType = "text/plain", + ) { + ReadResourceResult( + contents = listOf( + TextResourceContents( + text = "Test resource content 1", + uri = it.uri, + mimeType = "text/plain", + ), + ), ) } - val deletedResources = listOf(notifications[0].params.uri, notifications[1].params.uri) - assertTrue( - deletedResources.contains(testResourceUri1), - "Notification should contain the removed resource 1 URI", - ) - assertTrue( - deletedResources.contains(testResourceUri2), - "Notification should contain the removed resource 2 URI", - ) + server.addResource( + uri = testResourceUri2, + name = "Test Resource 2", + description = "A test resource 2", + mimeType = "text/plain", + ) { + ReadResourceResult( + contents = listOf( + TextResourceContents( + text = "Test resource content 2", + uri = it.uri, + mimeType = "text/plain", + ), + ), + ) + } + + // Verify that the notification was sent + await untilAsserted { + notifications.shouldHaveSize(2) + assertTrue( + notifications.any { it.params.uri == testResourceUri1 && it.method == NotificationsResourcesUpdated }, + "Notification should be sent when resource 1 was deleted", + ) + assertTrue( + notifications.any { it.params.uri == testResourceUri2 && it.method == NotificationsResourcesUpdated }, + "Notification should be sent when resource 2 was deleted", + ) + } + + notifications.clear() + + // Remove the resource + val result1 = server.removeResource(testResourceUri1) + assertTrue(result1, "Resource 1 should be removed successfully") + + val result2 = server.removeResource(testResourceUri2) + // Verify the resource was removed + assertTrue(result2, "Resource 2 should be removed successfully") + + // Verify that the notification was sent + await untilAsserted { + notifications.shouldHaveSize(2) + assertTrue( + notifications.any { it.params.uri == testResourceUri1 && it.method == NotificationsResourcesUpdated }, + "Notification should be sent when resource 1 was deleted", + ) + assertTrue( + notifications.any { it.params.uri == testResourceUri2 && it.method == NotificationsResourcesUpdated }, + "Notification should be sent when resource 2 was deleted", + ) + } } @Test - fun `notification should not be send when removed resource does not exists`() = runTest { + fun `notification should not be send when removed resource does not exists`() = runBlocking { + println("Thread ${Thread.currentThread().name} test 1") // Track notifications - val notifications = mutableListOf() - client.setNotificationHandler(Method.Defined.NotificationsResourcesUpdated) { + val notifications = ConcurrentLinkedQueue() + client.setNotificationHandler(NotificationsResourcesUpdated) { notifications.add(it) CompletableDeferred(Unit) } // Try to remove a non-existent resource val result = server.removeResource("non-existent-resource") + assertFalse(result, "Removing non-existent resource should return false") // Verify the result - assertFalse(result, "Removing non-existent resource should return false") + delay(1.seconds) assertTrue( notifications.isEmpty(), "No notification should be sent when resource doesn't exist", diff --git a/integration-test/src/jvmTest/resources/junit-platform.properties b/integration-test/src/jvmTest/resources/junit-platform.properties new file mode 100644 index 0000000..9af7987 --- /dev/null +++ b/integration-test/src/jvmTest/resources/junit-platform.properties @@ -0,0 +1,6 @@ +## https://docs.junit.org/5.3.0-M1/user-guide/index.html#writing-tests-parallel-execution +junit.jupiter.execution.parallel.enabled=true +junit.jupiter.execution.parallel.config.strategy=dynamic +junit.jupiter.execution.parallel.mode.default=concurrent +junit.jupiter.execution.parallel.mode.classes.default=concurrent +junit.jupiter.execution.timeout.default=2m diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/myClient.ts b/integration-test/src/jvmTest/typescript/client/sse-client.ts similarity index 71% rename from integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/myClient.ts rename to integration-test/src/jvmTest/typescript/client/sse-client.ts index 8a1a69c..0acc256 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/myClient.ts +++ b/integration-test/src/jvmTest/typescript/client/sse-client.ts @@ -1,36 +1,15 @@ -// @ts-ignore +import {Client} from '@modelcontextprotocol/sdk/client/index'; +import {StreamableHTTPClientTransport} from '@modelcontextprotocol/sdk/client/streamableHttp'; + const args = process.argv.slice(2); const serverUrl = args[0] || 'http://localhost:3001/mcp'; const toolName = args[1]; const toolArgs = args.slice(2); const PROTOCOL_VERSION = "2024-11-05"; -// @ts-ignore async function main() { - // @ts-ignore - const sdkDirRaw = process.env.TYPESCRIPT_SDK_DIR; - const sdkDir = sdkDirRaw ? sdkDirRaw.trim() : undefined; - let Client: any; - let StreamableHTTPClientTransport: any; - if (sdkDir) { - // @ts-ignore - const path = await import('path'); - // @ts-ignore - const { pathToFileURL } = await import('url'); - const clientUrl = pathToFileURL(path.join(sdkDir, 'packages', 'client', 'src', 'index.ts')).href; - const streamUrl = pathToFileURL(path.join(sdkDir, 'packages', 'client', 'src', 'streamableHttp.ts')).href; - // @ts-ignore - ({ Client } = await import(clientUrl)); - // @ts-ignore - ({ StreamableHTTPClientTransport } = await import(streamUrl)); - } else { - // @ts-ignore - ({Client} = await import("../../../../../../../resources/typescript-sdk/src/client")); - // @ts-ignore - ({StreamableHTTPClientTransport} = await import("../../../../../../../resources/typescript-sdk/src/client/streamableHttp.js")); - } if (!toolName) { - console.log('Usage: npx tsx myClient.ts [server-url] [tool-args...]'); + console.log('Usage: npx tsx client/sse-client.ts [server-url] [tool-args...]'); console.log('Using default server URL:', serverUrl); console.log('Available utils will be listed after connection'); } @@ -85,8 +64,8 @@ async function main() { if (toolName === "greet" && toolArgs.length > 0) { toolArguments["name"] = toolArgs[0]; - } else if (tool.input && tool.input.properties) { - const propNames = Object.keys(tool.input.properties); + } else if (tool.inputSchema && tool.inputSchema.properties) { + const propNames = Object.keys(tool.inputSchema.properties); if (propNames.length > 0 && toolArgs.length > 0) { toolArguments[propNames[0]] = toolArgs[0]; } diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/myClient.ts b/integration-test/src/jvmTest/typescript/client/stdio-client.ts similarity index 84% rename from integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/myClient.ts rename to integration-test/src/jvmTest/typescript/client/stdio-client.ts index cce2354..c6eaca6 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/myClient.ts +++ b/integration-test/src/jvmTest/typescript/client/stdio-client.ts @@ -1,24 +1,11 @@ -// @ts-nocheck +import {Client} from '@modelcontextprotocol/sdk/client/index'; + const args = process.argv.slice(2); const toolName = args[0]; const toolArgs = args.slice(1); const PROTOCOL_VERSION = "2024-11-05"; async function main() { - const sdkDirRaw = process.env.TYPESCRIPT_SDK_DIR; - const sdkDir = sdkDirRaw ? sdkDirRaw.trim() : undefined; - - let Client: any; - - if (!sdkDir) { - throw new Error('TYPESCRIPT_SDK_DIR environment variable is not set. It should point to the cloned typescript-sdk directory.'); - } - - const path = await import('path'); - const { pathToFileURL } = await import('url'); - const clientUrl = pathToFileURL(path.join(sdkDir, 'packages', 'client', 'src', 'index.ts')).href; - ({ Client } = await import(clientUrl)); - if (!toolName) { console.error('Available utils will be listed after connection'); } else { @@ -68,7 +55,10 @@ async function main() { async close(): Promise { this.closed = true; - try { process.stdin.pause(); } catch {} + try { + process.stdin.pause(); + } catch { + } } } @@ -80,7 +70,7 @@ async function main() { const transport = new MinimalStdioClientTransport(); try { - await client.connect(transport, { protocolVersion: PROTOCOL_VERSION }); + await client.connect(transport, {protocolVersion: PROTOCOL_VERSION}); console.error('Connected to server over stdio'); try { @@ -117,8 +107,8 @@ async function main() { if (toolName === 'greet' && toolArgs.length > 0) { toolArguments['name'] = toolArgs[0]; - } else if (tool.input && tool.input.properties) { - const propNames = Object.keys(tool.input.properties); + } else if (tool.inputSchema && tool.inputSchema.properties) { + const propNames = Object.keys(tool.inputSchema.properties); if (propNames.length > 0 && toolArgs.length > 0) { toolArguments[propNames[0]] = toolArgs[0]; } diff --git a/integration-test/src/jvmTest/typescript/package.json b/integration-test/src/jvmTest/typescript/package.json index 0ec09d1..24a1ac9 100644 --- a/integration-test/src/jvmTest/typescript/package.json +++ b/integration-test/src/jvmTest/typescript/package.json @@ -3,6 +3,8 @@ "version": "1.0.0", "private": true, "scripts": { + "client:stdio": "tsx client/stdio-client.ts", + "client:sse": "tsx client/sse-client.ts http://localhost:3000/mcp greet Hello", "server:stdio": "tsx server/stdio-server.ts", "server:sse": "tsx server/sse-server.ts" }, diff --git a/test-utils/build.gradle.kts b/test-utils/build.gradle.kts index ffffc3e..586f0cf 100644 --- a/test-utils/build.gradle.kts +++ b/test-utils/build.gradle.kts @@ -10,6 +10,7 @@ kotlin { api(libs.kotest.assertions.core) api(libs.kotest.assertions.json) api(libs.kotlinx.coroutines.test) + api(libs.ktor.server.core) api(libs.mockk) api(libs.awaitility) api(libs.junit.jupiter.params) diff --git a/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/TypeScriptRunner.kt b/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/TypeScriptRunner.kt index cd1d674..3aa1de7 100644 --- a/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/TypeScriptRunner.kt +++ b/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/TypeScriptRunner.kt @@ -24,6 +24,7 @@ public object TypeScriptRunner { env: Map = emptyMap(), redirectErrorStream: Boolean = false, logPrefix: String = "TS-RUNNER", + log: Boolean = true, ): Process { val command = mutableListOf("npx", "tsx", scriptPath) command.addAll(arguments) @@ -34,10 +35,12 @@ public object TypeScriptRunner { val proc = pb.start() - if (redirectErrorStream) { - createProcessOutputReader(proc, logPrefix).start() - } else { - createProcessErrorReader(proc, logPrefix).start() + if (log) { + if (redirectErrorStream) { + createProcessOutputReader(proc, logPrefix).start() + } else { + createProcessErrorReader(proc, logPrefix).start() + } } return proc @@ -50,16 +53,91 @@ public object TypeScriptRunner { * @throws RuntimeException if npm install fails */ public fun installDependencies(typescriptDir: File) { - require(typescriptDir.isDirectory()) { "Type script directory does not exist" } + require(typescriptDir.isDirectory()) { "Type script directory does not exist: ${typescriptDir.absolutePath}" } println("Installing TypeScript dependencies in ${typescriptDir.absolutePath}") - val pb = ProcessBuilder("npm", "install") - pb.directory(typescriptDir) - pb.redirectErrorStream(true) - val proc = pb.start() - proc.inputStream.bufferedReader().useLines { lines -> - lines.forEach { println("[NPM] $it") } + + val packageJson = File(typescriptDir, "package.json") + val content = if (packageJson.exists()) packageJson.readText() else "" + val hasCatalog = content.contains("catalog:") + val hasWorkspace = content.contains("workspace:") + val hasPnpmWorkspace = File(typescriptDir, "pnpm-workspace.yaml").exists() + + if (hasCatalog || hasWorkspace || hasPnpmWorkspace) { + println("Detected pnpm-specific protocols or workspace. Attempting pnpm install...") + if (tryCommand(listOf("pnpm", "install"), typescriptDir) || + tryCommand(listOf("npx", "pnpm", "install"), typescriptDir) + ) { + patchPackageJsonRecursively(typescriptDir, patchProtocols = false) + return + } } - val exitCode = proc.waitFor() - require(exitCode == 0) { "npm install failed with exit code $exitCode" } + + // Try standard npm install + if (tryCommand(listOf("npm", "install"), typescriptDir)) { + patchPackageJsonRecursively(typescriptDir, patchProtocols = false) + return + } + + // If npm fails and we have catalog/workspace, try patching and npm again + println("npm install failed. Patching package.json and trying again...") + patchPackageJsonRecursively(typescriptDir, patchProtocols = true) + if (tryCommand(listOf("npm", "install"), typescriptDir)) return + + throw RuntimeException("Failed to install TypeScript dependencies in ${typescriptDir.absolutePath}") + } + + private fun tryCommand(command: List, workingDir: File): Boolean { + val fullCommand = if (isWindows) { + listOf("cmd.exe", "/c") + command + } else { + command + } + println("Executing: ${fullCommand.joinToString(" ")}") + return try { + val pb = ProcessBuilder(fullCommand) + pb.directory(workingDir) + pb.redirectErrorStream(true) + val proc = pb.start() + proc.inputStream.bufferedReader().useLines { lines -> + lines.forEach { println("[TS-INSTALL] $it") } + } + proc.waitFor() == 0 + } catch (e: Exception) { + println("Command failed: ${e.message}") + false + } + } + + private fun patchPackageJsonRecursively(dir: File, patchProtocols: Boolean) { + dir.walkTopDown() + .onEnter { it.name != "node_modules" } + .filter { it.name == "package.json" } + .forEach { file -> + var content = file.readText() + var modified = false + + if (patchProtocols) { + val newContent = content + .replace(Regex(""""catalog:[^"]*""""), "\"*\"") + .replace(Regex(""""workspace:[^"]*""""), "\"*\"") + .replace(Regex(""""link:[^"]*""""), "\"*\"") + if (newContent != content) { + content = newContent + modified = true + } + } + + // Add entry point if missing and src/index.ts exists to help tsx resolution + val srcIndex = File(file.parentFile, "src/index.ts") + if (srcIndex.exists() && !content.contains("\"exports\"") && !content.contains("\"main\"")) { + println("Adding exports to ${file.absolutePath}") + content = content.replace(Regex("""^\{"""), "{\n \"exports\": { \".\": \"./src/index.ts\" },") + modified = true + } + + if (modified) { + file.writeText(content) + } + } } } diff --git a/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/ktor.kt b/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/ktor.kt new file mode 100644 index 0000000..cf3e4d1 --- /dev/null +++ b/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/ktor.kt @@ -0,0 +1,17 @@ +package io.modelcontextprotocol.kotlin.test.utils + +import io.ktor.server.engine.EmbeddedServer + +/** + * Retrieves the actual port that the embedded server is bound to. + * + * This function resolves the server's connectors and retrieves the port + * from the single resolved connector. It is useful for obtaining the + * port dynamically in cases where the server is bound to a random port. + * + * Note: This method assumes the server has exactly one resolved connector. + * + * @receiver The embedded server instance + * @return The port number the server is bound to + */ +public suspend fun EmbeddedServer<*, *>.actualPort(): Int = engine.resolvedConnectors().single().port diff --git a/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/processUtils.kt b/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/processUtils.kt index 43e0a6c..a80c825 100644 --- a/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/processUtils.kt +++ b/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/processUtils.kt @@ -4,6 +4,8 @@ import org.awaitility.kotlin.await import java.net.ServerSocket import java.net.Socket import java.util.concurrent.TimeUnit +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds /** * Kills any process currently using the specified port. @@ -52,9 +54,9 @@ public fun waitForPort(host: String = "localhost", port: Int, timeoutSeconds: Lo * * Attempts graceful shutdown first, then forces termination if necessary. */ -public fun stopProcess(process: Process) { +public fun stopProcess(process: Process, wait: Duration = 1.seconds) { process.destroy() - if (!process.waitFor(1, TimeUnit.SECONDS)) { + if (!process.waitFor(wait.inWholeMilliseconds, TimeUnit.MILLISECONDS)) { process.destroyForcibly() } } From 1ee43f07b0e1ade4823fea8ddb1c9c5982d881d2 Mon Sep 17 00:00:00 2001 From: adam jones Date: Tue, 13 Jan 2026 12:21:52 +0000 Subject: [PATCH 115/449] chore: update licensing to Apache 2.0 for new contributions (#481) As part of MCP joining the Linux Foundation, this PR updates licensing to Apache 2.0 for new contributions while grandfathering existing MIT code. See modelcontextprotocol/modelcontextprotocol#1994 --------- Co-authored-by: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> --- LICENSE | 199 +++++++++++++++++++++++++++++++++++++++++++++++++++++- README.md | 4 +- 2 files changed, 199 insertions(+), 4 deletions(-) diff --git a/LICENSE b/LICENSE index 136059a..185054b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,193 @@ +The MCP project is undergoing a licensing transition from the MIT License to the Apache License, Version 2.0 ("Apache-2.0"). All new code and specification contributions to the project are licensed under Apache-2.0. Documentation contributions (excluding specifications) are licensed under CC-BY-4.0. + +Contributions for which relicensing consent has been obtained are licensed under Apache-2.0. Contributions made by authors who originally licensed their work under the MIT License and who have not yet granted explicit permission to relicense remain licensed under the MIT License. + +No rights beyond those granted by the applicable original license are conveyed for such contributions. + +--- + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to the Licensor for inclusion in the Work by the copyright + owner or by an individual or Legal Entity authorized to submit on behalf + of the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + +--- + MIT License -Copyright (c) 2024 Anthropic, PBC +Copyright (c) 2024-2026 Model Context Protocol a Series of LF Projects, LLC. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +205,12 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. + +--- + +Creative Commons Attribution 4.0 International (CC-BY-4.0) + +Documentation in this project (excluding specifications) is licensed under +CC-BY-4.0. See https://creativecommons.org/licenses/by/4.0/legalcode for +the full license text. diff --git a/README.md b/README.md index ac2b73e..356493b 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![Kotlin](https://img.shields.io/badge/kotlin-2.2+-blueviolet.svg?logo=kotlin)](http://kotlinlang.org) [![Kotlin Multiplatform](https://img.shields.io/badge/Platforms-%20JVM%20%7C%20Wasm%2FJS%20%7C%20Native%20-blueviolet?logo=kotlin)](https://kotlinlang.org/docs/multiplatform.html) [![JVM](https://img.shields.io/badge/JVM-11+-red.svg?logo=jvm)](http://java.com) -[![License](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) +[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE) Kotlin Multiplatform implementation of the [Model Context Protocol](https://modelcontextprotocol.io) (MCP), providing both client and server capabilities for integrating with LLM surfaces across various platforms. @@ -207,4 +207,4 @@ Please see the [contribution guide](CONTRIBUTING.md) and the [Code of conduct](C ## License -This project is licensed under the MIT License—see the [LICENSE](LICENSE) file for details. +This project is licensed under Apache 2.0 for new contributions, with existing code under MIT—see the [LICENSE](LICENSE) file for details. From c73507b96a6bbe5b803d026e1e08f96a748836f4 Mon Sep 17 00:00:00 2001 From: Fedor Bobin Date: Mon, 19 Jan 2026 16:45:54 +0300 Subject: [PATCH 116/449] fix conformance tests (#483) # Fix conformance tests. 1. fix node version to workaround https://github.com/nodejs/node/issues/56645 2. use `@argfile` to handle long classpaths 3. specify extension for npx for windows 4. Install `@modelcontextprotocol/conformance@0.1.8` GitHub Actions workflow. 5. Centralize logging for process streams in conformance tests: Add `startLogging` utility for InputStream logging with `KotlinLogging`. Replace inline process stream handling with `startLogging` calls Note: Currently tests pass using `@modelcontextprotocol/conformance@0.1.8`/ But changing to 0.1.9 will fail client scenarios (it is not related to this PR). ## Motivation and Context Before fix conformance tests are broken on Windows ## How Has This Been Tested? Run test locally and in github actions ## Breaking Changes No ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed ## Additional context May be we should update `CONTRIBUTING.md/Building/Requirements`. But currently it does not mention at all: node, android, etc. --------- Co-authored-by: f.bobin Co-authored-by: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- .github/workflows/conformance.yml | 28 ++++- .../kotlin/sdk/conformance/ConformanceTest.kt | 104 ++++++++++-------- .../test/resources/simplelogger.properties | 2 + test-utils/build.gradle.kts | 1 + .../kotlin/test/utils/platform.kt | 2 + .../kotlin/test/utils/processBuilders.kt | 32 ++++++ 7 files changed, 119 insertions(+), 52 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1922ffb..81af8ac 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -62,7 +62,7 @@ jobs: if: matrix.os == 'ubuntu-latest' uses: actions/setup-node@v6 with: - node-version: 'lts/*' + node-version: '22' # increase only after https://github.com/nodejs/node/issues/56645 will be fixed - name: Set up Node Packages if: matrix.os == 'ubuntu-latest' diff --git a/.github/workflows/conformance.yml b/.github/workflows/conformance.yml index 7660be7..2c7b1ef 100644 --- a/.github/workflows/conformance.yml +++ b/.github/workflows/conformance.yml @@ -14,11 +14,23 @@ concurrency: jobs: run-conformance: - runs-on: macos-latest - name: Run Conformance Tests + runs-on: ${{ matrix.os }} + name: Run Conformance Tests on ${{ matrix.os }} timeout-minutes: 20 env: JAVA_OPTS: "-Xmx8g -Dfile.encoding=UTF-8 -Djava.awt.headless=true -Dkotlin.daemon.jvm.options=-Xmx6g" + + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + max-workers: 3 + - os: windows-latest + max-workers: 3 + - os: macos-latest + max-workers: 2 + steps: - uses: actions/checkout@v6 @@ -31,7 +43,12 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v6 with: - node-version: 'lts/*' + node-version: '22' # increase only after https://github.com/nodejs/node/issues/56645 will be fixed + + - name: Setup Conformance Tests + working-directory: conformance-test + run: |- + npm install -g @modelcontextprotocol/conformance@0.1.8 - name: Setup Gradle uses: gradle/actions/setup-gradle@v5 @@ -45,11 +62,12 @@ jobs: ../.konan/** - name: Run Conformance Tests - run: ./gradlew --no-daemon :conformance-test:test + run: |- + ./gradlew :conformance-test:test --no-daemon --max-workers ${{ matrix.max-workers }} - name: Upload Conformance Results if: always() uses: actions/upload-artifact@v6 with: - name: conformance-results + name: conformance-results-${{ matrix.os }} path: conformance-test/results/ diff --git a/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTest.kt b/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTest.kt index 47ca2e8..7754ff3 100644 --- a/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTest.kt +++ b/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTest.kt @@ -1,7 +1,9 @@ package io.modelcontextprotocol.kotlin.sdk.conformance import io.github.oshai.kotlinlogging.KotlinLogging +import io.modelcontextprotocol.kotlin.test.utils.NPX import io.modelcontextprotocol.kotlin.test.utils.findFreePort +import io.modelcontextprotocol.kotlin.test.utils.startLogging import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.DynamicTest @@ -13,10 +15,14 @@ import java.lang.management.ManagementFactory import java.net.HttpURLConnection import java.net.URI import java.util.concurrent.TimeUnit +import kotlin.io.path.createTempFile import kotlin.properties.Delegates private val logger = KotlinLogging.logger {} +val processStderrLogger = KotlinLogging.logger(name = "stderr") +val processStdoutLogger = KotlinLogging.logger(name = "stdout") + private const val CONFORMANCE_VERSION = "0.1.8" enum class TransportType { @@ -210,79 +216,75 @@ class ConformanceTest { } private fun runServerConformanceTest(scenario: String, transportType: TransportType) { - val processBuilder = when (transportType) { + val serverUrl = when (transportType) { TransportType.SSE -> { - val serverUrl = "http://127.0.0.1:$serverPort/mcp" - ProcessBuilder( - "npx", - "@modelcontextprotocol/conformance@$CONFORMANCE_VERSION", - "server", - "--url", - serverUrl, - "--scenario", - scenario, - ).apply { - inheritIO() - } + "http://127.0.0.1:$serverPort/mcp" } TransportType.WEBSOCKET -> { - val serverUrl = "ws://127.0.0.1:$serverPort/ws" - ProcessBuilder( - "npx", - "@modelcontextprotocol/conformance@$CONFORMANCE_VERSION", - "server", - "--url", - serverUrl, - "--scenario", - scenario, - ).apply { - inheritIO() - } + "ws://127.0.0.1:$serverPort/ws" } } + val processBuilder = ProcessBuilder( + NPX, + "@modelcontextprotocol/conformance@$CONFORMANCE_VERSION", + "server", + "--url", + serverUrl, + "--scenario", + scenario, + ) + runConformanceTest("server", scenario, processBuilder, transportType) } private fun runClientConformanceTest(scenario: String, transportType: TransportType) { val testClasspath = getTestClasspath() - val clientCommand = when (transportType) { + // Create an argfile to avoid Windows command line length limits + val argFile = createTempFile(suffix = ".args").toFile() + argFile.deleteOnExit() + + val mainClass = when (transportType) { TransportType.SSE -> { - val serverUrl = "http://127.0.0.1:$serverPort/mcp" - listOf( - "java", - "-cp", - testClasspath, - "io.modelcontextprotocol.kotlin.sdk.conformance.ConformanceClientKt", - serverUrl, + argFile.writeText( + buildString { + appendLine("-cp") + appendLine(testClasspath) + appendLine("io.modelcontextprotocol.kotlin.sdk.conformance.ConformanceClientKt") + }, ) + "http://127.0.0.1:$serverPort/mcp" } TransportType.WEBSOCKET -> { - val serverUrl = "ws://127.0.0.1:$serverPort/ws" - listOf( - "java", - "-cp", - testClasspath, - "io.modelcontextprotocol.kotlin.sdk.conformance.WebSocketConformanceClientKt", - serverUrl, + argFile.writeText( + buildString { + appendLine("-cp") + appendLine(testClasspath) + appendLine("io.modelcontextprotocol.kotlin.sdk.conformance.WebSocketConformanceClientKt") + }, ) + "ws://127.0.0.1:$serverPort/ws" } } + val clientCommand = listOf( + "java", + "@${argFile.absolutePath}", + mainClass, + ) + val processBuilder = ProcessBuilder( - "npx", + NPX, "@modelcontextprotocol/conformance@$CONFORMANCE_VERSION", "client", "--command", clientCommand.joinToString(" "), "--scenario", scenario, - ).apply { - inheritIO() - } + ) runConformanceTest("client", scenario, processBuilder, transportType) } @@ -296,10 +298,20 @@ class ConformanceTest { val capitalizedType = type.replaceFirstChar { it.uppercase() } logger.info { "Running $type conformance test [$transportType]: $scenario" } - val timeoutSeconds = System.getenv("CONFORMANCE_TEST_TIMEOUT_SECONDS")?.toLongOrNull() - ?: DEFAULT_TEST_TIMEOUT_SECONDS + val timeoutSeconds = + System.getenv("CONFORMANCE_TEST_TIMEOUT_SECONDS")?.toLongOrNull() ?: DEFAULT_TEST_TIMEOUT_SECONDS val process = processBuilder.start() + + process.errorStream.startLogging( + logger = processStderrLogger, + name = "test(PID=${process.pid()})", + ) + process.inputStream.startLogging( + logger = processStdoutLogger, + name = "test(PID=${process.pid()})", + ) + val completed = process.waitFor(timeoutSeconds, TimeUnit.SECONDS) if (!completed) { diff --git a/conformance-test/src/test/resources/simplelogger.properties b/conformance-test/src/test/resources/simplelogger.properties index 95fb646..c6f3b90 100644 --- a/conformance-test/src/test/resources/simplelogger.properties +++ b/conformance-test/src/test/resources/simplelogger.properties @@ -5,4 +5,6 @@ org.slf4j.simpleLogger.showDateTime=false # Log level for specific packages or classes org.slf4j.simpleLogger.log.io.ktor.server=DEBUG +org.slf4j.simpleLogger.log.stdout=TRACE +org.slf4j.simpleLogger.log.stderr=TRACE org.slf4j.simpleLogger.log.io.modelcontextprotocol=DEBUG diff --git a/test-utils/build.gradle.kts b/test-utils/build.gradle.kts index 586f0cf..346001a 100644 --- a/test-utils/build.gradle.kts +++ b/test-utils/build.gradle.kts @@ -14,6 +14,7 @@ kotlin { api(libs.mockk) api(libs.awaitility) api(libs.junit.jupiter.params) + api(libs.kotlin.logging) runtimeOnly(libs.slf4j.simple) } diff --git a/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/platform.kt b/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/platform.kt index fcdd1f4..ca34b6d 100644 --- a/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/platform.kt +++ b/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/platform.kt @@ -1,3 +1,5 @@ package io.modelcontextprotocol.kotlin.test.utils public val isWindows: Boolean = System.getProperty("os.name").lowercase().contains("windows") + +public val NPX: String = if (isWindows) "npx.cmd" else "npx" diff --git a/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/processBuilders.kt b/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/processBuilders.kt index 3ded6a3..519dfcd 100644 --- a/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/processBuilders.kt +++ b/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/processBuilders.kt @@ -1,5 +1,10 @@ package io.modelcontextprotocol.kotlin.test.utils +import io.github.oshai.kotlinlogging.KLogger +import java.io.BufferedReader +import java.io.InputStream +import java.io.InputStreamReader + public fun createTeeProcessBuilder(): ProcessBuilder = if (isWindows) { ProcessBuilder("cmd", "/c", "findstr", "^") } else { @@ -11,3 +16,30 @@ public fun createSleepyProcessBuilder(): ProcessBuilder = if (isWindows) { } else { ProcessBuilder("sh", "-c", "sleep 1 && echo 'simulated error' >&2 && exit 1") } + +/** + * Starts logging the lines from this InputStream to a specified logger. + * Each line from the InputStream is read and logged using the provided logger + * at the debug level, optionally with an additional prefix string appended + * to each logged line. The method runs the logging process in a separate daemon thread. + * + * @param logger The logger used to log the lines from the InputStream. + * @param name The name to assign to the daemon thread handling the logging process. + */ +public fun InputStream.startLogging(logger: KLogger, name: String) { + val stream = this + Thread { + try { + BufferedReader(InputStreamReader(stream)).use { reader -> + reader.lineSequence().forEach { line -> + logger.debug { line } + } + } + } catch (e: Exception) { + logger.trace(e) { "Error reading server test stdout" } + } + }.apply { + this.name = name + isDaemon = true + }.start() +} From ad7f372b4fa3b47951c76b84c3ac1d49a6014294 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Mon, 19 Jan 2026 16:02:31 +0200 Subject: [PATCH 117/449] Prepare for release version 0.8.2 (#461) # Prepare for release version 0.8.2 bump mcp kotlin-sdk to 0.8.2 ## How Has This Been Tested? CI ## Breaking Changes No ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [x] CI 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 ## Additional context --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 7182771..efeb7c0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,4 +14,4 @@ kotlin.mpp.enableCInteropCommonization=true kotlin.native.ignoreDisabledTargets=true group=io.modelcontextprotocol -version=0.8.2-SNAPSHOT +version=0.8.2 From 9f16ae45d29778bda22146deff75e89067952485 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Tue, 20 Jan 2026 11:30:33 +0200 Subject: [PATCH 118/449] chore(ci): Update deploy pipeline (#485) # chore(ci): Update deploy pipeline ## Motivation and Context To troubleshoot deployment failure, it has to be merged to main first ## How Has This Been Tested? Will be tested manually on main branch ## Breaking Changes ## Types of changes - [x] CI update --- .github/workflows/gradle-publish.yml | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml index a140872..ebb2217 100644 --- a/.github/workflows/gradle-publish.yml +++ b/.github/workflows/gradle-publish.yml @@ -10,6 +10,7 @@ name: Release on: release: types: [ published ] + workflow_dispatch: jobs: build: @@ -35,11 +36,25 @@ jobs: uses: gradle/actions/setup-gradle@v5 - name: Clean Build with Gradle - run: ./gradlew clean build -x :conformance-test:test --rerun-tasks + run: ./gradlew clean build -x :conformance-test:test --rerun-tasks --max-workers 2 --continue --configure-on-demand --parallel + + - name: Publish Test Report + uses: mikepenz/action-junit-report@v6 + if: ${{ !cancelled() }} + with: + annotate_only: true + detailed_summary: true + flaky_summary: true + group_suite: true + include_empty_in_summary: false + include_time_in_summary: true + report_paths: '**/test-results/**/TEST-*.xml' + truncate_stack_traces: false - name: Publish to Maven Central Portal id: publish - run: ./gradlew publishToMavenCentral --no-configuration-cache + run: ./gradlew publishToMavenLocal --no-configuration-cache +# run: ./gradlew publishToMavenCentral --no-configuration-cache env: ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.OSSRH_USERNAME }} ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.OSSRH_TOKEN }} From 4d505025b424d76c30a105f5d3f0c0c5caba9fb5 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Wed, 21 Jan 2026 08:54:35 +0200 Subject: [PATCH 119/449] fix(stdio-client)!: Stdio- and StreamableHttpClientTransport should throw McpException on send when not ready (#487) --- .../src/jvmTest/typescript/package-lock.json | 2 -- .../kotlin/sdk/client/StdioClientTransport.kt | 14 ++++++++++++-- .../sdk/client/StreamableHttpClientTransport.kt | 9 ++++++++- .../client/AbstractClientTransportLifecycleTest.kt | 5 ++++- .../stdio/StdioClientTransportErrorHandlingTest.kt | 12 +++++++++++- 5 files changed, 35 insertions(+), 7 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index 0ad4874..496bfa0 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -989,7 +989,6 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -1824,7 +1823,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt index 66d43f7..0b774a6 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt @@ -230,8 +230,18 @@ public class StdioClientTransport @JvmOverloads public constructor( } override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { - check(initialized.load()) { "Transport is not started" } - check(!onCloseCalled.load()) { "Transport is closed" } + if (!initialized.load()) { + throw McpException( + code = CONNECTION_CLOSED, + message = "Transport is not started", + ) + } + if (onCloseCalled.load()) { + throw McpException( + code = CONNECTION_CLOSED, + message = "Transport is closed", + ) + } @Suppress("TooGenericExceptionCaught", "SwallowedException") try { sendChannel.send(message) diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt index a171217..c8f250a 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt @@ -27,7 +27,9 @@ import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse +import io.modelcontextprotocol.kotlin.sdk.types.McpException import io.modelcontextprotocol.kotlin.sdk.types.McpJson +import io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.CONNECTION_CLOSED import io.modelcontextprotocol.kotlin.sdk.types.RequestId import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineName @@ -105,7 +107,12 @@ public class StreamableHttpClientTransport( resumptionToken: String?, onResumptionToken: ((String) -> Unit)? = null, ) { - check(initialized.load()) { "Transport is not started" } + if (!initialized.load()) { + throw McpException( + code = CONNECTION_CLOSED, + message = "Transport is not started", + ) + } logger.debug { "Client sending message via POST to $url: ${McpJson.encodeToString(message)}" } // If we have a resumption token, reconnect the SSE stream with it diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractClientTransportLifecycleTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractClientTransportLifecycleTest.kt index 8afc81f..02b6d25 100644 --- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractClientTransportLifecycleTest.kt +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractClientTransportLifecycleTest.kt @@ -4,7 +4,9 @@ import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport +import io.modelcontextprotocol.kotlin.sdk.types.McpException import io.modelcontextprotocol.kotlin.sdk.types.PingRequest +import io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.CONNECTION_CLOSED import io.modelcontextprotocol.kotlin.sdk.types.toJSON import kotlinx.coroutines.delay import kotlinx.coroutines.test.runTest @@ -46,10 +48,11 @@ abstract class AbstractClientTransportLifecycleTest { fun `should throw when sending before start`() = runTest { val transport = createTransport() - val exception = shouldThrow { + val exception = shouldThrow { transport.send(PingRequest().toJSON()) } exception.message shouldContain "not started" + exception.code shouldBe CONNECTION_CLOSED } @Test diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/stdio/StdioClientTransportErrorHandlingTest.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/stdio/StdioClientTransportErrorHandlingTest.kt index da18741..b227d5c 100644 --- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/stdio/StdioClientTransportErrorHandlingTest.kt +++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/stdio/StdioClientTransportErrorHandlingTest.kt @@ -6,6 +6,7 @@ import io.kotest.matchers.shouldBe import io.kotest.matchers.types.shouldBeInstanceOf import io.kotest.matchers.types.shouldBeSameInstanceAs import io.mockk.coEvery +import io.mockk.every import io.mockk.mockk import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage @@ -151,7 +152,16 @@ class StdioClientTransportErrorHandlingTest { fun `Send should handle exceptions`(throwable: Throwable, shouldWrap: Boolean, expectedCode: Int?) = runTest { val sendChannel: Channel = mockk(relaxed = true) - val stdin = createNonEmptyBuffer { "id: 1\ndata:\n" } + // Create a stdin Source that never returns EOF to prevent transport from closing + // Use mockk since Source is a sealed interface + val stdin: kotlinx.io.Source = mockk(relaxed = true) + every { stdin.readAtMostTo(any(), any()) } coAnswers { + // Return 0 to indicate no data available (but not EOF) + // This keeps the transport alive without blocking + delay(10) // Small delay to prevent busy-waiting + 0L + } + transport = StdioClientTransport( input = stdin, output = Buffer(), From 1e4003fe9a29590b2cb5504594867fe57881266e Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Wed, 21 Jan 2026 11:48:35 +0200 Subject: [PATCH 120/449] ci: re-enable publishing release workflow Re-enable publishing to Maven Central in release workflow --- .github/workflows/gradle-publish.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml index ebb2217..0621ac3 100644 --- a/.github/workflows/gradle-publish.yml +++ b/.github/workflows/gradle-publish.yml @@ -10,7 +10,6 @@ name: Release on: release: types: [ published ] - workflow_dispatch: jobs: build: @@ -53,8 +52,7 @@ jobs: - name: Publish to Maven Central Portal id: publish - run: ./gradlew publishToMavenLocal --no-configuration-cache -# run: ./gradlew publishToMavenCentral --no-configuration-cache + run: ./gradlew publishToMavenCentral --no-configuration-cache env: ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.OSSRH_USERNAME }} ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.OSSRH_TOKEN }} From bfb0516af4c87ba43bd9dc1adfbc986e0fe5999b Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Wed, 21 Jan 2026 12:12:01 +0200 Subject: [PATCH 121/449] ci: Update gradle-publish.yml Allow re-running release manually --- .github/workflows/gradle-publish.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml index 0621ac3..5acfce8 100644 --- a/.github/workflows/gradle-publish.yml +++ b/.github/workflows/gradle-publish.yml @@ -10,6 +10,7 @@ name: Release on: release: types: [ published ] + workflow_dispatch: jobs: build: From 48119367e62217a586b39478a8bded884a997b8e Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Wed, 21 Jan 2026 12:21:11 +0200 Subject: [PATCH 122/449] bump: update version to 0.8.3 (#488) Update version to 0.8.3. Prepare for release ## Motivation and Context Prepare for release ## How Has This Been Tested? CI ## Breaking Changes No ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [x] CI update --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index efeb7c0..c4128f5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,4 +14,4 @@ kotlin.mpp.enableCInteropCommonization=true kotlin.native.ignoreDisabledTargets=true group=io.modelcontextprotocol -version=0.8.2 +version=0.8.3 From 327b1546db62734957e00fc55ece66a1352594e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Jan 2026 21:52:44 +0200 Subject: [PATCH 123/449] Bump hono from 4.11.3 to 4.11.4 in /integration-test/src/jvmTest/typescript in the npm_and_yarn group across 1 directory (#482) --- integration-test/src/jvmTest/typescript/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index 496bfa0..821da34 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -1214,9 +1214,9 @@ } }, "node_modules/hono": { - "version": "4.11.3", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.3.tgz", - "integrity": "sha512-PmQi306+M/ct/m5s66Hrg+adPnkD5jiO6IjA7WhWw0gSBSo1EcRegwuI1deZ+wd5pzCGynCcn2DprnE4/yEV4w==", + "version": "4.11.4", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.4.tgz", + "integrity": "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA==", "license": "MIT", "peer": true, "engines": { From 6dc8b752e027418f98d15eb2b7e2e309533e0caa Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Mon, 26 Jan 2026 19:02:22 +0200 Subject: [PATCH 124/449] ci: integrate Detekt static analysis tool (#493) - Add Detekt configuration (`detekt.yml`) with custom rules - Apply Detekt plugin to all subprojects - Configure Detekt tasks in Gradle workflow - Update Gradle dependency versions to include Detekt plugin ## Motivation and Context Enhance static code analysis for improved code quality. ## Breaking Changes No ## Types of changes - [x] CI update - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] 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 --- .github/workflows/codeql.yml | 1 + .gitignore | 4 +++- .idea/detekt.xml | 14 ++++++++++++++ .idea/externalDependencies.xml | 7 +++++++ .idea/ktlint-plugin.xml | 7 +++++++ build.gradle.kts | 10 ++++++++++ .../src/main/kotlin/mcp.multiplatform.gradle.kts | 9 +++++++++ detekt.yml | 7 +++++++ gradle/libs.versions.toml | 14 ++++++++------ 9 files changed, 66 insertions(+), 7 deletions(-) create mode 100644 .idea/detekt.xml create mode 100644 .idea/externalDependencies.xml create mode 100644 .idea/ktlint-plugin.xml create mode 100644 detekt.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index fba2019..4657da9 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -71,6 +71,7 @@ jobs: :kotlin-sdk:compileKotlinJvm \ :integration-test:compileKotlinJvm \ :conformance-test:testClasses \ + detekt \ -Pkotlin.incremental=false \ --no-daemon --stacktrace --rerun-tasks diff --git a/.gitignore b/.gitignore index a8c18dd..805579c 100644 --- a/.gitignore +++ b/.gitignore @@ -11,13 +11,15 @@ build/ .idea/compiler.xml .idea/libraries/ .idea -!.idea/icon.png *.iws *.iml *.ipr out/ !**/src/main/**/out/ !**/src/test/**/out/ +!.idea/icon.png +!.idea/detekt.xml +!.idea/externalDependencies.xml ### Kotlin ### .kotlin diff --git a/.idea/detekt.xml b/.idea/detekt.xml new file mode 100644 index 0000000..a408ca9 --- /dev/null +++ b/.idea/detekt.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/.idea/externalDependencies.xml b/.idea/externalDependencies.xml new file mode 100644 index 0000000..0a081df --- /dev/null +++ b/.idea/externalDependencies.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/.idea/ktlint-plugin.xml b/.idea/ktlint-plugin.xml new file mode 100644 index 0000000..e8bd90c --- /dev/null +++ b/.idea/ktlint-plugin.xml @@ -0,0 +1,7 @@ + + + + DISTRACT_FREE + DEFAULT + + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index bcdf96f..2161a96 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,8 @@ +import dev.detekt.gradle.extensions.FailOnSeverity + plugins { id("mcp.dokka") + alias(libs.plugins.detekt) alias(libs.plugins.ktlint) alias(libs.plugins.kover) } @@ -18,6 +21,13 @@ dependencies { subprojects { apply(plugin = "org.jlleitschuh.gradle.ktlint") apply(plugin = "org.jetbrains.kotlinx.kover") + apply(plugin = "dev.detekt") + + detekt { + config = files("$rootDir/detekt.yml") + buildUponDefaultConfig = true + failOnSeverity.set(FailOnSeverity.Never) + } } dokka { diff --git a/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts b/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts index a676db7..f3d4616 100644 --- a/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts +++ b/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts @@ -61,3 +61,12 @@ kotlin { explicitApi = ExplicitApiMode.Strict jvmToolchain(21) } + +tasks.named("detekt").configure { + dependsOn( + "detektMainJvm", + "detektCommonMainSourceSet", + "detektTestJvm", + "detektCommonTestSourceSet", + ) +} diff --git a/detekt.yml b/detekt.yml new file mode 100644 index 0000000..f9eba19 --- /dev/null +++ b/detekt.yml @@ -0,0 +1,7 @@ +config: + validation: true + warningsAsErrors: true + +complexity: + LongMethod: + excludes: ['**Test.kt'] diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fed638b..5b6b087 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,13 +1,14 @@ [versions] # plugins version -kotlin = "2.2.21" -dokka = "2.1.0" atomicfu = "0.29.0" -ktlint = "14.0.1" -kover = "0.9.4" -netty = "4.2.9.Final" -mavenPublish = "0.35.0" binaryCompatibilityValidatorPlugin = "0.18.1" +detekt = "2.0.0-alpha.1" +dokka = "2.1.0" +kotlin = "2.2.21" +kover = "0.9.4" +ktlint = "14.0.1" +mavenPublish = "0.35.0" +netty = "4.2.9.Final" openapi-generator = "7.18.0" # libraries version @@ -69,6 +70,7 @@ ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio", version.ref = " ktor-server-cio = { group = "io.ktor", name = "ktor-server-cio", version.ref = "ktor" } [plugins] +detekt = { id = "dev.detekt", version.ref = "detekt" } kotlinx-binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binaryCompatibilityValidatorPlugin" } kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" } From f8c30af7ba34ee2e855489325f6f4cefcc7e4715 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jan 2026 19:47:43 +0200 Subject: [PATCH 125/449] Bump gradle-wrapper from 9.2.0 to 9.3.0 (#492) --- gradle/wrapper/gradle-wrapper.jar | Bin 45633 -> 46175 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index f8e1ee3125fe0768e9a76ee977ac089eb657005e..61285a659d17295f1de7c53e24fdf13ad755c379 100644 GIT binary patch delta 37058 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 NNswbwE82Y!{{uvZmpcFe delta 36446 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 JWd7DQ`9Hc?h(rJY diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index bad7c24..19a6bde 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From d958a987f27b76b2257e506ac081dd1fd84a5c5f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Jan 2026 11:33:22 +0200 Subject: [PATCH 126/449] Bump kotest from 6.0.7 to 6.1.1 (#491) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `kotest` from 6.0.7 to 6.1.1. Updates `io.kotest:kotest-assertions-core` from 6.0.7 to 6.1.1

Release notes

Sourced from io.kotest:kotest-assertions-core's releases.

v6.1.1

What's Changed

Full Changelog: https://github.com/kotest/kotest/compare/v6.1.0...v6.1.1

v6.1.0

The first Kotest release of 2026 is here - 6.1 'Bouncing Boimler'

  • Compatible with Kotlin 2.0, 2.1, 2.2 and 2.3
  • Enhanced data-testing with new syntax that mirrors the spec style to enable more intuitive tests. This also gives you control over how data tests interact with test lifecycle callbacks!
  • Much improved IDE plugin support including running individual tests for native targets and fixes for many long standing issues. Re-run tests and jump to source for all nested tests in JVM mode
  • The test framework has been updated with Jasmine style f-focused syntax to mirror the x-disabled syntax.
  • Support for JUnit 6 via the new kotest-runner-junit6 module.
  • The popular TestContainers extension has been updated to support Docker ComposeContainers. Also now with an option to pipe the container logs directly into the test output for easier debugging!
  • Full support for the Gradle test-retry plugin.
  • Many improvements and bug fixes to our assertions library including support for custom equality in shouldBe assertions
  • New assertions added such as shouldBeAtMost(x) and shouldBeAtLeast(y) for primitives. A reminder that assertions don't require the Kotest test framework. They are still the best Kotlin assertions library, in our opinion, even if you are using JUnit or another test framework.
  • Recreate race conditions easily using our parallel runner.
  • Various property test improvements such as new Arb types for arrays and support for specifying Arb collection sizes globally. Just like assertions, the property testing library doesn't require the Kotest test framework.

What's Changed

... (truncated)

Commits
  • d797f4b reintroduce string spec tests with config to be root tests (#5608)
  • aa7c4d4 Update KSP generation to skip private classes (#5604)
  • 57e569b Refactor TypeMatchers to use MatcherResultBuilder for cleaner and consist...
  • 442ec8b Add new documentation for conditional exceptions in Kotest
  • ef74a3f Refine conditional annotations documentation
  • 3cac3a1 Fix Test Execution fails when gradle configuration cache is enabled (#5603)
  • 08e03ff Fix xmethod focus using DSL (#5588)
  • 2466415 Add action-junit-report to PRs (#5593)
  • 2ed8c5e Restore JS tests module (#5597)
  • 3bc237d Add MatcherResultBuilder for third parties to integrate matchers (#5594)
  • Additional commits viewable in compare view

Updates `io.kotest:kotest-assertions-json` from 6.0.7 to 6.1.1
Release notes

Sourced from io.kotest:kotest-assertions-json's releases.

v6.1.1

What's Changed

Full Changelog: https://github.com/kotest/kotest/compare/v6.1.0...v6.1.1

v6.1.0

The first Kotest release of 2026 is here - 6.1 'Bouncing Boimler'

  • Compatible with Kotlin 2.0, 2.1, 2.2 and 2.3
  • Enhanced data-testing with new syntax that mirrors the spec style to enable more intuitive tests. This also gives you control over how data tests interact with test lifecycle callbacks!
  • Much improved IDE plugin support including running individual tests for native targets and fixes for many long standing issues. Re-run tests and jump to source for all nested tests in JVM mode
  • The test framework has been updated with Jasmine style f-focused syntax to mirror the x-disabled syntax.
  • Support for JUnit 6 via the new kotest-runner-junit6 module.
  • The popular TestContainers extension has been updated to support Docker ComposeContainers. Also now with an option to pipe the container logs directly into the test output for easier debugging!
  • Full support for the Gradle test-retry plugin.
  • Many improvements and bug fixes to our assertions library including support for custom equality in shouldBe assertions
  • New assertions added such as shouldBeAtMost(x) and shouldBeAtLeast(y) for primitives. A reminder that assertions don't require the Kotest test framework. They are still the best Kotlin assertions library, in our opinion, even if you are using JUnit or another test framework.
  • Recreate race conditions easily using our parallel runner.
  • Various property test improvements such as new Arb types for arrays and support for specifying Arb collection sizes globally. Just like assertions, the property testing library doesn't require the Kotest test framework.

What's Changed

... (truncated)

Commits
  • d797f4b reintroduce string spec tests with config to be root tests (#5608)
  • aa7c4d4 Update KSP generation to skip private classes (#5604)
  • 57e569b Refactor TypeMatchers to use MatcherResultBuilder for cleaner and consist...
  • 442ec8b Add new documentation for conditional exceptions in Kotest
  • ef74a3f Refine conditional annotations documentation
  • 3cac3a1 Fix Test Execution fails when gradle configuration cache is enabled (#5603)
  • 08e03ff Fix xmethod focus using DSL (#5588)
  • 2466415 Add action-junit-report to PRs (#5593)
  • 2ed8c5e Restore JS tests module (#5597)
  • 3bc237d Add MatcherResultBuilder for third parties to integrate matchers (#5594)
  • Additional commits viewable in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5b6b087..04a006b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,7 +15,7 @@ openapi-generator = "7.18.0" awaitility = "4.3.0" collections-immutable = "0.4.0" coroutines = "1.10.2" -kotest = "6.0.7" +kotest = "6.1.1" kotlinx-io = "0.8.2" ktor = "3.2.3" logging = "7.0.14" From 6f1c120c27e0414461a3eb188baca5affca6f82a Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Wed, 4 Feb 2026 16:02:58 +0100 Subject: [PATCH 127/449] Detekt configuration (#501) - generate detekt config - enable several rules - disable ktlint backing property check ## Motivation and Context Centralized control of detekt rules. Using detekt 2 configuration ## How Has This Been Tested? locally ## Breaking Changes - ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] 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 --- .editorconfig | 1 + build.gradle.kts | 2 +- config/detekt/detekt.yml | 853 ++++++++++++++++++ detekt.yml | 7 - .../kotlin/sdk/client/StdioClientTransport.kt | 3 +- .../kotlin/sdk/client/MockMcp.kt | 4 - .../sdk/client/OldStreamableHttpClientTest.kt | 1 - .../sdk/client/StreamableHttpClientTest.kt | 1 - .../kotlin/sdk/server/Server.kt | 6 - .../kotlin/sdk/server/ServerSession.kt | 3 - 10 files changed, 856 insertions(+), 25 deletions(-) create mode 100644 config/detekt/detekt.yml delete mode 100644 detekt.yml diff --git a/.editorconfig b/.editorconfig index 5c283ed..d8964b9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -30,6 +30,7 @@ ktlint_class_signature_rule_force_multiline_when_parameter_count_greater_or_equa ktlint_function_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = 4 ktlint_standard_chain-method-continuation = disabled ktlint_ignore_back_ticked_identifier = true +ktlint_standard_backing-property-naming = disabled ktlint_standard_multiline-expression-wrapping = disabled ktlint_standard_when-entry-bracing = disabled diff --git a/build.gradle.kts b/build.gradle.kts index 2161a96..dc72359 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -24,7 +24,7 @@ subprojects { apply(plugin = "dev.detekt") detekt { - config = files("$rootDir/detekt.yml") + config = files("$rootDir/config/detekt/detekt.yml") buildUponDefaultConfig = true failOnSeverity.set(FailOnSeverity.Never) } diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml new file mode 100644 index 0000000..5493fb7 --- /dev/null +++ b/config/detekt/detekt.yml @@ -0,0 +1,853 @@ +config: + validation: true + warningsAsErrors: false + checkExhaustiveness: false + # when writing own rules with new properties, exclude the property path e.g.: ['my_rule_set', '.*>.*>[my_property]'] + excludes: [] + +processors: + active: true + exclude: + - 'DetektProgressListener' + # - 'KtFileCountProcessor' + # - 'PackageCountProcessor' + # - 'ClassCountProcessor' + # - 'FunctionCountProcessor' + # - 'PropertyCountProcessor' + # - 'ProjectComplexityProcessor' + # - 'ProjectCognitiveComplexityProcessor' + # - 'ProjectLLOCProcessor' + # - 'ProjectCLOCProcessor' + # - 'ProjectLOCProcessor' + # - 'ProjectSLOCProcessor' + # - 'LicenseHeaderLoaderExtension' + +console-reports: + active: true + exclude: + - 'ProjectStatisticsReport' + - 'ComplexityReport' + - 'NotificationReport' + - 'IssuesReport' + - 'FileBasedIssuesReport' + # - 'LiteIssuesReport' + +comments: + active: true + AbsentOrWrongFileLicense: + active: false + licenseTemplateFile: 'license.template' + licenseTemplateIsRegex: false + CommentOverPrivateFunction: + active: false + CommentOverPrivateProperty: + active: false + DeprecatedBlockTag: + active: false + EndOfSentenceFormat: + active: false + endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)' + KDocReferencesNonPublicProperty: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + OutdatedDocumentation: + active: false + matchTypeParameters: true + matchDeclarationsOrder: true + allowParamOnConstructorProperties: false + UndocumentedPublicClass: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + searchInNestedClass: true + searchInInnerClass: true + searchInInnerObject: true + searchInInnerInterface: true + searchInProtectedClass: false + ignoreDefaultCompanionObject: false + UndocumentedPublicFunction: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + searchProtectedFunction: false + UndocumentedPublicProperty: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + searchProtectedProperty: false + ignoreEnumEntries: false + +complexity: + active: true + CognitiveComplexMethod: + active: false + allowedComplexity: 15 + ComplexCondition: + active: true + allowedConditions: 4 + ComplexInterface: + active: false + allowedDefinitions: 10 + includeStaticDeclarations: false + includePrivateDeclarations: false + ignoreOverloaded: false + CyclomaticComplexMethod: + active: true + allowedComplexity: 15 + ignoreSingleWhenExpression: false + ignoreSimpleWhenEntries: false + ignoreNestingFunctions: false + ignoreLocalFunctions: false + nestingFunctions: + - 'also' + - 'apply' + - 'forEach' + - 'isNotNull' + - 'ifNull' + - 'let' + - 'run' + - 'use' + - 'with' + LabeledExpression: + active: false + ignoredLabels: [] + LargeClass: + active: false + allowedLines: 600 + LongMethod: + active: true + allowedLines: 150 + LongParameterList: + active: true + allowedFunctionParameters: 8 + allowedConstructorParameters: 8 + ignoreDefaultParameters: false + ignoreDataClasses: true + ignoreAnnotatedParameter: [] + MethodOverloading: + active: true + allowedOverloads: 6 + NamedArguments: + active: false + allowedArguments: 3 + ignoreArgumentsMatchingNames: false + NestedBlockDepth: + active: true + allowedDepth: 4 + NestedScopeFunctions: + active: false + allowedDepth: 1 + functions: + - 'kotlin.apply' + - 'kotlin.run' + - 'kotlin.with' + - 'kotlin.let' + - 'kotlin.also' + ReplaceSafeCallChainWithRun: + active: false + StringLiteralDuplication: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + allowedDuplications: 3 + ignoreAnnotation: true + allowedWithLengthLessThan: 5 + ignoreStringsRegex: '$^' + TooManyFunctions: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + allowedFunctionsPerFile: 20 + allowedFunctionsPerClass: 20 + allowedFunctionsPerInterface: 20 + allowedFunctionsPerObject: 20 + allowedFunctionsPerEnum: 5 + ignoreDeprecated: true + ignorePrivate: false + ignoreInternal: false + ignoreOverridden: false + ignoreAnnotatedFunctions: [] + +coroutines: + active: true + CoroutineLaunchedInTestWithoutRunTest: + active: false + GlobalCoroutineUsage: + active: true + InjectDispatcher: + active: false + dispatcherNames: + - 'IO' + - 'Default' + - 'Unconfined' + RedundantSuspendModifier: + active: true + SleepInsteadOfDelay: + active: true + SuspendFunInFinallySection: + active: false + SuspendFunSwallowedCancellation: + active: false + SuspendFunWithCoroutineScopeReceiver: + active: true + aliases: ['SuspendFunctionOnCoroutineScope'] + SuspendFunWithFlowReturnType: + active: true + +empty-blocks: + active: true + EmptyCatchBlock: + active: true + allowedExceptionNameRegex: '_|(ignore|expected).*' + EmptyClassBlock: + active: true + EmptyDefaultConstructor: + active: true + EmptyDoWhileBlock: + active: true + EmptyElseBlock: + active: true + EmptyFinallyBlock: + active: true + EmptyForBlock: + active: true + EmptyFunctionBlock: + active: false + ignoreOverridden: false + EmptyIfBlock: + active: true + EmptyInitBlock: + active: true + EmptyKotlinFile: + active: true + EmptySecondaryConstructor: + active: true + EmptyTryBlock: + active: true + EmptyWhenBlock: + active: true + EmptyWhileBlock: + active: true + +exceptions: + active: true + ErrorUsageWithThrowable: + active: false + ExceptionRaisedInUnexpectedLocation: + active: true + methodNames: + - 'equals' + - 'finalize' + - 'hashCode' + - 'toString' + InstanceOfCheckForException: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + NotImplementedDeclaration: + active: false + ObjectExtendsThrowable: + active: true + PrintStackTrace: + active: true + RethrowCaughtException: + active: true + ReturnFromFinally: + active: true + ignoreLabeled: false + SwallowedException: + active: true + ignoredExceptionTypes: + - 'InterruptedException' + - 'MalformedURLException' + - 'NumberFormatException' + - 'ParseException' + allowedExceptionNameRegex: '_|(ignore|expected).*' + ThrowingExceptionFromFinally: + active: true + ThrowingExceptionInMain: + active: false + ThrowingExceptionsWithoutMessageOrCause: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + exceptions: + - 'ArrayIndexOutOfBoundsException' + - 'Exception' + - 'IllegalArgumentException' + - 'IllegalMonitorStateException' + - 'IllegalStateException' + - 'IndexOutOfBoundsException' + - 'NullPointerException' + - 'RuntimeException' + - 'Throwable' + ThrowingNewInstanceOfSameException: + active: true + TooGenericExceptionCaught: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + exceptionNames: + - 'ArrayIndexOutOfBoundsException' + - 'Error' + - 'Exception' + - 'IllegalMonitorStateException' + - 'IndexOutOfBoundsException' + - 'NullPointerException' + - 'RuntimeException' + - 'Throwable' + allowedExceptionNameRegex: '_|(ignore|expected).*' + TooGenericExceptionThrown: + active: true + exceptionNames: + - 'Error' + - 'Exception' + - 'RuntimeException' + - 'Throwable' + +naming: + active: true + BooleanPropertyNaming: + active: false + allowedPattern: '^(is|has|are)' + ClassNaming: + active: true + aliases: ['ClassName'] + classPattern: '[A-Z][a-zA-Z0-9]*' + ConstructorParameterNaming: + active: true + parameterPattern: '[a-z][A-Za-z0-9]*' + privateParameterPattern: '[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + EnumNaming: + active: true + aliases: ['EnumEntryName'] + enumEntryPattern: '[A-Z][_a-zA-Z0-9]*' + ForbiddenClassName: + active: false + forbiddenName: [] + FunctionNameMaxLength: + active: false + aliases: ['FunctionMaxNameLength'] + maximumFunctionNameLength: 30 + FunctionNameMinLength: + active: false + aliases: ['FunctionMinNameLength'] + minimumFunctionNameLength: 3 + FunctionNaming: + active: true + aliases: ['FunctionName'] + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + functionPattern: '[a-z][a-zA-Z0-9]*' + excludeClassPattern: '$^' + FunctionParameterNaming: + active: true + parameterPattern: '[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + InvalidPackageDeclaration: + active: true + aliases: ['PackageDirectoryMismatch'] + rootPackage: '' + requireRootInDeclaration: false + LambdaParameterNaming: + active: false + parameterPattern: '[a-z][A-Za-z0-9]*|_' + MatchingDeclarationName: + active: true + mustBeFirst: true + multiplatformTargets: + - 'ios' + - 'android' + - 'js' + - 'jvm' + - 'native' + - 'iosArm64' + - 'iosX64' + - 'macosX64' + - 'mingwX64' + - 'linuxX64' + MemberNameEqualsClassName: + active: true + ignoreOverridden: true + NoNameShadowing: + active: true + NonBooleanPropertyPrefixedWithIs: + active: true + ObjectPropertyNaming: + active: true + aliases: ['ObjectPropertyName'] + constantPattern: '[A-Za-z][_A-Za-z0-9]*' + propertyPattern: '[A-Za-z][_A-Za-z0-9]*' + privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*' + PackageNaming: + active: true + aliases: ['PackageName'] + packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*' + TopLevelPropertyNaming: + active: true + constantPattern: '[A-Z][_A-Z0-9]*' + propertyPattern: '[A-Za-z][_A-Za-z0-9]*' + privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*' + VariableMaxLength: + active: false + maximumVariableNameLength: 64 + VariableMinLength: + active: false + minimumVariableNameLength: 1 + VariableNaming: + active: true + aliases: ['PropertyName'] + variablePattern: '[a-z][A-Za-z0-9]*' + privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + +performance: + active: true + ArrayPrimitive: + active: true + CouldBeSequence: + active: false + allowedOperations: 3 + ForEachOnRange: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + SpreadOperator: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + UnnecessaryPartOfBinaryExpression: + active: false + UnnecessaryTemporaryInstantiation: + active: true + UnnecessaryTypeCasting: + active: false + +potential-bugs: + active: true + AvoidReferentialEquality: + active: true + forbiddenTypePatterns: + - 'kotlin.String' + CastNullableToNonNullableType: + active: true + ignorePlatformTypes: true + CastToNullableType: + active: false + CharArrayToStringCall: + active: false + Deprecation: + active: false + aliases: ['DEPRECATION'] + excludeImportStatements: false + DontDowncastCollectionTypes: + active: false + DoubleMutabilityForCollection: + active: true + aliases: ['DoubleMutability'] + mutableTypes: + - 'kotlin.collections.MutableList' + - 'kotlin.collections.MutableMap' + - 'kotlin.collections.MutableSet' + - 'java.util.ArrayList' + - 'java.util.LinkedHashSet' + - 'java.util.HashSet' + - 'java.util.LinkedHashMap' + - 'java.util.HashMap' + ElseCaseInsteadOfExhaustiveWhen: + active: false + ignoredSubjectTypes: [] + EqualsAlwaysReturnsTrueOrFalse: + active: true + EqualsWithHashCodeExist: + active: true + ExitOutsideMain: + active: false + ExplicitGarbageCollectionCall: + active: true + HasPlatformType: + active: true + IgnoredReturnValue: + active: true + restrictToConfig: true + returnValueAnnotations: + - 'CheckResult' + - '*.CheckResult' + - 'CheckReturnValue' + - '*.CheckReturnValue' + ignoreReturnValueAnnotations: + - 'CanIgnoreReturnValue' + - '*.CanIgnoreReturnValue' + returnValueTypes: + - 'kotlin.Function*' + - 'kotlin.sequences.Sequence' + - 'kotlinx.coroutines.flow.*Flow' + - 'java.util.stream.*Stream' + ignoreFunctionCall: [] + ImplicitDefaultLocale: + active: true + ImplicitUnitReturnType: + active: false + allowExplicitReturnType: true + InvalidRange: + active: true + IteratorHasNextCallsNextMethod: + active: true + IteratorNotThrowingNoSuchElementException: + active: true + LateinitUsage: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + ignoreOnClassesPattern: '' + MapGetWithNotNullAssertionOperator: + active: true + MissingPackageDeclaration: + active: true + excludes: ['**/*.kts'] + MissingSuperCall: + active: false + mustInvokeSuperAnnotations: + - 'androidx.annotation.CallSuper' + - 'javax.annotation.OverridingMethodsMustInvokeSuper' + MissingUseCall: + active: false + NullCheckOnMutableProperty: + active: false + NullableToStringCall: + active: false + PropertyUsedBeforeDeclaration: + active: false + UnconditionalJumpStatementInLoop: + active: false + UnnamedParameterUse: + active: false + allowAdjacentDifferentTypeParams: true + allowSingleParamUse: true + ignoreArgumentsMatchingNames: true + ignoreFunctionCall: [] + UnnecessaryNotNullCheck: + active: true + UnnecessaryNotNullOperator: + active: true + UnnecessarySafeCall: + active: true + UnreachableCatchBlock: + active: true + UnreachableCode: + active: true + UnsafeCallOnNullableType: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + UnsafeCast: + active: true + aliases: ['UNCHECKED_CAST'] + UnusedUnaryOperator: + active: true + UselessPostfixExpression: + active: true + WrongEqualsTypeParameter: + active: true + +style: + active: true + AbstractClassCanBeConcreteClass: + active: true + AbstractClassCanBeInterface: + active: true + AlsoCouldBeApply: + active: false + BracesOnIfStatements: + active: true + singleLine: 'never' + multiLine: 'always' + BracesOnWhenStatements: + active: true + singleLine: 'necessary' + multiLine: 'consistent' + CanBeNonNullable: + active: true + CascadingCallWrapping: + active: false + includeElvis: true + ClassOrdering: + active: false + CollapsibleIfStatements: + active: true + DataClassContainsFunctions: + active: false + conversionFunctionPrefix: + - 'to' + allowOperators: false + DataClassShouldBeImmutable: + active: false + DestructuringDeclarationWithTooManyEntries: + active: true + maxDestructuringEntries: 3 + DoubleNegativeExpression: + active: false + DoubleNegativeLambda: + active: false + negativeFunctions: + - reason: 'Use `takeIf` instead.' + value: 'takeUnless' + - reason: 'Use `all` instead.' + value: 'none' + negativeFunctionNameParts: + - 'not' + - 'non' + EqualsNullCall: + active: true + EqualsOnSignatureLine: + active: true + ExplicitCollectionElementAccessMethod: + active: false + ExplicitItLambdaMultipleParameters: + active: true + ExplicitItLambdaParameter: + active: true + ExpressionBodySyntax: + active: false + includeLineWrapping: false + ForbiddenAnnotation: + active: true + annotations: + - reason: 'it is a java annotation. Use `Suppress` instead.' + value: 'java.lang.SuppressWarnings' + - reason: 'it is a java annotation. Use `kotlin.Deprecated` instead.' + value: 'java.lang.Deprecated' + - reason: 'it is a java annotation. Use `kotlin.annotation.MustBeDocumented` instead.' + value: 'java.lang.annotation.Documented' + - reason: 'it is a java annotation. Use `kotlin.annotation.Target` instead.' + value: 'java.lang.annotation.Target' + - reason: 'it is a java annotation. Use `kotlin.annotation.Retention` instead.' + value: 'java.lang.annotation.Retention' + - reason: 'it is a java annotation. Use `kotlin.annotation.Repeatable` instead.' + value: 'java.lang.annotation.Repeatable' + - reason: 'Kotlin does not support @Inherited annotation, see https://youtrack.jetbrains.com/issue/KT-22265' + value: 'java.lang.annotation.Inherited' + ForbiddenComment: + active: true + comments: + - reason: 'Forbidden FIXME todo marker in comment, please fix the problem.' + value: 'FIXME:' + - reason: 'Forbidden STOPSHIP todo marker in comment, please address the problem before shipping the code.' + value: 'STOPSHIP:' + - reason: 'Forbidden TODO todo marker in comment, please do the changes.' + value: 'TODO:' + allowedPatterns: '' + ForbiddenImport: + active: false + imports: [] + forbiddenPatterns: '' + ForbiddenMethodCall: + active: true + methods: + - reason: 'print does not allow you to configure the output stream. Use a logger instead.' + value: 'kotlin.io.print' + - reason: 'println does not allow you to configure the output stream. Use a logger instead.' + value: 'kotlin.io.println' + - reason: 'using `BigDecimal(Double)` can result in unexpected floating point precision behavior. Use `BigDecimal.valueOf(Double)` or `String.toBigDecimalOrNull()` instead.' + value: 'java.math.BigDecimal.(kotlin.Double)' + - reason: 'using `BigDecimal(String)` can result in a `NumberFormatException`. Use `String.toBigDecimalOrNull()`' + value: 'java.math.BigDecimal.(kotlin.String)' + - reason: 'It is marked as obsolete. Use `kotlin.time.measureTime` instead.' + value: 'kotlin.system.measureTimeMillis' + ForbiddenNamedParam: + active: false + methods: [] + ForbiddenOptIn: + active: false + markerClasses: [] + ForbiddenSuppress: + active: false + rules: [] + ForbiddenVoid: + active: true + ignoreOverridden: false + ignoreUsageInGenerics: false + FunctionOnlyReturningConstant: + active: true + ignoreOverridableFunction: true + ignoreActualFunction: true + excludedFunctions: [] + LoopWithTooManyJumpStatements: + active: true + maxJumpCount: 3 + MagicNumber: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/*.kts'] + ignoreNumbers: + - '-1' + - '0' + - '1' + - '2' + ignoreHashCodeFunction: true + ignorePropertyDeclaration: false + ignoreLocalVariableDeclaration: false + ignoreConstantDeclaration: true + ignoreCompanionObjectPropertyDeclaration: true + ignoreAnnotation: false + ignoreNamedArgument: true + ignoreEnums: false + ignoreRanges: false + ignoreExtensionFunctions: true + MandatoryBracesLoops: + active: true + MaxChainedCallsOnSameLine: + active: true + maxChainedCalls: 5 + MaxLineLength: + active: true + maxLineLength: 120 + excludePackageStatements: true + excludeImportStatements: true + excludeCommentStatements: false + excludeRawStrings: true + MayBeConstant: + active: true + ModifierOrder: + active: true + MultilineLambdaItParameter: + active: true + MultilineRawStringIndentation: + active: false + indentSize: 4 + trimmingMethods: + - 'trimIndent' + - 'trimMargin' + NestedClassesVisibility: + active: true + NewLineAtEndOfFile: + active: true + NoTabs: + active: false + NullableBooleanCheck: + active: false + ObjectLiteralToLambda: + active: true + OptionalAbstractKeyword: + active: true + OptionalUnit: + active: false + ProtectedMemberInFinalClass: + active: true + RangeUntilInsteadOfRangeTo: + active: false + RedundantConstructorKeyword: + active: false + RedundantExplicitType: + active: false + RedundantHigherOrderMapUsage: + active: true + RedundantVisibilityModifier: + active: false + ReturnCount: + active: true + max: 6 + excludedFunctions: + - 'equals' + excludeLabeled: false + excludeReturnFromLambda: true + excludeGuardClauses: false + SafeCast: + active: true + SerialVersionUIDInSerializableClass: + active: true + SpacingAfterPackageDeclaration: + active: true + StringShouldBeRawString: + active: false + maxEscapedCharacterCount: 2 + ignoredCharacters: [] + ThrowsCount: + active: true + max: 3 + excludeGuardClauses: false + TrailingWhitespace: + active: false + TrimMultilineRawString: + active: false + trimmingMethods: + - 'trimIndent' + - 'trimMargin' + UnderscoresInNumericLiterals: + active: false + acceptableLength: 4 + allowNonStandardGrouping: false + UnnecessaryAnnotationUseSiteTarget: + active: false + UnnecessaryAny: + active: true + UnnecessaryApply: + active: true + UnnecessaryBackticks: + active: false + UnnecessaryBracesAroundTrailingLambda: + active: false + UnnecessaryFilter: + active: true + UnnecessaryInheritance: + active: true + UnnecessaryInnerClass: + active: false + UnnecessaryLet: + active: false + UnnecessaryParentheses: + active: false + allowForUnclearPrecedence: false + UnnecessaryReversed: + active: false + UnusedImport: + active: true + additionalOperatorSet: [] + UnusedParameter: + active: true + aliases: ['UNUSED_PARAMETER', 'unused'] + allowedNames: 'ignored|expected' + UnusedPrivateClass: + active: true + aliases: ['unused'] + UnusedPrivateFunction: + active: true + aliases: ['unused'] + allowedNames: '' + UnusedPrivateProperty: + active: true + aliases: ['unused'] + allowedNames: 'ignored|expected|serialVersionUID' + UnusedVariable: + active: true + aliases: ['UNUSED_VARIABLE', 'unused'] + allowedNames: 'ignored|_' + UseAnyOrNoneInsteadOfFind: + active: true + UseArrayLiteralsInAnnotations: + active: true + UseCheckNotNull: + active: true + UseCheckOrError: + active: true + UseDataClass: + active: false + allowVars: false + UseEmptyCounterpart: + active: true + UseIfEmptyOrIfBlank: + active: false + UseIfInsteadOfWhen: + active: false + ignoreWhenContainingVariableDeclaration: false + UseIsNullOrEmpty: + active: true + UseLet: + active: false + UseOrEmpty: + active: true + UseRequire: + active: true + UseRequireNotNull: + active: true + UseSumOfInsteadOfFlatMapSize: + active: false + UselessCallOnNotNull: + active: true + UtilityClassWithPublicConstructor: + active: true + VarCouldBeVal: + active: true + aliases: ['CanBeVal'] + ignoreLateinitVar: false + WildcardImport: + active: false + excludeImports: + - 'java.util.*' diff --git a/detekt.yml b/detekt.yml deleted file mode 100644 index f9eba19..0000000 --- a/detekt.yml +++ /dev/null @@ -1,7 +0,0 @@ -config: - validation: true - warningsAsErrors: true - -complexity: - LongMethod: - excludes: ['**Test.kt'] diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt index 0b774a6..ec567f3 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt @@ -242,7 +242,7 @@ public class StdioClientTransport @JvmOverloads public constructor( message = "Transport is closed", ) } - @Suppress("TooGenericExceptionCaught", "SwallowedException") + @Suppress("SwallowedException") try { sendChannel.send(message) } catch (e: CancellationException) { @@ -298,7 +298,6 @@ public class StdioClientTransport @JvmOverloads public constructor( } private suspend fun handleJSONRPCMessage(msg: JSONRPCMessage) { - @Suppress("TooGenericExceptionCaught") try { _onMessage.invoke(msg) } catch (e: Throwable) { diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt index af2110a..a4447cf 100644 --- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt +++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt @@ -31,7 +31,6 @@ internal class MockMcp(verbose: Boolean = false) { val url = "${mokksy.baseUrl()}/mcp" - @Suppress("LongParameterList") fun onInitialize( clientName: String? = null, sessionId: String, @@ -109,7 +108,6 @@ internal class MockMcp(verbose: Boolean = false) { } } - @Suppress("LongParameterList") fun handleWithResult( httpMethod: HttpMethod = HttpMethod.Post, jsonRpcMethod: String, @@ -145,7 +143,6 @@ internal class MockMcp(verbose: Boolean = false) { } } - @Suppress("LongParameterList") fun handleWithResult( httpMethod: HttpMethod = HttpMethod.Post, jsonRpcMethod: String, @@ -170,7 +167,6 @@ internal class MockMcp(verbose: Boolean = false) { ) } - @Suppress("LongParameterList") fun handleJSONRPCRequest( httpMethod: HttpMethod = HttpMethod.Post, jsonRpcMethod: String, diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldStreamableHttpClientTest.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldStreamableHttpClientTest.kt index 33897e6..d61d349 100644 --- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldStreamableHttpClientTest.kt +++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldStreamableHttpClientTest.kt @@ -30,7 +30,6 @@ import kotlin.uuid.Uuid */ @OptIn(ExperimentalUuidApi::class) @TestInstance(TestInstance.Lifecycle.PER_CLASS) -@Suppress("LongMethod") internal class OldStreamableHttpClientTest : AbstractStreamableHttpClientTest() { @Test diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt index 06d485f..6513b46 100644 --- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt +++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt @@ -27,7 +27,6 @@ import kotlin.uuid.Uuid @OptIn(ExperimentalUuidApi::class) @TestInstance(TestInstance.Lifecycle.PER_CLASS) -@Suppress("LongMethod") internal class StreamableHttpClientTest : AbstractStreamableHttpClientTest() { @Test diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt index ef52d72..2f80bf8 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt @@ -74,7 +74,6 @@ public class ServerOptions(public val capabilities: ServerCapabilities, enforceS * this server. The provider is called each time a new session is started to support dynamic instructions. * @param block A block to configure the mcp server. */ -@Suppress("TooManyFunctions") public open class Server( protected val serverInfo: Implementation, protected val options: ServerOptions, @@ -96,13 +95,10 @@ public open class Server( block: Server.() -> Unit = {}, ) : this(serverInfo, options, { instructions }, block) - @Suppress("ktlint:standard:backing-property-naming") private var _onInitialized: (() -> Unit) = {} - @Suppress("ktlint:standard:backing-property-naming") private var _onConnect: (() -> Unit) = {} - @Suppress("ktlint:standard:backing-property-naming") private var _onClose: () -> Unit = {} @OptIn(ExperimentalTime::class) @@ -320,7 +316,6 @@ public open class Server( * @param handler A suspend function that handles executing the tool when called by the client. * @throws IllegalStateException If the server does not support tools. */ - @Suppress("LongParameterList") public fun addTool( name: String, description: String, @@ -575,7 +570,6 @@ public open class Server( ) } - @Suppress("TooGenericExceptionCaught") // Execute the tool handler and catch any errors return try { logger.trace { "Executing tool ${requestParams.name} with input: ${requestParams.arguments}" } diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt index e65db2f..ec20db2 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt @@ -43,7 +43,6 @@ private val logger = KotlinLogging.logger {} /** * Represents a server session. */ -@Suppress("TooManyFunctions") public open class ServerSession( protected val serverInfo: Implementation, options: ServerOptions, @@ -53,10 +52,8 @@ public open class ServerSession( @OptIn(ExperimentalUuidApi::class) public val sessionId: String = Uuid.random().toString() - @Suppress("ktlint:standard:backing-property-naming") private var _onInitialized: (() -> Unit) = {} - @Suppress("ktlint:standard:backing-property-naming") private var _onClose: () -> Unit = {} /** From 92823b987fb00fabd78f3db09e2eb9c100548f5e Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Wed, 4 Feb 2026 20:04:11 +0100 Subject: [PATCH 128/449] Deprecation cycle (#498) - increase the deprecation level - remove tests that use deprecated objects with deprecation level = error ## Motivation and Context deprecation lifecycle ## How Has This Been Tested? - ## Breaking Changes - ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] 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 --- .../kotlin/sdk/client/OldSchemaClientTest.kt | 1042 ----------------- .../sdk/client/OldSchemaSseTransportTest.kt | 123 -- .../OldSchemaInMemoryTransportTest.kt | 110 -- .../sdk/shared/OldSchemaBaseTransportTest.kt | 63 - .../sdk/shared/OldSchemaInMemoryTransport.kt | 47 - .../OldSchemaAbstractPromptIntegrationTest.kt | 698 ----------- ...ldSchemaAbstractResourceIntegrationTest.kt | 319 ----- .../OldSchemaAbstractToolIntegrationTest.kt | 820 ------------- .../kotlin/OldSchemaKotlinTestBase.kt | 184 --- .../sse/OldSchemaPromptIntegrationTestSse.kt | 7 - .../sse/OldSchemaResourceIntegrationTestSse | 7 - .../sse/OldSchemaToolIntegrationTestSse.kt | 7 - .../OldSchemaPromptIntegrationTestStdio.kt | 7 - .../OldSchemaResourceIntegrationTestStdio.kt | 7 - .../OldSchemaToolIntegrationTestStdio.kt | 7 - .../OldSchemaWebSocketIntegrationTest.kt | 193 --- .../OldSchemaAbstractSseIntegrationTest.kt | 105 -- .../sse/OldSchemaSseIntegrationTest.kt | 109 -- ...dSchemaAbstractKotlinClientTsServerTest.kt | 77 -- ...emaKotlinClientTsServerEdgeCasesTestSse.kt | 200 ---- .../OldSchemaKotlinClientTsServerTestSse.kt | 25 - .../OldSchemaKotlinServerForTsClientSse.kt | 466 -------- ...aKotlinClientTsServerEdgeCasesTestStdio.kt | 177 --- .../OldSchemaKotlinClientTsServerTestStdio.kt | 23 - .../OldSchemaAbstractServerFeaturesTest.kt | 43 - .../server/OldSchemaServerInstructionsTest.kt | 68 -- .../sdk/server/OldSchemaServerPromptsTest.kt | 213 ---- .../server/OldSchemaServerResourcesTest.kt | 135 --- .../sdk/server/OldSchemaServerToolsTest.kt | 91 -- .../sdk/server/ServerInstructionsTest.kt | 4 +- .../kotlin/sdk/client/SSEClientTransport.kt | 3 - .../OldSchemaClientMetaParameterTest.kt | 275 ----- .../sdk/client/OldSchemaMockTransport.kt | 95 -- ...SchemaStreamableHttpClientTransportTest.kt | 422 ------- .../sdk/client/OldStreamableHttpClientTest.kt | 218 ---- .../kotlin/sdk/shared/Protocol.kt | 2 +- .../modelcontextprotocol/kotlin/sdk/types.kt | 399 ++++--- .../kotlin/sdk/types.util.kt | 8 +- .../kotlin/sdk/types/common.kt | 1 + .../kotlin/sdk/types/completion.kt | 2 +- .../kotlin/sdk/types/prompts.kt | 3 +- .../kotlin/sdk/types/resources.kt | 8 +- .../kotlin/sdk/types/roots.kt | 2 +- .../kotlin/sdk/types/tools.kt | 2 +- .../OldSchemaAudioContentSerializationTest.kt | 33 - .../sdk/OldSchemaCallToolResultUtilsTest.kt | 63 - .../sdk/OldSchemaRequestSerializerTest.kt | 329 ------ .../sdk/OldSchemaToolSerializationTest.kt | 507 -------- .../kotlin/sdk/OldSchemaTypesTest.kt | 450 ------- .../OldSchemaProgressNotificationsTest.kt | 71 -- .../sdk/shared/OldSchemaReadBufferTest.kt | 62 - .../kotlin/sdk/server/SSEServerTransport.kt | 3 - .../kotlin/sdk/server/Server.kt | 4 +- .../OldSchemaStdioServerTransportTest.kt | 140 --- 54 files changed, 224 insertions(+), 8255 deletions(-) delete mode 100644 integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaClientTest.kt delete mode 100644 integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaSseTransportTest.kt delete mode 100644 integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/OldSchemaInMemoryTransportTest.kt delete mode 100644 integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaBaseTransportTest.kt delete mode 100644 integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaInMemoryTransport.kt delete mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractPromptIntegrationTest.kt delete mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractResourceIntegrationTest.kt delete mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractToolIntegrationTest.kt delete mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaKotlinTestBase.kt delete mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaPromptIntegrationTestSse.kt delete mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaResourceIntegrationTestSse delete mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaToolIntegrationTestSse.kt delete mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaPromptIntegrationTestStdio.kt delete mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaResourceIntegrationTestStdio.kt delete mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaToolIntegrationTestStdio.kt delete mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/OldSchemaWebSocketIntegrationTest.kt delete mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaAbstractSseIntegrationTest.kt delete mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaSseIntegrationTest.kt delete mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/OldSchemaAbstractKotlinClientTsServerTest.kt delete mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerEdgeCasesTestSse.kt delete mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerTestSse.kt delete mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinServerForTsClientSse.kt delete mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerEdgeCasesTestStdio.kt delete mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerTestStdio.kt delete mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaAbstractServerFeaturesTest.kt delete mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerInstructionsTest.kt delete mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerPromptsTest.kt delete mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerResourcesTest.kt delete mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerToolsTest.kt delete mode 100644 kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaClientMetaParameterTest.kt delete mode 100644 kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaMockTransport.kt delete mode 100644 kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaStreamableHttpClientTransportTest.kt delete mode 100644 kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldStreamableHttpClientTest.kt delete mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaAudioContentSerializationTest.kt delete mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaCallToolResultUtilsTest.kt delete mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaRequestSerializerTest.kt delete mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaToolSerializationTest.kt delete mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaTypesTest.kt delete mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/models/OldSchemaProgressNotificationsTest.kt delete mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaReadBufferTest.kt delete mode 100644 kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaStdioServerTransportTest.kt diff --git a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaClientTest.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaClientTest.kt deleted file mode 100644 index f9ef4f3..0000000 --- a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaClientTest.kt +++ /dev/null @@ -1,1042 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.client - -import io.modelcontextprotocol.kotlin.sdk.ClientCapabilities -import io.modelcontextprotocol.kotlin.sdk.CreateElicitationRequest -import io.modelcontextprotocol.kotlin.sdk.CreateElicitationResult -import io.modelcontextprotocol.kotlin.sdk.CreateMessageResult -import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.InitializeRequest -import io.modelcontextprotocol.kotlin.sdk.InitializeResult -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage -import io.modelcontextprotocol.kotlin.sdk.JSONRPCRequest -import io.modelcontextprotocol.kotlin.sdk.JSONRPCResponse -import io.modelcontextprotocol.kotlin.sdk.LATEST_PROTOCOL_VERSION -import io.modelcontextprotocol.kotlin.sdk.ListResourcesRequest -import io.modelcontextprotocol.kotlin.sdk.ListResourcesResult -import io.modelcontextprotocol.kotlin.sdk.ListRootsRequest -import io.modelcontextprotocol.kotlin.sdk.ListToolsRequest -import io.modelcontextprotocol.kotlin.sdk.ListToolsResult -import io.modelcontextprotocol.kotlin.sdk.LoggingLevel -import io.modelcontextprotocol.kotlin.sdk.LoggingMessageNotification -import io.modelcontextprotocol.kotlin.sdk.Method -import io.modelcontextprotocol.kotlin.sdk.Role -import io.modelcontextprotocol.kotlin.sdk.Root -import io.modelcontextprotocol.kotlin.sdk.SUPPORTED_PROTOCOL_VERSIONS -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities -import io.modelcontextprotocol.kotlin.sdk.TextContent -import io.modelcontextprotocol.kotlin.sdk.Tool -import io.modelcontextprotocol.kotlin.sdk.server.Server -import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions -import io.modelcontextprotocol.kotlin.sdk.server.ServerSession -import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport -import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport -import io.modelcontextprotocol.kotlin.sdk.shared.TransportSendOptions -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.TimeoutCancellationException -import kotlinx.coroutines.cancel -import kotlinx.coroutines.delay -import kotlinx.coroutines.joinAll -import kotlinx.coroutines.launch -import kotlinx.coroutines.test.runTest -import kotlinx.coroutines.withTimeout -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.put -import kotlinx.serialization.json.putJsonObject -import kotlin.coroutines.cancellation.CancellationException -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertIs -import kotlin.test.assertTrue -import kotlin.test.fail - -class OldSchemaClientTest { - @Test - fun `should initialize with matching protocol version`() = runTest { - var initialised = false - val clientTransport = object : AbstractTransport() { - override suspend fun start() {} - - override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { - if (message !is JSONRPCRequest) return - initialised = true - val result = InitializeResult( - protocolVersion = LATEST_PROTOCOL_VERSION, - capabilities = ServerCapabilities(), - serverInfo = Implementation( - name = "test", - version = "1.0", - ), - ) - - val response = JSONRPCResponse( - id = message.id, - result = result, - ) - - _onMessage.invoke(response) - } - - override suspend fun close() { - } - } - - val client = Client( - clientInfo = Implementation( - name = "test client", - version = "1.0", - ), - options = ClientOptions( - capabilities = ClientCapabilities( - sampling = EmptyJsonObject, - ), - ), - ) - - client.connect(clientTransport) - assertTrue(initialised) - } - - @Test - fun `should initialize with supported older protocol version`() = runTest { - val oldVersion = SUPPORTED_PROTOCOL_VERSIONS[1] - val clientTransport = object : AbstractTransport() { - override suspend fun start() {} - - override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { - if (message !is JSONRPCRequest) return - check(message.method == Method.Defined.Initialize.value) - - val result = InitializeResult( - protocolVersion = oldVersion, - capabilities = ServerCapabilities(), - serverInfo = Implementation( - name = "test", - version = "1.0", - ), - ) - - val response = JSONRPCResponse( - id = message.id, - result = result, - ) - _onMessage.invoke(response) - } - - override suspend fun close() { - } - } - - val client = Client( - clientInfo = Implementation( - name = "test client", - version = "1.0", - ), - options = ClientOptions( - capabilities = ClientCapabilities( - sampling = EmptyJsonObject, - ), - ), - ) - - client.connect(clientTransport) - assertEquals( - Implementation("test", "1.0"), - client.serverVersion, - ) - } - - @Test - fun `should reject unsupported protocol version`() = runTest { - var closed = false - val clientTransport = object : AbstractTransport() { - override suspend fun start() {} - - override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { - if (message !is JSONRPCRequest) return - check(message.method == Method.Defined.Initialize.value) - - val result = InitializeResult( - protocolVersion = "invalid-version", - capabilities = ServerCapabilities(), - serverInfo = Implementation( - name = "test", - version = "1.0", - ), - ) - - val response = JSONRPCResponse( - id = message.id, - result = result, - ) - - _onMessage.invoke(response) - } - - override suspend fun close() { - closed = true - } - } - - val client = Client( - clientInfo = Implementation( - name = "test client", - version = "1.0", - ), - options = ClientOptions(), - ) - - assertFailsWith("Server's protocol version is not supported: invalid-version") { - client.connect(clientTransport) - } - - assertTrue(closed) - } - - @Test - fun `should reject due to non cancellation exception`() = runTest { - var closed = false - val failingTransport = object : AbstractTransport() { - override suspend fun start() {} - - override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { - if (message !is JSONRPCRequest) return - check(message.method == Method.Defined.Initialize.value) - throw IllegalStateException("Test error") - } - - override suspend fun close() { - closed = true - } - } - - val client = Client( - clientInfo = Implementation( - name = "test client", - version = "1.0", - ), - options = ClientOptions(), - ) - - val exception = assertFailsWith { - client.connect(failingTransport) - } - - assertEquals("Error connecting to transport: Test error", exception.message) - - assertTrue(closed) - } - - @Test - fun `should respect server capabilities`() = runTest { - val serverOptions = ServerOptions( - capabilities = ServerCapabilities( - resources = ServerCapabilities.Resources(null, null), - tools = ServerCapabilities.Tools(null), - ), - ) - val server = Server( - Implementation(name = "test server", version = "1.0"), - serverOptions, - ) - - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - - val client = Client( - clientInfo = Implementation(name = "test client", version = "1.0"), - options = ClientOptions( - capabilities = ClientCapabilities(sampling = EmptyJsonObject), - ), - ) - - val serverSessionResult = CompletableDeferred() - - listOf( - launch { - client.connect(clientTransport) - }, - launch { - serverSessionResult.complete(server.createSession(serverTransport)) - }, - ).joinAll() - - val serverSession = serverSessionResult.await() - serverSession.setRequestHandler(Method.Defined.Initialize) { _, _ -> - InitializeResult( - protocolVersion = LATEST_PROTOCOL_VERSION, - capabilities = ServerCapabilities( - resources = ServerCapabilities.Resources(null, null), - tools = ServerCapabilities.Tools(null), - ), - serverInfo = Implementation(name = "test", version = "1.0"), - ) - } - - serverSession.setRequestHandler(Method.Defined.ResourcesList) { _, _ -> - ListResourcesResult(resources = emptyList(), nextCursor = null) - } - - serverSession.setRequestHandler(Method.Defined.ToolsList) { _, _ -> - ListToolsResult(tools = emptyList(), nextCursor = null) - } - // Server supports resources and tools, but not prompts - val caps = client.serverCapabilities - assertEquals(ServerCapabilities.Resources(null, null), caps?.resources) - assertEquals(ServerCapabilities.Tools(null), caps?.tools) - assertTrue(caps?.prompts == null) // or check that prompts are absent - - // These should not throw - client.listResources() - client.listTools() - - // This should fail because prompts are not supported - val ex = assertFailsWith { - client.listPrompts() - } - assertTrue(ex.message?.contains("Server does not support prompts") == true) - } - - @Test - fun `should respect client notification capabilities`() = runTest { - val server = Server( - Implementation(name = "test server", version = "1.0"), - ServerOptions(capabilities = ServerCapabilities()), - ) - - val client = Client( - clientInfo = Implementation(name = "test client", version = "1.0"), - options = ClientOptions( - capabilities = ClientCapabilities( - roots = ClientCapabilities.Roots(listChanged = true), - ), - ), - ) - - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - - listOf( - launch { - client.connect(clientTransport) - println("Client connected") - }, - launch { - server.createSession(serverTransport) - println("Server connected") - }, - ).joinAll() - - // This should not throw because the client supports roots.listChanged - client.sendRootsListChanged() - - // Create a new client without the roots.listChanged capability - val clientWithoutCapability = Client( - clientInfo = Implementation(name = "test client without capability", version = "1.0"), - options = ClientOptions( - capabilities = ClientCapabilities(), - // enforceStrictCapabilities = true // TODO() - ), - ) - - clientWithoutCapability.connect(clientTransport) - // Using the same transport pair might not be realistic - in a real scenario you'd create another pair. - // Adjust if necessary. - - // This should fail - val ex = assertFailsWith { - clientWithoutCapability.sendRootsListChanged() - } - assertTrue(ex.message?.startsWith("Client does not support") == true) - } - - @Test - fun `should respect server notification capabilities`() = runTest { - val server = Server( - Implementation(name = "test server", version = "1.0"), - ServerOptions( - capabilities = ServerCapabilities( - logging = EmptyJsonObject, - resources = ServerCapabilities.Resources(listChanged = true, subscribe = null), - ), - ), - ) - - val client = Client( - clientInfo = Implementation(name = "test client", version = "1.0"), - options = ClientOptions( - capabilities = ClientCapabilities(), - ), - ) - - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - - val serverSessionResult = CompletableDeferred() - - listOf( - launch { - client.connect(clientTransport) - println("Client connected") - }, - launch { - serverSessionResult.complete(server.createSession(serverTransport)) - println("Server connected") - }, - ).joinAll() - - val serverSession = serverSessionResult.await() - // These should not throw - val jsonObject = buildJsonObject { - put("name", "John") - put("age", 30) - put("isStudent", false) - } - serverSession.sendLoggingMessage( - LoggingMessageNotification( - params = LoggingMessageNotification.Params( - level = LoggingLevel.info, - data = jsonObject, - ), - ), - ) - serverSession.sendResourceListChanged() - - // This should fail because the server doesn't have the tools capability - val ex = assertFailsWith { - serverSession.sendToolListChanged() - } - assertTrue(ex.message?.contains("Server does not support notifying of tool list changes") == true) - } - - @Test - fun `should handle client cancelling a request`() = runTest { - val server = Server( - Implementation(name = "test server", version = "1.0"), - ServerOptions( - capabilities = ServerCapabilities( - resources = ServerCapabilities.Resources( - listChanged = null, - subscribe = null, - ), - ), - ), - ) - - val def = CompletableDeferred() - val defTimeOut = CompletableDeferred() - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - - val client = Client( - clientInfo = Implementation(name = "test client", version = "1.0"), - options = ClientOptions(capabilities = ClientCapabilities()), - ) - - val serverSessionResult = CompletableDeferred() - - listOf( - launch { - client.connect(clientTransport) - println("Client connected") - }, - launch { - serverSessionResult.complete(server.createSession(serverTransport)) - println("Server connected") - }, - ).joinAll() - - val serverSession = serverSessionResult.await() - - serverSession.setRequestHandler(Method.Defined.ResourcesList) { _, _ -> - // Simulate delay - def.complete(Unit) - try { - delay(1000) - } catch (e: CancellationException) { - defTimeOut.complete(Unit) - throw e - } - ListResourcesResult(resources = emptyList()) - fail("Shouldn't have been called") - } - - val defCancel = CompletableDeferred() - val job = launch { - try { - client.listResources() - } catch (e: CancellationException) { - defCancel.complete(Unit) - assertEquals("Cancelled by test", e.message) - } - } - def.await() - runCatching { job.cancel("Cancelled by test") } - defCancel.await() - defTimeOut.await() - } - - @Test - fun `should handle request timeout`() = runTest { - val server = Server( - Implementation(name = "test server", version = "1.0"), - ServerOptions( - capabilities = ServerCapabilities( - resources = ServerCapabilities.Resources( - listChanged = null, - subscribe = null, - ), - ), - ), - ) - - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - val client = Client( - clientInfo = Implementation(name = "test client", version = "1.0"), - options = ClientOptions(capabilities = ClientCapabilities()), - ) - - val serverSessionResult = CompletableDeferred() - - listOf( - launch { - client.connect(clientTransport) - println("Client connected") - }, - launch { - serverSessionResult.complete(server.createSession(serverTransport)) - println("Server connected") - }, - ).joinAll() - - val serverSession = serverSessionResult.await() - serverSession.setRequestHandler(Method.Defined.ResourcesList) { _, _ -> - // Simulate a delayed response - // Wait ~100ms unless canceled - try { - withTimeout(100L) { - // Just delay here, if timeout is 0 on the client side, this won't return in time - delay(100) - } - } catch (_: Exception) { - // If aborted, just rethrow or return early - } - ListResourcesResult(resources = emptyList()) - } - - // Request with 1 msec timeout should fail immediately - val ex = assertFailsWith { - withTimeout(1) { - client.listResources() - } - } - assertTrue(ex is TimeoutCancellationException) - } - - @Test - fun `should only allow setRequestHandler for declared capabilities`() = runTest { - val client = Client( - clientInfo = Implementation( - name = "test client", - version = "1.0", - ), - options = ClientOptions( - capabilities = ClientCapabilities( - sampling = EmptyJsonObject, - ), - ), - ) - - client.setRequestHandler( - Method.Defined.SamplingCreateMessage, - ) { - _, - _, - -> - CreateMessageResult( - model = "test-model", - role = Role.assistant, - content = TextContent( - text = "Test response", - ), - ) - } - - assertFailsWith("Client does not support roots capability (required for RootsList)") { - client.setRequestHandler(Method.Defined.RootsList) { _, _ -> null } - } - } - - @Test - fun `JSONRPCRequest with ToolsList method and default params returns list of tools`() = runTest { - val serverOptions = ServerOptions( - capabilities = ServerCapabilities( - tools = ServerCapabilities.Tools(null), - ), - ) - val server = Server( - Implementation(name = "test server", version = "1.0"), - serverOptions, - ) - - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - - val client = Client( - clientInfo = Implementation(name = "test client", version = "1.0"), - options = ClientOptions( - capabilities = ClientCapabilities(sampling = EmptyJsonObject), - ), - ) - - var receivedMessage: JSONRPCMessage? = null - clientTransport.onMessage { msg -> - receivedMessage = msg - } - - val serverSessionResult = CompletableDeferred() - - listOf( - launch { - client.connect(clientTransport) - println("Client connected") - }, - launch { - serverSessionResult.complete(server.createSession(serverTransport)) - println("Server connected") - }, - ).joinAll() - - val serverSession = serverSessionResult.await() - - serverSession.setRequestHandler(Method.Defined.Initialize) { _, _ -> - InitializeResult( - protocolVersion = LATEST_PROTOCOL_VERSION, - capabilities = ServerCapabilities( - resources = ServerCapabilities.Resources(null, null), - tools = ServerCapabilities.Tools(null), - ), - serverInfo = Implementation(name = "test", version = "1.0"), - ) - } - - val serverListToolsResult = ListToolsResult( - tools = listOf( - Tool( - name = "testTool", - title = "testTool title", - description = "testTool description", - annotations = null, - inputSchema = Tool.Input(), - outputSchema = null, - ), - ), - nextCursor = null, - ) - - serverSession.setRequestHandler(Method.Defined.ToolsList) { _, _ -> - serverListToolsResult - } - - val serverCapabilities = client.serverCapabilities - assertEquals(ServerCapabilities.Tools(null), serverCapabilities?.tools) - - val request = JSONRPCRequest( - method = Method.Defined.ToolsList.value, - ) - clientTransport.send(request) - - assertIs(receivedMessage) - val receivedAsResponse = receivedMessage as JSONRPCResponse - assertEquals(request.id, receivedAsResponse.id) - assertEquals(request.jsonrpc, receivedAsResponse.jsonrpc) - assertEquals(serverListToolsResult, receivedAsResponse.result) - } - - @Test - fun `listRoots returns list of roots`() = runTest { - val client = Client( - Implementation(name = "test client", version = "1.0"), - ClientOptions( - capabilities = ClientCapabilities( - roots = ClientCapabilities.Roots(null), - ), - ), - ) - - val clientRoots = listOf( - Root(uri = "file:///test-root", name = "testRoot"), - ) - - client.addRoots(clientRoots) - - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - - val server = Server( - serverInfo = Implementation(name = "test server", version = "1.0"), - options = ServerOptions( - capabilities = ServerCapabilities(), - ), - ) - - val serverSessionResult = CompletableDeferred() - - listOf( - launch { - client.connect(clientTransport) - println("Client connected") - }, - launch { - serverSessionResult.complete(server.createSession(serverTransport)) - println("Server connected") - }, - ).joinAll() - - val serverSession = serverSessionResult.await() - - val clientCapabilities = serverSession.clientCapabilities - assertEquals(ClientCapabilities.Roots(null), clientCapabilities?.roots) - - val listRootsResult = serverSession.listRoots() - - assertEquals(listRootsResult.roots, clientRoots) - } - - @Test - fun `addRoot should throw when roots capability is not supported`() = runTest { - val client = Client( - Implementation(name = "test client", version = "1.0"), - ClientOptions( - capabilities = ClientCapabilities(), - ), - ) - - // Verify that adding a root throws an exception - val exception = assertFailsWith { - client.addRoot(uri = "file:///test-root1", name = "testRoot1") - } - assertEquals("Client does not support roots capability.", exception.message) - } - - @Test - fun `removeRoot should throw when roots capability is not supported`() = runTest { - val client = Client( - Implementation(name = "test client", version = "1.0"), - ClientOptions( - capabilities = ClientCapabilities(), - ), - ) - - // Verify that removing a root throws an exception - val exception = assertFailsWith { - client.removeRoot(uri = "file:///test-root1") - } - assertEquals("Client does not support roots capability.", exception.message) - } - - @Test - fun `removeRoot should remove a root`() = runTest { - val client = Client( - Implementation(name = "test client", version = "1.0"), - ClientOptions( - capabilities = ClientCapabilities( - roots = ClientCapabilities.Roots(null), - ), - ), - ) - - // Add some roots - client.addRoots( - listOf( - Root(uri = "file:///test-root1", name = "testRoot1"), - Root(uri = "file:///test-root2", name = "testRoot2"), - ), - ) - - // Remove a root - val result = client.removeRoot("file:///test-root1") - - // Verify the root was removed - assertTrue(result, "Root should be removed successfully") - } - - @Test - fun `removeRoots should remove multiple roots`() = runTest { - val client = Client( - Implementation(name = "test client", version = "1.0"), - ClientOptions( - capabilities = ClientCapabilities( - roots = ClientCapabilities.Roots(null), - ), - ), - ) - - // Add some roots - client.addRoots( - listOf( - Root(uri = "file:///test-root1", name = "testRoot1"), - Root(uri = "file:///test-root2", name = "testRoot2"), - ), - ) - - // Remove multiple roots - val result = client.removeRoots( - listOf("file:///test-root1", "file:///test-root2"), - ) - - // Verify the root was removed - assertEquals(2, result, "Both roots should be removed") - } - - @Test - fun `sendRootsListChanged should notify server`() = runTest { - val client = Client( - Implementation(name = "test client", version = "1.0"), - ClientOptions( - capabilities = ClientCapabilities( - roots = ClientCapabilities.Roots(listChanged = true), - ), - ), - ) - - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - - val server = Server( - serverInfo = Implementation(name = "test server", version = "1.0"), - options = ServerOptions( - capabilities = ServerCapabilities(), - ), - ) - - // Track notifications - var rootListChangedNotificationReceived = false - - val serverSessionResult = CompletableDeferred() - - listOf( - launch { - client.connect(clientTransport) - println("Client connected") - }, - launch { - serverSessionResult.complete(server.createSession(serverTransport)) - println("Server connected") - }, - ).joinAll() - - val serverSession = serverSessionResult.await() - serverSession.setNotificationHandler( - Method.Defined.NotificationsRootsListChanged, - ) { - rootListChangedNotificationReceived = true - CompletableDeferred(Unit) - } - - client.sendRootsListChanged() - - assertTrue( - rootListChangedNotificationReceived, - "Notification should be sent when sendRootsListChanged is called", - ) - } - - @Test - fun `should reject server elicitation when elicitation capability is not supported`() = runTest { - val client = Client( - Implementation(name = "test client", version = "1.0"), - ClientOptions( - capabilities = ClientCapabilities(), - ), - ) - - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - - val server = Server( - serverInfo = Implementation(name = "test server", version = "1.0"), - options = ServerOptions( - capabilities = ServerCapabilities(), - ), - ) - - val serverSessionResult = CompletableDeferred() - - listOf( - launch { - client.connect(clientTransport) - println("Client connected") - }, - launch { - serverSessionResult.complete(server.createSession(serverTransport)) - println("Server connected") - }, - ).joinAll() - - val serverSession = serverSessionResult.await() - - // Verify that creating an elicitation throws an exception - val exception = assertFailsWith { - serverSession.createElicitation( - message = "Please provide your GitHub username", - requestedSchema = CreateElicitationRequest.RequestedSchema( - properties = buildJsonObject { - putJsonObject("name") { - put("type", "string") - } - }, - required = listOf("name"), - ), - ) - } - assertEquals( - "Client does not support elicitation (required for elicitation/create)", - exception.message, - ) - } - - @Test - fun `should handle logging setLevel request`() = runTest { - val server = Server( - Implementation(name = "test server", version = "1.0"), - ServerOptions( - capabilities = ServerCapabilities( - logging = EmptyJsonObject, - ), - ), - ) - - val client = Client( - clientInfo = Implementation(name = "test client", version = "1.0"), - options = ClientOptions( - capabilities = ClientCapabilities(), - ), - ) - - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - - val receivedMessages = mutableListOf() - client.setNotificationHandler( - Method.Defined.NotificationsMessage, - ) { notification -> - receivedMessages.add(notification) - CompletableDeferred(Unit) - } - - val serverSessionResult = CompletableDeferred() - - listOf( - launch { - client.connect(clientTransport) - println("Client connected") - }, - launch { - serverSessionResult.complete(server.connect(serverTransport)) - println("Server connected") - }, - ).joinAll() - - val serverSession = serverSessionResult.await() - - // Set logging level to warning - val minLevel = LoggingLevel.warning - val result = client.setLoggingLevel(minLevel) - assertEquals(null, result.meta) - - // Send messages of different levels - val testMessages = listOf( - LoggingLevel.debug to "Debug - should be filtered", - LoggingLevel.info to "Info - should be filtered", - LoggingLevel.warning to "Warning - should pass", - LoggingLevel.error to "Error - should pass", - ) - - testMessages.forEach { (level, message) -> - serverSession.sendLoggingMessage( - LoggingMessageNotification( - params = LoggingMessageNotification.Params( - level = level, - data = buildJsonObject { put("message", message) }, - ), - ), - ) - } - - delay(100) - - // Only warning and error should be received - assertEquals(2, receivedMessages.size, "Should receive only 2 messages (warning and error)") - - // Verify all received messages have severity >= minLevel - receivedMessages.forEach { message -> - val messageSeverity = message.params.level.ordinal - assertTrue( - messageSeverity >= minLevel.ordinal, - "Received message with level ${message.params.level} should have severity >= $minLevel", - ) - } - } - - @Test - fun `should handle server elicitation`() = runTest { - val client = Client( - Implementation(name = "test client", version = "1.0"), - ClientOptions( - capabilities = ClientCapabilities( - elicitation = EmptyJsonObject, - ), - ), - ) - - val elicitationMessage = "Please provide your GitHub username" - val requestedSchema = CreateElicitationRequest.RequestedSchema( - properties = buildJsonObject { - putJsonObject("name") { - put("type", "string") - } - }, - required = listOf("name"), - ) - - val elicitationResultAction = CreateElicitationResult.Action.accept - val elicitationResultContent = buildJsonObject { - put("name", "octocat") - } - - client.setElicitationHandler { request -> - assertEquals(elicitationMessage, request.params.message) - assertEquals(requestedSchema, request.params.requestedSchema) - - CreateElicitationResult( - action = elicitationResultAction, - content = elicitationResultContent, - ) - } - - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - - val server = Server( - serverInfo = Implementation(name = "test server", version = "1.0"), - options = ServerOptions( - capabilities = ServerCapabilities(), - ), - ) - - val serverSessionResult = CompletableDeferred() - - listOf( - launch { - client.connect(clientTransport) - println("Client connected") - }, - launch { - serverSessionResult.complete(server.createSession(serverTransport)) - println("Server connected") - }, - ).joinAll() - - val serverSession = serverSessionResult.await() - - val result = serverSession.createElicitation( - message = elicitationMessage, - requestedSchema = requestedSchema, - ) - - assertEquals(elicitationResultAction, result.action) - assertEquals(elicitationResultContent, result.content) - } -} diff --git a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaSseTransportTest.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaSseTransportTest.kt deleted file mode 100644 index 3c2d75a..0000000 --- a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaSseTransportTest.kt +++ /dev/null @@ -1,123 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.client - -import io.ktor.client.HttpClient -import io.ktor.server.application.install -import io.ktor.server.cio.CIO -import io.ktor.server.engine.EmbeddedServer -import io.ktor.server.engine.embeddedServer -import io.ktor.server.routing.routing -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities -import io.modelcontextprotocol.kotlin.sdk.server.Server -import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions -import io.modelcontextprotocol.kotlin.sdk.server.mcp -import io.modelcontextprotocol.kotlin.sdk.shared.BaseTransportTest -import kotlinx.coroutines.test.runTest -import kotlin.test.BeforeTest -import kotlin.test.Ignore -import kotlin.test.Test -import io.ktor.client.plugins.sse.SSE as ClientSSE -import io.ktor.server.sse.SSE as ServerSSE - -class OldSchemaSseTransportTest : BaseTransportTest() { - - private suspend fun EmbeddedServer<*, *>.actualPort() = engine.resolvedConnectors().first().port - - private lateinit var mcpServer: Server - - @BeforeTest - fun setUp() { - mcpServer = Server( - serverInfo = Implementation( - name = "test-server", - version = "1.0", - ), - options = ServerOptions(ServerCapabilities()), - ) - } - - @Test - @Ignore // Ignored because it doesn’t work with wasm/js in Ktor 3.2.3 - fun `should start then close cleanly`() = runTest { - val server = embeddedServer(CIO, port = 0) { - install(ServerSSE) - routing { - mcp { mcpServer } - } - }.startSuspend(wait = false) - - val actualPort = server.actualPort() - - val transport = HttpClient { - install(ClientSSE) - }.mcpSseTransport { - url { - host = "localhost" - this.port = actualPort - } - } - - try { - testTransportRead(transport) - } finally { - server.stopSuspend() - } - } - - @Ignore - @Test - fun `should read messages`() = runTest { - val server = embeddedServer(CIO, port = 0) { - install(ServerSSE) - routing { - mcp { mcpServer } - } - }.startSuspend(wait = false) - - val actualPort = server.actualPort() - - val transport = HttpClient { - install(ClientSSE) - }.mcpSseTransport { - url { - host = "localhost" - this.port = actualPort - } - } - - try { - testTransportRead(transport) - } finally { - server.stopSuspend() - } - } - - @Ignore - @Test - fun `test sse path not root path`() = runTest { - val server = embeddedServer(CIO, port = 0) { - install(ServerSSE) - routing { - mcp { mcpServer } - } - }.startSuspend(wait = false) - - val actualPort = server.actualPort() - - val transport = HttpClient { - install(ClientSSE) - }.mcpSseTransport { - url { - host = "localhost" - this.port = actualPort - pathSegments = listOf("sse") - } - } - - try { - testTransportRead(transport) - } finally { - server.stopSuspend() - } - } -} diff --git a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/OldSchemaInMemoryTransportTest.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/OldSchemaInMemoryTransportTest.kt deleted file mode 100644 index 5f31bc1..0000000 --- a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/OldSchemaInMemoryTransportTest.kt +++ /dev/null @@ -1,110 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration - -import io.modelcontextprotocol.kotlin.sdk.InitializedNotification -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage -import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport -import io.modelcontextprotocol.kotlin.sdk.toJSON -import kotlinx.coroutines.test.runTest -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertNotNull -import kotlin.test.assertTrue - -class OldSchemaInMemoryTransportTest { - private lateinit var clientTransport: InMemoryTransport - private lateinit var serverTransport: InMemoryTransport - - @BeforeTest - fun setUp() { - val (client, server) = InMemoryTransport.createLinkedPair() - clientTransport = client - serverTransport = server - } - - @Test - fun `should create linked pair`() { - assertNotNull(clientTransport) - assertNotNull(serverTransport) - } - - @Test - fun `should start without error`() = runTest { - clientTransport.start() - serverTransport.start() - // If no exception is thrown, the test passes - } - - @Test - fun `should send message from client to server`() = runTest { - val message = InitializedNotification() - - var receivedMessage: JSONRPCMessage? = null - serverTransport.onMessage { msg -> - receivedMessage = msg - } - - val rpcNotification = message.toJSON() - clientTransport.send(rpcNotification) - assertEquals(rpcNotification, receivedMessage) - } - - @Test - fun `should send message from server to client`() = runTest { - val message = InitializedNotification() - .toJSON() - - var receivedMessage: JSONRPCMessage? = null - clientTransport.onMessage { msg -> - receivedMessage = msg - } - - serverTransport.send(message) - assertEquals(message, receivedMessage) - } - - @Test - fun `should handle close`() = runTest { - var clientClosed = false - var serverClosed = false - - clientTransport.onClose { - clientClosed = true - } - - serverTransport.onClose { - serverClosed = true - } - - clientTransport.close() - assertTrue(clientClosed) - assertTrue(serverClosed) - } - - @Test - fun `should throw error when sending after close`() = runTest { - clientTransport.close() - - assertFailsWith { - clientTransport.send( - InitializedNotification().toJSON(), - ) - } - } - - @Test - fun `should queue messages sent before start`() = runTest { - val message = InitializedNotification() - .toJSON() - - var receivedMessage: JSONRPCMessage? = null - serverTransport.onMessage { msg -> - receivedMessage = msg - } - - clientTransport.send(message) - serverTransport.start() - assertEquals(message, receivedMessage) - } -} diff --git a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaBaseTransportTest.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaBaseTransportTest.kt deleted file mode 100644 index d2d0402..0000000 --- a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaBaseTransportTest.kt +++ /dev/null @@ -1,63 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.shared - -import io.modelcontextprotocol.kotlin.sdk.InitializedNotification -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage -import io.modelcontextprotocol.kotlin.sdk.PingRequest -import io.modelcontextprotocol.kotlin.sdk.toJSON -import kotlinx.coroutines.CompletableDeferred -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue -import kotlin.test.fail - -abstract class OldSchemaBaseTransportTest { - - protected suspend fun testTransportOpenClose(transport: Transport) { - transport.onError { error -> - fail("Unexpected error: $error") - } - - var didClose = false - transport.onClose { didClose = true } - - transport.start() - assertFalse(didClose, "Transport should not be closed immediately after start") - - transport.close() - assertTrue(didClose, "Transport should be closed after close() call") - } - - protected suspend fun testTransportRead(transport: Transport) { - transport.onError { error -> - error.printStackTrace() - fail("Unexpected error: $error") - } - - val messages = listOf( - PingRequest().toJSON(), - InitializedNotification().toJSON(), - ) - - val readMessages = mutableListOf() - val finished = CompletableDeferred() - - transport.onMessage { message -> - readMessages.add(message) - if (message == messages.last()) { - finished.complete(Unit) - } - } - - transport.start() - - for (message in messages) { - transport.send(message) - } - - finished.await() - - assertEquals(messages, readMessages, "Assert messages received") - - transport.close() - } -} diff --git a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaInMemoryTransport.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaInMemoryTransport.kt deleted file mode 100644 index 9a0d4d3..0000000 --- a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaInMemoryTransport.kt +++ /dev/null @@ -1,47 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.shared - -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage - -/** - * In-memory transport for creating clients and servers that talk to each other within the same process. - */ -class OldSchemaInMemoryTransport : AbstractTransport() { - private var otherTransport: OldSchemaInMemoryTransport? = null - private val messageQueue: MutableList = mutableListOf() - - /** - * Creates a pair of linked in-memory transports that can communicate with each other. - * One should be passed to a Client and one to a Server. - */ - companion object { - fun createLinkedPair(): Pair { - val clientTransport = OldSchemaInMemoryTransport() - val serverTransport = OldSchemaInMemoryTransport() - clientTransport.otherTransport = serverTransport - serverTransport.otherTransport = clientTransport - return Pair(clientTransport, serverTransport) - } - } - - override suspend fun start() { - // Process any messages that were queued before start was called - while (messageQueue.isNotEmpty()) { - messageQueue.removeFirstOrNull()?.let { message -> - _onMessage.invoke(message) // todo? - } - } - } - - override suspend fun close() { - val other = otherTransport - otherTransport = null - other?.close() - _onClose.invoke() - } - - override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { - val other = otherTransport ?: throw IllegalStateException("Not connected") - - other._onMessage.invoke(message) - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractPromptIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractPromptIntegrationTest.kt deleted file mode 100644 index 1c7c130..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractPromptIntegrationTest.kt +++ /dev/null @@ -1,698 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.kotlin - -import io.modelcontextprotocol.kotlin.sdk.GetPromptRequest -import io.modelcontextprotocol.kotlin.sdk.GetPromptResult -import io.modelcontextprotocol.kotlin.sdk.PromptArgument -import io.modelcontextprotocol.kotlin.sdk.PromptMessage -import io.modelcontextprotocol.kotlin.sdk.Role -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities -import io.modelcontextprotocol.kotlin.sdk.TextContent -import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequestParams -import io.modelcontextprotocol.kotlin.sdk.types.McpException -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertTrue - -abstract class OldSchemaAbstractPromptIntegrationTest : OldSchemaKotlinTestBase() { - - private val basicPromptName = "basic-prompt" - private val basicPromptDescription = "A basic prompt for testing" - - private val complexPromptName = "multimodal-prompt" - private val complexPromptDescription = "A prompt with multiple content types" - private val conversationPromptName = "conversation" - private val conversationPromptDescription = "A prompt with multiple messages and roles" - private val strictPromptName = "strict-prompt" - private val strictPromptDescription = "A prompt with required arguments" - - private val largePromptName = "large-prompt" - private val largePromptDescription = "A very large prompt for testing" - private val largePromptContent = "X".repeat(100_000) // 100KB of data - - private val specialCharsPromptName = "special-chars-prompt" - private val specialCharsPromptDescription = "A prompt with special characters" - private val specialCharsContent = "!@#$%^&*()_+{}|:\"<>?~`-=[]\\;',./\n\t" - - override fun configureServerCapabilities(): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities = - ServerCapabilities( - prompts = ServerCapabilities.Prompts( - listChanged = true, - ), - ) - - override fun configureServer() { - // basic prompt with a name parameter - server.addPrompt( - name = basicPromptName, - description = basicPromptDescription, - arguments = listOf( - PromptArgument( - name = "name", - description = "The name to greet", - required = true, - ), - ), - ) { request -> - val name = request.params.arguments?.get("name") ?: "World" - - GetPromptResult( - description = basicPromptDescription, - messages = listOf( - PromptMessage( - role = Role.user, - content = TextContent(text = "Hello, $name!"), - ), - PromptMessage( - role = Role.assistant, - content = TextContent(text = "Greetings, $name! How can I assist you today?"), - ), - ), - ) - } - - // special chars prompt - server.addPrompt( - name = specialCharsPromptName, - description = specialCharsPromptDescription, - arguments = listOf( - PromptArgument( - name = "special", - description = "Special characters to include", - required = false, - ), - ), - ) { request -> - val special = request.params.arguments?.get("special") ?: specialCharsContent - - GetPromptResult( - description = specialCharsPromptDescription, - messages = listOf( - PromptMessage( - role = Role.user, - content = TextContent(text = "Special characters: $special"), - ), - PromptMessage( - role = Role.assistant, - content = TextContent(text = "Received special characters: $special"), - ), - ), - ) - } - - // very large prompt - server.addPrompt( - name = largePromptName, - description = largePromptDescription, - arguments = listOf( - PromptArgument( - name = "size", - description = "Size multiplier", - required = false, - ), - ), - ) { request -> - val size = request.params.arguments?.get("size")?.toIntOrNull() ?: 1 - val content = largePromptContent.repeat(size) - - GetPromptResult( - description = largePromptDescription, - messages = listOf( - PromptMessage( - role = Role.user, - content = TextContent(text = "Generate a large response"), - ), - PromptMessage( - role = Role.assistant, - content = TextContent(text = content), - ), - ), - ) - } - - // complext prompt - server.addPrompt( - name = complexPromptName, - description = complexPromptDescription, - arguments = listOf( - PromptArgument(name = "arg1", description = "Argument 1", required = true), - PromptArgument(name = "arg2", description = "Argument 2", required = true), - PromptArgument(name = "arg3", description = "Argument 3", required = true), - PromptArgument(name = "arg4", description = "Argument 4", required = false), - PromptArgument(name = "arg5", description = "Argument 5", required = false), - PromptArgument(name = "arg6", description = "Argument 6", required = false), - PromptArgument(name = "arg7", description = "Argument 7", required = false), - PromptArgument(name = "arg8", description = "Argument 8", required = false), - PromptArgument(name = "arg9", description = "Argument 9", required = false), - PromptArgument(name = "arg10", description = "Argument 10", required = false), - ), - ) { request -> - // validate required arguments - val requiredArgs = listOf("arg1", "arg2", "arg3") - for (argName in requiredArgs) { - if (request.params.arguments?.get(argName) == null) { - throw IllegalArgumentException("Missing required argument: $argName") - } - } - - val args = mutableMapOf() - for (i in 1..10) { - val argName = "arg$i" - val argValue = request.params.arguments?.get(argName) - if (argValue != null) { - args[argName] = argValue - } - } - - GetPromptResult( - description = complexPromptDescription, - messages = listOf( - PromptMessage( - role = Role.user, - content = TextContent( - text = "Arguments: ${ - args.entries.joinToString { - "${it.key}=${it.value}" - } - }", - ), - ), - PromptMessage( - role = Role.assistant, - content = TextContent(text = "Received ${args.size} arguments"), - ), - ), - ) - } - - // prompt with multiple messages and roles - server.addPrompt( - name = conversationPromptName, - description = conversationPromptDescription, - arguments = listOf( - PromptArgument( - name = "topic", - description = "The topic of the conversation", - required = false, - ), - ), - ) { request -> - val topic = request.params.arguments?.get("topic") ?: "weather" - - GetPromptResult( - description = conversationPromptDescription, - messages = listOf( - PromptMessage( - role = Role.user, - content = TextContent(text = "Let's talk about the $topic."), - ), - PromptMessage( - role = Role.assistant, - content = TextContent( - text = "Sure, I'd love to discuss the $topic. What would you like to know?", - ), - ), - PromptMessage( - role = Role.user, - content = TextContent(text = "What's your opinion on the $topic?"), - ), - PromptMessage( - role = Role.assistant, - content = TextContent( - text = "As an AI, I don't have personal opinions," + - " but I can provide information about $topic.", - ), - ), - PromptMessage( - role = Role.user, - content = TextContent(text = "That's helpful, thank you!"), - ), - PromptMessage( - role = Role.assistant, - content = TextContent( - text = "You're welcome! Let me know if you have more questions about $topic.", - ), - ), - ), - ) - } - - // prompt with strict required arguments - server.addPrompt( - name = strictPromptName, - description = strictPromptDescription, - arguments = listOf( - PromptArgument( - name = "requiredArg1", - description = "First required argument", - required = true, - ), - PromptArgument( - name = "requiredArg2", - description = "Second required argument", - required = true, - ), - PromptArgument( - name = "optionalArg", - description = "Optional argument", - required = false, - ), - ), - ) { request -> - val args = request.params.arguments ?: emptyMap() - val arg1 = args["requiredArg1"] ?: throw IllegalArgumentException( - "Missing required argument: requiredArg1", - ) - val arg2 = args["requiredArg2"] ?: throw IllegalArgumentException( - "Missing required argument: requiredArg2", - ) - val optArg = args["optionalArg"] ?: "default" - - GetPromptResult( - description = strictPromptDescription, - messages = listOf( - PromptMessage( - role = Role.user, - content = TextContent(text = "Required arguments: $arg1, $arg2. Optional: $optArg"), - ), - PromptMessage( - role = Role.assistant, - content = TextContent(text = "I received your arguments: $arg1, $arg2, and $optArg"), - ), - ), - ) - } - } - - @Test - fun testListPrompts() = runBlocking(Dispatchers.IO) { - val result = client.listPrompts() - - assertNotNull(result, "List prompts result should not be null") - assertTrue(result.prompts.isNotEmpty(), "Prompts list should not be empty") - - val testPrompt = result.prompts.find { it.name == basicPromptName } - assertNotNull(testPrompt, "Test prompt should be in the list") - assertEquals( - basicPromptDescription, - testPrompt.description, - "Prompt description should match", - ) - - val arguments = testPrompt.arguments ?: error("Prompt arguments should not be null") - assertTrue(arguments.isNotEmpty(), "Prompt arguments should not be empty") - - val nameArg = arguments.find { it.name == "name" } - assertNotNull(nameArg, "Name argument should be in the list") - assertEquals( - "The name to greet", - nameArg.description, - "Argument description should match", - ) - assertEquals(true, nameArg.required, "Argument required flag should match") - } - - @Test - fun testGetPrompt() = runBlocking(Dispatchers.IO) { - val testName = "Alice" - val result = client.getPrompt( - GetPromptRequest( - GetPromptRequestParams( - name = basicPromptName, - arguments = mapOf("name" to testName), - ), - ), - ) - - assertNotNull(result, "Get prompt result should not be null") - assertEquals( - basicPromptDescription, - result.description, - "Prompt description should match", - ) - - assertTrue(result.messages.isNotEmpty(), "Prompt messages should not be empty") - assertEquals(2, result.messages.size, "Prompt should have 2 messages") - - val userMessage = result.messages.find { it.role == Role.user } - assertNotNull(userMessage, "User message should be in the list") - val userContent = userMessage.content as? TextContent - assertNotNull(userContent, "User message content should be TextContent") - assertNotNull(userContent.text, "User message text should not be null") - assertEquals( - "Hello, $testName!", - userContent.text, - "User message content should match", - ) - - val assistantMessage = result.messages.find { it.role == Role.assistant } - assertNotNull(assistantMessage, "Assistant message should be in the list") - val assistantContent = assistantMessage.content as? TextContent - assertNotNull(assistantContent, "Assistant message content should be TextContent") - assertNotNull(assistantContent.text, "Assistant message text should not be null") - assertEquals( - "Greetings, $testName! How can I assist you today?", - assistantContent.text, - "Assistant message content should match", - ) - } - - @Test - fun testMissingRequiredArguments() = runBlocking(Dispatchers.IO) { - val promptsList = client.listPrompts() - assertNotNull(promptsList, "Prompts list should not be null") - val strictPrompt = promptsList.prompts.find { it.name == strictPromptName } - assertNotNull(strictPrompt, "Strict prompt should be in the list") - - val argsDef = strictPrompt.arguments ?: error("Prompt arguments should not be null") - val requiredArgs = argsDef.filter { it.required == true } - assertEquals( - 2, - requiredArgs.size, - "Strict prompt should have 2 required arguments", - ) - - // test missing required arg - val exception = assertThrows { - runBlocking { - client.getPrompt( - GetPromptRequest( - GetPromptRequestParams( - name = strictPromptName, - arguments = mapOf("requiredArg1" to "value1"), - ), - ), - ) - } - } - - assertEquals( - true, - exception.message?.contains("requiredArg2"), - "Exception should mention the missing argument", - ) - - // test with no args - val exception2 = assertThrows { - runBlocking { - client.getPrompt( - GetPromptRequest( - GetPromptRequestParams( - name = strictPromptName, - arguments = emptyMap(), - ), - ), - ) - } - } - - assertEquals( - exception2.message?.contains("requiredArg"), - true, - "Exception should mention a missing required argument", - ) - - // test with all required args - val result = client.getPrompt( - GetPromptRequest( - GetPromptRequestParams( - name = strictPromptName, - arguments = mapOf( - "requiredArg1" to "value1", - "requiredArg2" to "value2", - ), - ), - ), - ) - - assertNotNull(result, "Get prompt result should not be null") - assertEquals(2, result.messages.size, "Prompt should have 2 messages") - - val userMessage = result.messages.find { it.role == Role.user } - assertNotNull(userMessage, "User message should be in the list") - val userContent = userMessage.content as? TextContent - assertNotNull(userContent, "User message content should be TextContent") - val userText = requireNotNull(userContent.text) - assertTrue(userText.contains("value1"), "Message should contain first argument") - assertTrue(userText.contains("value2"), "Message should contain second argument") - } - - @Test - fun testMultipleMessagesAndRoles() = runBlocking(Dispatchers.IO) { - val topic = "climate change" - val result = client.getPrompt( - GetPromptRequest( - GetPromptRequestParams( - name = conversationPromptName, - arguments = mapOf("topic" to topic), - ), - ), - ) - - assertNotNull(result, "Get prompt result should not be null") - assertEquals( - conversationPromptDescription, - result.description, - "Prompt description should match", - ) - - assertTrue(result.messages.isNotEmpty(), "Prompt messages should not be empty") - assertEquals(6, result.messages.size, "Prompt should have 6 messages") - - val userMessages = result.messages.filter { it.role == Role.user } - val assistantMessages = result.messages.filter { it.role == Role.assistant } - - assertEquals(3, userMessages.size, "Should have 3 user messages") - assertEquals(3, assistantMessages.size, "Should have 3 assistant messages") - - for (i in 0 until result.messages.size) { - val expectedRole = if (i % 2 == 0) Role.user else Role.assistant - assertEquals( - expectedRole, - result.messages[i].role, - "Message $i should have role $expectedRole", - ) - } - - for (message in result.messages) { - val content = message.content as? TextContent - assertNotNull(content, "Message content should be TextContent") - val text = requireNotNull(content.text) - - // Either the message contains the topic or it's a generic conversation message - val containsTopic = text.contains(topic) - val isGenericMessage = text.contains("thank you") || text.contains("welcome") - - assertTrue( - containsTopic || isGenericMessage, - "Message should either contain the topic or be a generic conversation message", - ) - } - } - - @Test - fun testBasicPrompt() = runBlocking(Dispatchers.IO) { - val testName = "Alice" - val result = client.getPrompt( - GetPromptRequest( - GetPromptRequestParams( - name = basicPromptName, - arguments = mapOf("name" to testName), - ), - ), - ) - - assertNotNull(result, "Get prompt result should not be null") - assertEquals(basicPromptDescription, result.description, "Prompt description should match") - - assertEquals(2, result.messages.size, "Prompt should have 2 messages") - - val userMessage = result.messages.find { it.role == Role.user } - assertNotNull(userMessage, "User message should be in the list") - val userContent = userMessage.content as? TextContent - assertNotNull(userContent, "User message content should be TextContent") - assertEquals("Hello, $testName!", userContent.text, "User message content should match") - - val assistantMessage = result.messages.find { it.role == Role.assistant } - assertNotNull(assistantMessage, "Assistant message should be in the list") - val assistantContent = assistantMessage.content as? TextContent - assertNotNull(assistantContent, "Assistant message content should be TextContent") - assertEquals( - "Greetings, $testName! How can I assist you today?", - assistantContent.text, - "Assistant message content should match", - ) - } - - @Test - fun testComplexPromptWithManyArguments() = runBlocking(Dispatchers.IO) { - val arguments = (1..10).associate { i -> "arg$i" to "value$i" } - - val result = client.getPrompt( - GetPromptRequest( - GetPromptRequestParams( - name = complexPromptName, - arguments = arguments, - ), - ), - ) - - assertNotNull(result, "Get prompt result should not be null") - assertEquals(complexPromptDescription, result.description, "Prompt description should match") - - assertEquals(2, result.messages.size, "Prompt should have 2 messages") - - val userMessage = result.messages.find { it.role == Role.user } - assertNotNull(userMessage, "User message should be in the list") - val userContent = userMessage.content as? TextContent - assertNotNull(userContent, "User message content should be TextContent") - - // verify all arguments - val text = userContent.text ?: "" - for (i in 1..10) { - assertTrue(text.contains("arg$i=value$i"), "Message should contain arg$i=value$i") - } - - val assistantMessage = result.messages.find { it.role == Role.assistant } - assertNotNull(assistantMessage, "Assistant message should be in the list") - val assistantContent = assistantMessage.content as? TextContent - assertNotNull(assistantContent, "Assistant message content should be TextContent") - assertEquals( - "Received 10 arguments", - assistantContent.text, - "Assistant message should indicate 10 arguments", - ) - } - - @Test - fun testLargePrompt() = runBlocking(Dispatchers.IO) { - val result = client.getPrompt( - GetPromptRequest( - GetPromptRequestParams( - name = largePromptName, - arguments = mapOf("size" to "1"), - ), - ), - ) - - assertNotNull(result, "Get prompt result should not be null") - assertEquals(largePromptDescription, result.description, "Prompt description should match") - - assertEquals(2, result.messages.size, "Prompt should have 2 messages") - - val assistantMessage = result.messages.find { it.role == Role.assistant } - assertNotNull(assistantMessage, "Assistant message should be in the list") - val assistantContent = assistantMessage.content as? TextContent - assertNotNull(assistantContent, "Assistant message content should be TextContent") - val text = assistantContent.text ?: "" - assertEquals(100_000, text.length, "Assistant message should be 100KB in size") - } - - @Test - fun testSpecialCharacters() = runBlocking(Dispatchers.IO) { - val result = client.getPrompt( - GetPromptRequest( - GetPromptRequestParams( - name = specialCharsPromptName, - arguments = mapOf("special" to specialCharsContent), - ), - ), - ) - - assertNotNull(result, "Get prompt result should not be null") - assertEquals(specialCharsPromptDescription, result.description, "Prompt description should match") - - assertEquals(2, result.messages.size, "Prompt should have 2 messages") - - val userMessage = result.messages.find { it.role == Role.user } - assertNotNull(userMessage, "User message should be in the list") - val userContent = userMessage.content as? TextContent - assertNotNull(userContent, "User message content should be TextContent") - val userText = userContent.text ?: "" - assertTrue(userText.contains(specialCharsContent), "User message should contain special characters") - - val assistantMessage = result.messages.find { it.role == Role.assistant } - assertNotNull(assistantMessage, "Assistant message should be in the list") - val assistantContent = assistantMessage.content as? TextContent - assertNotNull(assistantContent, "Assistant message content should be TextContent") - val assistantText = assistantContent.text ?: "" - assertTrue( - assistantText.contains(specialCharsContent), - "Assistant message should contain special characters", - ) - } - - @Test - fun testConcurrentPromptRequests() = runTest { - val concurrentCount = 10 - val results = mutableListOf() - - runBlocking { - repeat(concurrentCount) { index -> - launch { - val promptName = when (index % 4) { - 0 -> basicPromptName - 1 -> complexPromptName - 2 -> largePromptName - else -> specialCharsPromptName - } - - val arguments = when (promptName) { - basicPromptName -> mapOf("name" to "User$index") - complexPromptName -> mapOf("arg1" to "v1", "arg2" to "v2", "arg3" to "v3") - largePromptName -> mapOf("size" to "1") - else -> mapOf("special" to "!@#$%^&*()") - } - - val result = client.getPrompt( - GetPromptRequest( - GetPromptRequestParams( - name = promptName, - arguments = arguments, - ), - ), - ) - - synchronized(results) { - results.add(result) - } - } - } - } - - assertEquals(concurrentCount, results.size, "All concurrent operations should complete") - - results.forEach { result -> - assertNotNull(result, "Result should not be null") - assertTrue(result.messages.isNotEmpty(), "Result messages should not be empty") - } - } - - @Test - fun testNonExistentPrompt() = runTest { - val nonExistentPromptName = "non-existent-prompt" - - val exception = assertThrows { - runBlocking { - client.getPrompt( - GetPromptRequest( - GetPromptRequestParams( - name = nonExistentPromptName, - arguments = mapOf("name" to "Test"), - ), - ), - ) - } - } - - val msg = exception.message ?: "" - val expectedMessage = "MCP error -32603: Prompt not found: non-existent-prompt" - - assertEquals(expectedMessage, msg, "Unexpected error message for non-existent prompt") - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractResourceIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractResourceIntegrationTest.kt deleted file mode 100644 index 7cc5f10..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractResourceIntegrationTest.kt +++ /dev/null @@ -1,319 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.kotlin - -import io.modelcontextprotocol.kotlin.sdk.BlobResourceContents -import io.modelcontextprotocol.kotlin.sdk.ReadResourceRequest -import io.modelcontextprotocol.kotlin.sdk.ReadResourceResult -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities -import io.modelcontextprotocol.kotlin.sdk.SubscribeRequest -import io.modelcontextprotocol.kotlin.sdk.TextResourceContents -import io.modelcontextprotocol.kotlin.sdk.UnsubscribeRequest -import io.modelcontextprotocol.kotlin.sdk.types.McpException -import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequestParams -import io.modelcontextprotocol.kotlin.sdk.types.SubscribeRequestParams -import io.modelcontextprotocol.kotlin.sdk.types.UnsubscribeRequestParams -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertNull -import org.junit.jupiter.api.assertThrows -import java.util.concurrent.atomic.AtomicBoolean -import kotlin.test.Ignore -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertTrue - -abstract class OldSchemaAbstractResourceIntegrationTest : OldSchemaKotlinTestBase() { - - private val testResourceUri = "test://example.txt" - private val testResourceName = "Test Resource" - private val testResourceDescription = "A test resource for integration testing" - private val testResourceContent = "This is the content of the test resource." - - private val binaryResourceUri = "test://image.png" - private val binaryResourceName = "Binary Resource" - private val binaryResourceDescription = "A binary resource for testing" - private val binaryResourceContent = - "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==" - - private val largeResourceUri = "test://large.txt" - private val largeResourceName = "Large Resource" - private val largeResourceDescription = "A large text resource for testing" - private val largeResourceContent = "X".repeat(100_000) // 100KB of data - - private val dynamicResourceUri = "test://dynamic.txt" - private val dynamicResourceName = "Dynamic Resource" - private val dynamicResourceContent = AtomicBoolean(false) - - override fun configureServerCapabilities(): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities = - ServerCapabilities( - resources = ServerCapabilities.Resources( - subscribe = true, - listChanged = true, - ), - ) - - override fun configureServer() { - server.addResource( - uri = testResourceUri, - name = testResourceName, - description = testResourceDescription, - mimeType = "text/plain", - ) { request -> - ReadResourceResult( - contents = listOf( - TextResourceContents( - text = testResourceContent, - uri = request.params.uri, - mimeType = "text/plain", - ), - ), - ) - } - - server.addResource( - uri = testResourceUri, - name = testResourceName, - description = testResourceDescription, - mimeType = "text/plain", - ) { request -> - ReadResourceResult( - contents = listOf( - TextResourceContents( - text = testResourceContent, - uri = request.params.uri, - mimeType = "text/plain", - ), - ), - ) - } - - server.addResource( - uri = binaryResourceUri, - name = binaryResourceName, - description = binaryResourceDescription, - mimeType = "image/png", - ) { request -> - ReadResourceResult( - contents = listOf( - BlobResourceContents( - blob = binaryResourceContent, - uri = request.params.uri, - mimeType = "image/png", - ), - ), - ) - } - - server.addResource( - uri = largeResourceUri, - name = largeResourceName, - description = largeResourceDescription, - mimeType = "text/plain", - ) { request -> - ReadResourceResult( - contents = listOf( - TextResourceContents( - text = largeResourceContent, - uri = request.params.uri, - mimeType = "text/plain", - ), - ), - ) - } - - server.addResource( - uri = dynamicResourceUri, - name = dynamicResourceName, - description = "A resource that can be updated", - mimeType = "text/plain", - ) { request -> - ReadResourceResult( - contents = listOf( - TextResourceContents( - text = if (dynamicResourceContent.get()) "Updated content" else "Original content", - uri = request.params.uri, - mimeType = "text/plain", - ), - ), - ) - } - } - - @Test - fun testListResources() = runBlocking(Dispatchers.IO) { - val result = client.listResources() - - assertNotNull(result, "List resources result should not be null") - assertNull(actual = result.meta, "List resources meta should be null") - assertTrue(result.resources.isNotEmpty(), "Resources list should not be empty") - - val testResource = result.resources.find { it.uri == testResourceUri } - assertNotNull(testResource, "Test resource should be in the list") - assertEquals(testResourceName, testResource.name, "Resource name should match") - assertEquals(testResourceDescription, testResource.description, "Resource description should match") - } - - @Test - fun testReadResource() = runBlocking(Dispatchers.IO) { - val result = client.readResource(ReadResourceRequest(ReadResourceRequestParams(uri = testResourceUri))) - - assertNotNull(result, "Read resource result should not be null") - assertNull(actual = result.meta, "Read resource result meta should be null") - assertTrue(result.contents.isNotEmpty(), "Resource contents should not be empty") - - val content = result.contents.firstOrNull() as? TextResourceContents - assertNotNull(content, "Resource content should be TextResourceContents") - assertEquals(testResourceContent, content.text, "Resource content should match") - } - - @Ignore("Blocked by https://github.com/modelcontextprotocol/kotlin-sdk/issues/249") - @Test - fun testSubscribeAndUnsubscribe() { - runBlocking(Dispatchers.IO) { - val subscribeResult = - client.subscribeResource(SubscribeRequest(SubscribeRequestParams(uri = testResourceUri))) - assertNotNull(subscribeResult, "Subscribe result should not be null") - - val unsubscribeResult = - client.unsubscribeResource(UnsubscribeRequest(UnsubscribeRequestParams(uri = testResourceUri))) - assertNotNull(unsubscribeResult, "Unsubscribe result should not be null") - } - } - - @Test - fun testBinaryResource() = runBlocking(Dispatchers.IO) { - val result = client.readResource(ReadResourceRequest(ReadResourceRequestParams(uri = binaryResourceUri))) - - assertNotNull(result, "Read resource result should not be null") - assertTrue(result.contents.isNotEmpty(), "Resource contents should not be empty") - - val content = result.contents.firstOrNull() as? BlobResourceContents - assertNotNull(content, "Resource content should be BlobResourceContents") - assertEquals(binaryResourceContent, content.blob, "Binary resource content should match") - assertEquals("image/png", content.mimeType, "MIME type should match") - } - - @Test - fun testLargeResource() = runBlocking(Dispatchers.IO) { - val result = client.readResource(ReadResourceRequest(ReadResourceRequestParams(uri = largeResourceUri))) - - assertNotNull(result, "Read resource result should not be null") - assertTrue(result.contents.isNotEmpty(), "Resource contents should not be empty") - - val content = result.contents.firstOrNull() as? TextResourceContents - assertNotNull(content, "Resource content should be TextResourceContents") - assertEquals(100_000, content.text.length, "Large resource content length should match") - assertEquals("X".repeat(100_000), content.text, "Large resource content should match") - } - - @Test - fun testInvalidResourceUri() = runTest { - val invalidUri = "test://nonexistent.txt" - - val exception = assertThrows { - runBlocking { - client.readResource(ReadResourceRequest(ReadResourceRequestParams(uri = invalidUri))) - } - } - - val msg = exception.message ?: "" - val expectedMessage = - "MCP error -32603: Resource not found: test://nonexistent.txt" - - assertEquals(expectedMessage, msg, "Unexpected error message for invalid resource URI") - } - - @Test - fun testDynamicResource() = runBlocking(Dispatchers.IO) { - val initialResult = client.readResource( - ReadResourceRequest(ReadResourceRequestParams(uri = dynamicResourceUri)), - ) - assertNotNull(initialResult, "Initial read result should not be null") - val initialContent = (initialResult.contents.firstOrNull() as? TextResourceContents)?.text - assertEquals("Original content", initialContent, "Initial content should match") - - // update resource - dynamicResourceContent.set(true) - - val updatedResult = client.readResource( - ReadResourceRequest(ReadResourceRequestParams(uri = dynamicResourceUri)), - ) - assertNotNull(updatedResult, "Updated read result should not be null") - val updatedContent = (updatedResult.contents.firstOrNull() as? TextResourceContents)?.text - assertEquals("Updated content", updatedContent, "Updated content should match") - } - - @Test - fun testResourceAddAndRemove() = runBlocking(Dispatchers.IO) { - val initialList = client.listResources() - assertNotNull(initialList, "Initial list result should not be null") - val initialCount = initialList.resources.size - - val newResourceUri = "test://new-resource.txt" - server.addResource( - uri = newResourceUri, - name = "New Resource", - description = "A newly added resource", - mimeType = "text/plain", - ) { request -> - ReadResourceResult( - contents = listOf( - TextResourceContents( - text = "New resource content", - uri = request.params.uri, - mimeType = "text/plain", - ), - ), - ) - } - - val updatedList = client.listResources() - assertNotNull(updatedList, "Updated list result should not be null") - val updatedCount = updatedList.resources.size - - assertEquals(initialCount + 1, updatedCount, "Resource count should increase by 1") - val newResource = updatedList.resources.find { it.uri == newResourceUri } - assertNotNull(newResource, "New resource should be in the list") - - server.removeResource(newResourceUri) - - val finalList = client.listResources() - assertNotNull(finalList, "Final list result should not be null") - val finalCount = finalList.resources.size - - assertEquals(initialCount, finalCount, "Resource count should return to initial value") - val removedResource = finalList.resources.find { it.uri == newResourceUri } - assertEquals(null, removedResource, "Resource should be removed from the list") - } - - @Test - fun testConcurrentResourceOperations() = runTest { - val concurrentCount = 10 - val results = mutableListOf() - - runBlocking { - repeat(concurrentCount) { index -> - launch { - val uri = when (index % 3) { - 0 -> testResourceUri - 1 -> binaryResourceUri - else -> largeResourceUri - } - - val result = client.readResource(ReadResourceRequest(ReadResourceRequestParams(uri = uri))) - synchronized(results) { - results.add(result) - } - } - } - } - - assertEquals(concurrentCount, results.size, "All concurrent operations should complete") - results.forEach { result -> - assertNotNull(result, "Result should not be null") - assertTrue(result.contents.isNotEmpty(), "Result contents should not be empty") - } - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractToolIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractToolIntegrationTest.kt deleted file mode 100644 index cd12d79..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractToolIntegrationTest.kt +++ /dev/null @@ -1,820 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.kotlin - -import io.kotest.assertions.json.shouldEqualJson -import io.modelcontextprotocol.kotlin.sdk.CallToolRequest -import io.modelcontextprotocol.kotlin.sdk.CallToolResult -import io.modelcontextprotocol.kotlin.sdk.ImageContent -import io.modelcontextprotocol.kotlin.sdk.PromptMessageContent -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities -import io.modelcontextprotocol.kotlin.sdk.TextContent -import io.modelcontextprotocol.kotlin.sdk.Tool -import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequestParams -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.runTest -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.add -import kotlinx.serialization.json.buildJsonArray -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.put -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertNull -import java.text.DecimalFormat -import java.text.DecimalFormatSymbols -import java.util.Locale -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertTrue - -abstract class OldSchemaAbstractToolIntegrationTest : OldSchemaKotlinTestBase() { - private val testToolName = "echo" - private val testToolDescription = "A simple echo tool that returns the input text" - private val complexToolName = "calculator" - private val complexToolDescription = "A calculator tool that performs operations on numbers" - private val errorToolName = "error-tool" - private val errorToolDescription = "A tool that demonstrates error handling" - private val multiContentToolName = "multi-content" - private val multiContentToolDescription = "A tool that returns multiple content types" - - private val basicToolName = "basic-tool" - private val basicToolDescription = "A basic tool for testing" - - private val largeToolName = "large-tool" - private val largeToolDescription = "A tool that returns a large response" - private val largeToolContent = "X".repeat(100_000) // 100KB of data - - private val slowToolName = "slow-tool" - private val slowToolDescription = "A tool that takes time to respond" - - private val specialCharsToolName = "special-chars-tool" - private val specialCharsToolDescription = "A tool that handles special characters" - private val specialCharsContent = "!@#$%^&*()_+{}|:\"<>?~`-=[]\\;',./\n\t" - - override fun configureServerCapabilities(): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities = - ServerCapabilities( - tools = ServerCapabilities.Tools( - listChanged = true, - ), - ) - - override fun configureServer() { - setupEchoTool() - setupCalculatorTool() - setupErrorHandlingTool() - setupMultiContentTool() - } - - private fun setupEchoTool() { - server.addTool( - name = testToolName, - description = testToolDescription, - inputSchema = Tool.Input( - properties = buildJsonObject { - put( - "text", - buildJsonObject { - put("type", "string") - put("description", "The text to echo back") - }, - ) - }, - required = listOf("text"), - ), - ) { request -> - val text = (request.params.arguments?.get("text") as? JsonPrimitive)?.content ?: "No text provided" - - CallToolResult( - content = listOf(TextContent(text = "Echo: $text")), - structuredContent = buildJsonObject { - put("result", text) - }, - ) - } - } - - private fun setupCalculatorTool() { - server.addTool( - name = basicToolName, - description = basicToolDescription, - inputSchema = Tool.Input( - properties = buildJsonObject { - put( - "text", - buildJsonObject { - put("type", "string") - put("description", "The text to echo back") - }, - ) - }, - required = listOf("text"), - ), - ) { request -> - val text = (request.params.arguments?.get("text") as? JsonPrimitive)?.content ?: "No text provided" - - CallToolResult( - content = listOf(TextContent(text = "Echo: $text")), - structuredContent = buildJsonObject { - put("result", text) - }, - ) - } - - server.addTool( - name = specialCharsToolName, - description = specialCharsToolDescription, - inputSchema = Tool.Input( - properties = buildJsonObject { - put( - "special", - buildJsonObject { - put("type", "string") - put("description", "Special characters to process") - }, - ) - }, - ), - ) { request -> - val special = (request.params.arguments?.get("special") as? JsonPrimitive)?.content ?: specialCharsContent - - CallToolResult( - content = listOf(TextContent(text = "Received special characters: $special")), - structuredContent = buildJsonObject { - put("special", special) - put("length", special.length) - }, - ) - } - - server.addTool( - name = slowToolName, - description = slowToolDescription, - inputSchema = Tool.Input( - properties = buildJsonObject { - put( - "delay", - buildJsonObject { - put("type", "integer") - put("description", "Delay in milliseconds") - }, - ) - }, - ), - ) { request -> - val delay = (request.params.arguments?.get("delay") as? JsonPrimitive)?.content?.toIntOrNull() ?: 1000 - - // simulate slow operation - runBlocking { - delay(delay.toLong()) - } - - CallToolResult( - content = listOf(TextContent(text = "Completed after ${delay}ms delay")), - structuredContent = buildJsonObject { - put("delay", delay) - }, - ) - } - - server.addTool( - name = largeToolName, - description = largeToolDescription, - inputSchema = Tool.Input( - properties = buildJsonObject { - put( - "size", - buildJsonObject { - put("type", "integer") - put("description", "Size multiplier") - }, - ) - }, - ), - ) { request -> - val size = (request.params.arguments?.get("size") as? JsonPrimitive)?.content?.toIntOrNull() ?: 1 - val content = largeToolContent.take(largeToolContent.length.coerceAtMost(size * 1000)) - - CallToolResult( - content = listOf(TextContent(text = content)), - structuredContent = buildJsonObject { - put("size", content.length) - }, - ) - } - - server.addTool( - name = complexToolName, - description = complexToolDescription, - inputSchema = Tool.Input( - properties = buildJsonObject { - put( - "operation", - buildJsonObject { - put("type", "string") - put("description", "The operation to perform (add, subtract, multiply, divide)") - put( - "enum", - buildJsonArray { - add("add") - add("subtract") - add("multiply") - add("divide") - }, - ) - }, - ) - put( - "a", - buildJsonObject { - put("type", "number") - put("description", "First operand") - }, - ) - put( - "b", - buildJsonObject { - put("type", "number") - put("description", "Second operand") - }, - ) - put( - "precision", - buildJsonObject { - put("type", "integer") - put("description", "Number of decimal places (optional)") - put("default", 2) - }, - ) - put( - "showSteps", - buildJsonObject { - put("type", "boolean") - put("description", "Whether to show calculation steps") - put("default", false) - }, - ) - put( - "tags", - buildJsonObject { - put("type", "array") - put("description", "Optional tags for the calculation") - put( - "items", - buildJsonObject { - put("type", "string") - }, - ) - }, - ) - }, - required = listOf("operation", "a", "b"), - ), - ) { request -> - val operation = (request.params.arguments?.get("operation") as? JsonPrimitive)?.content ?: "add" - val a = (request.params.arguments?.get("a") as? JsonPrimitive)?.content?.toDoubleOrNull() ?: 0.0 - val b = (request.params.arguments?.get("b") as? JsonPrimitive)?.content?.toDoubleOrNull() ?: 0.0 - val precision = (request.params.arguments?.get("precision") as? JsonPrimitive)?.content?.toIntOrNull() ?: 2 - val showSteps = - (request.params.arguments?.get("showSteps") as? JsonPrimitive)?.content?.toBoolean() ?: false - val tags = (request.params.arguments?.get("tags") as? JsonArray)?.mapNotNull { - (it as? JsonPrimitive)?.content - } ?: emptyList() - - val result = when (operation) { - "add" -> a + b - "subtract" -> a - b - "multiply" -> a * b - "divide" -> if (b != 0.0) a / b else Double.POSITIVE_INFINITY - else -> 0.0 - } - - val pattern = if (precision > 0) "0." + "0".repeat(precision) else "0" - val symbols = DecimalFormatSymbols(Locale.US).apply { decimalSeparator = '.' } - val df = DecimalFormat(pattern, symbols).apply { isGroupingUsed = false } - val formattedResult = df.format(result) - - val textContent = if (showSteps) { - "Operation: $operation\nA: $a\nB: $b\nResult: $formattedResult\nTags: ${ - tags.joinToString(", ") - }" - } else { - "Result: $formattedResult" - } - - CallToolResult( - content = listOf(TextContent(text = textContent)), - structuredContent = buildJsonObject { - put("operation", operation) - put("a", a) - put("b", b) - put("result", result) - put("formattedResult", formattedResult) - put("precision", precision) - put("tags", buildJsonArray { tags.forEach { add(it) } }) - }, - ) - } - } - - private fun setupErrorHandlingTool() { - server.addTool( - name = errorToolName, - description = errorToolDescription, - inputSchema = Tool.Input( - properties = buildJsonObject { - put( - "errorType", - buildJsonObject { - put("type", "string") - put("description", "Type of error to simulate (none, exception, error)") - put( - "enum", - buildJsonArray { - add("none") - add("exception") - add("error") - }, - ) - }, - ) - put( - "message", - buildJsonObject { - put("type", "string") - put("description", "Custom error message") - put("default", "An error occurred") - }, - ) - }, - required = listOf("errorType"), - ), - ) { request -> - val errorType = (request.params.arguments?.get("errorType") as? JsonPrimitive)?.content ?: "none" - val message = (request.params.arguments?.get("message") as? JsonPrimitive)?.content ?: "An error occurred" - - when (errorType) { - "exception" -> throw IllegalArgumentException(message) - - "error" -> CallToolResult( - content = listOf(TextContent(text = "Error: $message")), - structuredContent = buildJsonObject { - put("error", true) - put("message", message) - }, - ) - - else -> CallToolResult( - content = listOf(TextContent(text = "No error occurred")), - structuredContent = buildJsonObject { - put("error", false) - put("message", "Success") - }, - ) - } - } - } - - private fun setupMultiContentTool() { - server.addTool( - name = multiContentToolName, - description = multiContentToolDescription, - inputSchema = Tool.Input( - properties = buildJsonObject { - put( - "text", - buildJsonObject { - put("type", "string") - put("description", "Text to include in the response") - }, - ) - put( - "includeImage", - buildJsonObject { - put("type", "boolean") - put("description", "Whether to include an image in the response") - put("default", true) - }, - ) - }, - required = listOf("text"), - ), - ) { request -> - val text = (request.params.arguments?.get("text") as? JsonPrimitive)?.content ?: "Default text" - val includeImage = - (request.params.arguments?.get("includeImage") as? JsonPrimitive)?.content?.toBoolean() ?: true - - val content = mutableListOf( - TextContent(text = "Text content: $text"), - ) - - if (includeImage) { - content.add( - ImageContent( - data = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==", - mimeType = "image/png", - ), - ) - } - - CallToolResult( - content = content, - structuredContent = buildJsonObject { - put("text", text) - put("includeImage", includeImage) - }, - ) - } - } - - @Test - fun testListTools(): Unit = runBlocking(Dispatchers.IO) { - val result = client.listTools() - - assertNotNull(result, "List tools result should not be null") - assertNull(actual = result.meta, "List tools result meta should be null") - assertTrue(result.tools.isNotEmpty(), "Tools list should not be empty") - - val testTool = result.tools.find { it.name == testToolName } - - assertNotNull(testTool, "Test tool should be in the list") - assertEquals( - testToolDescription, - testTool.description, - "Tool description should match", - ) - } - - @Test - fun testCallTool(): Unit = runBlocking(Dispatchers.IO) { - val testText = "Hello, world!" - val arguments = mapOf("text" to testText) - - val result = client.callTool(testToolName, arguments) - assertNull(actual = result.meta, "CallTool result meta should be null") - - val actualContent = result.structuredContent.toString() - val expectedContent = """ - {"result":"Hello, world!"} - """.trimIndent() - - actualContent shouldEqualJson expectedContent - } - - @Test - fun testComplexInputSchemaTool(): Unit = runBlocking(Dispatchers.IO) { - val toolsList = client.listTools() - assertNotNull(toolsList, "Tools list should not be null") - val calculatorTool = toolsList.tools.find { it.name == complexToolName } - assertNotNull(calculatorTool, "Calculator tool should be in the list") - - val arguments = mapOf( - "operation" to "multiply", - "a" to 5.5, - "b" to 2.0, - "precision" to 3, - "showSteps" to true, - "tags" to listOf("test", "calculator", "integration"), - ) - - val result = client.callTool( - complexToolName, - arguments, - ) - - val actualContent = result.structuredContent.toString() - val expectedContent = """ - { - "operation" : "multiply", - "a" : 5.5, - "b" : 2.0, - "result" : 11.0, - "formattedResult" : "11.000", - "precision" : 3, - "tags" : ["test", "calculator", "integration"] - } - """.trimIndent() - - actualContent shouldEqualJson expectedContent - } - - @Test - fun testToolErrorHandling(): Unit = runBlocking(Dispatchers.IO) { - val successArgs = mapOf("errorType" to "none") - val successResult = client.callTool(errorToolName, successArgs) - - val actualContent = successResult?.structuredContent.toString() - val expectedContent = """ - { - "error" : false, - "message" : "Success" - } - """.trimIndent() - - actualContent shouldEqualJson expectedContent - - val errorArgs = mapOf( - "errorType" to "error", - "message" to "Custom error message", - ) - val errorResult = client.callTool( - errorToolName, - errorArgs, - ) - - val actualError = errorResult.structuredContent.toString() - val expectedError = """ - { - "error" : true, - "message" : "Custom error message" - } - """.trimIndent() - - actualError shouldEqualJson expectedError - - val exceptionArgs = mapOf( - "errorType" to "exception", - "message" to "My exception message", - ) - - val exceptionResult = client.callTool( - errorToolName, - exceptionArgs, - ) - - assertTrue(exceptionResult.isError ?: false, "isError should be true for exception") - - val exceptionContent = exceptionResult.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(exceptionContent, "Error content should be present in the result") - - val exceptionText = exceptionContent.text ?: "" - assertTrue( - exceptionText.contains("Error executing tool") && exceptionText.contains("My exception message"), - "Error message should contain the exception details", - ) - } - - @Test - fun testMultiContentTool(): Unit = runBlocking(Dispatchers.IO) { - val testText = "Test multi-content" - val arguments = mapOf( - "text" to testText, - "includeImage" to true, - ) - - val result = client.callTool( - multiContentToolName, - arguments, - ) - - assertEquals( - 2, - result.content.size, - "Tool result should have 2 content items", - ) - - val textContent = result.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Result should contain TextContent") - assertNotNull(textContent.text, "Text content should not be null") - assertEquals( - "Text content: $testText", - textContent.text, - "Text content should match", - ) - - val imageContent = result.content.firstOrNull { it is ImageContent } as? ImageContent - assertNotNull(imageContent, "Result should contain ImageContent") - assertEquals("image/png", imageContent.mimeType, "Image MIME type should match") - assertTrue(imageContent.data.isNotEmpty(), "Image data should not be empty") - - val actualContent = result.structuredContent.toString() - val expectedContent = """ - { - "text" : "Test multi-content", - "includeImage" : true - } - """.trimIndent() - - actualContent shouldEqualJson expectedContent - - val textOnlyArgs = mapOf( - "text" to testText, - "includeImage" to false, - ) - - val textOnlyResult = client.callTool( - multiContentToolName, - textOnlyArgs, - ) - - assertEquals( - 1, - textOnlyResult.content.size, - "Text-only result should have 1 content item", - ) - } - - @Test - fun testComplexNestedSchema(): Unit = runBlocking(Dispatchers.IO) { - val userJson = buildJsonObject { - put("name", JsonPrimitive("John Galt")) - put("age", JsonPrimitive(30)) - put( - "address", - buildJsonObject { - put("street", JsonPrimitive("123 Main St")) - put("city", JsonPrimitive("New York")) - put("country", JsonPrimitive("USA")) - }, - ) - } - - val optionsJson = buildJsonArray { - add(JsonPrimitive("option1")) - add(JsonPrimitive("option2")) - add(JsonPrimitive("option3")) - } - - val arguments = buildJsonObject { - put("user", userJson) - put("options", optionsJson) - } - - val result = client.callTool( - CallToolRequest( - CallToolRequestParams( - name = complexToolName, - arguments = arguments, - ), - ), - ) - - val actualContent = result.structuredContent.toString() - val expectedContent = """ - { - "operation": "add", - "a": 0.0, - "b": 0.0, - "result": 0.0, - "formattedResult": "0.00", - "precision": 2, - "tags": [] - } - """.trimIndent() - - actualContent shouldEqualJson expectedContent - } - - @Test - fun testLargeResponse(): Unit = runBlocking(Dispatchers.IO) { - val size = 10 - val arguments = mapOf("size" to size) - - val result = client.callTool( - largeToolName, - arguments, - ) - - val content = result.content.firstOrNull() as TextContent - assertNotNull(content, "Tool result content should be TextContent") - - val actualContent = result.structuredContent.toString() - val expectedContent = """ - { - "size" : 10000 - } - """.trimIndent() - - actualContent shouldEqualJson expectedContent - } - - @Test - fun testSlowTool(): Unit = runBlocking(Dispatchers.IO) { - val delay = 500 - val arguments = mapOf("delay" to delay) - - val startTime = System.currentTimeMillis() - val result = client.callTool(slowToolName, arguments) - val endTime = System.currentTimeMillis() - - val content = result.content.firstOrNull() as? TextContent - assertNotNull(content, "Tool result content should be TextContent") - - assertTrue(endTime - startTime >= delay, "Tool should take at least the specified delay") - - val actualContent = result.structuredContent.toString() - val expectedContent = """ - { - "delay" : 500 - } - """.trimIndent() - - actualContent shouldEqualJson expectedContent - } - - @Test - fun testSpecialCharacters() { - runBlocking(Dispatchers.IO) { - val arguments = mapOf("special" to specialCharsContent) - - val result = client.callTool( - specialCharsToolName, - arguments, - ) - - val content = result.content.firstOrNull() as? TextContent - assertNotNull(content, "Tool result content should be TextContent") - val text = content.text ?: "" - - assertTrue(text.contains(specialCharsContent), "Result should contain the special characters") - - val actualContent = result.structuredContent.toString() - val expectedContent = """ - { - "special" : "!@#$%^&*()_+{}|:\"<>?~`-=[]\\;',./\n\t", - "length" : 34 - } - """.trimIndent() - - actualContent shouldEqualJson expectedContent - } - } - - @Test - fun testConcurrentToolCalls() = runTest { - val concurrentCount = 10 - val results = mutableListOf() - - runBlocking { - repeat(concurrentCount) { index -> - launch { - val toolName = when (index % 5) { - 0 -> basicToolName - 1 -> complexToolName - 2 -> largeToolName - 3 -> slowToolName - else -> specialCharsToolName - } - - val arguments = when (toolName) { - basicToolName -> mapOf("text" to "Concurrent call $index") - - complexToolName -> mapOf( - "user" to mapOf( - "name" to "User $index", - "age" to 20 + index, - "address" to mapOf( - "street" to "Street $index", - "city" to "City $index", - "country" to "Country $index", - ), - ), - ) - - largeToolName -> mapOf("size" to 1) - - slowToolName -> mapOf("delay" to 100) - - else -> mapOf("special" to "!@#$%^&*()") - } - - val result = client.callTool(toolName, arguments) - - synchronized(results) { - results.add(result) - } - } - } - } - - assertEquals(concurrentCount, results.size, "All concurrent operations should complete") - results.forEach { result -> - assertNotNull(result, "Result should not be null") - assertTrue(result.content.isNotEmpty(), "Result content should not be empty") - } - } - - @Test - fun testNonExistentTool() = runTest { - val nonExistentToolName = "non-existent-tool" - val arguments = mapOf("text" to "Test") - - val result = runBlocking { - client.callTool(nonExistentToolName, arguments) - } - - assertNotNull(result, "Tool call result should not be null") - val callResult = result - assertTrue(callResult.isError ?: false, "isError should be true for non-existent tool") - - val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Error content should be present in the result") - - val errorText = textContent.text ?: "" - assertTrue( - errorText.contains("non-existent-tool") && errorText.contains("not found"), - "Error message should indicate the tool was not found", - ) - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaKotlinTestBase.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaKotlinTestBase.kt deleted file mode 100644 index 241172e..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaKotlinTestBase.kt +++ /dev/null @@ -1,184 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.kotlin - -import io.ktor.client.HttpClient -import io.ktor.client.engine.cio.CIO -import io.ktor.client.plugins.sse.SSE -import io.ktor.server.application.install -import io.ktor.server.engine.EmbeddedServer -import io.ktor.server.engine.embeddedServer -import io.ktor.server.routing.routing -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.client.SseClientTransport -import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport -import io.modelcontextprotocol.kotlin.sdk.server.Server -import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions -import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport -import io.modelcontextprotocol.kotlin.sdk.server.mcp -import io.modelcontextprotocol.kotlin.test.utils.Retry -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withTimeout -import kotlinx.io.Sink -import kotlinx.io.Source -import kotlinx.io.asSink -import kotlinx.io.asSource -import kotlinx.io.buffered -import org.awaitility.kotlin.await -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.BeforeEach -import java.io.PipedInputStream -import java.io.PipedOutputStream -import kotlin.time.Duration.Companion.seconds -import io.ktor.server.cio.CIO as ServerCIO -import io.ktor.server.sse.SSE as ServerSSE - -@Retry(times = 3) -abstract class OldSchemaKotlinTestBase { - - protected val host = "localhost" - protected var port: Int = 0 - - protected lateinit var server: Server - protected lateinit var client: Client - protected lateinit var serverEngine: EmbeddedServer<*, *> - - // Transport selection - protected enum class TransportKind { SSE, STDIO } - protected open val transportKind: TransportKind = TransportKind.STDIO - - // STDIO-specific fields - private var stdioServerTransport: StdioServerTransport? = null - private var stdioClientInput: Source? = null - private var stdioClientOutput: Sink? = null - - protected abstract fun configureServerCapabilities(): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities - protected abstract fun configureServer() - - @BeforeEach - fun setUp() { - setupServer() - if (transportKind == TransportKind.SSE) { - await - .ignoreExceptions() - .until { - port = runBlocking { serverEngine.engine.resolvedConnectors().first().port } - port != 0 - } - } - runBlocking { - setupClient() - } - } - - protected suspend fun setupClient() { - when (transportKind) { - TransportKind.SSE -> { - val transport = SseClientTransport( - HttpClient(CIO) { - install(SSE) - }, - "http://$host:$port", - ) - client = Client( - Implementation("test", "1.0"), - ) - client.connect(transport) - } - - TransportKind.STDIO -> { - val input = checkNotNull(stdioClientInput) { "STDIO client input not initialized" } - val output = checkNotNull(stdioClientOutput) { "STDIO client output not initialized" } - val transport = StdioClientTransport( - input = input, - output = output, - ) - client = Client( - Implementation("test", "1.0"), - ) - client.connect(transport) - } - } - } - - protected fun setupServer() { - val capabilities = configureServerCapabilities() - - server = Server( - Implementation(name = "test-server", version = "1.0"), - ServerOptions(capabilities = capabilities), - ) - - configureServer() - - if (transportKind == TransportKind.SSE) { - serverEngine = embeddedServer(ServerCIO, host = host, port = port) { - install(ServerSSE) - routing { - mcp { server } - } - }.start(wait = false) - } else { - // Create in-memory stdio pipes: client->server and server->client - val clientToServerOut = PipedOutputStream() - val clientToServerIn = PipedInputStream(clientToServerOut) - - val serverToClientOut = PipedOutputStream() - val serverToClientIn = PipedInputStream(serverToClientOut) - - // Server transport reads from client and writes to client - val serverTransport = StdioServerTransport( - inputStream = clientToServerIn.asSource().buffered(), - outputStream = serverToClientOut.asSink().buffered(), - ) - stdioServerTransport = serverTransport - - // Prepare client-side streams for later client initialization - stdioClientInput = serverToClientIn.asSource().buffered() - stdioClientOutput = clientToServerOut.asSink().buffered() - - // Start server transport by connecting the server - runBlocking { - server.createSession(serverTransport) - } - } - } - - @AfterEach - fun tearDown() { - // close client - if (::client.isInitialized) { - try { - runBlocking { - withTimeout(3.seconds) { - client.close() - } - } - } catch (e: Exception) { - println("Warning: Error during client close: ${e.message}") - } - } - - // stop server - if (transportKind == TransportKind.SSE) { - if (::serverEngine.isInitialized) { - try { - serverEngine.stop(500, 1000) - } catch (e: Exception) { - println("Warning: Error during server stop: ${e.message}") - } - } - } else { - stdioServerTransport?.let { - try { - runBlocking { it.close() } - } catch (e: Exception) { - println("Warning: Error during stdio server stop: ${e.message}") - } finally { - stdioServerTransport = null - stdioClientInput = null - stdioClientOutput = null - } - } - } - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaPromptIntegrationTestSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaPromptIntegrationTestSse.kt deleted file mode 100644 index 63d5865..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaPromptIntegrationTestSse.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.sse - -import io.modelcontextprotocol.kotlin.sdk.integration.kotlin.OldSchemaAbstractPromptIntegrationTest - -class OldSchemaPromptIntegrationTestSse : OldSchemaAbstractPromptIntegrationTest() { - override val transportKind: TransportKind = TransportKind.SSE -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaResourceIntegrationTestSse b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaResourceIntegrationTestSse deleted file mode 100644 index 6837753..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaResourceIntegrationTestSse +++ /dev/null @@ -1,7 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.sse - -import io.modelcontextprotocol.kotlin.sdk.integration.kotlin.OldSchemaAbstractResourceIntegrationTest - -class OldSchemaResourceIntegrationTestSse : OldSchemaAbstractResourceIntegrationTest() { - override val transportKind: TransportKind = TransportKind.SSE -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaToolIntegrationTestSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaToolIntegrationTestSse.kt deleted file mode 100644 index bd39d77..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaToolIntegrationTestSse.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.sse - -import io.modelcontextprotocol.kotlin.sdk.integration.kotlin.OldSchemaAbstractToolIntegrationTest - -class OldSchemaToolIntegrationTestSse : OldSchemaAbstractToolIntegrationTest() { - override val transportKind: TransportKind = TransportKind.SSE -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaPromptIntegrationTestStdio.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaPromptIntegrationTestStdio.kt deleted file mode 100644 index 68d03eb..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaPromptIntegrationTestStdio.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.stdio - -import io.modelcontextprotocol.kotlin.sdk.integration.kotlin.OldSchemaAbstractPromptIntegrationTest - -class OldSchemaPromptIntegrationTestStdio : OldSchemaAbstractPromptIntegrationTest() { - override val transportKind: TransportKind = TransportKind.STDIO -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaResourceIntegrationTestStdio.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaResourceIntegrationTestStdio.kt deleted file mode 100644 index 4b07760..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaResourceIntegrationTestStdio.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.stdio - -import io.modelcontextprotocol.kotlin.sdk.integration.kotlin.OldSchemaAbstractResourceIntegrationTest - -class OldSchemaResourceIntegrationTestStdio : OldSchemaAbstractResourceIntegrationTest() { - override val transportKind: TransportKind = TransportKind.STDIO -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaToolIntegrationTestStdio.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaToolIntegrationTestStdio.kt deleted file mode 100644 index 046eebf..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaToolIntegrationTestStdio.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.stdio - -import io.modelcontextprotocol.kotlin.sdk.integration.kotlin.OldSchemaAbstractToolIntegrationTest - -class OldSchemaToolIntegrationTestStdio : OldSchemaAbstractToolIntegrationTest() { - override val transportKind: TransportKind = TransportKind.STDIO -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/OldSchemaWebSocketIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/OldSchemaWebSocketIntegrationTest.kt deleted file mode 100644 index 312e3be..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/OldSchemaWebSocketIntegrationTest.kt +++ /dev/null @@ -1,193 +0,0 @@ -@file:Suppress("DEPRECATION") - -package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.websocket - -import io.ktor.client.HttpClient -import io.ktor.server.application.install -import io.ktor.server.cio.CIOApplicationEngine -import io.ktor.server.engine.EmbeddedServer -import io.ktor.server.engine.embeddedServer -import io.ktor.server.routing.routing -import io.modelcontextprotocol.kotlin.sdk.GetPromptRequest -import io.modelcontextprotocol.kotlin.sdk.GetPromptResult -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.PromptArgument -import io.modelcontextprotocol.kotlin.sdk.PromptMessage -import io.modelcontextprotocol.kotlin.sdk.Role -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities -import io.modelcontextprotocol.kotlin.sdk.TextContent -import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.client.mcpWebSocketTransport -import io.modelcontextprotocol.kotlin.sdk.server.Server -import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions -import io.modelcontextprotocol.kotlin.sdk.server.mcpWebSocket -import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequestParams -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.runBlocking -import kotlin.test.Test -import kotlin.test.assertTrue -import io.ktor.client.engine.cio.CIO as ClientCIO -import io.ktor.client.plugins.websocket.WebSockets as ClientWebSocket -import io.ktor.server.cio.CIO as ServerCIO -import io.ktor.server.websocket.WebSockets as ServerWebSockets - -class OldSchemaWebSocketIntegrationTest { - - @Test - fun `client should be able to connect to websocket server 2`() = runBlocking(Dispatchers.IO) { - var server: EmbeddedServer? = null - var client: Client? = null - - try { - server = initServer() - val port = server.engine.resolvedConnectors().first().port - client = initClient(serverPort = port) - } finally { - client?.close() - server?.stop(1000, 2000) - } - } - - /** - * Test Case #1: One opened connection, a client gets a prompt - * - * 1. Open WebSocket from Client A. - * 2. Send a POST request from Client A to POST /prompts/get. - * 3. Observe that Client A receives a response related to it. - */ - @Test - fun `single websocket connection`() = runBlocking(Dispatchers.IO) { - var server: EmbeddedServer? = null - var client: Client? = null - - try { - server = initServer() - val port = server.engine.resolvedConnectors().first().port - client = initClient("Client A", port) - - val promptA = getPrompt(client, "Client A") - assertTrue { "Client A" in promptA } - } finally { - client?.close() - server?.stop(1000, 2000) - } - } - - /** - * Test Case #1: Two open connections, each client gets a client-specific prompt - * - * 1. Open WebSocket connection #1 from Client A and note the sessionId= value. - * 2. Open WebSocket connection #2 from Client B and note the sessionId= value. - * 3. Send a POST request to POST /message with the corresponding sessionId#1. - * 4. Observe that Client B (connection #2) receives a response related to sessionId#1. - */ - @Test - fun `multiple websocket connections`() = runBlocking(Dispatchers.IO) { - var server: EmbeddedServer? = null - var clientA: Client? = null - var clientB: Client? = null - - try { - server = initServer() - val port = server.engine.resolvedConnectors().first().port - clientA = initClient("Client A", port) - clientB = initClient("Client B", port) - - // Step 3: Send a prompt request from Client A - val promptA = getPrompt(clientA, "Client A") - // Step 4: Send a prompt request from Client B - val promptB = getPrompt(clientB, "Client B") - - assertTrue { "Client A" in promptA } - assertTrue { "Client B" in promptB } - } finally { - clientA?.close() - clientB?.close() - server?.stop(1000, 2000) - } - } - - private suspend fun initClient(name: String = "", serverPort: Int): Client { - val client = Client( - Implementation(name = name, version = "1.0.0"), - ) - - val httpClient = HttpClient(ClientCIO) { - install(ClientWebSocket) - } - - // Create a transport wrapper that captures the session ID and received messages - val transport = httpClient.mcpWebSocketTransport { - url { - host = URL - port = serverPort - } - } - - client.connect(transport) - - return client - } - - private fun initServer(): EmbeddedServer { - val server = Server( - Implementation(name = "websocket-server", version = "1.0.0"), - ServerOptions( - capabilities = ServerCapabilities(prompts = ServerCapabilities.Prompts(listChanged = true)), - ), - ) - - server.addPrompt( - name = "prompt", - description = "Prompt description", - arguments = listOf( - PromptArgument( - name = "client", - description = "Client name who requested a prompt", - required = true, - ), - ), - ) { request -> - GetPromptResult( - description = "Prompt for ${request.params.name}", - messages = listOf( - PromptMessage( - role = Role.user, - content = TextContent("Prompt for client ${request.params.arguments?.get("client")}"), - ), - ), - ) - } - - val ktorServer = embeddedServer(ServerCIO, host = URL, port = PORT) { - install(ServerWebSockets) - routing { - mcpWebSocket(block = { server }) - } - } - - return ktorServer.start(wait = false) - } - - /** - * Retrieves a prompt result using the provided client and client name. - */ - private suspend fun getPrompt(client: Client, clientName: String): String { - val response = client.getPrompt( - GetPromptRequest( - GetPromptRequestParams( - "prompt", - arguments = mapOf("client" to clientName), - ), - ), - ) - - return (response.messages.first().content as? TextContent)?.text - ?: error("Failed to receive prompt for Client $clientName") - } - - companion object { - private const val PORT = 0 - private const val URL = "127.0.0.1" - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaAbstractSseIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaAbstractSseIntegrationTest.kt deleted file mode 100644 index 82d51b9..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaAbstractSseIntegrationTest.kt +++ /dev/null @@ -1,105 +0,0 @@ -@file:Suppress("DEPRECATION") - -package io.modelcontextprotocol.kotlin.sdk.integration.sse - -import io.ktor.client.HttpClient -import io.ktor.client.plugins.sse.SSE -import io.ktor.server.application.install -import io.ktor.server.cio.CIOApplicationEngine -import io.ktor.server.engine.EmbeddedServer -import io.ktor.server.engine.embeddedServer -import io.ktor.server.routing.routing -import io.modelcontextprotocol.kotlin.sdk.GetPromptResult -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.PromptArgument -import io.modelcontextprotocol.kotlin.sdk.PromptMessage -import io.modelcontextprotocol.kotlin.sdk.Role -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities -import io.modelcontextprotocol.kotlin.sdk.TextContent -import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.client.mcpSseTransport -import io.modelcontextprotocol.kotlin.sdk.server.Server -import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions -import io.modelcontextprotocol.kotlin.sdk.server.mcp -import io.ktor.client.engine.cio.CIO as ClientCIO -import io.ktor.server.cio.CIO as ServerCIO -import io.ktor.server.sse.SSE as ServerSSE - -open class OldSchemaAbstractSseIntegrationTest { - - suspend fun initTestClient(serverPort: Int, name: String? = null): Client { - val client = Client( - Implementation(name = name ?: DEFAULT_CLIENT_NAME, version = VERSION), - ) - - val httpClient = HttpClient(ClientCIO) { - install(SSE) - } - - // Create a transport wrapper that captures the session ID and received messages - val transport = httpClient.mcpSseTransport { - url { - host = URL - port = serverPort - } - } - - client.connect(transport) - - return client - } - - suspend fun initTestServer( - name: String? = null, - ): EmbeddedServer { - val server = Server( - Implementation(name = name ?: DEFAULT_SERVER_NAME, version = VERSION), - ServerOptions( - capabilities = ServerCapabilities(prompts = ServerCapabilities.Prompts(listChanged = true)), - ), - ) { - addPrompt( - name = "prompt", - description = "Prompt description", - arguments = listOf( - PromptArgument( - name = "client", - description = "Client name who requested a prompt", - required = true, - ), - ), - ) { request -> - GetPromptResult( - description = "Prompt for ${request.params.name}", - messages = listOf( - PromptMessage( - role = Role.user, - content = TextContent("Prompt for client ${request.params.arguments?.get("client")}"), - ), - ), - ) - } - } - - val ktorServer = embeddedServer( - ServerCIO, - host = URL, - port = PORT, - ) { - install(ServerSSE) - routing { - mcp { server } - } - } - - return ktorServer.startSuspend(wait = false) - } - - companion object { - private const val DEFAULT_CLIENT_NAME = "sse-test-client" - private const val DEFAULT_SERVER_NAME = "sse-test-server" - private const val VERSION = "1.0.0" - private const val URL = "127.0.0.1" - private const val PORT = 0 - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaSseIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaSseIntegrationTest.kt deleted file mode 100644 index 97313bd..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaSseIntegrationTest.kt +++ /dev/null @@ -1,109 +0,0 @@ -@file:Suppress("DEPRECATION") - -package io.modelcontextprotocol.kotlin.sdk.integration.sse - -import io.ktor.server.cio.CIOApplicationEngine -import io.ktor.server.engine.EmbeddedServer -import io.modelcontextprotocol.kotlin.sdk.GetPromptRequest -import io.modelcontextprotocol.kotlin.sdk.TextContent -import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequestParams -import io.modelcontextprotocol.kotlin.test.utils.actualPort -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.runBlocking -import kotlin.test.Test -import kotlin.test.assertTrue - -class OldSchemaSseIntegrationTest : OldSchemaAbstractSseIntegrationTest() { - - @Test - fun `client should be able to connect to sse server`() = runBlocking(Dispatchers.IO) { - var server: EmbeddedServer? = null - var client: Client? = null - - try { - server = initTestServer() - val port = server.actualPort() - client = initTestClient(serverPort = port) - } finally { - client?.close() - server?.stopSuspend(1000, 2000) - } - } - - /** - * Test Case #1: One opened connection, a client gets a prompt - * - * 1. Open SSE from Client A. - * 2. Send a POST request from Client A to POST /prompts/get. - * 3. Observe that Client A receives a response related to it. - */ - @Test - fun `single sse connection`() = runBlocking(Dispatchers.IO) { - var server: EmbeddedServer? = null - var client: Client? = null - try { - server = initTestServer() - val port = server.actualPort() - client = initTestClient(port, "Client A") - - val promptA = getPrompt(client, "Client A") - assertTrue { "Client A" in promptA } - } finally { - client?.close() - server?.stopSuspend(1000, 2000) - } - } - - /** - * Test Case #1: Two open connections, each client gets a client-specific prompt - * - * 1. Open SSE connection #1 from Client A and note the sessionId= value. - * 2. Open SSE connection #2 from Client B and note the sessionId= value. - * 3. Send a POST request to POST /message with the corresponding sessionId#1. - * 4. Observe that Client B (connection #2) receives a response related to sessionId#1. - */ - @Test - fun `multiple sse connections`() = runBlocking(Dispatchers.IO) { - var server: EmbeddedServer? = null - var clientA: Client? = null - var clientB: Client? = null - - try { - server = initTestServer() - val port = server.engine.resolvedConnectors().first().port - - clientA = initTestClient(port, "Client A") - clientB = initTestClient(port, "Client B") - - // Step 3: Send a prompt request from Client A - val promptA = getPrompt(clientA, "Client A") - // Step 4: Send a prompt request from Client B - val promptB = getPrompt(clientB, "Client B") - - assertTrue { "Client A" in promptA } - assertTrue { "Client B" in promptB } - } finally { - clientA?.close() - clientB?.close() - server?.stopSuspend(1000, 2000) - } - } - - /** - * Retrieves a prompt result using the provided client and client name. - */ - private suspend fun getPrompt(client: Client, clientName: String): String { - val response = client.getPrompt( - GetPromptRequest( - GetPromptRequestParams( - "prompt", - arguments = mapOf("client" to clientName), - ), - ), - ) - - return (response.messages.first().content as? TextContent)?.text - ?: error("Failed to receive prompt for Client $clientName") - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/OldSchemaAbstractKotlinClientTsServerTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/OldSchemaAbstractKotlinClientTsServerTest.kt deleted file mode 100644 index 5d0406c..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/OldSchemaAbstractKotlinClientTsServerTest.kt +++ /dev/null @@ -1,77 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.typescript - -import io.modelcontextprotocol.kotlin.sdk.TextContent -import io.modelcontextprotocol.kotlin.sdk.client.Client -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.runBlocking -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.Timeout -import java.util.concurrent.TimeUnit -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertTrue - -abstract class OldSchemaAbstractKotlinClientTsServerTest : TsTestBase() { - protected abstract suspend fun useClient(block: suspend (Client) -> T): T - - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun connectsAndPings() = runBlocking(Dispatchers.IO) { - useClient { client -> - assertNotNull(client, "Client should be initialized") - val ping = client.ping() - assertNotNull(ping, "Ping result should not be null") - val serverImpl = client.serverVersion - assertNotNull(serverImpl, "Server implementation should not be null") - println("Connected to TypeScript server: ${serverImpl.name} v${serverImpl.version}") - } - } - - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun listsTools() = runBlocking(Dispatchers.IO) { - useClient { client -> - val result = client.listTools() - assertNotNull(result, "Tools list should not be null") - assertTrue(result.tools.isNotEmpty(), "Tools list should not be empty") - val toolNames = result.tools.map { it.name } - assertTrue("greet" in toolNames, "Greet tool should be available") - assertTrue("multi-greet" in toolNames, "Multi-greet tool should be available") - // Some tests also check collect-user-info; keep base minimal and non-breaking - } - } - - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun callGreet() = runBlocking(Dispatchers.IO) { - useClient { client -> - val testName = "TestUser" - val arguments = mapOf("name" to testName) - val result = client.callTool("greet", arguments) - assertNotNull(result, "Tool call result should not be null") - val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult - val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Text content should be present in the result") - assertEquals("Hello, $testName!", textContent.text) - } - } - - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun multipleClients() = runBlocking(Dispatchers.IO) { - useClient { client1 -> - useClient { client2 -> - val tools1 = client1.listTools() - val tools2 = client2.listTools() - assertTrue(tools1.tools.isNotEmpty(), "Tools list for first client should not be empty") - assertTrue(tools2.tools.isNotEmpty(), "Tools list for second client should not be empty") - val toolNames1 = tools1.tools.map { it.name } - val toolNames2 = tools2.tools.map { it.name } - assertTrue("greet" in toolNames1, "Greet tool should be available to first client") - assertTrue("multi-greet" in toolNames1, "Multi-greet tool should be available to first client") - assertTrue("greet" in toolNames2, "Greet tool should be available to second client") - assertTrue("multi-greet" in toolNames2, "Multi-greet tool should be available to second client") - } - } - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerEdgeCasesTestSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerEdgeCasesTestSse.kt deleted file mode 100644 index f2f6009..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerEdgeCasesTestSse.kt +++ /dev/null @@ -1,200 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse - -import io.modelcontextprotocol.kotlin.sdk.TextContent -import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TsTestBase -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withTimeout -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.Timeout -import java.util.concurrent.TimeUnit -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertTrue -import kotlin.time.Duration.Companion.seconds - -class OldSchemaKotlinClientTsServerEdgeCasesTestSse : TsTestBase() { - - override val transportKind = TransportKind.SSE - - private val host = "localhost" - private val serverUrl: String by lazy { getSharedSseUrl() } - - private lateinit var client: Client - - @AfterEach - fun tearDown() { - if (::client.isInitialized) { - try { - runBlocking { - withTimeout(3.seconds) { - client.close() - } - } - } catch (e: Exception) { - println("Warning: Error during client close: ${e.message}") - } - } - } - - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun testNonExistentTool(): Unit = runBlocking(Dispatchers.IO) { - withClient(serverUrl) { client -> - val nonExistentToolName = "non-existent-tool" - val arguments = mapOf("name" to "TestUser") - - val result = client.callTool(nonExistentToolName, arguments) - assertNotNull(result, "Tool call result should not be null") - - val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult - assertTrue(callResult.isError ?: false, "isError should be true for non-existent tool") - - val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Error content should be present in the result") - - val errorText = textContent.text ?: "" - assertTrue( - errorText.contains("non-existent-tool") && errorText.contains("not found"), - "Error message should indicate the tool was not found", - ) - } - } - - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun testSpecialCharactersInArguments(): Unit = runBlocking(Dispatchers.IO) { - withClient(serverUrl) { client -> - val specialChars = "!@#$%^&*()_+{}[]|\\:;\"'<>,.?/" - val arguments = mapOf("name" to specialChars) - - val result = client.callTool("greet", arguments) - assertNotNull(result, "Tool call result should not be null") - - val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult - val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Text content should be present in the result") - - val text = textContent.text ?: "" - assertTrue( - text.contains(specialChars), - "Tool response should contain the special characters", - ) - } - } - - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun testLargePayload(): Unit = runBlocking(Dispatchers.IO) { - withClient(serverUrl) { client -> - val largeName = "A".repeat(10 * 1024) - val arguments = mapOf("name" to largeName) - - val result = client.callTool("greet", arguments) - assertNotNull(result, "Tool call result should not be null") - - val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult - val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Text content should be present in the result") - - val text = textContent.text ?: "" - assertTrue( - text.contains("Hello,") && text.contains("A"), - "Tool response should contain the greeting with the large name", - ) - } - } - - @Test - @Timeout(60, unit = TimeUnit.SECONDS) - fun testConcurrentRequests(): Unit = runBlocking(Dispatchers.IO) { - withClient(serverUrl) { client -> - val concurrentCount = 5 - val responses = coroutineScope { - val results = (1..concurrentCount).map { i -> - async { - val name = "ConcurrentClient$i" - val arguments = mapOf("name" to name) - - val result = client.callTool("greet", arguments) - assertNotNull(result, "Tool call result should not be null for client $i") - - val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult - val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Text content should be present for client $i") - - textContent.text ?: "" - } - } - results.awaitAll() - } - - for (i in 1..concurrentCount) { - val expectedName = "ConcurrentClient$i" - val matchingResponses = responses.filter { it.contains("Hello, $expectedName!") } - assertEquals( - 1, - matchingResponses.size, - "Should have exactly one response for $expectedName", - ) - } - } - } - - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun testInvalidArguments(): Unit = runBlocking(Dispatchers.IO) { - withClient(serverUrl) { client -> - val invalidArguments = mapOf( - "name" to JsonObject(mapOf("nested" to JsonPrimitive("value"))), - ) - - val result = client.callTool("greet", invalidArguments) - assertNotNull(result, "Tool call result should not be null") - - val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult - assertTrue(callResult.isError ?: false, "isError should be true for invalid arguments") - - val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Error content should be present in the result") - - val errorText = textContent.text ?: "" - assertTrue( - errorText.contains("Invalid arguments") && errorText.contains("greet"), - "Error message should indicate invalid arguments for tool greet", - ) - } - } - - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun testMultipleToolCalls(): Unit = runBlocking(Dispatchers.IO) { - withClient(serverUrl) { client -> - repeat(10) { i -> - val name = "SequentialClient$i" - val arguments = mapOf("name" to name) - - val result = client.callTool("greet", arguments) - assertNotNull(result, "Tool call result should not be null for call $i") - - val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult - val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Text content should be present for call $i") - - assertEquals( - "Hello, $name!", - textContent.text, - "Tool response should contain the greeting with the provided name", - ) - } - } - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerTestSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerTestSse.kt deleted file mode 100644 index f9415e1..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerTestSse.kt +++ /dev/null @@ -1,25 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse - -import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.OldSchemaAbstractKotlinClientTsServerTest -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind -import kotlinx.coroutines.withTimeout -import kotlin.time.Duration.Companion.seconds - -class OldSchemaKotlinClientTsServerTestSse : OldSchemaAbstractKotlinClientTsServerTest() { - - override val transportKind = TransportKind.SSE - - private val host = "localhost" - private val serverUrl: String by lazy { getSharedSseUrl() } - - override suspend fun useClient(block: suspend (Client) -> T): T = withClient(serverUrl) { client -> - try { - withTimeout(20.seconds) { block(client) } - } finally { - try { - withTimeout(3.seconds) { client.close() } - } catch (_: Exception) {} - } - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinServerForTsClientSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinServerForTsClientSse.kt deleted file mode 100644 index 08dcad7..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinServerForTsClientSse.kt +++ /dev/null @@ -1,466 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse - -import io.github.oshai.kotlinlogging.KotlinLogging -import io.ktor.http.ContentType -import io.ktor.http.HttpStatusCode -import io.ktor.server.application.ApplicationCall -import io.ktor.server.cio.CIO -import io.ktor.server.engine.EmbeddedServer -import io.ktor.server.engine.embeddedServer -import io.ktor.server.request.header -import io.ktor.server.request.receiveText -import io.ktor.server.response.header -import io.ktor.server.response.respond -import io.ktor.server.response.respondText -import io.ktor.server.response.respondTextWriter -import io.ktor.server.routing.delete -import io.ktor.server.routing.get -import io.ktor.server.routing.post -import io.ktor.server.routing.routing -import io.modelcontextprotocol.kotlin.sdk.CallToolResult -import io.modelcontextprotocol.kotlin.sdk.ErrorCode -import io.modelcontextprotocol.kotlin.sdk.GetPromptResult -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.JSONRPCError -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage -import io.modelcontextprotocol.kotlin.sdk.JSONRPCNotification -import io.modelcontextprotocol.kotlin.sdk.JSONRPCRequest -import io.modelcontextprotocol.kotlin.sdk.JSONRPCResponse -import io.modelcontextprotocol.kotlin.sdk.PromptArgument -import io.modelcontextprotocol.kotlin.sdk.PromptMessage -import io.modelcontextprotocol.kotlin.sdk.ReadResourceResult -import io.modelcontextprotocol.kotlin.sdk.RequestId -import io.modelcontextprotocol.kotlin.sdk.Role -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities -import io.modelcontextprotocol.kotlin.sdk.TextContent -import io.modelcontextprotocol.kotlin.sdk.TextResourceContents -import io.modelcontextprotocol.kotlin.sdk.Tool -import io.modelcontextprotocol.kotlin.sdk.server.Server -import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions -import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport -import io.modelcontextprotocol.kotlin.sdk.shared.McpJson -import io.modelcontextprotocol.kotlin.sdk.shared.TransportSendOptions -import io.modelcontextprotocol.kotlin.sdk.types.RPCError -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withTimeoutOrNull -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonNull -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.contentOrNull -import kotlinx.serialization.json.decodeFromJsonElement -import kotlinx.serialization.json.jsonPrimitive -import java.util.UUID -import java.util.concurrent.ConcurrentHashMap - -private val logger = KotlinLogging.logger {} - -class OldSchemaKotlinServerForTsClient { - private val serverTransports = ConcurrentHashMap() - private val jsonFormat = Json { ignoreUnknownKeys = true } - private var server: EmbeddedServer<*, *>? = null - - fun start(port: Int = 3000) { - logger.info { "Starting HTTP server on port $port" } - - server = embeddedServer(CIO, port = port) { - routing { - get("/mcp") { - val sessionId = call.request.header("mcp-session-id") - if (sessionId == null) { - call.respond(HttpStatusCode.BadRequest, "Missing mcp-session-id header") - return@get - } - val transport = serverTransports[sessionId] - if (transport == null) { - call.respond(HttpStatusCode.BadRequest, "Invalid mcp-session-id") - return@get - } - transport.stream(call) - } - - post("/mcp") { - val sessionId = call.request.header("mcp-session-id") - val requestBody = call.receiveText() - - logger.debug { "Received request with sessionId: $sessionId" } - logger.trace { "Request body: $requestBody" } - - val jsonElement = try { - jsonFormat.parseToJsonElement(requestBody) - } catch (e: Exception) { - logger.error(e) { "Failed to parse request body as JSON" } - call.respond( - HttpStatusCode.BadRequest, - jsonFormat.encodeToString( - JsonObject.serializer(), - JsonObject( - mapOf( - "jsonrpc" to JsonPrimitive("2.0"), - "error" to JsonObject( - mapOf( - "code" to JsonPrimitive(-32700), - "message" to JsonPrimitive("Parse error: ${e.message}"), - ), - ), - "id" to JsonNull, - ), - ), - ), - ) - return@post - } - - if (sessionId != null && serverTransports.containsKey(sessionId)) { - logger.debug { "Using existing transport for session: $sessionId" } - val transport = serverTransports[sessionId]!! - transport.handleRequest(call, jsonElement) - } else { - if (isInitializeRequest(jsonElement)) { - val newSessionId = UUID.randomUUID().toString() - logger.info { "Creating new session with ID: $newSessionId" } - - val transport = OldSchemaHttpServerTransport(newSessionId) - - serverTransports[newSessionId] = transport - - val mcpServer = createMcpServer() - - call.response.header("mcp-session-id", newSessionId) - - val serverThread = Thread { - runBlocking { - mcpServer.createSession(transport) - } - } - serverThread.start() - - Thread.sleep(500) - - transport.handleRequest(call, jsonElement) - } else { - logger.warn { "Invalid request: no session ID or not an initialization request" } - call.respond( - HttpStatusCode.BadRequest, - jsonFormat.encodeToString( - JsonObject.serializer(), - JsonObject( - mapOf( - "jsonrpc" to JsonPrimitive("2.0"), - "error" to JsonObject( - mapOf( - "code" to JsonPrimitive(-32000), - "message" to - JsonPrimitive("Bad Request: No valid session ID provided"), - ), - ), - "id" to JsonNull, - ), - ), - ), - ) - } - } - } - - delete("/mcp") { - val sessionId = call.request.header("mcp-session-id") - if (sessionId != null && serverTransports.containsKey(sessionId)) { - logger.info { "Terminating session: $sessionId" } - val transport = serverTransports[sessionId]!! - serverTransports.remove(sessionId) - runBlocking { - transport.close() - } - call.respond(HttpStatusCode.OK) - } else { - logger.warn { "Invalid session termination request: $sessionId" } - call.respond(HttpStatusCode.BadRequest, "Invalid or missing session ID") - } - } - } - } - - server?.start(wait = false) - } - - fun stop() { - logger.info { "Stopping HTTP server" } - server?.stop(500, 1000) - server = null - } - - fun createMcpServer(): Server { - val server = Server( - Implementation( - name = "kotlin-http-server", - version = "1.0.0", - ), - ServerOptions( - capabilities = ServerCapabilities( - prompts = ServerCapabilities.Prompts(listChanged = true), - resources = ServerCapabilities.Resources(subscribe = true, listChanged = true), - tools = ServerCapabilities.Tools(listChanged = true), - ), - ), - ) - - server.addTool( - name = "greet", - description = "A simple greeting tool", - inputSchema = Tool.Input( - properties = buildJsonObject { - put( - "name", - buildJsonObject { - put("type", JsonPrimitive("string")) - put("description", JsonPrimitive("Name to greet")) - }, - ) - }, - required = listOf("name"), - ), - ) { request -> - val name = (request.params.arguments?.get("name") as? JsonPrimitive)?.content ?: "World" - CallToolResult( - content = listOf(TextContent("Hello, $name!")), - structuredContent = buildJsonObject { - put("greeting", JsonPrimitive("Hello, $name!")) - }, - ) - } - - server.addTool( - name = "multi-greet", - description = "A greeting tool that sends multiple notifications", - inputSchema = Tool.Input( - properties = buildJsonObject { - put( - "name", - buildJsonObject { - put("type", JsonPrimitive("string")) - put("description", JsonPrimitive("Name to greet")) - }, - ) - }, - required = listOf("name"), - ), - ) { request -> - val name = (request.params.arguments?.get("name") as? JsonPrimitive)?.content ?: "World" - - CallToolResult( - content = listOf(TextContent("Multiple greetings sent to $name!")), - structuredContent = buildJsonObject { - put("greeting", JsonPrimitive("Multiple greetings sent to $name!")) - put("notificationCount", JsonPrimitive(3)) - }, - ) - } - - server.addPrompt( - name = "greeting-template", - description = "A simple greeting prompt template", - arguments = listOf( - PromptArgument( - name = "name", - description = "Name to include in greeting", - required = true, - ), - ), - ) { request -> - GetPromptResult( - description = "Greeting for ${request.params.name}", - messages = listOf( - PromptMessage( - role = Role.user, - content = TextContent( - "Please greet ${request.params.arguments?.get("name") ?: "someone"} in a friendly manner.", - ), - ), - ), - ) - } - - server.addResource( - uri = "https://example.com/greetings/default", - name = "Default Greeting", - description = "A simple greeting resource", - mimeType = "text/plain", - ) { request -> - ReadResourceResult( - contents = listOf( - TextResourceContents("Hello, world!", request.params.uri, "text/plain"), - ), - ) - } - - return server - } - - private fun isInitializeRequest(json: JsonElement): Boolean { - if (json !is JsonObject) return false - - val method = json["method"]?.jsonPrimitive?.contentOrNull - return method == "initialize" - } -} - -class OldSchemaHttpServerTransport(private val sessionId: String) : AbstractTransport() { - private val logger = KotlinLogging.logger {} - private val pendingResponses = ConcurrentHashMap>() - private val messageQueue = Channel(Channel.UNLIMITED) - - suspend fun stream(call: ApplicationCall) { - logger.debug { "Starting SSE stream for session: $sessionId" } - call.response.header("Cache-Control", "no-cache") - call.response.header("Connection", "keep-alive") - call.respondTextWriter(ContentType.Text.EventStream) { - try { - while (true) { - val result = messageQueue.receiveCatching() - val msg = result.getOrNull() ?: break - val json = McpJson.encodeToString(msg) - write("event: message\n") - write("data: ") - write(json) - write("\n\n") - flush() - } - } catch (e: Exception) { - logger.warn(e) { "SSE stream terminated for session: $sessionId" } - } finally { - logger.debug { "SSE stream closed for session: $sessionId" } - } - } - } - - suspend fun handleRequest(call: ApplicationCall, requestBody: JsonElement) { - try { - logger.info { "Handling request body: $requestBody" } - val message = McpJson.decodeFromJsonElement(requestBody) - logger.info { "Decoded message: $message" } - - if (message is JSONRPCRequest) { - val id = message.id.toString() - logger.info { "Received request with ID: $id, method: ${message.method}" } - val responseDeferred = CompletableDeferred() - pendingResponses[id] = responseDeferred - logger.info { "Created deferred response for ID: $id" } - - logger.info { "Invoking onMessage handler" } - _onMessage.invoke(message) - logger.info { "onMessage handler completed" } - - try { - val response = withTimeoutOrNull(10000) { - responseDeferred.await() - } - - if (response != null) { - val jsonResponse = McpJson.encodeToString(response) - call.respondText(jsonResponse, ContentType.Application.Json) - } else { - logger.warn { "Timeout waiting for response to request ID: $id" } - call.respondText( - McpJson.encodeToString( - JSONRPCError( - id = message.id, - error = RPCError( - code = ErrorCode.Defined.RequestTimeout, - message = "Request timed out", - ), - ), - ), - ContentType.Application.Json, - ) - } - } catch (_: CancellationException) { - logger.warn { "Request cancelled for ID: $id" } - pendingResponses.remove(id) - if (!call.response.isCommitted) { - call.respondText( - McpJson.encodeToString( - JSONRPCError( - id = message.id, - error = RPCError( - code = ErrorCode.Defined.ConnectionClosed, - message = "Request cancelled", - ), - ), - ), - ContentType.Application.Json, - HttpStatusCode.ServiceUnavailable, - ) - } - } - } else { - call.respondText("", ContentType.Application.Json, HttpStatusCode.Accepted) - } - } catch (e: Exception) { - logger.error(e) { "Error handling request" } - if (!call.response.isCommitted) { - try { - val errorResponse = JSONRPCError( - id = RequestId.NumberId(0), - error = RPCError( - code = ErrorCode.Defined.InternalError, - message = "Internal server error: ${e.message}", - ), - ) - - call.respondText( - McpJson.encodeToString(errorResponse), - ContentType.Application.Json, - HttpStatusCode.InternalServerError, - ) - } catch (responseEx: Exception) { - logger.error(responseEx) { "Failed to send error response" } - } - } - } - } - - override suspend fun start() { - logger.debug { "Starting HTTP server transport for session: $sessionId" } - } - - override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { - logger.info { "Sending message: $message" } - - if (message is JSONRPCResponse) { - val id = message.id.toString() - logger.info { "Sending response for request ID: $id" } - val deferred = pendingResponses.remove(id) - if (deferred != null) { - logger.info { "Found pending response for ID: $id, completing deferred" } - deferred.complete(message) - return - } else { - logger.warn { "No pending response found for ID: $id" } - } - } else if (message is JSONRPCRequest) { - logger.info { "Sending request with ID: ${message.id}" } - } else if (message is JSONRPCNotification) { - logger.info { "Sending notification: ${message.method}" } - } - - logger.info { "Queueing message for next client request" } - messageQueue.send(message) - } - - override suspend fun close() { - logger.debug { "Closing HTTP server transport for session: $sessionId" } - messageQueue.close() - _onClose.invoke() - } -} - -fun main() { - val server = OldSchemaKotlinServerForTsClient() - server.start() -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerEdgeCasesTestStdio.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerEdgeCasesTestStdio.kt deleted file mode 100644 index 0ed8676..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerEdgeCasesTestStdio.kt +++ /dev/null @@ -1,177 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.typescript.stdio - -import io.modelcontextprotocol.kotlin.sdk.TextContent -import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TsTestBase -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.runBlocking -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.Timeout -import java.util.concurrent.TimeUnit -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertTrue - -class OldSchemaKotlinClientTsServerEdgeCasesTestStdio : TsTestBase() { - - override val transportKind = TransportKind.STDIO - - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun testNonExistentToolOverStdio(): Unit = runBlocking(Dispatchers.IO) { - withClientStdio { client: Client, _ -> - val nonExistentToolName = "non-existent-tool" - val arguments = mapOf("name" to "TestUser") - - val result = client.callTool(nonExistentToolName, arguments) - assertNotNull(result, "Tool call result should not be null") - - val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult - assertTrue(callResult.isError ?: false, "isError should be true for non-existent tool") - - val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Error content should be present in the result") - - val errorText = textContent.text ?: "" - assertTrue( - errorText.contains("non-existent-tool") && errorText.contains("not found"), - "Error message should indicate the tool was not found", - ) - } - } - - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun testSpecialCharactersInArgumentsOverStdio(): Unit = runBlocking(Dispatchers.IO) { - withClientStdio { client: Client, _ -> - val specialChars = "!@#$%^&*()_+{}[]|\\:;\"'<>.,?/" - val arguments = mapOf("name" to specialChars) - - val result = client.callTool("greet", arguments) - assertNotNull(result, "Tool call result should not be null") - - val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult - val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Text content should be present in the result") - - val text = textContent.text ?: "" - assertTrue( - text.contains(specialChars), - "Tool response should contain the special characters", - ) - } - } - - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun testLargePayloadOverStdio(): Unit = runBlocking(Dispatchers.IO) { - withClientStdio { client: Client, _ -> - val largeName = "A".repeat(10 * 1024) - val arguments = mapOf("name" to largeName) - - val result = client.callTool("greet", arguments) - assertNotNull(result, "Tool call result should not be null") - - val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult - val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Text content should be present in the result") - - val text = textContent.text ?: "" - assertTrue( - text.contains("Hello,") && text.contains("A"), - "Tool response should contain the greeting with the large name", - ) - } - } - - @Test - @Timeout(60, unit = TimeUnit.SECONDS) - fun testConcurrentRequestsOverStdio(): Unit = runBlocking(Dispatchers.IO) { - withClientStdio { client: Client, _ -> - val concurrentCount = 5 - val responses = coroutineScope { - val results = (1..concurrentCount).map { i -> - async { - val name = "ConcurrentClient$i" - val arguments = mapOf("name" to name) - - val result = client.callTool("greet", arguments) - assertNotNull(result, "Tool call result should not be null for client $i") - - val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult - val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Text content should be present for client $i") - - textContent.text ?: "" - } - } - results.awaitAll() - } - - for (i in 1..concurrentCount) { - val expectedName = "ConcurrentClient$i" - val matchingResponses = responses.filter { it.contains("Hello, $expectedName!") } - assertEquals( - 1, - matchingResponses.size, - "Should have exactly one response for $expectedName", - ) - } - } - } - - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun testInvalidArgumentsOverStdio(): Unit = runBlocking(Dispatchers.IO) { - withClientStdio { client: Client, _ -> - val invalidArguments = mapOf( - "name" to JsonObject(mapOf("nested" to JsonPrimitive("value"))), - ) - - val result = client.callTool("greet", invalidArguments) - assertNotNull(result, "Tool call result should not be null") - - val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult - assertTrue(callResult.isError ?: false, "isError should be true for invalid arguments") - - val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Error content should be present in the result") - - val errorText = textContent.text ?: "" - assertTrue( - errorText.contains("Invalid arguments") && errorText.contains("greet"), - "Error message should indicate invalid arguments for tool greet", - ) - } - } - - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun testMultipleToolCallsOverStdio(): Unit = runBlocking(Dispatchers.IO) { - withClientStdio { client: Client, _ -> - repeat(10) { i -> - val name = "SequentialClient$i" - val arguments = mapOf("name" to name) - - val result = client.callTool("greet", arguments) - assertNotNull(result, "Tool call result should not be null for call $i") - - val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult - val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Text content should be present for call $i") - - assertEquals( - "Hello, $name!", - textContent.text, - "Tool response should contain the greeting with the provided name", - ) - } - } - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerTestStdio.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerTestStdio.kt deleted file mode 100644 index e87a5c8..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerTestStdio.kt +++ /dev/null @@ -1,23 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.typescript.stdio - -import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.OldSchemaAbstractKotlinClientTsServerTest -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind - -class OldSchemaKotlinClientTsServerTestStdio : OldSchemaAbstractKotlinClientTsServerTest() { - - override val transportKind = TransportKind.STDIO - - override suspend fun useClient(block: suspend (Client) -> T): T = withClientStdio { client, proc -> - try { - block(client) - } finally { - try { - client.close() - } catch (_: Exception) {} - try { - stopProcess(proc, name = "TypeScript stdio server") - } catch (_: Exception) {} - } - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaAbstractServerFeaturesTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaAbstractServerFeaturesTest.kt deleted file mode 100644 index d74c0b0..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaAbstractServerFeaturesTest.kt +++ /dev/null @@ -1,43 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.server - -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import org.junit.jupiter.api.BeforeEach - -abstract class OldSchemaAbstractServerFeaturesTest { - - protected lateinit var server: Server - protected lateinit var client: Client - - abstract fun getServerCapabilities(): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities - - protected open fun getServerInstructionsProvider(): (() -> String)? = null - - @BeforeEach - fun setUp() { - val serverOptions = ServerOptions( - capabilities = getServerCapabilities(), - ) - - server = Server( - serverInfo = Implementation(name = "test server", version = "1.0"), - options = serverOptions, - instructionsProvider = getServerInstructionsProvider(), - ) - - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - - client = Client( - clientInfo = Implementation(name = "test client", version = "1.0"), - ) - - runBlocking { - // Connect client and server - launch { client.connect(clientTransport) } - launch { server.createSession(serverTransport) } - } - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerInstructionsTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerInstructionsTest.kt deleted file mode 100644 index 26bb7bf..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerInstructionsTest.kt +++ /dev/null @@ -1,68 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.server - -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities -import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertNull -import kotlin.test.assertEquals - -class OldSchemaServerInstructionsTest { - - @Test - fun `Server constructor should accept instructions provider parameter`() = runTest { - val serverInfo = Implementation(name = "test server", version = "1.0") - val serverOptions = ServerOptions(capabilities = ServerCapabilities()) - val instructions = "This is a test server. Use it for testing purposes only." - - val server = Server(serverInfo, serverOptions, { instructions }) - - // 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) - - assertEquals(instructions, client.serverInstructions) - } - - @Test - fun `Server constructor should accept instructions parameter`() = runTest { - val serverInfo = Implementation(name = "test server", version = "1.0") - val serverOptions = ServerOptions(capabilities = ServerCapabilities()) - val instructions = "This is a test server. Use it for testing purposes only." - - val server = Server(serverInfo, serverOptions, instructions) - - // 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) - - assertEquals(instructions, client.serverInstructions) - } - - @Test - fun `Server constructor should work without instructions parameter`() = runTest { - val serverInfo = Implementation(name = "test server", version = "1.0") - val serverOptions = ServerOptions(capabilities = ServerCapabilities()) - - // Test that server works when instructions parameter is omitted (defaults to null) - 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) - - assertNull(client.serverInstructions) - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerPromptsTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerPromptsTest.kt deleted file mode 100644 index 1721bec..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerPromptsTest.kt +++ /dev/null @@ -1,213 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.server - -import io.kotest.assertions.throwables.shouldThrow -import io.kotest.matchers.collections.shouldBeEmpty -import io.kotest.matchers.collections.shouldContain -import io.kotest.matchers.collections.shouldContainExactly -import io.kotest.matchers.collections.shouldHaveSize -import io.kotest.matchers.nulls.shouldNotBeNull -import io.kotest.matchers.shouldBe -import io.kotest.matchers.throwable.shouldHaveMessage -import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject -import io.modelcontextprotocol.kotlin.sdk.GetPromptRequest -import io.modelcontextprotocol.kotlin.sdk.GetPromptResult -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.Method -import io.modelcontextprotocol.kotlin.sdk.Prompt -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities -import io.modelcontextprotocol.kotlin.sdk.types.McpException -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -class OldSchemaServerPromptsTest : OldSchemaAbstractServerFeaturesTest() { - - override fun getServerCapabilities(): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities = - ServerCapabilities( - prompts = ServerCapabilities.Prompts(false), - ) - - @Test - fun `Should list no prompts by default`() = runTest { - client.listPrompts() shouldNotBeNull { - prompts.shouldBeEmpty() - } - } - - @Test - fun `Should add a prompt`() = runTest { - // Add a prompt - val testPrompt = Prompt( - name = "test-prompt-with-custom-handler", - description = "Test Prompt", - arguments = null, - ) - val expectedPromptResult = GetPromptResult( - description = "Test prompt description", - messages = listOf(), - ) - - server.addPrompt(testPrompt) { - expectedPromptResult - } - - client.getPrompt( - GetPromptRequest( - name = "test-prompt-with-custom-handler", - arguments = null, - ), - ) shouldBe expectedPromptResult - - client.listPrompts() shouldNotBeNull { - prompts shouldContainExactly listOf(testPrompt) - nextCursor shouldBe null - _meta shouldBe EmptyJsonObject - } - } - - @Test - fun `Should remove a prompt`() = runTest { - // given - val testPrompt = Prompt( - name = "test-prompt-to-remove", - description = "Test Prompt", - arguments = null, - ) - server.addPrompt(testPrompt) { - GetPromptResult( - description = "Test prompt description", - messages = listOf(), - ) - } - - client.listPrompts() shouldNotBeNull { - prompts shouldContain testPrompt - } - - // when - val result = server.removePrompt(testPrompt.name) - - // then - assertTrue(result, "Prompt should be removed successfully") - val mcpException = shouldThrow { - client.getPrompt( - GetPromptRequest( - name = testPrompt.name, - arguments = null, - ), - ) - } - mcpException shouldHaveMessage "MCP error -32603: Prompt not found: ${testPrompt.name}" - - client.listPrompts() shouldNotBeNull { - prompts.firstOrNull { it.name == testPrompt.name } shouldBe null - } - } - - @Test - fun `Should remove multiple prompts and send notification`() = runTest { - // Add prompts - val testPrompt1 = Prompt("test-prompt-1", "Test Prompt 1", null) - val testPrompt2 = Prompt("test-prompt-2", "Test Prompt 2", null) - server.addPrompt(testPrompt1) { - GetPromptResult( - description = "Test prompt description 1", - messages = listOf(), - ) - } - server.addPrompt(testPrompt2) { - GetPromptResult( - description = "Test prompt description 2", - messages = listOf(), - ) - } - - client.listPrompts() shouldNotBeNull { - prompts shouldHaveSize 2 - } - // Remove the prompts - val result = server.removePrompts(listOf(testPrompt1.name, testPrompt2.name)) - - // Verify the prompts were removed - assertEquals(2, result, "Both prompts should be removed") - client.listPrompts() shouldNotBeNull { - prompts.shouldBeEmpty() - } - } - - @Test - fun `removePrompt should return false when prompt does not exist`() = runTest { - // Track notifications - var promptListChangedNotificationReceived = false - client.setNotificationHandler( - Method.Defined.NotificationsPromptsListChanged, - ) { - promptListChangedNotificationReceived = true - CompletableDeferred(Unit) - } - - // Try to remove a non-existent prompt - val result = server.removePrompt("non-existent-prompt") - - // Verify the result - assertFalse(result, "Removing non-existent prompt should return false") - assertFalse(promptListChangedNotificationReceived, "No notification should be sent when prompt doesn't exist") - } - - @Nested - inner class NoPromptsCapabilitiesTests { - // Create server without prompts capability - val serverWithoutPrompts = Server( - Implementation(name = "test server", version = "1.0"), - ServerOptions( - capabilities = ServerCapabilities(), - ), - ) - - @Test - fun `RemovePrompt should throw when prompts capability is not supported`() = runTest { - // Verify that removing a prompt throws an exception - val exception = assertThrows { - serverWithoutPrompts.removePrompt("test-prompt") - } - assertEquals("Server does not support prompts capability.", exception.message) - } - - @Test - fun `Remove Prompts should throw when prompts capability is not supported`() = runTest { - // Verify that removing a prompt throws an exception - val exception = assertThrows { - serverWithoutPrompts.removePrompts(emptyList()) - } - assertEquals("Server does not support prompts capability.", exception.message) - } - - @Test - fun `Add Prompt should throw when prompts capability is not supported`() = runTest { - // Verify that removing a prompt throws an exception - val exception = assertThrows { - serverWithoutPrompts.addPrompt(name = "test-prompt") { - GetPromptResult( - description = "Test prompt description", - messages = listOf(), - ) - } - } - assertEquals("Server does not support prompts capability.", exception.message) - } - - @Test - fun `Add Prompts should throw when prompts capability is not supported`() = runTest { - // Verify that removing a prompt throws an exception - val exception = assertThrows { - serverWithoutPrompts.addPrompts(emptyList()) - } - assertEquals("Server does not support prompts capability.", exception.message) - } - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerResourcesTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerResourcesTest.kt deleted file mode 100644 index 90cb9a0..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerResourcesTest.kt +++ /dev/null @@ -1,135 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.server - -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.Method -import io.modelcontextprotocol.kotlin.sdk.ReadResourceResult -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities -import io.modelcontextprotocol.kotlin.sdk.TextResourceContents -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -class OldSchemaServerResourcesTest : OldSchemaAbstractServerFeaturesTest() { - - override fun getServerCapabilities(): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities = - ServerCapabilities( - resources = ServerCapabilities.Resources(null, null), - ) - - @Test - fun `removeResource should remove a resource and send notification`() = runTest { - // Add a resource - val testResourceUri = "test://resource" - server.addResource( - uri = testResourceUri, - name = "Test Resource", - description = "A test resource", - mimeType = "text/plain", - ) { - ReadResourceResult( - contents = listOf( - TextResourceContents( - text = "Test resource content", - uri = testResourceUri, - mimeType = "text/plain", - ), - ), - ) - } - - // Remove the resource - val result = server.removeResource(testResourceUri) - - // Verify the resource was removed - assertTrue(result, "Resource should be removed successfully") - } - - @Test - fun `removeResources should remove multiple resources and send notification`() = runTest { - // Add resources - val testResourceUri1 = "test://resource1" - val testResourceUri2 = "test://resource2" - server.addResource( - uri = testResourceUri1, - name = "Test Resource 1", - description = "A test resource 1", - mimeType = "text/plain", - ) { - ReadResourceResult( - contents = listOf( - TextResourceContents( - text = "Test resource content 1", - uri = testResourceUri1, - mimeType = "text/plain", - ), - ), - ) - } - server.addResource( - uri = testResourceUri2, - name = "Test Resource 2", - description = "A test resource 2", - mimeType = "text/plain", - ) { - ReadResourceResult( - contents = listOf( - TextResourceContents( - text = "Test resource content 2", - uri = testResourceUri2, - mimeType = "text/plain", - ), - ), - ) - } - - // Remove the resources - val result = server.removeResources(listOf(testResourceUri1, testResourceUri2)) - - // Verify the resources were removed - assertEquals(2, result, "Both resources should be removed") - } - - @Test - fun `removeResource should return false when resource does not exist`() = runTest { - // Track notifications - var resourceListChangedNotificationReceived = false - client.setNotificationHandler( - Method.Defined.NotificationsResourcesListChanged, - ) { - resourceListChangedNotificationReceived = true - CompletableDeferred(Unit) - } - - // Try to remove a non-existent resource - val result = server.removeResource("non-existent-resource") - - // Verify the result - assertFalse(result, "Removing non-existent resource should return false") - assertFalse( - resourceListChangedNotificationReceived, - "No notification should be sent when resource doesn't exist", - ) - } - - @Test - fun `removeResource should throw when resources capability is not supported`() = runTest { - // Create server without resources capability - val serverOptions = ServerOptions( - capabilities = ServerCapabilities(), - ) - val server = Server( - Implementation(name = "test server", version = "1.0"), - serverOptions, - ) - - // Verify that removing a resource throws an exception - val exception = assertThrows { - server.removeResource("test://resource") - } - assertEquals("Server does not support resources capability.", exception.message) - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerToolsTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerToolsTest.kt deleted file mode 100644 index b956e2c..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerToolsTest.kt +++ /dev/null @@ -1,91 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.server - -import io.modelcontextprotocol.kotlin.sdk.CallToolResult -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.Method -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities -import io.modelcontextprotocol.kotlin.sdk.TextContent -import io.modelcontextprotocol.kotlin.sdk.Tool -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -class OldSchemaServerToolsTest : OldSchemaAbstractServerFeaturesTest() { - - override fun getServerCapabilities(): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities = - ServerCapabilities( - tools = ServerCapabilities.Tools(null), - ) - - @Test - fun `removeTool should remove a tool`() = runTest { - // Add a tool - server.addTool("test-tool", "Test Tool", Tool.Input()) { - CallToolResult(listOf(TextContent("Test result"))) - } - - // Remove the tool - val result = server.removeTool("test-tool") - - // Verify the tool was removed - assertTrue(result, "Tool should be removed successfully") - } - - @Test - fun `removeTool should return false when tool does not exist`() = runTest { - // Track notifications - var toolListChangedNotificationReceived = false - client.setNotificationHandler( - Method.Defined.NotificationsToolsListChanged, - ) { - toolListChangedNotificationReceived = true - CompletableDeferred(Unit) - } - - // Try to remove a non-existent tool - val result = server.removeTool("non-existent-tool") - - // Verify the result - assertFalse(result, "Removing non-existent tool should return false") - assertFalse(toolListChangedNotificationReceived, "No notification should be sent when tool doesn't exist") - } - - @Test - fun `removeTool should throw when tools capability is not supported`() = runTest { - // Create server without tools capability - val serverOptions = ServerOptions( - capabilities = ServerCapabilities(), - ) - val server = Server( - Implementation(name = "test server", version = "1.0"), - serverOptions, - ) - - // Verify that removing a tool throws an exception - val exception = assertThrows { - server.removeTool("test-tool") - } - assertEquals("Server does not support tools capability.", exception.message) - } - - @Test - fun `removeTools should remove multiple tools`() = runTest { - // Add tools - server.addTool("test-tool-1", "Test Tool 1") { - CallToolResult(listOf(TextContent("Test result 1"))) - } - server.addTool("test-tool-2", "Test Tool 2") { - CallToolResult(listOf(TextContent("Test result 2"))) - } - - // Remove the tools - val result = server.removeTools(listOf("test-tool-1", "test-tool-2")) - - // Verify the tools were removed - assertEquals(2, result, "Both tools should be removed") - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerInstructionsTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerInstructionsTest.kt index 53d0fd1..c289b53 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerInstructionsTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerInstructionsTest.kt @@ -1,12 +1,12 @@ 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.ClientOptions import io.modelcontextprotocol.kotlin.sdk.client.mcpClient import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertNull diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt index 0f1353b..4ffb970 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt @@ -34,9 +34,6 @@ import kotlin.concurrent.atomics.AtomicBoolean import kotlin.concurrent.atomics.ExperimentalAtomicApi import kotlin.time.Duration -@Deprecated("Use SseClientTransport instead", ReplaceWith("SseClientTransport"), DeprecationLevel.ERROR) -public typealias SSEClientTransport = SseClientTransport - /** * Client transport for SSE: this will connect to a server using Server-Sent Events for receiving * messages and make separate POST requests for sending messages. diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaClientMetaParameterTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaClientMetaParameterTest.kt deleted file mode 100644 index b541176..0000000 --- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaClientMetaParameterTest.kt +++ /dev/null @@ -1,275 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.client - -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.JSONRPCRequest -import kotlinx.coroutines.test.runTest -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.boolean -import kotlinx.serialization.json.int -import kotlinx.serialization.json.jsonPrimitive -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertContains -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertTrue - -/** - * Comprehensive test suite for MCP Client meta parameter functionality - * - * Tests cover: - * - Meta key validation according to MCP specification - * - JSON type conversion for various data types - * - Error handling for invalid meta keys - * - Integration with callTool method - */ -class OldSchemaClientMetaParameterTest { - - private lateinit var client: Client - private lateinit var mockTransport: OldSchemaMockTransport - private val clientInfo = Implementation("test-client", "1.0.0") - - @BeforeTest - fun setup() = runTest { - mockTransport = OldSchemaMockTransport() - client = Client(clientInfo = clientInfo) - mockTransport.setupInitializationResponse() - client.connect(mockTransport) - } - - @Test - fun `should accept valid meta keys without throwing exception`() = runTest { - val validMeta = buildMap { - put("simple-key", "value1") - put("api.example.com/version", "1.0") - put("com.company.app/setting", "enabled") - put("retry_count", 3) - put("user.preference", true) - put("valid123", "alphanumeric") - put("multi.dot.name", "multiple-dots") - put("under_score", "underscore") - put("hyphen-dash", "hyphen") - put("org.apache.kafka/consumer-config", "complex-valid-prefix") - } - - val result = runCatching { - client.callTool("test-tool", mapOf("arg" to "value"), validMeta) - } - - assertTrue(result.isSuccess, "Valid meta keys should not cause exceptions") - mockTransport.lastJsonRpcRequest()?.let { request -> - val params = request.params as JsonObject - assertTrue(params.containsKey("_meta"), "Request should contain _meta field") - val metaField = params["_meta"] as JsonObject - - // Verify all meta keys are present - assertEquals(validMeta.size, metaField.size, "All meta keys should be included") - - // Verify specific key-value pairs - assertEquals("value1", metaField["simple-key"]?.jsonPrimitive?.content) - assertEquals("1.0", metaField["api.example.com/version"]?.jsonPrimitive?.content) - assertEquals("enabled", metaField["com.company.app/setting"]?.jsonPrimitive?.content) - assertEquals(3, metaField["retry_count"]?.jsonPrimitive?.int) - assertEquals(true, metaField["user.preference"]?.jsonPrimitive?.boolean) - assertEquals("alphanumeric", metaField["valid123"]?.jsonPrimitive?.content) - assertEquals("multiple-dots", metaField["multi.dot.name"]?.jsonPrimitive?.content) - assertEquals("underscore", metaField["under_score"]?.jsonPrimitive?.content) - assertEquals("hyphen", metaField["hyphen-dash"]?.jsonPrimitive?.content) - assertEquals("complex-valid-prefix", metaField["org.apache.kafka/consumer-config"]?.jsonPrimitive?.content) - } - } - - @Test - fun `should accept edge case valid prefixes and names`() = runTest { - val edgeCaseValidMeta = buildMap { - put("a/", "single-char-prefix-empty-name") // empty name is allowed - put("a1-b2/test", "alphanumeric-hyphen-prefix") - put("long.domain.name.here/config", "long-prefix") - put("x/a", "minimal-valid-key") - put("test123", "alphanumeric-name-only") - } - - val result = runCatching { - client.callTool("test-tool", emptyMap(), edgeCaseValidMeta) - } - - assertTrue(result.isSuccess, "Edge case valid meta keys should be accepted") - } - - @Test - fun `should reject mcp reserved prefix`() = runTest { - val invalidMeta = mapOf("mcp/internal" to "value") - - val exception = assertFailsWith { - client.callTool("test-tool", emptyMap(), invalidMeta) - } - - assertContains( - charSequence = exception.message ?: "", - other = "Invalid _meta key", - ) - } - - @Test - fun `should reject modelcontextprotocol reserved prefix`() = runTest { - val invalidMeta = mapOf("modelcontextprotocol/config" to "value") - - val exception = assertFailsWith { - client.callTool("test-tool", emptyMap(), invalidMeta) - } - - assertContains( - charSequence = exception.message ?: "", - other = "Invalid _meta key", - ) - } - - @Test - fun `should reject nested reserved prefixes`() = runTest { - val invalidKeys = listOf( - "api.mcp.io/setting", - "com.modelcontextprotocol.test/value", - "example.mcp/data", - "subdomain.mcp.com/config", - "app.modelcontextprotocol.dev/setting", - "test.mcp/value", - "service.modelcontextprotocol/data", - ) - - invalidKeys.forEach { key -> - val exception = assertFailsWith( - message = "Should reject nested reserved key: $key", - ) { - client.callTool("test-tool", emptyMap(), mapOf(key to "value")) - } - assertContains( - charSequence = exception.message ?: "", - other = "Invalid _meta key", - ) - } - } - - @Test - fun `should reject case-insensitive reserved prefixes`() = runTest { - val invalidKeys = listOf( - "MCP/internal", - "Mcp/config", - "mCp/setting", - "MODELCONTEXTPROTOCOL/data", - "ModelContextProtocol/value", - "modelContextProtocol/test", - ) - - invalidKeys.forEach { key -> - val exception = assertFailsWith( - message = "Should reject case-insensitive reserved key: $key", - ) { - client.callTool("test-tool", emptyMap(), mapOf(key to "value")) - } - assertContains( - charSequence = exception.message ?: "", - other = "Invalid _meta key", - ) - } - } - - @Test - fun `should reject invalid key formats`() = runTest { - val invalidKeys = listOf( - "", // empty key - not allowed at key level - "/invalid", // starts with slash - "-invalid", // starts with hyphen - ".invalid", // starts with dot - "in valid", // contains space - "api../test", // consecutive dots - "api./test", // label ends with dot - ) - - invalidKeys.forEach { key -> - assertFailsWith( - message = "Should reject invalid key format: '$key'", - ) { - client.callTool("test-tool", emptyMap(), mapOf(key to "value")) - } - } - } - - @Test - fun `should convert various data types to JSON correctly`() = runTest { - val complexMeta = createComplexMetaData() - - val result = runCatching { - client.callTool( - "test-tool", - emptyMap(), - complexMeta, - ) - } - - assertTrue(result.isSuccess, "Complex data type conversion should not throw exceptions") - - mockTransport.lastJsonRpcRequest()?.let { request -> - assertEquals("tools/call", request.method) - val params = request.params as JsonObject - assertTrue(params.containsKey("_meta"), "Request should contain _meta field") - } - } - - @Test - fun `should handle nested map structures correctly`() = runTest { - val nestedMeta = buildNestedConfiguration() - - val result = runCatching { - client.callTool("test-tool", emptyMap(), nestedMeta) - } - - assertTrue(result.isSuccess) - - mockTransport.lastJsonRpcRequest()?.let { request -> - val params = request.params as JsonObject - val metaField = params["_meta"] as JsonObject - assertTrue(metaField.containsKey("config")) - } - } - - @Test - fun `should include empty meta object when meta parameter not provided`() = runTest { - client.callTool("test-tool", mapOf("arg" to "value")) - - mockTransport.lastJsonRpcRequest()?.let { request -> - val params = request.params as JsonObject - val metaField = params["_meta"] as JsonObject - assertTrue(metaField.isEmpty(), "Meta field should be empty when not provided") - } - } - - private fun createComplexMetaData(): Map = buildMap { - put("string", "text") - put("number", 42) - put("boolean", true) - put("null_value", null) - put("list", listOf(1, 2, 3)) - put("map", mapOf("nested" to "value")) - put("enum", "STRING") - put("int_array", intArrayOf(1, 2, 3)) - } - - private fun buildNestedConfiguration(): Map = buildMap { - put( - "config", - buildMap { - put( - "database", - buildMap { - put("host", "localhost") - put("port", 5432) - }, - ) - put("features", listOf("feature1", "feature2")) - }, - ) - } -} - -suspend fun OldSchemaMockTransport.lastJsonRpcRequest(): JSONRPCRequest? = - getSentMessages().lastOrNull() as? JSONRPCRequest diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaMockTransport.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaMockTransport.kt deleted file mode 100644 index 1a0db70..0000000 --- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaMockTransport.kt +++ /dev/null @@ -1,95 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.client - -import io.modelcontextprotocol.kotlin.sdk.CallToolResult -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.InitializeResult -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage -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 - -class OldSchemaMockTransport : Transport { - private val _sentMessages = mutableListOf() - private val _receivedMessages = mutableListOf() - private val mutex = Mutex() - - suspend fun getSentMessages() = mutex.withLock { _sentMessages.toList() } - suspend fun getReceivedMessages() = mutex.withLock { _receivedMessages.toList() } - - private var onMessageBlock: (suspend (JSONRPCMessage) -> Unit)? = null - private var onCloseBlock: (() -> Unit)? = null - private var onErrorBlock: ((Throwable) -> Unit)? = null - - override suspend fun start() = Unit - - override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { - mutex.withLock { - _sentMessages += message - } - - // Auto-respond to initialization and tool calls - when (message) { - is JSONRPCRequest -> { - when (message.method) { - "initialize" -> { - val initResponse = JSONRPCResponse( - id = message.id, - result = InitializeResult( - protocolVersion = "2024-11-05", - capabilities = ServerCapabilities( - tools = ServerCapabilities.Tools(listChanged = null), - ), - serverInfo = Implementation("mock-server", "1.0.0"), - ), - ) - onMessageBlock?.invoke(initResponse) - } - - "tools/call" -> { - val toolResponse = JSONRPCResponse( - id = message.id, - result = CallToolResult( - content = listOf(), - isError = false, - ), - ) - onMessageBlock?.invoke(toolResponse) - } - } - } - - else -> { - // Handle other message types if needed - } - } - } - - override suspend fun close() { - onCloseBlock?.invoke() - } - - override fun onMessage(block: suspend (JSONRPCMessage) -> Unit) { - onMessageBlock = { message -> - mutex.withLock { - _receivedMessages += message - } - block(message) - } - } - - override fun onClose(block: () -> Unit) { - onCloseBlock = block - } - - override fun onError(block: (Throwable) -> Unit) { - onErrorBlock = block - } - - fun setupInitializationResponse() { - // This method helps set up the mock for proper initialization - } -} diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaStreamableHttpClientTransportTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaStreamableHttpClientTransportTest.kt deleted file mode 100644 index c5be21e..0000000 --- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaStreamableHttpClientTransportTest.kt +++ /dev/null @@ -1,422 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.client - -import io.ktor.client.HttpClient -import io.ktor.client.engine.mock.MockEngine -import io.ktor.client.engine.mock.MockRequestHandler -import io.ktor.client.engine.mock.respond -import io.ktor.client.plugins.sse.SSE -import io.ktor.http.ContentType -import io.ktor.http.HttpHeaders -import io.ktor.http.HttpMethod -import io.ktor.http.HttpStatusCode -import io.ktor.http.content.TextContent -import io.ktor.http.headersOf -import io.ktor.utils.io.ByteReadChannel -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage -import io.modelcontextprotocol.kotlin.sdk.JSONRPCNotification -import io.modelcontextprotocol.kotlin.sdk.JSONRPCRequest -import io.modelcontextprotocol.kotlin.sdk.RequestId -import io.modelcontextprotocol.kotlin.sdk.shared.McpJson -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay -import kotlinx.coroutines.test.runTest -import kotlinx.coroutines.withContext -import kotlinx.coroutines.withTimeout -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.buildJsonObject -import kotlin.test.Ignore -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertNull -import kotlin.test.assertTrue -import kotlin.time.Duration.Companion.seconds - -class OldSchemaStreamableHttpClientTransportTest { - - private fun createTransport(handler: MockRequestHandler): StreamableHttpClientTransport { - val mockEngine = MockEngine(handler) - val httpClient = HttpClient(mockEngine) { - install(SSE) { - reconnectionTime = 1.seconds - } - } - - return StreamableHttpClientTransport(httpClient, url = "http://localhost:8080/mcp") - } - - @Test - fun testSendJsonRpcMessage() = runTest { - val message = JSONRPCRequest( - id = RequestId.StringId("test-id"), - method = "test", - params = buildJsonObject { }, - ) - - val transport = createTransport { request -> - assertEquals(HttpMethod.Post, request.method) - assertEquals("http://localhost:8080/mcp", request.url.toString()) - assertEquals(ContentType.Application.Json, request.body.contentType) - - val body = (request.body as TextContent).text - val decodedMessage = McpJson.decodeFromString(body) - assertEquals(message, decodedMessage) - - respond( - content = "", - status = HttpStatusCode.Accepted, - ) - } - - transport.start() - transport.send(message) - transport.close() - } - - @Test - fun testStoreSessionId() = runTest { - val initMessage = JSONRPCRequest( - id = RequestId.StringId("test-id"), - method = "initialize", - params = buildJsonObject { - put( - "clientInfo", - buildJsonObject { - put("name", JsonPrimitive("test-client")) - put("version", JsonPrimitive("1.0")) - }, - ) - put("protocolVersion", JsonPrimitive("2025-06-18")) - }, - ) - - val transport = createTransport { request -> - when (val msg = McpJson.decodeFromString((request.body as TextContent).text)) { - is JSONRPCRequest if msg.method == "initialize" -> respond( - content = "", - status = HttpStatusCode.OK, - headers = headersOf("mcp-session-id", "test-session-id"), - ) - - is JSONRPCNotification if msg.method == "test" -> { - assertEquals("test-session-id", request.headers["mcp-session-id"]) - respond( - content = "", - status = HttpStatusCode.Accepted, - ) - } - - else -> error("Unexpected message: $msg") - } - } - - transport.start() - transport.send(initMessage) - - assertEquals("test-session-id", transport.sessionId) - - transport.send(JSONRPCNotification(method = "test")) - - transport.close() - } - - @Test - fun testTerminateSession() = runTest { -// transport.sessionId = "test-session-id" - - val transport = createTransport { request -> - assertEquals(HttpMethod.Delete, request.method) - assertEquals("test-session-id", request.headers["mcp-session-id"]) - respond( - content = "", - status = HttpStatusCode.OK, - ) - } - - transport.start() - transport.terminateSession() - - assertNull(transport.sessionId) - transport.close() - } - - @Test - fun testTerminateSessionHandle405() = runTest { -// transport.sessionId = "test-session-id" - - val transport = createTransport { request -> - assertEquals(HttpMethod.Delete, request.method) - respond( - content = "", - status = HttpStatusCode.MethodNotAllowed, - ) - } - - transport.start() - // Should not throw for 405 - transport.terminateSession() - - // Session ID should still be cleared - assertNull(transport.sessionId) - transport.close() - } - - @Test - fun testProtocolVersionHeader() = runTest { - val transport = createTransport { request -> - assertEquals("2025-06-18", request.headers["mcp-protocol-version"]) - respond( - content = "", - status = HttpStatusCode.Accepted, - ) - } - transport.protocolVersion = "2025-06-18" - - transport.start() - transport.send(JSONRPCNotification(method = "test")) - transport.close() - } - - // Engine doesn't support SSECapability: https://youtrack.jetbrains.com/issue/KTOR-8177/MockEngine-Add-SSE-support - @Ignore - @Test - fun testNotificationSchemaE2E() = runTest { - val receivedMessages = mutableListOf() - var sseStarted = false - - val transport = createTransport { request -> - when (request.method) { - HttpMethod.Post if request.body.toString().contains("notifications/initialized") -> { - respond( - content = "", - status = HttpStatusCode.Accepted, - headers = headersOf("mcp-session-id", "notification-test-session"), - ) - } - - // Handle SSE connection - HttpMethod.Get -> { - sseStarted = true - val sseContent = buildString { - // Server sends various notifications - appendLine("event: message") - appendLine("id: 1") - appendLine( - """data: {"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"upload-123","progress":50,"total":100}}""", - ) - appendLine() - - appendLine("event: message") - appendLine("id: 2") - appendLine("""data: {"jsonrpc":"2.0","method":"notifications/resources/list_changed"}""") - appendLine() - - appendLine("event: message") - appendLine("id: 3") - appendLine("""data: {"jsonrpc":"2.0","method":"notifications/tools/list_changed"}""") - appendLine() - } - respond( - content = ByteReadChannel(sseContent), - status = HttpStatusCode.OK, - headers = headersOf( - HttpHeaders.ContentType, - ContentType.Text.EventStream.toString(), - ), - ) - } - - // Handle regular notifications - HttpMethod.Post -> { - respond( - content = "", - status = HttpStatusCode.Accepted, - ) - } - - else -> respond("", HttpStatusCode.OK) - } - } - - transport.onMessage { message -> - receivedMessages.add(message) - } - - transport.start() - - // Test 1: Send initialized notification to trigger SSE - val initializedNotification = JSONRPCNotification( - method = "notifications/initialized", - params = buildJsonObject { - put("protocolVersion", JsonPrimitive("1.0")) - put( - "capabilities", - buildJsonObject { - put("tools", JsonPrimitive(true)) - put("resources", JsonPrimitive(true)) - }, - ) - }, - ) - - transport.send(initializedNotification) - - // Verify SSE was triggered - assertTrue(sseStarted, "SSE should start after initialized notification") - - // Test 2: Verify received notifications - assertEquals(3, receivedMessages.size) - assertTrue(receivedMessages.all { it is JSONRPCNotification }) - - val notifications = receivedMessages.filterIsInstance() - - // Verify progress notification - val progressNotif = notifications[0] - assertEquals("notifications/progress", progressNotif.method) - val progressParams = progressNotif.params as JsonObject - assertEquals("upload-123", (progressParams["progressToken"] as JsonPrimitive).content) - assertEquals(50, (progressParams["progress"] as JsonPrimitive).content.toInt()) - - // Verify list changed notifications - assertEquals("notifications/resources/list_changed", notifications[1].method) - assertEquals("notifications/tools/list_changed", notifications[2].method) - - // Test 3: Send various client notifications - val clientNotifications = listOf( - JSONRPCNotification( - method = "notifications/progress", - params = buildJsonObject { - put("progressToken", JsonPrimitive("download-456")) - put("progress", JsonPrimitive(75)) - }, - ), - JSONRPCNotification( - method = "notifications/cancelled", - params = buildJsonObject { - put("requestId", JsonPrimitive("req-789")) - put("reason", JsonPrimitive("user_cancelled")) - }, - ), - JSONRPCNotification( - method = "notifications/message", - params = buildJsonObject { - put("level", JsonPrimitive("info")) - put("message", JsonPrimitive("Operation completed")) - put( - "data", - buildJsonObject { - put("duration", JsonPrimitive(1234)) - }, - ) - }, - ), - ) - - // Send all client notifications - clientNotifications.forEach { notification -> - transport.send(notification) - } - - // Verify session ID is maintained - assertEquals("notification-test-session", transport.sessionId) - transport.close() - } - - // Engine doesn't support SSECapability: https://youtrack.jetbrains.com/issue/KTOR-8177/MockEngine-Add-SSE-support - @Ignore - @Test - fun testNotificationWithResumptionToken() = runTest { - var resumptionTokenReceived: String? = null - var lastEventIdSent: String? = null - - val transport = createTransport { request -> - // Capture Last-Event-ID header - lastEventIdSent = request.headers["Last-Event-ID"] - - when (request.method) { - HttpMethod.Get -> { - val sseContent = buildString { - appendLine("event: message") - appendLine("id: resume-100") - appendLine( - """data: {"jsonrpc":"2.0","method":"notifications/resumed","params":{"fromToken":"$lastEventIdSent"}}""", - ) - appendLine() - } - respond( - content = ByteReadChannel(sseContent), - status = HttpStatusCode.OK, - headers = headersOf( - HttpHeaders.ContentType, - ContentType.Text.EventStream.toString(), - ), - ) - } - - else -> respond("", HttpStatusCode.Accepted) - } - } - - transport.start() - - // Send notification with resumption token - transport.send( - message = JSONRPCNotification( - method = "notifications/test", - params = buildJsonObject { - put("data", JsonPrimitive("test-data")) - }, - ), - resumptionToken = "previous-token-99", - onResumptionToken = { token -> - resumptionTokenReceived = token - }, - ) - - // Wait for response - delay(1.seconds) - - // Verify resumption token was sent in header - assertEquals("previous-token-99", lastEventIdSent) - - // Verify new resumption token was received - assertEquals("resume-100", resumptionTokenReceived) - transport.close() - } - - @Test - fun testClientConnectWithInvalidJson() = runTest { - // Transport under test: respond with invalid JSON for the initialize request - val transport = createTransport { _ -> - respond( - "this is not valid json", - status = HttpStatusCode.OK, - headers = headersOf(HttpHeaders.ContentType, ContentType.Application.Json.toString()), - ) - } - - val client = Client( - clientInfo = Implementation( - name = "test-client", - version = "1.0", - ), - ) - - try { - // Real time-keeping is needed; otherwise Protocol will always throw TimeoutCancellationException in tests - assertFailsWith( - message = "Expected client.connect to fail on invalid JSON response", - ) { - withContext(Dispatchers.Default.limitedParallelism(1)) { - withTimeout(5.seconds) { - client.connect(transport) - } - } - } - } finally { - transport.close() - } - } -} diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldStreamableHttpClientTest.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldStreamableHttpClientTest.kt deleted file mode 100644 index d61d349..0000000 --- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldStreamableHttpClientTest.kt +++ /dev/null @@ -1,218 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.client - -import io.kotest.matchers.collections.shouldContain -import io.ktor.http.ContentType -import io.ktor.http.HttpMethod -import io.ktor.http.HttpStatusCode -import io.ktor.sse.ServerSentEvent -import io.modelcontextprotocol.kotlin.sdk.ClientCapabilities -import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.Tool -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.runBlocking -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.put -import kotlinx.serialization.json.putJsonObject -import org.junit.jupiter.api.TestInstance -import kotlin.test.Test -import kotlin.time.Duration.Companion.milliseconds -import kotlin.time.Duration.Companion.seconds -import kotlin.uuid.ExperimentalUuidApi -import kotlin.uuid.Uuid - -/** - * Integration tests for the `StreamableHttpClientTransport` implementation - * using the [Mokksy](https://mokksy.dev) library - * to simulate Streaming HTTP with server-sent events (SSE). - * @author Konstantin Pavlov - */ -@OptIn(ExperimentalUuidApi::class) -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -internal class OldStreamableHttpClientTest : AbstractStreamableHttpClientTest() { - - @Test - fun `test streamableHttpClient`() = runBlocking { - val client = Client( - clientInfo = Implementation( - name = "client1", - version = "1.0.0", - ), - options = ClientOptions( - capabilities = ClientCapabilities(), - ), - ) - - val sessionId = Uuid.random().toString() - - mockMcp.onInitialize( - clientName = "client1", - sessionId = sessionId, - ) - - mockMcp.handleJSONRPCRequest( - jsonRpcMethod = "notifications/initialized", - expectedSessionId = sessionId, - sessionId = sessionId, - statusCode = HttpStatusCode.Accepted, - ) - - mockMcp.handleSubscribeWithGet(sessionId) { - flow { - delay(500.milliseconds) - emit( - ServerSentEvent( - event = "message", - id = "1", - data = @Suppress("MaxLineLength") - //language=json - """{"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"upload-123","progress":50,"total":100}}""", - ), - ) - delay(200.milliseconds) - emit( - ServerSentEvent( - data = @Suppress("MaxLineLength") - //language=json - """{"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"upload-123","progress":50,"total":100}}""", - ), - ) - } - } - - // TODO: how to get notifications via Client API? - - mockMcp.handleWithResult( - jsonRpcMethod = "tools/list", - sessionId = sessionId, - // language=json - result = """ - { - "tools": [ - { - "name": "get_weather", - "title": "Weather Information Provider", - "description": "Get current weather information for a location", - "inputSchema": { - "type": "object", - "properties": { - "location": { - "type": "string", - "description": "City name or zip code" - } - }, - "required": ["location"] - }, - "outputSchema": { - "type": "object", - "properties": { - "temperature": { - "type": "number", - "description": "Temperature, Celsius" - } - }, - "required": ["temperature"] - }, - "_meta": {} - } - ] - } - """.trimIndent(), - ) - - connect(client) - - val listToolsResult = client.listTools() - - listToolsResult.tools shouldContain Tool( - name = "get_weather", - title = "Weather Information Provider", - description = "Get current weather information for a location", - inputSchema = Tool.Input( - properties = buildJsonObject { - putJsonObject("location") { - put("type", "string") - put("description", "City name or zip code") - } - }, - required = listOf("location"), - ), - outputSchema = Tool.Output( - properties = buildJsonObject { - putJsonObject("temperature") { - put("type", "number") - put("description", "Temperature, Celsius") - } - }, - required = listOf("temperature"), - ), - annotations = null, - _meta = EmptyJsonObject, - ) - - mockMcp.mockUnsubscribeRequest(sessionId = sessionId) - - client.close() - } - - @Test - fun `handle MethodNotAllowed`() = runBlocking { - checkSupportNonStreamingResponse( - ContentType.Text.EventStream, - HttpStatusCode.MethodNotAllowed, - ) - } - - @Test - fun `handle non-streaming response`() = runBlocking { - checkSupportNonStreamingResponse( - ContentType.Application.Json, - HttpStatusCode.OK, - ) - } - - private suspend fun checkSupportNonStreamingResponse(contentType: ContentType, statusCode: HttpStatusCode) { - val sessionId = "SID_${Uuid.random().toHexString()}" - val clientName = "client-${Uuid.random().toHexString()}" - val client = Client( - clientInfo = Implementation(name = clientName, version = "1.0.0"), - options = ClientOptions( - capabilities = ClientCapabilities(), - ), - ) - - mockMcp.onInitialize(clientName = clientName, sessionId = sessionId) - - mockMcp.handleJSONRPCRequest( - jsonRpcMethod = "notifications/initialized", - expectedSessionId = sessionId, - sessionId = sessionId, - statusCode = HttpStatusCode.Accepted, - ) - - mockMcp.onSubscribe( - httpMethod = HttpMethod.Get, - sessionId = sessionId, - ) respondsWith { - headers += MCP_SESSION_ID_HEADER to sessionId - body = null - httpStatus = statusCode - this.contentType = contentType - } - - mockMcp.handleWithResult(jsonRpcMethod = "ping", sessionId = sessionId) { - buildJsonObject {} - } - - mockMcp.mockUnsubscribeRequest(sessionId = sessionId) - - connect(client) - - delay(1.seconds) - - client.ping() // connection is still alive - - client.close() - } -} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt index 358b565..4c0cc0d 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt @@ -55,7 +55,7 @@ public typealias ProgressCallback = (Progress) -> Unit @Deprecated( message = "Use McpJson instead", replaceWith = ReplaceWith("McpJson", "io.modelcontextprotocol.kotlin.sdk.types.McpJson"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val McpJson: Json by lazy { Json { diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt index 23b0e45..3447f18 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt @@ -18,7 +18,7 @@ import kotlin.concurrent.atomics.ExperimentalAtomicApi "LATEST_PROTOCOL_VERSION", "io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public const val LATEST_PROTOCOL_VERSION: String = io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION @@ -28,7 +28,7 @@ public const val LATEST_PROTOCOL_VERSION: String = io.modelcontextprotocol.kotli "SUPPORTED_PROTOCOL_VERSIONS", "io.modelcontextprotocol.kotlin.sdk.types.SUPPORTED_PROTOCOL_VERSIONS", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val SUPPORTED_PROTOCOL_VERSIONS: List = io.modelcontextprotocol.kotlin.sdk.types.SUPPORTED_PROTOCOL_VERSIONS @@ -36,7 +36,7 @@ public val SUPPORTED_PROTOCOL_VERSIONS: List = @Deprecated( message = "Use `JSONRPC_VERSION` instead", replaceWith = ReplaceWith("JSONRPC_VERSION", "io.modelcontextprotocol.kotlin.sdk.types.JSONRPC_VERSION"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public const val JSONRPC_VERSION: String = io.modelcontextprotocol.kotlin.sdk.types.JSONRPC_VERSION @@ -44,7 +44,7 @@ public const val JSONRPC_VERSION: String = io.modelcontextprotocol.kotlin.sdk.ty @Deprecated( message = "Use `REQUEST_MESSAGE_ID` instead", replaceWith = ReplaceWith("REQUEST_MESSAGE_ID", "io.modelcontextprotocol.kotlin.sdk.types.REQUEST_MESSAGE_ID"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) private val REQUEST_MESSAGE_ID: AtomicLong = AtomicLong(0L) @@ -55,7 +55,7 @@ private val REQUEST_MESSAGE_ID: AtomicLong = AtomicLong(0L) @Deprecated( message = "Use `ProgressToken` instead", replaceWith = ReplaceWith("ProgressToken", "io.modelcontextprotocol.kotlin.sdk.types.ProgressToken"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ProgressToken = io.modelcontextprotocol.kotlin.sdk.types.ProgressToken @@ -65,7 +65,7 @@ public typealias ProgressToken = io.modelcontextprotocol.kotlin.sdk.types.Progre @Deprecated( message = "This alias will be removed. Use String directly instead.", replaceWith = ReplaceWith("String"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias Cursor = String @@ -75,7 +75,7 @@ public typealias Cursor = String @Deprecated( message = "Use `WithMeta` instead", replaceWith = ReplaceWith("WithMeta", "io.modelcontextprotocol.kotlin.sdk.types.WithMeta"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias WithMeta = io.modelcontextprotocol.kotlin.sdk.types.WithMeta @@ -87,7 +87,7 @@ public typealias WithMeta = io.modelcontextprotocol.kotlin.sdk.types.WithMeta @Deprecated( message = "This class will be removed. Use `WithMeta` instead", replaceWith = ReplaceWith("WithMeta", "io.modelcontextprotocol.kotlin.sdk.types.WithMeta"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias CustomMeta = io.modelcontextprotocol.kotlin.sdk.types.WithMeta @@ -97,7 +97,7 @@ public typealias CustomMeta = io.modelcontextprotocol.kotlin.sdk.types.WithMeta @Deprecated( message = "Use `Method` instead", replaceWith = ReplaceWith("Method", "io.modelcontextprotocol.kotlin.sdk.types.Method"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public sealed interface Method { public val value: String @@ -109,7 +109,7 @@ public sealed interface Method { @Deprecated( message = "Use `Method.Custom` instead", replaceWith = ReplaceWith("Method.Custom", "io.modelcontextprotocol.kotlin.sdk.types.Method.Custom"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Custom(value: String): io.modelcontextprotocol.kotlin.sdk.types.Method.Custom = io.modelcontextprotocol.kotlin.sdk.types.Method.Custom(value) @@ -122,7 +122,7 @@ public sealed interface Method { "Method.Defined.Initialize", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.Initialize", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val Initialize: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.Initialize @@ -133,7 +133,7 @@ public sealed interface Method { "Method.Defined.Ping", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.Ping", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val Ping: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.Ping @@ -144,7 +144,7 @@ public sealed interface Method { "Method.Defined.ResourcesList", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesList", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val ResourcesList: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesList @@ -155,7 +155,7 @@ public sealed interface Method { "Method.Defined.ResourcesTemplatesList", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesTemplatesList", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val ResourcesTemplatesList: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesTemplatesList @@ -166,7 +166,7 @@ public sealed interface Method { "Method.Defined.ResourcesRead", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesRead", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val ResourcesRead: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesRead @@ -177,7 +177,7 @@ public sealed interface Method { "Method.Defined.ResourcesSubscribe", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesSubscribe", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val ResourcesSubscribe: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesSubscribe @@ -188,7 +188,7 @@ public sealed interface Method { "Method.Defined.ResourcesUnsubscribe", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesUnsubscribe", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val ResourcesUnsubscribe: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesUnsubscribe @@ -199,7 +199,7 @@ public sealed interface Method { "Method.Defined.PromptsList", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.PromptsList", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val PromptsList: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.PromptsList @@ -210,7 +210,7 @@ public sealed interface Method { "Method.Defined.PromptsGet", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.PromptsGet", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val PromptsGet: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.PromptsGet @@ -221,7 +221,7 @@ public sealed interface Method { "Method.Defined.NotificationsCancelled", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsCancelled", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val NotificationsCancelled: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsCancelled @@ -232,7 +232,7 @@ public sealed interface Method { "Method.Defined.NotificationsInitialized", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsInitialized", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val NotificationsInitialized: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsInitialized @@ -243,7 +243,7 @@ public sealed interface Method { "Method.Defined.NotificationsProgress", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsProgress", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val NotificationsProgress: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsProgress @@ -254,7 +254,7 @@ public sealed interface Method { "Method.Defined.NotificationsMessage", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsMessage", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val NotificationsMessage: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsMessage @@ -265,7 +265,7 @@ public sealed interface Method { "Method.Defined.NotificationsResourcesUpdated", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsResourcesUpdated", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val NotificationsResourcesUpdated: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsResourcesUpdated @@ -276,7 +276,7 @@ public sealed interface Method { "Method.Defined.NotificationsResourcesListChanged", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsResourcesListChanged", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val NotificationsResourcesListChanged: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsResourcesListChanged @@ -287,7 +287,7 @@ public sealed interface Method { "Method.Defined.NotificationsToolsListChanged", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsToolsListChanged", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val NotificationsToolsListChanged: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsToolsListChanged @@ -298,7 +298,7 @@ public sealed interface Method { "Method.Defined.NotificationsRootsListChanged", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsRootsListChanged", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val NotificationsRootsListChanged: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsRootsListChanged @@ -309,7 +309,7 @@ public sealed interface Method { "Method.Defined.NotificationsPromptsListChanged", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsPromptsListChanged", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val NotificationsPromptsListChanged: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsPromptsListChanged @@ -320,7 +320,7 @@ public sealed interface Method { "Method.Defined.ToolsList", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ToolsList", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val ToolsList: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ToolsList @@ -331,7 +331,7 @@ public sealed interface Method { "Method.Defined.ToolsCall", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ToolsCall", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val ToolsCall: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ToolsCall @@ -342,7 +342,7 @@ public sealed interface Method { "Method.Defined.LoggingSetLevel", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.LoggingSetLevel", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val LoggingSetLevel: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.LoggingSetLevel @@ -353,7 +353,7 @@ public sealed interface Method { "Method.Defined.SamplingCreateMessage", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.SamplingCreateMessage", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val SamplingCreateMessage: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.SamplingCreateMessage @@ -364,7 +364,7 @@ public sealed interface Method { "Method.Defined.CompletionComplete", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.CompletionComplete", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val CompletionComplete: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.CompletionComplete @@ -375,7 +375,7 @@ public sealed interface Method { "Method.Defined.RootsList", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.RootsList", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val RootsList: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.RootsList @@ -386,7 +386,7 @@ public sealed interface Method { "Method.Defined.ElicitationCreate", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ElicitationCreate", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val ElicitationCreate: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ElicitationCreate @@ -399,7 +399,7 @@ public sealed interface Method { @Deprecated( message = "Use `Request` instead", replaceWith = ReplaceWith("Request", "io.modelcontextprotocol.kotlin.sdk.types.Request"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias Request = io.modelcontextprotocol.kotlin.sdk.types.Request @@ -411,8 +411,9 @@ public typealias Request = io.modelcontextprotocol.kotlin.sdk.types.Request @Deprecated( message = "Use `toJSON` instead", replaceWith = ReplaceWith("toJSON", "io.modelcontextprotocol.kotlin.sdk.types.toJSON"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) +@Suppress("DEPRECATION_ERROR") public fun Request.toJSON(): JSONRPCRequest = this.toJSON() /** @@ -423,8 +424,9 @@ public fun Request.toJSON(): JSONRPCRequest = this.toJSON() @Deprecated( message = "Use `fromJSON` instead", replaceWith = ReplaceWith("fromJSON", "io.modelcontextprotocol.kotlin.sdk.types.fromJSON"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) +@Suppress("DEPRECATION_ERROR") internal fun JSONRPCRequest.fromJSON(): Request = this.fromJSON() /** @@ -435,7 +437,7 @@ internal fun JSONRPCRequest.fromJSON(): Request = this.fromJSON() @Deprecated( message = "Use `CustomRequest` instead", replaceWith = ReplaceWith("CustomRequest", "io.modelcontextprotocol.kotlin.sdk.types.CustomRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias CustomRequest = io.modelcontextprotocol.kotlin.sdk.types.CustomRequest @@ -445,7 +447,7 @@ public typealias CustomRequest = io.modelcontextprotocol.kotlin.sdk.types.Custom @Deprecated( message = "Use `Notification` instead", replaceWith = ReplaceWith("Notification", "io.modelcontextprotocol.kotlin.sdk.types.Notification"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias Notification = io.modelcontextprotocol.kotlin.sdk.types.Notification @@ -457,8 +459,9 @@ public typealias Notification = io.modelcontextprotocol.kotlin.sdk.types.Notific @Deprecated( message = "Use `toJSON` instead", replaceWith = ReplaceWith("toJSON", "io.modelcontextprotocol.kotlin.sdk.types.toJSON"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) +@Suppress("DEPRECATION_ERROR") public fun Notification.toJSON(): JSONRPCNotification = this.toJSON() /** @@ -469,8 +472,9 @@ public fun Notification.toJSON(): JSONRPCNotification = this.toJSON() @Deprecated( message = "Use `fromJSON` instead", replaceWith = ReplaceWith("toJSON", "io.modelcontextprotocol.kotlin.sdk.types.fromJSON"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) +@Suppress("DEPRECATION_ERROR") internal fun JSONRPCNotification.fromJSON(): Notification = this.fromJSON() /** @@ -479,7 +483,7 @@ internal fun JSONRPCNotification.fromJSON(): Notification = this.fromJSON() @Deprecated( message = "Use `RequestResult` instead", replaceWith = ReplaceWith("RequestResult", "io.modelcontextprotocol.kotlin.sdk.types.RequestResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias RequestResult = io.modelcontextprotocol.kotlin.sdk.types.RequestResult @@ -491,7 +495,7 @@ public typealias RequestResult = io.modelcontextprotocol.kotlin.sdk.types.Reques @Deprecated( message = "Use `EmptyResult` instead", replaceWith = ReplaceWith("EmptyResult", "io.modelcontextprotocol.kotlin.sdk.types.EmptyResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias EmptyRequestResult = io.modelcontextprotocol.kotlin.sdk.types.EmptyResult @@ -501,13 +505,13 @@ public typealias EmptyRequestResult = io.modelcontextprotocol.kotlin.sdk.types.E @Deprecated( message = "Use `RequestId` instead", replaceWith = ReplaceWith("RequestId", "io.modelcontextprotocol.kotlin.sdk.types.RequestId"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object RequestId { @Deprecated( message = "Use `RequestId.StringId` instead", replaceWith = ReplaceWith("RequestId.StringId", "io.modelcontextprotocol.kotlin.sdk.types.RequestId.StringId"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun StringId(value: String): io.modelcontextprotocol.kotlin.sdk.types.RequestId.StringId = io.modelcontextprotocol.kotlin.sdk.types.RequestId.StringId(value) @@ -515,7 +519,7 @@ public object RequestId { @Deprecated( message = "Use `RequestId.NumberId` instead", replaceWith = ReplaceWith("RequestId.NumberId", "io.modelcontextprotocol.kotlin.sdk.types.RequestId.NumberId"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun NumberId(value: Long): io.modelcontextprotocol.kotlin.sdk.types.RequestId.NumberId = io.modelcontextprotocol.kotlin.sdk.types.RequestId.NumberId(value) @@ -527,7 +531,7 @@ public object RequestId { @Deprecated( message = "Use `JSONRPCMessage` instead", replaceWith = ReplaceWith("JSONRPCMessage", "io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias JSONRPCMessage = io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage @@ -537,7 +541,7 @@ public typealias JSONRPCMessage = io.modelcontextprotocol.kotlin.sdk.types.JSONR @Deprecated( message = "Use `JSONRPCRequest` instead", replaceWith = ReplaceWith("JSONRPCRequest", "io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias JSONRPCRequest = io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest @@ -547,7 +551,7 @@ public typealias JSONRPCRequest = io.modelcontextprotocol.kotlin.sdk.types.JSONR @Deprecated( message = "Use `JSONRPCNotification` instead", replaceWith = ReplaceWith("JSONRPCNotification", "io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias JSONRPCNotification = io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification @@ -557,7 +561,7 @@ public typealias JSONRPCNotification = io.modelcontextprotocol.kotlin.sdk.types. @Deprecated( message = "Use `JSONRPCResponse` instead", replaceWith = ReplaceWith("JSONRPCResponse", "io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias JSONRPCResponse = io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse @@ -567,7 +571,7 @@ public typealias JSONRPCResponse = io.modelcontextprotocol.kotlin.sdk.types.JSON @Deprecated( message = "Use `RPCError` instead", replaceWith = ReplaceWith("RPCError", "io.modelcontextprotocol.kotlin.sdk.types.RPCError"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public sealed interface ErrorCode { public val code: Int @@ -579,7 +583,7 @@ public sealed interface ErrorCode { "RPCError.ErrorCode.CONNECTION_CLOSED", "io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.CONNECTION_CLOSED", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val ConnectionClosed: Int = io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.CONNECTION_CLOSED @@ -589,7 +593,7 @@ public sealed interface ErrorCode { "RPCError.ErrorCode.REQUEST_TIMEOUT", "io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.REQUEST_TIMEOUT", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val RequestTimeout: Int = io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.REQUEST_TIMEOUT @@ -599,7 +603,7 @@ public sealed interface ErrorCode { "RPCError.ErrorCode.PARSE_ERROR", "io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.PARSE_ERROR", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val ParseError: Int = io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.PARSE_ERROR @@ -609,7 +613,7 @@ public sealed interface ErrorCode { "RPCError.ErrorCode.INVALID_REQUEST", "io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.INVALID_REQUEST", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val InvalidRequest: Int = io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.INVALID_REQUEST @@ -619,7 +623,7 @@ public sealed interface ErrorCode { "RPCError.ErrorCode.METHOD_NOT_FOUND", "io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.METHOD_NOT_FOUND", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val MethodNotFound: Int = io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.METHOD_NOT_FOUND @@ -629,7 +633,7 @@ public sealed interface ErrorCode { "RPCError.ErrorCode.INVALID_PARAMS", "io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.INVALID_PARAMS", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val InvalidParams: Int = io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.INVALID_PARAMS @@ -639,7 +643,7 @@ public sealed interface ErrorCode { "RPCError.ErrorCode.INTERNAL_ERROR", "io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.INTERNAL_ERROR", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val InternalError: Int = io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.INTERNAL_ERROR } @@ -647,9 +651,10 @@ public sealed interface ErrorCode { @Deprecated( message = "Use `RPCError` instead", replaceWith = ReplaceWith("RPCError", "io.modelcontextprotocol.kotlin.sdk.types.RPCError"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) @Serializable + @Suppress("DEPRECATION_ERROR") public data class Unknown(override val code: Int) : ErrorCode } @@ -659,7 +664,7 @@ public sealed interface ErrorCode { @Deprecated( message = "Use `JSONRPCError` instead", replaceWith = ReplaceWith("JSONRPCError", "io.modelcontextprotocol.kotlin.sdk.types.JSONRPCError"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias JSONRPCError = io.modelcontextprotocol.kotlin.sdk.types.JSONRPCError @@ -669,7 +674,7 @@ public typealias JSONRPCError = io.modelcontextprotocol.kotlin.sdk.types.JSONRPC @Deprecated( message = "Use `NotificationParams` instead", replaceWith = ReplaceWith("NotificationParams", "io.modelcontextprotocol.kotlin.sdk.types.NotificationParams"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias NotificationParams = io.modelcontextprotocol.kotlin.sdk.types.NotificationParams @@ -681,7 +686,7 @@ public typealias NotificationParams = io.modelcontextprotocol.kotlin.sdk.types.N "CancelledNotification", "io.modelcontextprotocol.kotlin.sdk.types.CancelledNotification", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object CancelledNotification { @Deprecated( @@ -690,7 +695,7 @@ public object CancelledNotification { "CancelledNotificationParams", "io.modelcontextprotocol.kotlin.sdk.types.CancelledNotificationParams", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Params( requestId: io.modelcontextprotocol.kotlin.sdk.types.RequestId, @@ -715,7 +720,7 @@ public object CancelledNotification { "CancelledNotification", "io.modelcontextprotocol.kotlin.sdk.types.CancelledNotification", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun CancelledNotification( params: io.modelcontextprotocol.kotlin.sdk.types.CancelledNotificationParams, @@ -730,14 +735,14 @@ public fun CancelledNotification( @Deprecated( message = "Use `Implementation` instead", replaceWith = ReplaceWith("Implementation", "io.modelcontextprotocol.kotlin.sdk.types.Implementation"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias Implementation = io.modelcontextprotocol.kotlin.sdk.types.Implementation @Deprecated( message = "Use `ClientCapabilities` instead", replaceWith = ReplaceWith("ClientCapabilities", "io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object ClientCapabilities { @Deprecated( @@ -746,7 +751,7 @@ public object ClientCapabilities { "ClientCapabilities.Roots", "io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities.Roots", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Roots(listChanged: Boolean? = null): io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities.Roots = io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities.Roots(listChanged) @@ -760,7 +765,7 @@ public object ClientCapabilities { @Deprecated( message = "Use `ClientCapabilities` instead", replaceWith = ReplaceWith("ClientCapabilities", "io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun ClientCapabilities( sampling: JsonObject? = null, @@ -776,7 +781,7 @@ public fun ClientCapabilities( @Deprecated( message = "Use `ClientRequest` instead", replaceWith = ReplaceWith("ClientRequest", "io.modelcontextprotocol.kotlin.sdk.types.ClientRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ClientRequest = io.modelcontextprotocol.kotlin.sdk.types.ClientRequest @@ -786,7 +791,7 @@ public typealias ClientRequest = io.modelcontextprotocol.kotlin.sdk.types.Client @Deprecated( message = "Use `ClientNotification` instead", replaceWith = ReplaceWith("ClientNotification", "io.modelcontextprotocol.kotlin.sdk.types.ClientNotification"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ClientNotification = io.modelcontextprotocol.kotlin.sdk.types.ClientNotification @@ -796,7 +801,7 @@ public typealias ClientNotification = io.modelcontextprotocol.kotlin.sdk.types.C @Deprecated( message = "Use `ClientResult` instead", replaceWith = ReplaceWith("ClientResult", "io.modelcontextprotocol.kotlin.sdk.types.ClientResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ClientResult = io.modelcontextprotocol.kotlin.sdk.types.ClientResult @@ -806,7 +811,7 @@ public typealias ClientResult = io.modelcontextprotocol.kotlin.sdk.types.ClientR @Deprecated( message = "Use `ServerRequest` instead", replaceWith = ReplaceWith("ServerRequest", "io.modelcontextprotocol.kotlin.sdk.types.ServerRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ServerRequest = io.modelcontextprotocol.kotlin.sdk.types.ServerRequest @@ -816,7 +821,7 @@ public typealias ServerRequest = io.modelcontextprotocol.kotlin.sdk.types.Server @Deprecated( message = "Use `ServerNotification` instead", replaceWith = ReplaceWith("ServerNotification", "io.modelcontextprotocol.kotlin.sdk.types.ServerNotification"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ServerNotification = io.modelcontextprotocol.kotlin.sdk.types.ServerNotification @@ -826,7 +831,7 @@ public typealias ServerNotification = io.modelcontextprotocol.kotlin.sdk.types.S @Deprecated( message = "Use `ServerResult` instead", replaceWith = ReplaceWith("ServerResult", "io.modelcontextprotocol.kotlin.sdk.types.ServerResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ServerResult = io.modelcontextprotocol.kotlin.sdk.types.ServerResult @@ -838,8 +843,9 @@ public typealias ServerResult = io.modelcontextprotocol.kotlin.sdk.types.ServerR @Serializable @Deprecated( message = "This class will be removed", - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) +@Suppress("DEPRECATION_ERROR") public data class UnknownMethodRequestOrNotification(val method: Method, val params: NotificationParams? = null) /** @@ -848,14 +854,14 @@ public data class UnknownMethodRequestOrNotification(val method: Method, val par @Deprecated( message = "Use `InitializeRequest` instead", replaceWith = ReplaceWith("InitializeRequest", "io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias InitializeRequest = io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest @Deprecated( message = "Use `ServerCapabilities` instead", replaceWith = ReplaceWith("ServerCapabilities", "io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object ServerCapabilities { @@ -865,7 +871,7 @@ public object ServerCapabilities { "ServerCapabilities.Prompts", "io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Prompts", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Prompts( listChanged: Boolean? = null, @@ -878,7 +884,7 @@ public object ServerCapabilities { "ServerCapabilities.Resources", "io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Resources", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Resources( listChanged: Boolean? = null, @@ -892,7 +898,7 @@ public object ServerCapabilities { "ServerCapabilities.Tools", "io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Tools", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Tools(listChanged: Boolean? = null): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Tools = io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Tools(listChanged) @@ -910,7 +916,7 @@ public object ServerCapabilities { @Deprecated( message = "Use `ServerCapabilities` instead", replaceWith = ReplaceWith("ServerCapabilities", "io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun ServerCapabilities( tools: io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Tools? = null, @@ -935,7 +941,7 @@ public fun ServerCapabilities( @Deprecated( message = "Use `InitializeResult` instead", replaceWith = ReplaceWith("InitializeResult", "io.modelcontextprotocol.kotlin.sdk.types.InitializeResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias InitializeResult = io.modelcontextprotocol.kotlin.sdk.types.InitializeResult @@ -945,7 +951,7 @@ public typealias InitializeResult = io.modelcontextprotocol.kotlin.sdk.types.Ini "InitializedNotification", "io.modelcontextprotocol.kotlin.sdk.types.InitializedNotification", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object InitializedNotification { @Deprecated( @@ -954,8 +960,9 @@ public object InitializedNotification { "BaseNotificationParams", "io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) + @Suppress("DEPRECATION_ERROR") public fun Params( meta: JsonObject = EmptyJsonObject, ): io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams = @@ -971,7 +978,7 @@ public object InitializedNotification { "InitializedNotification", "io.modelcontextprotocol.kotlin.sdk.types.InitializedNotification", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun InitializedNotification( params: io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams? = null, @@ -987,7 +994,7 @@ public fun InitializedNotification( @Deprecated( message = "Use `PingRequest` instead", replaceWith = ReplaceWith("PingRequest", "io.modelcontextprotocol.kotlin.sdk.types.PingRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias PingRequest = io.modelcontextprotocol.kotlin.sdk.types.PingRequest @@ -997,7 +1004,7 @@ public typealias PingRequest = io.modelcontextprotocol.kotlin.sdk.types.PingRequ @Serializable @Deprecated( message = "This interface will be removed", - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public sealed interface ProgressBase { /** @@ -1027,14 +1034,14 @@ public sealed interface ProgressBase { @Deprecated( message = "Use `Progress` instead", replaceWith = ReplaceWith("Progress", "io.modelcontextprotocol.kotlin.sdk.types.Progress"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias Progress = io.modelcontextprotocol.kotlin.sdk.types.Progress @Deprecated( message = "Use `ProgressNotification` instead", replaceWith = ReplaceWith("ProgressNotification", "io.modelcontextprotocol.kotlin.sdk.types.ProgressNotification"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object ProgressNotification { @Deprecated( @@ -1043,7 +1050,7 @@ public object ProgressNotification { "ProgressNotificationParams", "io.modelcontextprotocol.kotlin.sdk.types.ProgressNotificationParams", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Params( progressToken: io.modelcontextprotocol.kotlin.sdk.types.ProgressToken, @@ -1067,7 +1074,7 @@ public object ProgressNotification { @Deprecated( message = "Use `ProgressNotification` instead", replaceWith = ReplaceWith("ProgressNotification", "io.modelcontextprotocol.kotlin.sdk.types.ProgressNotification"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun ProgressNotification( params: io.modelcontextprotocol.kotlin.sdk.types.ProgressNotificationParams, @@ -1082,7 +1089,7 @@ public fun ProgressNotification( @Deprecated( message = "Use `PaginatedRequest` instead", replaceWith = ReplaceWith("PaginatedRequest", "io.modelcontextprotocol.kotlin.sdk.types.PaginatedRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias PaginatedRequest = io.modelcontextprotocol.kotlin.sdk.types.PaginatedRequest @@ -1092,7 +1099,7 @@ public typealias PaginatedRequest = io.modelcontextprotocol.kotlin.sdk.types.Pag @Deprecated( message = "Use `PaginatedResult` instead", replaceWith = ReplaceWith("PaginatedResult", "io.modelcontextprotocol.kotlin.sdk.types.PaginatedResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias PaginatedResult = io.modelcontextprotocol.kotlin.sdk.types.PaginatedResult @@ -1104,7 +1111,7 @@ public typealias PaginatedResult = io.modelcontextprotocol.kotlin.sdk.types.Pagi @Deprecated( message = "Use `ResourceContents` instead", replaceWith = ReplaceWith("ResourceContents", "io.modelcontextprotocol.kotlin.sdk.types.ResourceContents"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ResourceContents = io.modelcontextprotocol.kotlin.sdk.types.ResourceContents @@ -1116,7 +1123,7 @@ public typealias ResourceContents = io.modelcontextprotocol.kotlin.sdk.types.Res @Deprecated( message = "Use `TextResourceContents` instead", replaceWith = ReplaceWith("TextResourceContents", "io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias TextResourceContents = io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents @@ -1128,7 +1135,7 @@ public typealias TextResourceContents = io.modelcontextprotocol.kotlin.sdk.types @Deprecated( message = "Use `BlobResourceContents` instead", replaceWith = ReplaceWith("BlobResourceContents", "io.modelcontextprotocol.kotlin.sdk.types.BlobResourceContents"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias BlobResourceContents = io.modelcontextprotocol.kotlin.sdk.types.BlobResourceContents @@ -1141,7 +1148,7 @@ public typealias BlobResourceContents = io.modelcontextprotocol.kotlin.sdk.types "UnknownResourceContents", "io.modelcontextprotocol.kotlin.sdk.types.UnknownResourceContents", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias UnknownResourceContents = io.modelcontextprotocol.kotlin.sdk.types.UnknownResourceContents @@ -1151,7 +1158,7 @@ public typealias UnknownResourceContents = io.modelcontextprotocol.kotlin.sdk.ty @Deprecated( message = "Use `Resource` instead", replaceWith = ReplaceWith("Resource", "io.modelcontextprotocol.kotlin.sdk.types.Resource"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias Resource = io.modelcontextprotocol.kotlin.sdk.types.Resource @@ -1161,7 +1168,7 @@ public typealias Resource = io.modelcontextprotocol.kotlin.sdk.types.Resource @Deprecated( message = "Use `ResourceTemplate` instead", replaceWith = ReplaceWith("ResourceTemplate", "io.modelcontextprotocol.kotlin.sdk.types.ResourceTemplate"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ResourceTemplate = io.modelcontextprotocol.kotlin.sdk.types.ResourceTemplate @@ -1171,7 +1178,7 @@ public typealias ResourceTemplate = io.modelcontextprotocol.kotlin.sdk.types.Res @Deprecated( message = "Use `ListResourcesRequest` instead", replaceWith = ReplaceWith("ListResourcesRequest", "io.modelcontextprotocol.kotlin.sdk.types.ListResourcesRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ListResourcesRequest = io.modelcontextprotocol.kotlin.sdk.types.ListResourcesRequest @@ -1181,7 +1188,7 @@ public typealias ListResourcesRequest = io.modelcontextprotocol.kotlin.sdk.types @Deprecated( message = "Use `ListResourcesResult` instead", replaceWith = ReplaceWith("ListResourcesResult", "io.modelcontextprotocol.kotlin.sdk.types.ListResourcesResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ListResourcesResult = io.modelcontextprotocol.kotlin.sdk.types.ListResourcesResult @@ -1194,7 +1201,7 @@ public typealias ListResourcesResult = io.modelcontextprotocol.kotlin.sdk.types. "ListResourceTemplatesRequest", "io.modelcontextprotocol.kotlin.sdk.types.ListResourceTemplatesRequest", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ListResourceTemplatesRequest = io.modelcontextprotocol.kotlin.sdk.types.ListResourceTemplatesRequest @@ -1207,7 +1214,7 @@ public typealias ListResourceTemplatesRequest = io.modelcontextprotocol.kotlin.s "ListResourceTemplatesResult", "io.modelcontextprotocol.kotlin.sdk.types.ListResourceTemplatesResult", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ListResourceTemplatesResult = io.modelcontextprotocol.kotlin.sdk.types.ListResourceTemplatesResult @@ -1217,7 +1224,7 @@ public typealias ListResourceTemplatesResult = io.modelcontextprotocol.kotlin.sd @Deprecated( message = "Use `ReadResourceRequest` instead", replaceWith = ReplaceWith("ReadResourceRequest", "io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ReadResourceRequest = io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest @@ -1227,7 +1234,7 @@ public typealias ReadResourceRequest = io.modelcontextprotocol.kotlin.sdk.types. @Deprecated( message = "Use `ReadResourceResult` instead", replaceWith = ReplaceWith("ReadResourceResult", "io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ReadResourceResult = io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult @@ -1237,7 +1244,7 @@ public typealias ReadResourceResult = io.modelcontextprotocol.kotlin.sdk.types.R "ResourceListChangedNotification", "io.modelcontextprotocol.kotlin.sdk.types.ResourceListChangedNotification", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object ResourceListChangedNotification { @Deprecated( @@ -1246,7 +1253,7 @@ public object ResourceListChangedNotification { "BaseNotificationParams", "io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Params(meta: JsonObject? = null): io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams = io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams(meta) @@ -1263,7 +1270,7 @@ public object ResourceListChangedNotification { "ResourceListChangedNotification", "io.modelcontextprotocol.kotlin.sdk.types.ResourceListChangedNotification", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun ResourceListChangedNotification( params: io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams? = null, @@ -1276,7 +1283,7 @@ public fun ResourceListChangedNotification( @Deprecated( message = "Use `SubscribeRequest` instead", replaceWith = ReplaceWith("SubscribeRequest", "io.modelcontextprotocol.kotlin.sdk.types.SubscribeRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias SubscribeRequest = io.modelcontextprotocol.kotlin.sdk.types.SubscribeRequest @@ -1286,7 +1293,7 @@ public typealias SubscribeRequest = io.modelcontextprotocol.kotlin.sdk.types.Sub @Deprecated( message = "Use `UnsubscribeRequest` instead", replaceWith = ReplaceWith("UnsubscribeRequest", "io.modelcontextprotocol.kotlin.sdk.types.UnsubscribeRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias UnsubscribeRequest = io.modelcontextprotocol.kotlin.sdk.types.UnsubscribeRequest @@ -1296,7 +1303,7 @@ public typealias UnsubscribeRequest = io.modelcontextprotocol.kotlin.sdk.types.U "ResourceUpdatedNotification", "io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotification", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object ResourceUpdatedNotification { @Deprecated( @@ -1305,7 +1312,7 @@ public object ResourceUpdatedNotification { "ResourceUpdatedNotificationParams", "io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotificationParams", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Params( uri: String, @@ -1323,7 +1330,7 @@ public object ResourceUpdatedNotification { "ResourceUpdatedNotification", "io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotification", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun ResourceUpdatedNotification( params: io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotificationParams, @@ -1338,7 +1345,7 @@ public fun ResourceUpdatedNotification( @Deprecated( message = "Use `PromptArgument` instead", replaceWith = ReplaceWith("PromptArgument", "io.modelcontextprotocol.kotlin.sdk.types.PromptArgument"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias PromptArgument = io.modelcontextprotocol.kotlin.sdk.types.PromptArgument @@ -1348,7 +1355,7 @@ public typealias PromptArgument = io.modelcontextprotocol.kotlin.sdk.types.Promp @Deprecated( message = "Use `Prompt` instead", replaceWith = ReplaceWith("Prompt", "io.modelcontextprotocol.kotlin.sdk.types.Prompt"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias Prompt = io.modelcontextprotocol.kotlin.sdk.types.Prompt @@ -1358,7 +1365,7 @@ public typealias Prompt = io.modelcontextprotocol.kotlin.sdk.types.Prompt @Deprecated( message = "Use `ListPromptsRequest` instead", replaceWith = ReplaceWith("ListPromptsRequest", "io.modelcontextprotocol.kotlin.sdk.types.ListPromptsRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ListPromptsRequest = io.modelcontextprotocol.kotlin.sdk.types.ListPromptsRequest @@ -1368,7 +1375,7 @@ public typealias ListPromptsRequest = io.modelcontextprotocol.kotlin.sdk.types.L @Deprecated( message = "Use `ListPromptsResult` instead", replaceWith = ReplaceWith("ListPromptsResult", "io.modelcontextprotocol.kotlin.sdk.types.ListPromptsResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ListPromptsResult = io.modelcontextprotocol.kotlin.sdk.types.ListPromptsResult @@ -1378,7 +1385,7 @@ public typealias ListPromptsResult = io.modelcontextprotocol.kotlin.sdk.types.Li @Deprecated( message = "Use `GetPromptRequest` instead", replaceWith = ReplaceWith("GetPromptRequest", "io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias GetPromptRequest = io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest @@ -1388,7 +1395,7 @@ public typealias GetPromptRequest = io.modelcontextprotocol.kotlin.sdk.types.Get @Deprecated( message = "Use `ContentBlock` instead", replaceWith = ReplaceWith("ContentBlock"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias PromptMessageContent = io.modelcontextprotocol.kotlin.sdk.types.ContentBlock @@ -1398,7 +1405,7 @@ public typealias PromptMessageContent = io.modelcontextprotocol.kotlin.sdk.types @Deprecated( message = "Use `MediaContent` instead", replaceWith = ReplaceWith("MediaContent"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias PromptMessageContentMultimodal = io.modelcontextprotocol.kotlin.sdk.types.MediaContent @@ -1408,7 +1415,7 @@ public typealias PromptMessageContentMultimodal = io.modelcontextprotocol.kotlin @Deprecated( message = "Use `TextContent` instead", replaceWith = ReplaceWith("TextContent", "io.modelcontextprotocol.kotlin.sdk.types.TextContent"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias TextContent = io.modelcontextprotocol.kotlin.sdk.types.TextContent @@ -1418,7 +1425,7 @@ public typealias TextContent = io.modelcontextprotocol.kotlin.sdk.types.TextCont @Deprecated( message = "Use `ImageContent` instead", replaceWith = ReplaceWith("ImageContent", "io.modelcontextprotocol.kotlin.sdk.types.ImageContent"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ImageContent = io.modelcontextprotocol.kotlin.sdk.types.ImageContent @@ -1428,7 +1435,7 @@ public typealias ImageContent = io.modelcontextprotocol.kotlin.sdk.types.ImageCo @Deprecated( message = "Use `AudioContent` instead", replaceWith = ReplaceWith("AudioContent", "io.modelcontextprotocol.kotlin.sdk.types.AudioContent"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias AudioContent = io.modelcontextprotocol.kotlin.sdk.types.AudioContent @@ -1439,7 +1446,7 @@ public typealias AudioContent = io.modelcontextprotocol.kotlin.sdk.types.AudioCo @Deprecated( message = "This class will be removed.", replaceWith = ReplaceWith("MediaContent"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public data class UnknownContent(val type: String) @@ -1449,7 +1456,7 @@ public data class UnknownContent(val type: String) @Deprecated( message = "Use `EmbeddedResource` instead", replaceWith = ReplaceWith("EmbeddedResource", "io.modelcontextprotocol.kotlin.sdk.types.EmbeddedResource"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias EmbeddedResource = io.modelcontextprotocol.kotlin.sdk.types.EmbeddedResource @@ -1459,20 +1466,20 @@ public typealias EmbeddedResource = io.modelcontextprotocol.kotlin.sdk.types.Emb @Deprecated( message = "Use `Role` instead", replaceWith = ReplaceWith("Role", "io.modelcontextprotocol.kotlin.sdk.types.Role"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object Role { @Deprecated( message = "Use `Role.User` instead", replaceWith = ReplaceWith("Role.User", "io.modelcontextprotocol.kotlin.sdk.types.Role"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val user: io.modelcontextprotocol.kotlin.sdk.types.Role = io.modelcontextprotocol.kotlin.sdk.types.Role.User @Deprecated( message = "Use `Role.Assistant` instead", replaceWith = ReplaceWith("Role.Assistant", "io.modelcontextprotocol.kotlin.sdk.types.Role"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val assistant: io.modelcontextprotocol.kotlin.sdk.types.Role = io.modelcontextprotocol.kotlin.sdk.types.Role.Assistant @@ -1485,7 +1492,7 @@ public object Role { @Deprecated( message = "Use `Annotations` instead", replaceWith = ReplaceWith("Annotations", "io.modelcontextprotocol.kotlin.sdk.types.Annotations"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias Annotations = io.modelcontextprotocol.kotlin.sdk.types.Annotations @@ -1495,7 +1502,7 @@ public typealias Annotations = io.modelcontextprotocol.kotlin.sdk.types.Annotati @Deprecated( message = "Use `PromptMessage` instead", replaceWith = ReplaceWith("PromptMessage", "io.modelcontextprotocol.kotlin.sdk.types.PromptMessage"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias PromptMessage = io.modelcontextprotocol.kotlin.sdk.types.PromptMessage @@ -1505,7 +1512,7 @@ public typealias PromptMessage = io.modelcontextprotocol.kotlin.sdk.types.Prompt @Deprecated( message = "Use `GetPromptResult` instead", replaceWith = ReplaceWith("GetPromptResult", "io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias GetPromptResult = io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult @@ -1515,7 +1522,7 @@ public typealias GetPromptResult = io.modelcontextprotocol.kotlin.sdk.types.GetP "PromptListChangedNotification", "io.modelcontextprotocol.kotlin.sdk.types.PromptListChangedNotification", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object PromptListChangedNotification { @Deprecated( @@ -1524,7 +1531,7 @@ public object PromptListChangedNotification { "BaseNotificationParams", "io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Params(meta: JsonObject? = null): io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams = io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams(meta) @@ -1540,7 +1547,7 @@ public object PromptListChangedNotification { "PromptListChangedNotification", "io.modelcontextprotocol.kotlin.sdk.types.PromptListChangedNotification", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun PromptListChangedNotification( params: io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams, @@ -1562,20 +1569,20 @@ public fun PromptListChangedNotification( @Deprecated( message = "Use `ToolAnnotations` instead", replaceWith = ReplaceWith("ToolAnnotations", "io.modelcontextprotocol.kotlin.sdk.types.ToolAnnotations"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ToolAnnotations = io.modelcontextprotocol.kotlin.sdk.types.ToolAnnotations @Deprecated( message = "Use `Tool` instead", replaceWith = ReplaceWith("Tool", "io.modelcontextprotocol.kotlin.sdk.types.Tool"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object Tool { @Deprecated( message = "Use `ToolSchema` instead", replaceWith = ReplaceWith("ToolSchema", "io.modelcontextprotocol.kotlin.sdk.types.ToolSchema"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Input( properties: JsonObject? = null, @@ -1586,7 +1593,7 @@ public object Tool { @Deprecated( message = "Use `ToolSchema` instead", replaceWith = ReplaceWith("ToolSchema", "io.modelcontextprotocol.kotlin.sdk.types.ToolSchema"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Output( properties: JsonObject? = null, @@ -1601,7 +1608,7 @@ public object Tool { @Deprecated( message = "Use `Tool` instead", replaceWith = ReplaceWith("Tool", "io.modelcontextprotocol.kotlin.sdk.types.Tool"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Tool( name: String, @@ -1629,7 +1636,7 @@ public fun Tool( @Deprecated( message = "Use `ListToolsRequest` instead", replaceWith = ReplaceWith("ListToolsRequest", "io.modelcontextprotocol.kotlin.sdk.types.ListToolsRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ListToolsRequest = io.modelcontextprotocol.kotlin.sdk.types.ListToolsRequest @@ -1639,7 +1646,7 @@ public typealias ListToolsRequest = io.modelcontextprotocol.kotlin.sdk.types.Lis @Deprecated( message = "Use `ListToolsResult` instead", replaceWith = ReplaceWith("ListToolsResult", "io.modelcontextprotocol.kotlin.sdk.types.ListToolsResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ListToolsResult = io.modelcontextprotocol.kotlin.sdk.types.ListToolsResult @@ -1650,8 +1657,9 @@ public typealias ListToolsResult = io.modelcontextprotocol.kotlin.sdk.types.List @Deprecated( message = "This interface will be removed.", replaceWith = ReplaceWith("CallToolResult", "io.modelcontextprotocol.kotlin.sdk.types.CallToolResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) +@Suppress("DEPRECATION_ERROR") public sealed interface CallToolResultBase { public val content: List public val structuredContent: JsonObject? @@ -1661,7 +1669,7 @@ public sealed interface CallToolResultBase { @Deprecated( message = "Use `CallToolResult` instead", replaceWith = ReplaceWith("CallToolResult", "io.modelcontextprotocol.kotlin.sdk.types.CallToolResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object CallToolResult @@ -1671,7 +1679,7 @@ public object CallToolResult @Deprecated( message = "Use `CallToolResult` instead", replaceWith = ReplaceWith("CallToolResult", "io.modelcontextprotocol.kotlin.sdk.types.CallToolResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun CallToolResult( content: List, @@ -1688,8 +1696,9 @@ public fun CallToolResult( @Deprecated( message = "This class will be removed.", replaceWith = ReplaceWith("ToolCallResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) +@Suppress("DEPRECATION_ERROR") public class CompatibilityCallToolResult( public val content: List, public val structuredContent: JsonObject? = null, @@ -1704,7 +1713,7 @@ public class CompatibilityCallToolResult( @Deprecated( message = "Use `CallToolRequest` instead", replaceWith = ReplaceWith("CallToolRequest", "io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias CallToolRequest = io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest @@ -1714,7 +1723,7 @@ public typealias CallToolRequest = io.modelcontextprotocol.kotlin.sdk.types.Call "ToolListChangedNotification", "io.modelcontextprotocol.kotlin.sdk.types.ToolListChangedNotification", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object ToolListChangedNotification { @Deprecated( @@ -1723,7 +1732,7 @@ public object ToolListChangedNotification { "BaseNotificationParams", "io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Params(meta: JsonObject? = null): io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams = io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams(meta) @@ -1739,7 +1748,7 @@ public object ToolListChangedNotification { "ToolListChangedNotification", "io.modelcontextprotocol.kotlin.sdk.types.ToolListChangedNotification", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun ToolListChangedNotification( params: io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams, @@ -1754,13 +1763,13 @@ public fun ToolListChangedNotification( @Deprecated( message = "Use `LoggingLevel` instead", replaceWith = ReplaceWith("LoggingLevel", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object LoggingLevel { @Deprecated( message = "Use `LoggingLevel.Debug` instead", replaceWith = ReplaceWith("LoggingLevel.Debug", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val debug: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel = io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel.Debug @@ -1768,7 +1777,7 @@ public object LoggingLevel { @Deprecated( message = "Use `LoggingLevel.Info` instead", replaceWith = ReplaceWith("LoggingLevel.Info", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val info: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel = io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel.Info @@ -1776,7 +1785,7 @@ public object LoggingLevel { @Deprecated( message = "Use `LoggingLevel.Notice` instead", replaceWith = ReplaceWith("LoggingLevel.Notice", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val notice: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel = io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel.Notice @@ -1784,7 +1793,7 @@ public object LoggingLevel { @Deprecated( message = "Use `LoggingLevel.Warning` instead", replaceWith = ReplaceWith("LoggingLevel.Warning", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val warning: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel = io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel.Warning @@ -1792,7 +1801,7 @@ public object LoggingLevel { @Deprecated( message = "Use `LoggingLevel.Error` instead", replaceWith = ReplaceWith("LoggingLevel.Error", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val error: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel = io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel.Error @@ -1800,7 +1809,7 @@ public object LoggingLevel { @Deprecated( message = "Use `LoggingLevel.Critical` instead", replaceWith = ReplaceWith("LoggingLevel.Critical", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val critical: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel = io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel.Critical @@ -1808,7 +1817,7 @@ public object LoggingLevel { @Deprecated( message = "Use `LoggingLevel.Alert` instead", replaceWith = ReplaceWith("LoggingLevel.Alert", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val alert: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel = io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel.Alert @@ -1816,7 +1825,7 @@ public object LoggingLevel { @Deprecated( message = "Use `LoggingLevel.Emergency` instead", replaceWith = ReplaceWith("LoggingLevel.Emergency", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val emergency: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel = io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel.Emergency @@ -1828,7 +1837,7 @@ public object LoggingLevel { "LoggingMessageNotification", "io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotification", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object LoggingMessageNotification { @Deprecated( @@ -1837,7 +1846,7 @@ public object LoggingMessageNotification { "LoggingMessageNotificationParams", "io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotificationParams", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Params( level: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel, @@ -1853,7 +1862,7 @@ public object LoggingMessageNotification { "SetLevelRequest", "io.modelcontextprotocol.kotlin.sdk.types.SetLevelRequest", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun SetLevelRequest( params: io.modelcontextprotocol.kotlin.sdk.types.SetLevelRequestParams, @@ -1872,7 +1881,7 @@ public object LoggingMessageNotification { "LoggingMessageNotification", "io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotification", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun LoggingMessageNotification( params: io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotificationParams, @@ -1887,7 +1896,7 @@ public fun LoggingMessageNotification( @Deprecated( message = "Use `ModelHint` instead", replaceWith = ReplaceWith("ModelHint", "io.modelcontextprotocol.kotlin.sdk.types.ModelHint"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ModelHint = io.modelcontextprotocol.kotlin.sdk.types.ModelHint @@ -1897,7 +1906,7 @@ public typealias ModelHint = io.modelcontextprotocol.kotlin.sdk.types.ModelHint @Deprecated( message = "Use `ModelPreferences` instead", replaceWith = ReplaceWith("ModelPreferences", "io.modelcontextprotocol.kotlin.sdk.types.ModelPreferences"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ModelPreferences = io.modelcontextprotocol.kotlin.sdk.types.ModelPreferences @@ -1907,14 +1916,14 @@ public typealias ModelPreferences = io.modelcontextprotocol.kotlin.sdk.types.Mod @Deprecated( message = "Use `SamplingMessage` instead", replaceWith = ReplaceWith("SamplingMessage", "io.modelcontextprotocol.kotlin.sdk.types.SamplingMessage"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias SamplingMessage = io.modelcontextprotocol.kotlin.sdk.types.SamplingMessage @Deprecated( message = "Use `CreateMessageRequest` instead", replaceWith = ReplaceWith("CreateMessageRequest", "io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object CreateMessageRequest { public val IncludeContext: io.modelcontextprotocol.kotlin.sdk.types.IncludeContext.Companion = @@ -1930,7 +1939,7 @@ public object CreateMessageRequest { @Deprecated( message = "Use `CreateMessageRequest` instead", replaceWith = ReplaceWith("CreateMessageRequest", "io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun CreateMessageRequest( params: io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequestParams, @@ -1940,7 +1949,7 @@ public fun CreateMessageRequest( @Deprecated( message = "Use `StopReason` instead", replaceWith = ReplaceWith("StopReason", "io.modelcontextprotocol.kotlin.sdk.types.StopReason"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias StopReason = io.modelcontextprotocol.kotlin.sdk.types.StopReason @@ -1952,7 +1961,7 @@ public typealias StopReason = io.modelcontextprotocol.kotlin.sdk.types.StopReaso @Deprecated( message = "Use `CreateMessageResult` instead", replaceWith = ReplaceWith("CreateMessageResult", "io.modelcontextprotocol.kotlin.sdk.types.CreateMessageResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias CreateMessageResult = io.modelcontextprotocol.kotlin.sdk.types.CreateMessageResult @@ -1960,7 +1969,7 @@ public typealias CreateMessageResult = io.modelcontextprotocol.kotlin.sdk.types. @Deprecated( message = "Use `Reference` instead", replaceWith = ReplaceWith("Reference", "io.modelcontextprotocol.kotlin.sdk.types.Reference"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias Reference = io.modelcontextprotocol.kotlin.sdk.types.Reference @@ -1973,7 +1982,7 @@ public typealias Reference = io.modelcontextprotocol.kotlin.sdk.types.Reference "ResourceTemplateReference", "io.modelcontextprotocol.kotlin.sdk.types.ResourceTemplateReference", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ResourceTemplateReference = io.modelcontextprotocol.kotlin.sdk.types.ResourceTemplateReference @@ -1983,7 +1992,7 @@ public typealias ResourceTemplateReference = io.modelcontextprotocol.kotlin.sdk. @Deprecated( message = "Use `PromptReference` instead", replaceWith = ReplaceWith("PromptReference", "io.modelcontextprotocol.kotlin.sdk.types.PromptReference"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias PromptReference = io.modelcontextprotocol.kotlin.sdk.types.PromptReference @@ -1993,14 +2002,14 @@ public typealias PromptReference = io.modelcontextprotocol.kotlin.sdk.types.Prom @Serializable @Deprecated( message = "This class will be removed.", - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public data class UnknownReference(val type: String) @Deprecated( message = "Use `CompleteRequest` instead", replaceWith = ReplaceWith("CompleteRequest", "io.modelcontextprotocol.kotlin.sdk.types.CompleteRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object CompleteRequest { @Deprecated( @@ -2009,7 +2018,7 @@ public object CompleteRequest { "CompleteRequestParams.Argument", "io.modelcontextprotocol.kotlin.sdk.types.CompleteRequestParams.Argument", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Argument( name: String, @@ -2024,7 +2033,7 @@ public object CompleteRequest { @Deprecated( message = "Use `CompleteRequest` instead", replaceWith = ReplaceWith("CompleteRequest", "io.modelcontextprotocol.kotlin.sdk.types.CompleteRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun CompleteRequest( params: io.modelcontextprotocol.kotlin.sdk.types.CompleteRequestParams, @@ -2034,7 +2043,7 @@ public fun CompleteRequest( @Deprecated( message = "Use `CompleteResult` instead", replaceWith = ReplaceWith("CompleteResult", "io.modelcontextprotocol.kotlin.sdk.types.CompleteResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object CompleteResult { @Deprecated( @@ -2043,7 +2052,7 @@ public object CompleteResult { "CompleteResult.Completion", "io.modelcontextprotocol.kotlin.sdk.types.CompleteResult.Completion", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Completion( values: List, @@ -2059,7 +2068,7 @@ public object CompleteResult { @Deprecated( message = "Use `CompleteResult` instead", replaceWith = ReplaceWith("CompleteResult", "io.modelcontextprotocol.kotlin.sdk.types.CompleteResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun CompleteResult( completion: io.modelcontextprotocol.kotlin.sdk.types.CompleteResult.Completion, @@ -2075,7 +2084,7 @@ public fun CompleteResult( @Deprecated( message = "Use `Root` instead", replaceWith = ReplaceWith("Root", "io.modelcontextprotocol.kotlin.sdk.types.Root"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias Root = io.modelcontextprotocol.kotlin.sdk.types.Root @@ -2085,7 +2094,7 @@ public typealias Root = io.modelcontextprotocol.kotlin.sdk.types.Root @Deprecated( message = "Use `ListRootsRequest` instead", replaceWith = ReplaceWith("ListRootsRequest", "io.modelcontextprotocol.kotlin.sdk.types.ListRootsRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ListRootsRequest = io.modelcontextprotocol.kotlin.sdk.types.ListRootsRequest @@ -2095,7 +2104,7 @@ public typealias ListRootsRequest = io.modelcontextprotocol.kotlin.sdk.types.Lis @Deprecated( message = "Use `ListRootsResult` instead", replaceWith = ReplaceWith("ListRootsResult", "io.modelcontextprotocol.kotlin.sdk.types.ListRootsResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ListRootsResult = io.modelcontextprotocol.kotlin.sdk.types.ListRootsResult @@ -2105,7 +2114,7 @@ public typealias ListRootsResult = io.modelcontextprotocol.kotlin.sdk.types.List "RootsListChangedNotification", "io.modelcontextprotocol.kotlin.sdk.types.RootsListChangedNotification", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object RootsListChangedNotification { @Deprecated( @@ -2114,7 +2123,7 @@ public object RootsListChangedNotification { "BaseNotificationParams", "io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Params(meta: JsonObject? = null): io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams = io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams(meta) @@ -2129,7 +2138,7 @@ public object RootsListChangedNotification { "RootsListChangedNotification", "io.modelcontextprotocol.kotlin.sdk.types.RootsListChangedNotification", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun RootsListChangedNotification( params: io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams? = null, @@ -2142,7 +2151,7 @@ public fun RootsListChangedNotification( "ElicitRequest", "io.modelcontextprotocol.kotlin.sdk.types.ElicitRequest", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object CreateElicitationRequest { public fun RequestedSchema( @@ -2161,7 +2170,7 @@ public object CreateElicitationRequest { "ElicitRequest", "io.modelcontextprotocol.kotlin.sdk.types.ElicitRequest", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun CreateElicitationRequest( params: io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestParams, @@ -2174,7 +2183,7 @@ public fun CreateElicitationRequest( "ElicitResult", "io.modelcontextprotocol.kotlin.sdk.types.ElicitResult", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object CreateElicitationResult { @Deprecated( @@ -2183,7 +2192,7 @@ public object CreateElicitationResult { "ElicitResult.Action", "io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object Action { @Deprecated( @@ -2192,7 +2201,7 @@ public object CreateElicitationResult { "ElicitResult.Action.Accept", "io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val accept: io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action = io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action.Accept @@ -2203,7 +2212,7 @@ public object CreateElicitationResult { "ElicitResult.Action.Decline", "io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val decline: io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action = io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action.Decline @@ -2214,7 +2223,7 @@ public object CreateElicitationResult { "ElicitResult.Action.Cancel", "io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val cancel: io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action = io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action.Cancel @@ -2230,7 +2239,7 @@ public object CreateElicitationResult { "ElicitResult", "io.modelcontextprotocol.kotlin.sdk.types.ElicitResult", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun CreateElicitationResult( action: io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action, @@ -2249,6 +2258,6 @@ public fun CreateElicitationResult( @Deprecated( message = "Use `McpException` instead", replaceWith = ReplaceWith("McpException"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias McpError = io.modelcontextprotocol.kotlin.sdk.types.McpException diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.util.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.util.kt index 305efe7..3e0abe1 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.util.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.util.kt @@ -7,7 +7,7 @@ import kotlinx.serialization.json.JsonObject @Deprecated( message = "Use `io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject` instead", replaceWith = ReplaceWith("EmptyJsonObject", "io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val EmptyJsonObject: JsonObject = io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject @@ -17,8 +17,9 @@ public val EmptyJsonObject: JsonObject = io.modelcontextprotocol.kotlin.sdk.type @Deprecated( message = "Use `CallToolResult.success` instead", replaceWith = ReplaceWith("CallToolResult.Companion.success"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) +@Suppress("DEPRECATION_ERROR") public fun CallToolResult.ok( content: String, meta: JsonObject = EmptyJsonObject, @@ -31,8 +32,9 @@ public fun CallToolResult.ok( @Deprecated( message = "Use `CallToolResult.error` instead", replaceWith = ReplaceWith("CallToolResult.Companion.error"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) +@Suppress("DEPRECATION_ERROR") public fun CallToolResult.error( content: String, meta: JsonObject = EmptyJsonObject, diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt index e22dab7..5e39d1f 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt @@ -33,6 +33,7 @@ public sealed interface WithMeta { @Deprecated( message = "Use 'meta' instead.", replaceWith = ReplaceWith("meta"), + level = DeprecationLevel.ERROR, ) @Suppress("PropertyName", "VariableNaming") public val _meta: JsonObject diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.kt index 59f558c..5ca41c6 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.kt @@ -43,7 +43,7 @@ public data class CompleteRequest(override val params: CompleteRequestParams) : @Deprecated( message = "Will be removed in a future release.", replaceWith = ReplaceWith(expression = "CompleteRequest(CompleteRequestParams(argument, ref, context, meta))"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public constructor( argument: Argument, diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.kt index 68a892f..08a7a9f 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.kt @@ -116,6 +116,7 @@ public data class GetPromptRequest(override val params: GetPromptRequestParams) @Deprecated( message = "Use constructor with GetPromptRequestParams instead", replaceWith = ReplaceWith("GetPromptRequest(GetPromptRequestParams(name, arguments, meta))"), + level = DeprecationLevel.ERROR, ) public constructor( name: String, @@ -217,7 +218,7 @@ public data class ListPromptsRequest(override val params: PaginatedRequestParams @Deprecated( message = "Use constructor with ListPromptsParams instead", replaceWith = ReplaceWith("ListPromptsRequest(ListPromptsParams(name, arguments, meta))"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public constructor( cursor: String?, diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.kt index ba9533e..8782db1 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.kt @@ -205,7 +205,7 @@ public data class ListResourcesRequest(override val params: PaginatedRequestPara @Deprecated( message = "Use the constructor with PaginatedRequestParams property instead", replaceWith = ReplaceWith("ReadResourceRequest(PaginatedRequestParams(cursor, meta))"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public constructor( cursor: String?, @@ -265,7 +265,7 @@ public data class ReadResourceRequest(override val params: ReadResourceRequestPa @Deprecated( message = "Use the constructor with ReadResourceRequestParams property instead", replaceWith = ReplaceWith("ReadResourceRequest(ReadResourceRequestParams(uri, meta))"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public constructor( uri: String, @@ -341,7 +341,7 @@ public data class SubscribeRequest(override val params: SubscribeRequestParams) @Deprecated( message = "Use the constructor with SubscribeRequestParams property instead", replaceWith = ReplaceWith("ReadResourceRequest(SubscribeRequestParams(uri, meta))"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public constructor( uri: String, @@ -447,7 +447,7 @@ public data class ListResourceTemplatesRequest(override val params: PaginatedReq @Deprecated( message = "Use the constructor with PaginatedRequestParams property instead", replaceWith = ReplaceWith("ListResourceTemplatesRequest(PaginatedRequestParams(cursor, meta))"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public constructor( cursor: String?, diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.kt index cc08739..9e7dbae 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.kt @@ -66,7 +66,7 @@ public data class ListRootsRequest(override val params: BaseRequestParams? = nul @Deprecated( message = "Use the constructor with BaseRequestParams property instead", replaceWith = ReplaceWith("ListRootsRequest(BaseRequestParams(meta))"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public constructor( meta: RequestMeta?, diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt index 6cba208..767e724 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt @@ -240,7 +240,7 @@ public data class ListToolsRequest(override val params: PaginatedRequestParams? @Deprecated( message = "Use the constructor with BaseRequestParams property instead", replaceWith = ReplaceWith("ListToolsRequest(BaseRequestParams(meta))"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public constructor( cursor: String?, diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaAudioContentSerializationTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaAudioContentSerializationTest.kt deleted file mode 100644 index 738539f..0000000 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaAudioContentSerializationTest.kt +++ /dev/null @@ -1,33 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk - -import io.kotest.assertions.json.shouldEqualJson -import io.modelcontextprotocol.kotlin.sdk.shared.McpJson -import kotlin.test.Test -import kotlin.test.assertEquals - -class OldSchemaAudioContentSerializationTest { - - private val audioContentJson = """ - { - "data": "base64-encoded-audio-data", - "mimeType": "audio/wav", - "type": "audio" - } - """.trimIndent() - - private val audioContent = AudioContent( - data = "base64-encoded-audio-data", - mimeType = "audio/wav", - ) - - @Test - fun `should serialize audio content`() { - McpJson.encodeToString(audioContent) shouldEqualJson audioContentJson - } - - @Test - fun `should deserialize audio content`() { - val content = McpJson.decodeFromString(audioContentJson) - assertEquals(expected = audioContent, actual = content) - } -} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaCallToolResultUtilsTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaCallToolResultUtilsTest.kt deleted file mode 100644 index b7bac6d..0000000 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaCallToolResultUtilsTest.kt +++ /dev/null @@ -1,63 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk - -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.buildJsonObject -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -class OldSchemaCallToolResultUtilsTest { - - @Test - fun testOkWithOnlyText() { - val content = "TextMessage" - val result = CallToolResult.ok(content) - - assertEquals(1, result.content.size) - assertEquals(content, (result.content[0] as TextContent).text) - assertFalse(result.isError == true) - assertEquals(EmptyJsonObject, result.meta) - } - - @Test - fun testOkWithMeta() { - val content = "TextMessageWithMeta" - val meta = buildJsonObject { - put("key1", JsonPrimitive("value1")) - put("key2", JsonPrimitive(42)) - } - val result = CallToolResult.ok(content, meta) - - assertEquals(1, result.content.size) - assertEquals(content, (result.content[0] as TextContent).text) - assertFalse(result.isError == true) - assertEquals(meta, result.meta) - } - - @Test - fun testErrorWithOnlyText() { - val content = "ErrorMessage" - val result = CallToolResult.error(content) - - assertEquals(1, result.content.size) - assertEquals(content, (result.content[0] as TextContent).text) - assertTrue(result.isError == true) - assertEquals(EmptyJsonObject, result.meta) - } - - @Test - fun testErrorWithMeta() { - val content = "ErrorMessageWithMeta" - val meta = buildJsonObject { - put("errorCode", JsonPrimitive(404)) - put("errorDetail", JsonPrimitive("资源未找到")) - } - val result = CallToolResult.error(content, meta) - - assertEquals(1, result.content.size) - assertEquals(content, (result.content[0] as TextContent).text) - assertTrue(result.isError == true) - assertEquals(meta, result.meta) - } -} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaRequestSerializerTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaRequestSerializerTest.kt deleted file mode 100644 index 2360b5b..0000000 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaRequestSerializerTest.kt +++ /dev/null @@ -1,329 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk - -import io.modelcontextprotocol.kotlin.sdk.shared.McpJson -import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult -import io.modelcontextprotocol.kotlin.sdk.types.CompleteResult -import io.modelcontextprotocol.kotlin.sdk.types.ElicitResult -import kotlinx.serialization.SerializationException -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.put -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertIs - -class OldSchemaRequestSerializerTest { - - // Client Result Tests - @Test - fun `should deserialize CreateMessageResult polymorphically`() { - val json = """{ - "model": "test-model", - "role": "assistant", - "content": { - "type": "text", - "text": "Hello" - }, - "stopReason": "endTurn" - }""" - - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals("test-model", decoded.model) - assertEquals(Role.assistant, decoded.role) - assertEquals(StopReason.EndTurn, decoded.stopReason) - } - - @Test - fun `should deserialize ListRootsResult polymorphically`() { - val json = """{ - "roots": [ - { - "uri": "file:///test", - "name": "Test Root" - } - ] - }""" - - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals(1, decoded.roots.size) - assertEquals("file:///test", decoded.roots[0].uri) - assertEquals("Test Root", decoded.roots[0].name) - } - - @Test - fun `should deserialize CreateElicitationResult polymorphically`() { - val json = """{ - "action": "accept", - "content": { - "timezone": "Europe/Amsterdam" - } - }""" - - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals(CreateElicitationResult.Action.accept, decoded.action) - } - - // Server Result Tests - @Test - fun `should deserialize ListToolsResult polymorphically`() { - val json = """{ - "tools": [ - { - "name": "test-tool", - "description": "A test tool", - "inputSchema": { - "type": "object", - "properties": {} - }, - "_meta": {} - } - ] - }""" - - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals(1, decoded.tools.size) - assertEquals("test-tool", decoded.tools[0].name) - assertEquals("A test tool", decoded.tools[0].description) - } - - @Test - fun `should deserialize ListResourcesResult polymorphically`() { - val json = """{ - "resources": [ - { - "uri": "file:///test.txt", - "name": "test.txt", - "mimeType": "text/plain" - } - ] - }""" - - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals(1, decoded.resources.size) - assertEquals("file:///test.txt", decoded.resources[0].uri) - assertEquals("test.txt", decoded.resources[0].name) - assertEquals("text/plain", decoded.resources[0].mimeType) - } - - @Test - fun `should deserialize ListResourceTemplatesResult polymorphically`() { - val json = """{ - "resourceTemplates": [ - { - "uriTemplate": "file:///templates/{name}", - "name": "template", - "mimeType": "text/plain" - } - ] - }""" - - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals(1, decoded.resourceTemplates.size) - assertEquals("file:///templates/{name}", decoded.resourceTemplates[0].uriTemplate) - assertEquals("template", decoded.resourceTemplates[0].name) - assertEquals("text/plain", decoded.resourceTemplates[0].mimeType) - } - - @Test - fun `should deserialize ListPromptsResult polymorphically`() { - val json = """{ - "prompts": [ - { - "name": "test-prompt", - "description": "A test prompt" - } - ] - }""" - - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals(1, decoded.prompts.size) - assertEquals("test-prompt", decoded.prompts[0].name) - assertEquals("A test prompt", decoded.prompts[0].description) - } - - @Test - fun `should deserialize InitializeResult polymorphically`() { - val json = """{ - "capabilities": { - "logging": {}, - "prompts": { - "listChanged": true - }, - "resources": { - "subscribe": true, - "listChanged": true - }, - "tools": { - "listChanged": true - } - }, - "protocolVersion": "2024-11-05", - "serverInfo": { - "name": "Test Server", - "version": "1.0.0" - } - }""" - - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals("2024-11-05", decoded.protocolVersion) - assertEquals("Test Server", decoded.serverInfo.name) - assertEquals("1.0.0", decoded.serverInfo.version) - } - - @Test - fun `should deserialize GetPromptResult polymorphically`() { - val json = """{ - "description": "A test prompt", - "messages": [ - { - "role": "user", - "content": { - "type": "text", - "text": "Hello" - } - } - ] - }""" - - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals("A test prompt", decoded.description) - assertEquals(1, decoded.messages.size) - assertEquals(Role.user, decoded.messages[0].role) - } - - @Test - fun `should deserialize CompleteResult polymorphically`() { - val json = """{ - "completion": { - "values": ["option1", "option2"], - "total": 2, - "hasMore": false - } - }""" - - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals(2, decoded.completion.values.size) - assertEquals("option1", decoded.completion.values[0]) - assertEquals("option2", decoded.completion.values[1]) - assertEquals(2, decoded.completion.total) - assertEquals(false, decoded.completion.hasMore) - } - - @Test - fun `should deserialize ReadResourceResult polymorphically`() { - val json = """{ - "contents": [ - { - "uri": "file:///test.txt", - "mimeType": "text/plain", - "text": "Hello World" - } - ] - }""" - - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals(1, decoded.contents.size) - assertIs(decoded.contents[0]) - val textContent = decoded.contents[0] as TextResourceContents - assertEquals("file:///test.txt", textContent.uri) - assertEquals("text/plain", textContent.mimeType) - assertEquals("Hello World", textContent.text) - } - - @Test - fun `should deserialize CallToolResult polymorphically`() { - val json = """{ - "content": [ - { - "type": "text", - "text": "Tool result" - } - ], - "isError": false - }""" - - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals(1, decoded.content.size) - assertIs(decoded.content[0]) - assertEquals("Tool result", (decoded.content[0] as TextContent).text) - assertEquals(false, decoded.isError) - } - - @Test - fun `should deserialize CompatibilityCallToolResult polymorphically`() { - val json = """{ - "toolResult": {"result": "Legacy tool result"}, - "content": [], - "isError": false - }""" - - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals(buildJsonObject { put("result", "Legacy tool result") }, decoded.toolResult) - } - - // Fallback Test - @Test - fun `should deserialize EmptyRequestResult for unknown result type`() { - val json = """{"unknownField": "value"}""" - - assertFailsWith { - McpJson.decodeFromString(json) - } - } - - @Test - fun `should handle empty JSON object`() { - val json = """{}""" - - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - } - - // Priority Test - Client results should take precedence over server results - @Test - fun `should prioritize client results over server results when both match`() { - // This JSON could potentially match both CreateMessageResult (client) and CallToolResult (server) - // but CreateMessageResult should be selected first due to the order - val json = """{ - "model": "test-model", - "role": "assistant", - "content": { - "type": "text", - "text": "Test message" - }, - "stopReason": "endTurn" - }""" - - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals("test-model", decoded.model) - } -} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaToolSerializationTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaToolSerializationTest.kt deleted file mode 100644 index 7e96dcf..0000000 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaToolSerializationTest.kt +++ /dev/null @@ -1,507 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk - -import io.kotest.assertions.json.shouldEqualJson -import io.modelcontextprotocol.kotlin.sdk.shared.McpJson -import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.buildJsonObject -import kotlin.test.Test -import kotlin.test.assertEquals - -class OldSchemaToolSerializationTest { - - // see https://docs.anthropic.com/en/docs/build-with-claude/tool-use - /* language=json */ - private val getWeatherToolJson = """ - { - "name": "get_weather", - "title": "Get weather", - "description": "Get the current weather in a given location", - "inputSchema": { - "type": "object", - "properties": { - "location": { - "type": "string", - "description": "The city and state, e.g. San Francisco, CA" - } - }, - "required": ["location"] - }, - "outputSchema": { - "type": "object", - "properties": { - "temperature": { - "type": "number", - "description": "Temperature in celsius" - }, - "conditions": { - "type": "string", - "description": "Weather conditions description" - }, - "humidity": { - "type": "number", - "description": "Humidity percentage" - } - }, - "required": ["temperature", "conditions", "humidity"] - }, - "_meta": { - "_for_test_only": true - } - } - """.trimIndent() - - val getWeatherTool = Tool( - name = "get_weather", - title = "Get weather", - description = "Get the current weather in a given location", - annotations = null, - inputSchema = Tool.Input( - properties = buildJsonObject { - put( - "location", - buildJsonObject { - put("type", JsonPrimitive("string")) - put("description", JsonPrimitive("The city and state, e.g. San Francisco, CA")) - }, - ) - }, - required = listOf("location"), - ), - outputSchema = Tool.Output( - properties = buildJsonObject { - put( - "temperature", - buildJsonObject { - put("type", JsonPrimitive("number")) - put("description", JsonPrimitive("Temperature in celsius")) - }, - ) - put( - "conditions", - buildJsonObject { - put("type", JsonPrimitive("string")) - put("description", JsonPrimitive("Weather conditions description")) - }, - ) - put( - "humidity", - buildJsonObject { - put("type", JsonPrimitive("number")) - put("description", JsonPrimitive("Humidity percentage")) - }, - ) - }, - required = listOf("temperature", "conditions", "humidity"), - ), - _meta = buildJsonObject { - put("_for_test_only", JsonPrimitive(true)) - }, - ) - - //region Serialize - - @Test - fun `should serialize get_weather tool`() { - McpJson.encodeToString(getWeatherTool) shouldEqualJson getWeatherToolJson - } - - @Test - fun `should always serialize default value`() { - val json = Json(from = McpJson) { - encodeDefaults = false - } - json.encodeToString(getWeatherTool) shouldEqualJson getWeatherToolJson - } - - @Test - fun `should serialize get_weather tool without optional properties`() { - val weatherTool = createWeatherTool(name = "get_weather") - val expectedJson = createWeatherToolJson(name = "get_weather") - val actualJson = McpJson.encodeToString(weatherTool) - - actualJson shouldEqualJson expectedJson - } - - @Test - fun `should serialize get_weather tool with title optional property specified`() { - val weatherTool = createWeatherTool(name = "get_weather", title = "Get weather") - val expectedJson = createWeatherToolJson(name = "get_weather", title = "Get weather") - val actualJson = McpJson.encodeToString(weatherTool) - - actualJson shouldEqualJson expectedJson - } - - @Test - fun `should serialize get_weather tool with outputSchema optional property specified`() { - val weatherTool = createWeatherTool( - name = "get_weather", - outputSchema = Tool.Output( - properties = buildJsonObject { - put( - "temperature", - buildJsonObject { - put("type", JsonPrimitive("number")) - put("description", JsonPrimitive("Temperature in celsius")) - }, - ) - put( - "conditions", - buildJsonObject { - put("type", JsonPrimitive("string")) - put("description", JsonPrimitive("Weather conditions description")) - }, - ) - put( - "humidity", - buildJsonObject { - put("type", JsonPrimitive("number")) - put("description", JsonPrimitive("Humidity percentage")) - }, - ) - }, - required = listOf("temperature", "conditions", "humidity"), - ), - ) - val expectedJson = - createWeatherToolJson( - name = "get_weather", - outputSchema = """ - { - "type": "object", - "properties": { - "temperature": { - "type": "number", - "description": "Temperature in celsius" - }, - "conditions": { - "type": "string", - "description": "Weather conditions description" - }, - "humidity": { - "type": "number", - "description": "Humidity percentage" - } - }, - "required": ["temperature", "conditions", "humidity"] - } - """.trimIndent(), - ) - - val actualJson = McpJson.encodeToString(weatherTool) - - actualJson shouldEqualJson expectedJson - } - - @Test - fun `should serialize get_weather tool with all properties specified`() { - val weatherTool = createWeatherTool( - name = "get_weather", - title = "Get weather", - outputSchema = Tool.Output( - properties = buildJsonObject { - put( - "temperature", - buildJsonObject { - put("type", JsonPrimitive("number")) - put("description", JsonPrimitive("Temperature in celsius")) - }, - ) - put( - "conditions", - buildJsonObject { - put("type", JsonPrimitive("string")) - put("description", JsonPrimitive("Weather conditions description")) - }, - ) - put( - "humidity", - buildJsonObject { - put("type", JsonPrimitive("number")) - put("description", JsonPrimitive("Humidity percentage")) - }, - ) - }, - required = listOf("temperature", "conditions", "humidity"), - ), - ) - val expectedJson = createWeatherToolJson( - name = "get_weather", - title = "Get weather", - outputSchema = """ - { - "type": "object", - "properties": { - "temperature": { - "type": "number", - "description": "Temperature in celsius" - }, - "conditions": { - "type": "string", - "description": "Weather conditions description" - }, - "humidity": { - "type": "number", - "description": "Humidity percentage" - } - }, - "required": ["temperature", "conditions", "humidity"] - } - """.trimIndent(), - ) - - val actualJson = McpJson.encodeToString(weatherTool) - - actualJson shouldEqualJson expectedJson - } - - //endregion Serialize - - //region Deserialize - - @Test - fun `should deserialize get_weather tool`() { - val actualTool = McpJson.decodeFromString(getWeatherToolJson) - assertEquals(expected = getWeatherTool, actual = actualTool) - } - - @Test - fun `should deserialize get_weather tool without optional properties`() { - val toolJson = createWeatherToolJson(name = "get_weather") - val expectedTool = createWeatherTool(name = "get_weather") - val actualTool = McpJson.decodeFromString(toolJson) - - assertEquals(expected = expectedTool, actual = actualTool) - } - - @Test - fun `should deserialize get_weather tool with title properties specified`() { - val toolJson = createWeatherToolJson(name = "get_weather", title = "Get weather") - val expectedTool = createWeatherTool(name = "get_weather", title = "Get weather") - - val actualTool = McpJson.decodeFromString(toolJson) - - assertEquals(expected = expectedTool, actual = actualTool) - } - - @Test - fun `should deserialize get_weather tool with outputSchema optional property specified`() { - val toolJson = - createWeatherToolJson( - name = "get_weather", - outputSchema = """ - { - "type": "object", - "properties": { - "temperature": { - "type": "number", - "description": "Temperature in celsius" - }, - "conditions": { - "type": "string", - "description": "Weather conditions description" - }, - "humidity": { - "type": "number", - "description": "Humidity percentage" - } - }, - "required": ["temperature", "conditions", "humidity"] - } - """.trimIndent(), - ) - - val expectedTool = createWeatherTool( - name = "get_weather", - outputSchema = Tool.Output( - properties = buildJsonObject { - put( - "temperature", - buildJsonObject { - put("type", JsonPrimitive("number")) - put("description", JsonPrimitive("Temperature in celsius")) - }, - ) - put( - "conditions", - buildJsonObject { - put("type", JsonPrimitive("string")) - put("description", JsonPrimitive("Weather conditions description")) - }, - ) - put( - "humidity", - buildJsonObject { - put("type", JsonPrimitive("number")) - put("description", JsonPrimitive("Humidity percentage")) - }, - ) - }, - required = listOf("temperature", "conditions", "humidity"), - ), - ) - - val actualTool = McpJson.decodeFromString(toolJson) - - assertEquals(expected = expectedTool, actual = actualTool) - } - - @Test - fun `should deserialize get_weather tool with all properties specified`() { - val toolJson = createWeatherToolJson( - name = "get_weather", - title = "Get weather", - outputSchema = """ - { - "type": "object", - "properties": { - "temperature": { - "type": "number", - "description": "Temperature in celsius" - }, - "conditions": { - "type": "string", - "description": "Weather conditions description" - }, - "humidity": { - "type": "number", - "description": "Humidity percentage" - } - }, - "required": ["temperature", "conditions", "humidity"] - } - """.trimIndent(), - ) - - val expectedTool = createWeatherTool( - name = "get_weather", - title = "Get weather", - outputSchema = Tool.Output( - properties = buildJsonObject { - put( - "temperature", - buildJsonObject { - put("type", JsonPrimitive("number")) - put("description", JsonPrimitive("Temperature in celsius")) - }, - ) - put( - "conditions", - buildJsonObject { - put("type", JsonPrimitive("string")) - put("description", JsonPrimitive("Weather conditions description")) - }, - ) - put( - "humidity", - buildJsonObject { - put("type", JsonPrimitive("number")) - put("description", JsonPrimitive("Humidity percentage")) - }, - ) - }, - required = listOf("temperature", "conditions", "humidity"), - ), - ) - - val actualTool = McpJson.decodeFromString(toolJson) - - assertEquals(expected = expectedTool, actual = actualTool) - } - - //endregion Deserialize - - //region Private Methods - - private fun createWeatherToolJson( - name: String = "get_weather", - title: String? = null, - outputSchema: String? = null, - @Suppress("LocalVariableName") _meta: String? = null, - ): String { - val stringBuilder = StringBuilder() - - stringBuilder - .appendLine("{") - .append(" \"name\": \"$name\"") - - if (title != null) { - stringBuilder - .appendLine(",") - .append(" \"title\": \"$title\"") - } - - stringBuilder - .appendLine(",") - .append(" \"description\": \"Get the current weather in a given location\"") - .appendLine(",") - .append( - """ - "inputSchema": { - "type": "object", - "properties": { - "location": { - "type": "string", - "description": "The city and state, e.g. San Francisco, CA" - } - }, - "required": ["location"] - } - """.trimIndent(), - ) - - if (outputSchema != null) { - stringBuilder - .appendLine(",") - .append( - """ - "outputSchema": $outputSchema - """.trimIndent(), - ) - } - - stringBuilder - .appendLine(",") - .append( - """ - "_meta": ${_meta ?: "{}"} - """.trimIndent(), - ) - - stringBuilder - .appendLine() - .appendLine("}") - - return stringBuilder.toString().trimIndent() - } - - private fun createWeatherTool( - name: String = "get_weather", - title: String? = null, - outputSchema: ToolSchema? = null, - @Suppress("LocalVariableName") _meta: JsonObject? = null, - ): io.modelcontextprotocol.kotlin.sdk.types.Tool = Tool( - name = name, - title = title, - description = "Get the current weather in a given location", - annotations = null, - inputSchema = Tool.Input( - properties = buildJsonObject { - put( - "location", - buildJsonObject { - put("type", JsonPrimitive("string")) - put("description", JsonPrimitive("The city and state, e.g. San Francisco, CA")) - }, - ) - }, - required = listOf("location"), - ), - outputSchema = outputSchema, - _meta = _meta ?: EmptyJsonObject, - ) - - //endregion Private Methods -} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaTypesTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaTypesTest.kt deleted file mode 100644 index f8ad0be..0000000 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaTypesTest.kt +++ /dev/null @@ -1,450 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk - -import io.modelcontextprotocol.kotlin.sdk.shared.McpJson -import io.modelcontextprotocol.kotlin.sdk.types.CompleteRequestParams -import io.modelcontextprotocol.kotlin.sdk.types.ContentTypes -import io.modelcontextprotocol.kotlin.sdk.types.ReferenceType -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertIs -import kotlin.test.assertNotEquals -import kotlin.test.assertTrue -import kotlin.time.ExperimentalTime - -class OldSchemaTypesTest { - - @Test - fun `should have correct latest protocol version`() { - assertNotEquals("", LATEST_PROTOCOL_VERSION) - assertEquals("2025-06-18", LATEST_PROTOCOL_VERSION) - } - - @Test - fun `should have correct supported protocol versions`() { - assertIs>(SUPPORTED_PROTOCOL_VERSIONS) - assertTrue(SUPPORTED_PROTOCOL_VERSIONS.contains(LATEST_PROTOCOL_VERSION)) - assertTrue(SUPPORTED_PROTOCOL_VERSIONS.contains("2024-11-05")) - assertEquals(3, SUPPORTED_PROTOCOL_VERSIONS.size) - } - - @Test - fun `should validate JSONRPC version constant`() { - assertEquals("2.0", JSONRPC_VERSION) - } - - // Reference Tests - @Test - fun `should validate ResourceTemplateReference`() { - val resourceRef = ResourceTemplateReference(uri = "file:///path/to/file.txt") - - assertEquals(ReferenceType.ResourceTemplate, resourceRef.type) - assertEquals("file:///path/to/file.txt", resourceRef.uri) - } - - @Test - fun `should serialize and deserialize ResourceTemplateReference correctly`() { - val resourceRef = ResourceTemplateReference(uri = "https://example.com/resource") - - val json = McpJson.encodeToString(resourceRef) - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals(ReferenceType.ResourceTemplate, decoded.type) - assertEquals("https://example.com/resource", decoded.uri) - } - - @Test - fun `should validate PromptReference`() { - val promptRef = PromptReference(name = "greeting") - - assertEquals(ReferenceType.Prompt, promptRef.type) - assertEquals("greeting", promptRef.name) - } - - @Test - fun `should serialize and deserialize PromptReference correctly`() { - val promptRef = PromptReference(name = "test-prompt") - - val json = McpJson.encodeToString(promptRef) - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals(ReferenceType.Prompt, decoded.type) - assertEquals("test-prompt", decoded.name) - } - - // PromptMessageContent Tests - @Test - fun `should validate text content`() { - val textContent = TextContent(text = "Hello, world!") - - assertEquals(ContentTypes.TEXT, textContent.type) - assertEquals("Hello, world!", textContent.text) - } - - @Test - fun `should serialize and deserialize text content correctly`() { - val textContent = TextContent(text = "Test message") - - val json = McpJson.encodeToString(textContent) - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals(ContentTypes.TEXT, decoded.type) - assertEquals("Test message", decoded.text) - } - - @Test - fun `should validate image content`() { - val imageContent = ImageContent( - data = "aGVsbG8=", // base64 encoded "hello" - mimeType = "image/png", - ) - - assertEquals(ContentTypes.IMAGE, imageContent.type) - assertEquals("aGVsbG8=", imageContent.data) - assertEquals("image/png", imageContent.mimeType) - } - - @Test - fun `should serialize and deserialize image content correctly`() { - val imageContent = ImageContent( - data = "dGVzdA==", // base64 encoded "test" - mimeType = "image/jpeg", - ) - - val json = McpJson.encodeToString(imageContent) - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals(ContentTypes.IMAGE, decoded.type) - assertEquals("dGVzdA==", decoded.data) - assertEquals("image/jpeg", decoded.mimeType) - } - - @Test - fun `should validate audio content`() { - val audioContent = AudioContent( - data = "aGVsbG8=", // base64 encoded "hello" - mimeType = "audio/mp3", - ) - - assertEquals(ContentTypes.AUDIO, audioContent.type) - assertEquals("aGVsbG8=", audioContent.data) - assertEquals("audio/mp3", audioContent.mimeType) - } - - @Test - fun `should serialize and deserialize audio content correctly`() { - val audioContent = AudioContent( - data = "YXVkaW8=", // base64 encoded "audio" - mimeType = "audio/wav", - ) - - val json = McpJson.encodeToString(audioContent) - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals(ContentTypes.AUDIO, decoded.type) - assertEquals("YXVkaW8=", decoded.data) - assertEquals("audio/wav", decoded.mimeType) - } - - @Test - fun `should validate embedded resource content`() { - val resource = TextResourceContents( - text = "File contents", - uri = "file:///path/to/file.txt", - mimeType = "text/plain", - ) - val embeddedResource = EmbeddedResource(resource = resource) - - assertEquals(ContentTypes.EMBEDDED_RESOURCE, embeddedResource.type) - assertEquals(resource, embeddedResource.resource) - } - - @Test - fun `should serialize and deserialize embedded resource content correctly`() { - val resource = BlobResourceContents( - blob = "YmluYXJ5ZGF0YQ==", - uri = "file:///path/to/binary.dat", - mimeType = "application/octet-stream", - ) - val embeddedResource = EmbeddedResource(resource = resource) - - val json = McpJson.encodeToString(embeddedResource) - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals(ContentTypes.EMBEDDED_RESOURCE, decoded.type) - assertIs(decoded.resource) - val decodedBlob = decoded.resource - assertEquals("YmluYXJ5ZGF0YQ==", decodedBlob.blob) - assertEquals("file:///path/to/binary.dat", decodedBlob.uri) - assertEquals("application/octet-stream", decodedBlob.mimeType) - } - - // PromptMessage Tests - @Test - fun `should validate prompt message with text content`() { - val textContent = TextContent(text = "Hello, assistant!") - val promptMessage = PromptMessage( - role = Role.user, - content = textContent, - ) - - assertEquals(Role.user, promptMessage.role) - assertEquals(textContent, promptMessage.content) - assertEquals(ContentTypes.TEXT, promptMessage.content.type) - } - - @Test - fun `should validate prompt message with embedded resource`() { - val resource = TextResourceContents( - text = "Primary application entry point", - uri = "file:///project/src/main.rs", - mimeType = "text/x-rust", - ) - val embeddedResource = EmbeddedResource(resource = resource) - val promptMessage = PromptMessage( - role = Role.assistant, - content = embeddedResource, - ) - - assertEquals(Role.assistant, promptMessage.role) - assertEquals(ContentTypes.EMBEDDED_RESOURCE, promptMessage.content.type) - val content = promptMessage.content as EmbeddedResource - val textResource = content.resource as TextResourceContents - assertEquals("Primary application entry point", textResource.text) - assertEquals("file:///project/src/main.rs", textResource.uri) - assertEquals("text/x-rust", textResource.mimeType) - } - - @OptIn(ExperimentalTime::class) - @Test - fun `should serialize and deserialize annotations correctly`() { - val annotations = Annotations( - audience = listOf(Role.assistant), - lastModified = "2025-06-18T00:00:00Z", - priority = 0.5, - ) - - val json = McpJson.encodeToString(annotations) - val decoded = McpJson.decodeFromString(json) - - assertEquals(listOf(Role.assistant), decoded.audience) - assertEquals("2025-06-18T00:00:00Z", decoded.lastModified) - assertEquals(0.5, decoded.priority) - } - - @Test - fun `should serialize and deserialize prompt message correctly`() { - val imageContent = ImageContent( - data = "aW1hZ2VkYXRh", // base64 encoded "imagedata" - mimeType = "image/png", - ) - val promptMessage = PromptMessage( - role = Role.assistant, - content = imageContent, - ) - - val json = McpJson.encodeToString(promptMessage) - val decoded = McpJson.decodeFromString(json) - - assertEquals(Role.assistant, decoded.role) - assertIs(decoded.content) - val decodedContent = decoded.content - assertEquals("aW1hZ2VkYXRh", decodedContent.data) - assertEquals("image/png", decodedContent.mimeType) - } - - // CallToolResult Tests - @Test - fun `should validate tool result with multiple content types`() { - val toolResult = CallToolResult( - content = listOf( - TextContent(text = "Found the following files:"), - EmbeddedResource( - resource = TextResourceContents( - text = "fn main() {}", - uri = "file:///project/src/main.rs", - mimeType = "text/x-rust", - ), - ), - EmbeddedResource( - resource = TextResourceContents( - text = "pub mod lib;", - uri = "file:///project/src/lib.rs", - mimeType = "text/x-rust", - ), - ), - ), - ) - - assertEquals(3, toolResult.content.size) - assertEquals(ContentTypes.TEXT, toolResult.content[0].type) - assertEquals(ContentTypes.EMBEDDED_RESOURCE, toolResult.content[1].type) - assertEquals(ContentTypes.EMBEDDED_RESOURCE, toolResult.content[2].type) - assertEquals(null, toolResult.isError) - } - - @Test - fun `should validate empty content array with default`() { - val toolResult = CallToolResult(content = emptyList()) - - assertEquals(0, toolResult.content.size) - assertEquals(null, toolResult.isError) - } - - @Test - fun `should serialize and deserialize CallToolResult correctly`() { - val toolResult = CallToolResult( - content = listOf( - TextContent(text = "Operation completed"), - ImageContent(data = "aW1hZ2U=", mimeType = "image/png"), - ), - isError = false, - ) - - val json = McpJson.encodeToString(toolResult) - val decoded = McpJson.decodeFromString(json) - - assertEquals(2, decoded.content.size) - assertIs(decoded.content[0]) - assertIs(decoded.content[1]) - assertEquals(false, decoded.isError) - } - - // CompleteRequest Tests - @Test - fun `should validate CompleteRequest with prompt reference`() { - val request = CompleteRequest( - CompleteRequestParams( - ref = PromptReference(name = "greeting"), - argument = CompleteRequest.Argument(name = "name", value = "A"), - ), - ) - - assertEquals("completion/complete", request.method.value) - assertIs(request.params.ref) - val promptRef = request.params.ref - assertEquals("greeting", promptRef.name) - assertEquals("name", request.params.argument.name) - assertEquals("A", request.params.argument.value) - } - - @Test - fun `should validate CompleteRequest with resource reference`() { - val request = CompleteRequest( - CompleteRequestParams( - ref = ResourceTemplateReference(uri = "github://repos/{owner}/{repo}"), - argument = CompleteRequest.Argument(name = "repo", value = "t"), - ), - ) - - assertEquals("completion/complete", request.method.value) - assertIs(request.params.ref) - val resourceRef = request.params.ref - assertEquals("github://repos/{owner}/{repo}", resourceRef.uri) - assertEquals("repo", request.params.argument.name) - assertEquals("t", request.params.argument.value) - } - - @Test - fun `should serialize and deserialize CompleteRequest correctly`() { - val request = CompleteRequest( - CompleteRequestParams( - ref = PromptReference(name = "test"), - argument = CompleteRequest.Argument(name = "arg", value = ""), - ), - ) - - val json = McpJson.encodeToString(request) - val decoded = McpJson.decodeFromString(json) - - assertEquals("completion/complete", decoded.method.value) - assertIs(decoded.params.ref) - val promptRef = decoded.params.ref - assertEquals("test", promptRef.name) - assertEquals("arg", decoded.params.argument.name) - assertEquals("", decoded.params.argument.value) - } - - @Test - fun `should validate CompleteRequest with complex URIs`() { - val request = CompleteRequest( - CompleteRequestParams( - ref = ResourceTemplateReference(uri = "api://v1/{tenant}/{resource}/{id}"), - argument = CompleteRequest.Argument(name = "id", value = "123"), - ), - ) - - val resourceRef = request.params.ref as ResourceTemplateReference - assertEquals("api://v1/{tenant}/{resource}/{id}", resourceRef.uri) - assertEquals("id", request.params.argument.name) - assertEquals("123", request.params.argument.value) - } - - // InitializeResult Tests - @Test - fun `should create InitializeResult with default instructions`() { - val serverInfo = Implementation(name = "test-server", version = "1.0.0") - val result = InitializeResult( - capabilities = ServerCapabilities(), - serverInfo = serverInfo, - ) - - assertEquals(LATEST_PROTOCOL_VERSION, result.protocolVersion) - assertEquals(serverInfo, result.serverInfo) - assertEquals(null, result.instructions) - } - - @Test - fun `should create InitializeResult with custom instructions`() { - val serverInfo = Implementation(name = "test-server", version = "1.0.0") - val instructions = "Use this server to perform calculations. Call the 'add' tool to add numbers." - val result = InitializeResult( - capabilities = ServerCapabilities(), - serverInfo = serverInfo, - instructions = instructions, - ) - - assertEquals(LATEST_PROTOCOL_VERSION, result.protocolVersion) - assertEquals(serverInfo, result.serverInfo) - assertEquals(instructions, result.instructions) - } - - @Test - fun `should serialize and deserialize InitializeResult with instructions`() { - val serverInfo = Implementation(name = "test-server", version = "1.0.0") - val instructions = "This server provides file system access. Use the 'read' tool to read files." - val result = InitializeResult( - capabilities = ServerCapabilities(), - serverInfo = serverInfo, - instructions = instructions, - ) - - val json = McpJson.encodeToString(result) - val decoded = McpJson.decodeFromString(json) - - assertEquals(LATEST_PROTOCOL_VERSION, decoded.protocolVersion) - assertEquals(serverInfo, decoded.serverInfo) - assertEquals(instructions, decoded.instructions) - } - - @Test - fun `should serialize and deserialize InitializeResult without instructions`() { - val serverInfo = Implementation(name = "test-server", version = "1.0.0") - val result = InitializeResult( - capabilities = ServerCapabilities(), - serverInfo = serverInfo, - ) - - val json = McpJson.encodeToString(result) - val decoded = McpJson.decodeFromString(json) - - assertEquals(LATEST_PROTOCOL_VERSION, decoded.protocolVersion) - assertEquals(serverInfo, decoded.serverInfo) - assertEquals(null, decoded.instructions) - } -} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/models/OldSchemaProgressNotificationsTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/models/OldSchemaProgressNotificationsTest.kt deleted file mode 100644 index 29bc12d..0000000 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/models/OldSchemaProgressNotificationsTest.kt +++ /dev/null @@ -1,71 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.models - -import io.kotest.matchers.shouldBe -import io.modelcontextprotocol.kotlin.sdk.ProgressNotification -import io.modelcontextprotocol.kotlin.sdk.RequestId -import io.modelcontextprotocol.kotlin.sdk.shared.McpJson -import kotlin.test.Test - -class OldSchemaProgressNotificationsTest { - - /** - * https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/progress#progress-flow - */ - @Test - fun `Read ProgressNotifications with string token`() { - //language=json - val json = """ - { - "jsonrpc": "2.0", - "method": "notifications/progress", - "params": { - "progressToken": "abc123", - "progress": 50, - "total": 100, - "message": "Reticulating splines..." - } - } - """.trimIndent() - - val result = McpJson.decodeFromString(json) - - result shouldBe ProgressNotification( - params = ProgressNotification.Params( - progressToken = RequestId.StringId("abc123"), - progress = 50.0, - message = "Reticulating splines...", - total = 100.0, - ), - ) - } - - /** - * https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/progress#progress-flow - */ - @Test - fun `Read ProgressNotifications with integer token`() { - //language=json - val json = """ - { - "jsonrpc": "2.0", - "method": "notifications/progress", - "params": { - "progressToken": 100500, - "progress": 50, - "total": 100, - "message": "Reticulating splines..." - } - } - """.trimIndent() - - val result = McpJson.decodeFromString(json) - result shouldBe ProgressNotification( - params = ProgressNotification.Params( - progressToken = RequestId.NumberId(100500), - progress = 50.0, - message = "Reticulating splines...", - total = 100.0, - ), - ) - } -} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaReadBufferTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaReadBufferTest.kt deleted file mode 100644 index 0311c13..0000000 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaReadBufferTest.kt +++ /dev/null @@ -1,62 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.shared - -import io.ktor.utils.io.charsets.Charsets -import io.ktor.utils.io.core.toByteArray -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage -import io.modelcontextprotocol.kotlin.sdk.JSONRPCNotification -import kotlinx.serialization.json.Json -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNull - -class OldSchemaReadBufferTest { - private val testMessage: JSONRPCMessage = JSONRPCNotification(method = "foobar") - - private val json = Json { - ignoreUnknownKeys = true - encodeDefaults = true - } - - @Test - fun `should have no messages after initialization`() { - val readBuffer = ReadBuffer() - assertNull(readBuffer.readMessage()) - } - - @Test - fun `should only yield a message after a newline`() { - val readBuffer = ReadBuffer() - - // Append message without a newline - val messageBytes = json.encodeToString(testMessage).encodeToByteArray() - readBuffer.append(messageBytes) - assertNull(readBuffer.readMessage()) - - // Append a newline and verify message is now available - readBuffer.append("\n".encodeToByteArray()) - assertEquals(testMessage, readBuffer.readMessage()) - assertNull(readBuffer.readMessage()) - } - - @Test - fun `skip empty line`() { - val readBuffer = ReadBuffer() - readBuffer.append("\n".toByteArray()) - assertNull(readBuffer.readMessage()) - } - - @Test - fun `should be reusable after clearing`() { - val readBuffer = ReadBuffer() - - readBuffer.append("foobar".toByteArray(Charsets.UTF_8)) - readBuffer.clear() - assertNull(readBuffer.readMessage()) - - val messageJson = serializeMessage(testMessage) - readBuffer.append(messageJson.toByteArray(Charsets.UTF_8)) - readBuffer.append("\n".toByteArray(Charsets.UTF_8)) - val message = readBuffer.readMessage() - assertEquals(testMessage, message) - } -} diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt index 1b33f39..bb436bf 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt @@ -22,9 +22,6 @@ import kotlin.uuid.Uuid internal const val SESSION_ID_PARAM = "sessionId" -@Deprecated("Use SseServerTransport instead", ReplaceWith("SseServerTransport"), DeprecationLevel.ERROR) -public typealias SSEServerTransport = SseServerTransport - /** * Server transport for SSE: this will send messages over an SSE connection and receive messages from HTTP POST requests. * diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt index 2f80bf8..1b035b4 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt @@ -173,7 +173,7 @@ public open class Server( @Deprecated( "Use createSession(transport) instead.", ReplaceWith("createSession(transport)"), - DeprecationLevel.WARNING, + DeprecationLevel.ERROR, ) public suspend fun connect(transport: Transport): ServerSession = createSession(transport) @@ -266,7 +266,7 @@ public open class Server( @Deprecated( "Initialization moved to ServerSession, use ServerSession.onInitialized instead.", ReplaceWith("ServerSession.onInitialized"), - DeprecationLevel.WARNING, + DeprecationLevel.ERROR, ) public fun onInitialized(block: () -> Unit) { val old = _onInitialized diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaStdioServerTransportTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaStdioServerTransportTest.kt deleted file mode 100644 index 32e25ba..0000000 --- a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaStdioServerTransportTest.kt +++ /dev/null @@ -1,140 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.server - -import io.modelcontextprotocol.kotlin.sdk.InitializedNotification -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage -import io.modelcontextprotocol.kotlin.sdk.PingRequest -import io.modelcontextprotocol.kotlin.sdk.shared.ReadBuffer -import io.modelcontextprotocol.kotlin.sdk.shared.serializeMessage -import io.modelcontextprotocol.kotlin.sdk.toJSON -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.runTest -import kotlinx.io.Sink -import kotlinx.io.Source -import kotlinx.io.asSink -import kotlinx.io.asSource -import kotlinx.io.buffered -import java.io.ByteArrayOutputStream -import java.io.PipedInputStream -import java.io.PipedOutputStream -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -class OldSchemaStdioServerTransportTest { - private lateinit var input: PipedInputStream - private lateinit var inputWriter: PipedOutputStream - private lateinit var outputBuffer: ReadBuffer - private lateinit var output: ByteArrayOutputStream - - // We'll store the wrapped streams that meet the constructor requirements - private lateinit var bufferedInput: Source - private lateinit var printOutput: Sink - - @BeforeTest - fun setUp() { - // Simulate an input stream that we can push data into using inputWriter. - input = PipedInputStream() - inputWriter = PipedOutputStream(input) - - outputBuffer = ReadBuffer() - - // A custom ByteArrayOutputStream that appends all written data into outputBuffer - output = object : ByteArrayOutputStream() { - override fun write(b: ByteArray, off: Int, len: Int) { - super.write(b, off, len) - outputBuffer.append(b.copyOfRange(off, off + len)) - } - } - - bufferedInput = input.asSource().buffered() - - printOutput = output.asSink().buffered() - } - - @Test - fun `should start then close cleanly`() { - runBlocking { - val server = StdioServerTransport(bufferedInput, printOutput) - server.onError { error -> - throw error - } - - var didClose = false - server.onClose { - didClose = true - } - - server.start() - assertFalse(didClose, "Should not have closed yet") - - server.close() - assertTrue(didClose, "Should have closed after calling close()") - } - } - - @Test - fun `should not read until started`() = runTest { - val server = StdioServerTransport(bufferedInput, printOutput) - server.onError { error -> - throw error - } - - var didRead = false - val readMessage = CompletableDeferred() - - server.onMessage { message -> - didRead = true - readMessage.complete(message) - } - - val message = PingRequest().toJSON() - - // Push a message before the server started - val serialized = serializeMessage(message) - inputWriter.write(serialized) - inputWriter.flush() - - assertFalse(didRead, "Should not have read message before start") - - server.start() - val received = readMessage.await() - assertEquals(message, received) - } - - @Test - fun `should read multiple messages`() = runTest { - val server = StdioServerTransport(bufferedInput, printOutput) - server.onError { error -> - throw error - } - - val messages = listOf( - PingRequest().toJSON(), - InitializedNotification().toJSON(), - ) - - val readMessages = mutableListOf() - val finished = CompletableDeferred() - - server.onMessage { message -> - readMessages.add(message) - if (message == messages[1]) { - finished.complete(Unit) - } - } - - // Push both messages before starting the server - for (m in messages) { - inputWriter.write(serializeMessage(m)) - } - inputWriter.flush() - - server.start() - finished.await() - - assertEquals(messages, readMessages) - } -} From def0e925e258a8d1ff833aeaf091cbb9e576d2a3 Mon Sep 17 00:00:00 2001 From: johny <69754601+jskjw157@users.noreply.github.com> Date: Thu, 5 Feb 2026 21:49:47 +0900 Subject: [PATCH 129/449] [WIP]test(integration): Add Streamable HTTP transport integration tests (#486) ## Summary Add integration tests for Prompts, Resources, and Tools over Streamable HTTP transport as outlined in #183. ## Changes - [x] Infrastructure: Enable `STREAMABLE_HTTP` transport in test base - [x] Prompts: Add functional integration tests - [x] Resources: Add functional integration tests - [x] Tools: Add functional integration tests Related to #332 --------- Co-authored-by: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> --- integration-test/build.gradle.kts | 2 + .../sdk/integration/kotlin/KotlinTestBase.kt | 166 +++++++++++++----- .../PromptIntegrationTestStreamableHttp.kt | 7 + .../ResourceIntegrationTestStreamableHttp.kt | 7 + .../ToolIntegrationTestStreamableHttp.kt | 7 + .../AbstractKotlinClientTsServerTest.kt | 8 +- .../sdk/integration/typescript/TsTestBase.kt | 28 ++- .../typescript/TypeScriptServer.kt | 2 +- .../stdio/KotlinClientTsServerTestStdio.kt | 15 +- .../jvmTest/typescript/client/stdio-client.ts | 8 +- .../kotlin/sdk/client/MockMcp.kt | 2 +- 11 files changed, 185 insertions(+), 67 deletions(-) create mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/streamablehttp/PromptIntegrationTestStreamableHttp.kt create mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/streamablehttp/ResourceIntegrationTestStreamableHttp.kt create mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/streamablehttp/ToolIntegrationTestStreamableHttp.kt diff --git a/integration-test/build.gradle.kts b/integration-test/build.gradle.kts index d5b8ef8..1b763ed 100644 --- a/integration-test/build.gradle.kts +++ b/integration-test/build.gradle.kts @@ -21,6 +21,8 @@ kotlin { implementation(libs.ktor.server.cio) implementation(libs.ktor.server.websockets) implementation(libs.ktor.server.test.host) + implementation(libs.ktor.server.content.negotiation) + implementation(libs.ktor.serialization) } } jvmTest { diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/KotlinTestBase.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/KotlinTestBase.kt index 43b1117..1a8ffe0 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/KotlinTestBase.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/KotlinTestBase.kt @@ -3,18 +3,27 @@ package io.modelcontextprotocol.kotlin.sdk.integration.kotlin import io.ktor.client.HttpClient import io.ktor.client.engine.cio.CIO import io.ktor.client.plugins.sse.SSE +import io.ktor.serialization.kotlinx.json.json import io.ktor.server.application.install import io.ktor.server.engine.EmbeddedServer import io.ktor.server.engine.embeddedServer +import io.ktor.server.plugins.contentnegotiation.ContentNegotiation +import io.ktor.server.routing.delete +import io.ktor.server.routing.get +import io.ktor.server.routing.post +import io.ktor.server.routing.route import io.ktor.server.routing.routing import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.client.SseClientTransport import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport +import io.modelcontextprotocol.kotlin.sdk.client.StreamableHttpClientTransport import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport +import io.modelcontextprotocol.kotlin.sdk.server.StreamableHttpServerTransport import io.modelcontextprotocol.kotlin.sdk.server.mcp import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.McpJson import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities import io.modelcontextprotocol.kotlin.test.utils.Retry import kotlinx.coroutines.runBlocking @@ -44,7 +53,7 @@ abstract class KotlinTestBase { protected lateinit var serverEngine: EmbeddedServer<*, *> // Transport selection - protected enum class TransportKind { SSE, STDIO } + protected enum class TransportKind { SSE, STDIO, STREAMABLE_HTTP } protected open val transportKind: TransportKind = TransportKind.STDIO // STDIO-specific fields @@ -52,13 +61,16 @@ abstract class KotlinTestBase { private var stdioClientInput: Source? = null private var stdioClientOutput: Sink? = null + // StreamableHTTP-specific fields + private var streamableHttpServerTransport: StreamableHttpServerTransport? = null + protected abstract fun configureServerCapabilities(): ServerCapabilities protected abstract fun configureServer() @BeforeEach fun setUp() { setupServer() - if (transportKind == TransportKind.SSE) { + if (transportKind == TransportKind.SSE || transportKind == TransportKind.STREAMABLE_HTTP) { await .ignoreExceptions() .until { @@ -98,6 +110,19 @@ abstract class KotlinTestBase { ) client.connect(transport) } + + TransportKind.STREAMABLE_HTTP -> { + val transport = StreamableHttpClientTransport( + client = HttpClient(CIO) { + install(SSE) + }, + url = "http://$host:$port/mcp", + ) + client = Client( + Implementation("test", "1.0"), + ) + client.connect(transport) + } } } @@ -111,35 +136,77 @@ abstract class KotlinTestBase { configureServer() - if (transportKind == TransportKind.SSE) { - serverEngine = embeddedServer(ServerCIO, host = host, port = port) { - install(ServerSSE) - routing { - mcp { server } + when (transportKind) { + TransportKind.SSE -> { + serverEngine = embeddedServer(ServerCIO, host = host, port = port) { + install(ServerSSE) + routing { + mcp { server } + } + }.start(wait = false) + } + + TransportKind.STREAMABLE_HTTP -> { + // Create StreamableHTTP server transport + // Using JSON response mode for simpler testing (no SSE session required) + val transport = StreamableHttpServerTransport( + enableJsonResponse = true, // Use JSON response mode for testing + ) + // Use stateless mode to skip session validation for simpler testing + transport.setSessionIdGenerator(null) + streamableHttpServerTransport = transport + + // IMPORTANT: Create server session BEFORE starting the HTTP server + // This ensures message handlers are set up before any requests come in + runBlocking { + server.createSession(transport) } - }.start(wait = false) - } else { - // Create in-memory stdio pipes: client->server and server->client - val clientToServerOut = PipedOutputStream() - val clientToServerIn = PipedInputStream(clientToServerOut) - val serverToClientOut = PipedOutputStream() - val serverToClientIn = PipedInputStream(serverToClientOut) + // Start embedded server with routing for StreamableHTTP + serverEngine = embeddedServer(ServerCIO, host = host, port = port) { + // Install ContentNegotiation for JSON serialization + install(ContentNegotiation) { + json(McpJson) + } + routing { + route("/mcp") { + post { + transport.handlePostRequest(null, call) + } + get { + transport.handleGetRequest(null, call) + } + delete { + transport.handleDeleteRequest(call) + } + } + } + }.start(wait = false) + } - // Server transport reads from client and writes to client - val serverTransport = StdioServerTransport( - inputStream = clientToServerIn.asSource().buffered(), - outputStream = serverToClientOut.asSink().buffered(), - ) - stdioServerTransport = serverTransport + TransportKind.STDIO -> { + // Create in-memory stdio pipes: client->server and server->client + val clientToServerOut = PipedOutputStream() + val clientToServerIn = PipedInputStream(clientToServerOut) - // Prepare client-side streams for later client initialization - stdioClientInput = serverToClientIn.asSource().buffered() - stdioClientOutput = clientToServerOut.asSink().buffered() + val serverToClientOut = PipedOutputStream() + val serverToClientIn = PipedInputStream(serverToClientOut) - // Start server transport by connecting the server - runBlocking { - server.createSession(serverTransport) + // Server transport reads from client and writes to client + val serverTransport = StdioServerTransport( + inputStream = clientToServerIn.asSource().buffered(), + outputStream = serverToClientOut.asSink().buffered(), + ) + stdioServerTransport = serverTransport + + // Prepare client-side streams for later client initialization + stdioClientInput = serverToClientIn.asSource().buffered() + stdioClientOutput = clientToServerOut.asSink().buffered() + + // Start server transport by connecting the server + runBlocking { + server.createSession(serverTransport) + } } } } @@ -160,24 +227,39 @@ abstract class KotlinTestBase { } // stop server - if (transportKind == TransportKind.SSE) { - if (::serverEngine.isInitialized) { - try { - serverEngine.stop(500, 1000) - } catch (e: Exception) { - println("Warning: Error during server stop: ${e.message}") + when (transportKind) { + TransportKind.SSE, TransportKind.STREAMABLE_HTTP -> { + if (::serverEngine.isInitialized) { + try { + serverEngine.stop(500, 1000) + } catch (e: Exception) { + println("Warning: Error during server stop: ${e.message}") + } + } + if (transportKind == TransportKind.STREAMABLE_HTTP) { + streamableHttpServerTransport?.let { + try { + runBlocking { it.close() } + } catch (e: Exception) { + println("Warning: Error during streamable http server stop: ${e.message}") + } finally { + streamableHttpServerTransport = null + } + } } } - } else { - stdioServerTransport?.let { - try { - runBlocking { it.close() } - } catch (e: Exception) { - println("Warning: Error during stdio server stop: ${e.message}") - } finally { - stdioServerTransport = null - stdioClientInput = null - stdioClientOutput = null + + TransportKind.STDIO -> { + stdioServerTransport?.let { + try { + runBlocking { it.close() } + } catch (e: Exception) { + println("Warning: Error during stdio server stop: ${e.message}") + } finally { + stdioServerTransport = null + stdioClientInput = null + stdioClientOutput = null + } } } } diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/streamablehttp/PromptIntegrationTestStreamableHttp.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/streamablehttp/PromptIntegrationTestStreamableHttp.kt new file mode 100644 index 0000000..8f4e334 --- /dev/null +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/streamablehttp/PromptIntegrationTestStreamableHttp.kt @@ -0,0 +1,7 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.streamablehttp + +import io.modelcontextprotocol.kotlin.sdk.integration.kotlin.AbstractPromptIntegrationTest + +class PromptIntegrationTestStreamableHttp : AbstractPromptIntegrationTest() { + override val transportKind: TransportKind = TransportKind.STREAMABLE_HTTP +} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/streamablehttp/ResourceIntegrationTestStreamableHttp.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/streamablehttp/ResourceIntegrationTestStreamableHttp.kt new file mode 100644 index 0000000..d72bcfd --- /dev/null +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/streamablehttp/ResourceIntegrationTestStreamableHttp.kt @@ -0,0 +1,7 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.streamablehttp + +import io.modelcontextprotocol.kotlin.sdk.integration.kotlin.AbstractResourceIntegrationTest + +class ResourceIntegrationTestStreamableHttp : AbstractResourceIntegrationTest() { + override val transportKind: TransportKind = TransportKind.STREAMABLE_HTTP +} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/streamablehttp/ToolIntegrationTestStreamableHttp.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/streamablehttp/ToolIntegrationTestStreamableHttp.kt new file mode 100644 index 0000000..288ff07 --- /dev/null +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/streamablehttp/ToolIntegrationTestStreamableHttp.kt @@ -0,0 +1,7 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.streamablehttp + +import io.modelcontextprotocol.kotlin.sdk.integration.kotlin.AbstractToolIntegrationTest + +class ToolIntegrationTestStreamableHttp : AbstractToolIntegrationTest() { + override val transportKind: TransportKind = TransportKind.STREAMABLE_HTTP +} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractKotlinClientTsServerTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractKotlinClientTsServerTest.kt index 1ed3cf4..9d27a1d 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractKotlinClientTsServerTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractKotlinClientTsServerTest.kt @@ -15,7 +15,7 @@ abstract class AbstractKotlinClientTsServerTest : TsTestBase() { protected abstract suspend fun useClient(block: suspend (Client) -> T): T @Test - @Timeout(20, unit = TimeUnit.SECONDS) + @Timeout(60, unit = TimeUnit.SECONDS) fun connectsAndPings() = runBlocking(Dispatchers.IO) { useClient { client -> assertNotNull(client, "Client should be initialized") @@ -28,7 +28,7 @@ abstract class AbstractKotlinClientTsServerTest : TsTestBase() { } @Test - @Timeout(20, unit = TimeUnit.SECONDS) + @Timeout(60, unit = TimeUnit.SECONDS) fun listsTools() = runBlocking(Dispatchers.IO) { useClient { client -> val result = client.listTools() @@ -42,7 +42,7 @@ abstract class AbstractKotlinClientTsServerTest : TsTestBase() { } @Test - @Timeout(20, unit = TimeUnit.SECONDS) + @Timeout(60, unit = TimeUnit.SECONDS) fun callGreet() = runBlocking(Dispatchers.IO) { useClient { client -> val testName = "TestUser" @@ -57,7 +57,7 @@ abstract class AbstractKotlinClientTsServerTest : TsTestBase() { } @Test - @Timeout(20, unit = TimeUnit.SECONDS) + @Timeout(60, unit = TimeUnit.SECONDS) fun multipleClients() = runBlocking(Dispatchers.IO) { useClient { client1 -> useClient { client2 -> diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TsTestBase.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TsTestBase.kt index 49632c4..aa47a70 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TsTestBase.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TsTestBase.kt @@ -213,14 +213,19 @@ abstract class TsTestBase { serverThread.isDaemon = true serverThread.start() - // Read ONLY stderr from client for human-readable output + // Read ONLY stderr from client for human-readable output. + // Also use it as a signal that the client is done (some Node processes keep the event-loop alive). val output = StringBuilder() + val doneLatch = java.util.concurrent.CountDownLatch(1) val errReader = Thread { try { process.errorStream.bufferedReader().useLines { lines -> lines.forEach { line -> println("[TS-CLIENT-STDIO][err] $line") output.append(line).append('\n') + if (line.contains("Disconnected from server")) { + doneLatch.countDown() + } } } } catch (e: Exception) { @@ -230,11 +235,26 @@ abstract class TsTestBase { errReader.isDaemon = true errReader.start() - // Wait up to 25s for client to exit - val finished = process.waitFor(25, TimeUnit.SECONDS) + // Wait for a "done" signal first, then give the process a short grace period to exit. + // This avoids hanging under the test's outer timeout (often ~30s). + val doneTimeoutSeconds = System.getenv("MCP_TS_CLIENT_DONE_TIMEOUT_SECONDS")?.toLongOrNull() ?: 25L + val sawDone = doneLatch.await(doneTimeoutSeconds, TimeUnit.SECONDS) + if (sawDone) { + // Encourage Node to exit if it is waiting on stdin. + try { + process.outputStream.close() + } catch (_: Exception) { + } + } + + // Primary completion condition: the process exits. + // (Some error-paths may not print the disconnect line, but should still exit.) + val clientTimeoutSeconds = System.getenv("MCP_TS_CLIENT_TIMEOUT_SECONDS")?.toLongOrNull() ?: 30L + val finished = process.waitFor(clientTimeoutSeconds, TimeUnit.SECONDS) if (!finished) { - println("Stdio client did not finish in time; destroying") + println("Stdio client did not finish in time (${clientTimeoutSeconds}s); destroying") process.destroyForcibly() + process.waitFor(2, TimeUnit.SECONDS) } try { diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TypeScriptServer.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TypeScriptServer.kt index d926590..579f96d 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TypeScriptServer.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TypeScriptServer.kt @@ -56,7 +56,7 @@ class TypeScriptServer(private val typescriptDir: File) { ) process = proc - check(waitForPort(port = port, timeoutSeconds = 10)) { + check(waitForPort(port = port, timeoutSeconds = 60)) { proc.destroyForcibly() "TypeScript SSE server did not become ready on localhost:$port within timeout" } diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerTestStdio.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerTestStdio.kt index 8273724..928d591 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerTestStdio.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerTestStdio.kt @@ -8,16 +8,7 @@ class KotlinClientTsServerTestStdio : AbstractKotlinClientTsServerTest() { override val transportKind = TransportKind.STDIO - override suspend fun useClient(block: suspend (Client) -> T): T = withClientStdio { client, proc -> - try { - block(client) - } finally { - try { - client.close() - } catch (_: Exception) {} - try { - stopProcess(proc, name = "TypeScript stdio server") - } catch (_: Exception) {} - } - } + override suspend fun useClient(block: suspend (Client) -> T): T = + // TsTestBase.withClientStdio already handles client.close() + process shutdown. + withClientStdio { client, _ -> block(client) } } diff --git a/integration-test/src/jvmTest/typescript/client/stdio-client.ts b/integration-test/src/jvmTest/typescript/client/stdio-client.ts index c6eaca6..82c25de 100644 --- a/integration-test/src/jvmTest/typescript/client/stdio-client.ts +++ b/integration-test/src/jvmTest/typescript/client/stdio-client.ts @@ -99,8 +99,9 @@ async function main() { const tool = tools.find((t: { name: string; }) => t.name === toolName); if (!tool) { - console.error(`Tool "${toolName}" not found`); - process.exit(1); + // Don't call process.exit() here: it prevents finally{} from running, + // which breaks tests that expect a clean disconnect signal. + throw new Error(`Tool "${toolName}" not found`); } const toolArguments: any = {}; @@ -136,7 +137,8 @@ async function main() { } catch (error) { console.error('Error:', error); - process.exit(1); + // Don't hard-exit; allow finally{} to run so the client always closes cleanly. + process.exitCode = 1; } finally { await client.close(); console.error('Disconnected from server'); diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt index a4447cf..ff39973 100644 --- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt +++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt @@ -19,7 +19,7 @@ import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.putJsonObject -const val MCP_SESSION_ID_HEADER = "Mcp-Session-Id" +const val MCP_SESSION_ID_HEADER = "MCP-Session-Id" internal class MockMcp(verbose: Boolean = false) { From 691e07e251a5e27565d752322e230a146863ac46 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 15:19:08 +0200 Subject: [PATCH 130/449] Bump kotest from 6.1.1 to 6.1.2 (#499) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `kotest` from 6.1.1 to 6.1.2. Updates `io.kotest:kotest-assertions-core` from 6.1.1 to 6.1.2
Release notes

Sourced from io.kotest:kotest-assertions-core's releases.

v6.1.2

What's Changed

Full Changelog: https://github.com/kotest/kotest/compare/6.1.2...v6.1.2

Commits
  • cbd9053 Run superclasses on EDT for test explorer service (#5633)
  • d66e03e Restore forEachAsClue.
  • 028be67 Migrate JUnit 4 runner to common source sets for JVM and Android code.
  • b6ff74b Add Android specific JUnit4 implementation with filter (#5632)
  • a12e3e0 add similarity to containOnly (#5626) (#5627)
  • 79d7673 Add JUnitTestEngineListener tests and update runBlocking context for single-t...
  • 34acaeb add-similarity-to-startWith-endWith (#5629)
  • aef140c Update support for nested JUnit4 tests used by Android (#5630)
  • fe90631 Setup intellij plugin for 253 and Android development (#5623)
  • d9f3757 Add missing docker compose file for test
  • Additional commits viewable in compare view

Updates `io.kotest:kotest-assertions-json` from 6.1.1 to 6.1.2
Release notes

Sourced from io.kotest:kotest-assertions-json's releases.

v6.1.2

What's Changed

Full Changelog: https://github.com/kotest/kotest/compare/6.1.2...v6.1.2

Commits
  • cbd9053 Run superclasses on EDT for test explorer service (#5633)
  • d66e03e Restore forEachAsClue.
  • 028be67 Migrate JUnit 4 runner to common source sets for JVM and Android code.
  • b6ff74b Add Android specific JUnit4 implementation with filter (#5632)
  • a12e3e0 add similarity to containOnly (#5626) (#5627)
  • 79d7673 Add JUnitTestEngineListener tests and update runBlocking context for single-t...
  • 34acaeb add-similarity-to-startWith-endWith (#5629)
  • aef140c Update support for nested JUnit4 tests used by Android (#5630)
  • fe90631 Setup intellij plugin for 253 and Android development (#5623)
  • d9f3757 Add missing docker compose file for test
  • Additional commits viewable in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 04a006b..95c261c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,7 +15,7 @@ openapi-generator = "7.18.0" awaitility = "4.3.0" collections-immutable = "0.4.0" coroutines = "1.10.2" -kotest = "6.1.1" +kotest = "6.1.3" kotlinx-io = "0.8.2" ktor = "3.2.3" logging = "7.0.14" From 6bea404652d527b044a81e6ba6fa44cc62df5926 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Feb 2026 00:40:21 +0200 Subject: [PATCH 131/449] Bump hono from 4.11.4 to 4.11.7 in /integration-test/src/jvmTest/typescript in the npm_and_yarn group across 1 directory (#495) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the npm_and_yarn group with 1 update in the /integration-test/src/jvmTest/typescript directory: [hono](https://github.com/honojs/hono). Updates `hono` from 4.11.4 to 4.11.7
Release notes

Sourced from hono's releases.

v4.11.7

Security Release

This release includes security fixes for multiple vulnerabilities in Hono and related middleware. We recommend upgrading if you are using any of the affected components.

Components

IP Restriction Middleware

Fixed an IPv4 address validation bypass that could allow IP-based access control to be bypassed under certain configurations.

Cache Middleware

Fixed an issue where responses marked with Cache-Control: private or no-store could be cached, potentially leading to information disclosure on some runtimes.

Serve Static Middleware (Cloudflare Workers adapter)

Fixed an issue that could allow unintended access to internal asset keys when serving static files with user-controlled paths.

hono/jsx ErrorBoundary

Fixed a reflected Cross-Site Scripting (XSS) issue in the ErrorBoundary component that could occur when untrusted strings were rendered without proper escaping.

Recommendation

Users are encouraged to upgrade to this release, especially if they:

  • Use IP Restriction Middleware
  • Use Cache Middleware on Deno, Bun, or Node.js
  • Use Serve Static Middleware with user-controlled paths on Cloudflare Workers
  • Render untrusted data inside ErrorBoundary components

Security Advisories & CVEs

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=hono&package-manager=npm_and_yarn&previous-version=4.11.4&new-version=4.11.7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/modelcontextprotocol/kotlin-sdk/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- integration-test/src/jvmTest/typescript/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index 821da34..001bfdd 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -1214,9 +1214,9 @@ } }, "node_modules/hono": { - "version": "4.11.4", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.4.tgz", - "integrity": "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA==", + "version": "4.11.7", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.7.tgz", + "integrity": "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==", "license": "MIT", "peer": true, "engines": { From 9d4535b53f34e5e5ff3261bb8c471257ad61f65c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Feb 2026 00:40:43 +0200 Subject: [PATCH 132/449] Bump com.vanniktech:gradle-maven-publish-plugin from 0.35.0 to 0.36.0 (#484) Bumps [com.vanniktech:gradle-maven-publish-plugin](https://github.com/vanniktech/gradle-maven-publish-plugin) from 0.35.0 to 0.36.0.
Release notes

Sourced from com.vanniktech:gradle-maven-publish-plugin's releases.

0.36.0

BREAKING

  • Updated minimum supported JDK, Gradle, Android Gradle Plugin and Kotlin versions.
  • Removed support for Dokka v1, it's now required to use Dokka in v2 mode.
  • Mark DirectorySignatureType internal.

Behavior changes

  • validateDeployment now has the DeploymentValidation enum as type instead of being a boolean. The default is now to just wait for the VALIDATED state. The previous behavior can be achieved by setting it to PUBLISHED. NONE can be used for disabling the validation completely.
  • When calling configure(...) manually to configure what to publish and not passing javadocJar explicity, the plugin now defaults to publishing an empty javadoc jar.

Features

  • Android projects now support using Dokka for javadoc creation, this will happen automatically when using the default options and the Dokka plugin is applied to the project.
  • Added consistent JavadocJar and SourcesJar options to configureBasedOnAppliedPlugins and to all applicable project types that can be passed to configure. The previous Boolean based versions have been deprecated.
  • When enabling Maven Central publishing through the DSL, the mavenCentralDeploymentValidation and mavenCentralAutomaticPublishing are used for the default values of the 2 parameters when they are not passed explicitly. This allows to more easily override them in certain environments.
  • When isolated projects is enabled the module/project specific gradle.properties files are now considered in the same way they are when isolated projects is disabled.

Improvements

  • Better error message when Maven Central credentials are missing.

Minimum supported versions

  • JDK 17
  • Gradle 9.0.0
  • Android Gradle Plugin 8.13.0
  • Kotlin Gradle Plugin 2.2.0

Compatibility tested up to

  • JDK 25
  • Gradle 9.3.0
  • Gradle 9.4.0-milestone-4
  • Android Gradle Plugin 8.13.2
  • Android Gradle Plugin 9.0.0
  • Android Gradle Plugin 9.1.0-alpha05
  • Kotlin Gradle Plugin 2.3.0
  • Kotlin Gradle Plugin 2.3.20-Beta1

0.36.0-rc2

BREAKING

  • Updated minimum supported JDK, Gradle, Android Gradle Plugin and Kotlin versions.
  • Removed support for Dokka v1, it's now required to use Dokka in v2 mode.
  • Mark DirectorySignatureType internal.

... (truncated)

Changelog

Sourced from com.vanniktech:gradle-maven-publish-plugin's changelog.

0.36.0 (2026-01-13)

BREAKING

  • Updated minimum supported JDK, Gradle, Android Gradle Plugin and Kotlin versions.
  • Removed support for Dokka v1, it's now required to use Dokka in v2 mode.
  • Mark DirectorySignatureType internal.

Behavior changes

  • validateDeployment now has the DeploymentValidation enum as type instead of being a boolean. The default is now to just wait for the VALIDATED state. The previous behavior can be achieved by setting it to PUBLISHED. NONE can be used for disabling the validation completely.
  • When calling configure(...) manually to configure what to publish and not passing javadocJar explicity, the plugin now defaults to publishing an empty javadoc jar.

Features

  • Android projects now support using Dokka for javadoc creation, this will happen automatically when using the default options and the Dokka plugin is applied to the project.
  • Added consistent JavadocJar and SourcesJar options to configureBasedOnAppliedPlugins and to all applicable project types that can be passed to configure. The previous Boolean based versions have been deprecated.
  • When enabling Maven Central publishing through the DSL, the mavenCentralDeploymentValidation and mavenCentralAutomaticPublishing are used for the default values of the 2 parameters when they are not passed explicitly. This allows to more easily override them in certain environments.
  • When isolated projects is enabled the module/project specific gradle.properties files are now considered in the same way they are when isolated projects is disabled.

Improvements

  • Better error message when Maven Central credentials are missing.

Minimum supported versions

  • JDK 17
  • Gradle 9.0.0
  • Android Gradle Plugin 8.13.0
  • Kotlin Gradle Plugin 2.2.0

Compatibility tested up to

  • JDK 25
  • Gradle 9.3.0
  • Gradle 9.4.0-milestone-4
  • Android Gradle Plugin 8.13.2
  • Android Gradle Plugin 9.0.0
  • Android Gradle Plugin 9.1.0-alpha05
  • Kotlin Gradle Plugin 2.3.0
  • Kotlin Gradle Plugin 2.3.20-Beta1
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=com.vanniktech:gradle-maven-publish-plugin&package-manager=gradle&previous-version=0.35.0&new-version=0.36.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 95c261c..1d5fd74 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,7 @@ dokka = "2.1.0" kotlin = "2.2.21" kover = "0.9.4" ktlint = "14.0.1" -mavenPublish = "0.35.0" +mavenPublish = "0.36.0" netty = "4.2.9.Final" openapi-generator = "7.18.0" From 7eb2a93c466bf91d0a4932d736ddfe8d1e60b8f9 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Fri, 6 Feb 2026 12:25:18 +0200 Subject: [PATCH 133/449] fix(test): improve timing stability in tests (#506) --- .../kotlin/sdk/shared/BaseTransportTest.kt | 20 +++++++++------- .../sdk/client/StdioClientTransportTest.kt | 15 +++++++----- ...erverResourcesNotificationSubscribeTest.kt | 24 +++++++++++-------- .../AbstractClientTransportLifecycleTest.kt | 16 ++++++++++--- .../StdioClientTransportErrorHandlingTest.kt | 24 +++++++++++++------ 5 files changed, 65 insertions(+), 34 deletions(-) diff --git a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/BaseTransportTest.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/BaseTransportTest.kt index d5849eb..a6c6e3d 100644 --- a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/BaseTransportTest.kt +++ b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/BaseTransportTest.kt @@ -1,14 +1,12 @@ package io.modelcontextprotocol.kotlin.sdk.shared +import io.kotest.assertions.nondeterministic.eventually +import io.kotest.matchers.shouldBe import io.modelcontextprotocol.kotlin.sdk.types.InitializedNotification import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage import io.modelcontextprotocol.kotlin.sdk.types.PingRequest import io.modelcontextprotocol.kotlin.sdk.types.toJSON import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.delay -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue import kotlin.test.fail import kotlin.time.Duration.Companion.seconds @@ -23,12 +21,18 @@ abstract class BaseTransportTest { transport.onClose { didClose = true } transport.start() - delay(1.seconds) - assertFalse(didClose, "Transport should not be closed immediately after start") + // Verify transport stays open after start (use eventually for cross-platform reliability) + eventually(2.seconds) { + didClose shouldBe false + } transport.close() - assertTrue(didClose, "Transport should be closed after close() call") + + // Verify transport is closed after close() call + eventually(2.seconds) { + didClose shouldBe true + } } protected suspend fun testTransportRead(transport: Transport) { @@ -60,7 +64,7 @@ abstract class BaseTransportTest { finished.await() - assertEquals(messages, readMessages, "Assert messages received") + messages shouldBe readMessages transport.close() } diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt index be06cff..44456f2 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt @@ -1,12 +1,12 @@ package io.modelcontextprotocol.kotlin.sdk.client +import io.kotest.assertions.nondeterministic.eventually import io.modelcontextprotocol.kotlin.sdk.shared.BaseTransportTest import io.modelcontextprotocol.kotlin.sdk.types.Implementation import io.modelcontextprotocol.kotlin.sdk.types.McpException import io.modelcontextprotocol.kotlin.test.utils.createSleepyProcessBuilder import io.modelcontextprotocol.kotlin.test.utils.createTeeProcessBuilder import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import kotlinx.io.asSink @@ -23,7 +23,6 @@ import kotlin.concurrent.atomics.ExperimentalAtomicApi import kotlin.test.assertFalse import kotlin.test.assertTrue import kotlin.test.fail -import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds @Timeout(30, unit = TimeUnit.SECONDS) @@ -89,17 +88,21 @@ class StdioClientTransportTest : BaseTransportTest() { transport.onClose { didClose.store(true) } transport.start() - delay(1.seconds) - assertFalse(didClose.load(), "Transport should not be closed immediately after start") + // Verify transport stays open after start (use eventually for cross-platform reliability) + eventually(2.seconds) { + assertFalse(didClose.load(), "Transport should not be closed immediately after start") + } // Destroy process BEFORE close() to unblock stdin reader process.destroyForcibly() - delay(100.milliseconds) // Give time for EOF to propagate transport.close() - assertTrue(didClose.load(), "Transport should be closed after close() call") + // Verify transport is closed after close() call + eventually(2.seconds) { + assertTrue(didClose.load(), "Transport should be closed after close() call") + } } @Test diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationSubscribeTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationSubscribeTest.kt index 1e102a7..d9f9250 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationSubscribeTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationSubscribeTest.kt @@ -1,6 +1,10 @@ package io.modelcontextprotocol.kotlin.sdk.server +import io.kotest.assertions.nondeterministic.eventually +import io.kotest.assertions.withClue +import io.kotest.matchers.collections.shouldBeEmpty import io.kotest.matchers.collections.shouldHaveSize +import io.kotest.matchers.shouldBe import io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsResourcesUpdated import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult import io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotification @@ -9,13 +13,11 @@ import io.modelcontextprotocol.kotlin.sdk.types.SubscribeRequest import io.modelcontextprotocol.kotlin.sdk.types.SubscribeRequestParams import io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import org.awaitility.kotlin.await import org.awaitility.kotlin.untilAsserted import org.junit.jupiter.api.Test import java.util.concurrent.ConcurrentLinkedQueue -import kotlin.test.assertFalse import kotlin.test.assertTrue import kotlin.time.Duration.Companion.seconds import kotlin.uuid.ExperimentalUuidApi @@ -115,7 +117,7 @@ class ServerResourcesNotificationSubscribeTest : AbstractServerFeaturesTest() { } @Test - fun `notification should not be send when removed resource does not exists`() = runBlocking { + fun `notification should not be send when removed resource does not exists`(): Unit = runBlocking { println("Thread ${Thread.currentThread().name} test 1") // Track notifications val notifications = ConcurrentLinkedQueue() @@ -126,13 +128,15 @@ class ServerResourcesNotificationSubscribeTest : AbstractServerFeaturesTest() { // Try to remove a non-existent resource val result = server.removeResource("non-existent-resource") - assertFalse(result, "Removing non-existent resource should return false") + withClue("Removing non-existent resource should return false") { + result shouldBe false + } - // Verify the result - delay(1.seconds) - assertTrue( - notifications.isEmpty(), - "No notification should be sent when resource doesn't exist", - ) + // Verify no notification is sent (use eventually for cross-platform reliability) + withClue("No notification should be sent when resource doesn't exist") { + eventually(2.seconds) { + notifications.shouldBeEmpty() + } + } } } diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractClientTransportLifecycleTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractClientTransportLifecycleTest.kt index 02b6d25..7040c65 100644 --- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractClientTransportLifecycleTest.kt +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractClientTransportLifecycleTest.kt @@ -1,5 +1,6 @@ package io.modelcontextprotocol.kotlin.sdk.client +import io.kotest.assertions.nondeterministic.eventually import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain @@ -13,6 +14,7 @@ import kotlinx.coroutines.test.runTest import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds abstract class AbstractClientTransportLifecycleTest { @@ -60,11 +62,15 @@ abstract class AbstractClientTransportLifecycleTest { val transport = createTransport() transport.start() + // Give transport time to initialize delay(50.milliseconds) transport.close() - shouldThrow { - transport.send(PingRequest().toJSON()) + // Verify that send throws after close using eventually for reliability + eventually(2.seconds) { + shouldThrow { + transport.send(PingRequest().toJSON()) + } } } @@ -76,13 +82,17 @@ abstract class AbstractClientTransportLifecycleTest { transport.onClose { closeCallCount++ } transport.start() + // Give transport time to initialize delay(50.milliseconds) // Multiple close attempts transport.close() transport.close() - closeCallCount shouldBe 1 + // Verify onClose was called exactly once using eventually for cross-platform reliability + eventually(2.seconds) { + closeCallCount shouldBe 1 + } } protected abstract fun createTransport(): T diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/stdio/StdioClientTransportErrorHandlingTest.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/stdio/StdioClientTransportErrorHandlingTest.kt index b227d5c..89b6042 100644 --- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/stdio/StdioClientTransportErrorHandlingTest.kt +++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/stdio/StdioClientTransportErrorHandlingTest.kt @@ -1,5 +1,6 @@ package io.modelcontextprotocol.kotlin.sdk.client.stdio +import io.kotest.assertions.nondeterministic.eventually import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.booleans.shouldBeFalse import io.kotest.matchers.shouldBe @@ -27,7 +28,7 @@ import java.util.stream.Stream import kotlin.concurrent.atomics.AtomicBoolean import kotlin.concurrent.atomics.ExperimentalAtomicApi import kotlin.test.Test -import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds /** * Tests for StdioClientTransport error handling: EOF, IO errors, and edge cases. @@ -57,10 +58,13 @@ class StdioClientTransportErrorHandlingTest { transport.onClose { closeCalled.store(true) } transport.start() - delay(200.milliseconds) // Stderr EOF should not close transport - closeCalled.load() shouldBe false + // Use eventually to handle timing differences across platforms (especially Windows) + eventually(2.seconds) { + // Wait for stderr to be processed, then verify transport is still open + closeCalled.load() shouldBe false + } transport.close() closeCalled.load() shouldBe true @@ -87,9 +91,13 @@ class StdioClientTransportErrorHandlingTest { transport.onClose { closeCallCount++ } transport.start() - delay(100.milliseconds) - // Explicit close after error already closed it + // FATAL stderr should trigger close, wait for it to complete + eventually(2.seconds) { + closeCallCount shouldBe 1 + } + + // Explicit close after error already closed it (should be no-op) transport.close() closeCallCount shouldBe 1 @@ -109,10 +117,12 @@ class StdioClientTransportErrorHandlingTest { transport.onError { errorCalled = true } transport.start() - delay(100.milliseconds) // Empty input should close cleanly without error - errorCalled.shouldBeFalse() + // Wait for EOF processing to complete, verify no error was called + eventually(2.seconds) { + errorCalled.shouldBeFalse() + } } companion object { From adc4150f1f9fa749f6e67003d651ecc11df84c9e Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Fri, 6 Feb 2026 18:40:31 +0200 Subject: [PATCH 134/449] docs: Update readme, enable Knit (#162) (#510) #162 ## Motivation and Context - update readme - add more snippets - add knit ## How Has This Been Tested? knit on CI ## Breaking Changes None ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] 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 - [x] I have added or updated documentation as needed --------- Co-authored-by: devcrocod Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/apidocs.yaml | 12 +- .github/workflows/build.yml | 34 ++ README.md | 842 ++++++++++++++++++++++++++++----- docs/.gitignore | 1 + docs/build.gradle.kts | 35 ++ docs/moduledoc.md | 109 ++--- gradle/libs.versions.toml | 4 +- knit.properties | 3 + kotlin-sdk-client/Module.md | 80 +++- kotlin-sdk-core/Module.md | 46 +- kotlin-sdk-server/Module.md | 129 ++++- settings.gradle.kts | 1 + 12 files changed, 1097 insertions(+), 199 deletions(-) create mode 100644 docs/.gitignore create mode 100644 docs/build.gradle.kts create mode 100644 knit.properties diff --git a/.github/workflows/apidocs.yaml b/.github/workflows/apidocs.yaml index 603ab3a..41e15f3 100644 --- a/.github/workflows/apidocs.yaml +++ b/.github/workflows/apidocs.yaml @@ -7,6 +7,10 @@ on: # Allow running this workflow manually from the Actions tab workflow_dispatch: +env: + JAVA_VERSION: 21 + JAVA_DISTRIBUTION: temurin + jobs: deploy: @@ -25,11 +29,11 @@ jobs: submodules: true # Fetch Hugo themes (true OR recursive) fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod - - name: Set up JDK 21 + - name: Set up JDK ${{ env.JAVA_VERSION }} uses: actions/setup-java@v5 with: - java-version: '21' - distribution: 'temurin' + java-version: ${{ env.JAVA_VERSION }} + distribution: ${{ env.JAVA_DISTRIBUTION }} cache: gradle - name: Setup Gradle @@ -37,7 +41,7 @@ jobs: - name: Generate Dokka Site run: |- - ./gradlew clean dokkaGenerate + ./gradlew clean knit docs:classes docs:knitCheck dokkaGenerate - name: Upload artifact uses: actions/upload-pages-artifact@v4 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 81af8ac..0041174 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -120,6 +120,40 @@ jobs: path: local-maven-repository.zip retention-days: 1 + knit: + runs-on: ubuntu-latest + name: Knit + timeout-minutes: 10 + permissions: + contents: read + + steps: + - uses: actions/checkout@v6 + + - name: Set up JDK ${{ env.JAVA_VERSION }} + uses: actions/setup-java@v5 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: ${{ env.JAVA_DISTRIBUTION }} + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + with: + validate-wrappers: true + add-job-summary: 'always' + cache-read-only: ${{ github.ref != 'refs/heads/main' }} + cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} + gradle-home-cache-includes: | + caches + notifications + sdks + ../.konan/** + + - name: Run Knit + run: |- + ./gradlew knit docs:classes docs:knitCheck -x :conformance-test:test --max-workers 3 --continue --rerun-tasks --configure-on-demand --parallel + + samples: needs: [ build ] runs-on: ubuntu-latest diff --git a/README.md b/README.md index 356493b..bf49bf0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ # MCP Kotlin SDK - [![Maven Central](https://img.shields.io/maven-central/v/io.modelcontextprotocol/kotlin-sdk.svg?label=Maven%20Central)](https://central.sonatype.com/artifact/io.modelcontextprotocol/kotlin-sdk) [![Build](https://github.com/modelcontextprotocol/kotlin-sdk/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/modelcontextprotocol/kotlin-sdk/actions/workflows/build.yml) [![Conformance Tests](https://github.com/modelcontextprotocol/kotlin-sdk/actions/workflows/conformance.yml/badge.svg)](https://github.com/modelcontextprotocol/kotlin-sdk/actions/workflows/conformance.yml) @@ -9,100 +8,306 @@ [![JVM](https://img.shields.io/badge/JVM-11+-red.svg?logo=jvm)](http://java.com) [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE) -Kotlin Multiplatform implementation of the [Model Context Protocol](https://modelcontextprotocol.io) (MCP), -providing both client and server capabilities for integrating with LLM surfaces across various platforms. +Kotlin Multiplatform SDK for the [Model Context Protocol](https://modelcontextprotocol.io). +It enables Kotlin applications targeting JVM, Native, JS, and Wasm to implement MCP clients and servers using a +standardized protocol interface. + +## Table of Contents + + + +* [Overview](#overview) +* [Installation](#installation) + * [Artifacts](#artifacts) + * [Gradle setup (JVM)](#gradle-setup-jvm) + * [Multiplatform](#multiplatform) + * [Ktor dependencies](#ktor-dependencies) +* [Quickstart](#quickstart) + * [Creating a Client](#creating-a-client) + * [Creating a Server](#creating-a-server) +* [Core Concepts](#core-concepts) + * [MCP Primitives](#mcp-primitives) + * [Capabilities](#capabilities) + * [Server Capabilities](#server-capabilities) + * [Client Capabilities](#client-capabilities) + * [Server Features](#server-features) + * [Prompts](#prompts) + * [Resources](#resources) + * [Tools](#tools) + * [Completion](#completion) + * [Logging](#logging) + * [Pagination](#pagination) + * [Client Features](#client-features) + * [Roots](#roots) + * [Sampling](#sampling) +* [Transports](#transports) + * [STDIO Transport](#stdio-transport) + * [Streamable HTTP Transport](#streamable-http-transport) + * [SSE Transport](#sse-transport) + * [WebSocket Transport](#websocket-transport) +* [Connecting your server](#connecting-your-server) +* [Examples](#examples) +* [Documentation](#documentation) +* [Contributing](#contributing) +* [License](#license) + + ## Overview The Model Context Protocol allows applications to provide context for LLMs in a standardized way, separating the concerns of providing context from the actual LLM interaction. -This SDK implements the MCP specification for Kotlin, -enabling you to build applications that can communicate using MCP on the JVM, WebAssembly and iOS. +This Kotlin SDK implements the MCP specification, making it easy to: -- Build MCP clients that can connect to any MCP server -- Create MCP servers that expose resources, prompts and tools -- Use standard transports like stdio, SSE, and WebSocket -- Handle all MCP protocol messages and lifecycle events - -## Samples - -- [kotlin-mcp-server](./samples/kotlin-mcp-server): demonstrates a multiplatform (JVM, Wasm) MCP server setup with various features and transports. -- [weather-stdio-server](./samples/weather-stdio-server): shows how to build a Kotlin MCP server providing weather forecast and alerts using STDIO transport. -- [kotlin-mcp-client](./samples/kotlin-mcp-client): demonstrates building an interactive Kotlin MCP client that connects to an MCP server via STDIO and integrates with Anthropic’s API. +- Build MCP **clients** that can connect to any MCP server +- Create MCP **servers** that expose resources, prompts, and tools +- Target **JVM, Native, JS, and Wasm** from a single codebase +- Use standard transports like **stdio**, **SSE**, **Streamable HTTP**, and **WebSocket** +- Handle MCP protocol messages and lifecycle events with coroutine-friendly APIs ## Installation -Add the new repository to your build file: +### Artifacts + +- `io.modelcontextprotocol:kotlin-sdk` – umbrella SDK (client + server APIs) +- `io.modelcontextprotocol:kotlin-sdk-client` – client-only APIs +- `io.modelcontextprotocol:kotlin-sdk-server` – server-only APIs + +### Gradle setup (JVM) + +Add the Maven Central repository and the SDK dependency: ```kotlin repositories { mavenCentral() } -``` -Add the dependency: - -```kotlin dependencies { // See the badge above for the latest version implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion") } ``` -MCP SDK uses [Ktor](https://ktor.io/), but does not come with a specific engine dependency. -You should add [Ktor client](https://ktor.io/docs/client-dependencies.html#engine-dependency) -and/or [Ktor server](https://ktor.io/docs/client-dependencies.html#engine-dependency) dependency -to your project yourself, e.g.: + +Use _kotlin-sdk-client_ or _kotlin-sdk-server_ if you only need one side of the API: + ```kotlin dependencies { - // for client: - implementation("io.ktor:ktor-client-cio:$ktorVersion") - // for server: - implementation("io.ktor:ktor-server-netty:$ktorVersion") - + implementation("io.modelcontextprotocol:kotlin-sdk-client:$mcpVersion") + implementation("io.modelcontextprotocol:kotlin-sdk-server:$mcpVersion") } ``` -## Quick Start +### Multiplatform + +In a Kotlin Multiplatform project you can add the SDK to commonMain: + +```kotlin +commonMain { + dependencies { + // Works as a common dependency as well as the platform one + implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion") + } +} +``` + +### Ktor dependencies + +The Kotlin MCP SDK uses [Ktor](https://ktor.io/), but it does not add Ktor engine dependencies transitively. +You need to declare +Ktor [client](https://ktor.io/docs/client-dependencies.html#engine-dependency)/[server](https://ktor.io/docs/server-dependencies.html) +dependencies yourself (or reuse the ones already used in your project), +for example: + +```kotlin +dependencies { + // MCP client with Ktor + implementation("io.ktor:ktor-client-cio:$ktorVersion") + implementation("io.modelcontextprotocol:kotlin-sdk-client:$mcpVersion") + + // MCP server with Ktor + implementation("io.ktor:ktor-server-netty:$ktorVersion") + implementation("io.modelcontextprotocol:kotlin-sdk-server:$mcpVersion") +} +``` + +## Quickstart + +Let's create a simple MCP client and server to demonstrate the basic usage of the Kotlin SDK. + + ### Creating a Client +Create an MCP client that connects to a server via Streamable HTTP transport and lists available tools: + ```kotlin +import io.ktor.client.HttpClient +import io.ktor.client.plugins.sse.SSE import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport -import io.modelcontextprotocol.kotlin.sdk.Implementation +import io.modelcontextprotocol.kotlin.sdk.client.StreamableHttpClientTransport +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import kotlinx.coroutines.runBlocking -val client = Client( - clientInfo = Implementation( - name = "example-client", - version = "1.0.0" +fun main(args: Array) = runBlocking { + val url = args.firstOrNull() ?: "http://localhost:3000/mcp" + + val httpClient = HttpClient { install(SSE) } + + val client = Client( + clientInfo = Implementation( + name = "example-client", + version = "1.0.0" + ) ) -) -val transport = StdioClientTransport( - inputStream = processInputStream, - outputStream = processOutputStream -) + val transport = StreamableHttpClientTransport( + client = httpClient, + url = url + ) -// Connect to server -client.connect(transport) + // Connect to server + client.connect(transport) -// List available resources -val resources = client.listResources() + // List available tools + val tools = client.listTools().tools -// Read a specific resource -val resourceContent = client.readResource( - ReadResourceRequest(uri = "file:///example.txt") -) + println(tools) +} ``` + + ### Creating a Server +Create an MCP server that exposes a simple tool and runs on an embedded Ktor server with SSE transport: + ```kotlin +import io.ktor.server.cio.CIO +import io.ktor.server.engine.embeddedServer import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions -import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.server.mcp +import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put + +fun main(args: Array) { + val port = args.firstOrNull()?.toIntOrNull() ?: 3000 + val mcpServer = Server( + serverInfo = Implementation( + name = "example-server", + version = "1.0.0" + ), + options = ServerOptions( + capabilities = ServerCapabilities( + tools = ServerCapabilities.Tools(listChanged = true), + ), + ) + ) + + mcpServer.addTool( + name = "example-tool", + description = "An example tool", + inputSchema = ToolSchema( + properties = buildJsonObject { + put("input", buildJsonObject { put("type", "string") }) + } + ) + ) { request -> + CallToolResult(content = listOf(TextContent("Hello, world!"))) + } + + embeddedServer(CIO, host = "127.0.0.1", port = port) { + mcp { + mcpServer + } + }.start(wait = true) +} +``` + + + +You can run the server and then connect to it using the client or test with the MCP Inspector: + +```bash +npx -y @modelcontextprotocol/inspector +``` + +In the inspector UI, connect to `http://localhost:3000`. + +## Core Concepts + +### MCP Primitives + +The MCP protocol defines core primitives that enable communication between servers and clients: + +| Primitive | Server Role | Client Role | Description | +|---------------|---------------------------------------------------|----------------------------------------|---------------------------------------------------| +| **Prompts** | Provides prompt templates with optional arguments | Requests and uses prompts | Interactive templates for LLM interactions | +| **Resources** | Exposes data sources (files, API responses, etc.) | Reads and subscribes to resources | Contextual data for augmenting LLM context | +| **Tools** | Defines executable functions | Calls tools to perform actions | Functions the LLM can invoke to take actions | +| **Sampling** | Requests LLM completions from client | Executes LLM calls and returns results | Server-initiated LLM requests (reverse direction) | + +### Capabilities + +Capabilities define what features a server or client supports. They are declared during initialization and determine +what operations are available. + +#### Server Capabilities + +Servers declare their capabilities to inform clients what features they provide: + +| Capability | Feature Flags | Description | +|----------------|-------------------------------|------------------------------------------------------------| +| `prompts` | `listChanged` | Prompt template management and notifications | +| `resources` | `subscribe`
`listChanged` | Resource exposure, subscriptions, and update notifications | +| `tools` | `listChanged` | Tool discovery, execution, and list change notifications | +| `logging` | - | Server logging to client console | +| `completions` | - | Argument autocompletion suggestions | +| `experimental` | Custom properties | Non-standard experimental features | + +#### Client Capabilities + +Clients declare their capabilities to inform servers what features they support: + +| Capability | Feature Flags | Description | +|----------------|-------------------|-------------------------------------------------------------| +| `sampling` | - | Client can sample from an LLM (execute model requests) | +| `roots` | `listChanged` | Client exposes root directories and can notify of changes | +| `elicitation` | - | Client can display schema/form dialogs for structured input | +| `experimental` | Custom properties | Non-standard experimental features | + +### Server Features + +The `Server` API lets you wire prompts, resources, and tools with only a few lines of Kotlin. Each feature is registered +up front and then resolved lazily when a client asks for it, so your handlers stay small and suspendable. + +#### Prompts + +Prompts are user-controlled templates that clients discover via `prompts/list` and fetch with `prompts/get` when a user +chooses one (think slash commands or saved flows). They’re best for repeatable, structured starters rather than ad-hoc +model calls. + + + +```kotlin val server = Server( serverInfo = Implementation( name = "example-server", @@ -110,97 +315,498 @@ val server = Server( ), options = ServerOptions( capabilities = ServerCapabilities( - resources = ServerCapabilities.Resources( - subscribe = true, - listChanged = true - ) - ) + prompts = ServerCapabilities.Prompts(listChanged = true), + ), ) -){ - "This server provides example resources and demonstrates MCP capabilities." +) + +server.addPrompt( + name = "code-review", + description = "Ask the model to review a diff", + arguments = listOf( + PromptArgument(name = "diff", description = "Unified diff", required = true), + ), +) { request -> + GetPromptResult( + description = "Quick code review helper", + messages = listOf( + PromptMessage( + role = Role.User, + content = TextContent(text = "Review this change:\n${request.arguments?.get("diff")}"), + ), + ), + ) } +``` + + + + + +Use prompts for anything that deserves a template: bug triage questions, onboarding checklists, or saved searches. Set +`listChanged = true` only if your prompt catalog can change at runtime and your server will emit +`notifications/prompts/list_changed` when it does. + +#### Resources + +Resources are application-driven context that clients discover via `resources/list` or `resources/templates/list`, then +fetch with `resources/read`. Register each one with a stable URI and return a `ReadResourceResult` when asked—contents +can be text or binary blobs. + + + +```kotlin +val server = Server( + serverInfo = Implementation( + name = "example-server", + version = "1.0.0" + ), + options = ServerOptions( + capabilities = ServerCapabilities( + resources = ServerCapabilities.Resources(subscribe = true, listChanged = true), + ), + ) +) -// Add a resource server.addResource( - uri = "file:///example.txt", - name = "Example Resource", - description = "An example text file", - mimeType = "text/plain" + uri = "note://release/latest", + name = "Release notes", + description = "Last deployment summary", + mimeType = "text/markdown", ) { request -> ReadResourceResult( contents = listOf( TextResourceContents( - text = "This is the content of the example resource.", + text = "Ship 42 reached production successfully.", uri = request.uri, - mimeType = "text/plain" - ) - ) + mimeType = "text/markdown", + ), + ), ) } - -// Start server with stdio transport -val transport = StdioServerTransport() -server.connect(transport) ``` -### Using SSE Transport + + + + +Resources can be static text, generated JSON, or blobs—anything the client can surface to the user or inject into the +model context. Set `subscribe = true` if you emit `notifications/resources/updated` for changes to specific URIs, and +`listChanged = true` if you’ll send `notifications/resources/list_changed` when the catalog itself changes. + +#### Tools + +Tools are model-controlled capabilities the client exposes to the model. Clients discover them via `tools/list`, invoke +them with `tools/call`, and your handlers receive JSON arguments, can emit streaming logs or progress, and return a +`CallToolResult`. Keep a human in the loop for sensitive operations. + + -Directly in Ktor's `Application`: ```kotlin -import io.ktor.server.application.* -import io.modelcontextprotocol.kotlin.sdk.server.mcp +val server = Server( + serverInfo = Implementation( + name = "example-server", + version = "1.0.0" + ), + options = ServerOptions( + capabilities = ServerCapabilities( + tools = ServerCapabilities.Tools(listChanged = true), + ), + ) +) -fun Application.module() { - mcp { - Server( - serverInfo = Implementation( - name = "example-sse-server", - version = "1.0.0" - ), - options = ServerOptions( - capabilities = ServerCapabilities( - prompts = ServerCapabilities.Prompts(listChanged = null), - resources = ServerCapabilities.Resources(subscribe = null, listChanged = null) - ) - ), - ) { - "This SSE server provides prompts and resources via Server-Sent Events." - } - } +server.addTool( + name = "echo", + description = "Return whatever the user sent back to them", +) { request -> + val text = request.arguments?.get("text")?.jsonPrimitive?.content ?: "(empty)" + CallToolResult(content = listOf(TextContent(text = "Echo: $text"))) } ``` -Inside a custom Ktor's `Route`: + + + + +Register as many tools as you need—long-running jobs can report progress via the request context, and tools can also +trigger sampling (see below) when they need the client’s LLM. Set `listChanged = true` only if your tool catalog can +change at runtime and your server will emit `notifications/tools/list_changed` when it does. + +#### Completion + +Completion provides argument suggestions for prompts or resource templates. Declare the `completions` capability and +handle `completion/complete` requests to return up to 100 ranked values (include `total`/`hasMore` if you paginate). + + + ```kotlin -import io.ktor.server.application.* -import io.ktor.server.sse.SSE -import io.modelcontextprotocol.kotlin.sdk.server.mcp +val server = Server( + serverInfo = Implementation( + name = "example-server", + version = "1.0.0" + ), + options = ServerOptions( + capabilities = ServerCapabilities( + completions = ServerCapabilities.Completions, + ), + ) +) -fun Application.module() { - install(SSE) +val session = server.createSession( + StdioServerTransport( + inputStream = System.`in`.asSource().buffered(), + outputStream = System.out.asSink().buffered() + ) +) - routing { - route("myRoute") { - mcp { - Server( - serverInfo = Implementation( - name = "example-sse-server", - version = "1.0.0" - ), - options = ServerOptions( - capabilities = ServerCapabilities( - prompts = ServerCapabilities.Prompts(listChanged = null), - resources = ServerCapabilities.Resources(subscribe = null, listChanged = null) - ) - ), - ) { - "Connect via SSE to interact with this MCP server." - } - } - } - } +session.setRequestHandler(Method.Defined.CompletionComplete) { request, _ -> + val options = listOf("kotlin", "compose", "coroutine") + val matches = options.filter { it.startsWith(request.argument.value.lowercase()) } + + CompleteResult( + completion = CompleteResult.Completion( + values = matches.take(3), + total = matches.size, + hasMore = matches.size > 3, + ), + ) } ``` + + + + + +Use `context.arguments` to refine suggestions for dependent fields (e.g., framework list filtered by chosen language). + +#### Logging + +Logging lets the server stream structured log notifications to the client using RFC 5424 levels (`debug` → `emergency`). +Declare the `logging` capability; clients can raise the minimum level with `logging/setLevel`, and the server emits +`notifications/message` with severity, optional logger name, and JSON data. + + + +```kotlin +val server = Server( + serverInfo = Implementation("example-server", "1.0.0"), + options = ServerOptions( + capabilities = ServerCapabilities( + logging = ServerCapabilities.Logging, + ), + ) +) + +val session = server.createSession( + StdioServerTransport( + inputStream = System.`in`.asSource().buffered(), + outputStream = System.out.asSink().buffered() + ) +) + +session.sendLoggingMessage( + LoggingMessageNotification( + LoggingMessageNotificationParams( + level = LoggingLevel.Info, + logger = "startup", + data = buildJsonObject { put("message", "Server started") }, + ), + ), +) +``` + + + + + +Keep logs free of sensitive data, and expect clients to surface them in their own UI. + +#### Pagination + +List operations return paginated results with an opaque `nextCursor`, clients echo that cursor to fetch the next page. +Supported list calls: `resources/list`, `resources/templates/list`, `prompts/list`, and `tools/list`. +Treat cursors as opaque—don’t parse or persist them across sessions. + + + +```kotlin +val server = Server( + serverInfo = Implementation("example-server", "1.0.0"), + options = ServerOptions( + capabilities = ServerCapabilities( + resources = ServerCapabilities.Resources(), + ), + ) +) + +val session = server.createSession( + StdioServerTransport( + inputStream = System.`in`.asSource().buffered(), + outputStream = System.out.asSink().buffered() + ) +) + +val resources = listOf( + Resource(uri = "note://1", name = "Note 1", description = "First"), + Resource(uri = "note://2", name = "Note 2", description = "Second"), + Resource(uri = "note://3", name = "Note 3", description = "Third"), +) +val pageSize = 2 + +session.setRequestHandler(Method.Defined.ResourcesList) { request, _ -> + val start = request.params?.cursor?.toIntOrNull() ?: 0 + val page = resources.drop(start).take(pageSize) + val next = if (start + page.size < resources.size) (start + page.size).toString() else null + + ListResourcesResult( + resources = page, + nextCursor = next, + ) +} +``` + + + + + +Include `nextCursor` only when more items remain an absent cursor ends pagination. + +### Client Features + +Clients advertise their capabilities (roots, sampling, elicitation, etc.) during initialization. After that they can +serve requests from the server while still initiating calls such as `listTools` or `callTool`. + +#### Roots + +Roots let the client declare where the server is allowed to operate. Declare the `roots` capability, respond to +`roots/list`, and emit `notifications/roots/list_changed` if you set `listChanged = true`. URIs **must** be `file://` +paths. + + + +```kotlin +val client = Client( + clientInfo = Implementation("demo-client", "1.0.0"), + options = ClientOptions( + capabilities = ClientCapabilities(roots = ClientCapabilities.Roots(listChanged = true)), + ), +) + +client.addRoot( + uri = "file:///Users/demo/projects", + name = "Projects", +) +client.sendRootsListChanged() +``` + + + + + +Call `addRoot`/`removeRoot` whenever your file system view changes, and use `sendRootsListChanged()` to notify the +server. Keep root lists user-controlled and revoke entries that are no longer authorized. + +#### Sampling + +Sampling lets the server ask the client to call its preferred LLM. Declare the `sampling` capability (and +`sampling.tools` if you allow tool-enabled sampling), and handle `sampling/createMessage`. Keep a human in the loop for +approvals. + + + +```kotlin +val client = Client( + clientInfo = Implementation("demo-client", "1.0.0"), + options = ClientOptions( + capabilities = ClientCapabilities( + sampling = buildJsonObject { putJsonObject("tools") { } }, // drop tools if you don't support tool use + ), + ), +) + +client.setRequestHandler(Method.Defined.SamplingCreateMessage) { request, _ -> + val content = request.messages.lastOrNull()?.content + val prompt = if (content is TextContent) content.text else "your topic" + CreateMessageResult( + model = "gpt-4o-mini", + role = Role.Assistant, + content = TextContent(text = "Here is a short note about $prompt"), + ) +} +``` + + + + + +Inside the handler you can pick any model/provider, require approvals, or reject the request. If you don’t support tool +use, omit `sampling.tools` from capabilities. + +[//]: # (TODO: add elicitation section) + +[//]: # (#### Elicitation) + +## Transports + +All transports share the same API surface, so you can change deployment style without touching business logic. Pick the +transport that best matches where the server runs. + +### STDIO Transport + +`StdioClientTransport` and `StdioServerTransport` tunnel MCP messages over stdin/stdout—perfect for editor plugins or +CLI tooling that spawns a helper process. No networking setup is required. + +### Streamable HTTP Transport + +`StreamableHttpClientTransport` and the Ktor `streamableHttpApp()` helpers expose MCP over a single HTTP endpoint with +optional JSON-only or SSE streaming responses. This is the recommended choice for remote deployments and integrates +nicely with proxies or service meshes. + +### SSE Transport + +Server-Sent Events remain available for backwards compatibility with older MCP clients. Use `SseServerTransport` or the +SSE Ktor plugin when you need drop-in compatibility, but prefer Streamable HTTP for new projects. + +### WebSocket Transport + +`WebSocketClientTransport` plus the matching server utilities provide full-duplex, low-latency connections—useful when +you expect lots of notifications or long-running sessions behind a reverse proxy that already terminates WebSockets. + +## Connecting your server + +1. Start a sample HTTP server on port 3000: + + ```bash + ./gradlew :samples:kotlin-mcp-server:run + ``` + +2. Connect with the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) or Claude Desktop/Code: + + ```bash + npx -y @modelcontextprotocol/inspector --connect http://localhost:3000 + # or + claude mcp add --transport http kotlin-mcp http://localhost:3000 + ``` + +3. In the Inspector, confirm prompts, tools, resources, completions, and logs show up. Iterate locally until you’re + ready to host the server wherever you prefer. + +## Examples + +| Scenario | Description | Example | +|--------------------------|-----------------------------------------------------------------|--------------------------------------------------------------------------| +| Streamable HTTP server | Full MCP server with prompts, resources, tools, completions | [samples/kotlin-mcp-server](./samples/kotlin-mcp-server) | +| STDIO weather server | Minimal STDIO transport server exposing weather info and alerts | [samples/weather-stdio-server](./samples/weather-stdio-server) | +| Interactive STDIO client | MCP client that connects over STDIO and pipes requests to LLMs | [samples/kotlin-mcp-client](./samples/kotlin-mcp-client) | +| Streamable HTTP client | MCP client demo in a runnable notebook | [samples/notebooks/McpClient.ipynb](./samples/notebooks/McpClient.ipynb) | + +## Documentation + +- [API Reference](https://modelcontextprotocol.github.io/kotlin-sdk/) +- [Model Context Protocol documentation](https://modelcontextprotocol.io) +- [MCP specification](https://modelcontextprotocol.io/specification/latest) + ## Contributing Please see the [contribution guide](CONTRIBUTING.md) and the [Code of conduct](CODE_OF_CONDUCT.md) before contributing. diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..8eba6c8 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1 @@ +src/ diff --git a/docs/build.gradle.kts b/docs/build.gradle.kts new file mode 100644 index 0000000..ac24979 --- /dev/null +++ b/docs/build.gradle.kts @@ -0,0 +1,35 @@ +plugins { + kotlin("jvm") + alias(libs.plugins.knit) +} + +dependencies { + implementation(project(":kotlin-sdk")) + implementation(libs.ktor.server.cio) +} + +tasks.matching { + it.name == "ktlintMainSourceSetCheck" || it.name == "ktlintMainSourceSetFormat" +}.configureEach { + enabled = false +} + +knit { + rootDir = project.rootDir + files = files(project.rootDir.resolve("README.md")) + defaultLineSeparator = "\n" + siteRoot = "" // Disable site root validation +} + +// Only run knitCheck when explicitly requested, not as part of build/check +tasks.named("knitCheck") { + onlyIf { + gradle.startParameter.taskNames.any { + it.contains("knitCheck") || it.contains("knit") + } + } +} + +tasks.clean { + delete("src") +} diff --git a/docs/moduledoc.md b/docs/moduledoc.md index 508fef9..05ce370 100644 --- a/docs/moduledoc.md +++ b/docs/moduledoc.md @@ -1,21 +1,17 @@ # MCP Kotlin SDK -Kotlin SDK for the Model Context Protocol (MCP). -This is a Kotlin Multiplatform library that helps you build MCP clients and servers that speak the same protocol and -share the same types. -The SDK focuses on clarity, small building blocks, and first‑class coroutine support. +Kotlin Multiplatform implementation of the Model Context Protocol (MCP). The SDK focuses on clear, explicit APIs, +small building blocks, and first-class coroutine support so clients and servers share the same well-typed messages and +transports. -Use the umbrella `kotlin-sdk` artifact when you want a single dependency that brings the core types plus both client and -server toolkits. If you only need one side, depend on `kotlin-sdk-client` or `kotlin-sdk-server` directly. - -Gradle (Kotlin DSL): +Use the umbrella `kotlin-sdk` artifact to bring in everything at once, or depend on the focused modules directly: ```kotlin dependencies { - // Convenience bundle with everything you need to start + // All-in-one bundle implementation("io.modelcontextprotocol:kotlin-sdk:") - // Or pick modules explicitly + // Or pick sides explicitly implementation("io.modelcontextprotocol:kotlin-sdk-client:") implementation("io.modelcontextprotocol:kotlin-sdk-server:") } @@ -25,79 +21,74 @@ dependencies { ## Module kotlin-sdk-core -Foundational, platform‑agnostic pieces: +Foundation shared by both sides: -- Protocol data model and JSON serialization (kotlinx.serialization) -- Request/response and notification types used by both sides of MCP -- Coroutine‑friendly protocol engine and utilities -- Transport abstractions shared by client and server +- Protocol data model for MCP requests, results, notifications, capabilities, and content types. +- `McpJson` (kotlinx.serialization) with MCP-friendly defaults plus helpers for converting native values to JSON. +- Transport abstractions (`Transport`, `AbstractTransport`, `WebSocketMcpTransport`) and streaming `ReadBuffer`. +- `Protocol` base class that handles JSON-RPC framing, correlation, progress tokens, and capability assertions. -You typically do not use `core` directly in application code; it is pulled in by the client/server modules. Use it -explicitly if you only need the protocol types or plan to implement a custom transport. +Use `core` when you need the raw types or want to author a custom transport. Public APIs are explicit to keep the shared +surface stable across platforms. --- ## Module kotlin-sdk-client -High‑level client API for connecting to an MCP server and invoking its tools, prompts, and resources. Ships with several -transports: +High-level client for connecting to MCP servers and invoking their features: -- WebSocketClientTransport – low latency, full‑duplex -- SSEClientTransport – Server‑Sent Events over HTTP -- StdioClientTransport – CLI‑friendly stdio bridge -- StreamableHttpClientTransport – simple HTTP streaming +- `Client` runtime (or `mcpClient` helper) performs the MCP handshake and exposes `serverCapabilities`, + `serverVersion`, and `serverInstructions`. +- Typed operations for tools, prompts, resources, completion, logging, roots, sampling, and elicitation with capability + enforcement. +- Transports: `StdioClientTransport`, `SseClientTransport`, `WebSocketClientTransport`, and `StreamableHttpClientTransport`, + plus Ktor client extensions for quick wiring. -A minimal client: +Minimal WebSocket client: ```kotlin -val client = Client( - clientInfo = Implementation(name = "sample-client", version = "1.0.0") +val client = mcpClient( + clientInfo = Implementation("sample-client", "1.0.0"), + clientOptions = ClientOptions(ClientCapabilities(tools = ClientCapabilities.Tools())), + transport = WebSocketClientTransport("ws://localhost:8080/mcp") ) -client.connect(WebSocketClientTransport("ws://localhost:8080/mcp")) - val tools = client.listTools() -val result = client.callTool( - name = "echo", - arguments = mapOf("text" to "Hello, MCP!") -) +val result = client.callTool("echo", mapOf("text" to "Hello, MCP!")) +println(result.content) ``` --- ## Module kotlin-sdk-server -Lightweight server toolkit for hosting MCP tools, prompts, and resources. It provides a small, composable API and -ready‑to‑use transports: +Server toolkit for exposing MCP tools, prompts, and resources: -- StdioServerTransport – integrates well with CLIs and editors -- SSE/WebSocket helpers for Ktor – easy HTTP deployment +- `Server` runtime coordinates sessions, initialization flow, and capability enforcement with registries for tools, + prompts, resources, and templates. +- Transports: `StdioServerTransport` for CLI/editor bridges; Ktor extensions (`mcp` for SSE + POST back-channel and + `mcpWebSocket` for WebSocket) for HTTP hosting. +- Built-in notifications for list changes and resource subscriptions when capabilities enable them. -Register tools and run over stdio: +Minimal Ktor SSE server: ```kotlin - -val server = Server( - serverInfo = Implementation(name = "sample-server", version = "1.0.0"), - options = ServerOptions(ServerCapabilities()) -) - -server.addTool( - name = "echo", - description = "Echoes the provided text" -) { request -> - // Build and return a CallToolResult from request.arguments - // (see CallToolResult and related types in kotlin-sdk-core) - /* ... */ +fun Application.module() { + mcp { + Server( + serverInfo = Implementation("sample-server", "1.0.0"), + options = ServerOptions(ServerCapabilities( + tools = ServerCapabilities.Tools(listChanged = true), + resources = ServerCapabilities.Resources(listChanged = true, subscribe = true), + )), + ) { + addTool(name = "echo", description = "Echo text back") { request -> + val text = request.params.arguments?.get("text")?.jsonPrimitive?.content ?: "" + CallToolResult(content = listOf(TextContent("You said: $text"))) + } + } + } } - -// Bridge the protocol over stdio -val transport = StdioServerTransport( - inputStream = kotlinx.io.files.Path("/dev/stdin").source(), - outputStream = kotlinx.io.files.Path("/dev/stdout").sink() -) -// Start transport and wire it with the server using provided helpers in the SDK. ``` -For HTTP deployments, use the Ktor extensions included in the module to expose an MCP WebSocket or SSE endpoint with a -few lines of code. +Pick the module that matches your role, or use the umbrella artifact to get both sides with the shared core. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1d5fd74..7f25127 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,6 +7,7 @@ dokka = "2.1.0" kotlin = "2.2.21" kover = "0.9.4" ktlint = "14.0.1" +knit = "0.5.1" mavenPublish = "0.36.0" netty = "4.2.9.Final" openapi-generator = "7.18.0" @@ -23,7 +24,7 @@ mockk = "1.14.7" mokksy = "0.6.2" serialization = "1.9.0" slf4j = "2.0.17" -junit="5.14.2" +junit = "5.14.2" [libraries] # Plugins @@ -74,4 +75,5 @@ detekt = { id = "dev.detekt", version.ref = "detekt" } kotlinx-binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binaryCompatibilityValidatorPlugin" } kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" } +knit = { id = "org.jetbrains.kotlinx.knit", version.ref = "knit" } openapi-generator = { id = "org.openapi.generator", version.ref = "openapi-generator" } diff --git a/knit.properties b/knit.properties new file mode 100644 index 0000000..1c74097 --- /dev/null +++ b/knit.properties @@ -0,0 +1,3 @@ +# Knit configuration +knit.dir=docs/src/main/kotlin/ +knit.package=io.modelcontextprotocol.kotlin.sdk.examples diff --git a/kotlin-sdk-client/Module.md b/kotlin-sdk-client/Module.md index 7154911..4e6fd63 100644 --- a/kotlin-sdk-client/Module.md +++ b/kotlin-sdk-client/Module.md @@ -1,10 +1,78 @@ # Module kotlin-sdk-client -The Model Context Protocol (MCP) Kotlin SDK client module provides a multiplatform implementation for connecting to and -interacting with MCP servers. +`kotlin-sdk-client` is the multiplatform MCP client for Kotlin. It handles the MCP handshake, enforces capabilities, and +gives you typed APIs to call tools, prompts, resources, completions, and logging endpoints on MCP servers. It reuses +protocol types from `kotlin-sdk-core` and ships with transports for CLI and Ktor-based networking. -## Features +## What the module provides -- Cross-platform support for JVM, WASM, JS, and Native -- Built-in support for multiple transport protocols (stdio, SSE, WebSocket) -- Type-safe API for resource management and server interactions +- **Client runtime**: The `Client` class (or `mcpClient` helper) wraps initialization, capability checks, and + request/notification plumbing. After connecting, it exposes `serverCapabilities`, `serverVersion`, and + `serverInstructions`. +- **Typed operations**: Convenience functions like `listTools`, `callTool`, `listResources`, `readResource`, + `listPrompts`, `complete`, `setLoggingLevel`, and subscription helpers for resources. +- **Capability enforcement**: Methods fail fast if the server does not advertise the needed capability; optional + strictness mirrors the protocol options used by the server SDK. +- **Transports**: Ready-to-use implementations for STDIO (CLI interoperability), Ktor SSE (`SseClientTransport` with + POST back-channel), Ktor WebSocket, and Streamable HTTP for environments that prefer request/response streaming. +- **Ktor client integration**: Extension helpers wire MCP over Ktor client engines with minimal setup for SSE, + Streamable HTTP, or WebSocket. + +## Typical client setup + +1. Declare client capabilities with `ClientOptions` (e.g., sampling, roots, elicitation, experimental). +2. Create a transport suitable for your environment. +3. Connect using `mcpClient` or instantiate `Client` and call `connect(transport)`. +4. Use typed APIs to interact with the server; results and errors use the shared MCP types. + +### Minimal STDIO client + +```kotlin +val client = mcpClient( + clientInfo = Implementation(name = "demo-client", version = "1.0.0"), + clientOptions = ClientOptions( + capabilities = ClientCapabilities( + tools = ClientCapabilities.Tools(), + resources = ClientCapabilities.Resources(subscribe = true), + ) + ), + transport = StdioClientTransport( + System.`in`.asSource().buffered(), + System.out.asSink().buffered() + ) +) + +val tools = client.listTools() +val result = client.callTool("hello", mapOf("name" to "Kotlin")) +println(result.content) +``` + +### Ktor-based transports + +- **SSE** (streaming GET + POST back-channel): create `SseClientTransport(httpClient, url)` (or + `HttpClient.mcpSseTransport(url)`); sessions are keyed by `sessionId` managed by the SDK. +- **WebSocket**: use `WebSocketClientTransport(httpClient, url)` (or `HttpClient.mcpWebSocketTransport(url)`) to get + bidirectional messaging in one connection. +- **Streamable HTTP**: `StreamableHttpClientTransport(httpClient, url)` enables MCP over streaming HTTP + (or `HttpClient.mcpStreamableHttpTransport(url)`). + +## Feature usage highlights + +- **Tools**: `callTool` accepts raw maps or `CallToolRequest`; metadata keys are validated per MCP rules. +- **Prompts & completion**: `getPrompt`, `listPrompts`, and `complete` wrap common prompt flows. +- **Resources**: `listResources`, `listResourceTemplates`, `readResource`, and `subscribeResource` cover discovery, + templating, and updates (when the server allows subscriptions). +- **Roots**: If enabled, register local roots with `addRoot`/`addRoots`; the client responds to `RootsList` requests + from the server. +- **Notifications**: Built-in handlers cover initialization, cancellation, progress, and roots list changes; you can + register more handlers via the underlying `Protocol` API. + +## Diagnostics and lifecycle + +- Capability assertions prevent calling endpoints the server does not expose; errors surface with clear messages. +- Logging uses `KotlinLogging`; enable DEBUG to inspect transport and handshake behavior. +- `close()` tears down the transport and cancels in-flight requests. + +Use this module whenever you need an MCP-aware Kotlin client—whether embedding in a CLI over STDIO or talking to remote +servers via Ktor SSE/WebSocket/streamable HTTP transports. Public APIs are explicit so downstream users can rely on +stable, well-typed surface areas across platforms. diff --git a/kotlin-sdk-core/Module.md b/kotlin-sdk-core/Module.md index 696360a..b4ed71b 100644 --- a/kotlin-sdk-core/Module.md +++ b/kotlin-sdk-core/Module.md @@ -1,5 +1,47 @@ # Module kotlin-sdk-core -The MCP Kotlin SDK Core module provides fundamental functionality for interacting with Model Context Protocol (MCP). -This module serves as the foundation for building MCP-enabled applications in Kotlin. +`kotlin-sdk-core` is the foundation of the MCP Kotlin SDK. It contains protocol message types, JSON handling, and +transport abstractions used by both client and server modules. No platform-specific code lives here; everything is +designed for Kotlin Multiplatform with explicit API mode enabled. +## What the module provides + +- **Protocol model**: Complete MCP data classes for requests, results, notifications, capabilities, tools, prompts, + resources, logging, completion, sampling, elicitation, and roots. DSL helpers (`*.dsl.kt`) let you build messages + fluently without repeating boilerplate. +- **JSON utilities**: A shared `McpJson` configuration (kotlinx.serialization) with MCP-friendly settings (ignore + unknown keys, no class discriminator). Helpers for converting maps to `JsonElement`, `EmptyJsonObject`, and + encoding/decoding JSON-RPC envelopes. +- **Transport abstractions**: `Transport` and `AbstractTransport` define the message pipeline, callbacks, and error + handling. `WebSocketMcpTransport` adds a shared WebSocket implementation for both client and server sides, and + `ReadBuffer` handles streaming JSON-RPC framing. +- **Protocol engine**: The `Protocol` base class manages request/response correlation, notifications, progress tokens, + and capability assertions. Higher-level modules extend it to become `Client` and `Server`. +- **Errors and safety**: Common exception types (`McpException`, parsing errors) plus capability enforcement hooks + ensure callers cannot use endpoints the peer does not advertise. + +## Typical usage + +- Import MCP types to define capabilities and message payloads for your own transports or custom integrations. +- Extend `Protocol` if you need a bespoke peer role while reusing correlation logic and JSON-RPC helpers. +- Use the DSL builders to construct well-typed requests (tools, prompts, resources, logging, completion) instead of + crafting raw JSON. + +### Example: building a tool call request + +```kotlin +val request = CallToolRequest { + name = "summarize" + arguments = mapOf("text" to "Hello MCP") +} +``` + +### Example: parsing a JSON-RPC message + +```kotlin +val message: JSONRPCMessage = McpJson.decodeFromString(jsonString) +``` + +Use this module when you need the raw building blocks of MCP—types, JSON config, and transport base classes—whether to +embed in another runtime, author new transports, or contribute higher-level features in the client/server modules. The +APIs are explicit to keep the shared surface stable for downstream users. diff --git a/kotlin-sdk-server/Module.md b/kotlin-sdk-server/Module.md index 94ae71e..43a7359 100644 --- a/kotlin-sdk-server/Module.md +++ b/kotlin-sdk-server/Module.md @@ -1,14 +1,125 @@ # Module kotlin-sdk-server -The server module provides core server-side functionality for the Model Context Protocol (MCP) Kotlin SDK. -This module includes essential parts for writing MCP server applications. +`kotlin-sdk-server` contains the server-side building blocks for Model Context Protocol (MCP) applications written in +Kotlin Multiplatform. It pairs the protocol types from `kotlin-sdk-core` with transport implementations and utilities +that make it straightforward to expose tools, prompts, and resources to MCP clients. -## Features +## What the module provides -* Server-side request handling -* Model context management -* Protocol implementation -* Integration utilities +- **Server runtime**: The `Server` class coordinates sessions, initialization flow, and capability enforcement. It + tracks active sessions and cleans them up when transports close. +- **Feature registries**: Helpers to register and remove tools, prompts, resources, and resource templates. Registry + listeners emit list-changed notifications when the capability is enabled. +- **Transports**: Ready-to-use transports for STDIO (CLI-style hosting), Ktor Server-Sent Events (SSE) with POST + back-channel, and Ktor WebSocket. All share the common `Transport` abstraction from the SDK. +- **Ktor integration**: Extension functions (`Routing.mcp`, `Routing.mcpWebSocket`, and `Application` variants) that + plug MCP into an existing Ktor server with minimal boilerplate. +- **Notifications**: Built-in support for resource subscription, resource-updated events, and feature list change + notifications when capabilities are set accordingly. -For detailed usage instructions and examples, please refer to -the [documentation](https://github.com/modelcontextprotocol/kotlin-sdk/blob/main/README.md). +## Typical server setup + +1. Declare capabilities your server supports using `ServerOptions`. Capabilities drive which handlers are installed and + whether list-change notifications are emitted. +2. Register features: tools, prompts, resources, and resource templates. Handlers run in suspending context and can be + removed or replaced at runtime. +3. Attach a transport and open a session via `createSession(transport)`. Multiple sessions can be active simultaneously. +4. Close the server to tear down transports and unsubscribe sessions. + +### Minimal STDIO server + +```kotlin +val server = Server( + serverInfo = Implementation(name = "demo-server", version = "1.0.0"), + options = ServerOptions( + capabilities = ServerCapabilities( + tools = ServerCapabilities.Tools(listChanged = true) + ) + ) +) { + addTool( + name = "hello", + description = "Greets the caller by name" + ) { request -> + val name = request.params.arguments?.get("name")?.jsonPrimitive?.content ?: "there" + CallToolResult( + content = listOf(TextContent("Hello, $name!")), + isError = false, + ) + } +} + +val transport = StdioServerTransport( + System.`in`.asSource().buffered(), + System.out.asSink().buffered() +) +server.createSession(transport) +``` + +### Hosting with Ktor (SSE) + +```kotlin +fun Application.module() { + mcp { + Server( + serverInfo = Implementation("ktor-sse", "1.0.0"), + options = ServerOptions( + ServerCapabilities( + prompts = ServerCapabilities.Prompts(listChanged = true), + resources = ServerCapabilities.Resources(listChanged = true, subscribe = true), + ) + ) + ) { + addPrompt( + name = "welcome", + description = "Explain how to use the server" + ) { + GetPromptResult( + description = "Welcome to the MCP server", + messages = listOf(CreateMessageResult.Message(TextContent("Try listing tools first"))) + ) + } + } + } +} +``` + +The SSE route exposes a streaming GET endpoint and a POST endpoint for client messages. Session affinity is maintained +by a `sessionId` query parameter managed by the SDK. + +### Hosting with Ktor (WebSocket) + +```kotlin +fun Application.module() { + mcpWebSocket { + Server( + serverInfo = Implementation("ktor-ws", "1.0.0"), + options = ServerOptions(ServerCapabilities(tools = ServerCapabilities.Tools())) + ) + } +} +``` + +`mcpWebSocket` installs Ktor WebSockets and wires message handling over a single bidirectional connection. + +## Feature registration details + +- **Tools**: `addTool` accepts a `Tool` descriptor or inline parameters plus a suspend handler. `removeTool` and + `addTools/removeTools` support dynamic updates. +- **Prompts**: `addPrompt` wires a provider that returns `GetPromptResult` and optional arguments metadata. +- **Resources**: `addResource` exposes static or generated content via `ReadResourceResult`; `addResources` + batch-registers. Templates and roots are also supported for structured discovery. +- **Instructions**: Provide static text or a function via `instructionsProvider` to send per-session usage guidance + during initialization. + +## Lifecycle and diagnostics + +- Sessions emit callbacks via `onConnect` and `onClose`. Deprecated `onInitialized` exists for backward compatibility; + prefer session-level hooks. +- The server enforces declared capabilities; attempting to register an unsupported feature throws with a descriptive + message. +- Logging uses `KotlinLogging`; avoid logging secrets. To inspect traffic, run the server with DEBUG level enabled. + +Use this module whenever you need to expose MCP features from a Kotlin service—whether embedding in a CLI via STDIO, +serving from Ktor over SSE/WebSocket, or composing transports for custom environments. Public APIs are explicit to keep +server-facing contracts stable for consumers. diff --git a/settings.gradle.kts b/settings.gradle.kts index 9a646a1..95d80b1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -24,5 +24,6 @@ include( ":kotlin-sdk-server", ":kotlin-sdk", ":integration-test", + ":docs", ":conformance-test", ) From 4b85bf84790c7383f4154772a5231233f6370c72 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Mon, 9 Feb 2026 12:47:43 +0200 Subject: [PATCH 135/449] fix(test): Refactor flaky StdioClientTransport tests to use real stdin pipes (#508) # fix(test): Refactor flaky StdioClientTransport tests to use real stdin pipes - Replace mock-based stdin with real input/output pipes for better parity with actual usage. - Ensure proper cleanup of resources (stdin and stdout pipes). - Improve test stability by migrating from `runTest` to `runBlocking` for real-time I/O operations. ## Motivation and Context StdioClientTransportTest is flaky. ## How Has This Been Tested? Unit test ## Breaking Changes No ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 --- .../StdioClientTransportErrorHandlingTest.kt | 67 ++++++++++++------- 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/stdio/StdioClientTransportErrorHandlingTest.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/stdio/StdioClientTransportErrorHandlingTest.kt index 89b6042..e93be84 100644 --- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/stdio/StdioClientTransportErrorHandlingTest.kt +++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/stdio/StdioClientTransportErrorHandlingTest.kt @@ -7,7 +7,6 @@ import io.kotest.matchers.shouldBe import io.kotest.matchers.types.shouldBeInstanceOf import io.kotest.matchers.types.shouldBeSameInstanceAs import io.mockk.coEvery -import io.mockk.every import io.mockk.mockk import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage @@ -15,19 +14,26 @@ import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest import io.modelcontextprotocol.kotlin.sdk.types.McpException import io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.ClosedSendChannelException import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import kotlinx.io.Buffer +import kotlinx.io.asSource +import kotlinx.io.buffered import kotlinx.io.writeString import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource +import java.io.PipedInputStream +import java.io.PipedOutputStream import java.util.stream.Stream import kotlin.concurrent.atomics.AtomicBoolean import kotlin.concurrent.atomics.ExperimentalAtomicApi import kotlin.test.Test +import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds /** @@ -39,17 +45,23 @@ class StdioClientTransportErrorHandlingTest { @OptIn(ExperimentalAtomicApi::class) @Test - fun `should continue on stderr EOF`() = runTest { - val stderrBuffer = Buffer() + fun `should continue on stderr EOF`(): Unit = runBlocking(Dispatchers.IO) { // Empty stderr = immediate EOF + val stderrBuffer = Buffer() + + // Create a pipe for stdin that stays open (simulates real stdin behavior) + val pipedOutputStream = PipedOutputStream() + val pipedInputStream = PipedInputStream(pipedOutputStream) + + // Write one message to stdin + pipedOutputStream.write("""data: {"jsonrpc":"2.0","method":"ping","id":1}\n\n""".toByteArray()) + pipedOutputStream.flush() + // Keep the pipe open by not closing pipedOutputStream - this prevents stdin EOF - val inputBuffer = createNonEmptyBuffer { - """data: {"jsonrpc":"2.0","method":"ping","id":1}\n\n""" - } val outputBuffer = Buffer() transport = StdioClientTransport( - input = inputBuffer, + input = pipedInputStream.asSource().buffered(), output = outputBuffer, error = stderrBuffer, ) @@ -59,15 +71,21 @@ class StdioClientTransportErrorHandlingTest { transport.start() - // Stderr EOF should not close transport - // Use eventually to handle timing differences across platforms (especially Windows) - eventually(2.seconds) { - // Wait for stderr to be processed, then verify transport is still open - closeCalled.load() shouldBe false - } + // Wait for stderr EOF and stdin message to be processed + delay(500.milliseconds) - transport.close() - closeCalled.load() shouldBe true + // Transport should still be alive because stdin is still open (not EOF'd) + closeCalled.load() shouldBe false + + // Close pipes to trigger stdin EOF. + // `transport.close()` cann't help here, since the underlying Java read() is blocked on I/O operation + pipedOutputStream.close() + pipedInputStream.close() + + // Transport should close when stdin EOF is detected + eventually(2.seconds) { + closeCalled.load() shouldBe true + } } @Test @@ -162,18 +180,13 @@ class StdioClientTransportErrorHandlingTest { fun `Send should handle exceptions`(throwable: Throwable, shouldWrap: Boolean, expectedCode: Int?) = runTest { val sendChannel: Channel = mockk(relaxed = true) - // Create a stdin Source that never returns EOF to prevent transport from closing - // Use mockk since Source is a sealed interface - val stdin: kotlinx.io.Source = mockk(relaxed = true) - every { stdin.readAtMostTo(any(), any()) } coAnswers { - // Return 0 to indicate no data available (but not EOF) - // This keeps the transport alive without blocking - delay(10) // Small delay to prevent busy-waiting - 0L - } + // Create stdin pipe that stays open to prevent transport from closing + val pipedOutputStream = PipedOutputStream() + val pipedInputStream = PipedInputStream(pipedOutputStream) + // Keep pipe open (don't write or close) - stdin will block on read, not EOF transport = StdioClientTransport( - input = stdin, + input = pipedInputStream.asSource().buffered(), output = Buffer(), sendChannel = sendChannel, ) @@ -195,6 +208,10 @@ class StdioClientTransportErrorHandlingTest { } else { exception shouldBeSameInstanceAs throwable } + + // Cleanup + pipedOutputStream.close() + pipedInputStream.close() } fun createNonEmptyBuffer(block: () -> String): Buffer { From fb934682eadc319a04e51c521c93f7065a5e9eca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Feb 2026 13:07:27 +0200 Subject: [PATCH 136/449] Bump io.netty:netty-bom from 4.2.9.Final to 4.2.10.Final (#511) Bumps [io.netty:netty-bom](https://github.com/netty/netty) from 4.2.9.Final to 4.2.10.Final.
Commits
  • 4cc9873 [maven-release-plugin] prepare release netty-4.2.10.Final
  • 54b8663 Remove unnecessary allocations and abstractions in HttpContentCompressor (#16...
  • 961f427 Update to netty-tcnative 2.0.75.Final (#16227)
  • 3007ba9 Use recommanded finalize chain pattern when override finalize() method (#16212)
  • b918042 Update some dependencies (#16198) (#16215)
  • 874c995 Reduce allocations on DefaultHeaders::containsValue (#15843)
  • e0fe794 Remove unnecessary null check in WebSocketServerExtensionHandler (#16201)
  • 1b0636b Move default compression options into static variable in HttpContentCompresso...
  • 85a3a0e codec-http2: move the accessors from Http2Headers to DefaultHttp2Headers (#16...
  • f44a88d Improve chunk picking for the large-size buddy allocator (#16179)
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=io.netty:netty-bom&package-manager=gradle&previous-version=4.2.9.Final&new-version=4.2.10.Final)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7f25127..206687b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,7 +9,7 @@ kover = "0.9.4" ktlint = "14.0.1" knit = "0.5.1" mavenPublish = "0.36.0" -netty = "4.2.9.Final" +netty = "4.2.10.Final" openapi-generator = "7.18.0" # libraries version From c40fa16563a2cec6970b5cbd807657019d87761c Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Wed, 11 Feb 2026 11:32:56 +0200 Subject: [PATCH 137/449] docs: Update AGENTS.md (#509) # docs: Update AGENTS.md - Introduce CLAUDE.md symlink to AGENTS.md - Enhance AGENTS.md with updated test setup, coding standards, and module-specific practices ## Motivation and Context Motivation: Improve consistency, readability, and maintainability of code contributions across AI agents and developers. ## Breaking Changes No ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] 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 - [x] I have added or updated documentation as needed ## Verified image (I have other files locally, but AGENTS.md is known to Claude) --- AGENTS.md | 364 ++++++++++++++++++++++++++++++++++++++++++------------ CLAUDE.md | 1 + 2 files changed, 289 insertions(+), 76 deletions(-) create mode 100644 CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md index 29aacef..bf2b318 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,101 +1,313 @@ -# kotlin-mcp-sdk +# AGENTS: Development Guidelines for AI Contributors -MCP Kotlin SDK — Kotlin Multiplatform implementation of the Model Context Protocol. +This document is for autonomous agents and AI copilots contributing code to this repository. +Follow these rules to keep changes safe, comprehensible, and easy to maintain. ## Repository Layout -- `kotlin-sdk-core`: Core protocol types, transport abstractions, WebSocket implementation +- `kotlin-sdk-core`: Core protocol types, transport abstractions, serialization utilities - `kotlin-sdk-client`: Client transports (STDIO, SSE, StreamableHttp, WebSocket) - `kotlin-sdk-server`: Server transports + Ktor integration (STDIO, SSE, WebSocket) - `kotlin-sdk`: Umbrella module that aggregates all three above -- `kotlin-sdk-test`: Shared test utilities -- `samples/`: Three sample projects (composite builds) +- `test-utils`: Shared test utilities +- `integration-test`: Integration tests with TypeScript/other implementations +- `conformance-test`: Protocol conformance tests +- `samples/`: Sample projects (composite builds) +- Package structure: `io.modelcontextprotocol.kotlin.sdk.*` +- JVM 11+, Kotlin 2.2+ -## General Guidance +## Prime Directives + +1. **Tests first, always.** + - Before changing code, identify or add tests that express the desired behavior. + - Prefer readable, minimal tests over clever ones. Tests are documentation. +2. **Keep tests simple and explicit.** + - Arrange/Act/Assert structure; avoid hidden magic and overuse of helpers. + - Prefer concrete inputs/outputs; avoid randomness and time dependence. +3. **Uphold SOLID principles** in production code: + - Single Responsibility: each class/function should do one thing well. + - Open/Closed: extend via new code, avoid risky edits to stable code paths. + - Liskov Substitution: honor contracts; keep types substitutable. + - Interface Segregation: keep abstractions small and focused. + - Dependency Inversion: depend on abstractions, not concretions. +4. **Make the minimal change** that satisfies the tests and the issue. + Stay focused on a single concern and avoid "drive-by" changes. +5. **Never commit changes!** It's human who should verify and commit to git. +6. **Keep the build green.** Do not merge changes that break existing tests. +7. **Prefer clarity** over micro-optimizations and cleverness. +8. **Ask when uncertain.** If requirements are ambiguous, request clarification with a concise question. +9. **Write code with the quality of a Kotlin Champion.** +10. **Prefer using MCP servers** like `jetbrains` and `intellij-index` for code navigation, analysis, and refactoring. +11. **Use the Bash tool for terminal commands** instead of MCP terminal execution features. +12. **Suggest updates** to AGENTS.md/CLAUDE.md with best practices and guidelines. + +## Code Style + +### Kotlin - Avoid changing public API signatures. Run `./gradlew apiCheck` before every commit. - **Explicit API mode** is strict: all public APIs must have explicit visibility modifiers and return types. - Anything under an `internal` directory is not part of the public API and may change freely. - Package structure follows: `io.modelcontextprotocol.kotlin.sdk.*` -- The SDK targets Kotlin 2.2+ and JVM 1.8+ as minimum. +- Follow Kotlin coding conventions +- Use the provided `.editorconfig` for consistent formatting +- Use Kotlin typesafe DSL builders where possible; prioritize fluent builder style over standard builder methods +- Prefer DSL builder style (method with lambda blocks) over constructors, if possible +- Use `val` for immutable properties and `var` for mutable; consider `lateinit var` instead of nullable types when appropriate +- Use multi-dollar interpolation prefix for strings where applicable +- Use fully qualified imports instead of star imports +- Ensure backward compatibility when making changes -## Building and Testing -1. Build the project: - ```bash - ./gradlew build - ``` -2. Run tests. To save time, run only the module you changed: - ```bash - ./gradlew :kotlin-sdk-core:test - ./gradlew :kotlin-sdk-client:test - ./gradlew :kotlin-sdk-server:test - ``` -3. For platform-specific tests: - ```bash - ./gradlew jsTest - ./gradlew wasmJsTest - ``` -4. Check binary compatibility (required before commit): - ```bash - ./gradlew apiCheck - ``` -5. If you intentionally changed public APIs, update the API dump: - ```bash - ./gradlew apiDump - ``` +## Testing Guidance -## Tests -- All tests for each module are located in `src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/` -- Platform-specific tests go in `src/jvmTest/`, `src/jsTest/`, etc. -- Use Kotest assertions (`shouldBe`, `shouldContain`, etc.) for readable test failures. -- For nullable objects with nested properties, prefer `shouldNotBeNull { ... }` blocks: - ```kotlin - content.annotations shouldNotBeNull { - priority shouldBe 1.0 - } - ``` -- Use `shouldMatchJson` from Kotest for JSON validation. -- Mock Ktor HTTP clients using `MockEngine` from `io.ktor:ktor-client-mock`. -- Always add tests for new features or bug fixes, even if not explicitly requested. +### Test Organization +- **Location**: `src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/` +- **Platform-specific**: `src/jvmTest/`, `src/jsTest/`, etc. +- **Naming**: Use function `Names with backticks`, e.g., `fun \`should return 200 OK\`()` +- **Structure**: Arrange/Act/Assert pattern +- **Documentation**: Keep tests self-documenting; don't add KDocs to tests. -## Code Conventions +### Test Principles +- Write comprehensive tests for new features. Always add tests for new features or bug fixes, even if not explicitly requested. +- **Prioritize test readability** +- Avoid creating too many test methods; use parametrized tests when testing multiple similar scenarios +- When running tests on Kotlin Multiplatform projects, run JVM tests only unless asked for other platforms + +### Test Framework Stack +- **kotlin-test**: Core test framework +- **Kotest assertions**: Use infix style (`shouldBe`, `shouldContain`) instead of `assertEquals`. + - Use `shouldMatchJson` from kotest-assertions-json for JSON validation. + - For nullable objects with nested properties, prefer `shouldNotBeNull { ... }` blocks: + ```kotlin + content.annotations shouldNotBeNull { + priority shouldBe 1.0 + } + ``` +- **mockk**: Mocking library +- **Ktor MockEngine**: For HTTP client mocking (`io.ktor:ktor-client-mock`) +- **Java tests**: Use JUnit5, Mockito, AssertJ core + +### Kotest Patterns + +- Basic assertions: Use infix style (`shouldBe`, `shouldContain`) instead of `assertEquals`. + ```kotlin + result shouldBe expected + list shouldContain item + ``` +- Nullable Assertions + ```kotlin + // Preferred: lambda-style for multiple assertions + content.annotations shouldNotBeNull { + priority shouldBe 1.0 + } + ``` + +- JSON Assertions (kotest-assertions-json): + ```kotlin + // language=json + val expected = """ + { + "id": 123 + } + """.trimIndent() + actual shouldEqualJson expected + ``` + +- Soft Assertions + ```kotlin + // Use for multiple assertions on SAME subject + assertSoftly(subject) { + name shouldBe "test" + value shouldBe 42 + } + // DON'T use for different subjects or single assertions + ``` +- Clues (Use Sparingly) + ```kotlin + // Only when assertion is NOT obvious + withClue("Failed to parse response") { + result shouldNotBe null + } + ``` + +#### Avoiding `this.` in Lambda Blocks +```kotlin +// Avoid explicit `this.` unless necessary for disambiguation +prop.shouldNotBeNull { + enum shouldHaveSize 2 // Preferred + this.enum shouldHaveSize 2 // Avoid +} +``` ### Multiplatform Patterns - Use `expect`/`actual` pattern for platform-specific implementations in `utils.*` files. - Test changes on JVM first, then verify platform-specific behavior if needed. -- Supported targets: JVM (1.8+), JS/Wasm, iOS, watchOS, tvOS. +- Supported targets: JVM (11+), JS/Wasm, iOS, watchOS, tvOS. + +## Coding Guidance + +### Module Boundaries +- Respect separation of concerns between modules +- Keep abstractions framework-agnostic +- Avoid leaking implementation details across module boundaries ### Serialization -- Use Kotlinx Serialization with explicit `@Serializable` annotations. -- Custom serializers should be registered in the companion object. -- JSON config is defined in `McpJson.kt` — use it consistently. - -### Concurrency and State -- Use `kotlinx.atomicfu` for thread-safe operations across platforms. -- Prefer coroutines over callbacks where possible. -- Transport implementations extend `AbstractTransport` for consistent callback management. +- Use Kotlinx Serialization with explicit `@Serializable` annotations +- JSON config is defined in `jsonUtils.kt` as `McpJson` — use it consistently +- Register custom serializers in companion objects ### Error Handling -- Use sealed classes for representing different result states. -- Map errors to JSON-RPC error codes in protocol handlers. -- Log errors using `io.github.oshai.kotlinlogging.KotlinLogging`. +- Use sealed classes for result states +- Map errors to JSON-RPC error codes in protocol handlers +- Log errors using `io.github.oshai.kotlinlogging.KotlinLogging` +- Never log sensitive data (credentials, tokens) -### Logging -- Use `KotlinLogging.logger {}` for structured logging. -- Never log sensitive data (credentials, tokens). +### Code Quality +- Handle nullability and references as established patterns +- Keep changes small and reversible; prefer adding functions over editing many call sites +- Add focused comments **only** where intent is non-obvious +- Use `git mv` command when moving version-controlled files +- Never commit or push changes. This is a Human responsibility. -## Pull Requests -- Base all PRs on the `main` branch. -- PR title format: Brief description of the change -- Include in PR description: - - **What changed?** - - **Why? Motivation and context** - - **Breaking changes?** (if any) -- Run `./gradlew apiCheck` before submitting. -- Link PR to related issue (except for minor docs/typo fixes). -- Follow [Kotlin Coding Conventions](https://kotlinlang.org/docs/reference/coding-conventions.html). +## Workflow for AI Agents -## Review Checklist -- `./gradlew apiCheck` must pass. -- `./gradlew test` must succeed for affected modules. -- New tests added for any new feature or bug fix. -- Documentation updated for user-facing changes. -- No breaking changes to public APIs without discussion. +1. **Understand the issue** + - Summarize requirement in 1-2 sentences + - Identify affected files and tests; ask if unclear + +2. **Plan minimal changes** + - Use TaskCreate/TaskUpdate to track work, or EnterPlanMode for complex changes + - Document affected files and approach + +3. **Work with code** + - View, edit, and run tests through MCP tools + +4. **Start with tests** + - Update existing or add new tests in relevant modules + +5. **Implement the change** + - Prefer composition and small functions + - Avoid breaking public APIs without tests and discussion + +6. **Run tests locally** + ```bash + ./gradlew test + ./gradlew :kotlin-sdk-core:test # Specific module + ./gradlew :integration-test:test # Integration tests + ``` + +7. **Verify results** + - Use Kotest JSON matchers for schema validation + - Double-check the readability of code and tests + +8. **Document key decisions** + - Brief notes in markdown document, if requested + +## Build and Run Commands + +```bash +# Build all modules +./gradlew build + +# Run all tests +./gradlew test + +# Run specific module tests +./gradlew :kotlin-sdk-core:test +./gradlew :kotlin-sdk-client:test +./gradlew :kotlin-sdk-server:test + +# Check API compatibility (required before commit) +./gradlew apiCheck + +# Update API dump (after intentional API changes) +./gradlew apiDump + +# Platform-specific tests +./gradlew jsTest +./gradlew wasmJsTest +``` + +## Definition of done + +- Tests for affected functionality exist +- All tests pass across all targets +- Changes are focused on a single concern. +- Code respects module boundaries +- `./gradlew apiCheck` passes +- Documentation updated for user-facing changes + +## Documentation + +### General Guidelines +- **Never make up documentation/facts** +- Update README files when adding new features +- Document API changes in the appropriate module +- **Keep documentation concise and straight to the point** +- Always verify documentation matches code; fix discrepancies +- Write tutorials in Markdown format in README.md + +### KDoc Guidelines +- Properly document interfaces and abstract classes in production code +- Avoid KDocs on override functions to reduce verbosity +- Update KDocs when API changes +- Use references: `[SendMessageRequest]` instead of `SendMessageRequest` +- Add brief code examples to KDocs +- Add links to specifications with verified URLs; never add broken links + +### Module.md Files for Dokka + +Each module must have a `Module.md` file at the module root for Dokka-generated API documentation. + +**Format Requirements** (per [Dokka specification](https://kotlinlang.org/docs/dokka-module-and-package-docs.html)): +- Must start with `# Module module-name` (level 1 header) +- Package documentation uses `# Package package.name` (level 1 header) +- All other content uses level 2+ headers (`##`, `###`, etc.) + +**Content Guidelines**: +- **Purpose**: Provide module-level overview in generated API docs +- **Detail level**: API documentation style (concise, focused on classes/interfaces) +- **Module section**: + - Brief module description (1-2 sentences) + - Platform support (format: "Multiplatform • Kotlin 2.2+" or "JVM only • Kotlin 2.2+") + - Key classes/components (use Dokka references like `[ClassName]`) + - Example (minimal, demonstrating primary usage) +- **Package sections**: Use `# Package package.name` headers with brief description +- **Optional sections**: Features, Limitations, Related Specifications +- **Style**: + - Use bullet points over prose + - Keep examples short (5-15 lines) + - Reference related modules using Dokka links + - Avoid duplicating README content + +**Example structure (markdown)**: + + # Module module-name + + Brief description. + + **Platform Support:** Multiplatform • Kotlin 2.2+ + + ## Key Classes + + - [MainClass] - what it does + - [HelperClass] - what it does + + ## Example + + ```kotlin + val example = MainClass() + ``` + + # Package package.name + + Package description. + + # Package another.package + + Another package description. + + +## When to Ask for Help + +- Requirements conflict with existing tests or documentation +- The smallest change still requires altering core abstractions +- Unclear expected schema shape for a new feature—ask for a concrete, readable test case to anchor implementation diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..43c994c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md From da971aaee50fdf4c5d21c60d942f4663b3ea6dec Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Thu, 12 Feb 2026 19:28:16 +0100 Subject: [PATCH 138/449] Ktor extensions for streamable http (#504) ## Motivation and Context Adds Ktor extensions for streaming data over the server-side HTTP transport ## How Has This Been Tested? Integration test ## Breaking Changes - ## 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- integration-test/build.gradle.kts | 2 + .../AbstractStreamableHttpIntegrationTest.kt | 82 +++++++ .../StreamableHttpIntegrationTest.kt | 65 ++++++ kotlin-sdk-server/api/kotlin-sdk-server.api | 4 + .../kotlin/sdk/server/KtorServer.kt | 215 ++++++++++++++++-- .../server/StreamableHttpServerTransport.kt | 4 +- 6 files changed, 356 insertions(+), 16 deletions(-) create mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/AbstractStreamableHttpIntegrationTest.kt create mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/StreamableHttpIntegrationTest.kt diff --git a/integration-test/build.gradle.kts b/integration-test/build.gradle.kts index 1b763ed..5352234 100644 --- a/integration-test/build.gradle.kts +++ b/integration-test/build.gradle.kts @@ -19,6 +19,8 @@ kotlin { implementation(libs.kotlinx.coroutines.test) implementation(libs.ktor.client.cio) implementation(libs.ktor.server.cio) + implementation(libs.ktor.server.content.negotiation) + implementation(libs.ktor.serialization) implementation(libs.ktor.server.websockets) implementation(libs.ktor.server.test.host) implementation(libs.ktor.server.content.negotiation) diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/AbstractStreamableHttpIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/AbstractStreamableHttpIntegrationTest.kt new file mode 100644 index 0000000..0f6646e --- /dev/null +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/AbstractStreamableHttpIntegrationTest.kt @@ -0,0 +1,82 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.streamablehttp + +import io.ktor.serialization.kotlinx.json.json +import io.ktor.server.application.install +import io.ktor.server.cio.CIOApplicationEngine +import io.ktor.server.engine.EmbeddedServer +import io.ktor.server.engine.embeddedServer +import io.ktor.server.plugins.contentnegotiation.ContentNegotiation +import io.modelcontextprotocol.kotlin.sdk.server.Server +import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions +import io.modelcontextprotocol.kotlin.sdk.server.mcpStreamableHttp +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.McpJson +import io.modelcontextprotocol.kotlin.sdk.types.PromptArgument +import io.modelcontextprotocol.kotlin.sdk.types.PromptMessage +import io.modelcontextprotocol.kotlin.sdk.types.Role +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.ktor.server.cio.CIO as ServerCIO + +open class AbstractStreamableHttpIntegrationTest { + + suspend fun initTestServer(name: String? = null): StreamableHttpTestServer { + val mcpServer = Server( + Implementation(name = name ?: DEFAULT_SERVER_NAME, version = VERSION), + ServerOptions( + capabilities = ServerCapabilities(prompts = ServerCapabilities.Prompts(listChanged = true)), + ), + ) { + addPrompt( + name = "prompt", + description = "Prompt description", + arguments = listOf( + PromptArgument( + name = "client", + description = "Client name who requested a prompt", + required = true, + ), + ), + ) { request -> + GetPromptResult( + messages = listOf( + PromptMessage( + role = Role.User, + content = TextContent("Prompt for client ${request.params.arguments?.get("client")}"), + ), + ), + description = "Prompt for ${request.params.name}", + ) + } + } + + val ktorServer = embeddedServer( + ServerCIO, + host = URL, + port = PORT, + ) { + install(ContentNegotiation) { + json(McpJson) + } + mcpStreamableHttp { mcpServer } + } + + return StreamableHttpTestServer( + mcpServer = mcpServer, + ktorServer = ktorServer.startSuspend(wait = false), + ) + } + + data class StreamableHttpTestServer( + val mcpServer: Server, + val ktorServer: EmbeddedServer, + ) + + companion object { + internal const val DEFAULT_SERVER_NAME = "streamable-http-test-server" + internal const val VERSION = "1.0.0" + internal const val URL = "127.0.0.1" + internal const val PORT = 0 + } +} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/StreamableHttpIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/StreamableHttpIntegrationTest.kt new file mode 100644 index 0000000..b64df4a --- /dev/null +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/StreamableHttpIntegrationTest.kt @@ -0,0 +1,65 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.streamablehttp + +import io.ktor.client.HttpClient +import io.ktor.client.plugins.sse.SSE +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.client.StreamableHttpClientTransport +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.test.utils.actualPort +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import kotlin.test.Test +import kotlin.test.assertTrue +import io.ktor.client.engine.cio.CIO as ClientCIO + +class StreamableHttpIntegrationTest : AbstractStreamableHttpIntegrationTest() { + + @Test + fun `client should receive responses via POST json and server notifications via GET sse`() = + runBlocking(Dispatchers.IO) { + var server: StreamableHttpTestServer? = null + var client: Client? = null + var httpClient: HttpClient? = null + + try { + server = initTestServer() + val port = server.ktorServer.actualPort() + + httpClient = HttpClient(ClientCIO) { install(SSE) } + val transport = StreamableHttpClientTransport( + client = httpClient, + url = "http://$URL:$port/mcp", + ).apply { + protocolVersion = LATEST_PROTOCOL_VERSION + } + + client = Client(Implementation("streamable-http-client", VERSION)) + client.connect(transport) + + val prompt = getPrompt(client, "Client A") + assertTrue { "Client A" in prompt } + } finally { + client?.close() + httpClient?.close() + server?.ktorServer?.stopSuspend(1000, 2000) + } + } + + private suspend fun getPrompt(client: Client, clientName: String): String { + val response = client.getPrompt( + GetPromptRequest( + GetPromptRequestParams( + name = "prompt", + arguments = mapOf("client" to clientName), + ), + ), + ) + + return (response.messages.first().content as? TextContent)?.text + ?: error("Failed to receive prompt for Client $clientName") + } +} diff --git a/kotlin-sdk-server/api/kotlin-sdk-server.api b/kotlin-sdk-server/api/kotlin-sdk-server.api index 53c971b..f0eb5c1 100644 --- a/kotlin-sdk-server/api/kotlin-sdk-server.api +++ b/kotlin-sdk-server/api/kotlin-sdk-server.api @@ -8,6 +8,10 @@ public final class io/modelcontextprotocol/kotlin/sdk/server/KtorServerKt { public static final fun mcp (Lio/ktor/server/application/Application;Lkotlin/jvm/functions/Function1;)V public static final fun mcp (Lio/ktor/server/routing/Routing;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V public static final fun mcp (Lio/ktor/server/routing/Routing;Lkotlin/jvm/functions/Function1;)V + public static final fun mcpStatelessStreamableHttp (Lio/ktor/server/application/Application;ZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/jvm/functions/Function1;)V + public static synthetic fun mcpStatelessStreamableHttp$default (Lio/ktor/server/application/Application;ZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V + public static final fun mcpStreamableHttp (Lio/ktor/server/application/Application;ZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/jvm/functions/Function1;)V + public static synthetic fun mcpStreamableHttp$default (Lio/ktor/server/application/Application;ZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V } public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredPrompt : io/modelcontextprotocol/kotlin/sdk/server/Feature { diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt index 2a2f950..8bb09a8 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt @@ -3,10 +3,15 @@ package io.modelcontextprotocol.kotlin.sdk.server import io.github.oshai.kotlinlogging.KotlinLogging import io.ktor.http.HttpStatusCode import io.ktor.server.application.Application +import io.ktor.server.application.ApplicationCall import io.ktor.server.application.install +import io.ktor.server.request.ApplicationRequest +import io.ktor.server.request.header import io.ktor.server.response.respond import io.ktor.server.routing.Routing import io.ktor.server.routing.RoutingContext +import io.ktor.server.routing.delete +import io.ktor.server.routing.get import io.ktor.server.routing.post import io.ktor.server.routing.route import io.ktor.server.routing.routing @@ -14,6 +19,8 @@ import io.ktor.server.sse.SSE import io.ktor.server.sse.ServerSSESession import io.ktor.server.sse.sse import io.ktor.utils.io.KtorDsl +import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport +import io.modelcontextprotocol.kotlin.sdk.types.RPCError import kotlinx.atomicfu.AtomicRef import kotlinx.atomicfu.atomic import kotlinx.atomicfu.update @@ -23,13 +30,15 @@ import kotlinx.coroutines.awaitCancellation private val logger = KotlinLogging.logger {} -internal class SseTransportManager(transports: Map = emptyMap()) { - private val transports: AtomicRef> = atomic(transports.toPersistentMap()) +internal class TransportManager(transports: Map = emptyMap()) { + private val transports: AtomicRef> = atomic(transports.toPersistentMap()) - fun getTransport(sessionId: String): SseServerTransport? = transports.value[sessionId] + fun hasTransport(sessionId: String): Boolean = transports.value.containsKey(sessionId) - fun addTransport(transport: SseServerTransport) { - transports.update { it.put(transport.sessionId, transport) } + fun getTransport(sessionId: String): AbstractTransport? = transports.value[sessionId] + + fun addTransport(sessionId: String, transport: AbstractTransport) { + transports.update { it.put(sessionId, transport) } } fun removeTransport(sessionId: String) { @@ -49,14 +58,14 @@ public fun Routing.mcp(path: String, block: ServerSSESession.() -> Server) { */ @KtorDsl public fun Routing.mcp(block: ServerSSESession.() -> Server) { - val sseTransportManager = SseTransportManager() + val transportManager = TransportManager() sse { - mcpSseEndpoint("", sseTransportManager, block) + mcpSseEndpoint("", transportManager, block) } post { - mcpPostEndpoint(sseTransportManager) + mcpPostEndpoint(transportManager) } } @@ -69,18 +78,98 @@ public fun Application.mcp(block: ServerSSESession.() -> Server) { } } +@KtorDsl +public fun Application.mcpStreamableHttp( + enableDnsRebindingProtection: Boolean = false, + allowedHosts: List? = null, + allowedOrigins: List? = null, + eventStore: EventStore? = null, + block: RoutingContext.() -> Server, +) { + install(SSE) + + val transportManager = TransportManager() + + routing { + route("/mcp") { + sse { + val transport = existingStreamableTransport(call, transportManager) ?: return@sse + transport.handleRequest(this, call) + } + + post { + val transport = streamableTransport( + transportManager = transportManager, + enableDnsRebindingProtection = enableDnsRebindingProtection, + allowedHosts = allowedHosts, + allowedOrigins = allowedOrigins, + eventStore = eventStore, + block = block, + ) + ?: return@post + + transport.handleRequest(null, call) + } + + delete { + val transport = existingStreamableTransport(call, transportManager) ?: return@delete + transport.handleRequest(null, call) + } + } + } +} + +@KtorDsl +public fun Application.mcpStatelessStreamableHttp( + enableDnsRebindingProtection: Boolean = false, + allowedHosts: List? = null, + allowedOrigins: List? = null, + eventStore: EventStore? = null, + block: RoutingContext.() -> Server, +) { + install(SSE) + + routing { + route("/mcp") { + post { + mcpStatelessStreamableHttpEndpoint( + enableDnsRebindingProtection = enableDnsRebindingProtection, + allowedHosts = allowedHosts, + allowedOrigins = allowedOrigins, + eventStore = eventStore, + block = block, + ) + } + get { + call.reject( + HttpStatusCode.MethodNotAllowed, + RPCError.ErrorCode.CONNECTION_CLOSED, + "Method not allowed.", + ) + } + delete { + call.reject( + HttpStatusCode.MethodNotAllowed, + RPCError.ErrorCode.CONNECTION_CLOSED, + "Method not allowed.", + ) + } + } + } +} + internal suspend fun ServerSSESession.mcpSseEndpoint( postEndpoint: String, - sseTransportManager: SseTransportManager, + transportManager: TransportManager, block: ServerSSESession.() -> Server, ) { - val transport = mcpSseTransport(postEndpoint, sseTransportManager) + val transport = mcpSseTransport(postEndpoint, transportManager) val server = block() server.onClose { logger.info { "Server connection closed for sessionId: ${transport.sessionId}" } - sseTransportManager.removeTransport(transport.sessionId) + transportManager.removeTransport(transport.sessionId) } server.createSession(transport) @@ -92,16 +181,41 @@ internal suspend fun ServerSSESession.mcpSseEndpoint( internal fun ServerSSESession.mcpSseTransport( postEndpoint: String, - sseTransportManager: SseTransportManager, + transportManager: TransportManager, ): SseServerTransport { val transport = SseServerTransport(postEndpoint, this) - sseTransportManager.addTransport(transport) + transportManager.addTransport(transport.sessionId, transport) logger.info { "New SSE connection established and stored with sessionId: ${transport.sessionId}" } return transport } -internal suspend fun RoutingContext.mcpPostEndpoint(sseTransportManager: SseTransportManager) { +internal suspend fun RoutingContext.mcpStatelessStreamableHttpEndpoint( + enableDnsRebindingProtection: Boolean = false, + allowedHosts: List? = null, + allowedOrigins: List? = null, + eventStore: EventStore? = null, + block: RoutingContext.() -> Server, +) { + val transport = StreamableHttpServerTransport( + enableDnsRebindingProtection = enableDnsRebindingProtection, + allowedHosts = allowedHosts, + allowedOrigins = allowedOrigins, + eventStore = eventStore, + enableJsonResponse = true, + ).also { it.setSessionIdGenerator(null) } + + logger.info { "New stateless StreamableHttp connection established without sessionId" } + + val server = block() + server.onClose { logger.info { "Server connection closed without sessionId" } } + server.createSession(transport) + + transport.handleRequest(null, this.call) + logger.debug { "Server connected to transport without sessionId" } +} + +internal suspend fun RoutingContext.mcpPostEndpoint(transportManager: TransportManager) { val sessionId: String = call.request.queryParameters["sessionId"] ?: run { call.respond(HttpStatusCode.BadRequest, "sessionId query parameter is not provided") return @@ -109,7 +223,7 @@ internal suspend fun RoutingContext.mcpPostEndpoint(sseTransportManager: SseTran logger.debug { "Received message for sessionId: $sessionId" } - val transport = sseTransportManager.getTransport(sessionId) + val transport = transportManager.getTransport(sessionId) as SseServerTransport? if (transport == null) { logger.warn { "Session not found for sessionId: $sessionId" } call.respond(HttpStatusCode.NotFound, "Session not found") @@ -119,3 +233,74 @@ internal suspend fun RoutingContext.mcpPostEndpoint(sseTransportManager: SseTran transport.handlePostMessage(call) logger.trace { "Message handled for sessionId: $sessionId" } } + +private fun ApplicationRequest.sessionId(): String? = header(MCP_SESSION_ID_HEADER) + +private suspend fun existingStreamableTransport( + call: ApplicationCall, + transportManager: TransportManager, +): StreamableHttpServerTransport? { + val sessionId = call.request.sessionId() + if (sessionId.isNullOrEmpty()) { + call.reject( + HttpStatusCode.BadRequest, + RPCError.ErrorCode.CONNECTION_CLOSED, + "Bad Request: No valid session ID provided", + ) + return null + } + + val transport = transportManager.getTransport(sessionId) as? StreamableHttpServerTransport + if (transport == null) { + call.reject( + HttpStatusCode.NotFound, + RPCError.ErrorCode.CONNECTION_CLOSED, + "Session not found", + ) + return null + } + + return transport +} + +private suspend fun RoutingContext.streamableTransport( + transportManager: TransportManager, + enableDnsRebindingProtection: Boolean, + allowedHosts: List?, + allowedOrigins: List?, + eventStore: EventStore?, + block: RoutingContext.() -> Server, +): StreamableHttpServerTransport? { + val sessionId = call.request.sessionId() + if (sessionId != null) { + val transport = transportManager.getTransport(sessionId) as? StreamableHttpServerTransport + return transport ?: existingStreamableTransport(call, transportManager) + } + + val transport = StreamableHttpServerTransport( + enableDnsRebindingProtection = enableDnsRebindingProtection, + allowedHosts = allowedHosts, + allowedOrigins = allowedOrigins, + eventStore = eventStore, + enableJsonResponse = true, + ) + + transport.setOnSessionInitialized { initializedSessionId -> + transportManager.addTransport(initializedSessionId, transport) + logger.info { "New StreamableHttp connection established and stored with sessionId: $initializedSessionId" } + } + + transport.setOnSessionClosed { closedSession -> + transportManager.removeTransport(closedSession) + logger.info { "Closed StreamableHttp connection and removed sessionId: $closedSession" } + } + + val server = block() + server.onClose { + transport.sessionId?.let { transportManager.removeTransport(it) } + logger.info { "Server connection closed for sessionId: ${transport.sessionId}" } + } + server.createSession(transport) + + return transport +} diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt index 56bcead..e4ccf71 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt @@ -254,7 +254,9 @@ public class StreamableHttpServerTransport( */ public suspend fun handlePostRequest(session: ServerSSESession?, call: ApplicationCall) { try { - if (!enableJsonResponse && session == null) error("Server session can't be null with json response") + if (!enableJsonResponse && session == null) { + error("Server session can't be null for SSE responses") + } val acceptHeader = call.request.header(HttpHeaders.Accept) val isAcceptEventStream = acceptHeader.accepts(ContentType.Text.EventStream) From 99908beb2a1be3a9d298a64e124253c7e51817d1 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Fri, 13 Feb 2026 12:33:34 +0200 Subject: [PATCH 139/449] ci: add workflow to manage stale issues and PRs (dry-run) - Introduce `.github/workflows/automations.yml` - Automate marking and closing stale PRs and issues based on activity - Runs in debug mode only! --- .github/workflows/automations.yml | 69 +++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 .github/workflows/automations.yml diff --git a/.github/workflows/automations.yml b/.github/workflows/automations.yml new file mode 100644 index 0000000..fbccaea --- /dev/null +++ b/.github/workflows/automations.yml @@ -0,0 +1,69 @@ +name: Automations + +on: + schedule: + # Runs every day at 2:00 AM UTC + - cron: '0 2 * * *' + workflow_dispatch: + +permissions: + actions: write + # contents: write # only for delete-branch option + issues: write + pull-requests: write + +jobs: + close-question-prs: + name: Mark/Close stale PRs and issues + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v10 + ## https://github.com/marketplace/actions/close-stale-issues + with: + debug-only: true # 'true' for dry run + exempt-draft-pr: true # pull requests currently in draft will not be marked as stale + exempt-issue-labels: 'awaiting-approval,work-in-progress' + stale-pr-label: 'stale' + only-labels: 'awaiting-feedback,awaiting-answers' + + # --- Scope: only PRs, not issues --- + stale-issue-message: > + 👋 **This issue has been marked as Stale.** + + This issue was awaiting feedback and has had no activity for **30 days**. + + — 🤖 Stale PR Bot + days-before-issue-stale: 30 + days-before-issue-close: -1 + + # --- Timing --- + # Since we target PRs already labeled "question", + # days-before-stale counts from last activity. + days-before-pr-stale: 30 + days-before-pr-close: 15 + + # --- Labels --- + remove-stale-when-updated: true + remove-pr-stale-when-updated: true + + # --- Messages --- + stale-pr-message: > + 👋 **This PR has been marked as Stale.** + + This PR was awaiting feedback and has had no activity for **30 days**. + + — 🤖 Stale PR Bot + close-pr-message: > + 👋 **This PR has been automatically closed.** + + This PR was labeled with `stale` and has had no activity + for **15 days** since that. + + If this is still relevant, please feel free to reopen it + or create a new PR. + + — 🤖 Stale PR Bot + + # --- Operations --- + operations-per-run: 100 + ascending: true From 6120b3109e319c0f3ca362063ad2b2042670e620 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Fri, 13 Feb 2026 13:08:26 +0200 Subject: [PATCH 140/449] refactor: Introduce AbstractClientTransport and simplify client transport lifecycle (#517) (#516) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## refactor: Introduce AbstractClientTransport and simplify client transport lifecycle This PR unifies state management logic across client transports, except for the websocket transport, as it needs more refactoring first and is not the primary transport. - Introduce `AbstractClientTransport` as base class for other client transports. - Migrate to `ClientTransportState` states with state machine, streamline state management. - Ensure proper cleanup and error handling consistency across transport implementations. - Add unit tests to validate `AbstractClientTransport` behavior and state transitions. - Update related documentation and API contract. - Make sure `onClose` callback is called at most once - fix [StreamableHttpClientTransportTest.kt‎](https://github.com/modelcontextprotocol/kotlin-sdk/pull/516/changes#diff-b9fe8891d08a492448a7ed2dc782151c6a915ae1403a4d70c2034c6062c84cb5) to throw `McpException` on connect when received invalid Json ## Motivation and Context Unify lifecycle handling and eliminate duplication in client Transport implementations. ## How Has This Been Tested? Unit test, regression tests ## Breaking Changes - `onClose` callback is now called only once - send(...) will throw McpException with message "Transport is not ready" when `ClientTransportState != Operational` ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [x] Refactoring - [ ] New feature (non-breaking change which adds functionality) - [x] 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 - [ ] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed ## Additional context See #517 --- .../sdk/conformance/ConformanceServer.kt | 2 +- .../kotlin/sdk/shared/InMemoryTransport.kt | 2 +- .../sse/KotlinServerForTsClientSse.kt | 2 +- .../src/jvmTest/typescript/package-lock.json | 1 - kotlin-sdk-client/api/kotlin-sdk-client.api | 15 +- ...ientTransport.kt => SseClientTransport.kt} | 85 ++- .../kotlin/sdk/client/StdioClientTransport.kt | 56 +- .../client/StreamableHttpClientTransport.kt | 85 ++- .../AbstractClientTransportLifecycleTest.kt | 4 +- .../http/StreamableHttpClientTransportTest.kt | 12 +- .../StdioClientTransportErrorHandlingTest.kt | 8 +- kotlin-sdk-core/api/kotlin-sdk-core.api | 35 +- kotlin-sdk-core/build.gradle.kts | 2 + .../kotlin/sdk/InternalMcpApi.kt | 49 ++ .../sdk/shared/AbstractClientTransport.kt | 274 ++++++++++ .../kotlin/sdk/shared/AbstractTransport.kt | 21 +- .../kotlin/sdk/shared/ClientTransportState.kt | 79 +++ .../kotlin/sdk/shared/Protocol.kt | 2 +- .../kotlin/sdk/shared/Transport.kt | 4 +- .../sdk/shared/WebSocketMcpTransport.kt | 2 +- .../sdk/shared/AbstractClientTransportTest.kt | 498 ++++++++++++++++++ .../kotlin/sdk/server/SSEServerTransport.kt | 4 +- .../kotlin/sdk/server/StdioServerTransport.kt | 2 +- .../server/StreamableHttpServerTransport.kt | 2 +- 24 files changed, 1056 insertions(+), 190 deletions(-) rename kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/{SSEClientTransport.kt => SseClientTransport.kt} (67%) create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/InternalMcpApi.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/AbstractClientTransport.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ClientTransportState.kt create mode 100644 kotlin-sdk-core/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/AbstractClientTransportTest.kt diff --git a/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceServer.kt b/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceServer.kt index 01bdd40..402cf81 100644 --- a/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceServer.kt +++ b/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceServer.kt @@ -417,6 +417,6 @@ private class HttpServerTransport(private val sessionId: String) : AbstractTrans logger.debug { "Closing transport for session $sessionId" } messageQueue.close() pendingResponses.clear() - _onClose.invoke() + invokeOnCloseCallback() } } diff --git a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/InMemoryTransport.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/InMemoryTransport.kt index eabdf28..ae760f1 100644 --- a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/InMemoryTransport.kt +++ b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/InMemoryTransport.kt @@ -36,7 +36,7 @@ class InMemoryTransport : AbstractTransport() { val other = otherTransport otherTransport = null other?.close() - _onClose.invoke() + invokeOnCloseCallback() } override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt index d0056cb..97acc29 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt @@ -455,7 +455,7 @@ class HttpServerTransport(private val sessionId: String) : AbstractTransport() { override suspend fun close() { logger.debug { "Closing HTTP server transport for session: $sessionId" } messageQueue.close() - _onClose.invoke() + invokeOnCloseCallback() } } diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index 001bfdd..a7a67d6 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -1108,7 +1108,6 @@ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, - "hasInstallScript": true, "license": "MIT", "optional": true, "os": [ diff --git a/kotlin-sdk-client/api/kotlin-sdk-client.api b/kotlin-sdk-client/api/kotlin-sdk-client.api index 0609482..268a2b8 100644 --- a/kotlin-sdk-client/api/kotlin-sdk-client.api +++ b/kotlin-sdk-client/api/kotlin-sdk-client.api @@ -62,24 +62,18 @@ public final class io/modelcontextprotocol/kotlin/sdk/client/KtorClientKt { public static synthetic fun mcpSseTransport-5_5nbZA$default (Lio/ktor/client/HttpClient;Ljava/lang/String;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/client/SseClientTransport; } -public final class io/modelcontextprotocol/kotlin/sdk/client/SseClientTransport : io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport { +public final class io/modelcontextprotocol/kotlin/sdk/client/SseClientTransport : io/modelcontextprotocol/kotlin/sdk/shared/AbstractClientTransport { public synthetic fun (Lio/ktor/client/HttpClient;Ljava/lang/String;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun (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;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 final class io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport : io/modelcontextprotocol/kotlin/sdk/shared/AbstractClientTransport { public static final field BUFFER_SIZE J public fun (Lkotlinx/io/Source;Lkotlinx/io/Sink;)V public fun (Lkotlinx/io/Source;Lkotlinx/io/Sink;Lkotlinx/io/Source;)V public fun (Lkotlinx/io/Source;Lkotlinx/io/Sink;Lkotlinx/io/Source;Lkotlinx/coroutines/channels/Channel;)V public fun (Lkotlinx/io/Source;Lkotlinx/io/Sink;Lkotlinx/io/Source;Lkotlinx/coroutines/channels/Channel;Lkotlin/jvm/functions/Function1;)V public synthetic fun (Lkotlinx/io/Source;Lkotlinx/io/Sink;Lkotlinx/io/Source;Lkotlinx/coroutines/channels/Channel;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun close (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$StderrSeverity : java/lang/Enum { @@ -93,17 +87,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/client/StdioClientTranspor public static fun values ()[Lio/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport$StderrSeverity; } -public final class io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport : io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport { +public final class io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport : io/modelcontextprotocol/kotlin/sdk/shared/AbstractClientTransport { public synthetic fun (Lio/ktor/client/HttpClient;Ljava/lang/String;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun (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 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 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; public final fun terminateSession (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseClientTransport.kt similarity index 67% rename from kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt rename to kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseClientTransport.kt index 4ffb970..19480c7 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseClientTransport.kt @@ -1,5 +1,6 @@ package io.modelcontextprotocol.kotlin.sdk.client +import io.github.oshai.kotlinlogging.KLogger import io.github.oshai.kotlinlogging.KotlinLogging import io.ktor.client.HttpClient import io.ktor.client.plugins.sse.ClientSSESession @@ -14,7 +15,7 @@ import io.ktor.http.Url 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.AbstractClientTransport import io.modelcontextprotocol.kotlin.sdk.shared.TransportSendOptions import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage import io.modelcontextprotocol.kotlin.sdk.types.McpJson @@ -30,7 +31,6 @@ import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.ensureActive import kotlinx.coroutines.launch import kotlinx.serialization.SerializationException -import kotlin.concurrent.atomics.AtomicBoolean import kotlin.concurrent.atomics.ExperimentalAtomicApi import kotlin.time.Duration @@ -44,10 +44,10 @@ public class SseClientTransport( private val urlString: String?, private val reconnectionTime: Duration? = null, private val requestBuilder: HttpRequestBuilder.() -> Unit = {}, -) : AbstractTransport() { - private val logger = KotlinLogging.logger {} +) : AbstractClientTransport() { + + override val logger: KLogger = KotlinLogging.logger {} - private val initialized: AtomicBoolean = AtomicBoolean(false) private val endpoint = CompletableDeferred() private lateinit var session: ClientSSESession @@ -65,64 +65,43 @@ public class SseClientTransport( } } - override suspend fun start() { - check(initialized.compareAndSet(expectedValue = false, newValue = true)) { - "SSEClientTransport already started! If using Client class, note that connect() calls start() automatically." - } - - try { - session = urlString?.let { - client.sseSession( - urlString = it, - reconnectionTime = reconnectionTime, - block = requestBuilder, - ) - } ?: client.sseSession( + override suspend fun initialize() { + session = urlString?.let { + client.sseSession( + urlString = it, reconnectionTime = reconnectionTime, block = requestBuilder, ) - scope = CoroutineScope(session.coroutineContext + SupervisorJob()) + } ?: client.sseSession( + reconnectionTime = reconnectionTime, + block = requestBuilder, + ) + scope = CoroutineScope(session.coroutineContext + SupervisorJob()) - job = scope.launch(CoroutineName("SseMcpClientTransport.connect#${hashCode()}")) { - collectMessages() - } - - endpoint.await() - } catch (e: Exception) { - closeResources() - initialized.store(false) - throw e + job = scope.launch(CoroutineName("SseMcpClientTransport.connect#${hashCode()}")) { + collectMessages() } + + endpoint.await() } @OptIn(ExperimentalCoroutinesApi::class) - override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { - check(initialized.load()) { "SseClientTransport is not initialized!" } + override suspend fun performSend(message: JSONRPCMessage, options: TransportSendOptions?) { check(job?.isActive == true) { "SseClientTransport is closed!" } check(endpoint.isCompleted) { "Not connected!" } - try { - val response = client.post(endpoint.getCompleted()) { - requestBuilder() - headers.append(HttpHeaders.ContentType, ContentType.Application.Json) - setBody(McpJson.encodeToString(message)) - } - - if (!response.status.isSuccess()) { - val text = response.bodyAsText() - error("Error POSTing to endpoint (HTTP ${response.status}): $text") - } - - logger.debug { "Client successfully sent message via SSE $endpoint" } - } catch (e: Throwable) { - _onError(e) - throw e + val response = client.post(endpoint.getCompleted()) { + requestBuilder() + headers.append(HttpHeaders.ContentType, ContentType.Application.Json) + setBody(McpJson.encodeToString(message)) } - } - override suspend fun close() { - check(initialized.load()) { "SseClientTransport is not initialized!" } - closeResources() + if (!response.status.isSuccess()) { + val text = response.bodyAsText() + error("Error POSTing to endpoint (HTTP ${response.status}): $text") + } + + logger.debug { "Client successfully sent message via SSE $endpoint" } } private suspend fun CoroutineScope.collectMessages() { @@ -178,9 +157,7 @@ public class SseClientTransport( } } - private suspend fun closeResources() { - if (!initialized.compareAndSet(expectedValue = true, newValue = false)) return - + override suspend fun closeResources() { job?.cancelAndJoin() try { if (::session.isInitialized) session.cancel() @@ -189,7 +166,5 @@ public class SseClientTransport( } catch (e: Throwable) { _onError(e) } - - _onClose() } } diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt index ec567f3..12529cc 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt @@ -1,5 +1,6 @@ package io.modelcontextprotocol.kotlin.sdk.client +import io.github.oshai.kotlinlogging.KLogger import io.github.oshai.kotlinlogging.KotlinLogging import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport.StderrSeverity.DEBUG import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport.StderrSeverity.FATAL @@ -7,7 +8,7 @@ import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport.StderrSeve import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport.StderrSeverity.INFO import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport.StderrSeverity.WARNING import io.modelcontextprotocol.kotlin.sdk.internal.IODispatcher -import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport +import io.modelcontextprotocol.kotlin.sdk.shared.AbstractClientTransport import io.modelcontextprotocol.kotlin.sdk.shared.ReadBuffer import io.modelcontextprotocol.kotlin.sdk.shared.TransportSendOptions import io.modelcontextprotocol.kotlin.sdk.shared.serializeMessage @@ -39,7 +40,6 @@ import kotlinx.io.buffered import kotlinx.io.readByteArray import kotlinx.io.writeString import kotlinx.serialization.SerializationException -import kotlin.concurrent.atomics.AtomicBoolean import kotlin.concurrent.atomics.ExperimentalAtomicApi import kotlin.coroutines.CoroutineContext import kotlin.jvm.JvmOverloads @@ -88,7 +88,9 @@ public class StdioClientTransport @JvmOverloads public constructor( private val error: Source? = null, private val sendChannel: Channel = Channel(Channel.BUFFERED), private val classifyStderr: (String) -> StderrSeverity = { DEBUG }, -) : AbstractTransport() { +) : AbstractClientTransport() { + + override val logger: KLogger = KotlinLogging.logger {} private companion object { /** @@ -96,8 +98,6 @@ public class StdioClientTransport @JvmOverloads public constructor( * 8KB is optimal for most systems (matches default page size). */ const val BUFFER_SIZE = 8 * 1024L - - private val logger = KotlinLogging.logger {} } /** @@ -113,13 +113,8 @@ public class StdioClientTransport @JvmOverloads public constructor( private val ioCoroutineContext: CoroutineContext = IODispatcher private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) - private val initialized = AtomicBoolean(false) - private val onCloseCalled = AtomicBoolean(false) - override suspend fun start() { - if (!initialized.compareAndSet(expectedValue = false, newValue = true)) { - error("StdioClientTransport already started!") - } + override suspend fun initialize() { logger.debug { "Starting StdioClientTransport..." } // Producers run on IODispatcher for I/O @@ -224,24 +219,12 @@ public class StdioClientTransport @JvmOverloads public constructor( // Wait for write job to complete before closing, matching old implementation writeJob?.cancelAndJoin() logger.debug { "Transport coroutine completed, calling onClose" } - callOnCloseOnce() + invokeOnCloseCallback() } } } - override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { - if (!initialized.load()) { - throw McpException( - code = CONNECTION_CLOSED, - message = "Transport is not started", - ) - } - if (onCloseCalled.load()) { - throw McpException( - code = CONNECTION_CLOSED, - message = "Transport is closed", - ) - } + override suspend fun performSend(message: JSONRPCMessage, options: TransportSendOptions?) { @Suppress("SwallowedException") try { sendChannel.send(message) @@ -254,33 +237,14 @@ public class StdioClientTransport @JvmOverloads public constructor( message = "Transport is closed", cause = e, ) - } catch (e: McpException) { - logger.debug(e) { "Error while sending message: ${e.message}" } - throw e - } catch (e: Throwable) { - logger.error(e) { "Error while sending message: ${e.message}" } - throw McpException( - code = INTERNAL_ERROR, - message = "Error while sending message: ${e.message}", - cause = e, - ) } } - override suspend fun close() { - if (!initialized.compareAndSet(expectedValue = true, newValue = false)) { - return // Already closed - } + override suspend fun closeResources() { scope.stopProcessing("Closed") scope.coroutineContext[Job]?.join() // Wait for all coroutines to complete } - private fun callOnCloseOnce() { - if (onCloseCalled.compareAndSet(expectedValue = false, newValue = true)) { - runCatching { _onClose() } - } - } - private fun sendOutboundMessage(message: JSONRPCMessage, sink: Sink, mainScope: CoroutineScope) { try { val json = serializeMessage(message) @@ -308,7 +272,7 @@ public class StdioClientTransport @JvmOverloads public constructor( private fun CoroutineScope.stopProcessing(reason: String, cause: Throwable? = null) { sendChannel.close() // Stop accepting new messages - callOnCloseOnce() + invokeOnCloseCallback() cancel(reason, cause) // cancel current coroutine context } diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt index c8f250a..fb1c640 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt @@ -1,5 +1,6 @@ package io.modelcontextprotocol.kotlin.sdk.client +import io.github.oshai.kotlinlogging.KLogger import io.github.oshai.kotlinlogging.KotlinLogging import io.ktor.client.HttpClient import io.ktor.client.plugins.sse.ClientSSESession @@ -21,15 +22,13 @@ import io.ktor.http.HttpStatusCode 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.AbstractClientTransport 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 import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse -import io.modelcontextprotocol.kotlin.sdk.types.McpException import io.modelcontextprotocol.kotlin.sdk.types.McpJson -import io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.CONNECTION_CLOSED import io.modelcontextprotocol.kotlin.sdk.types.RequestId import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineName @@ -40,7 +39,6 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.launch -import kotlin.concurrent.atomics.AtomicBoolean import kotlin.concurrent.atomics.ExperimentalAtomicApi import kotlin.time.Duration @@ -65,18 +63,14 @@ public class StreamableHttpClientTransport( private val url: String, private val reconnectionTime: Duration? = null, private val requestBuilder: HttpRequestBuilder.() -> Unit = {}, -) : AbstractTransport() { +) : AbstractClientTransport() { - private companion object { - private val logger = KotlinLogging.logger {} - } + override val logger: KLogger = KotlinLogging.logger {} public var sessionId: String? = null private set public var protocolVersion: String? = null - private val initialized: AtomicBoolean = AtomicBoolean(false) - private var sseSession: ClientSSESession? = null private var sseJob: Job? = null @@ -84,42 +78,21 @@ public class StreamableHttpClientTransport( private var lastEventId: String? = null - override suspend fun start() { - if (!initialized.compareAndSet(expectedValue = false, newValue = true)) { - error("StreamableHttpClientTransport already started!") - } - logger.debug { "Client transport starting..." } + override suspend fun initialize() { + logger.debug { "Client transport is starting..." } } /** * Sends a single message with optional resumption support */ - override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { - send(message, options?.resumptionToken, options?.onResumptionToken) - } - - /** - * Sends one or more messages with optional resumption support. - * This is the main send method that matches the TypeScript implementation. - */ - public suspend fun send( - message: JSONRPCMessage, - resumptionToken: String?, - onResumptionToken: ((String) -> Unit)? = null, - ) { - if (!initialized.load()) { - throw McpException( - code = CONNECTION_CLOSED, - message = "Transport is not started", - ) - } + override suspend fun performSend(message: JSONRPCMessage, options: TransportSendOptions?) { logger.debug { "Client sending message via POST to $url: ${McpJson.encodeToString(message)}" } // If we have a resumption token, reconnect the SSE stream with it - resumptionToken?.let { token -> + options?.resumptionToken?.let { token -> startSseSession( resumptionToken = token, - onResumptionToken = onResumptionToken, + onResumptionToken = options.onResumptionToken, replayMessageId = if (message is JSONRPCRequest) message.id else null, ) return @@ -138,7 +111,7 @@ public class StreamableHttpClientTransport( if (response.status == HttpStatusCode.Accepted) { if (message is JSONRPCNotification && message.method == "notifications/initialized") { - startSseSession(onResumptionToken = onResumptionToken) + startSseSession(onResumptionToken = options?.onResumptionToken) } return } @@ -161,7 +134,7 @@ public class StreamableHttpClientTransport( ContentType.Text.EventStream -> handleInlineSse( response, - onResumptionToken = onResumptionToken, + onResumptionToken = options?.onResumptionToken, replayMessageId = if (message is JSONRPCRequest) message.id else null, ) @@ -177,23 +150,31 @@ public class StreamableHttpClientTransport( } } - override suspend fun close() { - if (!initialized.load()) return // Already closed or never started + /** + * Sends one or more messages with optional resumption support. + * This is the main send method that matches the TypeScript implementation. + */ + public suspend fun send( + message: JSONRPCMessage, + resumptionToken: String?, + onResumptionToken: ((String) -> Unit)? = null, + ): Unit = send( + message = message, + options = TransportSendOptions( + resumptionToken = resumptionToken, + onResumptionToken = onResumptionToken, + ), + ) + + override suspend fun closeResources() { logger.debug { "Client transport closing." } - try { - // Try to terminate session if we have one - terminateSession() + // Try to terminate session if we have one + terminateSession() - sseSession?.cancel() - sseJob?.cancelAndJoin() - scope.cancel() - } catch (_: Exception) { - // Ignore errors during cleanup - } finally { - initialized.store(false) - _onClose() - } + sseSession?.cancel() + sseJob?.cancelAndJoin() + scope.cancel() } /** diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractClientTransportLifecycleTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractClientTransportLifecycleTest.kt index 7040c65..b6ece42 100644 --- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractClientTransportLifecycleTest.kt +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractClientTransportLifecycleTest.kt @@ -32,7 +32,7 @@ abstract class AbstractClientTransportLifecycleTest { val exception = shouldThrow { transport.start() } - exception.message shouldContain "already started" + exception.message shouldContain "Can't change state: expected transport state" } @Test @@ -53,8 +53,8 @@ abstract class AbstractClientTransportLifecycleTest { val exception = shouldThrow { transport.send(PingRequest().toJSON()) } - exception.message shouldContain "not started" exception.code shouldBe CONNECTION_CLOSED + exception.message shouldContain "Transport is not ready" } @Test diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt index 5cf96b6..4696184 100644 --- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt @@ -1,5 +1,6 @@ package io.modelcontextprotocol.kotlin.sdk.client.streamable.http +import io.kotest.matchers.shouldBe import io.ktor.client.HttpClient import io.ktor.client.engine.mock.MockEngine import io.ktor.client.engine.mock.MockRequestHandler @@ -18,7 +19,9 @@ import io.modelcontextprotocol.kotlin.sdk.types.Implementation import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest +import io.modelcontextprotocol.kotlin.sdk.types.McpException import io.modelcontextprotocol.kotlin.sdk.types.McpJson +import io.modelcontextprotocol.kotlin.sdk.types.RPCError import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.test.runTest @@ -94,7 +97,11 @@ class StreamableHttpClientTransportTest { ) val transport = createTransport { request -> - when (val msg = McpJson.decodeFromString((request.body as TextContent).text)) { + when ( + val msg = McpJson.decodeFromString( + (request.body as TextContent).text, + ) + ) { is JSONRPCRequest if msg.method == "initialize" -> respond( content = "", status = HttpStatusCode.OK, @@ -407,7 +414,7 @@ class StreamableHttpClientTransportTest { try { // Real time-keeping is needed; otherwise Protocol will always throw TimeoutCancellationException in tests - assertFailsWith( + val mcpException = assertFailsWith( message = "Expected client.connect to fail on invalid JSON response", ) { withContext(Dispatchers.Default.limitedParallelism(1)) { @@ -416,6 +423,7 @@ class StreamableHttpClientTransportTest { } } } + mcpException.code shouldBe RPCError.ErrorCode.INTERNAL_ERROR } finally { transport.close() } diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/stdio/StdioClientTransportErrorHandlingTest.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/stdio/StdioClientTransportErrorHandlingTest.kt index e93be84..3c1915f 100644 --- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/stdio/StdioClientTransportErrorHandlingTest.kt +++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/stdio/StdioClientTransportErrorHandlingTest.kt @@ -166,18 +166,12 @@ class StdioClientTransportErrorHandlingTest { true, ErrorCode.INTERNAL_ERROR, ), - Arguments.of( - OutOfMemoryError(), - true, - ErrorCode.INTERNAL_ERROR, - ), - ) } @ParameterizedTest @MethodSource("exceptions") - fun `Send should handle exceptions`(throwable: Throwable, shouldWrap: Boolean, expectedCode: Int?) = runTest { + fun `Send should handle exceptions`(throwable: Exception, shouldWrap: Boolean, expectedCode: Int?) = runTest { val sendChannel: Channel = mockk(relaxed = true) // Create stdin pipe that stays open to prevent transport from closing diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index 67acbdd..a5b581e 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -139,6 +139,9 @@ public final class io/modelcontextprotocol/kotlin/sdk/InitializedNotification { public static synthetic fun Params$default (Lio/modelcontextprotocol/kotlin/sdk/InitializedNotification;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; } +public abstract interface annotation class io/modelcontextprotocol/kotlin/sdk/InternalMcpApi : java/lang/annotation/Annotation { +} + public final class io/modelcontextprotocol/kotlin/sdk/LibVersionKt { public static final field LIB_VERSION Ljava/lang/String; } @@ -408,16 +411,46 @@ public final class io/modelcontextprotocol/kotlin/sdk/internal/Utils_jvmKt { public static final fun getIODispatcher ()Lkotlinx/coroutines/CoroutineDispatcher; } +public abstract class io/modelcontextprotocol/kotlin/sdk/shared/AbstractClientTransport : io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport { + public fun ()V + protected final fun checkState (Lio/modelcontextprotocol/kotlin/sdk/shared/ClientTransportState;Lkotlin/jvm/functions/Function1;)V + public static synthetic fun checkState$default (Lio/modelcontextprotocol/kotlin/sdk/shared/AbstractClientTransport;Lio/modelcontextprotocol/kotlin/sdk/shared/ClientTransportState;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V + public fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + protected abstract fun closeResources (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + protected abstract fun getLogger ()Lio/github/oshai/kotlinlogging/KLogger; + protected final fun getState ()Lio/modelcontextprotocol/kotlin/sdk/shared/ClientTransportState; + protected abstract fun initialize (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + protected abstract fun performSend (Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage;Lio/modelcontextprotocol/kotlin/sdk/shared/TransportSendOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun performSend$default (Lio/modelcontextprotocol/kotlin/sdk/shared/AbstractClientTransport;Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage;Lio/modelcontextprotocol/kotlin/sdk/shared/TransportSendOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)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; + protected final fun stateTransition (Lio/modelcontextprotocol/kotlin/sdk/shared/ClientTransportState;Lio/modelcontextprotocol/kotlin/sdk/shared/ClientTransportState;)V + protected final fun updateState (Lio/modelcontextprotocol/kotlin/sdk/shared/ClientTransportState;)V +} + public abstract class io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport : io/modelcontextprotocol/kotlin/sdk/shared/Transport { public fun ()V - protected final fun get_onClose ()Lkotlin/jvm/functions/Function0; protected final fun get_onError ()Lkotlin/jvm/functions/Function1; protected final fun get_onMessage ()Lkotlin/jvm/functions/Function2; + protected final fun invokeOnCloseCallback ()V public fun onClose (Lkotlin/jvm/functions/Function0;)V public fun onError (Lkotlin/jvm/functions/Function1;)V public fun onMessage (Lkotlin/jvm/functions/Function2;)V } +public final class io/modelcontextprotocol/kotlin/sdk/shared/ClientTransportState : java/lang/Enum { + public static final field InitializationFailed Lio/modelcontextprotocol/kotlin/sdk/shared/ClientTransportState; + public static final field Initializing Lio/modelcontextprotocol/kotlin/sdk/shared/ClientTransportState; + public static final field New Lio/modelcontextprotocol/kotlin/sdk/shared/ClientTransportState; + public static final field Operational Lio/modelcontextprotocol/kotlin/sdk/shared/ClientTransportState; + public static final field ShutdownFailed Lio/modelcontextprotocol/kotlin/sdk/shared/ClientTransportState; + public static final field ShuttingDown Lio/modelcontextprotocol/kotlin/sdk/shared/ClientTransportState; + public static final field Stopped Lio/modelcontextprotocol/kotlin/sdk/shared/ClientTransportState; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/shared/ClientTransportState; + public static fun values ()[Lio/modelcontextprotocol/kotlin/sdk/shared/ClientTransportState; +} + public abstract class io/modelcontextprotocol/kotlin/sdk/shared/Protocol { public fun (Lio/modelcontextprotocol/kotlin/sdk/shared/ProtocolOptions;)V protected abstract fun assertCapabilityForMethod (Lio/modelcontextprotocol/kotlin/sdk/types/Method;)V diff --git a/kotlin-sdk-core/build.gradle.kts b/kotlin-sdk-core/build.gradle.kts index dd095e6..40848b1 100644 --- a/kotlin-sdk-core/build.gradle.kts +++ b/kotlin-sdk-core/build.gradle.kts @@ -130,6 +130,8 @@ kotlin { jvmTest { dependencies { + implementation(libs.junit.jupiter.params) + implementation(libs.mockk) runtimeOnly(libs.slf4j.simple) } } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/InternalMcpApi.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/InternalMcpApi.kt new file mode 100644 index 0000000..ba9b348 --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/InternalMcpApi.kt @@ -0,0 +1,49 @@ +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 + +/** + * Denotes that the annotated API is internal to the MCP SDK and its usage is restricted to components + * within the SDK. It is not intended to be used in general application code or by external consumers. + * + * This annotation is used to communicate and enforce the limited visibility of certain APIs to prevent + * accidental usage outside their intended context. + * + * Applying this annotation requires consumers to explicitly opt-in, acknowledging the warning level + * associated with its use. + * + * This annotation can be used on classes, annotations, properties, fields, local variables, value + * parameters, constructors, functions, property accessors, and type aliases. + * + * The annotation is retained in the binary and must be documented. + */ +@RequiresOptIn( + message = "This API is internal, and its usage is restricted to internal components of the MCP SDK.", + level = RequiresOptIn.Level.WARNING, +) +@MustBeDocumented +@Target( + CLASS, + ANNOTATION_CLASS, + PROPERTY, + FIELD, + LOCAL_VARIABLE, + VALUE_PARAMETER, + CONSTRUCTOR, + FUNCTION, + PROPERTY_GETTER, + PROPERTY_SETTER, + TYPEALIAS, +) +@Retention(AnnotationRetention.BINARY) +public annotation class InternalMcpApi diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/AbstractClientTransport.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/AbstractClientTransport.kt new file mode 100644 index 0000000..5f67999 --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/AbstractClientTransport.kt @@ -0,0 +1,274 @@ +package io.modelcontextprotocol.kotlin.sdk.shared + +import io.github.oshai.kotlinlogging.KLogger +import io.modelcontextprotocol.kotlin.sdk.InternalMcpApi +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.McpException +import io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.CONNECTION_CLOSED +import io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.INTERNAL_ERROR +import kotlin.concurrent.atomics.AtomicReference +import kotlin.concurrent.atomics.ExperimentalAtomicApi +import kotlin.coroutines.cancellation.CancellationException + +/** + * Abstract base class representing a client-side transport layer for communication. + * + * This class is responsible for managing the lifecycle of a transport instance, including its initialization, + * state transitions, message transmission, and graceful shutdown. It provides a framework for implementing + * specific transport mechanisms by defining abstract methods that subclasses must implement. + * + * Thread-safety is achieved through the use of atomic references to manage transport state transitions and + * to ensure invariants during lifecycle events. The initialization and shutdown logic is designed to handle + * async workflows, ensuring proper resource cleanup in the case of failure or cancellation. + */ +@OptIn(ExperimentalAtomicApi::class, InternalMcpApi::class) +public abstract class AbstractClientTransport : AbstractTransport() { + + protected abstract val logger: KLogger + + /** + * Represents the current state of the client transport in an atomic and thread-safe manner. + * + * This private field is used internally to manage state transitions of the transport, ensuring + * consistency across concurrent operations. The state transitions are validated and managed + * using the `stateTransition` method, adhering to defined rules in [ClientTransportState.VALID_TRANSITIONS]. + * + * @see ClientTransportState + * @see stateTransition + * @see checkState + */ + private val _state: AtomicReference = AtomicReference(ClientTransportState.New) + + /** + * Current transport state. Thread-safe via atomic reference. + * Initial state is [ClientTransportState.New]. + */ + @InternalMcpApi + protected val state: ClientTransportState + get() = _state.load() + + /** + * Updates the internal state of the client transport to the specified new state. + * + * This method is used to manage the lifecycle and operational state of the client transport + * by transitioning it to the provided [new] state. The new state is stored atomically, + * ensuring thread safety during concurrent modifications. + * + * @param new The new state to transition the client transport to. It must be a valid state + * as defined in the transport's lifecycle logic. + */ + @InternalMcpApi + protected fun updateState(new: ClientTransportState) { + _state.store(new) + } + + /** + * Performs transport-specific initialization (connections, I/O streams, background coroutines). + * + * Called by [start] during [ClientTransportState.Initializing]. On exception, state transitions to + * [ClientTransportState.InitializationFailed], [closeResources] is called, and exception propagates. + */ + protected abstract suspend fun initialize() + + /** + * Atomically transitions state from `from` to `to`. + * + * Validates transition per [ClientTransportState.VALID_TRANSITIONS] and performs atomic compare-and-exchange. + * + * @throws IllegalArgumentException if transition isn't allowed + * @throws IllegalStateException if the actual state doesn't match `from` + */ + @InternalMcpApi + protected fun stateTransition(from: ClientTransportState, to: ClientTransportState) { + require(to in ClientTransportState.VALID_TRANSITIONS.getValue(from)) { + "Invalid transition: $from → $to" + } + val actualState = _state.compareAndExchange( + expectedValue = from, + newValue = to, + ) + check(actualState == from) { + "Can't change state: expected transport state $from, but found $actualState." + } + } + + /** + * Verifies that the current transport state matches the expected state and throws an exception if it does not. + * + * This method ensures that the transport's state is in alignment with the caller’s expectations before + * proceeding with further operations. It is often used to enforce state invariants within the transport's + * lifecycle mechanics, enabling robust error handling and debugging in case of unexpected state transitions. + * + * @param expected The expected transport state. An exception will be thrown if the actual state does not match this value. + * @throws IllegalStateException If the current state does not match the expected state. + */ + protected fun checkState( + expected: ClientTransportState, + lazyMessage: (ClientTransportState) -> Any = { + "Expected transport state $expected, but found $it" + }, + ) { + val actualState = state + check(actualState == expected) { lazyMessage } + } + + /** + * Initiates the transport by transitioning through the required states to prepare it for operation. + * + * This method transitions the transport state from `New` to `Initializing`, performs initialization + * logic specific to the transport implementation, and then transitions the state to `Operational` + * upon successful initialization. If an exception occurs during initialization, the state is transitioned + * to `InitializationFailed`, resources are cleaned up, and the exception is rethrown. + * + * This method operates within a suspend context to allow for asynchronous initialization tasks. + * + * **Important! This method will eventually become `final`. + * Please don't override it, or consistency guarantees might be lost. Override [initialize] instead.** + * + * @throws Exception if the initialization process fails and transfers state + * to [ClientTransportState.InitializationFailed]. + * @see initialize + */ + public override suspend fun start() { + stateTransition(from = ClientTransportState.New, to = ClientTransportState.Initializing) + try { + initialize() + stateTransition(from = ClientTransportState.Initializing, to = ClientTransportState.Operational) + } catch (e: Exception) { + _state.store(ClientTransportState.InitializationFailed) + closeResources() + throw e + } + } + + /** + * Sends a JSON-RPC message using the transport layer. + * + * This method ensures that the transport is operational before attempting to send the message. + * If the transport's state does not allow sending operations, an exception is thrown. + * It delegates the actual transmission to the abstract [performSend] method, which must be + * implemented by subclasses to handle the specifics of the transmission mechanism. Errors + * during the send operation are handled appropriately, and cancellation exceptions are always + * propagated. + * + * **Important! This method will eventually become `final`. + * Please don't override it, or consistency guarantees might be lost. Override [performSend] instead.** + * + * @param message The JSON-RPC message to be sent. Must adhere to the JSON-RPC 2.0 specification. + * @param options Optional parameters that influence how the message is transmitted, such as + * timeout specifications or message delivery guarantees. Can be null if no + * specific options are required. + * @throws McpException If the transport is not operational or other application-specific errors occur. + * @throws CancellationException If the coroutine is canceled. + * @see performSend + */ + public override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { + // fast path - state check to avoid nonsense operations + if (state != ClientTransportState.Operational) { + throw McpException( + code = CONNECTION_CLOSED, + message = "Transport is not ready", + ) + } + // limitation: state could still change at the time of use + try { + performSend(message, options) + } catch (e: CancellationException) { + throw e // Always propagate cancellation + } catch (e: Exception) { + _onError(e) + if (e is McpException) { + throw e + } else { + throw McpException( + code = INTERNAL_ERROR, + message = "Error while sending message: ${e.message}", + cause = e, + ) + } + } + } + + /** + * Transmits a JSON-RPC message via the underlying protocol. + * + * The method is responsible for transmitting a given JSON-RPC message to the intended destination. + * It is implemented by concrete subclasses of the transport to handle specific transmission + * mechanisms. + * + * Called by [send] after verifying transport [state]. + * + * @param message The JSON-RPC message to be sent. Must comply with the JSON-RPC 2.0 specification. + * @param options Optional parameters that influence how the message is transmitted, such as + * timeout settings or delivery guarantees. May be null if no specific options + * are required. + * @see send + */ + protected abstract suspend fun performSend(message: JSONRPCMessage, options: TransportSendOptions? = null) + + /** + * Closes the client transport gracefully, transitioning its state to a non-operational state. + * + * **Important! This method will eventually become `final`. + * Please don't override it, or consistency guarantees might be lost.** + * + * The method is designed to operate within a suspendable context, allowing support for asynchronous + * behavior during the resource cleanup and state transitions. + * + * Transitions [ClientTransportState.Operational] → [ClientTransportState.ShuttingDown] → [ClientTransportState.Stopped]. + * Calls [closeResources] then invokes [_onClose] callbacks exactly once. + * On [kotlin.coroutines.cancellation.CancellationException], transitions to [ClientTransportState.ShutdownFailed] and propagates. + * + * The method also handles unexpected exceptions gracefully during resource cleanup by transitioning + * the transport to the `ShutdownFailed` state. + * + * **Important! This method will eventually become `final`. + * Please don't override it, or consistency guarantees might be lost. Override [closeResources] instead.** + * + * @see closeResources + */ + public override suspend fun close() { + val performClose: Boolean + when (state) { + ClientTransportState.Operational -> { + // Only Operational state can transition to ShuttingDown + stateTransition(ClientTransportState.Operational, ClientTransportState.ShuttingDown) + performClose = true + } + + ClientTransportState.New -> { + // New state transitions directly to Stopped without any cleanup + stateTransition(ClientTransportState.New, ClientTransportState.Stopped) + performClose = false + } + + else -> { + // Already shutting down or in terminal state - idempotent, do nothing + performClose = false + } + } + if (performClose) { + try { + closeResources() + stateTransition(from = ClientTransportState.ShuttingDown, to = ClientTransportState.Stopped) + } catch (e: CancellationException) { + stateTransition(from = ClientTransportState.ShuttingDown, to = ClientTransportState.ShutdownFailed) + throw e // Always propagate cancellation + } catch (e: Exception) { + // Ignore errors during cleanup + logger.error(e) { "Error during transport shutdown" } + stateTransition(from = ClientTransportState.ShuttingDown, to = ClientTransportState.ShutdownFailed) + } finally { + invokeOnCloseCallback() + } + } + } + + /** + * Releases transport-specific resources (coroutines, connections, streams). + * + * Called by [close] during [ClientTransportState.ShuttingDown]. + * May be called even if [initialize] failed partially. + */ + protected abstract suspend fun closeResources() +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport.kt index cbab7ae..7b88100 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport.kt @@ -2,15 +2,18 @@ package io.modelcontextprotocol.kotlin.sdk.shared import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage import kotlinx.coroutines.CompletableDeferred +import kotlin.concurrent.atomics.AtomicBoolean +import kotlin.concurrent.atomics.ExperimentalAtomicApi /** * Implements [onClose], [onError] and [onMessage] functions of [Transport] providing * corresponding [_onClose], [_onError] and [_onMessage] properties to use for an implementation. */ +@OptIn(ExperimentalAtomicApi::class) @Suppress("PropertyName") public abstract class AbstractTransport : Transport { - protected var _onClose: (() -> Unit) = {} - private set + private val onCloseCalled = AtomicBoolean(false) + private var _onClose: (() -> Unit) = {} protected var _onError: ((Throwable) -> Unit) = {} private set @@ -51,4 +54,18 @@ public abstract class AbstractTransport : Transport { _onMessageInitialized.complete(Unit) } + + /** + * Invokes the `_onClose` callback if it has not been already triggered. + * + * This method ensures the `_onClose` callback is executed only once by utilizing + * an atomic flag (`onCloseCalled`). If the callback has already been executed, + * the method does nothing. Any exceptions thrown during the execution of the + * `_onClose` callback are caught and suppressed. + */ + protected fun invokeOnCloseCallback() { + if (onCloseCalled.compareAndSet(expectedValue = false, newValue = true)) { + runCatching { _onClose() } + } + } } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ClientTransportState.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ClientTransportState.kt new file mode 100644 index 0000000..4f14d03 --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ClientTransportState.kt @@ -0,0 +1,79 @@ +package io.modelcontextprotocol.kotlin.sdk.shared + +/** + * States of a client transport within the protocol lifecycle + * + * These states are generally aligned with the corresponding phases + * defined under the MCP [Protocol Lifecycle](https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle). + */ +public enum class ClientTransportState { + + /** + * The transport has just been created. + * + * This is the Initial state + */ + New, + + /** + * Indicates that the protocol + * is in the [Initialization Phase](https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle#initialization). + */ + Initializing, + + /** + * Indicates that the initialization phase of the protocol lifecycle has failed. + * + * This state suggests that the transport encountered an error or issue during + * initialization, preventing it from successfully transitioning to a fully operational state. + */ + InitializationFailed, + + /** + * Represents the [operational phase](https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle#operation) + * of the protocol lifecycle. + * + * During the operation phase, the client and server exchange messages according to the negotiated capabilities. + */ + Operational, + + /** + * Represents the shutting down phase of the protocol lifecycle. + * + * This state indicates that the transport is in the process of shutting down + * and transitioning to a non-operational state. + * + * During this phase, no new outgoing messages should be accepted. + */ + ShuttingDown, + + /** + * Indicates that the shutdown phase of the protocol lifecycle has failed. + * + * This state signifies that an error was encountered during the shutdown phase. + */ + ShutdownFailed, + + /** + * Indicates that the transport has fully stopped. + * + * This state represents the final phase of the lifecycle, where the transport is no longer + * operational and all cleanup routines or resource deallocations have been completed. + */ + Stopped, + + ; + + internal companion object { + val VALID_TRANSITIONS: Map> = mapOf( + New to setOf(Initializing, Stopped), + Initializing to setOf(Operational, InitializationFailed), + Operational to setOf(ShuttingDown), + ShuttingDown to setOf(Stopped, ShutdownFailed), + // Terminal states allow no transitions + InitializationFailed to emptySet(), + Stopped to emptySet(), + ShutdownFailed to emptySet(), + ) + } +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt index 4c0cc0d..e9d833a 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt @@ -255,7 +255,7 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio } logger.info { "Starting transport" } - return transport.start() + transport.start() } private fun doClose() { diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Transport.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Transport.kt index 52f1438..cb57547 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Transport.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Transport.kt @@ -11,6 +11,8 @@ public interface Transport { * * This method should only be called after callbacks are installed, or else messages may be lost. * + * Method waits for the initialization flow to complete and then returns, so the transport is ready to send and receive user messages. + * * NOTE: This method should not be called explicitly when using Client, Server, or Protocol classes, * as they will implicitly call start(). */ @@ -26,7 +28,7 @@ public interface Transport { public suspend fun send(message: JSONRPCMessage, options: TransportSendOptions? = null) /** - * Closes the connection. + * Closes the transport. */ public suspend fun close() diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt index 392efdc..de75cc4 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt @@ -85,7 +85,7 @@ public abstract class WebSocketMcpTransport : AbstractTransport() { if (it != null) { _onError.invoke(it) } else { - _onClose.invoke() + invokeOnCloseCallback() } } } diff --git a/kotlin-sdk-core/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/AbstractClientTransportTest.kt b/kotlin-sdk-core/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/AbstractClientTransportTest.kt new file mode 100644 index 0000000..1d144d8 --- /dev/null +++ b/kotlin-sdk-core/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/AbstractClientTransportTest.kt @@ -0,0 +1,498 @@ +package io.modelcontextprotocol.kotlin.sdk.shared + +import io.github.oshai.kotlinlogging.KLogger +import io.github.oshai.kotlinlogging.KotlinLogging +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain +import io.mockk.spyk +import io.modelcontextprotocol.kotlin.sdk.InternalMcpApi +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.McpException +import io.modelcontextprotocol.kotlin.sdk.types.PingRequest +import io.modelcontextprotocol.kotlin.sdk.types.RPCError +import io.modelcontextprotocol.kotlin.sdk.types.toJSON +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.CsvSource +import org.junit.jupiter.params.provider.EnumSource +import org.junit.jupiter.params.provider.MethodSource +import kotlin.concurrent.atomics.ExperimentalAtomicApi +import kotlin.coroutines.cancellation.CancellationException + +/** + * Unit tests for [AbstractClientTransport] state machine and lifecycle. + * + * Tests behavioral correctness of state transitions, lifecycle management, + * and message sending without relying on mocking frameworks. + */ +@OptIn(ExperimentalAtomicApi::class) +class AbstractClientTransportTest { + + companion object { + @JvmStatic + fun nonMcpExceptions() = listOf( + TestException("Generic failure"), + java.io.IOException("Network error"), + IllegalStateException("Invalid state"), + ) + } + + private lateinit var transport: TestClientTransport + + @BeforeEach + fun beforeEach() { + transport = spyk(TestClientTransport()) + } + + @Nested + @DisplayName("Lifecycle") + inner class LifecycleTests { + + @Test + fun `should start with NEW state`() { + transport.currentState shouldBe ClientTransportState.New + } + + @Test + fun `should transition to OPERATIONAL after successful start`() = runTest { + transport.start() + + transport.currentState shouldBe ClientTransportState.Operational + transport.initializeCalled shouldBe true + } + + @Test + fun `should call initialize during start`() = runTest { + transport.start() + + transport.initializeCalled shouldBe true + transport.currentState shouldBe ClientTransportState.Operational + } + + @Test + fun `should transition to INITIALIZATION_FAILED on start error`() = runTest { + transport.shouldFailInitialization = true + + shouldThrow { + transport.start() + } + + transport.currentState shouldBe ClientTransportState.InitializationFailed + transport.closeResourcesCalled shouldBe true + } + + @Test + fun `should call closeResources on initialization failure`() = runTest { + transport.shouldFailInitialization = true + + shouldThrow { + transport.start() + } + + transport.closeResourcesCalled shouldBe true + } + + @Test + fun `should reject starting twice`() = runTest { + transport.start() + + val exception = shouldThrow { + transport.start() + } + exception.message shouldContain "expected transport state New" + } + + @Test + fun `should transition to STOPPED after successful close`() = runTest { + transport.start() + transport.close() + + transport.currentState shouldBe ClientTransportState.Stopped + transport.closeResourcesCalled shouldBe true + } + + @Test + fun `should call closeResources during close`() = runTest { + transport.start() + + transport.close() + + transport.closeResourcesCalled shouldBe true + transport.currentState shouldBe ClientTransportState.Stopped + } + + @Test + fun `should be idempotent when closed multiple times`() = runTest { + // given + transport.start() + transport.close() + // when + transport.close() + transport.close() + + transport.currentState shouldBe ClientTransportState.Stopped + transport.closeResourcesCallCount shouldBe 1 + } + + @Test + fun `should call onClose callback exactly once on multiple close calls`() = runTest { + var onCloseCallCount = 0 + transport.onClose { onCloseCallCount++ } + + transport.start() + transport.close() + transport.close() + + onCloseCallCount shouldBe 1 + } + + @Test + fun `should transition to SHUTDOWN_FAILED on close error`() = runTest { + transport.shouldFailClose = true + transport.start() + + transport.close() + + transport.currentState shouldBe ClientTransportState.ShutdownFailed + } + + @Test + fun `should call onClose even when close fails`() = runTest { + // given + var onCloseCalled = false + transport.onClose { onCloseCalled = true } + transport.shouldFailClose = true + transport.start() + // when + transport.close() + // then + onCloseCalled shouldBe true + } + + @Test + fun `should propagate CancellationException on close`() = runTest { + // given + transport.shouldThrowCancellation = true + transport.start() + // when-then + shouldThrow { + transport.close() + } + + transport.currentState shouldBe ClientTransportState.ShutdownFailed + } + + @Test + fun `should call onClose even when CancellationException is thrown`() = runTest { + var onCloseCalled = false + transport.onClose { onCloseCalled = true } + transport.shouldThrowCancellation = true + transport.start() + + shouldThrow { + transport.close() + } + + onCloseCalled shouldBe true + } + } + + @Nested + @DisplayName("State Transitions") + inner class StateTransitionTests { + + @ParameterizedTest + @CsvSource( + "New, Initializing", + "New, Stopped", + "Initializing, Operational", + "Initializing, InitializationFailed", + "Operational, ShuttingDown", + "ShuttingDown, Stopped", + "ShuttingDown, ShutdownFailed", + ) + fun `should allow valid transitions`(fromName: String, toName: String) { + val from = ClientTransportState.valueOf(fromName) + val to = ClientTransportState.valueOf(toName) + + transport.forceState(from) + transport.testStateTransition(from, to) + + transport.currentState shouldBe to + } + + @ParameterizedTest + @CsvSource( + // From New: only Initializing is valid + "New, Operational", + "New, InitializationFailed", + "New, ShuttingDown", + "New, ShutdownFailed", + // From Initializing: only Operational or InitializationFailed are valid + "Initializing, New", + "Initializing, Initializing", + "Initializing, ShuttingDown", + "Initializing, Stopped", + "Initializing, ShutdownFailed", + // From Operational: only ShuttingDown is valid + "Operational, New", + "Operational, Initializing", + "Operational, InitializationFailed", + "Operational, Operational", + "Operational, Stopped", + "Operational, ShutdownFailed", + // From ShuttingDown: only Stopped or ShutdownFailed are valid + "ShuttingDown, New", + "ShuttingDown, Initializing", + "ShuttingDown, InitializationFailed", + "ShuttingDown, Operational", + "ShuttingDown, ShuttingDown", + // From terminal states: no transitions allowed + "InitializationFailed, New", + "InitializationFailed, Operational", + "Stopped, New", + "Stopped, Operational", + "ShutdownFailed, New", + "ShutdownFailed, Operational", + ) + fun `should reject invalid transitions`(fromName: String, toName: String) { + val from = ClientTransportState.valueOf(fromName) + val to = ClientTransportState.valueOf(toName) + + transport.forceState(from) + + val exception = shouldThrow { + transport.testStateTransition(from, to) + } + exception.message shouldContain "Invalid transition: $from → $to" + } + } + + @Nested + @DisplayName("Send Operations") + inner class SendTests { + + @Test + fun `should send message successfully when OPERATIONAL`() = runTest { + val message = PingRequest().toJSON() + + transport.start() + transport.send(message) + + transport.sentMessages shouldBe listOf(message) + } + + @Test + fun `should pass options to performSend`() = runTest { + val message = PingRequest().toJSON() + val options = TransportSendOptions() + + transport.start() + transport.send(message, options) + + transport.sentMessages shouldBe listOf(message) + transport.lastSendOptions shouldBe options + } + + @Test + fun `should throw when sending before start`() = runTest { + val exception = shouldThrow { + transport.send(PingRequest().toJSON()) + } + + exception.code shouldBe RPCError.ErrorCode.CONNECTION_CLOSED + exception.message shouldContain "Transport is not ready" + } + + @Test + fun `should throw when sending after close`() = runTest { + transport.start() + transport.close() + + val exception = shouldThrow { + transport.send(PingRequest().toJSON()) + } + + exception.code shouldBe RPCError.ErrorCode.CONNECTION_CLOSED + exception.message shouldContain "Transport is not ready" + } + + @ParameterizedTest + @EnumSource( + value = ClientTransportState::class, + names = ["Operational"], + mode = EnumSource.Mode.EXCLUDE, + ) + fun `should throw when sending in non-OPERATIONAL state`(state: ClientTransportState) = runTest { + transport.forceState(state) + + val exception = shouldThrow { + transport.send(PingRequest().toJSON()) + } + + exception.code shouldBe RPCError.ErrorCode.CONNECTION_CLOSED + exception.message shouldContain "Transport is not ready" + transport.sentMessages.isEmpty() shouldBe true + } + + @Test + fun `should call onError when performSend throws McpException`() = runTest { + var errorCallCount = 0 + var capturedError: Throwable? = null + transport.onError { error -> + errorCallCount++ + capturedError = error + } + transport.shouldFailSend = true + transport.sendException = McpException(RPCError.ErrorCode.INTERNAL_ERROR, "Send failed") + + transport.start() + val exception = shouldThrow { + transport.send(PingRequest().toJSON()) + } + + errorCallCount shouldBe 1 + capturedError shouldBe transport.sendException + exception shouldBe transport.sendException + } + + @ParameterizedTest + @MethodSource("io.modelcontextprotocol.kotlin.sdk.shared.AbstractClientTransportTest#nonMcpExceptions") + fun `should call onError when performSend throws non-MCP exception`(testException: Throwable) = runTest { + var errorCallCount = 0 + var capturedError: Throwable? = null + transport.onError { error -> + errorCallCount++ + capturedError = error + } + transport.shouldFailSend = true + transport.sendException = testException + + transport.start() + val exception = shouldThrow { + transport.send(PingRequest().toJSON()) + } + + errorCallCount shouldBe 1 + capturedError shouldBe testException + exception.code shouldBe RPCError.ErrorCode.INTERNAL_ERROR + exception.cause shouldBe testException + } + + @Test + fun `should NOT call onError when performSend throws CancellationException`() = runTest { + var errorCallCount = 0 + transport.onError { errorCallCount++ } + transport.shouldFailSend = true + transport.sendException = CancellationException("Operation cancelled") + + transport.start() + shouldThrow { + transport.send(PingRequest().toJSON()) + } + + errorCallCount shouldBe 0 + } + } + + @Nested + @DisplayName("Idempotency") + inner class IdempotencyTests { + + @Test + fun `should be idempotent when closing from NEW state`() = runTest { + transport.close() + + transport.currentState shouldBe ClientTransportState.Stopped + transport.closeResourcesCalled shouldBe false + } + + @Test + fun `should be idempotent when closing from INITIALIZATION_FAILED state`() = runTest { + transport.shouldFailInitialization = true + shouldThrow { transport.start() } + + transport.close() + + transport.currentState shouldBe ClientTransportState.InitializationFailed + // closeResources was called during a failed start, but not during this close + transport.closeResourcesCallCount shouldBe 1 + } + + @ParameterizedTest + @EnumSource( + value = ClientTransportState::class, + names = ["ShuttingDown", "ShutdownFailed", "Stopped"], + ) + fun `should be idempotent when closing from terminal or shutdown states`(state: ClientTransportState) = + runTest { + transport.forceState(state) + val initialCloseCount = transport.closeResourcesCallCount + + transport.close() + + transport.currentState shouldBe state + transport.closeResourcesCallCount shouldBe initialCloseCount + } + } + + /** + * Simple test implementation of [AbstractClientTransport] for behavioral testing. + * Tracks method calls without mocking frameworks. + */ + @OptIn(InternalMcpApi::class) + private class TestClientTransport : AbstractClientTransport() { + override val logger: KLogger = KotlinLogging.logger {} + val sentMessages = mutableListOf() + var lastSendOptions: TransportSendOptions? = null + var initializeCalled = false + var closeResourcesCalled = false + var closeResourcesCallCount = 0 + var shouldFailInitialization = false + var shouldFailClose = false + var shouldThrowCancellation = false + var shouldFailSend = false + var sendException: Throwable? = null + + val currentState: ClientTransportState + get() = state + + override suspend fun initialize() { + initializeCalled = true + if (shouldFailInitialization) { + throw TestException("Initialization failed") + } + } + + override suspend fun performSend(message: JSONRPCMessage, options: TransportSendOptions?) { + if (shouldFailSend) { + throw sendException ?: TestException("Send failed") + } + sentMessages.add(message) + lastSendOptions = options + } + + override suspend fun closeResources() { + closeResourcesCalled = true + closeResourcesCallCount++ + + when { + shouldThrowCancellation -> throw CancellationException("Test cancellation") + shouldFailClose -> throw TestException("Close failed") + } + } + + fun testStateTransition(from: ClientTransportState, to: ClientTransportState) { + stateTransition(from, to) + } + + fun forceState(newState: ClientTransportState) = updateState(newState) + } + + private class TestException(message: String) : Exception(message) +} diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt index bb436bf..619266a 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt @@ -58,7 +58,7 @@ public class SseServerTransport(private val endpoint: String, private val sessio if (it != null && it !is CancellationException) { _onError.invoke(it) } else { - _onClose.invoke() + invokeOnCloseCallback() } } } @@ -114,7 +114,7 @@ public class SseServerTransport(private val endpoint: String, private val sessio override suspend fun close() { session.close() - _onClose.invoke() + invokeOnCloseCallback() } override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport.kt index 092d0a6..b3ec4d6 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport.kt @@ -142,7 +142,7 @@ public class StdioServerTransport(private val inputStream: Source, outputStream: outputWriter.close() }.onFailure { logger.warn(it) { "Failed to close stdout" } } - _onClose.invoke() + invokeOnCloseCallback() } } diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt index e4ccf71..bd683af 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt @@ -221,7 +221,7 @@ public class StreamableHttpServerTransport( streamsMapping.clear() requestToStreamMapping.clear() requestToResponseMapping.clear() - _onClose() + invokeOnCloseCallback() } } From 52a0ec3b8fec0bc5d4a32625446c977b13e57ad5 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Fri, 13 Feb 2026 15:21:29 +0200 Subject: [PATCH 141/449] ci: enhance stale issue/PR workflow (#522) # ci: enhance stale issue/PR workflow - Split stale workflow into marking and closing jobs - Add dry-run mode input and environment variables - Improve configuration for timing, messages, and operations per run ## Motivation and Context To mark and close stale issues/PRs automatically ## How it works The automations.yml workflow automates the lifecycle of inactive issues and pull requests using a two-stage process. It is scheduled to run daily at 2:00 AM UTC and supports manual execution with a dry-run option. ### 1. Marking Phase (mark-stale job) This job identifies inactive items and labels them as stale without closing them. - Criteria: Any issue or PR with no activity for 30 days. - Action: Adds the stale label and posts a notification comment. - Exemptions: - Draft PRs are ignored. - Issues labeled with awaiting-approval or work-in-progress are ignored. - Recovery: If a contributor updates the item, the stale label is automatically removed. - Safety: This job is explicitly configured never to close items (days-before-*-close: -1). ### 2. Closing Phase (close-stale job) This job handles the final closure of items that have remained inactive after being marked. - Criteria: Only targets items that already have the stale label and have seen no further activity for 15 days. - Action: Closes the issue or PR and posts a final closure message. - Safety: This job is explicitly configured never to mark new items (days-before-*-stale: -1). ### Global Configuration - Dry Run: Controlled by the DRY_RUN variable. When true, the bot logs planned actions in the workflow output but does not actually apply labels, post comments, or close items. - Throttling: Processes up to 100 items per run to avoid hitting GitHub API rate limits. - Permissions: Requires write access to issues and pull requests to perform its actions. ## How Has This Been Tested? [Manually tested on CI](https://github.com/modelcontextprotocol/kotlin-sdk/actions/runs/21984701285) with dry run ## Breaking Changes No ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [x] Workflow update ## Checklist - [ ] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [ ] My code follows the repository's style guidelines - [ ] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed ## Additional context --- .github/workflows/automations.yml | 106 ++++++++++++++++++++---------- 1 file changed, 70 insertions(+), 36 deletions(-) diff --git a/.github/workflows/automations.yml b/.github/workflows/automations.yml index fbccaea..4f88d37 100644 --- a/.github/workflows/automations.yml +++ b/.github/workflows/automations.yml @@ -5,6 +5,11 @@ on: # Runs every day at 2:00 AM UTC - cron: '0 2 * * *' workflow_dispatch: + inputs: + DRY_RUN: + type: boolean + description: 'Run in dry run mode' + default: true permissions: actions: write @@ -12,58 +17,87 @@ permissions: issues: write pull-requests: write +env: + DRY_RUN: true + MOVE_TO_STALE_DAYS: 30 + CLOSE_AFTER_STALE_DAYS: 15 + jobs: - close-question-prs: - name: Mark/Close stale PRs and issues + mark-stale: + name: Mark/unmark stale PRs and issues runs-on: ubuntu-latest steps: - uses: actions/stale@v10 - ## https://github.com/marketplace/actions/close-stale-issues with: - debug-only: true # 'true' for dry run - exempt-draft-pr: true # pull requests currently in draft will not be marked as stale + debug-only: ${{ env.DRY_RUN }} + exempt-draft-pr: true exempt-issue-labels: 'awaiting-approval,work-in-progress' + stale-issue-label: 'stale' stale-pr-label: 'stale' - only-labels: 'awaiting-feedback,awaiting-answers' - - # --- Scope: only PRs, not issues --- - stale-issue-message: > - 👋 **This issue has been marked as Stale.** - - This issue was awaiting feedback and has had no activity for **30 days**. - - — 🤖 Stale PR Bot - days-before-issue-stale: 30 - days-before-issue-close: -1 # --- Timing --- - # Since we target PRs already labeled "question", - # days-before-stale counts from last activity. - days-before-pr-stale: 30 - days-before-pr-close: 15 + # Job 1 marks/unmarks, but never closes. + days-before-issue-stale: ${{ env.MOVE_TO_STALE_DAYS }} + days-before-issue-close: -1 + days-before-pr-stale: ${{ env.MOVE_TO_STALE_DAYS }} + days-before-pr-close: -1 - # --- Labels --- remove-stale-when-updated: true remove-pr-stale-when-updated: true # --- Messages --- + stale-issue-message: > + 👋 **This issue has been marked as Stale.** + + This issue has had no activity for **${{ env.MOVE_TO_STALE_DAYS }} days**. + + — 🤖 Stale Bot stale-pr-message: > 👋 **This PR has been marked as Stale.** - - This PR was awaiting feedback and has had no activity for **30 days**. - - — 🤖 Stale PR Bot - close-pr-message: > - 👋 **This PR has been automatically closed.** - - This PR was labeled with `stale` and has had no activity - for **15 days** since that. - - If this is still relevant, please feel free to reopen it - or create a new PR. - - — 🤖 Stale PR Bot - # --- Operations --- + This PR has had no activity for **${{ env.MOVE_TO_STALE_DAYS }} days**. + + — 🤖 Stale Bot + + operations-per-run: 100 + ascending: true + + close-stale: + name: Close stale PRs and issues + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v10 + with: + debug-only: ${{ env.DRY_RUN }} + only-labels: 'stale' + stale-issue-label: 'stale' + stale-pr-label: 'stale' + + # --- Timing --- + # Job 2 only closes already marked items. + days-before-issue-stale: -1 + days-before-pr-stale: -1 + days-before-issue-close: ${{ env.CLOSE_AFTER_STALE_DAYS }} + days-before-pr-close: ${{ env.CLOSE_AFTER_STALE_DAYS }} + + # --- Messages --- + close-issue-message: > + 👋 **This issue has been automatically closed.** + + This issue was labeled with `stale` and has had no activity + for **${{ env.CLOSE_AFTER_STALE_DAYS }} days** since that. + + — 🤖 Stale Bot + close-pr-message: > + 👋 **This PR has been automatically closed.** + + This PR was labeled with `stale` and has had no activity + for **${{ env.CLOSE_AFTER_STALE_DAYS }} days** since that. + + If this is still relevant, please feel free to reopen it + or create a new PR. + + — 🤖 Stale Bot + operations-per-run: 100 ascending: true From f3a980ae85527f3ddb06adcf1fff736c28000896 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Feb 2026 15:22:41 +0200 Subject: [PATCH 142/449] Update modelcontextprotocol/sdk to1.26.0, qs to 6.14.2 (#519) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the npm_and_yarn group with 2 updates in the /integration-test/src/jvmTest/typescript directory: [@modelcontextprotocol/sdk](https://github.com/modelcontextprotocol/typescript-sdk) and [qs](https://github.com/ljharb/qs). Updates `@modelcontextprotocol/sdk` from 1.25.2 to 1.26.0
Release notes

Sourced from @​modelcontextprotocol/sdk's releases.

v1.26.0

Addresses "Sharing server/transport instances can leak cross-client response data" in this GHSA https://github.com/modelcontextprotocol/typescript-sdk/security/advisories/GHSA-345p-7cg4-v4c7

What's Changed

New Contributors

Full Changelog: https://github.com/modelcontextprotocol/typescript-sdk/compare/v1.25.3...v1.26.0

v1.25.3

What's Changed

Full Changelog: https://github.com/modelcontextprotocol/typescript-sdk/compare/v1.25.2...v1.25.3

Commits
  • fe9c07b chore: bump version to 1.26.0 (#1479)
  • 4f01e7e fix: add non-null assertions for optional setupServer fields in stateful test
  • a05be17 Merge commit from fork
  • 50d9fa3 Fix #1430: Client Credentials providers scopes support (backported) (#1442)
  • aa81a66 fix(deps): resolve npm audit vulnerabilities and bump dependencies (v1.x back...
  • 6aba065 chore: bump v1.25.3 for backport fixes (#1412)
  • 6e8f7e1 fix: prevent Hono from overriding global Response object (v1.x) (#1411)
  • 12ae856 [v1.x backport] Use correct schema for client sampling validation when tools ...
  • See full diff in compare view

Updates `qs` from 6.14.1 to 6.14.2
Changelog

Sourced from qs's changelog.

6.14.2

  • [Fix] parse: mark overflow objects for indexed notation exceeding arrayLimit (#546)
  • [Fix] arrayLimit means max count, not max index, in combine/merge/parseArrayValue
  • [Fix] parse: throw on arrayLimit exceeded with indexed notation when throwOnLimitExceeded is true (#529)
  • [Fix] parse: enforce arrayLimit on comma-parsed values
  • [Fix] parse: fix error message to reflect arrayLimit as max index; remove extraneous comments (#545)
  • [Robustness] avoid .push, use void
  • [readme] document that addQueryPrefix does not add ? to empty output (#418)
  • [readme] clarify parseArrays and arrayLimit documentation (#543)
  • [readme] replace runkit CI badge with shields.io check-runs badge
  • [meta] fix changelog typo (arrayLength → arrayLimit)
  • [actions] fix rebase workflow permissions
Commits
  • bdcf0c7 v6.14.2
  • 294db90 [readme] document that addQueryPrefix does not add ? to empty output
  • 5c308e5 [readme] clarify parseArrays and arrayLimit documentation
  • 6addf8c [Fix] parse: mark overflow objects for indexed notation exceeding arrayLimit
  • cfc108f [Fix] arrayLimit means max count, not max index, in combine/merge/`pars...
  • febb644 [Fix] parse: throw on arrayLimit exceeded with indexed notation when `thr...
  • f6a7abf [Fix] parse: enforce arrayLimit on comma-parsed values
  • fbc5206 [Fix] parse: fix error message to reflect arrayLimit as max index; remove e...
  • 1b9a8b4 [actions] fix rebase workflow permissions
  • 2a35775 [meta] fix changelog typo (arrayLength → arrayLimit)
  • Additional commits viewable in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/modelcontextprotocol/kotlin-sdk/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../src/jvmTest/typescript/package-lock.json | 56 +++++++++++-------- .../src/jvmTest/typescript/package.json | 2 +- 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index a7a67d6..19aec64 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -8,7 +8,7 @@ "name": "mcp-typescript-test-sdk", "version": "1.0.0", "dependencies": { - "@modelcontextprotocol/sdk": "^1.25.2", + "@modelcontextprotocol/sdk": "^1.26.0", "body-parser": "^2.2.2", "express": "^5.2.1", "uuid": "^13.0.0", @@ -481,9 +481,9 @@ } }, "node_modules/@hono/node-server": { - "version": "1.19.8", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.8.tgz", - "integrity": "sha512-0/g2lIOPzX8f3vzW1ggQgvG5mjtFBDBHFAzI5SFAi2DzSqS9luJwqg9T6O/gKYLi+inS7eNxBeIFkkghIPvrMA==", + "version": "1.19.9", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", + "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", "license": "MIT", "engines": { "node": ">=18.14.1" @@ -493,12 +493,12 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.25.2", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.25.2.tgz", - "integrity": "sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww==", + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz", + "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==", "license": "MIT", "dependencies": { - "@hono/node-server": "^1.19.7", + "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", @@ -506,14 +506,15 @@ "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", - "express": "^5.0.1", - "express-rate-limit": "^7.5.0", - "jose": "^6.1.1", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", - "zod-to-json-schema": "^3.25.0" + "zod-to-json-schema": "^3.25.1" }, "engines": { "node": ">=18" @@ -1028,10 +1029,13 @@ } }, "node_modules/express-rate-limit": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", - "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", + "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", "license": "MIT", + "dependencies": { + "ip-address": "10.0.1" + }, "engines": { "node": ">= 16" }, @@ -1213,11 +1217,10 @@ } }, "node_modules/hono": { - "version": "4.11.7", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.7.tgz", - "integrity": "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==", + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.9.tgz", + "integrity": "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=16.9.0" } @@ -1264,6 +1267,15 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -1469,9 +1481,9 @@ } }, "node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" diff --git a/integration-test/src/jvmTest/typescript/package.json b/integration-test/src/jvmTest/typescript/package.json index 24a1ac9..6972151 100644 --- a/integration-test/src/jvmTest/typescript/package.json +++ b/integration-test/src/jvmTest/typescript/package.json @@ -9,7 +9,7 @@ "server:sse": "tsx server/sse-server.ts" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.25.2", + "@modelcontextprotocol/sdk": "^1.26.0", "body-parser": "^2.2.2", "express": "^5.2.1", "uuid": "^13.0.0", From 0ca0061b4b349ee92cbbdb998adf09058c7dbdf4 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Fri, 13 Feb 2026 15:24:43 +0200 Subject: [PATCH 143/449] Add DeepWiki badge to README Added badge for DeepWiki to README. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index bf49bf0..881ee98 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ [![Kotlin Multiplatform](https://img.shields.io/badge/Platforms-%20JVM%20%7C%20Wasm%2FJS%20%7C%20Native%20-blueviolet?logo=kotlin)](https://kotlinlang.org/docs/multiplatform.html) [![JVM](https://img.shields.io/badge/JVM-11+-red.svg?logo=jvm)](http://java.com) [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE) +[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/modelcontextprotocol/kotlin-sdk) Kotlin Multiplatform SDK for the [Model Context Protocol](https://modelcontextprotocol.io). It enables Kotlin applications targeting JVM, Native, JS, and Wasm to implement MCP clients and servers using a From f79d3786976f85295cec2366f3eb0b629a6f8c19 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Feb 2026 18:40:40 +0200 Subject: [PATCH 144/449] Bump org.jetbrains.kotlinx:atomicfu-gradle-plugin from 0.29.0 to 0.31.0 (#513) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 206687b..3dca4cf 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] # plugins version -atomicfu = "0.29.0" +atomicfu = "0.31.0" binaryCompatibilityValidatorPlugin = "0.18.1" detekt = "2.0.0-alpha.1" dokka = "2.1.0" From 4501f14b5621f1cbb6c3f7cc7545bbed6fbe9750 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Feb 2026 21:19:06 +0200 Subject: [PATCH 145/449] Bump dev.detekt from 2.0.0-alpha.1 to 2.0.0-alpha.2 (#512) --- config/detekt/detekt.yml | 848 -------------------------------------- gradle/libs.versions.toml | 2 +- 2 files changed, 1 insertion(+), 849 deletions(-) diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml index 5493fb7..a7504c1 100644 --- a/config/detekt/detekt.yml +++ b/config/detekt/detekt.yml @@ -2,852 +2,4 @@ config: validation: true warningsAsErrors: false checkExhaustiveness: false - # when writing own rules with new properties, exclude the property path e.g.: ['my_rule_set', '.*>.*>[my_property]'] - excludes: [] -processors: - active: true - exclude: - - 'DetektProgressListener' - # - 'KtFileCountProcessor' - # - 'PackageCountProcessor' - # - 'ClassCountProcessor' - # - 'FunctionCountProcessor' - # - 'PropertyCountProcessor' - # - 'ProjectComplexityProcessor' - # - 'ProjectCognitiveComplexityProcessor' - # - 'ProjectLLOCProcessor' - # - 'ProjectCLOCProcessor' - # - 'ProjectLOCProcessor' - # - 'ProjectSLOCProcessor' - # - 'LicenseHeaderLoaderExtension' - -console-reports: - active: true - exclude: - - 'ProjectStatisticsReport' - - 'ComplexityReport' - - 'NotificationReport' - - 'IssuesReport' - - 'FileBasedIssuesReport' - # - 'LiteIssuesReport' - -comments: - active: true - AbsentOrWrongFileLicense: - active: false - licenseTemplateFile: 'license.template' - licenseTemplateIsRegex: false - CommentOverPrivateFunction: - active: false - CommentOverPrivateProperty: - active: false - DeprecatedBlockTag: - active: false - EndOfSentenceFormat: - active: false - endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)' - KDocReferencesNonPublicProperty: - active: false - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] - OutdatedDocumentation: - active: false - matchTypeParameters: true - matchDeclarationsOrder: true - allowParamOnConstructorProperties: false - UndocumentedPublicClass: - active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] - searchInNestedClass: true - searchInInnerClass: true - searchInInnerObject: true - searchInInnerInterface: true - searchInProtectedClass: false - ignoreDefaultCompanionObject: false - UndocumentedPublicFunction: - active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] - searchProtectedFunction: false - UndocumentedPublicProperty: - active: false - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] - searchProtectedProperty: false - ignoreEnumEntries: false - -complexity: - active: true - CognitiveComplexMethod: - active: false - allowedComplexity: 15 - ComplexCondition: - active: true - allowedConditions: 4 - ComplexInterface: - active: false - allowedDefinitions: 10 - includeStaticDeclarations: false - includePrivateDeclarations: false - ignoreOverloaded: false - CyclomaticComplexMethod: - active: true - allowedComplexity: 15 - ignoreSingleWhenExpression: false - ignoreSimpleWhenEntries: false - ignoreNestingFunctions: false - ignoreLocalFunctions: false - nestingFunctions: - - 'also' - - 'apply' - - 'forEach' - - 'isNotNull' - - 'ifNull' - - 'let' - - 'run' - - 'use' - - 'with' - LabeledExpression: - active: false - ignoredLabels: [] - LargeClass: - active: false - allowedLines: 600 - LongMethod: - active: true - allowedLines: 150 - LongParameterList: - active: true - allowedFunctionParameters: 8 - allowedConstructorParameters: 8 - ignoreDefaultParameters: false - ignoreDataClasses: true - ignoreAnnotatedParameter: [] - MethodOverloading: - active: true - allowedOverloads: 6 - NamedArguments: - active: false - allowedArguments: 3 - ignoreArgumentsMatchingNames: false - NestedBlockDepth: - active: true - allowedDepth: 4 - NestedScopeFunctions: - active: false - allowedDepth: 1 - functions: - - 'kotlin.apply' - - 'kotlin.run' - - 'kotlin.with' - - 'kotlin.let' - - 'kotlin.also' - ReplaceSafeCallChainWithRun: - active: false - StringLiteralDuplication: - active: false - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] - allowedDuplications: 3 - ignoreAnnotation: true - allowedWithLengthLessThan: 5 - ignoreStringsRegex: '$^' - TooManyFunctions: - active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] - allowedFunctionsPerFile: 20 - allowedFunctionsPerClass: 20 - allowedFunctionsPerInterface: 20 - allowedFunctionsPerObject: 20 - allowedFunctionsPerEnum: 5 - ignoreDeprecated: true - ignorePrivate: false - ignoreInternal: false - ignoreOverridden: false - ignoreAnnotatedFunctions: [] - -coroutines: - active: true - CoroutineLaunchedInTestWithoutRunTest: - active: false - GlobalCoroutineUsage: - active: true - InjectDispatcher: - active: false - dispatcherNames: - - 'IO' - - 'Default' - - 'Unconfined' - RedundantSuspendModifier: - active: true - SleepInsteadOfDelay: - active: true - SuspendFunInFinallySection: - active: false - SuspendFunSwallowedCancellation: - active: false - SuspendFunWithCoroutineScopeReceiver: - active: true - aliases: ['SuspendFunctionOnCoroutineScope'] - SuspendFunWithFlowReturnType: - active: true - -empty-blocks: - active: true - EmptyCatchBlock: - active: true - allowedExceptionNameRegex: '_|(ignore|expected).*' - EmptyClassBlock: - active: true - EmptyDefaultConstructor: - active: true - EmptyDoWhileBlock: - active: true - EmptyElseBlock: - active: true - EmptyFinallyBlock: - active: true - EmptyForBlock: - active: true - EmptyFunctionBlock: - active: false - ignoreOverridden: false - EmptyIfBlock: - active: true - EmptyInitBlock: - active: true - EmptyKotlinFile: - active: true - EmptySecondaryConstructor: - active: true - EmptyTryBlock: - active: true - EmptyWhenBlock: - active: true - EmptyWhileBlock: - active: true - -exceptions: - active: true - ErrorUsageWithThrowable: - active: false - ExceptionRaisedInUnexpectedLocation: - active: true - methodNames: - - 'equals' - - 'finalize' - - 'hashCode' - - 'toString' - InstanceOfCheckForException: - active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] - NotImplementedDeclaration: - active: false - ObjectExtendsThrowable: - active: true - PrintStackTrace: - active: true - RethrowCaughtException: - active: true - ReturnFromFinally: - active: true - ignoreLabeled: false - SwallowedException: - active: true - ignoredExceptionTypes: - - 'InterruptedException' - - 'MalformedURLException' - - 'NumberFormatException' - - 'ParseException' - allowedExceptionNameRegex: '_|(ignore|expected).*' - ThrowingExceptionFromFinally: - active: true - ThrowingExceptionInMain: - active: false - ThrowingExceptionsWithoutMessageOrCause: - active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] - exceptions: - - 'ArrayIndexOutOfBoundsException' - - 'Exception' - - 'IllegalArgumentException' - - 'IllegalMonitorStateException' - - 'IllegalStateException' - - 'IndexOutOfBoundsException' - - 'NullPointerException' - - 'RuntimeException' - - 'Throwable' - ThrowingNewInstanceOfSameException: - active: true - TooGenericExceptionCaught: - active: false - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] - exceptionNames: - - 'ArrayIndexOutOfBoundsException' - - 'Error' - - 'Exception' - - 'IllegalMonitorStateException' - - 'IndexOutOfBoundsException' - - 'NullPointerException' - - 'RuntimeException' - - 'Throwable' - allowedExceptionNameRegex: '_|(ignore|expected).*' - TooGenericExceptionThrown: - active: true - exceptionNames: - - 'Error' - - 'Exception' - - 'RuntimeException' - - 'Throwable' - -naming: - active: true - BooleanPropertyNaming: - active: false - allowedPattern: '^(is|has|are)' - ClassNaming: - active: true - aliases: ['ClassName'] - classPattern: '[A-Z][a-zA-Z0-9]*' - ConstructorParameterNaming: - active: true - parameterPattern: '[a-z][A-Za-z0-9]*' - privateParameterPattern: '[a-z][A-Za-z0-9]*' - excludeClassPattern: '$^' - EnumNaming: - active: true - aliases: ['EnumEntryName'] - enumEntryPattern: '[A-Z][_a-zA-Z0-9]*' - ForbiddenClassName: - active: false - forbiddenName: [] - FunctionNameMaxLength: - active: false - aliases: ['FunctionMaxNameLength'] - maximumFunctionNameLength: 30 - FunctionNameMinLength: - active: false - aliases: ['FunctionMinNameLength'] - minimumFunctionNameLength: 3 - FunctionNaming: - active: true - aliases: ['FunctionName'] - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] - functionPattern: '[a-z][a-zA-Z0-9]*' - excludeClassPattern: '$^' - FunctionParameterNaming: - active: true - parameterPattern: '[a-z][A-Za-z0-9]*' - excludeClassPattern: '$^' - InvalidPackageDeclaration: - active: true - aliases: ['PackageDirectoryMismatch'] - rootPackage: '' - requireRootInDeclaration: false - LambdaParameterNaming: - active: false - parameterPattern: '[a-z][A-Za-z0-9]*|_' - MatchingDeclarationName: - active: true - mustBeFirst: true - multiplatformTargets: - - 'ios' - - 'android' - - 'js' - - 'jvm' - - 'native' - - 'iosArm64' - - 'iosX64' - - 'macosX64' - - 'mingwX64' - - 'linuxX64' - MemberNameEqualsClassName: - active: true - ignoreOverridden: true - NoNameShadowing: - active: true - NonBooleanPropertyPrefixedWithIs: - active: true - ObjectPropertyNaming: - active: true - aliases: ['ObjectPropertyName'] - constantPattern: '[A-Za-z][_A-Za-z0-9]*' - propertyPattern: '[A-Za-z][_A-Za-z0-9]*' - privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*' - PackageNaming: - active: true - aliases: ['PackageName'] - packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*' - TopLevelPropertyNaming: - active: true - constantPattern: '[A-Z][_A-Z0-9]*' - propertyPattern: '[A-Za-z][_A-Za-z0-9]*' - privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*' - VariableMaxLength: - active: false - maximumVariableNameLength: 64 - VariableMinLength: - active: false - minimumVariableNameLength: 1 - VariableNaming: - active: true - aliases: ['PropertyName'] - variablePattern: '[a-z][A-Za-z0-9]*' - privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*' - excludeClassPattern: '$^' - -performance: - active: true - ArrayPrimitive: - active: true - CouldBeSequence: - active: false - allowedOperations: 3 - ForEachOnRange: - active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] - SpreadOperator: - active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] - UnnecessaryPartOfBinaryExpression: - active: false - UnnecessaryTemporaryInstantiation: - active: true - UnnecessaryTypeCasting: - active: false - -potential-bugs: - active: true - AvoidReferentialEquality: - active: true - forbiddenTypePatterns: - - 'kotlin.String' - CastNullableToNonNullableType: - active: true - ignorePlatformTypes: true - CastToNullableType: - active: false - CharArrayToStringCall: - active: false - Deprecation: - active: false - aliases: ['DEPRECATION'] - excludeImportStatements: false - DontDowncastCollectionTypes: - active: false - DoubleMutabilityForCollection: - active: true - aliases: ['DoubleMutability'] - mutableTypes: - - 'kotlin.collections.MutableList' - - 'kotlin.collections.MutableMap' - - 'kotlin.collections.MutableSet' - - 'java.util.ArrayList' - - 'java.util.LinkedHashSet' - - 'java.util.HashSet' - - 'java.util.LinkedHashMap' - - 'java.util.HashMap' - ElseCaseInsteadOfExhaustiveWhen: - active: false - ignoredSubjectTypes: [] - EqualsAlwaysReturnsTrueOrFalse: - active: true - EqualsWithHashCodeExist: - active: true - ExitOutsideMain: - active: false - ExplicitGarbageCollectionCall: - active: true - HasPlatformType: - active: true - IgnoredReturnValue: - active: true - restrictToConfig: true - returnValueAnnotations: - - 'CheckResult' - - '*.CheckResult' - - 'CheckReturnValue' - - '*.CheckReturnValue' - ignoreReturnValueAnnotations: - - 'CanIgnoreReturnValue' - - '*.CanIgnoreReturnValue' - returnValueTypes: - - 'kotlin.Function*' - - 'kotlin.sequences.Sequence' - - 'kotlinx.coroutines.flow.*Flow' - - 'java.util.stream.*Stream' - ignoreFunctionCall: [] - ImplicitDefaultLocale: - active: true - ImplicitUnitReturnType: - active: false - allowExplicitReturnType: true - InvalidRange: - active: true - IteratorHasNextCallsNextMethod: - active: true - IteratorNotThrowingNoSuchElementException: - active: true - LateinitUsage: - active: false - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] - ignoreOnClassesPattern: '' - MapGetWithNotNullAssertionOperator: - active: true - MissingPackageDeclaration: - active: true - excludes: ['**/*.kts'] - MissingSuperCall: - active: false - mustInvokeSuperAnnotations: - - 'androidx.annotation.CallSuper' - - 'javax.annotation.OverridingMethodsMustInvokeSuper' - MissingUseCall: - active: false - NullCheckOnMutableProperty: - active: false - NullableToStringCall: - active: false - PropertyUsedBeforeDeclaration: - active: false - UnconditionalJumpStatementInLoop: - active: false - UnnamedParameterUse: - active: false - allowAdjacentDifferentTypeParams: true - allowSingleParamUse: true - ignoreArgumentsMatchingNames: true - ignoreFunctionCall: [] - UnnecessaryNotNullCheck: - active: true - UnnecessaryNotNullOperator: - active: true - UnnecessarySafeCall: - active: true - UnreachableCatchBlock: - active: true - UnreachableCode: - active: true - UnsafeCallOnNullableType: - active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] - UnsafeCast: - active: true - aliases: ['UNCHECKED_CAST'] - UnusedUnaryOperator: - active: true - UselessPostfixExpression: - active: true - WrongEqualsTypeParameter: - active: true - -style: - active: true - AbstractClassCanBeConcreteClass: - active: true - AbstractClassCanBeInterface: - active: true - AlsoCouldBeApply: - active: false - BracesOnIfStatements: - active: true - singleLine: 'never' - multiLine: 'always' - BracesOnWhenStatements: - active: true - singleLine: 'necessary' - multiLine: 'consistent' - CanBeNonNullable: - active: true - CascadingCallWrapping: - active: false - includeElvis: true - ClassOrdering: - active: false - CollapsibleIfStatements: - active: true - DataClassContainsFunctions: - active: false - conversionFunctionPrefix: - - 'to' - allowOperators: false - DataClassShouldBeImmutable: - active: false - DestructuringDeclarationWithTooManyEntries: - active: true - maxDestructuringEntries: 3 - DoubleNegativeExpression: - active: false - DoubleNegativeLambda: - active: false - negativeFunctions: - - reason: 'Use `takeIf` instead.' - value: 'takeUnless' - - reason: 'Use `all` instead.' - value: 'none' - negativeFunctionNameParts: - - 'not' - - 'non' - EqualsNullCall: - active: true - EqualsOnSignatureLine: - active: true - ExplicitCollectionElementAccessMethod: - active: false - ExplicitItLambdaMultipleParameters: - active: true - ExplicitItLambdaParameter: - active: true - ExpressionBodySyntax: - active: false - includeLineWrapping: false - ForbiddenAnnotation: - active: true - annotations: - - reason: 'it is a java annotation. Use `Suppress` instead.' - value: 'java.lang.SuppressWarnings' - - reason: 'it is a java annotation. Use `kotlin.Deprecated` instead.' - value: 'java.lang.Deprecated' - - reason: 'it is a java annotation. Use `kotlin.annotation.MustBeDocumented` instead.' - value: 'java.lang.annotation.Documented' - - reason: 'it is a java annotation. Use `kotlin.annotation.Target` instead.' - value: 'java.lang.annotation.Target' - - reason: 'it is a java annotation. Use `kotlin.annotation.Retention` instead.' - value: 'java.lang.annotation.Retention' - - reason: 'it is a java annotation. Use `kotlin.annotation.Repeatable` instead.' - value: 'java.lang.annotation.Repeatable' - - reason: 'Kotlin does not support @Inherited annotation, see https://youtrack.jetbrains.com/issue/KT-22265' - value: 'java.lang.annotation.Inherited' - ForbiddenComment: - active: true - comments: - - reason: 'Forbidden FIXME todo marker in comment, please fix the problem.' - value: 'FIXME:' - - reason: 'Forbidden STOPSHIP todo marker in comment, please address the problem before shipping the code.' - value: 'STOPSHIP:' - - reason: 'Forbidden TODO todo marker in comment, please do the changes.' - value: 'TODO:' - allowedPatterns: '' - ForbiddenImport: - active: false - imports: [] - forbiddenPatterns: '' - ForbiddenMethodCall: - active: true - methods: - - reason: 'print does not allow you to configure the output stream. Use a logger instead.' - value: 'kotlin.io.print' - - reason: 'println does not allow you to configure the output stream. Use a logger instead.' - value: 'kotlin.io.println' - - reason: 'using `BigDecimal(Double)` can result in unexpected floating point precision behavior. Use `BigDecimal.valueOf(Double)` or `String.toBigDecimalOrNull()` instead.' - value: 'java.math.BigDecimal.(kotlin.Double)' - - reason: 'using `BigDecimal(String)` can result in a `NumberFormatException`. Use `String.toBigDecimalOrNull()`' - value: 'java.math.BigDecimal.(kotlin.String)' - - reason: 'It is marked as obsolete. Use `kotlin.time.measureTime` instead.' - value: 'kotlin.system.measureTimeMillis' - ForbiddenNamedParam: - active: false - methods: [] - ForbiddenOptIn: - active: false - markerClasses: [] - ForbiddenSuppress: - active: false - rules: [] - ForbiddenVoid: - active: true - ignoreOverridden: false - ignoreUsageInGenerics: false - FunctionOnlyReturningConstant: - active: true - ignoreOverridableFunction: true - ignoreActualFunction: true - excludedFunctions: [] - LoopWithTooManyJumpStatements: - active: true - maxJumpCount: 3 - MagicNumber: - active: false - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/*.kts'] - ignoreNumbers: - - '-1' - - '0' - - '1' - - '2' - ignoreHashCodeFunction: true - ignorePropertyDeclaration: false - ignoreLocalVariableDeclaration: false - ignoreConstantDeclaration: true - ignoreCompanionObjectPropertyDeclaration: true - ignoreAnnotation: false - ignoreNamedArgument: true - ignoreEnums: false - ignoreRanges: false - ignoreExtensionFunctions: true - MandatoryBracesLoops: - active: true - MaxChainedCallsOnSameLine: - active: true - maxChainedCalls: 5 - MaxLineLength: - active: true - maxLineLength: 120 - excludePackageStatements: true - excludeImportStatements: true - excludeCommentStatements: false - excludeRawStrings: true - MayBeConstant: - active: true - ModifierOrder: - active: true - MultilineLambdaItParameter: - active: true - MultilineRawStringIndentation: - active: false - indentSize: 4 - trimmingMethods: - - 'trimIndent' - - 'trimMargin' - NestedClassesVisibility: - active: true - NewLineAtEndOfFile: - active: true - NoTabs: - active: false - NullableBooleanCheck: - active: false - ObjectLiteralToLambda: - active: true - OptionalAbstractKeyword: - active: true - OptionalUnit: - active: false - ProtectedMemberInFinalClass: - active: true - RangeUntilInsteadOfRangeTo: - active: false - RedundantConstructorKeyword: - active: false - RedundantExplicitType: - active: false - RedundantHigherOrderMapUsage: - active: true - RedundantVisibilityModifier: - active: false - ReturnCount: - active: true - max: 6 - excludedFunctions: - - 'equals' - excludeLabeled: false - excludeReturnFromLambda: true - excludeGuardClauses: false - SafeCast: - active: true - SerialVersionUIDInSerializableClass: - active: true - SpacingAfterPackageDeclaration: - active: true - StringShouldBeRawString: - active: false - maxEscapedCharacterCount: 2 - ignoredCharacters: [] - ThrowsCount: - active: true - max: 3 - excludeGuardClauses: false - TrailingWhitespace: - active: false - TrimMultilineRawString: - active: false - trimmingMethods: - - 'trimIndent' - - 'trimMargin' - UnderscoresInNumericLiterals: - active: false - acceptableLength: 4 - allowNonStandardGrouping: false - UnnecessaryAnnotationUseSiteTarget: - active: false - UnnecessaryAny: - active: true - UnnecessaryApply: - active: true - UnnecessaryBackticks: - active: false - UnnecessaryBracesAroundTrailingLambda: - active: false - UnnecessaryFilter: - active: true - UnnecessaryInheritance: - active: true - UnnecessaryInnerClass: - active: false - UnnecessaryLet: - active: false - UnnecessaryParentheses: - active: false - allowForUnclearPrecedence: false - UnnecessaryReversed: - active: false - UnusedImport: - active: true - additionalOperatorSet: [] - UnusedParameter: - active: true - aliases: ['UNUSED_PARAMETER', 'unused'] - allowedNames: 'ignored|expected' - UnusedPrivateClass: - active: true - aliases: ['unused'] - UnusedPrivateFunction: - active: true - aliases: ['unused'] - allowedNames: '' - UnusedPrivateProperty: - active: true - aliases: ['unused'] - allowedNames: 'ignored|expected|serialVersionUID' - UnusedVariable: - active: true - aliases: ['UNUSED_VARIABLE', 'unused'] - allowedNames: 'ignored|_' - UseAnyOrNoneInsteadOfFind: - active: true - UseArrayLiteralsInAnnotations: - active: true - UseCheckNotNull: - active: true - UseCheckOrError: - active: true - UseDataClass: - active: false - allowVars: false - UseEmptyCounterpart: - active: true - UseIfEmptyOrIfBlank: - active: false - UseIfInsteadOfWhen: - active: false - ignoreWhenContainingVariableDeclaration: false - UseIsNullOrEmpty: - active: true - UseLet: - active: false - UseOrEmpty: - active: true - UseRequire: - active: true - UseRequireNotNull: - active: true - UseSumOfInsteadOfFlatMapSize: - active: false - UselessCallOnNotNull: - active: true - UtilityClassWithPublicConstructor: - active: true - VarCouldBeVal: - active: true - aliases: ['CanBeVal'] - ignoreLateinitVar: false - WildcardImport: - active: false - excludeImports: - - 'java.util.*' diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3dca4cf..b9ef64f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ # plugins version atomicfu = "0.31.0" binaryCompatibilityValidatorPlugin = "0.18.1" -detekt = "2.0.0-alpha.1" +detekt = "2.0.0-alpha.2" dokka = "2.1.0" kotlin = "2.2.21" kover = "0.9.4" From ed9ad832905b44279482bb7563d1e0583716605d Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Fri, 13 Feb 2026 22:17:27 +0200 Subject: [PATCH 146/449] refactor: Harden detekt setup, address some issues (#507) # Detekt setup hardeing; address some issues - Address some detekt findings: missing KDocs, formatting, use `check*` & `orEmpty()`` - Remove unnecessary KDoc comments from deprecated classes in `types.kt` - Add Detekt baseline XML files for all modules - Enforce stricter Detekt rules by setting `failOnSeverity` from `None` to `Error` ## Motivation and Context Detekt is not actually failing the build on violations. This PR is enabling Detekt to fail on errors. Baseline fines added for historical issues. Follow-up for #501 ## How Has This Been Tested? Regressions ## Breaking Changes No breaking changes. ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] Refactoring - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] 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 --- build.gradle.kts | 2 +- conformance-test/detekt-baseline.xml | 9 +++ .../sdk/conformance/ConformanceServer.kt | 6 +- .../kotlin/sdk/conformance/ConformanceTest.kt | 9 ++- .../conformance/WebSocketConformanceClient.kt | 1 + .../detekt-baseline-commonTestSourceSet.xml | 11 ++++ integration-test/detekt-baseline-test.xml | 47 ++++++++++++++ .../kotlin/sdk/shared/InMemoryTransport.kt | 2 +- .../kotlin/AbstractPromptIntegrationTest.kt | 2 +- .../server/ServerPromptsNotificationTest.kt | 12 ++-- .../kotlin/sdk/server/ServerPromptsTest.kt | 6 +- ...erverResourcesNotificationSubscribeTest.kt | 12 ++-- .../server/ServerResourcesNotificationTest.kt | 4 +- .../sdk/server/ServerToolsNotificationTest.kt | 12 ++-- .../detekt-baseline-commonMainSourceSet.xml | 14 ++++ .../detekt-baseline-commonTestSourceSet.xml | 9 +++ kotlin-sdk-client/detekt-baseline-main.xml | 19 ++++++ kotlin-sdk-client/detekt-baseline-test.xml | 18 +++++ .../kotlin/sdk/client/Client.kt | 65 +++++++++---------- .../client/StreamableHttpClientTransport.kt | 4 ++ .../sdk/client/WebSocketClientTransport.kt | 4 +- .../detekt-baseline-commonMainSourceSet.xml | 27 ++++++++ .../detekt-baseline-commonTestSourceSet.xml | 12 ++++ kotlin-sdk-core/detekt-baseline-main.xml | 32 +++++++++ kotlin-sdk-core/detekt-baseline-test.xml | 12 ++++ .../modelcontextprotocol/kotlin/sdk/types.kt | 63 +++++++----------- .../kotlin/sdk/types/completion.dsl.kt | 3 +- .../kotlin/sdk/types/jsonRpc.kt | 14 ++++ .../kotlin/sdk/types/jsonUtils.kt | 15 ++++- .../kotlin/sdk/types/notification.kt | 3 +- .../kotlin/sdk/types/request.kt | 6 ++ .../kotlin/sdk/types/RequestTest.kt | 5 +- .../sdk/types/dsl/PingRequestDslTest.kt | 8 ++- .../detekt-baseline-commonMainSourceSet.xml | 21 ++++++ kotlin-sdk-server/detekt-baseline-main.xml | 51 +++++++++++++++ .../kotlin/sdk/server/KtorServer.kt | 8 ++- .../server/StreamableHttpServerTransport.kt | 37 +++++++---- .../WebSocketMcpKtorServerExtensions.kt | 2 + test-utils/detekt-baseline.xml | 9 +++ .../kotlin/test/utils/TypeScriptRunner.kt | 3 +- .../kotlin/test/utils/processUtils.kt | 1 + 41 files changed, 478 insertions(+), 122 deletions(-) create mode 100644 conformance-test/detekt-baseline.xml create mode 100644 integration-test/detekt-baseline-commonTestSourceSet.xml create mode 100644 integration-test/detekt-baseline-test.xml create mode 100644 kotlin-sdk-client/detekt-baseline-commonMainSourceSet.xml create mode 100644 kotlin-sdk-client/detekt-baseline-commonTestSourceSet.xml create mode 100644 kotlin-sdk-client/detekt-baseline-main.xml create mode 100644 kotlin-sdk-client/detekt-baseline-test.xml create mode 100644 kotlin-sdk-core/detekt-baseline-commonMainSourceSet.xml create mode 100644 kotlin-sdk-core/detekt-baseline-commonTestSourceSet.xml create mode 100644 kotlin-sdk-core/detekt-baseline-main.xml create mode 100644 kotlin-sdk-core/detekt-baseline-test.xml create mode 100644 kotlin-sdk-server/detekt-baseline-commonMainSourceSet.xml create mode 100644 kotlin-sdk-server/detekt-baseline-main.xml create mode 100644 test-utils/detekt-baseline.xml diff --git a/build.gradle.kts b/build.gradle.kts index dc72359..57dd810 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -26,7 +26,7 @@ subprojects { detekt { config = files("$rootDir/config/detekt/detekt.yml") buildUponDefaultConfig = true - failOnSeverity.set(FailOnSeverity.Never) + failOnSeverity.set(FailOnSeverity.Error) } } diff --git a/conformance-test/detekt-baseline.xml b/conformance-test/detekt-baseline.xml new file mode 100644 index 0000000..fe120f5 --- /dev/null +++ b/conformance-test/detekt-baseline.xml @@ -0,0 +1,9 @@ + + + + + BracesOnWhenStatements:ConformanceServer.kt:HttpServerTransport$when + BracesOnWhenStatements:ConformanceTest.kt:ConformanceTest$when + ForbiddenComment:ConformanceTest.kt:ConformanceTest.Companion$// TODO: Fix + + diff --git a/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceServer.kt b/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceServer.kt index 402cf81..b3331dd 100644 --- a/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceServer.kt +++ b/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceServer.kt @@ -73,6 +73,7 @@ private const val MESSAGE_QUEUE_CAPACITY = 256 private fun isInitializeRequest(json: JsonElement): Boolean = json is JsonObject && json["method"]?.jsonPrimitive?.contentOrNull == "initialize" +@Suppress("CyclomaticComplexMethod", "LongMethod") fun main(args: Array) { val port = args.getOrNull(0)?.toIntOrNull() ?: 3000 @@ -236,6 +237,7 @@ fun main(args: Array) { }.start(wait = true) } +@Suppress("LongMethod") private fun createConformanceServer(): Server { val server = Server( Implementation( @@ -371,7 +373,9 @@ private class HttpServerTransport(private val sessionId: String) : AbstractTrans } } - else -> call.respond(HttpStatusCode.Accepted) + else -> { + call.respond(HttpStatusCode.Accepted) + } } } catch (e: CancellationException) { throw e diff --git a/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTest.kt b/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTest.kt index 7754ff3..41c9c3c 100644 --- a/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTest.kt +++ b/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTest.kt @@ -17,6 +17,7 @@ import java.net.URI import java.util.concurrent.TimeUnit import kotlin.io.path.createTempFile import kotlin.properties.Delegates +import kotlin.test.fail private val logger = KotlinLogging.logger {} @@ -320,7 +321,8 @@ class ConformanceTest { } process.destroyForcibly() throw AssertionError( - "❌ $capitalizedType conformance test [$transportType] '$scenario' timed out after $timeoutSeconds seconds", + "❌ $capitalizedType conformance test [$transportType] '$scenario' " + + "timed out after $timeoutSeconds seconds", ) } @@ -331,8 +333,9 @@ class ConformanceTest { logger.error { "$capitalizedType conformance test [$transportType] '$scenario' failed with exit code: $exitCode" } - throw AssertionError( - "❌ $capitalizedType conformance test [$transportType] '$scenario' failed (exit code: $exitCode). Check test output above for details.", + fail( + "❌ $capitalizedType conformance test [$transportType] '$scenario' " + + "failed (exit code: $exitCode). Check test output above for details.", ) } } diff --git a/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/WebSocketConformanceClient.kt b/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/WebSocketConformanceClient.kt index f112319..f385ddd 100644 --- a/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/WebSocketConformanceClient.kt +++ b/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/WebSocketConformanceClient.kt @@ -24,6 +24,7 @@ class WebSocketClientTransport(override val session: WebSocketSession) : WebSock } } +@Suppress("LongMethod") fun main(args: Array) { require(args.isNotEmpty()) { "Server WebSocket URL must be provided as an argument" diff --git a/integration-test/detekt-baseline-commonTestSourceSet.xml b/integration-test/detekt-baseline-commonTestSourceSet.xml new file mode 100644 index 0000000..660fa66 --- /dev/null +++ b/integration-test/detekt-baseline-commonTestSourceSet.xml @@ -0,0 +1,11 @@ + + + + + EmptyFunctionBlock:ClientTest.kt:ClientTest.<no name provided>${ } + EmptyFunctionBlock:ClientTest.kt:ClientTest.<no name provided>${} + LargeClass:ClientTest.kt:ClientTest + LongMethod:ClientTest.kt:ClientTest$@Test fun `JSONRPCRequest with ToolsList method and default params returns list of tools` + LongMethod:ClientTest.kt:ClientTest$@Test fun `should handle logging setLevel request` + + diff --git a/integration-test/detekt-baseline-test.xml b/integration-test/detekt-baseline-test.xml new file mode 100644 index 0000000..46849e9 --- /dev/null +++ b/integration-test/detekt-baseline-test.xml @@ -0,0 +1,47 @@ + + + + + AbstractClassCanBeConcreteClass:AbstractPromptIntegrationTest.kt:AbstractPromptIntegrationTest$AbstractPromptIntegrationTest + AbstractClassCanBeConcreteClass:AbstractResourceIntegrationTest.kt:AbstractResourceIntegrationTest$AbstractResourceIntegrationTest + AbstractClassCanBeConcreteClass:AbstractToolIntegrationTest.kt:AbstractToolIntegrationTest$AbstractToolIntegrationTest + AbstractClassCanBeConcreteClass:BaseTransportTest.kt:BaseTransportTest$BaseTransportTest + CyclomaticComplexMethod:AbstractToolIntegrationTest.kt:AbstractToolIntegrationTest$private fun setupCalculatorTool + EmptyFunctionBlock:ClientTest.kt:ClientTest.<no name provided>${ } + EmptyFunctionBlock:ClientTest.kt:ClientTest.<no name provided>${} + ForbiddenComment:StdioClientTransportTest.kt:StdioClientTransportTest$// TODO: fix running on windows + InjectDispatcher:AbstractKotlinClientTsServerTest.kt:AbstractKotlinClientTsServerTest$IO + InjectDispatcher:AbstractPromptIntegrationTest.kt:AbstractPromptIntegrationTest$IO + InjectDispatcher:AbstractResourceIntegrationTest.kt:AbstractResourceIntegrationTest$IO + InjectDispatcher:AbstractToolIntegrationTest.kt:AbstractToolIntegrationTest$IO + InjectDispatcher:KotlinClientTsServerEdgeCasesTestSse.kt:KotlinClientTsServerEdgeCasesTestSse$IO + InjectDispatcher:KotlinClientTsServerEdgeCasesTestStdio.kt:KotlinClientTsServerEdgeCasesTestStdio$IO + InjectDispatcher:SseIntegrationTest.kt:SseIntegrationTest$IO + InjectDispatcher:StdioClientTransportTest.kt:StdioClientTransportTest$IO + InjectDispatcher:StreamableHttpIntegrationTest.kt:StreamableHttpIntegrationTest$IO + InjectDispatcher:TsEdgeCasesTestSse.kt:TsEdgeCasesTestSse$IO + InjectDispatcher:WebSocketIntegrationTest.kt:WebSocketIntegrationTest$IO + LargeClass:AbstractToolIntegrationTest.kt:AbstractToolIntegrationTest : KotlinTestBase + LargeClass:ClientTest.kt:ClientTest + LongMethod:AbstractPromptIntegrationTest.kt:AbstractPromptIntegrationTest$@Test fun testMissingRequiredArguments + LongMethod:AbstractPromptIntegrationTest.kt:AbstractPromptIntegrationTest$override fun configureServer + LongMethod:AbstractResourceIntegrationTest.kt:AbstractResourceIntegrationTest$override fun configureServer + LongMethod:AbstractToolIntegrationTest.kt:AbstractToolIntegrationTest$private fun setupCalculatorTool + LongMethod:ClientTest.kt:ClientTest$@Test fun `JSONRPCRequest with ToolsList method and default params returns list of tools` + LongMethod:ClientTest.kt:ClientTest$@Test fun `should handle logging setLevel request` + LongMethod:KotlinServerForTsClientSse.kt:HttpServerTransport$suspend fun handleRequest + LongMethod:KotlinServerForTsClientSse.kt:KotlinServerForTsClient$fun createMcpServer: Server + LongMethod:KotlinServerForTsClientSse.kt:KotlinServerForTsClient$fun start + LongMethod:KotlinTestBase.kt:KotlinTestBase$protected fun setupServer + LongMethod:ServerResourcesNotificationSubscribeTest.kt:ServerResourcesNotificationSubscribeTest$@Test fun `should send resource notifications` + LongMethod:TsEdgeCasesTestSse.kt:TsEdgeCasesTestSse$@Test @Timeout(30, unit = TimeUnit.SECONDS) fun testComplexConcurrentRequests: Unit + MatchingDeclarationName:PromptIntegrationTestSse.kt:SchemaPromptIntegrationTestSse : AbstractPromptIntegrationTest + SleepInsteadOfDelay:KotlinServerForTsClientSse.kt:KotlinServerForTsClient$sleep(500) + ThrowsCount:AbstractPromptIntegrationTest.kt:AbstractPromptIntegrationTest$override fun configureServer + UnusedPrivateProperty:KotlinClientTsServerTestSse.kt:KotlinClientTsServerTestSse$private val host = "localhost" + UseCheckOrError:ClientTest.kt:ClientTest.<no name provided>$throw IllegalStateException("Test error") + UseOrEmpty:AbstractToolIntegrationTest.kt:AbstractToolIntegrationTest$(request.params.arguments?.get("tags") as? JsonArray)?.mapNotNull { (it as? JsonPrimitive)?.content } ?: emptyList() + UseRequire:AbstractPromptIntegrationTest.kt:AbstractPromptIntegrationTest$throw IllegalArgumentException("Missing required argument: $argName") + VarCouldBeVal:KotlinClientTsServerEdgeCasesTestSse.kt:KotlinClientTsServerEdgeCasesTestSse$private lateinit var client: Client + + diff --git a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/InMemoryTransport.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/InMemoryTransport.kt index ae760f1..2c15635 100644 --- a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/InMemoryTransport.kt +++ b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/InMemoryTransport.kt @@ -40,7 +40,7 @@ class InMemoryTransport : AbstractTransport() { } override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { - val other = otherTransport ?: throw IllegalStateException("Not connected") + val other = checkNotNull(otherTransport) { "Not connected" } other._onMessage.invoke(message) } diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt index fbaca72..9380401 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt @@ -266,7 +266,7 @@ abstract class AbstractPromptIntegrationTest : KotlinTestBase() { ), ), ) { request -> - val args = request.params.arguments ?: emptyMap() + val args = request.params.arguments.orEmpty() val arg1 = args["requiredArg1"] ?: throw IllegalArgumentException( "Missing required argument: requiredArg1", ) diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsNotificationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsNotificationTest.kt index 3674401..08687d9 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsNotificationTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsNotificationTest.kt @@ -24,8 +24,10 @@ class ServerPromptsNotificationTest : AbstractServerFeaturesTest() { fun `addPrompt should send notification`() = runTest { // Configure notification handler val notifications = mutableListOf() - client.setNotificationHandler(Method.Defined.NotificationsPromptsListChanged) { - notifications.add(it) + client.setNotificationHandler( + Method.Defined.NotificationsPromptsListChanged, + ) { notification -> + notifications.add(notification) CompletableDeferred(Unit) } @@ -96,8 +98,10 @@ class ServerPromptsNotificationTest : AbstractServerFeaturesTest() { fun `notification should not be send when removed prompt does not exists`() = runTest { // Track notifications val notifications = mutableListOf() - client.setNotificationHandler(Method.Defined.NotificationsPromptsListChanged) { - notifications.add(it) + client.setNotificationHandler( + Method.Defined.NotificationsPromptsListChanged, + ) { notification -> + notifications.add(notification) CompletableDeferred(Unit) } diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsTest.kt index 5f49d04..d7ae46d 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsTest.kt @@ -34,7 +34,7 @@ class ServerPromptsTest : AbstractServerFeaturesTest() { server.addPrompt(testPrompt) { GetPromptResult( description = "Test prompt description", - messages = listOf(), + messages = emptyList(), ) } @@ -70,13 +70,13 @@ class ServerPromptsTest : AbstractServerFeaturesTest() { server.addPrompt(testPrompt1) { GetPromptResult( description = "Test prompt description 1", - messages = listOf(), + messages = emptyList(), ) } server.addPrompt(testPrompt2) { GetPromptResult( description = "Test prompt description 2", - messages = listOf(), + messages = emptyList(), ) } diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationSubscribeTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationSubscribeTest.kt index d9f9250..06fa962 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationSubscribeTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationSubscribeTest.kt @@ -50,12 +50,12 @@ class ServerResourcesNotificationSubscribeTest : AbstractServerFeaturesTest() { name = "Test Resource 1", description = "A test resource 1", mimeType = "text/plain", - ) { + ) { request -> ReadResourceResult( contents = listOf( TextResourceContents( text = "Test resource content 1", - uri = it.uri, + uri = request.uri, mimeType = "text/plain", ), ), @@ -67,12 +67,12 @@ class ServerResourcesNotificationSubscribeTest : AbstractServerFeaturesTest() { name = "Test Resource 2", description = "A test resource 2", mimeType = "text/plain", - ) { + ) { request -> ReadResourceResult( contents = listOf( TextResourceContents( text = "Test resource content 2", - uri = it.uri, + uri = request.uri, mimeType = "text/plain", ), ), @@ -121,8 +121,8 @@ class ServerResourcesNotificationSubscribeTest : AbstractServerFeaturesTest() { println("Thread ${Thread.currentThread().name} test 1") // Track notifications val notifications = ConcurrentLinkedQueue() - client.setNotificationHandler(NotificationsResourcesUpdated) { - notifications.add(it) + client.setNotificationHandler(NotificationsResourcesUpdated) { notification -> + notifications.add(notification) CompletableDeferred(Unit) } diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationTest.kt index 6337479..f007723 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesNotificationTest.kt @@ -26,8 +26,8 @@ class ServerResourcesNotificationTest : AbstractServerFeaturesTest() { val notifications = mutableListOf() client.setNotificationHandler( Method.Defined.NotificationsResourcesListChanged, - ) { - notifications.add(it) + ) { notification -> + notifications.add(notification) CompletableDeferred(Unit) } diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsNotificationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsNotificationTest.kt index 4cda75f..3572082 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsNotificationTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsNotificationTest.kt @@ -51,8 +51,10 @@ class ServerToolsNotificationTest : AbstractServerFeaturesTest() { fun `removeTools should remove multiple tools and send two notifications`() = runTest { // Configure notification handler val notifications = mutableListOf() - client.setNotificationHandler(Method.Defined.NotificationsToolsListChanged) { - notifications.add(it) + client.setNotificationHandler( + Method.Defined.NotificationsToolsListChanged, + ) { notification -> + notifications.add(notification) CompletableDeferred(Unit) } @@ -83,8 +85,10 @@ class ServerToolsNotificationTest : AbstractServerFeaturesTest() { fun `notification should not be send when removed tool does not exists`() = runTest { // Track notifications val notifications = mutableListOf() - client.setNotificationHandler(Method.Defined.NotificationsToolsListChanged) { - notifications.add(it) + client.setNotificationHandler( + Method.Defined.NotificationsToolsListChanged, + ) { notification -> + notifications.add(notification) CompletableDeferred(Unit) } diff --git a/kotlin-sdk-client/detekt-baseline-commonMainSourceSet.xml b/kotlin-sdk-client/detekt-baseline-commonMainSourceSet.xml new file mode 100644 index 0000000..5cddbe1 --- /dev/null +++ b/kotlin-sdk-client/detekt-baseline-commonMainSourceSet.xml @@ -0,0 +1,14 @@ + + + + + CyclomaticComplexMethod:StdioClientTransport.kt:StdioClientTransport$override suspend fun initialize + LongMethod:StdioClientTransport.kt:StdioClientTransport$override suspend fun initialize + MaxLineLength:StdioClientTransport.kt:StdioClientTransport$* + ThrowsCount:SseClientTransport.kt:SseClientTransport$private suspend fun CoroutineScope.collectMessages + ThrowsCount:StreamableHttpClientTransport.kt:StreamableHttpClientTransport$@Suppress("ReturnCount", "CyclomaticComplexMethod") override suspend fun performSend + TooGenericExceptionCaught:Client.kt:Client$error: Throwable + TooGenericExceptionCaught:SseClientTransport.kt:SseClientTransport$e: Throwable + TooGenericExceptionCaught:StdioClientTransport.kt:StdioClientTransport$e: Throwable + + diff --git a/kotlin-sdk-client/detekt-baseline-commonTestSourceSet.xml b/kotlin-sdk-client/detekt-baseline-commonTestSourceSet.xml new file mode 100644 index 0000000..b73eb9f --- /dev/null +++ b/kotlin-sdk-client/detekt-baseline-commonTestSourceSet.xml @@ -0,0 +1,9 @@ + + + + + LongMethod:StreamableHttpClientTransportTest.kt:StreamableHttpClientTransportTest$@Ignore @Test fun testNotificationSchemaE2E + MaxLineLength:StreamableHttpClientTransportTest.kt:StreamableHttpClientTransportTest$"""data: {"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"upload-123","progress":50,"total":100}}""" + MaxLineLength:StreamableHttpClientTransportTest.kt:StreamableHttpClientTransportTest$"""data: {"jsonrpc":"2.0","method":"notifications/resumed","params":{"fromToken":"$lastEventIdSent"}}""" + + diff --git a/kotlin-sdk-client/detekt-baseline-main.xml b/kotlin-sdk-client/detekt-baseline-main.xml new file mode 100644 index 0000000..8411ab0 --- /dev/null +++ b/kotlin-sdk-client/detekt-baseline-main.xml @@ -0,0 +1,19 @@ + + + + + CyclomaticComplexMethod:StdioClientTransport.kt:StdioClientTransport$override suspend fun initialize + InjectDispatcher:StdioClientTransport.kt:StdioClientTransport$Default + InjectDispatcher:StreamableHttpClientTransport.kt:StreamableHttpClientTransport$Default + LongMethod:StdioClientTransport.kt:StdioClientTransport$override suspend fun initialize + MaxLineLength:StdioClientTransport.kt:StdioClientTransport$* + NoNameShadowing:StdioClientTransport.kt:StdioClientTransport$source + ThrowsCount:SseClientTransport.kt:SseClientTransport$private suspend fun CoroutineScope.collectMessages + ThrowsCount:StreamableHttpClientTransport.kt:StreamableHttpClientTransport$@Suppress("ReturnCount", "CyclomaticComplexMethod") override suspend fun performSend + TooGenericExceptionCaught:Client.kt:Client$error: Throwable + TooGenericExceptionCaught:SseClientTransport.kt:SseClientTransport$e: Throwable + TooGenericExceptionCaught:StdioClientTransport.kt:StdioClientTransport$e: Throwable + UseCheckOrError:Client.kt:Client$throw IllegalStateException( "Server does not support resource subscriptions (required for $method)", ) + UseCheckOrError:Client.kt:Client$throw IllegalStateException("Client does not support roots capability.") + + diff --git a/kotlin-sdk-client/detekt-baseline-test.xml b/kotlin-sdk-client/detekt-baseline-test.xml new file mode 100644 index 0000000..30a384a --- /dev/null +++ b/kotlin-sdk-client/detekt-baseline-test.xml @@ -0,0 +1,18 @@ + + + + + AbstractClassCanBeConcreteClass:AbstractStreamableHttpClientTest.kt:AbstractStreamableHttpClientTest$AbstractStreamableHttpClientTest + ForbiddenComment:StreamableHttpClientTest.kt:StreamableHttpClientTest$// TODO: how to get notifications via Client API? + InjectDispatcher:StdioClientTransportErrorHandlingTest.kt:StdioClientTransportErrorHandlingTest$IO + InjectDispatcher:StreamableHttpClientTransportTest.kt:StreamableHttpClientTransportTest$Default + LongMethod:StreamableHttpClientTest.kt:StreamableHttpClientTest$@Test fun `test streamableHttpClient` + LongMethod:StreamableHttpClientTransportTest.kt:StreamableHttpClientTransportTest$@Ignore @Test fun testNotificationSchemaE2E + LongParameterList:MockMcp.kt:MockMcp$fun handleJSONRPCRequest + LongParameterList:MockMcp.kt:MockMcp$fun handleWithResult + LongParameterList:MockMcp.kt:MockMcp$fun onInitialize + MaxLineLength:StreamableHttpClientTransportTest.kt:StreamableHttpClientTransportTest$"""data: {"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"upload-123","progress":50,"total":100}}""" + MaxLineLength:StreamableHttpClientTransportTest.kt:StreamableHttpClientTransportTest$"""data: {"jsonrpc":"2.0","method":"notifications/resumed","params":{"fromToken":"$lastEventIdSent"}}""" + UseOrEmpty:ClientMetaParameterTest.kt:ClientMetaParameterTest$exception.message ?: "" + + diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt index d861bd0..e3added 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt @@ -107,6 +107,7 @@ public suspend fun mcpClient( * @param clientInfo Information about the client implementation (name, version). * @param options Configuration options for this client. */ +@Suppress("TooManyFunctions") public open class Client(private val clientInfo: Implementation, options: ClientOptions = ClientOptions()) : Protocol(options) { @@ -159,8 +160,8 @@ public open class Client(private val clientInfo: Implementation, options: Client else -> true } - if (!hasCapability) { - throw IllegalStateException("Server does not support $capability (required for $method)") + check(hasCapability) { + "Server does not support $capability (required for $method)" } } @@ -184,7 +185,7 @@ public open class Client(private val clientInfo: Implementation, options: Client val result = request(message) if (!SUPPORTED_PROTOCOL_VERSIONS.contains(result.protocolVersion)) { - throw IllegalStateException( + error( "Server's protocol version is not supported: ${result.protocolVersion}", ) } @@ -213,8 +214,8 @@ public open class Client(private val clientInfo: Implementation, options: Client override fun assertCapabilityForMethod(method: Method) { when (method) { Method.Defined.LoggingSetLevel -> { - if (serverCapabilities?.logging == null) { - throw IllegalStateException("Server does not support logging (required for $method)") + checkNotNull(serverCapabilities?.logging) { + "Server does not support logging (required for $method)" } } @@ -222,7 +223,7 @@ public open class Client(private val clientInfo: Implementation, options: Client Method.Defined.PromptsList, Method.Defined.CompletionComplete, -> { - if (serverCapabilities?.prompts == null) { + checkNotNull(serverCapabilities?.prompts) { throw IllegalStateException("Server does not support prompts (required for $method)") } } @@ -244,8 +245,8 @@ public open class Client(private val clientInfo: Implementation, options: Client } Method.Defined.ToolsCall, Method.Defined.ToolsList -> { - if (serverCapabilities?.tools == null) { - throw IllegalStateException("Server does not support tools (required for $method)") + checkNotNull(serverCapabilities?.tools) { + "Server does not support tools (required for $method)" } } @@ -262,10 +263,8 @@ public open class Client(private val clientInfo: Implementation, options: Client override fun assertNotificationCapability(method: Method) { when (method) { Method.Defined.NotificationsRootsListChanged -> { - if (capabilities.roots?.listChanged != true) { - throw IllegalStateException( - "Client does not support roots list changed notifications (required for $method)", - ) + check(capabilities.roots?.listChanged == true) { + "Client does not support roots list changed notifications (required for $method)" } } @@ -285,26 +284,20 @@ public open class Client(private val clientInfo: Implementation, options: Client override fun assertRequestHandlerCapability(method: Method) { when (method) { Method.Defined.SamplingCreateMessage -> { - if (capabilities.sampling == null) { - throw IllegalStateException( - "Client does not support sampling capability (required for $method)", - ) + checkNotNull(capabilities.sampling) { + "Client does not support sampling capability (required for $method)" } } Method.Defined.RootsList -> { - if (capabilities.roots == null) { - throw IllegalStateException( - "Client does not support roots capability (required for $method)", - ) + checkNotNull(capabilities.roots) { + "Client does not support roots capability (required for $method)" } } Method.Defined.ElicitationCreate -> { - if (capabilities.elicitation == null) { - throw IllegalStateException( - "Client does not support elicitation capability (required for $method)", - ) + checkNotNull(capabilities.elicitation) { + "Client does not support elicitation capability (required for $method)" } } @@ -494,9 +487,9 @@ public open class Client(private val clientInfo: Implementation, options: Client * @throws IllegalStateException If the client does not support roots. */ public fun addRoot(uri: String, name: String) { - if (capabilities.roots == null) { + checkNotNull(capabilities.roots) { logger.error { "Failed to add root '$name': Client does not support roots capability" } - throw IllegalStateException("Client does not support roots capability.") + "Client does not support roots capability." } logger.info { "Adding root: $name ($uri)" } roots.update { current -> current.put(uri, Root(uri, name)) } @@ -509,9 +502,9 @@ public open class Client(private val clientInfo: Implementation, options: Client * @throws IllegalStateException If the client does not support roots. */ public fun addRoots(rootsToAdd: List) { - if (capabilities.roots == null) { + checkNotNull(capabilities.roots) { logger.error { "Failed to add roots: Client does not support roots capability" } - throw IllegalStateException("Client does not support roots capability.") + "Client does not support roots capability." } logger.info { "Adding ${rootsToAdd.size} roots" } roots.update { current -> current.putAll(rootsToAdd.associateBy { it.uri }) } @@ -550,9 +543,9 @@ public open class Client(private val clientInfo: Implementation, options: Client * @throws IllegalStateException If the client does not support roots. */ public fun removeRoots(uris: List): Int { - if (capabilities.roots == null) { + checkNotNull(capabilities.roots) { logger.error { "Failed to remove roots: Client does not support roots capability" } - throw IllegalStateException("Client does not support roots capability.") + "Client does not support roots capability." } logger.info { "Removing ${uris.size} roots" } @@ -587,9 +580,9 @@ public open class Client(private val clientInfo: Implementation, options: Client * @throws IllegalStateException if the client does not support elicitation. */ public fun setElicitationHandler(handler: (ElicitRequest) -> ElicitResult) { - if (capabilities.elicitation == null) { + checkNotNull(capabilities.elicitation) { logger.error { "Failed to set elicitation handler: Client does not support elicitation" } - throw IllegalStateException("Client does not support elicitation.") + "Client does not support elicitation." } logger.info { "Setting the elicitation handler" } @@ -600,7 +593,7 @@ public open class Client(private val clientInfo: Implementation, options: Client // --- Internal Handlers --- - private suspend fun handleListRoots(): ListRootsResult { + private fun handleListRoots(): ListRootsResult { val rootList = roots.value.values.toList() return ListRootsResult(rootList) } @@ -634,7 +627,8 @@ public open class Client(private val clientInfo: Implementation, options: Client val labels = it.split('.') require(labels.all { label -> label.matches(labelPattern) }) { - "Invalid _meta key '$key': prefix labels must start with a letter, end with letter/digit, and contain only letters, digits, or hyphens" + "Invalid _meta key '$key': prefix labels must start with a letter, end with letter/digit, " + + "and contain only letters, digits, or hyphens" } require( @@ -649,7 +643,8 @@ public open class Client(private val clientInfo: Implementation, options: Client // Validate name (empty allowed) require(name.isEmpty() || name.matches(namePattern)) { - "Invalid _meta key '$key': name must start and end with alphanumeric characters, and contain only alphanumerics, hyphens, underscores, or dots" + "Invalid _meta key '$key': name must start and end with alphanumeric characters, " + + "and contain only alphanumerics, hyphens, underscores, or dots" } } } diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt index fb1c640..efa5832 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt @@ -85,6 +85,7 @@ public class StreamableHttpClientTransport( /** * Sends a single message with optional resumption support */ + @Suppress("ReturnCount", "CyclomaticComplexMethod") override suspend fun performSend(message: JSONRPCMessage, options: TransportSendOptions?) { logger.debug { "Client sending message via POST to $url: ${McpJson.encodeToString(message)}" } @@ -259,6 +260,7 @@ public class StreamableHttpClientTransport( } } + @Suppress("TooGenericExceptionCaught") private suspend fun collectSse( session: ClientSSESession, replayMessageId: RequestId?, @@ -295,6 +297,7 @@ public class StreamableHttpClientTransport( } } + @Suppress("CyclomaticComplexMethod") private suspend fun handleInlineSse( response: HttpResponse, replayMessageId: RequestId?, @@ -331,6 +334,7 @@ public class StreamableHttpClientTransport( } } + @Suppress("LoopWithTooManyJumpStatements") while (!channel.isClosedForRead) { val line = channel.readUTF8Line() ?: break if (line.isEmpty()) { diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketClientTransport.kt index ec3f947..c02d68e 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketClientTransport.kt @@ -26,8 +26,8 @@ public class WebSocketClientTransport( override suspend fun initializeSession() { logger.debug { "Websocket session initialization started..." } - session = urlString?.let { - client.webSocketSession(it) { + session = urlString?.let { url -> + client.webSocketSession(url) { requestBuilder() header(HttpHeaders.SecWebSocketProtocol, MCP_SUBPROTOCOL) diff --git a/kotlin-sdk-core/detekt-baseline-commonMainSourceSet.xml b/kotlin-sdk-core/detekt-baseline-commonMainSourceSet.xml new file mode 100644 index 0000000..d774a5b --- /dev/null +++ b/kotlin-sdk-core/detekt-baseline-commonMainSourceSet.xml @@ -0,0 +1,27 @@ + + + + + CyclomaticComplexMethod:jsonUtils.kt:@OptIn(ExperimentalUnsignedTypes::class, ExperimentalSerializationApi::class) private fun convertToJsonElement: JsonElement + LongMethod:Protocol.kt:Protocol$public suspend fun <T : RequestResult> request: T + MagicNumber:completion.kt:CompleteResult.Completion$100 + MatchingDeclarationName:capabilities.dsl.kt:ClientCapabilitiesBuilder + MatchingDeclarationName:methods.kt:Method + MaxLineLength:AbstractClientTransport.kt:AbstractClientTransport$* + MaxLineLength:ClientTransportState.kt:ClientTransportState.Initializing$* + MaxLineLength:Protocol.kt:Protocol$"Received progress notification: token=${notification.params.progressToken}, progress=${notification.params.progress}/${notification.params.total}" + MaxLineLength:Protocol.kt:Protocol$* + MaxLineLength:Protocol.kt:RequestOptions$* + MaxLineLength:Transport.kt:Transport$* + MaxLineLength:TransportSendOptions.kt:TransportSendOptions$* + ReturnCount:ReadBuffer.kt:ReadBuffer$public fun readMessage: JSONRPCMessage? + ThrowsCount:AbstractClientTransport.kt:AbstractClientTransport$public override suspend fun send + TooGenericExceptionCaught:AbstractClientTransport.kt:AbstractClientTransport$e: Exception + TooGenericExceptionCaught:Protocol.kt:Protocol$cause: Throwable + TooGenericExceptionCaught:Protocol.kt:Protocol$error: Throwable + TooGenericExceptionCaught:Protocol.kt:Protocol$sendError: Throwable + TooGenericExceptionCaught:ReadBuffer.kt:ReadBuffer$e: Exception + TooGenericExceptionCaught:WebSocketMcpTransport.kt:WebSocketMcpTransport$e: Exception + TooManyFunctions:Protocol.kt:Protocol + + diff --git a/kotlin-sdk-core/detekt-baseline-commonTestSourceSet.xml b/kotlin-sdk-core/detekt-baseline-commonTestSourceSet.xml new file mode 100644 index 0000000..0a4c838 --- /dev/null +++ b/kotlin-sdk-core/detekt-baseline-commonTestSourceSet.xml @@ -0,0 +1,12 @@ + + + + + EmptyFunctionBlock:ProtocolTest.kt:RecordingTransport${} + EmptyFunctionBlock:ProtocolTest.kt:TestProtocol${} + LongMethod:ElicitationTest.kt:ElicitationTest$@Test fun `should serialize ElicitRequest with requested schema` + LongMethod:SamplingDslTest.kt:SamplingDslTest$@Test fun `buildCreateMessageRequest should build with all fields` + LongMethod:SamplingTest.kt:SamplingTest$@Test fun `should serialize CreateMessageRequest with all fields` + LongMethod:ToolsTest.kt:ToolsTest$@Test fun `should serialize Tool with annotations and schemas` + + diff --git a/kotlin-sdk-core/detekt-baseline-main.xml b/kotlin-sdk-core/detekt-baseline-main.xml new file mode 100644 index 0000000..a22fbcd --- /dev/null +++ b/kotlin-sdk-core/detekt-baseline-main.xml @@ -0,0 +1,32 @@ + + + + + CyclomaticComplexMethod:jsonUtils.kt:@OptIn(ExperimentalUnsignedTypes::class, ExperimentalSerializationApi::class) private fun convertToJsonElement: JsonElement + InjectDispatcher:utils.jvm.kt:IO + InstanceOfCheckForException:AbstractClientTransport.kt:AbstractClientTransport$e is McpException + LongMethod:Protocol.kt:Protocol$public suspend fun <T : RequestResult> request: T + MagicNumber:completion.kt:CompleteResult.Completion$100 + MatchingDeclarationName:capabilities.dsl.kt:ClientCapabilitiesBuilder + MatchingDeclarationName:methods.kt:Method + MaxLineLength:AbstractClientTransport.kt:AbstractClientTransport$* + MaxLineLength:ClientTransportState.kt:ClientTransportState.Initializing$* + MaxLineLength:Protocol.kt:Protocol$"Received progress notification: token=${notification.params.progressToken}, progress=${notification.params.progress}/${notification.params.total}" + MaxLineLength:Protocol.kt:Protocol$* + MaxLineLength:Protocol.kt:RequestOptions$* + MaxLineLength:Transport.kt:Transport$* + MaxLineLength:TransportSendOptions.kt:TransportSendOptions$* + NoNameShadowing:Protocol.kt:Protocol$error + ReturnCount:ReadBuffer.kt:ReadBuffer$public fun readMessage: JSONRPCMessage? + ThrowsCount:AbstractClientTransport.kt:AbstractClientTransport$public override suspend fun send + TooGenericExceptionCaught:AbstractClientTransport.kt:AbstractClientTransport$e: Exception + TooGenericExceptionCaught:Protocol.kt:Protocol$cause: Throwable + TooGenericExceptionCaught:Protocol.kt:Protocol$error: Throwable + TooGenericExceptionCaught:Protocol.kt:Protocol$sendError: Throwable + TooGenericExceptionCaught:ReadBuffer.kt:ReadBuffer$e: Exception + TooGenericExceptionCaught:WebSocketMcpTransport.kt:WebSocketMcpTransport$e: Exception + TooManyFunctions:Protocol.kt:Protocol + UnsafeCallOnNullableType:Protocol.kt:Protocol$response!! + UseCheckNotNull:Protocol.kt:Protocol$check(error != null) + + diff --git a/kotlin-sdk-core/detekt-baseline-test.xml b/kotlin-sdk-core/detekt-baseline-test.xml new file mode 100644 index 0000000..0a4c838 --- /dev/null +++ b/kotlin-sdk-core/detekt-baseline-test.xml @@ -0,0 +1,12 @@ + + + + + EmptyFunctionBlock:ProtocolTest.kt:RecordingTransport${} + EmptyFunctionBlock:ProtocolTest.kt:TestProtocol${} + LongMethod:ElicitationTest.kt:ElicitationTest$@Test fun `should serialize ElicitRequest with requested schema` + LongMethod:SamplingDslTest.kt:SamplingDslTest$@Test fun `buildCreateMessageRequest should build with all fields` + LongMethod:SamplingTest.kt:SamplingTest$@Test fun `should serialize CreateMessageRequest with all fields` + LongMethod:ToolsTest.kt:ToolsTest$@Test fun `should serialize Tool with annotations and schemas` + + diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt index 3447f18..1b43482 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt @@ -1,16 +1,14 @@ -@file:Suppress("FunctionName") +@file:Suppress("FunctionName", "TooManyFunctions") package io.modelcontextprotocol.kotlin.sdk -import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities.Roots import io.modelcontextprotocol.kotlin.sdk.types.ContentBlock import io.modelcontextprotocol.kotlin.sdk.types.fromJSON import io.modelcontextprotocol.kotlin.sdk.types.toJSON import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject -import kotlin.concurrent.atomics.AtomicLong -import kotlin.concurrent.atomics.ExperimentalAtomicApi +import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities.Roots as TypesRoots @Deprecated( message = "Use `LATEST_PROTOCOL_VERSION` instead", @@ -40,14 +38,6 @@ public val SUPPORTED_PROTOCOL_VERSIONS: List = ) public const val JSONRPC_VERSION: String = io.modelcontextprotocol.kotlin.sdk.types.JSONRPC_VERSION -@OptIn(ExperimentalAtomicApi::class) -@Deprecated( - message = "Use `REQUEST_MESSAGE_ID` instead", - replaceWith = ReplaceWith("REQUEST_MESSAGE_ID", "io.modelcontextprotocol.kotlin.sdk.types.REQUEST_MESSAGE_ID"), - level = DeprecationLevel.ERROR, -) -private val REQUEST_MESSAGE_ID: AtomicLong = AtomicLong(0L) - /** * A progress token, used to associate progress notifications with the original request. * Stores message ID. @@ -81,8 +71,6 @@ public typealias WithMeta = io.modelcontextprotocol.kotlin.sdk.types.WithMeta /** * An implementation of [WithMeta] containing custom metadata. - * - * @param _meta The JSON object holding metadata. Defaults to an empty JSON object. */ @Deprecated( message = "This class will be removed. Use `WithMeta` instead", @@ -431,8 +419,6 @@ internal fun JSONRPCRequest.fromJSON(): Request = this.fromJSON() /** * A custom request with a specified method. - * - * @param method The method associated with the request. */ @Deprecated( message = "Use `CustomRequest` instead", @@ -489,8 +475,6 @@ public typealias RequestResult = io.modelcontextprotocol.kotlin.sdk.types.Reques /** * An empty result for a request containing optional metadata. - * - * @param _meta Additional metadata for the response. Defaults to an empty JSON object. */ @Deprecated( message = "Use `EmptyResult` instead", @@ -708,7 +692,8 @@ public object CancelledNotification { /** * This notification can be sent by either side to indicate that it is cancelling a previously issued request. * - * The request SHOULD still be in-flight, but due to communication latency, it is always possible that this notification MAY arrive after the request has already finished. + * The request SHOULD still be in-flight, but due to communication latency, it is always possible that + * this notification MAY arrive after the request has already finished. * * This notification indicates that the result will be unused, so any associated processing SHOULD cease. * @@ -745,6 +730,15 @@ public typealias Implementation = io.modelcontextprotocol.kotlin.sdk.types.Imple level = DeprecationLevel.ERROR, ) public object ClientCapabilities { + /** + * Creates an instance of [Roots], representing the client's capability + * to list roots and optionally listen for changes in the roots list. + * + * @param listChanged Optional flag indicating whether the client supports notifications + * for changes to the roots list. If set to true, the client will notify the server when + * roots are added or removed. + * @return A new instance of [Roots] configured with the provided [listChanged] value. + */ @Deprecated( message = "Use `ClientCapabilities.Roots` instead", replaceWith = ReplaceWith( @@ -753,8 +747,7 @@ public object ClientCapabilities { ), level = DeprecationLevel.ERROR, ) - public fun Roots(listChanged: Boolean? = null): io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities.Roots = - io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities.Roots(listChanged) + public fun Roots(listChanged: Boolean? = null): TypesRoots = TypesRoots(listChanged) } /** @@ -769,7 +762,7 @@ public object ClientCapabilities { ) public fun ClientCapabilities( sampling: JsonObject? = null, - roots: Roots? = null, + roots: TypesRoots? = null, elicitation: JsonObject? = null, experimental: JsonObject? = null, ): io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities = @@ -918,6 +911,7 @@ public object ServerCapabilities { replaceWith = ReplaceWith("ServerCapabilities", "io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities"), level = DeprecationLevel.ERROR, ) +@Suppress("LongParameterList") public fun ServerCapabilities( tools: io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Tools? = null, resources: io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Resources? = null, @@ -1027,9 +1021,6 @@ public sealed interface ProgressBase { /** * Represents a progress notification. - * - * @property progress The current progress value. - * @property total The total progress required, if known. */ @Deprecated( message = "Use `Progress` instead", @@ -1117,8 +1108,6 @@ public typealias ResourceContents = io.modelcontextprotocol.kotlin.sdk.types.Res /** * Represents the text contents of a resource. - * - * @property text The text of the item. This must only be set if the item can actually be represented as text (not binary data). */ @Deprecated( message = "Use `TextResourceContents` instead", @@ -1129,8 +1118,6 @@ public typealias TextResourceContents = io.modelcontextprotocol.kotlin.sdk.types /** * Represents the binary contents of a resource encoded as a base64 string. - * - * @property blob A base64-encoded string representing the binary data of the item. */ @Deprecated( message = "Use `BlobResourceContents` instead", @@ -1278,7 +1265,8 @@ public fun ResourceListChangedNotification( io.modelcontextprotocol.kotlin.sdk.types.ResourceListChangedNotification(params) /** - * Sent from the client to request resources/updated notifications from the server whenever a particular resource changes. + * Sent from the client to request resources/updated notifications from the server + * whenever a particular resource changes. */ @Deprecated( message = "Use `SubscribeRequest` instead", @@ -1288,7 +1276,8 @@ public fun ResourceListChangedNotification( public typealias SubscribeRequest = io.modelcontextprotocol.kotlin.sdk.types.SubscribeRequest /** - * Sent from the client to request cancellation of resources/updated notifications from the server. This should follow a previous resources/subscribe request. + * Sent from the client to request cancellation of resources/updated notifications from the server. + * This should follow a previous resources/subscribe request. */ @Deprecated( message = "Use `UnsubscribeRequest` instead", @@ -1322,7 +1311,8 @@ public object ResourceUpdatedNotification { } /** - * A notification from the server to the client, informing it that a resource has changed and may need to be read again. This should only be sent if the client previously sent a resources/subscribe request. + * A notification from the server to the client, informing it that a resource has changed and may need to be read again. + * This should only be sent if the client previously sent a resources/subscribe request. */ @Deprecated( message = "Use `ResourceUpdatedNotification` instead", @@ -1397,7 +1387,7 @@ public typealias GetPromptRequest = io.modelcontextprotocol.kotlin.sdk.types.Get replaceWith = ReplaceWith("ContentBlock"), level = DeprecationLevel.ERROR, ) -public typealias PromptMessageContent = io.modelcontextprotocol.kotlin.sdk.types.ContentBlock +public typealias PromptMessageContent = ContentBlock /** * Represents prompt message content that is either text, image or audio. @@ -1610,6 +1600,7 @@ public object Tool { replaceWith = ReplaceWith("Tool", "io.modelcontextprotocol.kotlin.sdk.types.Tool"), level = DeprecationLevel.ERROR, ) +@Suppress("LongParameterList", "FunctionParameterNaming") public fun Tool( name: String, inputSchema: io.modelcontextprotocol.kotlin.sdk.types.ToolSchema, @@ -1698,7 +1689,7 @@ public fun CallToolResult( replaceWith = ReplaceWith("ToolCallResult"), level = DeprecationLevel.ERROR, ) -@Suppress("DEPRECATION_ERROR") +@Suppress("DEPRECATION_ERROR", "ConstructorParameterNaming") public class CompatibilityCallToolResult( public val content: List, public val structuredContent: JsonObject? = null, @@ -2250,10 +2241,6 @@ public fun CreateElicitationResult( /** * Represents an error specific to the MCP protocol. - * - * @property code The error code. - * @property message The error message. - * @property data Additional error data as a JSON object. */ @Deprecated( message = "Use `McpException` instead", diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.dsl.kt index bbcfe6c..29d3c99 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.dsl.kt @@ -160,7 +160,8 @@ public class CompleteRequestBuilder @PublishedApi internal constructor() : Reque } val reference = requireNotNull(ref) { - "Missing required field 'ref(Reference)'. Use ref(PromptReference(\"name\")) or ref(ResourceTemplateReference(\"uri\"))" + "Missing required field 'ref(Reference)'. Use ref(PromptReference(\"name\")) " + + "or ref(ResourceTemplateReference(\"uri\"))" } val params = CompleteRequestParams(argument = argument, ref = reference, context = ctx, meta = meta) diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt index 50fd02f..68b2f02 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt @@ -16,8 +16,22 @@ import kotlin.uuid.Uuid public const val JSONRPC_VERSION: String = "2.0" +/** + * Creates a `RequestId` instance using the provided string value. + * + * @param value The string representation of the request ID. This value is used + * to uniquely identify a request in the JSON-RPC context. + * @return A `RequestId` instance of type `StringId` with the provided string value. + */ public fun RequestId(value: String): RequestId = RequestId.StringId(value) +/** + * Creates a `RequestId` instance using the given numeric value. + * + * @param value The numeric value to be used for the `RequestId`. This should uniquely + * identify a request when represented as a `NumberId`. + * @return A `RequestId.NumberId` instance encapsulating the provided numeric value. + */ public fun RequestId(value: Long): RequestId = RequestId.NumberId(value) /** diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonUtils.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonUtils.kt index b9fce4a..566905f 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonUtils.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonUtils.kt @@ -24,6 +24,17 @@ public val McpJson: Json by lazy { } } +/** + * Converts a [Map] with [String] keys and nullable [Any] values into a [Map] with + * [String] keys and [JsonElement] values. + * + * Each value in the resulting map is derived from the corresponding value + * in the input map, transformed into a [JsonElement]. If the transformation + * fails for any reason, the value is replaced with a [JsonPrimitive] containing + * the original value's `toString` representation. + * + * @return A new map where the values are converted to [JsonElement] instances. + */ public fun Map.toJson(): Map = this.mapValues { (_, value) -> runCatching { convertToJsonElement(value) } .getOrElse { JsonPrimitive(value.toString()) } @@ -77,7 +88,5 @@ private fun convertToJsonElement(value: Any?): JsonElement = when (value) { is UByteArray -> buildJsonArray { value.forEach { add(JsonPrimitive(it)) } } - else -> { - JsonPrimitive(value.toString()) - } + else -> JsonPrimitive(value.toString()) } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/notification.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/notification.kt index 9270884..8eb4e4b 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/notification.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/notification.kt @@ -262,7 +262,8 @@ public data class PromptListChangedNotification(override val params: BaseNotific // ============================================================================ /** - * An optional notification from the server to the client, informing it that the list of resources it can read from has changed. + * An optional notification from the server to the client, + * informing it that the list of resources it can read from has changed. * * Servers may issue this without any previous subscription from the client. * Sent only if the server's [ServerCapabilities.resources] has `listChanged = true`. diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.kt index 7e10204..699dfce 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.kt @@ -22,6 +22,12 @@ public value class RequestMeta(public val json: JsonObject) { } } + /** + * Retrieves the value associated with the specified key from the JSON object. + * + * @param key the key whose corresponding value is to be returned + * @return the JsonElement associated with the specified key, or null if the key does not exist + */ public operator fun get(key: String): JsonElement? = json[key] } diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/RequestTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/RequestTest.kt index 9341265..c42589e 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/RequestTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/RequestTest.kt @@ -1,6 +1,7 @@ package io.modelcontextprotocol.kotlin.sdk.types import io.kotest.assertions.json.shouldEqualJson +import io.kotest.matchers.shouldBe import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.double import kotlinx.serialization.json.jsonPrimitive @@ -149,7 +150,9 @@ class RequestTest { val method = custom.method val customMethod = assertIs(method) assertEquals("extensions/customAction", customMethod.value) - assertEquals("custom-1", custom.params?.meta?.json?.get("progressToken")?.jsonPrimitive?.content) + custom.params?.meta?.json + ?.get("progressToken") + ?.jsonPrimitive?.content shouldBe "custom-1" } @Test diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/PingRequestDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/PingRequestDslTest.kt index 5a219ba..79cef06 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/PingRequestDslTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/PingRequestDslTest.kt @@ -49,12 +49,16 @@ class PingRequestDslTest { val requestInt = buildPingRequest { meta { progressToken(123) } } - requestInt.params?.meta?.json?.get("progressToken")?.jsonPrimitive?.int shouldBe 123 + requestInt.params?.meta?.json + ?.get("progressToken") + ?.jsonPrimitive?.int shouldBe 123 val requestLong = buildPingRequest { meta { progressToken(456L) } } - requestLong.params?.meta?.json?.get("progressToken")?.jsonPrimitive?.int shouldBe 456 + requestLong.params?.meta?.json + ?.get("progressToken") + ?.jsonPrimitive?.int shouldBe 456 } @Test diff --git a/kotlin-sdk-server/detekt-baseline-commonMainSourceSet.xml b/kotlin-sdk-server/detekt-baseline-commonMainSourceSet.xml new file mode 100644 index 0000000..67d5e73 --- /dev/null +++ b/kotlin-sdk-server/detekt-baseline-commonMainSourceSet.xml @@ -0,0 +1,21 @@ + + + + + MagicNumber:StdioServerTransport.kt:StdioServerTransport$8192 + MaxLineLength:SSEServerTransport.kt:SseServerTransport$"SSEServerTransport already started! If using Server class, note that connect() calls start() automatically." + MaxLineLength:SSEServerTransport.kt:SseServerTransport$* + MaxLineLength:ServerSession.kt:ServerSession$"Client requested unsupported protocol version $requestedVersion, falling back to $LATEST_PROTOCOL_VERSION" + MaxLineLength:ServerSession.kt:ServerSession$"Creating message with ${params.params.messages.size} messages, maxTokens=${params.params.maxTokens}, temperature=${params.params.temperature}, systemPrompt=${if (params.params.systemPrompt != null) "present" else "absent"}" + ReturnCount:KtorServer.kt:private suspend fun existingStreamableTransport: StreamableHttpServerTransport? + ThrowsCount:ServerSession.kt:ServerSession$override fun assertCapabilityForMethod + ThrowsCount:ServerSession.kt:ServerSession$override fun assertNotificationCapability + ThrowsCount:ServerSession.kt:ServerSession$override fun assertRequestHandlerCapability + TooGenericExceptionCaught:SSEServerTransport.kt:SseServerTransport$e: Exception + TooGenericExceptionCaught:Server.kt:Server$e: Exception + TooGenericExceptionCaught:StdioServerTransport.kt:StdioServerTransport$e: Throwable + TooManyFunctions:KtorServer.kt:io.modelcontextprotocol.kotlin.sdk.server.KtorServer.kt + TooManyFunctions:Server.kt:Server + TooManyFunctions:ServerSession.kt:ServerSession : Protocol + + diff --git a/kotlin-sdk-server/detekt-baseline-main.xml b/kotlin-sdk-server/detekt-baseline-main.xml new file mode 100644 index 0000000..c016bed --- /dev/null +++ b/kotlin-sdk-server/detekt-baseline-main.xml @@ -0,0 +1,51 @@ + + + + + InjectDispatcher:FeatureNotificationService.kt:FeatureNotificationService$Default + LongParameterList:KtorServer.kt:private suspend fun RoutingContext.streamableTransport: StreamableHttpServerTransport? + LongParameterList:Server.kt:Server$public fun addTool + MagicNumber:StdioServerTransport.kt:StdioServerTransport$8192 + MaxLineLength:SSEServerTransport.kt:SseServerTransport$"SSEServerTransport already started! If using Server class, note that connect() calls start() automatically." + MaxLineLength:SSEServerTransport.kt:SseServerTransport$* + MaxLineLength:ServerSession.kt:ServerSession$"Client requested unsupported protocol version $requestedVersion, falling back to $LATEST_PROTOCOL_VERSION" + MaxLineLength:ServerSession.kt:ServerSession$"Creating message with ${params.params.messages.size} messages, maxTokens=${params.params.maxTokens}, temperature=${params.params.temperature}, systemPrompt=${if (params.params.systemPrompt != null) "present" else "absent"}" + NoNameShadowing:FeatureNotificationService.kt:FeatureNotificationService${ it.remove(job) } + RedundantSuspendModifier:ServerSession.kt:ServerSession$suspend + ReturnCount:KtorServer.kt:private suspend fun existingStreamableTransport: StreamableHttpServerTransport? + ThrowsCount:ServerSession.kt:ServerSession$override fun assertCapabilityForMethod + ThrowsCount:ServerSession.kt:ServerSession$override fun assertNotificationCapability + ThrowsCount:ServerSession.kt:ServerSession$override fun assertRequestHandlerCapability + TooGenericExceptionCaught:SSEServerTransport.kt:SseServerTransport$e: Exception + TooGenericExceptionCaught:Server.kt:Server$e: Exception + TooGenericExceptionCaught:StdioServerTransport.kt:StdioServerTransport$e: Throwable + TooManyFunctions:KtorServer.kt:io.modelcontextprotocol.kotlin.sdk.server.KtorServer.kt + TooManyFunctions:Server.kt:Server + TooManyFunctions:ServerSession.kt:ServerSession : Protocol + UnsafeCallOnNullableType:StreamableHttpServerTransport.kt:StreamableHttpServerTransport$responseRequestId!! + UseCheckNotNull:Server.kt:Server$check(options.capabilities.prompts != null) { logger.error { "Failed to add prompt '${prompt.name}': Server does not support prompts capability" } "Server does not support prompts capability." } + UseCheckNotNull:Server.kt:Server$check(options.capabilities.prompts != null) { logger.error { "Failed to add prompts: Server does not support prompts capability" } "Server does not support prompts capability." } + UseCheckNotNull:Server.kt:Server$check(options.capabilities.prompts != null) { logger.error { "Failed to remove prompt '$name': Server does not support prompts capability" } "Server does not support prompts capability." } + UseCheckNotNull:Server.kt:Server$check(options.capabilities.prompts != null) { logger.error { "Failed to remove prompts: Server does not support prompts capability" } "Server does not support prompts capability." } + UseCheckNotNull:Server.kt:Server$check(options.capabilities.resources != null) { logger.error { "Failed to add resource '$name': Server does not support resources capability" } "Server does not support resources capability." } + UseCheckNotNull:Server.kt:Server$check(options.capabilities.resources != null) { logger.error { "Failed to add resources: Server does not support resources capability" } "Server does not support resources capability." } + UseCheckNotNull:Server.kt:Server$check(options.capabilities.resources != null) { logger.error { "Failed to remove resource '$uri': Server does not support resources capability" } "Server does not support resources capability." } + UseCheckNotNull:Server.kt:Server$check(options.capabilities.resources != null) { logger.error { "Failed to remove resources: Server does not support resources capability" } "Server does not support resources capability." } + UseCheckNotNull:Server.kt:Server$check(options.capabilities.tools != null) { logger.error { "Failed to add tool '${tool.name}': Server does not support tools capability" } "Server does not support tools capability. Enable it in ServerOptions." } + UseCheckNotNull:Server.kt:Server$check(options.capabilities.tools != null) { logger.error { "Failed to add tools: Server does not support tools capability" } "Server does not support tools capability." } + UseCheckNotNull:Server.kt:Server$check(options.capabilities.tools != null) { logger.error { "Failed to remove tool '$name': Server does not support tools capability" } "Server does not support tools capability." } + UseCheckNotNull:Server.kt:Server$check(options.capabilities.tools != null) { logger.error { "Failed to remove tools: Server does not support tools capability" } "Server does not support tools capability." } + UseCheckOrError:ServerSession.kt:ServerSession$throw IllegalStateException( "Server does not support notifying about resources (required for ${method.value})", ) + UseCheckOrError:ServerSession.kt:ServerSession$throw IllegalStateException( "Server does not support notifying of prompt list changes (required for ${method.value})", ) + UseCheckOrError:ServerSession.kt:ServerSession$throw IllegalStateException( "Server does not support notifying of tool list changes (required for ${method.value})", ) + UseCheckOrError:ServerSession.kt:ServerSession$throw IllegalStateException("Client does not support elicitation (required for ${method.value})") + UseCheckOrError:ServerSession.kt:ServerSession$throw IllegalStateException("Client does not support listing roots (required for ${method.value})") + UseCheckOrError:ServerSession.kt:ServerSession$throw IllegalStateException("Client does not support sampling (required for ${method.value})") + UseCheckOrError:ServerSession.kt:ServerSession$throw IllegalStateException("Server does not support logging (required for $method)") + UseCheckOrError:ServerSession.kt:ServerSession$throw IllegalStateException("Server does not support logging (required for ${method.value})") + UseCheckOrError:ServerSession.kt:ServerSession$throw IllegalStateException("Server does not support prompts (required for $method)") + UseCheckOrError:ServerSession.kt:ServerSession$throw IllegalStateException("Server does not support resources (required for $method)") + UseCheckOrError:ServerSession.kt:ServerSession$throw IllegalStateException("Server does not support sampling (required for $method)") + UseCheckOrError:ServerSession.kt:ServerSession$throw IllegalStateException("Server does not support tools (required for $method)") + + diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt index 8bb09a8..90aee6b 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt @@ -46,6 +46,12 @@ internal class TransportManager(transports: Map = emp } } +/** + * Registers a server-sent events (SSE) route at the specified path. + * + * @param path the URL path to register the route for SSE. + * @param block the block of code that defines the server's behavior for the SSE session. + */ @KtorDsl public fun Routing.mcp(path: String, block: ServerSSESession.() -> Server) { route(path) { @@ -53,7 +59,7 @@ public fun Routing.mcp(path: String, block: ServerSSESession.() -> Server) { } } -/* +/** * Configures the Ktor Application to handle Model Context Protocol (MCP) over Server-Sent Events (SSE). */ @KtorDsl diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt index bd683af..1894cdc 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt @@ -25,6 +25,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse import io.modelcontextprotocol.kotlin.sdk.types.McpJson import io.modelcontextprotocol.kotlin.sdk.types.Method import io.modelcontextprotocol.kotlin.sdk.types.RPCError +import io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.REQUEST_TIMEOUT import io.modelcontextprotocol.kotlin.sdk.types.RequestId import io.modelcontextprotocol.kotlin.sdk.types.SUPPORTED_PROTOCOL_VERSIONS import kotlinx.coroutines.job @@ -66,20 +67,23 @@ private data class SessionContext(val session: ServerSSESession?, val call: Appl * - No session validation is performed * * @param enableJsonResponse If true, the server will return JSON responses instead of starting an SSE stream. - * This can be useful for simple request/response scenarios without streaming. - * Default is false (SSE streams are preferred). - * @param enableDnsRebindingProtection Enable DNS rebinding protection (requires allowedHosts and/or allowedOrigins to be configured). - * Default is false for backwards compatibility. + * This can be useful for simple request/response scenarios without streaming. + * Default is false (SSE streams are preferred). + * @param enableDnsRebindingProtection Enable DNS rebinding protection + * (requires allowedHosts and/or allowedOrigins to be configured). + * Default is false for backwards compatibility. * @param allowedHosts List of allowed host header values for DNS rebinding protection. - * If not specified, host validation is disabled. + * If not specified, host validation is disabled. * @param allowedOrigins List of allowed origin header values for DNS rebinding protection. - * If not specified, origin validation is disabled. + * If not specified, origin validation is disabled. * @param eventStore Event store for resumability support - * If provided, resumability will be enabled, allowing clients to reconnect and resume messages - * @param retryIntervalMillis Retry interval (in milliseconds) advertised via SSE priming events to hint the client when to reconnect. - * Applies only when an [eventStore] is configured. Defaults to `null` (no retry hint). + * If provided, resumability will be enabled, allowing clients to reconnect and resume messages + * @param retryIntervalMillis Retry interval (in milliseconds) advertised via SSE priming events + * to hint the client when to reconnect. Applies only when an [eventStore] is configured. + * Defaults to `null` (no retry hint). */ @OptIn(ExperimentalUuidApi::class, ExperimentalAtomicApi::class) +@Suppress("TooManyFunctions") public class StreamableHttpServerTransport( private val enableJsonResponse: Boolean = false, private val enableDnsRebindingProtection: Boolean = false, @@ -145,10 +149,12 @@ public class StreamableHttpServerTransport( override suspend fun start() { check(started.compareAndSet(expectedValue = false, newValue = true)) { - "StreamableHttpServerTransport already started! If using Server class, note that connect() calls start() automatically." + "StreamableHttpServerTransport already started! If using Server class, " + + "note that connect() calls start() automatically." } } + @Suppress("CyclomaticComplexMethod", "ReturnCount") override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { val responseRequestId: RequestId? = when (message) { is JSONRPCResponse -> message.id @@ -252,6 +258,7 @@ public class StreamableHttpServerTransport( /** * Handles POST requests containing JSON-RPC messages */ + @Suppress("CyclomaticComplexMethod", "LongMethod", "ReturnCount", "TooGenericExceptionCaught") public suspend fun handlePostRequest(session: ServerSSESession?, call: ApplicationCall) { try { if (!enableJsonResponse && session == null) { @@ -344,6 +351,7 @@ public class StreamableHttpServerTransport( } } + @Suppress("ReturnCount") public suspend fun handleGetRequest(session: ServerSSESession?, call: ApplicationCall) { if (enableJsonResponse) { call.reject( @@ -403,6 +411,7 @@ public class StreamableHttpServerTransport( * Closes the SSE stream associated with the given [requestId], prompting the client to reconnect. * Useful for implementing polling behavior for long-running operations. */ + @Suppress("ReturnCount", "TooGenericExceptionCaught") public suspend fun closeSseStream(requestId: RequestId) { if (enableJsonResponse) return val streamId = requestToStreamMapping[requestId] ?: return @@ -417,6 +426,7 @@ public class StreamableHttpServerTransport( } } + @Suppress("TooGenericExceptionCaught") private suspend fun replayEvents(store: EventStore, lastEventId: String, session: ServerSSESession) { val call: ApplicationCall = session.call @@ -479,6 +489,7 @@ public class StreamableHttpServerTransport( } } + @Suppress("ReturnCount") private suspend fun validateSession(call: ApplicationCall): Boolean { if (sessionIdGenerator == null) return true @@ -519,7 +530,7 @@ public class StreamableHttpServerTransport( else -> { call.reject( HttpStatusCode.NotFound, - -32001, + REQUEST_TIMEOUT, "Session not found", ) false @@ -549,6 +560,7 @@ public class StreamableHttpServerTransport( } } + @Suppress("ReturnCount") private fun validateHeaders(call: ApplicationCall): String? { if (!enableDnsRebindingProtection) return null @@ -573,6 +585,7 @@ public class StreamableHttpServerTransport( return null } + @Suppress("TooGenericExceptionCaught") private suspend fun flushSse(session: ServerSSESession?) { try { session?.send(data = "") @@ -581,6 +594,7 @@ public class StreamableHttpServerTransport( } } + @Suppress("ReturnCount", "MagicNumber") private suspend fun parseBody(call: ApplicationCall): List? { val contentLength = call.request.header(HttpHeaders.ContentLength)?.toIntOrNull() ?: 0 if (contentLength > MAXIMUM_MESSAGE_SIZE) { @@ -630,6 +644,7 @@ public class StreamableHttpServerTransport( } } + @Suppress("TooGenericExceptionCaught") private suspend fun maybeSendPrimingEvent(streamId: String, session: ServerSSESession?) { val store = eventStore ?: return val sseSession = session ?: return diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt index b4d8849..edd54d3 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt @@ -1,3 +1,5 @@ +@file:Suppress("TooManyFunctions") + package io.modelcontextprotocol.kotlin.sdk.server import io.github.oshai.kotlinlogging.KotlinLogging diff --git a/test-utils/detekt-baseline.xml b/test-utils/detekt-baseline.xml new file mode 100644 index 0000000..390ccd0 --- /dev/null +++ b/test-utils/detekt-baseline.xml @@ -0,0 +1,9 @@ + + + + + CyclomaticComplexMethod:Retry.kt:RetryExtension$private fun executeWithRetry + ReturnCount:TypeScriptRunner.kt:TypeScriptRunner$public fun installDependencies + ThrowsCount:Retry.kt:RetryExtension$private fun executeWithRetry + + diff --git a/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/TypeScriptRunner.kt b/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/TypeScriptRunner.kt index 3aa1de7..ff9292e 100644 --- a/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/TypeScriptRunner.kt +++ b/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/TypeScriptRunner.kt @@ -1,5 +1,6 @@ package io.modelcontextprotocol.kotlin.test.utils +import org.junit.jupiter.api.fail import java.io.File /** @@ -83,7 +84,7 @@ public object TypeScriptRunner { patchPackageJsonRecursively(typescriptDir, patchProtocols = true) if (tryCommand(listOf("npm", "install"), typescriptDir)) return - throw RuntimeException("Failed to install TypeScript dependencies in ${typescriptDir.absolutePath}") + fail("Failed to install TypeScript dependencies in ${typescriptDir.absolutePath}") } private fun tryCommand(command: List, workingDir: File): Boolean { diff --git a/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/processUtils.kt b/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/processUtils.kt index a80c825..71fcfa6 100644 --- a/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/processUtils.kt +++ b/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/processUtils.kt @@ -14,6 +14,7 @@ import kotlin.time.Duration.Companion.seconds */ public fun killProcessOnPort(port: Int) { val killCommand = if (isWindows) { + @Suppress("MaxLineLength") listOf( "cmd.exe", "/c", From 25aa9dd551ecacdd32ff01331c2b3f31a68d0741 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Feb 2026 09:44:56 +0200 Subject: [PATCH 147/449] Bump io.mockk:mockk from 1.14.7 to 1.14.9 (#524) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b9ef64f..77a6c56 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -20,7 +20,7 @@ kotest = "6.1.3" kotlinx-io = "0.8.2" ktor = "3.2.3" logging = "7.0.14" -mockk = "1.14.7" +mockk = "1.14.9" mokksy = "0.6.2" serialization = "1.9.0" slf4j = "2.0.17" From 2a04916c5f79fa65c724c947d400b210451bd2f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Feb 2026 10:01:22 +0200 Subject: [PATCH 148/449] Bump org.jetbrains.kotlinx.kover from 0.9.4 to 0.9.7 (#525) Bumps [org.jetbrains.kotlinx.kover](https://github.com/Kotlin/kotlinx-kover) from 0.9.4 to 0.9.7.
Release notes

Sourced from org.jetbrains.kotlinx.kover's releases.

0.9.7

Kover Gradle Plugin

Bugfixes

0.9.6

Kover Gradle Plugin

Bugfixes

0.9.5

Kover Gradle Plugin

Bugfixes

Changelog

Sourced from org.jetbrains.kotlinx.kover's changelog.

0.9.7 / 2026-02-10

Kover Gradle Plugin

Bugfixes

0.9.6 / 2026-02-04

Kover Gradle Plugin

Bugfixes

0.9.5 / 2026-01-30

Kover Gradle Plugin

Bugfixes

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.jetbrains.kotlinx.kover&package-manager=gradle&previous-version=0.9.4&new-version=0.9.7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 77a6c56..460b825 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ binaryCompatibilityValidatorPlugin = "0.18.1" detekt = "2.0.0-alpha.2" dokka = "2.1.0" kotlin = "2.2.21" -kover = "0.9.4" +kover = "0.9.7" ktlint = "14.0.1" knit = "0.5.1" mavenPublish = "0.36.0" From f9d491e4fa3d7074c574bf78b9e1a0c5dc339488 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Feb 2026 10:31:59 +0200 Subject: [PATCH 149/449] Bump gradle-wrapper from 9.3.0 to 9.3.1 (#523) Bumps gradle-wrapper from 9.3.0 to 9.3.1. [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=gradle-wrapper&package-manager=gradle&previous-version=9.3.0&new-version=9.3.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> --- gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 45633 -> 46175 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 45633 -> 46175 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 45633 -> 46175 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- 7 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 19a6bde..37f78a6 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.jar b/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.jar index f8e1ee3125fe0768e9a76ee977ac089eb657005e..61285a659d17295f1de7c53e24fdf13ad755c379 100644 GIT binary patch delta 37058 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 NNswbwE82Y!{{uvZmpcFe delta 36446 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 JWd7DQ`9Hc?h(rJY diff --git a/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties b/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties index bad7c24..37f78a6 100644 --- a/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties +++ b/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.jar b/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.jar index f8e1ee3125fe0768e9a76ee977ac089eb657005e..61285a659d17295f1de7c53e24fdf13ad755c379 100644 GIT binary patch delta 37058 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 NNswbwE82Y!{{uvZmpcFe delta 36446 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 JWd7DQ`9Hc?h(rJY diff --git a/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties b/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties index bad7c24..37f78a6 100644 --- a/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties +++ b/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.jar b/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.jar index f8e1ee3125fe0768e9a76ee977ac089eb657005e..61285a659d17295f1de7c53e24fdf13ad755c379 100644 GIT binary patch delta 37058 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 NNswbwE82Y!{{uvZmpcFe delta 36446 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 JWd7DQ`9Hc?h(rJY diff --git a/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties b/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties index bad7c24..37f78a6 100644 --- a/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties +++ b/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From 212cbfa325a32994d534ced818870da80c0b5a2b Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Mon, 16 Feb 2026 14:10:56 +0200 Subject: [PATCH 150/449] feat: Add kotlin-sdk-testing module with channel-based transport implementation (#505) # feat: Add kotlin-sdk-testing module with channel-based transport implementation - Introduced `kotlin-sdk-testing` module for testing utilities in the MCP Kotlin SDK. - Implemented `ChannelTransport`, providing JSON-RPC transport via Kotlin coroutines channels. ## Motivation and Context This ChannelTransport can be used for testing Server implementation without using real transports. ## How Has This Been Tested? Unit test ## Breaking Changes No ## 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) - [x] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- README.md | 7 + build.gradle.kts | 4 +- integration-test/build.gradle.kts | 1 + .../sdk/testing/ChannelTransportTest.kt | 88 ++++++++ kotlin-sdk-client/build.gradle.kts | 1 + .../AbstractClientTransportLifecycleTest.kt | 4 +- .../ChannelClientTransportLifecycleTest.kt | 20 ++ kotlin-sdk-testing/Module.md | 67 ++++++ kotlin-sdk-testing/api/kotlin-sdk-testing.api | 26 +++ kotlin-sdk-testing/build.gradle.kts | 53 +++++ .../kotlin/sdk/testing/ChannelTransport.kt | 176 +++++++++++++++ .../sdk/testing/ChannelTransportTest.kt | 212 ++++++++++++++++++ .../resources/junit-platform.properties | 5 + .../jvmTest/resources/simplelogger.properties | 7 + settings.gradle.kts | 1 + 15 files changed, 669 insertions(+), 3 deletions(-) create mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/testing/ChannelTransportTest.kt create mode 100644 kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/channel/ChannelClientTransportLifecycleTest.kt create mode 100644 kotlin-sdk-testing/Module.md create mode 100644 kotlin-sdk-testing/api/kotlin-sdk-testing.api create mode 100644 kotlin-sdk-testing/build.gradle.kts create mode 100644 kotlin-sdk-testing/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/testing/ChannelTransport.kt create mode 100644 kotlin-sdk-testing/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/testing/ChannelTransportTest.kt create mode 100644 kotlin-sdk-testing/src/jvmTest/resources/junit-platform.properties create mode 100644 kotlin-sdk-testing/src/jvmTest/resources/simplelogger.properties diff --git a/README.md b/README.md index 881ee98..7df2be2 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ standardized protocol interface. * [Streamable HTTP Transport](#streamable-http-transport) * [SSE Transport](#sse-transport) * [WebSocket Transport](#websocket-transport) + * [ChannelTransport (testing)](#channeltransport-testing) * [Connecting your server](#connecting-your-server) * [Examples](#examples) * [Documentation](#documentation) @@ -774,6 +775,12 @@ SSE Ktor plugin when you need drop-in compatibility, but prefer Streamable HTTP `WebSocketClientTransport` plus the matching server utilities provide full-duplex, low-latency connections—useful when you expect lots of notifications or long-running sessions behind a reverse proxy that already terminates WebSockets. +### ChannelTransport (testing) + +`ChannelTransport` provides a simple, non-networked transport for testing and local development. +It uses Kotlin coroutines channels to provide a full-duplex connection between a client and server, +allowing for easy testing of MCP functionality without the need for network setup. + ## Connecting your server 1. Start a sample HTTP server on port 3000: diff --git a/build.gradle.kts b/build.gradle.kts index 57dd810..09e9adb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,11 +11,13 @@ dependencies { dokka(project(":kotlin-sdk-core")) dokka(project(":kotlin-sdk-client")) dokka(project(":kotlin-sdk-server")) + dokka(project(":kotlin-sdk-testing")) kover(project(":kotlin-sdk-core")) kover(project(":kotlin-sdk-client")) kover(project(":kotlin-sdk-server")) - "kover"(project(":integration-test")) + kover(project(":kotlin-sdk-testing")) + kover(project(":integration-test")) } subprojects { diff --git a/integration-test/build.gradle.kts b/integration-test/build.gradle.kts index 5352234..82078e7 100644 --- a/integration-test/build.gradle.kts +++ b/integration-test/build.gradle.kts @@ -30,6 +30,7 @@ kotlin { jvmTest { dependencies { implementation(project(":test-utils")) + implementation(project(":kotlin-sdk-testing")) } } } diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/testing/ChannelTransportTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/testing/ChannelTransportTest.kt new file mode 100644 index 0000000..ea246db --- /dev/null +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/testing/ChannelTransportTest.kt @@ -0,0 +1,88 @@ +package io.modelcontextprotocol.kotlin.sdk.testing + +import io.kotest.matchers.collections.shouldHaveSize +import io.kotest.matchers.nulls.shouldNotBeNull +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.server.Server +import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions +import io.modelcontextprotocol.kotlin.sdk.server.ServerSession +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest +import io.modelcontextprotocol.kotlin.sdk.types.InitializeResult +import io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION +import io.modelcontextprotocol.kotlin.sdk.types.ListResourcesRequest +import io.modelcontextprotocol.kotlin.sdk.types.ListResourcesResult +import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.Resource +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlin.test.Test + +@OptIn(ExperimentalMcpApi::class) +class ChannelTransportTest { + + @Test + fun `should connect and list resources`(): Unit = runBlocking { + val serverOptions = ServerOptions( + capabilities = ServerCapabilities( + resources = ServerCapabilities.Resources(), + ), + ) + val server = Server( + Implementation(name = "test server", version = "1.0"), + serverOptions, + ) + + val (clientTransport, serverTransport) = ChannelTransport.createLinkedPair() + + val client = Client( + clientInfo = Implementation(name = "test client", version = "1.0"), + ) + + val serverSessionResult = CompletableDeferred() + + listOf( + launch { + client.connect(clientTransport) + }, + launch { + serverSessionResult.complete(server.createSession(serverTransport)) + }, + ).joinAll() + + val serverSession = serverSessionResult.await() + serverSession.setRequestHandler(Method.Defined.Initialize) { _, _ -> + InitializeResult( + protocolVersion = LATEST_PROTOCOL_VERSION, + capabilities = ServerCapabilities( + resources = ServerCapabilities.Resources(null, null), + tools = ServerCapabilities.Tools(null), + ), + serverInfo = Implementation(name = "test", version = "1.0"), + ) + } + + serverSession.setRequestHandler(Method.Defined.ResourcesList) { _, _ -> + ListResourcesResult( + resources = listOf( + Resource( + uri = "/foo/bar", + name = "foo-bar-resource", + ), + ), + ) + } + + // These should not throw + client.listResources() shouldNotBeNull { + this.resources shouldHaveSize 1 + } + + client.close() + server.close() + } +} diff --git a/kotlin-sdk-client/build.gradle.kts b/kotlin-sdk-client/build.gradle.kts index 36a3ed8..f50266c 100644 --- a/kotlin-sdk-client/build.gradle.kts +++ b/kotlin-sdk-client/build.gradle.kts @@ -41,6 +41,7 @@ kotlin { commonTest { dependencies { implementation(kotlin("test")) + implementation(project(":kotlin-sdk-testing")) implementation(libs.kotest.assertions.core) implementation(libs.kotlinx.coroutines.test) implementation(libs.ktor.client.logging) diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractClientTransportLifecycleTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractClientTransportLifecycleTest.kt index b6ece42..28a1337 100644 --- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractClientTransportLifecycleTest.kt +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractClientTransportLifecycleTest.kt @@ -4,7 +4,7 @@ import io.kotest.assertions.nondeterministic.eventually import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain -import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport +import io.modelcontextprotocol.kotlin.sdk.shared.AbstractClientTransport import io.modelcontextprotocol.kotlin.sdk.types.McpException import io.modelcontextprotocol.kotlin.sdk.types.PingRequest import io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.CONNECTION_CLOSED @@ -16,7 +16,7 @@ import kotlin.test.Test import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds -abstract class AbstractClientTransportLifecycleTest { +abstract class AbstractClientTransportLifecycleTest { protected lateinit var transport: T diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/channel/ChannelClientTransportLifecycleTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/channel/ChannelClientTransportLifecycleTest.kt new file mode 100644 index 0000000..853a433 --- /dev/null +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/channel/ChannelClientTransportLifecycleTest.kt @@ -0,0 +1,20 @@ +package io.modelcontextprotocol.kotlin.sdk.client.channel + +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import io.modelcontextprotocol.kotlin.sdk.client.AbstractClientTransportLifecycleTest +import io.modelcontextprotocol.kotlin.sdk.testing.ChannelTransport +import kotlin.test.Ignore +import kotlin.test.Test + +@OptIn(ExperimentalMcpApi::class) +class ChannelClientTransportLifecycleTest : AbstractClientTransportLifecycleTest() { + + /** + * Dummy method to make IDE treat this class as a test + */ + @Test + @Ignore + fun dummyTest() = Unit + + override fun createTransport(): ChannelTransport = ChannelTransport() +} diff --git a/kotlin-sdk-testing/Module.md b/kotlin-sdk-testing/Module.md new file mode 100644 index 0000000..9291de5 --- /dev/null +++ b/kotlin-sdk-testing/Module.md @@ -0,0 +1,67 @@ +# Module kotlin-sdk-testing + +`kotlin-sdk-testing` provides testing utilities for MCP client-server interactions. It eliminates the need for external +processes or network connections by offering coroutine-safe in-memory transport implementations that connect clients and servers directly. + +## Typical usage + +1. Create linked transport pair with `ChannelTransport.createLinkedPair()`. +2. Connect client and server concurrently using the paired transports. +3. Configure server request handlers. +4. Execute client operations and verify results. + +### Complete test example + +```kotlin +@OptIn(ExperimentalMcpApi::class) +@Test +fun testClientServerCommunication(): Unit = runBlocking { + val server = Server( + Implementation(name = "test server", version = "1.0"), + ServerOptions(capabilities = ServerCapabilities(resources = ServerCapabilities.Resources())), + ) + + val (clientTransport, serverTransport) = ChannelTransport.createLinkedPair() + + val client = Client(clientInfo = Implementation(name = "test client", version = "1.0")) + + val serverSessionResult = CompletableDeferred() + listOf( + launch { client.connect(clientTransport) }, + launch { serverSessionResult.complete(server.createSession(serverTransport)) }, + ).joinAll() + + val serverSession = serverSessionResult.await() + serverSession.setRequestHandler(Method.Defined.Initialize) { _, _ -> + InitializeResult( + protocolVersion = LATEST_PROTOCOL_VERSION, + capabilities = ServerCapabilities( + resources = ServerCapabilities.Resources(null, null), + tools = ServerCapabilities.Tools(null), + ), + serverInfo = Implementation(name = "test", version = "1.0"), + ) + } + + serverSession.setRequestHandler(Method.Defined.ResourcesList) { _, _ -> + ListResourcesResult( + resources = listOf(Resource(uri = "/foo/bar", name = "foo-bar-resource")), + ) + } + + // Test client operations + client.listResources() shouldNotBeNull { + this.resources shouldHaveSize 1 + } + + // Clean up resources + client.close() + server.close() +} +``` + +**Note**: In production tests, wrap client/server operations in try-finally blocks to ensure proper resource cleanup even if assertions fail. + +Use this module for unit and integration testing of MCP implementations without managing external server processes, +network ports, or I/O streams. The channel-based transport ensures tests run instantly with full isolation between test +cases. diff --git a/kotlin-sdk-testing/api/kotlin-sdk-testing.api b/kotlin-sdk-testing/api/kotlin-sdk-testing.api new file mode 100644 index 0000000..4a942a4 --- /dev/null +++ b/kotlin-sdk-testing/api/kotlin-sdk-testing.api @@ -0,0 +1,26 @@ +public final class io/modelcontextprotocol/kotlin/sdk/testing/ChannelTransport : io/modelcontextprotocol/kotlin/sdk/shared/AbstractClientTransport { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/testing/ChannelTransport$Companion; + public fun (Lkotlinx/coroutines/channels/Channel;Lkotlinx/coroutines/CoroutineDispatcher;)V + public synthetic fun (Lkotlinx/coroutines/channels/Channel;Lkotlinx/coroutines/CoroutineDispatcher;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lkotlinx/coroutines/channels/SendChannel;Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlinx/coroutines/CoroutineDispatcher;)V + public synthetic fun (Lkotlinx/coroutines/channels/SendChannel;Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlinx/coroutines/CoroutineDispatcher;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public final class io/modelcontextprotocol/kotlin/sdk/testing/ChannelTransport$Companion { + public final fun createLinkedPair (ILkotlinx/coroutines/CoroutineDispatcher;)Lio/modelcontextprotocol/kotlin/sdk/testing/ChannelTransport$LinkedTransports; + public static synthetic fun createLinkedPair$default (Lio/modelcontextprotocol/kotlin/sdk/testing/ChannelTransport$Companion;ILkotlinx/coroutines/CoroutineDispatcher;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/testing/ChannelTransport$LinkedTransports; +} + +public final class io/modelcontextprotocol/kotlin/sdk/testing/ChannelTransport$LinkedTransports { + public fun (Lio/modelcontextprotocol/kotlin/sdk/testing/ChannelTransport;Lio/modelcontextprotocol/kotlin/sdk/testing/ChannelTransport;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/testing/ChannelTransport; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/testing/ChannelTransport; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/testing/ChannelTransport;Lio/modelcontextprotocol/kotlin/sdk/testing/ChannelTransport;)Lio/modelcontextprotocol/kotlin/sdk/testing/ChannelTransport$LinkedTransports; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/testing/ChannelTransport$LinkedTransports;Lio/modelcontextprotocol/kotlin/sdk/testing/ChannelTransport;Lio/modelcontextprotocol/kotlin/sdk/testing/ChannelTransport;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/testing/ChannelTransport$LinkedTransports; + public fun equals (Ljava/lang/Object;)Z + public final fun getClientTransport ()Lio/modelcontextprotocol/kotlin/sdk/testing/ChannelTransport; + public final fun getServerTransport ()Lio/modelcontextprotocol/kotlin/sdk/testing/ChannelTransport; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + diff --git a/kotlin-sdk-testing/build.gradle.kts b/kotlin-sdk-testing/build.gradle.kts new file mode 100644 index 0000000..902665c --- /dev/null +++ b/kotlin-sdk-testing/build.gradle.kts @@ -0,0 +1,53 @@ +@file:OptIn(ExperimentalWasmDsl::class) + +import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl + +plugins { + id("mcp.multiplatform") + id("mcp.publishing") + id("mcp.dokka") + alias(libs.plugins.kotlinx.binary.compatibility.validator) +} + +kotlin { + + iosArm64() + iosX64() + iosSimulatorArm64() + watchosX64() + watchosArm64() + watchosSimulatorArm64() + tvosX64() + tvosArm64() + tvosSimulatorArm64() + js { + browser() + nodejs() + } + wasmJs { + browser() + nodejs() + } + + sourceSets { + commonMain { + dependencies { + api(project(":kotlin-sdk-core")) + implementation(libs.kotlin.logging) + } + } + + commonTest { + dependencies { + implementation(kotlin("test")) + implementation(libs.kotlinx.coroutines.test) + implementation(libs.kotest.assertions.core) + } + } + jvmTest { + dependencies { + runtimeOnly(libs.slf4j.simple) + } + } + } +} diff --git a/kotlin-sdk-testing/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/testing/ChannelTransport.kt b/kotlin-sdk-testing/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/testing/ChannelTransport.kt new file mode 100644 index 0000000..e6d19f7 --- /dev/null +++ b/kotlin-sdk-testing/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/testing/ChannelTransport.kt @@ -0,0 +1,176 @@ +package io.modelcontextprotocol.kotlin.sdk.testing + +import io.github.oshai.kotlinlogging.KLogger +import io.github.oshai.kotlinlogging.KotlinLogging +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import io.modelcontextprotocol.kotlin.sdk.shared.AbstractClientTransport +import io.modelcontextprotocol.kotlin.sdk.shared.TransportSendOptions +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED +import kotlinx.coroutines.channels.ReceiveChannel +import kotlinx.coroutines.channels.SendChannel +import kotlinx.coroutines.launch +import kotlinx.coroutines.yield + +/** + * A transport implementation that uses Kotlin Coroutines Channels for asynchronous + * communication of JSON-RPC messages. This is useful in scenarios where communication is + * message-based and channels are used as the underlying mechanism for delivery. + * + * @constructor Initializes the transport with the specified [SendChannel] and [ReceiveChannel]. + * @property sendChannel The channel used to send JSON-RPC messages. + * @property receiveChannel The channel used to receive JSON-RPC messages. + * @param dispatcher The coroutine dispatcher for the event loop. Defaults to [Dispatchers.Default]. + */ +@ExperimentalMcpApi +public class ChannelTransport( + private val sendChannel: SendChannel, + private val receiveChannel: ReceiveChannel, + dispatcher: CoroutineDispatcher = Dispatchers.Default, +) : AbstractClientTransport() { + + override val logger: KLogger = KotlinLogging.logger {} + + private val scope = CoroutineScope(SupervisorJob() + dispatcher) + + /** + * Creates a `ChannelTransport` instance using a single channel for both sending and receiving messages. + * + * This constructor simplifies the use of `ChannelTransport` in scenarios where a single channel + * is enough for bidirectional communication. A default channel with unlimited capacity is used + * if no channel is provided. + * + * @param channel The `Channel` of `JSONRPCMessage` instances, used for both sending and receiving. + * Defaults to a channel with unlimited capacity. + * @param dispatcher The coroutine dispatcher for the event loop. Defaults to [Dispatchers.Default]. + */ + public constructor( + channel: Channel = Channel(UNLIMITED), + dispatcher: CoroutineDispatcher = Dispatchers.Default, + ) : this(channel, channel, dispatcher) + + /** + * Represents a pair of interconnected [ChannelTransport]s for bidirectional communication. + * + * Messages sent via one transport are received by the other. This is useful for + * setting up communication between a client and server without requiring external networking. + * + * @property clientTransport The transport intended for use on the client-side. + * @property serverTransport The transport intended for use on the server-side. + */ + public data class LinkedTransports(val clientTransport: ChannelTransport, val serverTransport: ChannelTransport) + + public companion object { + + /** + * Creates a pair of interconnected [ChannelTransport] objects for bidirectional communication. + * + * This method sets up a client and server transport using Kotlin channels, enabling messages sent + * through one transport to be received by the other. It is typically used for testing or in-memory + * communication without external networking. + * + * @param capacity The buffer capacity of the internal channels used for communication. Defaults to 256. + * @return A [LinkedTransports] instance containing the client and server transports. + */ + public fun createLinkedPair( + capacity: Int = 256, + dispatcher: CoroutineDispatcher = Dispatchers.Default, + ): LinkedTransports { + val sendChannel = Channel(capacity) + val receiveChannel = Channel(capacity) + val clientTransport = ChannelTransport(sendChannel, receiveChannel, dispatcher) + val serverTransport = ChannelTransport(receiveChannel, sendChannel, dispatcher) + return LinkedTransports(clientTransport = clientTransport, serverTransport = serverTransport) + } + } + + /** + * Starts the transport, allowing it to begin processing messages. + * + * Waits for the event loop to start before returning, ensuring the transport is + * fully initialized and ready to send/receive messages. + * + * Processes messages until the channel closes or cancellation occurs. + * Exceptions from message handlers are reported via onError but do not stop processing. + */ + override suspend fun initialize() { + logger.info { "ChannelTransport starting message processing" } + val started = CompletableDeferred() + @Suppress("TooGenericExceptionCaught") + scope.launch(CoroutineName("ChannelTransport#${hashCode()}-event-loop")) { + try { + // Signal that event loop has started, then yield to ensure we're ready + started.complete(Unit) + yield() + + for (message in receiveChannel) { + logger.debug { "Received message: ${message::class.simpleName}" } + + try { + _onMessage.invoke(message) + logger.trace { "Message processed successfully: ${message::class.simpleName}" } + } catch (e: CancellationException) { + // Let cancellation propagate immediately + logger.debug { "Cancellation requested during message processing" } + throw e + } catch (e: Exception) { + // Report other errors but continue processing + logger.warn(e) { "Error processing message: ${message::class.simpleName}" } + _onError.invoke(e) + } + } + logger.info { "ChannelTransport stopped: receive channel closed" } + } catch (e: Exception) { + // Only complete exceptionally if not already completed + if (!started.isCompleted) { + started.completeExceptionally(e) + } + throw e + } finally { + close() + } + } + // Wait for the event loop to start + started.await() + } + + /** + * Sends a JSON-RPC message through the transport. + * + * @param message The JSON-RPC message to send. This can be a request or a response, + * conforming to the JSON-RPC 2.0 specification. + * @param options Optional transport-specific options that provide additional context or behavior + * for the message transmission, such as associating the message with a specific + * incoming request or handling resumption tokens. + */ + override suspend fun performSend(message: JSONRPCMessage, options: TransportSendOptions?) { + logger.debug { "Sending message: ${message::class.simpleName}" } + sendChannel.send(message) + logger.trace { "Message sent successfully: ${message::class.simpleName}" } + } + + /** + * Closes the transport, preventing further message transmission and processing. + */ + override suspend fun closeResources() { + logger.info { "Closing ChannelTransport" } + sendChannel.close() + if (receiveChannel !== sendChannel) { + logger.debug { "Cancelling separate receive channel" } + receiveChannel.cancel() + } + scope.cancel() + scope.coroutineContext[Job]?.join() // Wait for cleanup + logger.info { "ChannelTransport closed" } + } +} diff --git a/kotlin-sdk-testing/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/testing/ChannelTransportTest.kt b/kotlin-sdk-testing/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/testing/ChannelTransportTest.kt new file mode 100644 index 0000000..7fc41bb --- /dev/null +++ b/kotlin-sdk-testing/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/testing/ChannelTransportTest.kt @@ -0,0 +1,212 @@ +package io.modelcontextprotocol.kotlin.sdk.testing + +import io.kotest.assertions.nondeterministic.eventually +import io.kotest.matchers.collections.shouldContainExactly +import io.kotest.matchers.shouldBe +import io.kotest.matchers.types.shouldBeInstanceOf +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest +import io.modelcontextprotocol.kotlin.sdk.types.RequestId +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.cancelAndJoin +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.ClosedSendChannelException +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import kotlin.test.Test +import kotlin.time.Duration.Companion.seconds + +@OptIn(ExperimentalMcpApi::class) +class ChannelTransportTest { + + @Test + fun `send writes to sendChannel`() = runTest { + val sendChannel = Channel(Channel.UNLIMITED) + val transport = ChannelTransport(sendChannel, Channel()) + + transport.start() + + val message = JSONRPCRequest(RequestId.NumberId(42), "test") + transport.send(message) + + sendChannel.receive() shouldBe message + } + + @Test + fun `start reads from receiveChannel and invokes onMessage`() = runTest { + val receiveChannel = Channel(Channel.UNLIMITED) + val transport = ChannelTransport(Channel(), receiveChannel) + + val messagesProcessed = CompletableDeferred() + val received = mutableListOf() + transport.onMessage { msg -> + received.add(msg) + if (received.size == 2) { + messagesProcessed.complete(Unit) + } + } + + transport.start() + + val msg1 = JSONRPCRequest(RequestId.NumberId(1), "method1") + val msg2 = JSONRPCRequest(RequestId.NumberId(2), "method2") + receiveChannel.send(msg1) + receiveChannel.send(msg2) + + // Wait for messages to be processed + messagesProcessed.await() + + received.shouldContainExactly(msg1, msg2) + } + + @Test + fun `start completes when channel closes`() = runTest { + val receiveChannel = Channel(Channel.UNLIMITED) + val transport = ChannelTransport(Channel(), receiveChannel) + + val messageProcessed = CompletableDeferred() + val received = mutableListOf() + transport.onMessage { + received.add(it) + messageProcessed.complete(Unit) + } + + transport.start() + + val msg = JSONRPCRequest(RequestId.NumberId(1), "method1") + receiveChannel.send(msg) + receiveChannel.close() // Close the channel + + // Wait for a message to be processed + messageProcessed.await() + + received.shouldContainExactly(msg) + } + + @Test + fun `close is idempotent`() = runTest { + val transport = ChannelTransport() + + var closeCount = 0 + transport.onClose { closeCount++ } + + transport.start() + + transport.close() + transport.close() + + closeCount shouldBe 1 + } + + @Test + fun `callbacks chain not replace`() = runTest { + val receiveChannel = Channel(Channel.UNLIMITED) + val transport = ChannelTransport(Channel(), receiveChannel) + + val messageProcessed = CompletableDeferred() + val calls = mutableListOf() + transport.onMessage { + calls.add("first") + } + transport.onMessage { + calls.add("second") + messageProcessed.complete(Unit) + } + + val startJob = backgroundScope.launch { transport.start() } + + receiveChannel.send(JSONRPCRequest(RequestId.NumberId(1), "test")) + messageProcessed.await() + + calls.shouldContainExactly("first", "second") + startJob.cancelAndJoin() + } + + @Test + fun `secondary constructor uses single channel`() = runTest { + val channel = Channel(Channel.UNLIMITED) + val transport = ChannelTransport(channel) + + val messageProcessed = CompletableDeferred() + val received = mutableListOf() + transport.onMessage { msg -> + received.add(msg) + messageProcessed.complete(Unit) + } + + transport.start() + + val message = JSONRPCRequest(RequestId.NumberId(1), "test") + transport.send(message) + messageProcessed.await() + + eventually(2.seconds) { + received.shouldContainExactly(message) + } + } + + @Test + fun `send to closed channel triggers error`() = runTest { + val sendChannel = Channel(Channel.UNLIMITED) + val transport = ChannelTransport(sendChannel, Channel()) + + transport.start() + + var errorCaught = false + transport.onError { + it.shouldBeInstanceOf() + errorCaught = true + } + sendChannel.close() + + try { + transport.send(JSONRPCRequest(RequestId.NumberId(1), "method")) + } catch (e: Exception) { + // send() wraps ClosedSendChannelException in McpException + e.shouldBeInstanceOf() + } + + eventually(2.seconds) { + errorCaught shouldBe true + } + } + + @Test + fun `exceptions in message handler do not stop processing`() = runTest { + val receiveChannel = Channel(Channel.UNLIMITED) + val transport = ChannelTransport(Channel(), receiveChannel) + + val allProcessed = CompletableDeferred() + val received = mutableListOf() + val errors = mutableListOf() + + transport.onError { errors.add(it) } + transport.onMessage { msg -> + val id = ((msg as JSONRPCRequest).id as RequestId.NumberId).value.toInt() + received.add(id) + if (id == 2) { + @Suppress("TooGenericExceptionThrown") + throw RuntimeException("Error processing message 2") + } + if (received.size == 4) { + allProcessed.complete(Unit) + } + } + + transport.start() + + // Send 4 messages, second one will throw + receiveChannel.send(JSONRPCRequest(RequestId.NumberId(1), "m1")) + receiveChannel.send(JSONRPCRequest(RequestId.NumberId(2), "m2")) + receiveChannel.send(JSONRPCRequest(RequestId.NumberId(3), "m3")) + receiveChannel.send(JSONRPCRequest(RequestId.NumberId(4), "m4")) + + allProcessed.await() + + // All messages should be processed despite error in message 2 + received.shouldContainExactly(1, 2, 3, 4) + errors.size shouldBe 1 + errors[0].message shouldBe "Error processing message 2" + } +} diff --git a/kotlin-sdk-testing/src/jvmTest/resources/junit-platform.properties b/kotlin-sdk-testing/src/jvmTest/resources/junit-platform.properties new file mode 100644 index 0000000..f14d100 --- /dev/null +++ b/kotlin-sdk-testing/src/jvmTest/resources/junit-platform.properties @@ -0,0 +1,5 @@ +## https://docs.junit.org/5.3.0-M1/user-guide/index.html#writing-tests-parallel-execution +junit.jupiter.execution.parallel.enabled=true +junit.jupiter.execution.parallel.config.strategy=dynamic +junit.jupiter.execution.parallel.mode.default=concurrent +junit.jupiter.execution.parallel.mode.classes.default=concurrent diff --git a/kotlin-sdk-testing/src/jvmTest/resources/simplelogger.properties b/kotlin-sdk-testing/src/jvmTest/resources/simplelogger.properties new file mode 100644 index 0000000..5679351 --- /dev/null +++ b/kotlin-sdk-testing/src/jvmTest/resources/simplelogger.properties @@ -0,0 +1,7 @@ +# Level of logging for the ROOT logger: ERROR, WARN, INFO, DEBUG, TRACE (default is INFO) +org.slf4j.simpleLogger.defaultLogLevel=INFO +org.slf4j.simpleLogger.showThreadName=true +org.slf4j.simpleLogger.showDateTime=false + +# Log level for specific packages or classes +org.slf4j.simpleLogger.log.io.modelcontextprotocol=DEBUG diff --git a/settings.gradle.kts b/settings.gradle.kts index 95d80b1..60e3d1c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -22,6 +22,7 @@ include( ":kotlin-sdk-core", ":kotlin-sdk-client", ":kotlin-sdk-server", + ":kotlin-sdk-testing", ":kotlin-sdk", ":integration-test", ":docs", From 1b255a2baf957f68423751b05fabded1399ca510 Mon Sep 17 00:00:00 2001 From: Ilia Brovarnik Date: Mon, 16 Feb 2026 22:42:35 +0100 Subject: [PATCH 151/449] Add $defs to ToolSchema and tests (#526) Add $defs support in ToolSchema and update tests. Motivation and Context Fix parsing when tool schemas include $defs. How Has This Been Tested? `./gradlew :kotlin-sdk-core:jvmTest --tests "io.modelcontextprotocol.kotlin.sdk.types.ToolsTest"` Breaking Changes No. Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 - [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 N/A --- kotlin-sdk-core/api/kotlin-sdk-core.api | 10 +- .../kotlin/sdk/types/tools.kt | 8 +- .../kotlin/sdk/types/ToolsTest.kt | 112 ++++++++++++++++++ 3 files changed, 125 insertions(+), 5 deletions(-) diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index a5b581e..0607df5 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -4525,13 +4525,15 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ToolListChangedNotif public final class io/modelcontextprotocol/kotlin/sdk/types/ToolSchema { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema$Companion; public fun ()V - public fun (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;)V - public synthetic fun (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lkotlinx/serialization/json/JsonObject; public final fun component2 ()Ljava/util/List; - public final fun copy (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema; + public final fun component3 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema; public fun equals (Ljava/lang/Object;)Z + public final fun getDefs ()Lkotlinx/serialization/json/JsonObject; public final fun getProperties ()Lkotlinx/serialization/json/JsonObject; public final fun getRequired ()Ljava/util/List; public final fun getType ()Ljava/lang/String; diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt index 767e724..b7aade5 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt @@ -78,9 +78,15 @@ public data class Tool( * @property type Always "object" for tool schemas. * @property properties Optional map of property names to their schema definitions. * @property required Optional list of property names that are required. + * @property defs Optional schema definitions available to references in [properties]. Serialized as `$defs`. */ @Serializable -public data class ToolSchema(val properties: JsonObject? = null, val required: List? = null) { +public data class ToolSchema( + val properties: JsonObject? = null, + val required: List? = null, + @SerialName("\$defs") + val defs: JsonObject? = null, +) { @OptIn(ExperimentalSerializationApi::class) @EncodeDefault val type: String = "object" diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ToolsTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ToolsTest.kt index a662e44..447df4f 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ToolsTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ToolsTest.kt @@ -152,6 +152,57 @@ class ToolsTest { assertEquals("language", tool.meta?.get("category")?.jsonPrimitive?.content) } + @Test + fun `should serialize ToolSchema with defs`() { + val tool = toolWithDefs() + + val json = McpJson.encodeToString(tool) + + json shouldEqualJson toolWithDefsJson() + } + + @Test + fun `should deserialize ToolSchema with defs`() { + val json = $$""" + { + "name": "create-page", + "inputSchema": { + "type": "object", + "$defs": { + "parentRequest": { + "type": "object", + "properties": { + "page_id": { + "type": "string" + } + } + } + }, + "properties": { + "parent": { + "$ref": "#/$defs/parentRequest" + } + }, + "required": ["parent"] + } + } + """.trimIndent() + + val tool = McpJson.decodeFromString(json) + + val schema = tool.inputSchema + val defs = schema.defs + assertNotNull(defs) + val parentRequest = defs["parentRequest"]?.jsonObject + assertNotNull(parentRequest) + assertEquals("object", parentRequest["type"]?.jsonPrimitive?.content) + assertEquals( + $$"#/$defs/parentRequest", + schema.properties?.get("parent")?.jsonObject?.get($$"$ref")?.jsonPrimitive?.content, + ) + assertEquals(listOf("parent"), schema.required) + } + @Test fun `should serialize CallToolRequest with arguments`() { val request = CallToolRequest( @@ -520,4 +571,65 @@ class ToolsTest { } } """.trimIndent() + + private fun toolWithDefs(): Tool = Tool( + name = "create-page", + inputSchema = toolSchemaWithDefs(), + ) + + private fun toolSchemaWithDefs(): ToolSchema = ToolSchema( + properties = buildJsonObject { + put( + "parent", + buildJsonObject { + put($$"$ref", $$"#/$defs/parentRequest") + }, + ) + }, + required = listOf("parent"), + defs = buildJsonObject { + put( + "parentRequest", + buildJsonObject { + put("type", "object") + put( + "properties", + buildJsonObject { + put( + "page_id", + buildJsonObject { + put("type", "string") + }, + ) + }, + ) + }, + ) + }, + ) + + private fun toolWithDefsJson(): String = $$""" + { + "name": "create-page", + "inputSchema": { + "type": "object", + "$defs": { + "parentRequest": { + "type": "object", + "properties": { + "page_id": { + "type": "string" + } + } + } + }, + "properties": { + "parent": { + "$ref": "#/$defs/parentRequest" + } + }, + "required": ["parent"] + } + } + """.trimIndent() } From 367618d109706a8a9cb34811da86c3ed39ab3624 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Tue, 17 Feb 2026 12:16:02 +0200 Subject: [PATCH 152/449] refactor: Refactor serialization tests (#527) 1. Migrate test-utils module to multiplatform, add serialization utilities - Migrated `test-utils` to support Kotlin Multiplatform (JVM, JS, Native). - Added `verifySerialization`, `verifyDeserialization`, `verifySerializationRoundTrip` utilities to streamline and validate JSON serialization/deserialization 2. Refactor serialization tests to use test serialization utilities - Updated serialization tests across the project to use `verifySerialization` and `verifyDeserialization` utilities. - Improved consistency and ensured proper round-trip validation for JSON deserialization. 3. Remove some detekt baseline files and suppress `LongMethod` findings - Deleted detekt baseline XML files for test sources. - Suppressed `LongMethod` findings in relevant tests using `@Suppress` annotations. - Replaced empty method implementations with explicit `// noop` comments for clarity. - Adjusted min test coverage threshold in `build.gradle.kts`. ## Motivation and Context To improve serialization testing ## How Has This Been Tested? Unit tests ## Breaking Changes No ## 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 - [x] Refactoring ## 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 --- build.gradle.kts | 2 +- kotlin-sdk-core/build.gradle.kts | 4 +- .../detekt-baseline-commonTestSourceSet.xml | 12 - kotlin-sdk-core/detekt-baseline-test.xml | 12 - .../kotlin/sdk/shared/ProtocolTest.kt | 20 +- .../kotlin/sdk/types/CapabilitiesTest.kt | 247 ++++++++++-------- .../kotlin/sdk/types/CommonTypeTest.kt | 78 +++--- .../kotlin/sdk/types/CompletionTest.kt | 38 +-- .../kotlin/sdk/types/ContentTest.kt | 88 ++++--- .../kotlin/sdk/types/ElicitationTest.kt | 19 +- .../kotlin/sdk/types/InitializeTest.kt | 28 +- .../kotlin/sdk/types/JsonRpcTest.kt | 70 +++-- .../kotlin/sdk/types/LoggingTest.kt | 29 +- .../kotlin/sdk/types/NotificationTest.kt | 93 ++++--- .../kotlin/sdk/types/PingRequestTest.kt | 25 +- .../kotlin/sdk/types/PromptsTest.kt | 104 +++++--- .../kotlin/sdk/types/RequestTest.kt | 38 +-- .../kotlin/sdk/types/ResourcesTest.kt | 146 ++++++----- .../kotlin/sdk/types/RootsTest.kt | 57 ++-- .../kotlin/sdk/types/SamplingTest.kt | 58 ++-- .../kotlin/sdk/types/ToolsTest.kt | 90 ++++--- .../kotlin/sdk/types/dsl/SamplingDslTest.kt | 1 + test-utils/build.gradle.kts | 62 +++-- test-utils/detekt-baseline.xml | 1 - .../kotlin/test/utils/ktor.kt | 0 .../kotlin/test/utils/serializationUtils.kt | 88 +++++++ .../kotlin/test/utils/DisabledOnCI.kt | 0 .../kotlin/test/utils/Retry.kt | 3 +- .../kotlin/test/utils/TypeScriptRunner.kt | 2 + .../kotlin/test/utils/platform.jvm.kt} | 0 .../kotlin/test/utils/processBuilders.kt | 0 .../kotlin/test/utils/processUtils.kt | 0 32 files changed, 857 insertions(+), 558 deletions(-) delete mode 100644 kotlin-sdk-core/detekt-baseline-commonTestSourceSet.xml delete mode 100644 kotlin-sdk-core/detekt-baseline-test.xml rename test-utils/src/{main => commonMain}/kotlin/io/modelcontextprotocol/kotlin/test/utils/ktor.kt (100%) create mode 100644 test-utils/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/serializationUtils.kt rename test-utils/src/{main => jvmMain}/kotlin/io/modelcontextprotocol/kotlin/test/utils/DisabledOnCI.kt (100%) rename test-utils/src/{main => jvmMain}/kotlin/io/modelcontextprotocol/kotlin/test/utils/Retry.kt (96%) rename test-utils/src/{main => jvmMain}/kotlin/io/modelcontextprotocol/kotlin/test/utils/TypeScriptRunner.kt (98%) rename test-utils/src/{main/kotlin/io/modelcontextprotocol/kotlin/test/utils/platform.kt => jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/platform.jvm.kt} (100%) rename test-utils/src/{main => jvmMain}/kotlin/io/modelcontextprotocol/kotlin/test/utils/processBuilders.kt (100%) rename test-utils/src/{main => jvmMain}/kotlin/io/modelcontextprotocol/kotlin/test/utils/processUtils.kt (100%) diff --git a/build.gradle.kts b/build.gradle.kts index 09e9adb..f4ddd44 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -61,7 +61,7 @@ kover { } verify { rule { - minBound(65) + minBound(73) } } } diff --git a/kotlin-sdk-core/build.gradle.kts b/kotlin-sdk-core/build.gradle.kts index 40848b1..0e702c4 100644 --- a/kotlin-sdk-core/build.gradle.kts +++ b/kotlin-sdk-core/build.gradle.kts @@ -121,10 +121,8 @@ kotlin { commonTest { dependencies { + implementation(project(":test-utils")) implementation(kotlin("test")) - implementation(libs.kotlinx.coroutines.test) - implementation(libs.kotest.assertions.core) - implementation(libs.kotest.assertions.json) } } diff --git a/kotlin-sdk-core/detekt-baseline-commonTestSourceSet.xml b/kotlin-sdk-core/detekt-baseline-commonTestSourceSet.xml deleted file mode 100644 index 0a4c838..0000000 --- a/kotlin-sdk-core/detekt-baseline-commonTestSourceSet.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - EmptyFunctionBlock:ProtocolTest.kt:RecordingTransport${} - EmptyFunctionBlock:ProtocolTest.kt:TestProtocol${} - LongMethod:ElicitationTest.kt:ElicitationTest$@Test fun `should serialize ElicitRequest with requested schema` - LongMethod:SamplingDslTest.kt:SamplingDslTest$@Test fun `buildCreateMessageRequest should build with all fields` - LongMethod:SamplingTest.kt:SamplingTest$@Test fun `should serialize CreateMessageRequest with all fields` - LongMethod:ToolsTest.kt:ToolsTest$@Test fun `should serialize Tool with annotations and schemas` - - diff --git a/kotlin-sdk-core/detekt-baseline-test.xml b/kotlin-sdk-core/detekt-baseline-test.xml deleted file mode 100644 index 0a4c838..0000000 --- a/kotlin-sdk-core/detekt-baseline-test.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - EmptyFunctionBlock:ProtocolTest.kt:RecordingTransport${} - EmptyFunctionBlock:ProtocolTest.kt:TestProtocol${} - LongMethod:ElicitationTest.kt:ElicitationTest$@Test fun `should serialize ElicitRequest with requested schema` - LongMethod:SamplingDslTest.kt:SamplingDslTest$@Test fun `buildCreateMessageRequest should build with all fields` - LongMethod:SamplingTest.kt:SamplingTest$@Test fun `should serialize CreateMessageRequest with all fields` - LongMethod:ToolsTest.kt:ToolsTest$@Test fun `should serialize Tool with annotations and schemas` - - diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolTest.kt index 142c2af..b228071 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolTest.kt @@ -154,9 +154,15 @@ class ProtocolTest { } private class TestProtocol : Protocol(null) { - override fun assertCapabilityForMethod(method: Method) {} - override fun assertNotificationCapability(method: Method) {} - override fun assertRequestHandlerCapability(method: Method) {} + override fun assertCapabilityForMethod(method: Method) { + // noop + } + override fun assertNotificationCapability(method: Method) { + // noop + } + override fun assertRequestHandlerCapability(method: Method) { + // noop + } } private class RecordingTransport : Transport { @@ -164,7 +170,9 @@ private class RecordingTransport : Transport { private var onMessageCallback: (suspend (JSONRPCMessage) -> Unit)? = null private var onCloseCallback: (() -> Unit)? = null - override suspend fun start() {} + override suspend fun start() { + // noop + } override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { sentMessages.send(message) @@ -178,7 +186,9 @@ private class RecordingTransport : Transport { onCloseCallback = block } - override fun onError(block: (Throwable) -> Unit) {} + override fun onError(block: (Throwable) -> Unit) { + // noop + } override fun onMessage(block: suspend (JSONRPCMessage) -> Unit) { onMessageCallback = block diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CapabilitiesTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CapabilitiesTest.kt index c288cca..33d5653 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CapabilitiesTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CapabilitiesTest.kt @@ -1,6 +1,9 @@ package io.modelcontextprotocol.kotlin.sdk.types import io.kotest.assertions.json.shouldEqualJson +import io.modelcontextprotocol.kotlin.test.utils.verifyDeserialization +import io.modelcontextprotocol.kotlin.test.utils.verifySerialization +import io.modelcontextprotocol.kotlin.test.utils.verifySerializationRoundTrip import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.put import kotlin.test.Test @@ -15,14 +18,16 @@ class CapabilitiesTest { name = "test-server", version = "1.0.0", ) - val json = McpJson.encodeToString(implementation) - - json shouldEqualJson """ + verifySerialization( + implementation, + McpJson, + """ { "name": "test-server", "version": "1.0.0" } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -36,9 +41,10 @@ class CapabilitiesTest { Icon(src = "https://example.com/icon.png"), ), ) - val json = McpJson.encodeToString(implementation) - - json shouldEqualJson """ + verifySerialization( + implementation, + McpJson, + """ { "name": "test-server", "version": "1.0.0", @@ -48,7 +54,8 @@ class CapabilitiesTest { {"src": "https://example.com/icon.png"} ] } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -62,7 +69,7 @@ class CapabilitiesTest { } """.trimIndent() - val implementation = McpJson.decodeFromString(json) + val implementation = verifyDeserialization(McpJson, json) assertEquals("test-server", implementation.name) assertEquals("2.1.3-beta", implementation.version) @@ -79,7 +86,7 @@ class CapabilitiesTest { } """.trimIndent() - val implementation = McpJson.decodeFromString(json) + val implementation = verifyDeserialization(McpJson, json) assertEquals("minimal-server", implementation.name) assertEquals("0.1.0", implementation.version) @@ -97,13 +104,7 @@ class CapabilitiesTest { websiteUrl = "https://test.com", ) - val json = McpJson.encodeToString(original) - val decoded = McpJson.decodeFromString(json) - - assertEquals(original.name, decoded.name) - assertEquals(original.version, decoded.version) - assertEquals(original.title, decoded.title) - assertEquals(original.websiteUrl, decoded.websiteUrl) + verifySerializationRoundTrip(original, McpJson) } // ClientCapabilities tests @@ -120,13 +121,15 @@ class CapabilitiesTest { val capabilities = ClientCapabilities( sampling = ClientCapabilities.sampling, ) - val json = McpJson.encodeToString(capabilities) - - json shouldEqualJson """ + verifySerialization( + capabilities, + McpJson, + """ { "sampling": {} } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -134,13 +137,15 @@ class CapabilitiesTest { val capabilities = ClientCapabilities( roots = ClientCapabilities.Roots(), ) - val json = McpJson.encodeToString(capabilities) - - json shouldEqualJson """ + verifySerialization( + capabilities, + McpJson, + """ { "roots": {} } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -148,15 +153,17 @@ class CapabilitiesTest { val capabilities = ClientCapabilities( roots = ClientCapabilities.Roots(listChanged = true), ) - val json = McpJson.encodeToString(capabilities) - - json shouldEqualJson """ + verifySerialization( + capabilities, + McpJson, + """ { "roots": { "listChanged": true } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -164,13 +171,15 @@ class CapabilitiesTest { val capabilities = ClientCapabilities( elicitation = ClientCapabilities.elicitation, ) - val json = McpJson.encodeToString(capabilities) - - json shouldEqualJson """ + verifySerialization( + capabilities, + McpJson, + """ { "elicitation": {} } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -186,9 +195,10 @@ class CapabilitiesTest { val capabilities = ClientCapabilities( experimental = experimental, ) - val json = McpJson.encodeToString(capabilities) - - json shouldEqualJson """ + verifySerialization( + capabilities, + McpJson, + """ { "experimental": { "customFeature": { @@ -196,7 +206,8 @@ class CapabilitiesTest { } } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -210,9 +221,10 @@ class CapabilitiesTest { elicitation = ClientCapabilities.elicitation, experimental = experimental, ) - val json = McpJson.encodeToString(capabilities) - - json shouldEqualJson """ + verifySerialization( + capabilities, + McpJson, + """ { "sampling": {}, "roots": { @@ -225,7 +237,8 @@ class CapabilitiesTest { } } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -240,7 +253,7 @@ class CapabilitiesTest { } """.trimIndent() - val capabilities = McpJson.decodeFromString(json) + val capabilities = verifyDeserialization(McpJson, json) assertEquals(EmptyJsonObject, capabilities.sampling) assertEquals(true, capabilities.roots?.listChanged) @@ -251,7 +264,7 @@ class CapabilitiesTest { fun `should deserialize empty ClientCapabilities from JSON`() { val json = "{}" - val capabilities = McpJson.decodeFromString(json) + val capabilities = verifyDeserialization(McpJson, json) assertNull(capabilities.sampling) assertNull(capabilities.roots) @@ -267,12 +280,7 @@ class CapabilitiesTest { elicitation = ClientCapabilities.elicitation, ) - val json = McpJson.encodeToString(original) - val decoded = McpJson.decodeFromString(json) - - assertEquals(original.sampling, decoded.sampling) - assertEquals(original.roots?.listChanged, decoded.roots?.listChanged) - assertEquals(original.elicitation, decoded.elicitation) + verifySerializationRoundTrip(original, McpJson) } // ServerCapabilities tests @@ -289,13 +297,15 @@ class CapabilitiesTest { val capabilities = ServerCapabilities( tools = ServerCapabilities.Tools(), ) - val json = McpJson.encodeToString(capabilities) - - json shouldEqualJson """ + verifySerialization( + capabilities, + McpJson, + """ { "tools": {} } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -303,15 +313,17 @@ class CapabilitiesTest { val capabilities = ServerCapabilities( tools = ServerCapabilities.Tools(listChanged = true), ) - val json = McpJson.encodeToString(capabilities) - - json shouldEqualJson """ + verifySerialization( + capabilities, + McpJson, + """ { "tools": { "listChanged": true } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -319,13 +331,15 @@ class CapabilitiesTest { val capabilities = ServerCapabilities( resources = ServerCapabilities.Resources(), ) - val json = McpJson.encodeToString(capabilities) - - json shouldEqualJson """ + verifySerialization( + capabilities, + McpJson, + """ { "resources": {} } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -333,15 +347,17 @@ class CapabilitiesTest { val capabilities = ServerCapabilities( resources = ServerCapabilities.Resources(listChanged = true), ) - val json = McpJson.encodeToString(capabilities) - - json shouldEqualJson """ + verifySerialization( + capabilities, + McpJson, + """ { "resources": { "listChanged": true } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -349,15 +365,17 @@ class CapabilitiesTest { val capabilities = ServerCapabilities( resources = ServerCapabilities.Resources(subscribe = true), ) - val json = McpJson.encodeToString(capabilities) - - json shouldEqualJson """ + verifySerialization( + capabilities, + McpJson, + """ { "resources": { "subscribe": true } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -368,16 +386,18 @@ class CapabilitiesTest { subscribe = false, ), ) - val json = McpJson.encodeToString(capabilities) - - json shouldEqualJson """ + verifySerialization( + capabilities, + McpJson, + """ { "resources": { "listChanged": true, "subscribe": false } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -385,13 +405,15 @@ class CapabilitiesTest { val capabilities = ServerCapabilities( prompts = ServerCapabilities.Prompts(), ) - val json = McpJson.encodeToString(capabilities) - - json shouldEqualJson """ + verifySerialization( + capabilities, + McpJson, + """ { "prompts": {} } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -399,15 +421,17 @@ class CapabilitiesTest { val capabilities = ServerCapabilities( prompts = ServerCapabilities.Prompts(listChanged = false), ) - val json = McpJson.encodeToString(capabilities) - - json shouldEqualJson """ + verifySerialization( + capabilities, + McpJson, + """ { "prompts": { "listChanged": false } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -415,13 +439,15 @@ class CapabilitiesTest { val capabilities = ServerCapabilities( logging = ServerCapabilities.Logging, ) - val json = McpJson.encodeToString(capabilities) - - json shouldEqualJson """ + verifySerialization( + capabilities, + McpJson, + """ { "logging": {} } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -429,13 +455,15 @@ class CapabilitiesTest { val capabilities = ServerCapabilities( completions = ServerCapabilities.Completions, ) - val json = McpJson.encodeToString(capabilities) - - json shouldEqualJson """ + verifySerialization( + capabilities, + McpJson, + """ { "completions": {} } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -451,9 +479,10 @@ class CapabilitiesTest { val capabilities = ServerCapabilities( experimental = experimental, ) - val json = McpJson.encodeToString(capabilities) - - json shouldEqualJson """ + verifySerialization( + capabilities, + McpJson, + """ { "experimental": { "customCapability": { @@ -461,7 +490,8 @@ class CapabilitiesTest { } } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -480,9 +510,10 @@ class CapabilitiesTest { completions = ServerCapabilities.Completions, experimental = experimental, ) - val json = McpJson.encodeToString(capabilities) - - json shouldEqualJson """ + verifySerialization( + capabilities, + McpJson, + """ { "tools": { "listChanged": true @@ -502,7 +533,8 @@ class CapabilitiesTest { } } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -524,7 +556,7 @@ class CapabilitiesTest { } """.trimIndent() - val capabilities = McpJson.decodeFromString(json) + val capabilities = verifyDeserialization(McpJson, json) assertEquals(true, capabilities.tools?.listChanged) assertEquals(false, capabilities.resources?.listChanged) @@ -538,7 +570,7 @@ class CapabilitiesTest { fun `should deserialize empty ServerCapabilities from JSON`() { val json = "{}" - val capabilities = McpJson.decodeFromString(json) + val capabilities = verifyDeserialization(McpJson, json) assertNull(capabilities.tools) assertNull(capabilities.resources) @@ -560,14 +592,7 @@ class CapabilitiesTest { logging = ServerCapabilities.Logging, ) - val json = McpJson.encodeToString(original) - val decoded = McpJson.decodeFromString(json) - - assertEquals(original.tools?.listChanged, decoded.tools?.listChanged) - assertEquals(original.resources?.listChanged, decoded.resources?.listChanged) - assertEquals(original.resources?.subscribe, decoded.resources?.subscribe) - assertEquals(original.prompts?.listChanged, decoded.prompts?.listChanged) - assertEquals(original.logging, decoded.logging) + verifySerializationRoundTrip(original, McpJson) } // Additional nested type tests @@ -579,7 +604,7 @@ class CapabilitiesTest { } """.trimIndent() - val capabilities = McpJson.decodeFromString(json) + val capabilities = verifyDeserialization(McpJson, json) assertNull(capabilities.roots?.listChanged) } @@ -594,7 +619,7 @@ class CapabilitiesTest { } """.trimIndent() - val capabilities = McpJson.decodeFromString(json) + val capabilities = verifyDeserialization(McpJson, json) assertNull(capabilities.tools?.listChanged) assertNull(capabilities.resources?.listChanged) @@ -612,7 +637,7 @@ class CapabilitiesTest { } """.trimIndent() - val capabilities = McpJson.decodeFromString(json) + val capabilities = verifyDeserialization(McpJson, json) // Should not fail - additionalProperties are allowed assertEquals("customValue", capabilities.sampling?.get("customProperty")?.toString()?.trim('"')) @@ -628,7 +653,7 @@ class CapabilitiesTest { } """.trimIndent() - val capabilities = McpJson.decodeFromString(json) + val capabilities = verifyDeserialization(McpJson, json) // Should not fail - additionalProperties are allowed assertEquals("debug", capabilities.logging?.get("level")?.toString()?.trim('"')) diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CommonTypeTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CommonTypeTest.kt index 679d14a..44e88ac 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CommonTypeTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CommonTypeTest.kt @@ -1,10 +1,12 @@ package io.modelcontextprotocol.kotlin.sdk.types -import io.kotest.assertions.json.shouldEqualJson +import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder +import io.kotest.matchers.shouldBe +import io.modelcontextprotocol.kotlin.test.utils.verifyDeserialization +import io.modelcontextprotocol.kotlin.test.utils.verifySerialization +import io.modelcontextprotocol.kotlin.test.utils.verifySerializationRoundTrip import kotlin.test.Test import kotlin.test.assertEquals -import kotlin.test.assertIs -import kotlin.test.assertNotEquals import kotlin.test.assertNull import kotlin.test.assertTrue @@ -12,29 +14,30 @@ class CommonTypeTest { @Test fun `should have correct latest protocol version`() { - assertNotEquals("", LATEST_PROTOCOL_VERSION) - assertEquals("2025-06-18", LATEST_PROTOCOL_VERSION) + LATEST_PROTOCOL_VERSION shouldBe "2025-06-18" } @Test fun `should have correct supported protocol versions`() { - assertIs>(SUPPORTED_PROTOCOL_VERSIONS) - assertTrue(SUPPORTED_PROTOCOL_VERSIONS.contains(LATEST_PROTOCOL_VERSION)) - assertTrue(SUPPORTED_PROTOCOL_VERSIONS.contains("2025-03-26")) - assertTrue(SUPPORTED_PROTOCOL_VERSIONS.contains("2024-11-05")) - assertEquals(3, SUPPORTED_PROTOCOL_VERSIONS.size) + SUPPORTED_PROTOCOL_VERSIONS shouldContainExactlyInAnyOrder listOf( + LATEST_PROTOCOL_VERSION, + "2025-03-26", + "2024-11-05", + ) } @Test fun `should serialize Icon with minimal fields`() { val icon = Icon(src = "https://example.com/icon.png") - val json = McpJson.encodeToString(icon) - - json shouldEqualJson """ + verifySerialization( + icon, + McpJson, + """ { "src": "https://example.com/icon.png" } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -45,16 +48,18 @@ class CommonTypeTest { sizes = listOf("48x48", "96x96"), theme = Icon.Theme.Light, ) - val json = McpJson.encodeToString(icon) - - json shouldEqualJson """ + verifySerialization( + icon, + McpJson, + """ { "src": "https://example.com/icon.png", "mimeType": "image/png", "sizes": ["48x48", "96x96"], "theme": "light" } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -68,7 +73,7 @@ class CommonTypeTest { } """.trimIndent() - val icon = McpJson.decodeFromString(json) + val icon = verifyDeserialization(McpJson, json) assertEquals("https://example.com/icon.png", icon.src) assertEquals("image/png", icon.mimeType) @@ -80,7 +85,7 @@ class CommonTypeTest { fun `should deserialize Icon with minimal fields from JSON`() { val json = """{"src": "https://example.com/icon.png"}""" - val icon = McpJson.decodeFromString(json) + val icon = verifyDeserialization(McpJson, json) assertEquals("https://example.com/icon.png", icon.src) assertNull(icon.mimeType) @@ -111,8 +116,8 @@ class CommonTypeTest { @Test fun `should deserialize Role from lowercase strings`() { - val user = McpJson.decodeFromString("\"user\"") - val assistant = McpJson.decodeFromString("\"assistant\"") + val user = verifyDeserialization(McpJson, "\"user\"") + val assistant = verifyDeserialization(McpJson, "\"assistant\"") assertEquals(Role.User, user) assertEquals(Role.Assistant, assistant) @@ -126,28 +131,32 @@ class CommonTypeTest { lastModified = "2025-01-12T15:00:58Z", ) - val json = McpJson.encodeToString(annotations) - - json shouldEqualJson """ + verifySerialization( + annotations, + McpJson, + """ { "audience": ["user", "assistant"], "priority": 0.8, "lastModified": "2025-01-12T15:00:58Z" } - """.trimIndent() + """.trimIndent(), + ) } @Test fun `should serialize Annotations with minimal fields`() { val annotations = Annotations(priority = 0.5) - val json = McpJson.encodeToString(annotations) - - json shouldEqualJson """ + verifySerialization( + annotations, + McpJson, + """ { "priority": 0.5 } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -160,7 +169,7 @@ class CommonTypeTest { } """.trimIndent() - val annotations = McpJson.decodeFromString(json) + val annotations = verifyDeserialization(McpJson, json) assertEquals(listOf(Role.User), annotations.audience) assertEquals(0.9, annotations.priority) @@ -175,11 +184,6 @@ class CommonTypeTest { lastModified = "2025-01-12T15:00:58Z", ) - val json = McpJson.encodeToString(original) - val decoded = McpJson.decodeFromString(json) - - assertEquals(original.audience, decoded.audience) - assertEquals(original.priority, decoded.priority) - assertEquals(original.lastModified, decoded.lastModified) + verifySerializationRoundTrip(original, McpJson) } } diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CompletionTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CompletionTest.kt index 9a27ac5..03a76e4 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CompletionTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CompletionTest.kt @@ -1,6 +1,8 @@ package io.modelcontextprotocol.kotlin.sdk.types import io.kotest.assertions.json.shouldEqualJson +import io.modelcontextprotocol.kotlin.test.utils.verifyDeserialization +import io.modelcontextprotocol.kotlin.test.utils.verifySerialization import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.put @@ -25,9 +27,10 @@ class CompletionTest { ), ) - val json = McpJson.encodeToString(request) - - json shouldEqualJson """ + verifySerialization( + request, + McpJson, + """ { "method": "completion/complete", "params": { @@ -41,7 +44,8 @@ class CompletionTest { } } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -67,9 +71,10 @@ class CompletionTest { ), ) - val json = McpJson.encodeToString(request) - - json shouldEqualJson """ + verifySerialization( + request, + McpJson, + """ { "method": "completion/complete", "params": { @@ -92,7 +97,8 @@ class CompletionTest { } } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -121,7 +127,7 @@ class CompletionTest { } """.trimIndent() - val request = McpJson.decodeFromString(json) + val request = verifyDeserialization(McpJson, json) assertEquals(Method.Defined.CompletionComplete, request.method) val params = request.params @@ -148,9 +154,10 @@ class CompletionTest { }, ) - val json = McpJson.encodeToString(result) - - json shouldEqualJson """ + verifySerialization( + result, + McpJson, + """ { "completion": { "values": [ @@ -164,7 +171,8 @@ class CompletionTest { "source": "cache" } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -182,7 +190,7 @@ class CompletionTest { } """.trimIndent() - val result = McpJson.decodeFromString(json) + val result = verifyDeserialization(McpJson, json) assertEquals(listOf("README.md", "CONTRIBUTING.md"), result.completion.values) assertEquals(2, result.completion.total) assertEquals(false, result.completion.hasMore) @@ -209,7 +217,7 @@ class CompletionTest { } """.trimIndent() - val decoded = McpJson.decodeFromString(json) + val decoded = verifyDeserialization(McpJson, json) assertEquals(listOf("foo"), decoded.completion.values) assertNull(decoded.completion.total) assertNull(decoded.completion.hasMore) diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ContentTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ContentTest.kt index 2290dce..f110326 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ContentTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ContentTest.kt @@ -1,6 +1,7 @@ package io.modelcontextprotocol.kotlin.sdk.types -import io.kotest.assertions.json.shouldEqualJson +import io.modelcontextprotocol.kotlin.test.utils.verifyDeserialization +import io.modelcontextprotocol.kotlin.test.utils.verifySerialization import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.put @@ -15,14 +16,16 @@ class ContentTest { fun `should serialize TextContent with minimal fields`() { val content = TextContent(text = "Hello, MCP!") - val json = McpJson.encodeToString(content) - - json shouldEqualJson """ + verifySerialization( + content, + McpJson, + """ { "type": "text", "text": "Hello, MCP!" } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -33,9 +36,10 @@ class ContentTest { meta = buildJsonObject { put("origin", "assistant") }, ) - val json = McpJson.encodeToString(content) - - json shouldEqualJson """ + verifySerialization( + content, + McpJson, + """ { "type": "text", "text": "Need help?", @@ -46,12 +50,8 @@ class ContentTest { "origin": "assistant" } } - """.trimIndent() - - val decoded = McpJson.decodeFromString(json) as TextContent - assertEquals("Need help?", decoded.text) - assertEquals(listOf(Role.User), decoded.annotations?.audience) - assertEquals("assistant", decoded.meta?.get("origin")?.jsonPrimitive?.content) + """.trimIndent(), + ) } @Test @@ -61,15 +61,17 @@ class ContentTest { mimeType = "image/png", ) - val json = McpJson.encodeToString(content) - - json shouldEqualJson """ + verifySerialization( + content, + McpJson, + """ { "type": "image", "data": "aW1hZ2VEYXRh", "mimeType": "image/png" } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -83,7 +85,7 @@ class ContentTest { } """.trimIndent() - val content = McpJson.decodeFromString(json) as ImageContent + val content = verifyDeserialization(McpJson, json) as ImageContent assertEquals("Zm9vYmFy", content.data) assertEquals("image/jpeg", content.mimeType) assertEquals(0.6, content.annotations?.priority) @@ -98,9 +100,10 @@ class ContentTest { meta = buildJsonObject { put("durationMs", 1200) }, ) - val json = McpJson.encodeToString(content) - - json shouldEqualJson """ + verifySerialization( + content, + McpJson, + """ { "type": "audio", "data": "YXVkaW9kYXRh", @@ -109,7 +112,8 @@ class ContentTest { "durationMs": 1200 } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -123,7 +127,7 @@ class ContentTest { } """.trimIndent() - val content = McpJson.decodeFromString(json) as AudioContent + val content = verifyDeserialization(McpJson, json) as AudioContent assertEquals("audio/mpeg", content.mimeType) assertEquals("YmF6", content.data) assertEquals("user", content.meta?.get("speaker")?.jsonPrimitive?.content) @@ -136,15 +140,17 @@ class ContentTest { uri = "file:///workspace/README.md", ) - val json = McpJson.encodeToString(resource) - - json shouldEqualJson """ + verifySerialization( + resource, + McpJson, + """ { "type": "resource_link", "name": "README", "uri": "file:///workspace/README.md" } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -161,9 +167,10 @@ class ContentTest { meta = buildJsonObject { put("etag", "1234") }, ) - val json = McpJson.encodeToString(resource) - - json shouldEqualJson """ + verifySerialization( + resource, + McpJson, + """ { "type": "resource_link", "name": "README", @@ -184,7 +191,8 @@ class ContentTest { "etag": "1234" } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -199,7 +207,7 @@ class ContentTest { } """.trimIndent() - val resource = McpJson.decodeFromString(json) as ResourceLink + val resource = verifyDeserialization(McpJson, json) as ResourceLink assertEquals("config", resource.name) assertEquals("file:///config.yaml", resource.uri) assertEquals(512, resource.size) @@ -220,9 +228,10 @@ class ContentTest { meta = buildJsonObject { put("source", "analysis") }, ) - val json = McpJson.encodeToString(embedded) - - json shouldEqualJson """ + verifySerialization( + embedded, + McpJson, + """ { "type": "resource", "resource": { @@ -240,7 +249,8 @@ class ContentTest { "source": "analysis" } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -257,7 +267,7 @@ class ContentTest { } """.trimIndent() - val embedded = McpJson.decodeFromString(json) as EmbeddedResource + val embedded = verifyDeserialization(McpJson, json) as EmbeddedResource assertEquals(ContentTypes.EMBEDDED_RESOURCE, embedded.type) assertNull(embedded.annotations) assertEquals("base64", embedded.meta?.get("encoding")?.jsonPrimitive?.content) @@ -292,7 +302,7 @@ class ContentTest { ] """.trimIndent() - val content = McpJson.decodeFromString>(json) + val content = verifyDeserialization>(McpJson, json) assertEquals(3, content.size) assertIs(content[0]) assertIs(content[1]) diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ElicitationTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ElicitationTest.kt index c59c23f..0026ea0 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ElicitationTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ElicitationTest.kt @@ -1,6 +1,8 @@ package io.modelcontextprotocol.kotlin.sdk.types import io.kotest.assertions.json.shouldEqualJson +import io.modelcontextprotocol.kotlin.test.utils.verifyDeserialization +import io.modelcontextprotocol.kotlin.test.utils.verifySerialization import kotlinx.serialization.json.boolean import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.int @@ -16,6 +18,7 @@ import kotlin.test.assertNull class ElicitationTest { @Test + @Suppress("LongMethod") fun `should serialize ElicitRequest with requested schema`() { val request = ElicitRequest( ElicitRequestParams( @@ -54,9 +57,10 @@ class ElicitationTest { ), ) - val json = McpJson.encodeToString(request) - - json shouldEqualJson """ + verifySerialization( + request, + McpJson, + """ { "method": "elicitation/create", "params": { @@ -84,7 +88,8 @@ class ElicitationTest { } } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -111,7 +116,7 @@ class ElicitationTest { } """.trimIndent() - val request = McpJson.decodeFromString(json) + val request = verifyDeserialization(McpJson, json) assertEquals(Method.Defined.ElicitationCreate, request.method) val params = request.params @@ -163,7 +168,7 @@ class ElicitationTest { } """.trimIndent() - val decoded = McpJson.decodeFromString(json) + val decoded = verifyDeserialization(McpJson, json) assertEquals(ElicitResult.Action.Accept, decoded.action) val content = decoded.content @@ -188,7 +193,7 @@ class ElicitationTest { } """.trimIndent() - val result = McpJson.decodeFromString(json) + val result = verifyDeserialization(McpJson, json) assertEquals(ElicitResult.Action.Decline, result.action) assertNull(result.content) assertEquals("User skipped", result.meta?.get("reason")?.jsonPrimitive?.content) diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/InitializeTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/InitializeTest.kt index 6af7c4e..69c0878 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/InitializeTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/InitializeTest.kt @@ -1,6 +1,7 @@ package io.modelcontextprotocol.kotlin.sdk.types -import io.kotest.assertions.json.shouldEqualJson +import io.modelcontextprotocol.kotlin.test.utils.verifyDeserialization +import io.modelcontextprotocol.kotlin.test.utils.verifySerialization import kotlinx.serialization.json.boolean import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.jsonObject @@ -14,6 +15,7 @@ import kotlin.test.assertNull class InitializeTest { @Test + @Suppress("LongMethod") fun `should serialize InitializeRequest with capabilities and meta`() { val request = InitializeRequest( InitializeRequestParams( @@ -41,9 +43,10 @@ class InitializeTest { ), ) - val json = McpJson.encodeToString(request) - - json shouldEqualJson """ + verifySerialization( + request, + McpJson, + """ { "method": "initialize", "params": { @@ -73,7 +76,8 @@ class InitializeTest { } } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -106,7 +110,7 @@ class InitializeTest { } """.trimIndent() - val request = McpJson.decodeFromString(json) + val request = verifyDeserialization(McpJson, json) assertEquals(Method.Defined.Initialize, request.method) val params = request.params @@ -149,9 +153,10 @@ class InitializeTest { meta = buildJsonObject { put("issuedAt", "2025-01-12T15:00:58Z") }, ) - val json = McpJson.encodeToString(result) - - json shouldEqualJson """ + verifySerialization( + result, + McpJson, + """ { "protocolVersion": "2024-11-05", "capabilities": { @@ -180,7 +185,8 @@ class InitializeTest { "issuedAt": "2025-01-12T15:00:58Z" } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -201,7 +207,7 @@ class InitializeTest { } """.trimIndent() - val result = McpJson.decodeFromString(json) + val result = verifyDeserialization(McpJson, json) assertEquals("2025-03-26", result.protocolVersion) assertEquals("result-server", result.serverInfo.name) diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/JsonRpcTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/JsonRpcTest.kt index ce02cc0..db111fe 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/JsonRpcTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/JsonRpcTest.kt @@ -3,6 +3,8 @@ package io.modelcontextprotocol.kotlin.sdk.types import io.kotest.assertions.json.shouldEqualJson import io.kotest.matchers.shouldBe import io.kotest.matchers.types.shouldBeSameInstanceAs +import io.modelcontextprotocol.kotlin.test.utils.verifyDeserialization +import io.modelcontextprotocol.kotlin.test.utils.verifySerialization import kotlinx.serialization.json.boolean import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.int @@ -43,7 +45,8 @@ class JsonRpcTest { @Test fun `should convert JSONRPCRequest to Request`() { - val jsonRpc = McpJson.decodeFromString( + val jsonRpc = verifyDeserialization( + McpJson, """ { "id": 17, @@ -101,7 +104,8 @@ class JsonRpcTest { @Test fun `should convert JSONRPCNotification to Notification`() { - val jsonRpc = McpJson.decodeFromString( + val jsonRpc = verifyDeserialization( + McpJson, """ { "method": "notifications/message", @@ -147,9 +151,10 @@ class JsonRpcTest { }, ) - val json = McpJson.encodeToString(request) - - json shouldEqualJson """ + verifySerialization( + request, + McpJson, + """ { "id": "req-1", "method": "tools/list", @@ -159,7 +164,8 @@ class JsonRpcTest { }, "jsonrpc": "2.0" } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -175,7 +181,7 @@ class JsonRpcTest { } """.trimIndent() - val request = McpJson.decodeFromString(json) + val request = verifyDeserialization(McpJson, json) val id = request.id assertIs(id) @@ -196,9 +202,10 @@ class JsonRpcTest { }, ) - val json = McpJson.encodeToString(notification) - - json shouldEqualJson """ + verifySerialization( + notification, + McpJson, + """ { "method": "notifications/log", "params": { @@ -207,7 +214,8 @@ class JsonRpcTest { }, "jsonrpc": "2.0" } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -223,7 +231,7 @@ class JsonRpcTest { } """.trimIndent() - val notification = McpJson.decodeFromString(json) + val notification = verifyDeserialization(McpJson, json) assertEquals("notifications/progress", notification.method) val params = notification.params?.jsonObject assertNotNull(params) @@ -240,9 +248,10 @@ class JsonRpcTest { ), ) - val json = McpJson.encodeToString(response) - - json shouldEqualJson """ + verifySerialization( + response, + McpJson, + """ { "id": "call-1", "result": { @@ -252,7 +261,8 @@ class JsonRpcTest { }, "jsonrpc": "2.0" } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -269,7 +279,7 @@ class JsonRpcTest { } """.trimIndent() - val response = McpJson.decodeFromString(json) + val response = verifyDeserialization(McpJson, json) val result = response.result assertIs(result) assertEquals(RequestId.NumberId(7L), response.id) @@ -289,9 +299,10 @@ class JsonRpcTest { ), ) - val json = McpJson.encodeToString(error) - - json shouldEqualJson """ + verifySerialization( + error, + McpJson, + """ { "id": 99, "error": { @@ -303,7 +314,8 @@ class JsonRpcTest { }, "jsonrpc": "2.0" } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -323,7 +335,7 @@ class JsonRpcTest { } """.trimIndent() - val error = McpJson.decodeFromString(json) + val error = verifyDeserialization(McpJson, json) assertEquals(RequestId("req-404"), error.id) assertEquals(RPCError.ErrorCode.INVALID_PARAMS, error.error.code) assertEquals("Invalid params", error.error.message) @@ -348,7 +360,7 @@ class JsonRpcTest { } """.trimIndent() - val message = McpJson.decodeFromString(json) + val message = verifyDeserialization(McpJson, json) val request = assertIs(message) assertEquals("sampling/create", request.method) val params = request.params?.jsonObject @@ -373,7 +385,7 @@ class JsonRpcTest { } """.trimIndent() - val message = McpJson.decodeFromString(json) + val message = verifyDeserialization(McpJson, json) val error = assertIs(message) assertEquals(RPCError.ErrorCode.REQUEST_TIMEOUT, error.error.code) val data = error.error.data?.jsonObject @@ -388,9 +400,10 @@ class JsonRpcTest { params = buildJsonObject { put("message", "Polymorphic") }, ) - val json = McpJson.encodeToString(message) - - json shouldEqualJson """ + verifySerialization( + message, + McpJson, + """ { "method": "notifications/log", "params": { @@ -398,7 +411,8 @@ class JsonRpcTest { }, "jsonrpc": "2.0" } - """.trimIndent() + """.trimIndent(), + ) } @Test diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/LoggingTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/LoggingTest.kt index 2b8c61a..bc03a47 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/LoggingTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/LoggingTest.kt @@ -1,6 +1,7 @@ package io.modelcontextprotocol.kotlin.sdk.types -import io.kotest.assertions.json.shouldEqualJson +import io.modelcontextprotocol.kotlin.test.utils.verifyDeserialization +import io.modelcontextprotocol.kotlin.test.utils.verifySerialization import kotlinx.serialization.json.boolean import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.jsonObject @@ -47,9 +48,10 @@ class LoggingTest { ), ) - val json = McpJson.encodeToString(request) - - json shouldEqualJson """ + verifySerialization( + request, + McpJson, + """ { "method": "logging/setLevel", "params": { @@ -59,7 +61,8 @@ class LoggingTest { } } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -76,7 +79,7 @@ class LoggingTest { } """.trimIndent() - val request = McpJson.decodeFromString(json) + val request = verifyDeserialization(McpJson, json) assertEquals(Method.Defined.LoggingSetLevel, request.method) assertEquals(LoggingLevel.Alert, request.params.level) assertEquals(ProgressToken(1001), request.params.meta?.progressToken) @@ -93,9 +96,10 @@ class LoggingTest { ), ) - val json = McpJson.encodeToString(notification) - - json shouldEqualJson """ + verifySerialization( + notification, + McpJson, + """ { "method": "notifications/message", "params": { @@ -109,7 +113,8 @@ class LoggingTest { } } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -124,7 +129,7 @@ class LoggingTest { } """.trimIndent() - val notification = McpJson.decodeFromString(json) + val notification = verifyDeserialization(McpJson, json) assertEquals(Method.Defined.NotificationsMessage, notification.method) val params = notification.params @@ -153,7 +158,7 @@ class LoggingTest { } """.trimIndent() - val notification = McpJson.decodeFromString(json) + val notification = verifyDeserialization(McpJson, json) val params = notification.params assertEquals(LoggingLevel.Critical, params.level) val data = params.data.jsonObject diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/NotificationTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/NotificationTest.kt index bbd209d..b55a441 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/NotificationTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/NotificationTest.kt @@ -1,6 +1,7 @@ package io.modelcontextprotocol.kotlin.sdk.types -import io.kotest.assertions.json.shouldEqualJson +import io.modelcontextprotocol.kotlin.test.utils.verifyDeserialization +import io.modelcontextprotocol.kotlin.test.utils.verifySerialization import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.put @@ -20,9 +21,10 @@ class NotificationTest { ), ) - val json = McpJson.encodeToString(notification) - - json shouldEqualJson """ + verifySerialization( + notification, + McpJson, + """ { "method": "notifications/cancelled", "params": { @@ -33,7 +35,8 @@ class NotificationTest { } } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -48,7 +51,7 @@ class NotificationTest { } """.trimIndent() - val notification = McpJson.decodeFromString(json) + val notification = verifyDeserialization(McpJson, json) val params = notification.params assertEquals(Method.Defined.NotificationsCancelled, notification.method) @@ -65,9 +68,10 @@ class NotificationTest { ), ) - val json = McpJson.encodeToString(notification) - - json shouldEqualJson """ + verifySerialization( + notification, + McpJson, + """ { "method": "notifications/initialized", "params": { @@ -76,7 +80,8 @@ class NotificationTest { } } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -87,7 +92,7 @@ class NotificationTest { } """.trimIndent() - val notification = McpJson.decodeFromString(json) + val notification = verifyDeserialization(McpJson, json) assertEquals(Method.Defined.NotificationsInitialized, notification.method) assertNull(notification.params) @@ -105,9 +110,10 @@ class NotificationTest { ), ) - val json = McpJson.encodeToString(notification) - - json shouldEqualJson """ + verifySerialization( + notification, + McpJson, + """ { "method": "notifications/progress", "params": { @@ -120,7 +126,8 @@ class NotificationTest { } } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -135,7 +142,7 @@ class NotificationTest { } """.trimIndent() - val notification = McpJson.decodeFromString(json) + val notification = verifyDeserialization(McpJson, json) val params = notification.params assertEquals(Method.Defined.NotificationsProgress, notification.method) @@ -155,9 +162,10 @@ class NotificationTest { ), ) - val json = McpJson.encodeToString(notification) - - json shouldEqualJson """ + verifySerialization( + notification, + McpJson, + """ { "method": "notifications/resources/updated", "params": { @@ -167,7 +175,8 @@ class NotificationTest { } } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -184,7 +193,7 @@ class NotificationTest { } """.trimIndent() - val notification = McpJson.decodeFromString(json) + val notification = verifyDeserialization(McpJson, json) val params = notification.params assertEquals(Method.Defined.NotificationsResourcesUpdated, notification.method) @@ -200,9 +209,10 @@ class NotificationTest { ), ) - val json = McpJson.encodeToString(notification) - - json shouldEqualJson """ + verifySerialization( + notification, + McpJson, + """ { "method": "notifications/prompts/list_changed", "params": { @@ -211,7 +221,8 @@ class NotificationTest { } } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -222,7 +233,7 @@ class NotificationTest { } """.trimIndent() - val notification = McpJson.decodeFromString(json) + val notification = verifyDeserialization(McpJson, json) assertEquals(Method.Defined.NotificationsPromptsListChanged, notification.method) assertNull(notification.params) @@ -236,9 +247,10 @@ class NotificationTest { ), ) - val json = McpJson.encodeToString(notification) - - json shouldEqualJson """ + verifySerialization( + notification, + McpJson, + """ { "method": "notifications/resources/list_changed", "params": { @@ -247,7 +259,8 @@ class NotificationTest { } } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -258,9 +271,10 @@ class NotificationTest { ), ) - val json = McpJson.encodeToString(notification) - - json shouldEqualJson """ + verifySerialization( + notification, + McpJson, + """ { "method": "notifications/roots/list_changed", "params": { @@ -269,7 +283,8 @@ class NotificationTest { } } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -280,9 +295,10 @@ class NotificationTest { ), ) - val json = McpJson.encodeToString(notification) - - json shouldEqualJson """ + verifySerialization( + notification, + McpJson, + """ { "method": "notifications/tools/list_changed", "params": { @@ -291,6 +307,7 @@ class NotificationTest { } } } - """.trimIndent() + """.trimIndent(), + ) } } diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PingRequestTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PingRequestTest.kt index 070d58e..72bddb0 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PingRequestTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PingRequestTest.kt @@ -1,6 +1,7 @@ package io.modelcontextprotocol.kotlin.sdk.types -import io.kotest.assertions.json.shouldEqualJson +import io.modelcontextprotocol.kotlin.test.utils.verifyDeserialization +import io.modelcontextprotocol.kotlin.test.utils.verifySerialization import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.int import kotlinx.serialization.json.jsonObject @@ -17,13 +18,15 @@ class PingRequestTest { fun `should serialize PingRequest without params`() { val request = PingRequest() - val json = McpJson.encodeToString(request) - - json shouldEqualJson """ + verifySerialization( + request, + McpJson, + """ { "method": "ping" } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -36,9 +39,10 @@ class PingRequestTest { ), ) - val json = McpJson.encodeToString(request) - - json shouldEqualJson """ + verifySerialization( + request, + McpJson, + """ { "method": "ping", "params": { @@ -47,7 +51,8 @@ class PingRequestTest { } } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -84,7 +89,7 @@ class PingRequestTest { } """.trimIndent() - val jsonRpc = McpJson.decodeFromString(json) + val jsonRpc = verifyDeserialization(McpJson, json) val request = jsonRpc.fromJSON() val pingRequest = assertIs(request) diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PromptsTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PromptsTest.kt index 01eb01c..d7101d3 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PromptsTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PromptsTest.kt @@ -1,6 +1,8 @@ package io.modelcontextprotocol.kotlin.sdk.types import io.kotest.assertions.json.shouldEqualJson +import io.modelcontextprotocol.kotlin.test.utils.verifyDeserialization +import io.modelcontextprotocol.kotlin.test.utils.verifySerialization import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.double import kotlinx.serialization.json.int @@ -18,13 +20,15 @@ class PromptsTest { fun `should serialize PromptArgument with minimal fields`() { val argument = PromptArgument(name = "repository") - val json = McpJson.encodeToString(argument) - - json shouldEqualJson """ + verifySerialization( + argument, + McpJson, + """ { "name": "repository" } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -36,16 +40,18 @@ class PromptsTest { title = "Language", ) - val json = McpJson.encodeToString(argument) - - json shouldEqualJson """ + verifySerialization( + argument, + McpJson, + """ { "name": "language", "description": "Programming language to use", "required": true, "title": "Language" } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -69,9 +75,10 @@ class PromptsTest { meta = buildJsonObject { put("category", "status-report") }, ) - val json = McpJson.encodeToString(prompt) - - json shouldEqualJson """ + verifySerialization( + prompt, + McpJson, + """ { "name": "summarize_update", "description": "Summarize the latest repository changes.", @@ -97,7 +104,8 @@ class PromptsTest { "category": "status-report" } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -127,7 +135,7 @@ class PromptsTest { } """.trimIndent() - val prompt = McpJson.decodeFromString(json) + val prompt = verifyDeserialization(McpJson, json) assertEquals("code_review", prompt.name) assertEquals("Generate a succinct code review.", prompt.description) @@ -151,9 +159,10 @@ class PromptsTest { content = TextContent(text = "Provide a concise summary of the changes."), ) - val json = McpJson.encodeToString(message) - - json shouldEqualJson """ + verifySerialization( + message, + McpJson, + """ { "role": "assistant", "content": { @@ -161,7 +170,8 @@ class PromptsTest { "text": "Provide a concise summary of the changes." } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -180,7 +190,7 @@ class PromptsTest { } """.trimIndent() - val message = McpJson.decodeFromString(json) + val message = verifyDeserialization(McpJson, json) assertEquals(Role.User, message.role) val content = message.content @@ -203,7 +213,7 @@ class PromptsTest { } """.trimIndent() - val decoded = McpJson.decodeFromString(json) + val decoded = verifyDeserialization(McpJson, json) val promptReference = assertIs(decoded) assertEquals("daily-summary", promptReference.name) assertEquals("Daily Summary", promptReference.title) @@ -222,9 +232,10 @@ class PromptsTest { ), ) - val json = McpJson.encodeToString(request) - - json shouldEqualJson """ + verifySerialization( + request, + McpJson, + """ { "method": "prompts/get", "params": { @@ -237,7 +248,8 @@ class PromptsTest { } } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -258,7 +270,7 @@ class PromptsTest { } """.trimIndent() - val request = McpJson.decodeFromString(json) + val request = verifyDeserialization(McpJson, json) assertEquals(Method.Defined.PromptsGet, request.method) val params = request.params @@ -287,9 +299,10 @@ class PromptsTest { meta = buildJsonObject { put("generatedAt", "2025-01-12T15:00:58Z") }, ) - val json = McpJson.encodeToString(result) - - json shouldEqualJson """ + verifySerialization( + result, + McpJson, + """ { "messages": [ { @@ -312,7 +325,8 @@ class PromptsTest { "generatedAt": "2025-01-12T15:00:58Z" } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -332,7 +346,7 @@ class PromptsTest { } """.trimIndent() - val result = McpJson.decodeFromString(json) + val result = verifyDeserialization(McpJson, json) val messages = result.messages assertEquals(1, messages.size) @@ -354,9 +368,10 @@ class PromptsTest { ), ) - val json = McpJson.encodeToString(request) - - json shouldEqualJson """ + verifySerialization( + request, + McpJson, + """ { "method": "prompts/list", "params": { @@ -366,20 +381,23 @@ class PromptsTest { } } } - """.trimIndent() + """.trimIndent(), + ) } @Test fun `should serialize ListPromptsRequest without params`() { val request = ListPromptsRequest() - val json = McpJson.encodeToString(request) - - json shouldEqualJson """ + verifySerialization( + request, + McpJson, + """ { "method": "prompts/list" } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -393,9 +411,10 @@ class PromptsTest { meta = buildJsonObject { put("page", 1) }, ) - val json = McpJson.encodeToString(result) - - json shouldEqualJson """ + verifySerialization( + result, + McpJson, + """ { "prompts": [ {"name": "morning-briefing"}, @@ -406,7 +425,8 @@ class PromptsTest { "page": 1 } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -433,7 +453,7 @@ class PromptsTest { } """.trimIndent() - val result = McpJson.decodeFromString(json) + val result = verifyDeserialization(McpJson, json) val prompts = result.prompts assertEquals(1, prompts.size) diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/RequestTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/RequestTest.kt index c42589e..1d6da19 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/RequestTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/RequestTest.kt @@ -2,6 +2,8 @@ package io.modelcontextprotocol.kotlin.sdk.types import io.kotest.assertions.json.shouldEqualJson import io.kotest.matchers.shouldBe +import io.modelcontextprotocol.kotlin.test.utils.verifyDeserialization +import io.modelcontextprotocol.kotlin.test.utils.verifySerialization import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.double import kotlinx.serialization.json.jsonPrimitive @@ -41,16 +43,18 @@ class RequestTest { ), ) - val json = McpJson.encodeToString(params) - - json shouldEqualJson """ + verifySerialization( + params, + McpJson, + """ { "_meta": { "progressToken": "base-42", "origin": "test-suite" } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -64,7 +68,7 @@ class RequestTest { } """.trimIndent() - val params = McpJson.decodeFromString(json) + val params = verifyDeserialization(McpJson, json) val meta = params.meta assertNotNull(meta) @@ -79,16 +83,18 @@ class RequestTest { meta = RequestMeta(buildJsonObject { put("progressToken", "page-req") }), ) - val json = McpJson.encodeToString(params) - - json shouldEqualJson """ + verifySerialization( + params, + McpJson, + """ { "cursor": "cursor-1", "_meta": { "progressToken": "page-req" } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -102,7 +108,7 @@ class RequestTest { } """.trimIndent() - val params = McpJson.decodeFromString(json) + val params = verifyDeserialization(McpJson, json) assertEquals("cursor-2", params.cursor) assertEquals(ProgressToken(99), params.meta?.progressToken) @@ -163,22 +169,24 @@ class RequestTest { }, ) - val json = McpJson.encodeToString(result) - - json shouldEqualJson """ + verifySerialization( + result, + McpJson, + """ { "_meta": { "processedBy": "worker-1" } } - """.trimIndent() + """.trimIndent(), + ) } @Test fun `should deserialize EmptyResult without meta`() { val json = "{}" - val result = McpJson.decodeFromString(json) + val result = verifyDeserialization(McpJson, json) assertNull(result.meta) } diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ResourcesTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ResourcesTest.kt index 0313b75..e47a0f5 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ResourcesTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ResourcesTest.kt @@ -1,6 +1,8 @@ package io.modelcontextprotocol.kotlin.sdk.types import io.kotest.assertions.json.shouldEqualJson +import io.modelcontextprotocol.kotlin.test.utils.verifyDeserialization +import io.modelcontextprotocol.kotlin.test.utils.verifySerialization import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.int import kotlinx.serialization.json.jsonPrimitive @@ -20,14 +22,16 @@ class ResourcesTest { name = "README", ) - val json = McpJson.encodeToString(resource) - - json shouldEqualJson """ + verifySerialization( + resource, + McpJson, + """ { "uri": "file:///workspace/README.md", "name": "README" } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -46,9 +50,10 @@ class ResourcesTest { meta = buildJsonObject { put("etag", "abc123") }, ) - val json = McpJson.encodeToString(resource) - - json shouldEqualJson """ + verifySerialization( + resource, + McpJson, + """ { "uri": "file:///workspace/CHANGELOG.md", "name": "CHANGELOG", @@ -67,7 +72,8 @@ class ResourcesTest { "etag": "abc123" } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -92,7 +98,7 @@ class ResourcesTest { } """.trimIndent() - val resource = McpJson.decodeFromString(json) + val resource = verifyDeserialization(McpJson, json) assertEquals("file:///config/settings.json", resource.uri) assertEquals("config-settings", resource.name) @@ -122,9 +128,10 @@ class ResourcesTest { meta = buildJsonObject { put("requiresAuth", true) }, ) - val json = McpJson.encodeToString(template) - - json shouldEqualJson """ + verifySerialization( + template, + McpJson, + """ { "uriTemplate": "file:///workspace/{path}", "name": "workspace-file", @@ -145,7 +152,8 @@ class ResourcesTest { "requiresAuth": true } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -161,7 +169,7 @@ class ResourcesTest { } """.trimIndent() - val template = McpJson.decodeFromString(json) + val template = verifyDeserialization(McpJson, json) assertEquals("db://users/{id}", template.uriTemplate) assertEquals("user-record", template.name) @@ -183,7 +191,7 @@ class ResourcesTest { } """.trimIndent() - val decoded = McpJson.decodeFromString(json) + val decoded = verifyDeserialization(McpJson, json) val templateReference = assertIs(decoded) assertEquals("file:///workspace/{path}", templateReference.uri) assertEquals(ReferenceType.ResourceTemplate, templateReference.type) @@ -198,9 +206,10 @@ class ResourcesTest { meta = buildJsonObject { put("encoding", "utf-8") }, ) - val json = McpJson.encodeToString(contents) - - json shouldEqualJson """ + verifySerialization( + contents, + McpJson, + """ { "text": "Hello, MCP!", "uri": "file:///workspace/hello.txt", @@ -209,7 +218,8 @@ class ResourcesTest { "encoding": "utf-8" } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -225,7 +235,7 @@ class ResourcesTest { } """.trimIndent() - val contents = McpJson.decodeFromString(json) + val contents = verifyDeserialization(McpJson, json) val blobContents = assertIs(contents) assertEquals("YmluYXJ5ZGF0YQ==", blobContents.blob) assertEquals("file:///workspace/logo.png", blobContents.uri) @@ -240,14 +250,16 @@ class ResourcesTest { mimeType = "application/octet-stream", ) - val json = McpJson.encodeToString(contents) - - json shouldEqualJson """ + verifySerialization( + contents, + McpJson, + """ { "uri": "custom://resource/42", "mimeType": "application/octet-stream" } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -259,9 +271,10 @@ class ResourcesTest { ), ) - val json = McpJson.encodeToString(request) - - json shouldEqualJson """ + verifySerialization( + request, + McpJson, + """ { "method": "resources/list", "params": { @@ -271,20 +284,23 @@ class ResourcesTest { } } } - """.trimIndent() + """.trimIndent(), + ) } @Test fun `should serialize ListResourcesRequest without params`() { val request = ListResourcesRequest() - val json = McpJson.encodeToString(request) - - json shouldEqualJson """ + verifySerialization( + request, + McpJson, + """ { "method": "resources/list" } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -298,9 +314,10 @@ class ResourcesTest { meta = buildJsonObject { put("page", 1) }, ) - val json = McpJson.encodeToString(result) - - json shouldEqualJson """ + verifySerialization( + result, + McpJson, + """ { "resources": [ {"uri": "file:///workspace/README.md", "name": "README"}, @@ -311,7 +328,8 @@ class ResourcesTest { "page": 1 } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -332,7 +350,7 @@ class ResourcesTest { } """.trimIndent() - val result = McpJson.decodeFromString(json) + val result = verifyDeserialization(McpJson, json) assertEquals("cursor-next", result.nextCursor) val resources = result.resources @@ -353,9 +371,10 @@ class ResourcesTest { ), ) - val json = McpJson.encodeToString(request) - - json shouldEqualJson """ + verifySerialization( + request, + McpJson, + """ { "method": "resources/read", "params": { @@ -365,7 +384,8 @@ class ResourcesTest { } } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -390,7 +410,7 @@ class ResourcesTest { } """.trimIndent() - val result = McpJson.decodeFromString(json) + val result = verifyDeserialization(McpJson, json) val contents = result.contents assertEquals(2, contents.size) @@ -410,9 +430,10 @@ class ResourcesTest { ), ) - val json = McpJson.encodeToString(request) - - json shouldEqualJson """ + verifySerialization( + request, + McpJson, + """ { "method": "resources/subscribe", "params": { @@ -422,7 +443,8 @@ class ResourcesTest { } } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -434,9 +456,10 @@ class ResourcesTest { ), ) - val json = McpJson.encodeToString(request) - - json shouldEqualJson """ + verifySerialization( + request, + McpJson, + """ { "method": "resources/unsubscribe", "params": { @@ -446,7 +469,8 @@ class ResourcesTest { } } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -458,9 +482,10 @@ class ResourcesTest { ), ) - val json = McpJson.encodeToString(request) - - json shouldEqualJson """ + verifySerialization( + request, + McpJson, + """ { "method": "resources/templates/list", "params": { @@ -470,7 +495,8 @@ class ResourcesTest { } } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -483,9 +509,10 @@ class ResourcesTest { meta = buildJsonObject { put("page", 3) }, ) - val json = McpJson.encodeToString(result) - - json shouldEqualJson """ + verifySerialization( + result, + McpJson, + """ { "resourceTemplates": [ { @@ -498,7 +525,8 @@ class ResourcesTest { "page": 3 } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -522,7 +550,7 @@ class ResourcesTest { } """.trimIndent() - val result = McpJson.decodeFromString(json) + val result = verifyDeserialization(McpJson, json) assertEquals("cursor-next", result.nextCursor) val templates = result.resourceTemplates diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/RootsTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/RootsTest.kt index 3f1b8d6..2350c25 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/RootsTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/RootsTest.kt @@ -1,6 +1,7 @@ package io.modelcontextprotocol.kotlin.sdk.types -import io.kotest.assertions.json.shouldEqualJson +import io.modelcontextprotocol.kotlin.test.utils.verifyDeserialization +import io.modelcontextprotocol.kotlin.test.utils.verifySerialization import kotlinx.serialization.json.boolean import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.jsonPrimitive @@ -15,13 +16,15 @@ class RootsTest { fun `should serialize Root with minimal fields`() { val root = Root(uri = "file:///workspace/project") - val json = McpJson.encodeToString(root) - - json shouldEqualJson """ + verifySerialization( + root, + McpJson, + """ { "uri": "file:///workspace/project" } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -32,9 +35,10 @@ class RootsTest { meta = buildJsonObject { put("writable", true) }, ) - val json = McpJson.encodeToString(root) - - json shouldEqualJson """ + verifySerialization( + root, + McpJson, + """ { "uri": "file:///workspace/docs", "name": "Docs", @@ -42,7 +46,8 @@ class RootsTest { "writable": true } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -57,7 +62,7 @@ class RootsTest { } """.trimIndent() - val root = McpJson.decodeFromString(json) + val root = verifyDeserialization(McpJson, json) assertEquals("file:///workspace/code", root.uri) assertEquals("Code", root.name) @@ -74,9 +79,10 @@ class RootsTest { ), ) - val json = McpJson.encodeToString(request) - - json shouldEqualJson """ + verifySerialization( + request, + McpJson, + """ { "method": "roots/list", "params": { @@ -85,20 +91,23 @@ class RootsTest { } } } - """.trimIndent() + """.trimIndent(), + ) } @Test fun `should serialize ListRootsRequest without params`() { val request = ListRootsRequest() - val json = McpJson.encodeToString(request) - - json shouldEqualJson """ + verifySerialization( + request, + McpJson, + """ { "method": "roots/list" } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -111,9 +120,10 @@ class RootsTest { meta = buildJsonObject { put("issuedAt", "2025-01-12T15:00:58Z") }, ) - val json = McpJson.encodeToString(result) - - json shouldEqualJson """ + verifySerialization( + result, + McpJson, + """ { "roots": [ {"uri": "file:///workspace/project", "name": "Project"}, @@ -123,7 +133,8 @@ class RootsTest { "issuedAt": "2025-01-12T15:00:58Z" } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -142,7 +153,7 @@ class RootsTest { } """.trimIndent() - val result = McpJson.decodeFromString(json) + val result = verifyDeserialization(McpJson, json) val roots = result.roots assertEquals(1, roots.size) diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/SamplingTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/SamplingTest.kt index 7dc3fe6..696f322 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/SamplingTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/SamplingTest.kt @@ -1,6 +1,7 @@ package io.modelcontextprotocol.kotlin.sdk.types -import io.kotest.assertions.json.shouldEqualJson +import io.modelcontextprotocol.kotlin.test.utils.verifyDeserialization +import io.modelcontextprotocol.kotlin.test.utils.verifySerialization import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.double import kotlinx.serialization.json.jsonPrimitive @@ -17,13 +18,15 @@ class SamplingTest { fun `should serialize ModelHint`() { val hint = ModelHint(name = "claude-3-5-sonnet") - val json = McpJson.encodeToString(hint) - - json shouldEqualJson """ + verifySerialization( + hint, + McpJson, + """ { "name": "claude-3-5-sonnet" } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -35,9 +38,10 @@ class SamplingTest { intelligencePriority = 1.0, ) - val json = McpJson.encodeToString(preferences) - - json shouldEqualJson """ + verifySerialization( + preferences, + McpJson, + """ { "hints": [ {"name": "haiku"}, @@ -47,7 +51,8 @@ class SamplingTest { "speedPriority": 0.75, "intelligencePriority": 1.0 } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -64,9 +69,10 @@ class SamplingTest { content = TextContent(text = "Summarize the latest release."), ) - val json = McpJson.encodeToString(message) - - json shouldEqualJson """ + verifySerialization( + message, + McpJson, + """ { "role": "user", "content": { @@ -74,10 +80,12 @@ class SamplingTest { "text": "Summarize the latest release." } } - """.trimIndent() + """.trimIndent(), + ) } @Test + @Suppress("LongMethod") fun `should serialize CreateMessageRequest with all fields`() { val request = CreateMessageRequest( CreateMessageRequestParams( @@ -105,9 +113,10 @@ class SamplingTest { ), ) - val json = McpJson.encodeToString(request) - - json shouldEqualJson """ + verifySerialization( + request, + McpJson, + """ { "method": "sampling/createMessage", "params": { @@ -146,7 +155,8 @@ class SamplingTest { } } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -181,7 +191,7 @@ class SamplingTest { } """.trimIndent() - val request = McpJson.decodeFromString(json) + val request = verifyDeserialization(McpJson, json) assertEquals(Method.Defined.SamplingCreateMessage, request.method) val params = request.params @@ -209,9 +219,10 @@ class SamplingTest { meta = buildJsonObject { put("latencyMs", 850) }, ) - val json = McpJson.encodeToString(result) - - json shouldEqualJson """ + verifySerialization( + result, + McpJson, + """ { "role": "assistant", "content": { @@ -224,7 +235,8 @@ class SamplingTest { "latencyMs": 850 } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -244,7 +256,7 @@ class SamplingTest { } """.trimIndent() - val result = McpJson.decodeFromString(json) + val result = verifyDeserialization(McpJson, json) assertEquals(Role.Assistant, result.role) val text = assertIs(result.content) diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ToolsTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ToolsTest.kt index 447df4f..4663fbd 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ToolsTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ToolsTest.kt @@ -1,6 +1,7 @@ package io.modelcontextprotocol.kotlin.sdk.types -import io.kotest.assertions.json.shouldEqualJson +import io.modelcontextprotocol.kotlin.test.utils.verifyDeserialization +import io.modelcontextprotocol.kotlin.test.utils.verifySerialization import kotlinx.serialization.json.Json import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.int @@ -21,19 +22,22 @@ class ToolsTest { inputSchema = ToolSchema(), ) - val json = McpJson.encodeToString(tool) - - json shouldEqualJson """ + verifySerialization( + tool, + McpJson, + """ { "name": "search", "inputSchema": { "type": "object" } } - """.trimIndent() + """.trimIndent(), + ) } @Test + @Suppress("LongMethod") fun `should serialize Tool with annotations and schemas`() { val inputSchema = ToolSchema( properties = buildJsonObject { @@ -74,9 +78,10 @@ class ToolsTest { meta = buildJsonObject { put("category", "search") }, ) - val json = McpJson.encodeToString(tool) - - json shouldEqualJson """ + verifySerialization( + tool, + McpJson, + """ { "name": "web-search", "inputSchema": { @@ -113,7 +118,8 @@ class ToolsTest { "category": "search" } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -139,7 +145,7 @@ class ToolsTest { } """.trimIndent() - val tool = McpJson.decodeFromString(json) + val tool = verifyDeserialization(McpJson, json) assertEquals("translate", tool.name) assertEquals("Translate Text", tool.annotations?.title) @@ -156,9 +162,7 @@ class ToolsTest { fun `should serialize ToolSchema with defs`() { val tool = toolWithDefs() - val json = McpJson.encodeToString(tool) - - json shouldEqualJson toolWithDefsJson() + verifySerialization(tool, McpJson, toolWithDefsJson()) } @Test @@ -188,7 +192,7 @@ class ToolsTest { } """.trimIndent() - val tool = McpJson.decodeFromString(json) + val tool = verifyDeserialization(McpJson, json) val schema = tool.inputSchema val defs = schema.defs @@ -215,9 +219,10 @@ class ToolsTest { ), ) - val json = McpJson.encodeToString(request) - - json shouldEqualJson """ + verifySerialization( + request, + McpJson, + """ { "method": "tools/call", "params": { @@ -230,7 +235,8 @@ class ToolsTest { } } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -250,7 +256,7 @@ class ToolsTest { } """.trimIndent() - val request = McpJson.decodeFromString(json) + val request = verifyDeserialization(McpJson, json) assertEquals(Method.Defined.ToolsCall, request.method) val params = request.params @@ -273,9 +279,10 @@ class ToolsTest { meta = buildJsonObject { put("elapsedMs", 1200) }, ) - val json = McpJson.encodeToString(result) - - json shouldEqualJson """ + verifySerialization( + result, + McpJson, + """ { "content": [ { @@ -294,7 +301,8 @@ class ToolsTest { "elapsedMs": 1200 } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -318,7 +326,7 @@ class ToolsTest { } """.trimIndent() - val result = McpJson.decodeFromString(json) + val result = verifyDeserialization(McpJson, json) assertEquals(true, result.isError) val text = assertIs(result.content.first()) @@ -338,9 +346,10 @@ class ToolsTest { ), ) - val json = McpJson.encodeToString(request) - - json shouldEqualJson """ + verifySerialization( + request, + McpJson, + """ { "method": "tools/list", "params": { @@ -350,20 +359,23 @@ class ToolsTest { } } } - """.trimIndent() + """.trimIndent(), + ) } @Test fun `should serialize ListToolsRequest without params`() { val request = ListToolsRequest() - val json = McpJson.encodeToString(request) - - json shouldEqualJson """ + verifySerialization( + request, + McpJson, + """ { "method": "tools/list" } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -377,9 +389,10 @@ class ToolsTest { meta = buildJsonObject { put("page", 1) }, ) - val json = McpJson.encodeToString(result) - - json shouldEqualJson """ + verifySerialization( + result, + McpJson, + """ { "tools": [ { @@ -400,7 +413,8 @@ class ToolsTest { "page": 1 } } - """.trimIndent() + """.trimIndent(), + ) } @Test @@ -423,7 +437,7 @@ class ToolsTest { } """.trimIndent() - val result = McpJson.decodeFromString(json) + val result = verifyDeserialization(McpJson, json) assertEquals("cursor-next", result.nextCursor) val tools = result.tools @@ -483,7 +497,7 @@ class ToolsTest { @Test fun `should deserialize complex tool definition`() { val expected = weatherTool() - val actual = McpJson.decodeFromString(weatherToolJson()) + val actual = verifyDeserialization(McpJson, weatherToolJson()) assertEquals(expected, actual) } diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/SamplingDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/SamplingDslTest.kt index 5b60cfb..c53a400 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/SamplingDslTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/SamplingDslTest.kt @@ -28,6 +28,7 @@ import kotlin.test.Test @OptIn(ExperimentalMcpApi::class) class SamplingDslTest { @Test + @Suppress("LongMethod") fun `buildCreateMessageRequest should build with all fields`() { val request = buildCreateMessageRequest { maxTokens = 1000 diff --git a/test-utils/build.gradle.kts b/test-utils/build.gradle.kts index 346001a..91574e0 100644 --- a/test-utils/build.gradle.kts +++ b/test-utils/build.gradle.kts @@ -1,24 +1,56 @@ +@file:OptIn(ExperimentalWasmDsl::class) + +import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl + plugins { - kotlin("jvm") + id("mcp.multiplatform") } kotlin { - explicitApi() - dependencies { - api(kotlin("test")) - api(libs.kotest.assertions.core) - api(libs.kotest.assertions.json) - api(libs.kotlinx.coroutines.test) - api(libs.ktor.server.core) - api(libs.mockk) - api(libs.awaitility) - api(libs.junit.jupiter.params) - api(libs.kotlin.logging) - runtimeOnly(libs.slf4j.simple) + iosArm64() + iosX64() + iosSimulatorArm64() + watchosX64() + watchosArm64() + watchosSimulatorArm64() + tvosX64() + tvosArm64() + tvosSimulatorArm64() + js { + browser() + nodejs() + } + wasmJs { + browser() + nodejs() } - tasks.withType { - useJUnitPlatform() + sourceSets { + commonMain { + dependencies { + + api(kotlin("test")) + api(libs.kotest.assertions.core) + api(libs.kotest.assertions.json) + api(libs.kotlinx.serialization.json) + api(libs.kotlinx.coroutines.test) + api(libs.ktor.server.core) + api(libs.kotlin.logging) + } + } + + jvmMain { + dependencies { + api(libs.mockk) + api(libs.awaitility) + api(libs.junit.jupiter.params) + runtimeOnly(libs.slf4j.simple) + } + } } } + +tasks.withType { + useJUnitPlatform() +} diff --git a/test-utils/detekt-baseline.xml b/test-utils/detekt-baseline.xml index 390ccd0..5abd0b4 100644 --- a/test-utils/detekt-baseline.xml +++ b/test-utils/detekt-baseline.xml @@ -3,7 +3,6 @@ CyclomaticComplexMethod:Retry.kt:RetryExtension$private fun executeWithRetry - ReturnCount:TypeScriptRunner.kt:TypeScriptRunner$public fun installDependencies ThrowsCount:Retry.kt:RetryExtension$private fun executeWithRetry diff --git a/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/ktor.kt b/test-utils/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/ktor.kt similarity index 100% rename from test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/ktor.kt rename to test-utils/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/ktor.kt diff --git a/test-utils/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/serializationUtils.kt b/test-utils/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/serializationUtils.kt new file mode 100644 index 0000000..8f3d33a --- /dev/null +++ b/test-utils/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/serializationUtils.kt @@ -0,0 +1,88 @@ +package io.modelcontextprotocol.kotlin.test.utils + +import io.kotest.assertions.json.shouldEqualJson +import io.kotest.assertions.withClue +import io.kotest.matchers.shouldBe +import kotlinx.serialization.SerializationException +import kotlinx.serialization.json.Json +import org.intellij.lang.annotations.Language +import kotlin.test.fail + +/** + * Verifies the serialization and deserialization process for a given object, ensuring: + * - The object can be serialized to JSON. + * - The serialized JSON matches the expected JSON. + * - The serialized JSON can be deserialized back into the original object. + * + * This is especially useful for round-trip serialization tests. + * + * @param T The type of the object to be verified. + * @param value The object to be serialized and deserialized. + * @param json The JSON serializer/deserializer used for the serialization process. + * @param expectedJson A lambda returning the expected JSON string for comparison purposes. + */ +public inline fun verifySerialization(value: T, json: Json, expectedJson: () -> String) { + verifySerialization(value, json, expectedJson.invoke()) +} + +/** + * Verifies the serialization and deserialization process for a given object, ensuring: + * - The object can be serialized to JSON. + * - The serialized JSON matches the expected JSON. + * - The serialized JSON can be deserialized back into the original object. + * + * This is especially useful for round-trip serialization tests. + * + * @param T The type of the object being tested for serialization and deserialization. + * @param value The object to be serialized and deserialized. + * @param json The JSON serialization/deserialization processor. + * @param expectedJson The expected JSON string to compare against the serialized output. + */ +public inline fun verifySerialization(value: T, json: Json, @Language("json") expectedJson: String) { + val jsonString = json.encodeToString(value) + + jsonString shouldEqualJson expectedJson + + val parsedObject: T = try { + json.decodeFromString(jsonString) + } catch (e: SerializationException) { + fail("Failed to parse generated json string: ${e.message}:\n$value", e) + } + withClue("Parsed object is not equal to original value") { + parsedObject shouldBe value + } +} + +/** + * Verifies the deserialization of a JSON payload into an object of the specified type, + * and ensures that the reverse serialization process returns the same JSON payload. + * + * This is especially useful for round-trip serialization tests. + * + * @param T The type of the object being deserialized from the JSON payload. + * @param json The JSON processor used for deserialization and serialization. + * @param payload The JSON string to deserialize into an object and verify against. + * @return The deserialized object of type T. + */ +public inline fun verifyDeserialization(json: Json, @Language("json") payload: String): T { + val value: T = json.decodeFromString(payload) + verifySerialization(value = value, json = json, expectedJson = payload) + return value +} + +/** + * Verifies the round-trip serialization and deserialization process for a given object. + * This method ensures that: + * - The object can be serialized into a JSON string. + * - The serialized JSON string can be deserialized back into an equivalent object. + * + * This is particularly useful for testing the integrity of serialization logic. + * + * @param T The type of the object being tested for serialization and deserialization. + * @param value The object to be serialized and deserialized. + * @param json The JSON processor used for the serialization and deserialization process. + */ +public inline fun verifySerializationRoundTrip(value: T, json: Json) { + val jsonString: String = json.encodeToString(value) + verifySerialization(value = value, json = json, expectedJson = jsonString) +} diff --git a/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/DisabledOnCI.kt b/test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/DisabledOnCI.kt similarity index 100% rename from test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/DisabledOnCI.kt rename to test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/DisabledOnCI.kt diff --git a/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/Retry.kt b/test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/Retry.kt similarity index 96% rename from test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/Retry.kt rename to test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/Retry.kt index e173f3f..a37e6e3 100644 --- a/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/Retry.kt +++ b/test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/Retry.kt @@ -16,6 +16,7 @@ import java.util.Optional @ExtendWith(RetryExtension::class) public annotation class Retry(val times: Int = 3, val delayMs: Long = 1000) +@Suppress("ForbiddenVoid") // needed for Java API interop public class RetryExtension : InvocationInterceptor { override fun interceptTestMethod( invocation: Invocation, @@ -87,7 +88,7 @@ public class RetryExtension : InvocationInterceptor { } } - throw lastError ?: IllegalStateException("Unexpected state in retry logic") + throw lastError ?: error("Unexpected state in retry logic") } private fun describeTest(ctx: ExtensionContext): String { diff --git a/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/TypeScriptRunner.kt b/test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/TypeScriptRunner.kt similarity index 98% rename from test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/TypeScriptRunner.kt rename to test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/TypeScriptRunner.kt index ff9292e..e7f5798 100644 --- a/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/TypeScriptRunner.kt +++ b/test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/TypeScriptRunner.kt @@ -18,6 +18,7 @@ public object TypeScriptRunner { * @param logPrefix Prefix for log lines * @return The started Process */ + @Suppress("LongParameterList") public fun run( typescriptDir: File, scriptPath: String, @@ -53,6 +54,7 @@ public object TypeScriptRunner { * @param typescriptDir The directory containing package.json * @throws RuntimeException if npm install fails */ + @Suppress("ReturnCount") public fun installDependencies(typescriptDir: File) { require(typescriptDir.isDirectory()) { "Type script directory does not exist: ${typescriptDir.absolutePath}" } println("Installing TypeScript dependencies in ${typescriptDir.absolutePath}") diff --git a/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/platform.kt b/test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/platform.jvm.kt similarity index 100% rename from test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/platform.kt rename to test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/platform.jvm.kt diff --git a/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/processBuilders.kt b/test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/processBuilders.kt similarity index 100% rename from test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/processBuilders.kt rename to test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/processBuilders.kt diff --git a/test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/processUtils.kt b/test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/processUtils.kt similarity index 100% rename from test-utils/src/main/kotlin/io/modelcontextprotocol/kotlin/test/utils/processUtils.kt rename to test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/processUtils.kt From efed85ae06a069351a9e80d3ce2981c5253009c9 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Tue, 17 Feb 2026 12:16:32 +0200 Subject: [PATCH 153/449] fix(test): add timeout to prevent indefinite blocking in StdioClientTransportTest (#514) (#528) - Introduced `withTimeout` to avoid indefinite hangs during test execution (#514) ## Motivation and Context Workaround for #514 in tests ## How Has This Been Tested? Integration test updated, CI ## Breaking Changes No ## Types of changes - [x] Test fix ## 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 ## Additional context #514 --- .../kotlin/sdk/client/StdioClientTransportTest.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt index 44456f2..585af3f 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt @@ -9,6 +9,7 @@ import io.modelcontextprotocol.kotlin.test.utils.createTeeProcessBuilder import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.withTimeout import kotlinx.io.asSink import kotlinx.io.asSource import kotlinx.io.buffered @@ -56,8 +57,11 @@ class StdioClientTransportTest : BaseTransportTest() { ) // The error in stderr should cause connecting to fail + // Use explicit timeout to avoid blocking indefinitely due to kotlinx.io cancellation issue (#514) assertThrows { - client.connect(transport) + withTimeout(5.seconds) { + client.connect(transport) + } } process.destroyForcibly() From b2878ed406afaa4b6355ee587fb0b69f1c968ba9 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Tue, 17 Feb 2026 14:21:43 +0200 Subject: [PATCH 154/449] test: add DSL builder tests for Request, Capabilities, and Content types (#529) # Add DSL builder tests for Request, Capabilities, and Content types - Introduced unit tests for `RequestDslTest`, `CapabilitiesDslTest`, and `ContentDslTest` to validate DSL builder functionality. - Covered edge cases, nested properties, custom fields, and overwrite behavior for requests, capabilities, and content creation. - Update AGENTS.md ## Motivation and Context for confidence ## Breaking Changes No ## Types of changes - [x] Tests ## 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 --- AGENTS.md | 43 +- .../sdk/types/dsl/CapabilitiesDslTest.kt | 135 ++++++ .../kotlin/sdk/types/dsl/ContentDslTest.kt | 385 ++++++++++++++++++ .../kotlin/sdk/types/dsl/RequestDslTest.kt | 256 ++++++++++++ 4 files changed, 806 insertions(+), 13 deletions(-) create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CapabilitiesDslTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ContentDslTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/RequestDslTest.kt diff --git a/AGENTS.md b/AGENTS.md index bf2b318..7b723e8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,6 +4,7 @@ This document is for autonomous agents and AI copilots contributing code to this Follow these rules to keep changes safe, comprehensible, and easy to maintain. ## Repository Layout + - `kotlin-sdk-core`: Core protocol types, transport abstractions, serialization utilities - `kotlin-sdk-client`: Client transports (STDIO, SSE, StreamableHttp, WebSocket) - `kotlin-sdk-server`: Server transports + Ktor integration (STDIO, SSE, WebSocket) @@ -30,19 +31,21 @@ Follow these rules to keep changes safe, comprehensible, and easy to maintain. - Interface Segregation: keep abstractions small and focused. - Dependency Inversion: depend on abstractions, not concretions. 4. **Make the minimal change** that satisfies the tests and the issue. - Stay focused on a single concern and avoid "drive-by" changes. + Stay focused on a single concern and avoid "drive-by" changes. 5. **Never commit changes!** It's human who should verify and commit to git. 6. **Keep the build green.** Do not merge changes that break existing tests. 7. **Prefer clarity** over micro-optimizations and cleverness. 8. **Ask when uncertain.** If requirements are ambiguous, request clarification with a concise question. 9. **Write code with the quality of a Kotlin Champion.** -10. **Prefer using MCP servers** like `jetbrains` and `intellij-index` for code navigation, analysis, and refactoring. +10. **Use MCP servers** like `jetbrains` and `intellij-index` to explore the codebase, for code navigation, analysis, + refactoring, and running tests. 11. **Use the Bash tool for terminal commands** instead of MCP terminal execution features. 12. **Suggest updates** to AGENTS.md/CLAUDE.md with best practices and guidelines. ## Code Style ### Kotlin + - Avoid changing public API signatures. Run `./gradlew apiCheck` before every commit. - **Explicit API mode** is strict: all public APIs must have explicit visibility modifiers and return types. - Anything under an `internal` directory is not part of the public API and may change freely. @@ -51,7 +54,8 @@ Follow these rules to keep changes safe, comprehensible, and easy to maintain. - Use the provided `.editorconfig` for consistent formatting - Use Kotlin typesafe DSL builders where possible; prioritize fluent builder style over standard builder methods - Prefer DSL builder style (method with lambda blocks) over constructors, if possible -- Use `val` for immutable properties and `var` for mutable; consider `lateinit var` instead of nullable types when appropriate +- Use `val` for immutable properties and `var` for mutable; consider `lateinit var` instead of nullable types when + appropriate - Use multi-dollar interpolation prefix for strings where applicable - Use fully qualified imports instead of star imports - Ensure backward compatibility when making changes @@ -59,6 +63,7 @@ Follow these rules to keep changes safe, comprehensible, and easy to maintain. ## Testing Guidance ### Test Organization + - **Location**: `src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/` - **Platform-specific**: `src/jvmTest/`, `src/jsTest/`, etc. - **Naming**: Use function `Names with backticks`, e.g., `fun \`should return 200 OK\`()` @@ -66,21 +71,24 @@ Follow these rules to keep changes safe, comprehensible, and easy to maintain. - **Documentation**: Keep tests self-documenting; don't add KDocs to tests. ### Test Principles -- Write comprehensive tests for new features. Always add tests for new features or bug fixes, even if not explicitly requested. + +- Write comprehensive tests for new features. Always add tests for new features or bug fixes, even if not explicitly + requested. - **Prioritize test readability** - Avoid creating too many test methods; use parametrized tests when testing multiple similar scenarios - When running tests on Kotlin Multiplatform projects, run JVM tests only unless asked for other platforms ### Test Framework Stack + - **kotlin-test**: Core test framework - **Kotest assertions**: Use infix style (`shouldBe`, `shouldContain`) instead of `assertEquals`. - - Use `shouldMatchJson` from kotest-assertions-json for JSON validation. - - For nullable objects with nested properties, prefer `shouldNotBeNull { ... }` blocks: - ```kotlin - content.annotations shouldNotBeNull { - priority shouldBe 1.0 - } - ``` + - Use `shouldMatchJson` from kotest-assertions-json for JSON validation. + - For nullable objects with nested properties, prefer `shouldNotBeNull { ... }` blocks: + ```kotlin + content.annotations shouldNotBeNull { + priority shouldBe 1.0 + } + ``` - **mockk**: Mocking library - **Ktor MockEngine**: For HTTP client mocking (`io.ktor:ktor-client-mock`) - **Java tests**: Use JUnit5, Mockito, AssertJ core @@ -129,6 +137,7 @@ Follow these rules to keep changes safe, comprehensible, and easy to maintain. ``` #### Avoiding `this.` in Lambda Blocks + ```kotlin // Avoid explicit `this.` unless necessary for disambiguation prop.shouldNotBeNull { @@ -138,6 +147,7 @@ prop.shouldNotBeNull { ``` ### Multiplatform Patterns + - Use `expect`/`actual` pattern for platform-specific implementations in `utils.*` files. - Test changes on JVM first, then verify platform-specific behavior if needed. - Supported targets: JVM (11+), JS/Wasm, iOS, watchOS, tvOS. @@ -145,22 +155,26 @@ prop.shouldNotBeNull { ## Coding Guidance ### Module Boundaries + - Respect separation of concerns between modules - Keep abstractions framework-agnostic - Avoid leaking implementation details across module boundaries ### Serialization + - Use Kotlinx Serialization with explicit `@Serializable` annotations - JSON config is defined in `jsonUtils.kt` as `McpJson` — use it consistently - Register custom serializers in companion objects ### Error Handling + - Use sealed classes for result states - Map errors to JSON-RPC error codes in protocol handlers - Log errors using `io.github.oshai.kotlinlogging.KotlinLogging` - Never log sensitive data (credentials, tokens) ### Code Quality + - Handle nullability and references as established patterns - Keep changes small and reversible; prefer adding functions over editing many call sites - Add focused comments **only** where intent is non-obvious @@ -228,7 +242,7 @@ prop.shouldNotBeNull { ## Definition of done -- Tests for affected functionality exist +- Tests for affected functionality exist - All tests pass across all targets - Changes are focused on a single concern. - Code respects module boundaries @@ -238,6 +252,7 @@ prop.shouldNotBeNull { ## Documentation ### General Guidelines + - **Never make up documentation/facts** - Update README files when adding new features - Document API changes in the appropriate module @@ -246,6 +261,7 @@ prop.shouldNotBeNull { - Write tutorials in Markdown format in README.md ### KDoc Guidelines + - Properly document interfaces and abstract classes in production code - Avoid KDocs on override functions to reduce verbosity - Update KDocs when API changes @@ -258,11 +274,13 @@ prop.shouldNotBeNull { Each module must have a `Module.md` file at the module root for Dokka-generated API documentation. **Format Requirements** (per [Dokka specification](https://kotlinlang.org/docs/dokka-module-and-package-docs.html)): + - Must start with `# Module module-name` (level 1 header) - Package documentation uses `# Package package.name` (level 1 header) - All other content uses level 2+ headers (`##`, `###`, etc.) **Content Guidelines**: + - **Purpose**: Provide module-level overview in generated API docs - **Detail level**: API documentation style (concise, focused on classes/interfaces) - **Module section**: @@ -305,7 +323,6 @@ Each module must have a `Module.md` file at the module root for Dokka-generated Another package description. - ## When to Ask for Help - Requirements conflict with existing tests or documentation diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CapabilitiesDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CapabilitiesDslTest.kt new file mode 100644 index 0000000..e4a949f --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CapabilitiesDslTest.kt @@ -0,0 +1,135 @@ +package io.modelcontextprotocol.kotlin.sdk.types.dsl + +import io.kotest.matchers.nulls.shouldBeNull +import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.shouldBe +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import io.modelcontextprotocol.kotlin.sdk.types.buildInitializeRequest +import kotlinx.serialization.json.double +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import kotlin.test.Test + +/** + * Tests for ClientCapabilities DSL builder. + * + * Verifies that capabilities can be constructed via DSL within InitializeRequest, + * covering minimal (empty), full (all fields), and variant patterns. + */ +@OptIn(ExperimentalMcpApi::class) +class CapabilitiesDslTest { + @Test + fun `capabilities should build minimal empty capabilities`() { + val request = buildInitializeRequest { + protocolVersion = "2024-11-05" + capabilities { } + info("Test", "1.0") + } + + request.params.capabilities.shouldNotBeNull { + sampling.shouldBeNull() + roots.shouldBeNull() + elicitation.shouldBeNull() + experimental.shouldBeNull() + } + } + + @Test + fun `capabilities should build full with all fields and nested properties`() { + val request = buildInitializeRequest { + protocolVersion = "2024-11-05" + capabilities { + sampling { + put("temperature", 0.7) + put("maxTokens", 1000) + put("topP", 0.95) + } + roots(listChanged = true) + elicitation { + put("mode", "interactive") + put("timeout", 30) + put("retries", 3) + } + experimental { + put("customFeature", true) + put("version", "1.0") + put("beta", false) + put("maxConcurrency", 10) + } + } + info("Test", "1.0") + } + + request.params.capabilities.shouldNotBeNull { + sampling shouldNotBeNull { + get("temperature")?.jsonPrimitive?.double shouldBe 0.7 + get("maxTokens")?.jsonPrimitive?.content shouldBe "1000" + get("topP")?.jsonPrimitive?.double shouldBe 0.95 + } + roots shouldNotBeNull { + listChanged shouldBe true + } + elicitation shouldNotBeNull { + get("mode")?.jsonPrimitive?.content shouldBe "interactive" + get("timeout")?.jsonPrimitive?.content shouldBe "30" + get("retries")?.jsonPrimitive?.content shouldBe "3" + } + experimental shouldNotBeNull { + get("customFeature")?.jsonPrimitive?.content shouldBe "true" + get("version")?.jsonPrimitive?.content shouldBe "1.0" + get("beta")?.jsonPrimitive?.content shouldBe "false" + get("maxConcurrency")?.jsonPrimitive?.content shouldBe "10" + } + } + } + + @Test + fun `capabilities should support roots variants`() { + // Test listChanged = true + val requestTrue = buildInitializeRequest { + protocolVersion = "2024-11-05" + capabilities { roots(listChanged = true) } + info("Test", "1.0") + } + requestTrue.params.capabilities.roots?.listChanged shouldBe true + + // Test listChanged = false + val requestFalse = buildInitializeRequest { + protocolVersion = "2024-11-05" + capabilities { roots(listChanged = false) } + info("Test", "1.0") + } + requestFalse.params.capabilities.roots?.listChanged shouldBe false + + // Test listChanged = null (not provided) + val requestNull = buildInitializeRequest { + protocolVersion = "2024-11-05" + capabilities { roots() } + info("Test", "1.0") + } + requestNull.params.capabilities.roots?.listChanged.shouldBeNull() + } + + @Test + fun `capabilities should overwrite when same field set multiple times`() { + val request = buildInitializeRequest { + protocolVersion = "2024-11-05" + capabilities { + sampling { put("temperature", 0.5) } + sampling { put("temperature", 0.9) } // Should overwrite + experimental { put("feature", "v1") } + experimental { put("feature", "v2") } // Should overwrite + } + info("Test", "1.0") + } + + request.params.capabilities.shouldNotBeNull { + sampling shouldNotBeNull { + get("temperature")?.jsonPrimitive?.double shouldBe 0.9 + } + experimental shouldNotBeNull { + get("feature")?.jsonPrimitive?.content shouldBe "v2" + } + } + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ContentDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ContentDslTest.kt new file mode 100644 index 0000000..180a308 --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ContentDslTest.kt @@ -0,0 +1,385 @@ +package io.modelcontextprotocol.kotlin.sdk.types.dsl + +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.nulls.shouldBeNull +import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.shouldBe +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import io.modelcontextprotocol.kotlin.sdk.types.Annotations +import io.modelcontextprotocol.kotlin.sdk.types.AudioContent +import io.modelcontextprotocol.kotlin.sdk.types.ImageContent +import io.modelcontextprotocol.kotlin.sdk.types.Role +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.sdk.types.assistantAudio +import io.modelcontextprotocol.kotlin.sdk.types.assistantImage +import io.modelcontextprotocol.kotlin.sdk.types.buildCreateMessageRequest +import io.modelcontextprotocol.kotlin.sdk.types.userText +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import kotlin.test.Test + +/** + * Tests for MediaContent DSL builders (TextContent, ImageContent, AudioContent). + * + * Verifies content can be constructed via DSL within sampling messages, + * covering minimal (required fields), full (all fields), validation, and edge cases. + */ +@OptIn(ExperimentalMcpApi::class) +class ContentDslTest { + + // ======================================================================== + // TextContent Tests + // ======================================================================== + + @Test + fun `userText should build minimal with text only`() { + val request = buildCreateMessageRequest { + maxTokens = 100 + messages { + userText { + text = "Hello, world!" + } + } + } + + (request.params.messages[0].content as TextContent).shouldNotBeNull { + text shouldBe "Hello, world!" + annotations.shouldBeNull() + meta.shouldBeNull() + } + } + + @Test + fun `userText should build full with all fields and nested meta`() { + val request = buildCreateMessageRequest { + maxTokens = 100 + messages { + userText { + text = "Analyze the quarterly sales report for Q4 2025" + annotations( + audience = listOf(Role.User, Role.Assistant), + priority = 0.85, + lastModified = "2025-02-17T10:30:00Z", + ) + meta { + put("source", "dashboard") + put("userId", "user-123") + put("sessionId", 42) + put("analyzed", true) + } + } + } + } + + (request.params.messages[0].content as TextContent).shouldNotBeNull { + text shouldBe "Analyze the quarterly sales report for Q4 2025" + annotations shouldNotBeNull { + audience shouldBe listOf(Role.User, Role.Assistant) + priority shouldBe 0.85 + lastModified shouldBe "2025-02-17T10:30:00Z" + } + meta shouldNotBeNull { + get("source")?.jsonPrimitive?.content shouldBe "dashboard" + get("userId")?.jsonPrimitive?.content shouldBe "user-123" + get("sessionId")?.jsonPrimitive?.content shouldBe "42" + get("analyzed")?.jsonPrimitive?.content shouldBe "true" + } + } + } + + @Test + fun `userText should handle unicode and special characters`() { + val request = buildCreateMessageRequest { + maxTokens = 100 + messages { + userText { + text = "Hello 🌍! Ça va? Москва 北京 مرحبا" + } + } + } + + (request.params.messages[0].content as TextContent).text shouldBe "Hello 🌍! Ça va? Москва 北京 مرحبا" + } + + @Test + fun `userText should handle empty string`() { + val request = buildCreateMessageRequest { + maxTokens = 100 + messages { + userText { + text = "" + } + } + } + + (request.params.messages[0].content as TextContent).text shouldBe "" + } + + @Test + fun `userText should throw if text is missing`() { + shouldThrow { + buildCreateMessageRequest { + maxTokens = 100 + messages { + userText { } + } + } + } + } + + // ======================================================================== + // ImageContent Tests + // ======================================================================== + + @Test + fun `assistantImage should build minimal with data and mimeType`() { + val request = buildCreateMessageRequest { + maxTokens = 100 + messages { + assistantImage { + data = + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" + mimeType = "image/png" + } + } + } + + (request.params.messages[0].content as ImageContent).shouldNotBeNull { + data shouldBe + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" + mimeType shouldBe "image/png" + annotations.shouldBeNull() + meta.shouldBeNull() + } + } + + @Test + fun `assistantImage should build full with all fields and image metadata`() { + val request = buildCreateMessageRequest { + maxTokens = 100 + messages { + assistantImage { + data = "iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAFklEQVR42mNk+M9QzzCCgAEYjRZnRwEA" + mimeType = "image/jpeg" + annotations( + audience = listOf(Role.Assistant), + priority = 0.92, + lastModified = "2025-02-17T11:45:30Z", + ) + meta { + put("width", 1920) + put("height", 1080) + put("format", "jpeg") + put("compression", "lossy") + put("quality", 85) + } + } + } + } + + (request.params.messages[0].content as ImageContent).shouldNotBeNull { + mimeType shouldBe "image/jpeg" + annotations shouldNotBeNull { + audience shouldBe listOf(Role.Assistant) + priority shouldBe 0.92 + lastModified shouldBe "2025-02-17T11:45:30Z" + } + meta shouldNotBeNull { + get("width")?.jsonPrimitive?.content shouldBe "1920" + get("height")?.jsonPrimitive?.content shouldBe "1080" + get("format")?.jsonPrimitive?.content shouldBe "jpeg" + get("compression")?.jsonPrimitive?.content shouldBe "lossy" + get("quality")?.jsonPrimitive?.content shouldBe "85" + } + } + } + + @Test + fun `assistantImage should accept various MIME types`() { + val mimeTypes = listOf("image/png", "image/jpeg", "image/webp", "image/gif", "image/svg+xml") + + mimeTypes.forEach { mime -> + val request = buildCreateMessageRequest { + maxTokens = 100 + messages { + assistantImage { + data = "base64data" + mimeType = mime + } + } + } + (request.params.messages[0].content as ImageContent).mimeType shouldBe mime + } + } + + @Test + fun `assistantImage should throw if data is missing`() { + shouldThrow { + buildCreateMessageRequest { + maxTokens = 100 + messages { + assistantImage { + mimeType = "image/png" + } + } + } + } + } + + @Test + fun `assistantImage should throw if mimeType is missing`() { + shouldThrow { + buildCreateMessageRequest { + maxTokens = 100 + messages { + assistantImage { + data = "base64data" + } + } + } + } + } + + // ======================================================================== + // AudioContent Tests + // ======================================================================== + + @Test + fun `assistantAudio should build minimal with data and mimeType`() { + val request = buildCreateMessageRequest { + maxTokens = 100 + messages { + assistantAudio { + data = "UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAAABmYWN0BAAAAAAAAABkYXRhAAAAAA==" + mimeType = "audio/wav" + } + } + } + + (request.params.messages[0].content as AudioContent).shouldNotBeNull { + data shouldBe "UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAAABmYWN0BAAAAAAAAABkYXRhAAAAAA==" + mimeType shouldBe "audio/wav" + annotations.shouldBeNull() + meta.shouldBeNull() + } + } + + @Test + fun `assistantAudio should build full with all fields and audio metadata`() { + val request = buildCreateMessageRequest { + maxTokens = 100 + messages { + assistantAudio { + data = "//uQxAAAAAAAAAAAAAAAAAAAAAAAWGluZwAAAA8AAAACAAADIADMzMzMzMzMzMzMzMzMzMzMzMzMzMzM" + mimeType = "audio/mpeg" + annotations( + audience = listOf(Role.User), + priority = 0.75, + lastModified = "2025-02-17T12:00:00Z", + ) + meta { + put("duration", 180) + put("bitrate", 128) + put("codec", "mp3") + put("sampleRate", 44100) + put("channels", 2) + } + } + } + } + + (request.params.messages[0].content as AudioContent).shouldNotBeNull { + mimeType shouldBe "audio/mpeg" + annotations shouldNotBeNull { + audience shouldBe listOf(Role.User) + priority shouldBe 0.75 + lastModified shouldBe "2025-02-17T12:00:00Z" + } + meta shouldNotBeNull { + get("duration")?.jsonPrimitive?.content shouldBe "180" + get("bitrate")?.jsonPrimitive?.content shouldBe "128" + get("codec")?.jsonPrimitive?.content shouldBe "mp3" + get("sampleRate")?.jsonPrimitive?.content shouldBe "44100" + get("channels")?.jsonPrimitive?.content shouldBe "2" + } + } + } + + @Test + fun `assistantAudio should throw if data is missing`() { + shouldThrow { + buildCreateMessageRequest { + maxTokens = 100 + messages { + assistantAudio { + mimeType = "audio/wav" + } + } + } + } + } + + @Test + fun `assistantAudio should throw if mimeType is missing`() { + shouldThrow { + buildCreateMessageRequest { + maxTokens = 100 + messages { + assistantAudio { + data = "base64audio" + } + } + } + } + } + + // ======================================================================== + // Annotations Edge Cases + // ======================================================================== + + @Test + fun `annotations should support boundary priority values`() { + // Priority = 0.0 (minimum) + val requestMin = buildCreateMessageRequest { + maxTokens = 100 + messages { + userText { + text = "Min priority" + annotations(priority = 0.0) + } + } + } + (requestMin.params.messages[0].content as TextContent).annotations?.priority shouldBe 0.0 + + // Priority = 1.0 (maximum) + val requestMax = buildCreateMessageRequest { + maxTokens = 100 + messages { + userText { + text = "Max priority" + annotations(priority = 1.0) + } + } + } + (requestMax.params.messages[0].content as TextContent).annotations?.priority shouldBe 1.0 + } + + @Test + fun `annotations should support direct object construction`() { + val request = buildCreateMessageRequest { + maxTokens = 100 + messages { + userText { + text = "With annotations object" + annotations(Annotations(priority = 0.5, audience = listOf(Role.User))) + } + } + } + + (request.params.messages[0].content as TextContent).annotations shouldNotBeNull { + priority shouldBe 0.5 + audience shouldBe listOf(Role.User) + lastModified.shouldBeNull() + } + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/RequestDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/RequestDslTest.kt new file mode 100644 index 0000000..ad9c719 --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/RequestDslTest.kt @@ -0,0 +1,256 @@ +package io.modelcontextprotocol.kotlin.sdk.types.dsl + +import io.kotest.matchers.collections.shouldContainAll +import io.kotest.matchers.nulls.shouldBeNull +import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.shouldBe +import io.kotest.matchers.types.shouldBeInstanceOf +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.RequestId +import io.modelcontextprotocol.kotlin.sdk.types.buildListToolsRequest +import kotlinx.serialization.json.JsonNull +import kotlinx.serialization.json.add +import kotlinx.serialization.json.boolean +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.long +import kotlinx.serialization.json.put +import kotlin.test.Test + +/** + * Tests for RequestMeta and PaginatedRequest DSL builders. + * + * Verifies meta and cursor can be constructed via DSL within paginated requests, + * covering minimal (no params), full (all field types), variants, and edge cases. + */ +@OptIn(ExperimentalMcpApi::class) +class RequestDslTest { + @Test + fun `request should build minimal without any params`() { + val request = buildListToolsRequest { } + + request.params.shouldBeNull() + request.method shouldBe Method.Defined.ToolsList + } + + @Test + fun `request should build full with cursor and meta containing all field types`() { + val request = buildListToolsRequest { + cursor = "next-page-eyJvZmZzZXQiOjEwMH0" + meta { + // ProgressToken + progressToken("progress-abc123") + + // String fields + put("requestId", "req-456") + put("source", "api-gateway") + + // Number fields + put("priority", 5) + put("retryCount", 3L) + put("timeout", 30000) + + // Boolean fields + put("urgent", true) + put("cached", false) + + // Null field + put("optional", null) + + // Nested JsonObject + putJsonObject("context") { + put("userId", "user-789") + put("sessionId", "sess-abc") + put("authenticated", true) + put("permissions", 7) + } + + // JsonArray + putJsonArray("tags") { + add("production") + add("critical") + add("monitored") + } + } + } + + request.params shouldNotBeNull { + cursor shouldBe "next-page-eyJvZmZzZXQiOjEwMH0" + meta shouldNotBeNull { + // ProgressToken + progressToken shouldNotBeNull { + shouldBeInstanceOf() + value shouldBe "progress-abc123" + } + + // String fields + get("requestId")?.jsonPrimitive?.content shouldBe "req-456" + get("source")?.jsonPrimitive?.content shouldBe "api-gateway" + + // Number fields + get("priority")?.jsonPrimitive?.int shouldBe 5 + get("retryCount")?.jsonPrimitive?.long shouldBe 3L + get("timeout")?.jsonPrimitive?.int shouldBe 30000 + + // Boolean fields + get("urgent")?.jsonPrimitive?.boolean shouldBe true + get("cached")?.jsonPrimitive?.boolean shouldBe false + + // Null field + get("optional") shouldBe JsonNull + + // Nested JsonObject + get("context") shouldNotBeNull { + jsonObject["userId"]?.jsonPrimitive?.content shouldBe "user-789" + jsonObject["sessionId"]?.jsonPrimitive?.content shouldBe "sess-abc" + jsonObject["authenticated"]?.jsonPrimitive?.boolean shouldBe true + jsonObject["permissions"]?.jsonPrimitive?.int shouldBe 7 + } + + // JsonArray + get("tags") shouldNotBeNull { + jsonArray.map { it.jsonPrimitive.content } shouldContainAll listOf( + "production", + "critical", + "monitored", + ) + } + } + } + } + + @Test + fun `meta progressToken should support String Int and Long types`() { + // String progressToken + val stringRequest = buildListToolsRequest { + meta { progressToken("token-abc") } + } + stringRequest.params?.meta?.progressToken shouldNotBeNull { + shouldBeInstanceOf() + value shouldBe "token-abc" + } + + // Int progressToken + val intRequest = buildListToolsRequest { + meta { progressToken(42) } + } + intRequest.params?.meta?.progressToken shouldNotBeNull { + shouldBeInstanceOf() + value shouldBe 42L + } + + // Long progressToken + val longRequest = buildListToolsRequest { + meta { progressToken(999L) } + } + longRequest.params?.meta?.progressToken shouldNotBeNull { + shouldBeInstanceOf() + value shouldBe 999L + } + } + + @Test + fun `meta should support custom fields without progressToken`() { + val request = buildListToolsRequest { + meta { + put("requestId", "req-123") + put("source", "cli") + // No progressToken + } + } + + request.params shouldNotBeNull { + meta shouldNotBeNull { + progressToken.shouldBeNull() + get("requestId")?.jsonPrimitive?.content shouldBe "req-123" + get("source")?.jsonPrimitive?.content shouldBe "cli" + } + } + } + + @Test + fun `cursor should work without meta`() { + val request = buildListToolsRequest { + cursor = "page-2-cursor" + } + + request.params shouldNotBeNull { + cursor shouldBe "page-2-cursor" + meta.shouldBeNull() + } + } + + @Test + fun `cursor should handle empty string`() { + val request = buildListToolsRequest { + cursor = "" + } + + request.params shouldNotBeNull { + cursor shouldBe "" + } + } + + @Test + fun `meta should handle special characters in keys`() { + val request = buildListToolsRequest { + meta { + put("key-with-dashes", "value1") + put("key.with.dots", "value2") + put("key_with_underscores", "value3") + put("keyWithCamelCase", "value4") + } + } + + request.params?.meta shouldNotBeNull { + get("key-with-dashes")?.jsonPrimitive?.content shouldBe "value1" + get("key.with.dots")?.jsonPrimitive?.content shouldBe "value2" + get("key_with_underscores")?.jsonPrimitive?.content shouldBe "value3" + get("keyWithCamelCase")?.jsonPrimitive?.content shouldBe "value4" + } + } + + @Test + fun `meta should overwrite when progressToken set multiple times`() { + val request = buildListToolsRequest { + meta { + progressToken("first") + progressToken(123) // Different type + progressToken("second") // Back to string, should be final + } + } + + request.params?.meta?.progressToken shouldNotBeNull { + shouldBeInstanceOf() + value shouldBe "second" + } + } + + @Test + fun `meta should overwrite when custom field set multiple times`() { + val request = buildListToolsRequest { + meta { + put("key", "first") + put("key", 123) // Different type + put("key", "third") // Back to string, should be final + } + } + + request.params?.meta shouldNotBeNull { + get("key")?.jsonPrimitive?.content shouldBe "third" + } + } + + @Test + fun `meta should handle very long cursor strings`() { + val longCursor = "x".repeat(1000) + val request = buildListToolsRequest { + cursor = longCursor + } + + request.params?.cursor shouldBe longCursor + } +} From bb79e200c42b72c66427292c17eebc189d0cb14b Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Wed, 18 Feb 2026 16:43:37 +0200 Subject: [PATCH 155/449] fix!: Migrate Ktor extension functions from `Routing` to `Route` (#237) (#531) # Migrate Ktor extension functions from `Routing` to `Route` (#237) - Replaced `Routing` with `Route` in MCP-related functions for better composability in nested contexts. - Updated tests to verify compatibility with nested routes and subpaths. - Deprecated legacy signatures and removed redundant overloads. ## How Has This Been Tested? Unit test ## Breaking Changes `Routing.mcp(...) `extension was replaced with `Route.mcp(...)` ## Types of changes - [X] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [x] 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 - [ ] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed ## Additional context Based on #314 by @IainHull but that PR is too outdated. --- README.md | 86 +++++++++++- gradle/libs.versions.toml | 1 + .../sdk/integration/kotlin/KotlinTestBase.kt | 20 +-- .../kotlin/sdk/client/KtorClient.kt | 2 +- kotlin-sdk-server/Module.md | 2 +- kotlin-sdk-server/api/kotlin-sdk-server.api | 15 +-- kotlin-sdk-server/build.gradle.kts | 10 +- .../kotlin/sdk/server/KtorServer.kt | 43 ++++-- .../WebSocketMcpKtorServerExtensions.kt | 29 +--- .../sdk/server/AbstractKtorExtensionsTest.kt | 68 ++++++++++ .../server/KtorApplicationExtensionsTest.kt | 42 ++++++ .../sdk/server/KtorRouteExtensionsTest.kt | 125 ++++++++++++++++++ 12 files changed, 373 insertions(+), 70 deletions(-) create mode 100644 kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/AbstractKtorExtensionsTest.kt create mode 100644 kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorApplicationExtensionsTest.kt create mode 100644 kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorRouteExtensionsTest.kt diff --git a/README.md b/README.md index 7df2be2..50beb54 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,7 @@ fun main(args: Array) = runBlocking { Create an MCP server that exposes a simple tool and runs on an embedded Ktor server with SSE transport: + ```kotlin import io.ktor.server.cio.CIO import io.ktor.server.engine.embeddedServer @@ -761,14 +762,89 @@ CLI tooling that spawns a helper process. No networking setup is required. ### Streamable HTTP Transport -`StreamableHttpClientTransport` and the Ktor `streamableHttpApp()` helpers expose MCP over a single HTTP endpoint with -optional JSON-only or SSE streaming responses. This is the recommended choice for remote deployments and integrates -nicely with proxies or service meshes. +`StreamableHttpClientTransport` and the Ktor `mcpStreamableHttp()` / `mcpStatelessStreamableHttp()` helpers expose MCP +over a single HTTP endpoint with optional JSON-only or SSE streaming responses. This is the recommended choice for +remote deployments and integrates nicely with proxies or service meshes. Both accept a `path` parameter (default: +`"/mcp"`) to mount the endpoint at any URL: + + + +```kotlin +embeddedServer(CIO, port = 3000) { + mcpStreamableHttp(path = "/api/mcp") { + MyServer() + } +}.start(wait = true) +``` + + ### SSE Transport -Server-Sent Events remain available for backwards compatibility with older MCP clients. Use `SseServerTransport` or the -SSE Ktor plugin when you need drop-in compatibility, but prefer Streamable HTTP for new projects. +Server-Sent Events remain available for backwards compatibility with older MCP clients. Two Ktor helpers are provided: + +- **`Application.mcp { }`** — installs the SSE plugin automatically and registers MCP endpoints at `/`. +- **`Route.mcp { }`** — registers MCP endpoints at the current route path; requires `install(SSE)` in the application + first. Use this to host MCP alongside other routes or under a path prefix: + + + +```kotlin +embeddedServer(CIO, port = 3000) { + install(SSE) + routing { + route("/api/mcp") { + mcp { MyServer() } + } + } +}.start(wait = true) +``` + + + +Prefer Streamable HTTP for new projects. ### WebSocket Transport diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 460b825..08bf61e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -57,6 +57,7 @@ ktor-server-websockets = { group = "io.ktor", name = "ktor-server-websockets", v awaitility = { group = "org.awaitility", name = "awaitility-kotlin", version.ref = "awaitility" } kotest-assertions-core = { group = "io.kotest", name = "kotest-assertions-core", version.ref = "kotest" } kotest-assertions-json = { group = "io.kotest", name = "kotest-assertions-json", version.ref = "kotest" } +kotest-assertions-ktor = { group = "io.kotest", name = "kotest-assertions-ktor", version.ref = "kotest" } kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines" } ktor-client-mock = { group = "io.ktor", name = "ktor-client-mock", version.ref = "ktor" } ktor-server-test-host = { group = "io.ktor", name = "ktor-server-test-host", version.ref = "ktor" } diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/KotlinTestBase.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/KotlinTestBase.kt index 1a8ffe0..f8e8740 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/KotlinTestBase.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/KotlinTestBase.kt @@ -8,10 +8,6 @@ import io.ktor.server.application.install import io.ktor.server.engine.EmbeddedServer import io.ktor.server.engine.embeddedServer import io.ktor.server.plugins.contentnegotiation.ContentNegotiation -import io.ktor.server.routing.delete -import io.ktor.server.routing.get -import io.ktor.server.routing.post -import io.ktor.server.routing.route import io.ktor.server.routing.routing import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.client.SseClientTransport @@ -22,6 +18,7 @@ import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport import io.modelcontextprotocol.kotlin.sdk.server.StreamableHttpServerTransport import io.modelcontextprotocol.kotlin.sdk.server.mcp +import io.modelcontextprotocol.kotlin.sdk.server.mcpStreamableHttp import io.modelcontextprotocol.kotlin.sdk.types.Implementation import io.modelcontextprotocol.kotlin.sdk.types.McpJson import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities @@ -54,6 +51,7 @@ abstract class KotlinTestBase { // Transport selection protected enum class TransportKind { SSE, STDIO, STREAMABLE_HTTP } + protected open val transportKind: TransportKind = TransportKind.STDIO // STDIO-specific fields @@ -168,19 +166,7 @@ abstract class KotlinTestBase { install(ContentNegotiation) { json(McpJson) } - routing { - route("/mcp") { - post { - transport.handlePostRequest(null, call) - } - get { - transport.handleGetRequest(null, call) - } - delete { - transport.handleDeleteRequest(call) - } - } - } + mcpStreamableHttp(path = "/mcp") { server } }.start(wait = false) } diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/KtorClient.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/KtorClient.kt index fe92e15..0c69504 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/KtorClient.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/KtorClient.kt @@ -13,7 +13,7 @@ import kotlin.time.Duration * @param urlString Optional URL of the MCP server. * @param reconnectionTime Optional duration to wait before attempting to reconnect. * @param requestBuilder Optional lambda to configure the HTTP request. - * @return A [SSEClientTransport] configured for MCP communication. + * @return A [SseClientTransport] configured for MCP communication. */ public fun HttpClient.mcpSseTransport( urlString: String? = null, diff --git a/kotlin-sdk-server/Module.md b/kotlin-sdk-server/Module.md index 43a7359..176a398 100644 --- a/kotlin-sdk-server/Module.md +++ b/kotlin-sdk-server/Module.md @@ -12,7 +12,7 @@ that make it straightforward to expose tools, prompts, and resources to MCP clie listeners emit list-changed notifications when the capability is enabled. - **Transports**: Ready-to-use transports for STDIO (CLI-style hosting), Ktor Server-Sent Events (SSE) with POST back-channel, and Ktor WebSocket. All share the common `Transport` abstraction from the SDK. -- **Ktor integration**: Extension functions (`Routing.mcp`, `Routing.mcpWebSocket`, and `Application` variants) that +- **Ktor integration**: Extension functions (`Route.mcp`, `Route.mcpWebSocket`, and `Application` variants) that plug MCP into an existing Ktor server with minimal boilerplate. - **Notifications**: Built-in support for resource subscription, resource-updated events, and feature list change notifications when capabilities are set accordingly. diff --git a/kotlin-sdk-server/api/kotlin-sdk-server.api b/kotlin-sdk-server/api/kotlin-sdk-server.api index f0eb5c1..f97845b 100644 --- a/kotlin-sdk-server/api/kotlin-sdk-server.api +++ b/kotlin-sdk-server/api/kotlin-sdk-server.api @@ -6,12 +6,12 @@ public abstract interface class io/modelcontextprotocol/kotlin/sdk/server/EventS public final class io/modelcontextprotocol/kotlin/sdk/server/KtorServerKt { public static final fun mcp (Lio/ktor/server/application/Application;Lkotlin/jvm/functions/Function1;)V - public static final fun mcp (Lio/ktor/server/routing/Routing;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V - public static final fun mcp (Lio/ktor/server/routing/Routing;Lkotlin/jvm/functions/Function1;)V - public static final fun mcpStatelessStreamableHttp (Lio/ktor/server/application/Application;ZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/jvm/functions/Function1;)V - public static synthetic fun mcpStatelessStreamableHttp$default (Lio/ktor/server/application/Application;ZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V - public static final fun mcpStreamableHttp (Lio/ktor/server/application/Application;ZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/jvm/functions/Function1;)V - public static synthetic fun mcpStreamableHttp$default (Lio/ktor/server/application/Application;ZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V + public static final fun mcp (Lio/ktor/server/routing/Route;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V + public static final fun mcp (Lio/ktor/server/routing/Route;Lkotlin/jvm/functions/Function1;)V + public static final fun mcpStatelessStreamableHttp (Lio/ktor/server/application/Application;Ljava/lang/String;ZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/jvm/functions/Function1;)V + public static synthetic fun mcpStatelessStreamableHttp$default (Lio/ktor/server/application/Application;Ljava/lang/String;ZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V + public static final fun mcpStreamableHttp (Lio/ktor/server/application/Application;Ljava/lang/String;ZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/jvm/functions/Function1;)V + public static synthetic fun mcpStreamableHttp$default (Lio/ktor/server/application/Application;Ljava/lang/String;ZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V } public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredPrompt : io/modelcontextprotocol/kotlin/sdk/server/Feature { @@ -185,9 +185,8 @@ public final class io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorSer public static final fun mcpWebSocket (Lio/ktor/server/application/Application;Lkotlin/jvm/functions/Function0;)V public static final fun mcpWebSocket (Lio/ktor/server/routing/Route;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Lkotlin/jvm/functions/Function2;)V public static final fun mcpWebSocket (Lio/ktor/server/routing/Route;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Lkotlin/jvm/functions/Function2;)V + public static final fun mcpWebSocket (Lio/ktor/server/routing/Route;Ljava/lang/String;Lkotlin/jvm/functions/Function0;)V public static final fun mcpWebSocket (Lio/ktor/server/routing/Route;Lkotlin/jvm/functions/Function0;)V - public static final fun mcpWebSocket (Lio/ktor/server/routing/Routing;Ljava/lang/String;Lkotlin/jvm/functions/Function0;)V - public static final fun mcpWebSocket (Lio/ktor/server/routing/Routing;Lkotlin/jvm/functions/Function0;)V public static synthetic fun mcpWebSocket$default (Lio/ktor/server/routing/Route;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V public static synthetic fun mcpWebSocket$default (Lio/ktor/server/routing/Route;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V public static final fun mcpWebSocketTransport (Lio/ktor/server/routing/Route;Ljava/lang/String;Lkotlin/jvm/functions/Function2;)V diff --git a/kotlin-sdk-server/build.gradle.kts b/kotlin-sdk-server/build.gradle.kts index e51a643..9d0bddf 100644 --- a/kotlin-sdk-server/build.gradle.kts +++ b/kotlin-sdk-server/build.gradle.kts @@ -20,19 +20,21 @@ kotlin { commonTest { dependencies { implementation(kotlin("test")) + implementation(libs.kotest.assertions.core) + implementation(libs.kotest.assertions.json) implementation(libs.kotlinx.coroutines.test) } } jvmTest { dependencies { - implementation(libs.ktor.client.logging) - implementation(libs.ktor.server.content.negotiation) + implementation(libs.kotest.assertions.ktor) implementation(libs.ktor.client.content.negotiation) + implementation(libs.ktor.client.logging) implementation(libs.ktor.serialization) + implementation(libs.ktor.server.content.negotiation) implementation(libs.ktor.server.test.host) - implementation(libs.kotest.assertions.core) - implementation(libs.kotest.assertions.json) + runtimeOnly(libs.slf4j.simple) } } diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt index 90aee6b..5a750a5 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt @@ -4,11 +4,12 @@ import io.github.oshai.kotlinlogging.KotlinLogging import io.ktor.http.HttpStatusCode import io.ktor.server.application.Application import io.ktor.server.application.ApplicationCall +import io.ktor.server.application.MissingApplicationPluginException import io.ktor.server.application.install import io.ktor.server.request.ApplicationRequest import io.ktor.server.request.header import io.ktor.server.response.respond -import io.ktor.server.routing.Routing +import io.ktor.server.routing.Route import io.ktor.server.routing.RoutingContext import io.ktor.server.routing.delete import io.ktor.server.routing.get @@ -53,17 +54,33 @@ internal class TransportManager(transports: Map = emp * @param block the block of code that defines the server's behavior for the SSE session. */ @KtorDsl -public fun Routing.mcp(path: String, block: ServerSSESession.() -> Server) { +public fun Route.mcp(path: String, block: ServerSSESession.() -> Server) { route(path) { mcp(block) } } /** -* Configures the Ktor Application to handle Model Context Protocol (MCP) over Server-Sent Events (SSE). -*/ + * Configures the Ktor Application to handle Model Context Protocol (MCP) over Server-Sent Events (SSE). + * + * **Precondition:** the [SSE] plugin must be installed on the application before calling this function. + * Use [Application.mcp] if you want SSE to be installed automatically. + * + * @throws IllegalStateException if the [SSE] plugin is not installed. + */ @KtorDsl -public fun Routing.mcp(block: ServerSSESession.() -> Server) { +public fun Route.mcp(block: ServerSSESession.() -> Server) { + try { + plugin(SSE) + } catch (e: MissingApplicationPluginException) { + throw IllegalStateException( + "The SSE plugin must be installed before registering MCP routes. " + + "Add `install(SSE)` to your application configuration, " + + "or use Application.mcp() which installs it automatically.", + e, + ) + } + val transportManager = TransportManager() sse { @@ -85,7 +102,9 @@ public fun Application.mcp(block: ServerSSESession.() -> Server) { } @KtorDsl +@Suppress("LongParameterList") public fun Application.mcpStreamableHttp( + path: String = "/mcp", enableDnsRebindingProtection: Boolean = false, allowedHosts: List? = null, allowedOrigins: List? = null, @@ -97,7 +116,7 @@ public fun Application.mcpStreamableHttp( val transportManager = TransportManager() routing { - route("/mcp") { + route(path) { sse { val transport = existingStreamableTransport(call, transportManager) ?: return@sse transport.handleRequest(this, call) @@ -126,7 +145,9 @@ public fun Application.mcpStreamableHttp( } @KtorDsl +@Suppress("LongParameterList") public fun Application.mcpStatelessStreamableHttp( + path: String = "/mcp", enableDnsRebindingProtection: Boolean = false, allowedHosts: List? = null, allowedOrigins: List? = null, @@ -136,7 +157,7 @@ public fun Application.mcpStatelessStreamableHttp( install(SSE) routing { - route("/mcp") { + route(path) { post { mcpStatelessStreamableHttpEndpoint( enableDnsRebindingProtection = enableDnsRebindingProtection, @@ -164,7 +185,7 @@ public fun Application.mcpStatelessStreamableHttp( } } -internal suspend fun ServerSSESession.mcpSseEndpoint( +private suspend fun ServerSSESession.mcpSseEndpoint( postEndpoint: String, transportManager: TransportManager, block: ServerSSESession.() -> Server, @@ -185,7 +206,7 @@ internal suspend fun ServerSSESession.mcpSseEndpoint( awaitCancellation() } -internal fun ServerSSESession.mcpSseTransport( +private fun ServerSSESession.mcpSseTransport( postEndpoint: String, transportManager: TransportManager, ): SseServerTransport { @@ -196,7 +217,7 @@ internal fun ServerSSESession.mcpSseTransport( return transport } -internal suspend fun RoutingContext.mcpStatelessStreamableHttpEndpoint( +private suspend fun RoutingContext.mcpStatelessStreamableHttpEndpoint( enableDnsRebindingProtection: Boolean = false, allowedHosts: List? = null, allowedOrigins: List? = null, @@ -221,7 +242,7 @@ internal suspend fun RoutingContext.mcpStatelessStreamableHttpEndpoint( logger.debug { "Server connected to transport without sessionId" } } -internal suspend fun RoutingContext.mcpPostEndpoint(transportManager: TransportManager) { +private suspend fun RoutingContext.mcpPostEndpoint(transportManager: TransportManager) { val sessionId: String = call.request.queryParameters["sessionId"] ?: run { call.respond(HttpStatusCode.BadRequest, "sessionId query parameter is not provided") return diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt index edd54d3..c2cb2c4 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt @@ -6,7 +6,6 @@ import io.github.oshai.kotlinlogging.KotlinLogging import io.ktor.server.application.Application import io.ktor.server.application.install import io.ktor.server.routing.Route -import io.ktor.server.routing.Routing import io.ktor.server.routing.routing import io.ktor.server.websocket.WebSocketServerSession import io.ktor.server.websocket.WebSockets @@ -25,7 +24,7 @@ private val logger = KotlinLogging.logger {} * Configures the Ktor Application to handle Model Context Protocol (MCP) over WebSocket. */ @KtorDsl -public fun Routing.mcpWebSocket(block: () -> Server) { +public fun Route.mcpWebSocket(block: () -> Server) { webSocket { mcpWebSocketEndpoint(block) } @@ -35,7 +34,7 @@ public fun Routing.mcpWebSocket(block: () -> Server) { * Configures the Ktor Application to handle Model Context Protocol (MCP) over WebSocket. */ @KtorDsl -public fun Routing.mcpWebSocket(path: String, block: () -> Server) { +public fun Route.mcpWebSocket(path: String, block: () -> Server) { webSocket(path) { mcpWebSocketEndpoint(block) } @@ -89,7 +88,7 @@ private fun createMcpTransport(webSocketSession: WebSocketServerSession): WebSoc */ @Deprecated( "Use mcpWebSocket with a lambda that returns a Server instance instead", - ReplaceWith("Routing.mcpWebSocket"), + ReplaceWith(""), DeprecationLevel.WARNING, ) public fun Route.mcpWebSocket(options: ServerOptions? = null, handler: suspend Server.() -> Unit = {}) { @@ -98,17 +97,6 @@ public fun Route.mcpWebSocket(options: ServerOptions? = null, handler: suspend S } } -@Deprecated( - "Use mcpWebSocket with a lambda that returns a Server instance instead", - ReplaceWith("Routing.mcpWebSocket"), - DeprecationLevel.WARNING, -) -public fun Route.mcpWebSocket(block: () -> Server) { - webSocket { - block().createSession(createMcpTransport(this)) - } -} - /** * Registers a WebSocket route at the specified [path] that establishes an MCP server session. * @@ -118,7 +106,7 @@ public fun Route.mcpWebSocket(block: () -> Server) { */ @Deprecated( "Use mcpWebSocket with a path and a lambda that returns a Server instance instead", - ReplaceWith("Routing.mcpWebSocket"), + ReplaceWith(""), DeprecationLevel.WARNING, ) public fun Route.mcpWebSocket(path: String, options: ServerOptions? = null, handler: suspend Server.() -> Unit = {}) { @@ -134,7 +122,7 @@ public fun Route.mcpWebSocket(path: String, options: ServerOptions? = null, hand */ @Deprecated( "Use mcpWebSocket with a lambda that returns a Server instance instead", - ReplaceWith("Routing.mcpWebSocket"), + ReplaceWith(""), DeprecationLevel.WARNING, ) public fun Route.mcpWebSocketTransport(handler: suspend WebSocketMcpServerTransport.() -> Unit = {}) { @@ -166,12 +154,7 @@ public fun Route.mcpWebSocketTransport(path: String, handler: suspend WebSocketM } } -@Deprecated( - "Use mcpWebSocket with a lambda that returns a Server instance instead", - ReplaceWith("mcpWebSocket"), - DeprecationLevel.WARNING, -) -private suspend fun Route.createMcpServer( +private suspend fun createMcpServer( session: WebSocketServerSession, options: ServerOptions?, handler: suspend Server.() -> Unit, diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/AbstractKtorExtensionsTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/AbstractKtorExtensionsTest.kt new file mode 100644 index 0000000..28e21b9 --- /dev/null +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/AbstractKtorExtensionsTest.kt @@ -0,0 +1,68 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.kotest.assertions.ktor.client.shouldHaveContentType +import io.kotest.assertions.ktor.client.shouldHaveStatus +import io.ktor.client.HttpClient +import io.ktor.client.request.post +import io.ktor.client.request.prepareGet +import io.ktor.client.request.setBody +import io.ktor.client.statement.bodyAsChannel +import io.ktor.http.ContentType +import io.ktor.http.HttpStatusCode +import io.ktor.http.contentType +import io.ktor.utils.io.readUTF8Line +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities + +@Suppress("AbstractClassCanBeConcreteClass") +abstract class AbstractKtorExtensionsTest { + + protected val sseContentType = ContentType("text", "event-stream") + + protected fun testServer() = Server( + serverInfo = Implementation(name = "test-server", version = "1.0.0"), + options = ServerOptions(capabilities = ServerCapabilities()), + ) + + /** + * Asserts that both MCP transport endpoints are registered at [path]: + * - GET returns 200 with `text/event-stream` content type (SSE endpoint) + * - POST with a valid MCP payload and session returns 202 Accepted + * - POST without a sessionId returns 400 Bad Request + */ + protected suspend fun HttpClient.assertMcpEndpointsAt(path: String) { + prepareGet(path).execute { response -> + response.shouldHaveStatus(HttpStatusCode.OK) + response.shouldHaveContentType(sseContentType) + + // Extract sessionId from the SSE "endpoint" event + val channel = response.bodyAsChannel() + var eventName: String? = null + var sessionId: String? = null + + while (sessionId == null && !channel.isClosedForRead) { + val line = channel.readUTF8Line() ?: break + when { + line.startsWith("event:") -> eventName = line.substringAfter("event:").trim() + + line.startsWith("data:") && eventName == "endpoint" -> { + val data = line.substringAfter("data:").trim() + sessionId = data.substringAfter("sessionId=").ifEmpty { null } + } + } + } + + requireNotNull(sessionId) { "sessionId not found in SSE endpoint event" } + + // POST a valid JSON-RPC ping while the SSE connection is alive + val postResponse = post("$path?sessionId=$sessionId") { + contentType(ContentType.Application.Json) + setBody("""{"jsonrpc":"2.0","id":1,"method":"ping"}""") + } + postResponse.shouldHaveStatus(HttpStatusCode.Accepted) + } + + // POST without sessionId returns 400 Bad Request + post(path).shouldHaveStatus(HttpStatusCode.BadRequest) + } +} diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorApplicationExtensionsTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorApplicationExtensionsTest.kt new file mode 100644 index 0000000..05200e5 --- /dev/null +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorApplicationExtensionsTest.kt @@ -0,0 +1,42 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.kotest.assertions.ktor.client.shouldHaveStatus +import io.kotest.matchers.shouldBe +import io.ktor.client.request.get +import io.ktor.client.statement.bodyAsText +import io.ktor.http.HttpStatusCode +import io.ktor.server.response.respondText +import io.ktor.server.routing.get +import io.ktor.server.routing.routing +import io.ktor.server.testing.testApplication +import kotlin.test.Test + +/** + * Integration tests for Ktor Application.mcp() extension. + * + * Verifies that Application.mcp() installs SSE automatically and registers + * MCP endpoints at the application root, without requiring explicit install(SSE). + */ +class KtorApplicationExtensionsTest : AbstractKtorExtensionsTest() { + + /** + * Verifies that Application.mcp() does not interfere with other routes + * added to the same application. + */ + @Test + fun `Application mcp should installs SSE and coexist with other routes`() = testApplication { + application { + mcp { testServer() } + + routing { + get("/health") { call.respondText("healthy") } + } + } + + val healthResponse = client.get("/health") + healthResponse.shouldHaveStatus(HttpStatusCode.OK) + healthResponse.bodyAsText() shouldBe "healthy" + + client.assertMcpEndpointsAt("/") + } +} diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorRouteExtensionsTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorRouteExtensionsTest.kt new file mode 100644 index 0000000..5ba4448 --- /dev/null +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorRouteExtensionsTest.kt @@ -0,0 +1,125 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.kotest.assertions.ktor.client.shouldHaveStatus +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain +import io.ktor.client.request.get +import io.ktor.client.request.post +import io.ktor.client.statement.bodyAsText +import io.ktor.http.HttpStatusCode +import io.ktor.server.application.install +import io.ktor.server.response.respondText +import io.ktor.server.routing.get +import io.ktor.server.routing.route +import io.ktor.server.routing.routing +import io.ktor.server.sse.SSE +import io.ktor.server.testing.testApplication +import kotlin.test.Test +import kotlin.test.assertFailsWith + +/** + * Integration tests for Ktor Route.mcp() extensions. + * + * Verifies issue #237: Route.mcp() should work on subpaths and with middleware. + * The key issue was that Routing.mcp() registered at top-level, preventing use on subpaths. + * Now Route.mcp() allows registration on any route path. + */ +class KtorRouteExtensionsTest : AbstractKtorExtensionsTest() { + + /** + * Verifies that Route.mcp() throws immediately at route registration time + * when the SSE plugin has not been installed, rather than failing silently on the first request. + */ + @Test + fun `Route mcp should throw at registration time if SSE plugin is not installed`() { + val exception = assertFailsWith { + testApplication { + application { + // Intentionally omit install(SSE) + routing { + mcp { testServer() } + } + } + client.get("/") + } + } + exception.message shouldContain "SSE" + exception.message shouldContain "install" + } + + /** + * Verifies that Route.mcp() registers SSE and POST endpoints on the subpath, + * not at root, and does not disturb sibling routes. + */ + @Test + fun `Route mcp should register SSE and POST endpoints at the given subpath`() = testApplication { + application { + install(SSE) + + routing { + get("/") { call.respondText("root") } + + route("/api/mcp") { + get("/test") { call.respondText("test-endpoint") } + mcp { testServer() } + } + } + } + + // Sibling routes are unaffected + val rootResponse = client.get("/") + rootResponse.shouldHaveStatus(HttpStatusCode.OK) + rootResponse.bodyAsText() shouldBe "root" + + val testResponse = client.get("/api/mcp/test") + testResponse.shouldHaveStatus(HttpStatusCode.OK) + testResponse.bodyAsText() shouldBe "test-endpoint" + + client.assertMcpEndpointsAt("/api/mcp") + } + + /** + * Verifies that Route.mcp() registers endpoints at the correct fully-qualified + * path when nested inside multiple route() calls. + */ + @Test + fun `Route mcp should register endpoints at the full nested path`() = testApplication { + application { + install(SSE) + + routing { + route("/v1") { + route("/services") { + route("/mcp") { + mcp { testServer() } + } + } + } + } + } + + client.assertMcpEndpointsAt("/v1/services/mcp") + } + + /** + * Verifies that mcp(path) registers SSE and POST endpoints at the sub-path + * relative to the enclosing route, not at the root. + */ + @Test + fun `Route mcp with path parameter should register endpoints at the resolved path`() = testApplication { + application { + install(SSE) + + routing { + route("/api") { + mcp("/mcp-endpoint") { testServer() } + } + } + } + + client.assertMcpEndpointsAt("/api/mcp-endpoint") + + // Root /api is not an MCP endpoint + client.post("/api").shouldHaveStatus(HttpStatusCode.NotFound) + } +} From 15559e690d5c1c338d49fa7864583f595ed7696d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Feb 2026 18:32:33 +0200 Subject: [PATCH 156/449] chore(deps): bump ajv from 8.17.1 to 8.18.0 in /integration-test/src/jvmTest/typescript in the npm_and_yarn group across 1 directory (#533) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the npm_and_yarn group with 1 update in the /integration-test/src/jvmTest/typescript directory: [ajv](https://github.com/ajv-validator/ajv). Updates `ajv` from 8.17.1 to 8.18.0

Release notes

Sourced from ajv's releases.

v8.18.0

What's Changed

New Contributors

Full Changelog: https://github.com/ajv-validator/ajv/compare/v8.17.1...v8.18.0

Commits
  • 142ce84 8.18.0
  • 720a23f fix(pattern): use configured RegExp engine with $data keyword to mitigate ReD...
  • 82735a1 fix: typos in schema-language.md (#2507)
  • b17ec32 fix: small grammatical error in managing-schemas.md (#2508)
  • 69568d0 fix: #2482 Infinity and NaN serialise to null (#2487)
  • f06766f feat: allow tree-shaking by adding ``"sideEffects": falsetopackage.json` ...
  • See full diff in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=ajv&package-manager=npm_and_yarn&previous-version=8.17.1&new-version=8.18.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/modelcontextprotocol/kotlin-sdk/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- integration-test/src/jvmTest/typescript/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index 19aec64..fddbba4 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -661,9 +661,9 @@ } }, "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", From 14139b6485c908e4c575b702698f60847630ff9d Mon Sep 17 00:00:00 2001 From: Den Delimarsky <53200638+localden@users.noreply.github.com> Date: Wed, 18 Feb 2026 13:03:42 -0800 Subject: [PATCH 157/449] Update SECURITY.md to use GitHub Security Advisories (#536) Replaces Anthropic HackerOne VDP references with standard GitHub Security Advisory process. --- SECURITY.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..5029242 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,21 @@ +# Security Policy + +Thank you for helping keep the Model Context Protocol and its ecosystem secure. + +## Reporting Security Issues + +If you discover a security vulnerability in this repository, please report it through +the [GitHub Security Advisory process](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability) +for this repository. + +Please **do not** report security vulnerabilities through public GitHub issues, discussions, +or pull requests. + +## What to Include + +To help us triage and respond quickly, please include: + +- A description of the vulnerability +- Steps to reproduce the issue +- The potential impact +- Any suggested fixes (optional) From fe97b442f6a146fbb6b508178087571af76427cf Mon Sep 17 00:00:00 2001 From: Anastasiia Zarechneva <49490937+aozherelyeva@users.noreply.github.com> Date: Fri, 20 Feb 2026 16:48:06 +0100 Subject: [PATCH 158/449] Some more Streamable HTTP client/server transport tests (#551) --- .../client/StreamableHttpClientTransport.kt | 4 + .../http/StreamableHttpClientTransportTest.kt | 255 ++++++++++++++++++ .../StreamableHttpServerTransportTest.kt | 192 ++++++++++++- 3 files changed, 440 insertions(+), 11 deletions(-) diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt index efa5832..a3b4190 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt @@ -332,6 +332,10 @@ public class StreamableHttpClientTransport( throw it } } + if (eventName == "error") { + _onError(StreamableHttpError(null, data)) + return + } } @Suppress("LoopWithTooManyJumpStatements") diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt index 4696184..d509813 100644 --- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt @@ -1,6 +1,9 @@ package io.modelcontextprotocol.kotlin.sdk.client.streamable.http +import io.kotest.assertions.withClue +import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.shouldBe +import io.kotest.matchers.types.shouldBeInstanceOf import io.ktor.client.HttpClient import io.ktor.client.engine.mock.MockEngine import io.ktor.client.engine.mock.MockRequestHandler @@ -22,6 +25,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest import io.modelcontextprotocol.kotlin.sdk.types.McpException import io.modelcontextprotocol.kotlin.sdk.types.McpJson import io.modelcontextprotocol.kotlin.sdk.types.RPCError +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.test.runTest @@ -51,6 +55,14 @@ class StreamableHttpClientTransportTest { return StreamableHttpClientTransport(httpClient, url = "http://localhost:8080/mcp") } + private fun buildSseMessage(id: String, method: String, params: String): String = buildString { + appendLine("event: message") + appendLine("id: $id") + val data = """{"jsonrpc":"2.0","method":"$method","params":$params}""" + appendLine("data: $data") + appendLine() + } + @Test fun testSendJsonRpcMessage() = runTest { val message = JSONRPCRequest( @@ -79,6 +91,42 @@ class StreamableHttpClientTransportTest { transport.close() } + @Test + fun testStartTransportTwoTimesThrowsException() = runTest { + val transport = createTransport { _ -> + respond( + content = "", + status = HttpStatusCode.Accepted, + ) + } + + transport.start() + try { + transport.start() + } catch (e: IllegalStateException) { + e shouldBe IllegalStateException("Can't change state: expected transport state New, but found Operational.") + } + transport.close() + } + + @Test + fun testCloseTransportTwoTimesIsPossible() = runTest { + val transport = createTransport { _ -> + respond( + content = "", + status = HttpStatusCode.Accepted, + ) + } + + transport.start() + transport.close() + try { + transport.close() + } catch (e: Exception) { + withClue("We expect no exceptions when closing already closed transport") { e shouldBe null } + } + } + @Test fun testStoreSessionId() = runTest { val initMessage = JSONRPCRequest( @@ -428,4 +476,211 @@ class StreamableHttpClientTransportTest { transport.close() } } + + @Test + fun testInlineSSEInResponse() = runTest { + val transport = createTransport { request -> + if (request.method == HttpMethod.Post) { + val sseContent = buildString { + append( + buildSseMessage( + id = "1", + method = "notifications/progress", + params = """{"progressToken":"task-1","progress":50}""", + ), + ) + append( + buildSseMessage( + id = "2", + method = "notifications/tools/list_changed", + params = "{}", + ), + ) + } + + respond( + content = ByteReadChannel(sseContent), + status = HttpStatusCode.OK, + headers = headersOf( + HttpHeaders.ContentType, + ContentType.Text.EventStream.toString(), + ), + ) + } else { + respond("", HttpStatusCode.OK) + } + } + + val receivedMessages = mutableListOf() + val twoMessagesReceived = CompletableDeferred() + + transport.onMessage { message -> + receivedMessages.add(message) + if (receivedMessages.size >= 2 && !twoMessagesReceived.isCompleted) { + twoMessagesReceived.complete(Unit) + } + } + + transport.start() + + transport.send( + JSONRPCRequest( + id = "test-1", + method = "test", + params = buildJsonObject { }, + ), + ) + + withTimeout(2.seconds) { + twoMessagesReceived.await() + } + + receivedMessages.size shouldBe 2 + + val firstNotification = receivedMessages[0] as JSONRPCNotification + firstNotification.method shouldBe "notifications/progress" + + val secondNotification = receivedMessages[1] as JSONRPCNotification + secondNotification.method shouldBe "notifications/tools/list_changed" + + transport.close() + } + + @Test + fun testErrorInSSEResponse() = runTest { + val errorMessage = "Something went wrong on the server" + val transport = createTransport { request -> + if (request.method == HttpMethod.Post) { + val sseContent = buildString { + appendLine("event: error") + appendLine("data: $errorMessage") + appendLine() + } + + respond( + content = ByteReadChannel(sseContent), + status = HttpStatusCode.OK, + headers = headersOf( + HttpHeaders.ContentType, + ContentType.Text.EventStream.toString(), + ), + ) + } else { + respond("", HttpStatusCode.OK) + } + } + + val receivedErrors = mutableListOf() + val errorDeferred = CompletableDeferred() + + transport.onError { error -> + receivedErrors.add(error) + if (!errorDeferred.isCompleted) { + errorDeferred.complete(error) + } + } + + transport.start() + + transport.send( + JSONRPCRequest( + id = "test-error", + method = "test", + params = buildJsonObject { }, + ), + ) + + val error = withTimeout(2.seconds) { errorDeferred.await() } + + receivedErrors.size shouldBe 1 + error.message shouldBe "Streamable HTTP error: $errorMessage" + + transport.close() + } + + @Test + fun testCloseTransportDuringEventStream() = runTest { + val transport = createTransport { request -> + if (request.method == HttpMethod.Post) { + val sseContent = buildString { + append( + buildSseMessage( + id = "1", + method = "notifications/progress", + params = """{"progressToken":"task-1","progress":25}""", + ), + ) + append( + buildSseMessage( + id = "2", + method = "notifications/progress", + params = """{"progressToken":"task-1","progress":50}""", + ), + ) + append( + buildSseMessage( + id = "3", + method = "notifications/progress", + params = """{"progressToken":"task-1","progress":75}""", + ), + ) + } + respond( + content = ByteReadChannel(sseContent), + status = HttpStatusCode.OK, + headers = headersOf( + HttpHeaders.ContentType, + ContentType.Text.EventStream.toString(), + ), + ) + } else { + respond("", HttpStatusCode.OK) + } + } + + val (receivedMessages, receivedErrors) = setupTransportAndCollectMessages(transport) + + withTimeout(2.seconds) { + while (receivedMessages.isEmpty()) { + delay(10) + } + } + + transport.close() + + assertTrue(receivedMessages.isNotEmpty()) + + receivedMessages.forEach { message -> + message.shouldBeInstanceOf() + message.method shouldBe "notifications/progress" + } + receivedErrors shouldHaveSize 0 + } + + private suspend fun setupTransportAndCollectMessages( + transport: StreamableHttpClientTransport, + ): Pair, MutableList> { + val receivedMessages = mutableListOf() + val receivedErrors = mutableListOf() + + transport.onMessage { message -> + receivedMessages.add(message) + } + + transport.onError { error -> + receivedErrors.add(error) + } + + transport.start() + + transport.send( + JSONRPCRequest( + id = "test-close", + method = "test", + params = buildJsonObject { }, + ), + ) + + return receivedMessages to receivedErrors + } } diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt index 968c057..c6e539a 100644 --- a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt @@ -1,6 +1,6 @@ package io.modelcontextprotocol.kotlin.sdk.server -import io.kotest.matchers.collections.shouldContain +import io.kotest.matchers.collections.shouldContainAll import io.kotest.matchers.equals.shouldBeEqual import io.kotest.matchers.shouldBe import io.ktor.client.HttpClient @@ -42,6 +42,8 @@ import io.modelcontextprotocol.kotlin.sdk.types.toJSON import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.put +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource import java.util.concurrent.atomic.AtomicBoolean import kotlin.test.Test import kotlin.test.assertEquals @@ -51,6 +53,18 @@ import io.ktor.client.plugins.contentnegotiation.ContentNegotiation as ClientCon import io.ktor.server.plugins.contentnegotiation.ContentNegotiation as ServerContentNegotiation class StreamableHttpServerTransportTest { + companion object { + @JvmStatic + @MethodSource + fun invalidPayloads() = listOf( + "", + " ", + " \n \t ", + null, + "lolol", + ) + } + private val path = "/transport" @Test @@ -114,7 +128,86 @@ class StreamableHttpServerTransportTest { } @Test - fun `notifications only payload responds with 202 Accepted`() = testApplication { + fun `second initialization request returns an HTTP error`() = testApplication { + configTestServer() + + val client = createTestClient() + + val transport = StreamableHttpServerTransport(enableJsonResponse = true) + transport.onMessage { message -> + if (message is JSONRPCRequest) { + transport.send(JSONRPCResponse(message.id, EmptyResult())) + } + } + + configureTransportEndpoint(transport) + + val payload = buildInitializeRequestPayload() + + val firstResponse = client.post(path) { + addStreamableHeaders() + setBody(payload) + } + + firstResponse.status shouldBe HttpStatusCode.OK + + val secondResponse = client.post(path) { + addStreamableHeaders() + header("mcp-session-id", firstResponse.headers[MCP_SESSION_ID_HEADER]) + setBody(payload) + } + + secondResponse.status shouldBe HttpStatusCode.BadRequest + } + + @Test + fun `request with unsupported protocol version returns an HTTP error`() = testApplication { + configTestServer() + + val client = createTestClient() + + val transport = StreamableHttpServerTransport(enableJsonResponse = true) + transport.onMessage { message -> + if (message is JSONRPCRequest) { + transport.send(JSONRPCResponse(message.id, EmptyResult())) + } + } + + configureTransportEndpoint(transport) + + val initPayload = buildInitializeRequestPayload() + val initResponse = client.post(path) { + addStreamableHeaders() + setBody(initPayload) + } + + initResponse.status shouldBe HttpStatusCode.OK + val sessionId = initResponse.headers[MCP_SESSION_ID_HEADER] + assertNotNull(sessionId) + + // TODO When https://github.com/modelcontextprotocol/kotlin-sdk/issues/547 is fixed, + // check the incompatible mcp-protocol-version in the InitializeRequest and delete the part below + val response = client.post(path) { + addStreamableHeaders() + header("mcp-session-id", sessionId) + header("mcp-protocol-version", "1900-01-01") + setBody( + encodeMessages( + listOf( + JSONRPCRequest( + id = RequestId("test-1"), + method = Method.Defined.ToolsList.value, + ), + ), + ), + ) + } + + response.status shouldBe HttpStatusCode.BadRequest + } + + @Test + fun `notifications only payload is accepted`() = testApplication { configTestServer() val client = createTestClient() @@ -202,15 +295,92 @@ class StreamableHttpServerTransportTest { assertEquals(HttpStatusCode.OK, response.status) - val responses = response.body>().map { it.result } - responses shouldContain (firstResult) - responses shouldContain (secondResult) - // TODO(check order) -// assertEquals(listOf(firstRequest.id, secondRequest.id), responses.map { it.id }) -// val firstMeta = (responses[0].result as EmptyResult).meta -// val secondMeta = (responses[1].result as EmptyResult).meta -// assertEquals("first", firstMeta?.get("label")?.jsonPrimitive?.content) -// assertEquals("second", secondMeta?.get("label")?.jsonPrimitive?.content) + val responses = response.body>() + val results = responses.map { it.result } + results.shouldContainAll(firstResult, secondResult) + + // Check responses' order + + // TODO Uncomment when fixed https://github.com/modelcontextprotocol/kotlin-sdk/issues/548 + /*assertEquals(listOf(firstRequest.id, secondRequest.id), responses.map { it.id }) + val firstMeta = (responses[0] as ListToolsResult).meta + val secondMeta = (responses[1] as ListResourcesResult).meta + assertEquals("first", firstMeta?.get("label")?.jsonPrimitive?.content) + assertEquals("second", secondMeta?.get("label")?.jsonPrimitive?.content)*/ + } + + @ParameterizedTest + @MethodSource("invalidPayloads") + fun `POST with a null or empty body returns an error`(payload: String?) = testApplication { + configTestServer() + + val client = createTestClient() + + val transport = StreamableHttpServerTransport(enableJsonResponse = true) + transport.onMessage { message -> + if (message is JSONRPCRequest) { + transport.send(JSONRPCResponse(message.id, EmptyResult())) + } + } + + configureTransportEndpoint(transport) + + val response = client.post(path) { + addStreamableHeaders() + setBody(payload) + } + + response.status shouldBe HttpStatusCode.BadRequest + } + + @Test + fun `POST with payload at max size is accepted`() = testApplication { + configTestServer() + + val client = createTestClient() + + val transport = StreamableHttpServerTransport(enableJsonResponse = true) + transport.onMessage { message -> + if (message is JSONRPCRequest) { + transport.send(JSONRPCResponse(message.id, EmptyResult())) + } + } + + configureTransportEndpoint(transport) + + val maxSizePayload = "x".repeat(4 * 1024 * 1024) + + val response = client.post(path) { + addStreamableHeaders() + setBody(maxSizePayload) + } + + response.status shouldBe HttpStatusCode.BadRequest + } + + @Test + fun `POST with oversized body returns an error`() = testApplication { + configTestServer() + + val client = createTestClient() + + val transport = StreamableHttpServerTransport(enableJsonResponse = true) + transport.onMessage { message -> + if (message is JSONRPCRequest) { + transport.send(JSONRPCResponse(message.id, EmptyResult())) + } + } + + configureTransportEndpoint(transport) + + val oversizedPayload = "x".repeat(4 * 1024 * 1024 + 1) + + val response = client.post(path) { + addStreamableHeaders() + setBody(oversizedPayload) + } + + response.status shouldBe HttpStatusCode.PayloadTooLarge } private fun ApplicationTestBuilder.configureTransportEndpoint(transport: StreamableHttpServerTransport) { From 947d03dd44e847dc454b914e94a8ca3b8b1c82f1 Mon Sep 17 00:00:00 2001 From: Anastasiia Zarechneva <49490937+aozherelyeva@users.noreply.github.com> Date: Mon, 23 Feb 2026 14:03:39 +0100 Subject: [PATCH 159/449] Streamable HTTP transport tests follow-up (#559) ## Motivation and Context Replace `withTimeout` with `eventually` in test assertions and improve readability in exception handling ## How Has This Been Tested? ## Breaking Changes None ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [x] Test 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 ## Additional context --- .../http/StreamableHttpClientTransportTest.kt | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt index d509813..e303326 100644 --- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt @@ -1,6 +1,7 @@ package io.modelcontextprotocol.kotlin.sdk.client.streamable.http -import io.kotest.assertions.withClue +import io.kotest.assertions.fail +import io.kotest.assertions.nondeterministic.eventually import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.shouldBe import io.kotest.matchers.types.shouldBeInstanceOf @@ -122,8 +123,8 @@ class StreamableHttpClientTransportTest { transport.close() try { transport.close() - } catch (e: Exception) { - withClue("We expect no exceptions when closing already closed transport") { e shouldBe null } + } catch (_: Exception) { + fail("We expect no exceptions when closing already closed transport") } } @@ -323,10 +324,9 @@ class StreamableHttpClientTransportTest { assertTrue(sseStarted, "SSE should start after initialized notification") // Test 2: Verify received notifications - assertEquals(3, receivedMessages.size) - assertTrue(receivedMessages.all { it is JSONRPCNotification }) - + receivedMessages shouldHaveSize 3 val notifications = receivedMessages.filterIsInstance() + notifications shouldHaveSize 3 // Verify progress notification val progressNotif = notifications[0] @@ -531,11 +531,11 @@ class StreamableHttpClientTransportTest { ), ) - withTimeout(2.seconds) { + eventually { twoMessagesReceived.await() } - receivedMessages.size shouldBe 2 + receivedMessages shouldHaveSize 2 val firstNotification = receivedMessages[0] as JSONRPCNotification firstNotification.method shouldBe "notifications/progress" @@ -590,7 +590,7 @@ class StreamableHttpClientTransportTest { ), ) - val error = withTimeout(2.seconds) { errorDeferred.await() } + val error = eventually { errorDeferred.await() } receivedErrors.size shouldBe 1 error.message shouldBe "Streamable HTTP error: $errorMessage" @@ -640,7 +640,7 @@ class StreamableHttpClientTransportTest { val (receivedMessages, receivedErrors) = setupTransportAndCollectMessages(transport) - withTimeout(2.seconds) { + eventually { while (receivedMessages.isEmpty()) { delay(10) } @@ -648,7 +648,7 @@ class StreamableHttpClientTransportTest { transport.close() - assertTrue(receivedMessages.isNotEmpty()) + receivedMessages shouldHaveSize 3 receivedMessages.forEach { message -> message.shouldBeInstanceOf() From 4a0895912197ab8036bedba8dffd35f2ebf15502 Mon Sep 17 00:00:00 2001 From: Ryan Nett Date: Mon, 23 Feb 2026 06:30:15 -0800 Subject: [PATCH 160/449] feat! Add context to server call handlers (#515) ## Motivation and Context Adds a `ClientConnection` context receiver to server call handlers, which provides access to the client communication channel. This is necessary to allow tools to send notifications, logging, elicitations, etc. Supersedes https://github.com/modelcontextprotocol/kotlin-sdk/pull/144, discussed in https://github.com/modelcontextprotocol/kotlin-sdk/issues/295. ## How Has This Been Tested? With the added integration test. ## Breaking Changes It breaks binary compatibility and breaks source compatibility for tool handlers that already use `this`. If you care a lot about maintaining compatibility, I could add some overloads to ease the transition. But the new methods would likely need to be named differently for resolution. Future compatibility should be easier to maintain - `Context` is an interface (with experimental opt-in subclassing), so new fields can be added without breaking compatibility. Different types could also be introduced for different handles without breaking compatibility, as long as they extend `ClientConnection`. ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [x] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed ## Additional context --------- Co-authored-by: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> --- integration-test/detekt-baseline-test.xml | 1 - .../sdk/server/AbstractServerFeaturesTest.kt | 33 +++ .../sdk/server/ClientConnectionLoggingTest.kt | 172 ++++++++++++ .../kotlin/sdk/server/ClientConnectionTest.kt | 163 +++++++++++ .../sdk/server/ServerBulkFeaturesTest.kt | 183 +++++++++++++ .../server/ServerFeaturesInvocationTest.kt | 222 +++++++++++++++ .../sdk/server/ServerFeaturesListTest.kt | 221 +++++++++++++++ .../server/ServerHandlerNotificationTest.kt | 124 +++++++++ kotlin-sdk-server/api/kotlin-sdk-server.api | 77 ++++-- .../detekt-baseline-commonMainSourceSet.xml | 8 - kotlin-sdk-server/detekt-baseline-main.xml | 22 -- .../kotlin/sdk/server/ClientConnection.kt | 249 +++++++++++++++++ .../kotlin/sdk/server/Feature.kt | 9 +- .../kotlin/sdk/server/Server.kt | 105 +++---- .../kotlin/sdk/server/ServerSession.kt | 257 ++++++++---------- 15 files changed, 1589 insertions(+), 257 deletions(-) create mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnectionLoggingTest.kt create mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnectionTest.kt create mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerBulkFeaturesTest.kt create mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerFeaturesInvocationTest.kt create mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerFeaturesListTest.kt create mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerHandlerNotificationTest.kt create mode 100644 kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnection.kt diff --git a/integration-test/detekt-baseline-test.xml b/integration-test/detekt-baseline-test.xml index 46849e9..1e93fe0 100644 --- a/integration-test/detekt-baseline-test.xml +++ b/integration-test/detekt-baseline-test.xml @@ -32,7 +32,6 @@ LongMethod:KotlinServerForTsClientSse.kt:HttpServerTransport$suspend fun handleRequest LongMethod:KotlinServerForTsClientSse.kt:KotlinServerForTsClient$fun createMcpServer: Server LongMethod:KotlinServerForTsClientSse.kt:KotlinServerForTsClient$fun start - LongMethod:KotlinTestBase.kt:KotlinTestBase$protected fun setupServer LongMethod:ServerResourcesNotificationSubscribeTest.kt:ServerResourcesNotificationSubscribeTest$@Test fun `should send resource notifications` LongMethod:TsEdgeCasesTestSse.kt:TsEdgeCasesTestSse$@Test @Timeout(30, unit = TimeUnit.SECONDS) fun testComplexConcurrentRequests: Unit MatchingDeclarationName:PromptIntegrationTestSse.kt:SchemaPromptIntegrationTestSse : AbstractPromptIntegrationTest diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/AbstractServerFeaturesTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/AbstractServerFeaturesTest.kt index 21b46d2..1d28218 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/AbstractServerFeaturesTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/AbstractServerFeaturesTest.kt @@ -1,9 +1,16 @@ package io.modelcontextprotocol.kotlin.sdk.server import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.client.ClientOptions import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport +import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult +import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.BeforeEach @@ -13,8 +20,31 @@ abstract class AbstractServerFeaturesTest { protected lateinit var server: Server protected lateinit var client: Client + protected fun addTool(name: String, block: suspend ClientConnection.() -> Unit) { + server.addTool(name, "Test $name") { + block() + CallToolResult(listOf(TextContent("Success"))) + } + } + + protected fun addPrompt(name: String, block: suspend ClientConnection.() -> Unit) { + server.addPrompt(name, "Test $name") { + block() + GetPromptResult(messages = emptyList()) + } + } + + protected fun addResource(uri: String, block: suspend ClientConnection.() -> Unit) { + server.addResource(uri, uri, "Test resource $uri") { + block() + ReadResourceResult(contents = listOf(TextResourceContents(text = "content", uri = uri))) + } + } + abstract fun getServerCapabilities(): ServerCapabilities + protected open fun getClientCapabilities(): ClientCapabilities = ClientCapabilities() + protected open fun getServerInstructionsProvider(): (() -> String)? = null @BeforeEach @@ -33,6 +63,9 @@ abstract class AbstractServerFeaturesTest { client = Client( clientInfo = Implementation(name = "test client", version = "1.0"), + options = ClientOptions( + capabilities = getClientCapabilities(), + ), ) runBlocking { diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnectionLoggingTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnectionLoggingTest.kt new file mode 100644 index 0000000..81a3b41 --- /dev/null +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnectionLoggingTest.kt @@ -0,0 +1,172 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.kotest.matchers.collections.shouldBeEmpty +import io.kotest.matchers.collections.shouldHaveSize +import io.kotest.matchers.shouldBe +import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest +import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel +import io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotification +import io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotificationParams +import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource + +class ClientConnectionLoggingTest : AbstractServerFeaturesTest() { + + override fun getServerCapabilities(): ServerCapabilities = ServerCapabilities( + tools = ServerCapabilities.Tools(listChanged = true), + logging = JsonObject(emptyMap()), + ) + + @Test + fun `notification should send logging message to client`(): Unit = runBlocking { + val notificationReceived = CompletableDeferred() + client.setNotificationHandler(Method.Defined.NotificationsMessage) { + notificationReceived.complete(it) + CompletableDeferred(Unit) + } + + val expectedLevel = LoggingLevel.Info + val expectedData = JsonPrimitive("test-data-sample") + + addTool("test-notification") { + notification( + LoggingMessageNotification( + LoggingMessageNotificationParams( + level = expectedLevel, + data = expectedData, + ), + ), + ) + } + + client.callTool(CallToolRequest(CallToolRequestParams("test-notification"))) + val received = notificationReceived.await() + received.params.level shouldBe expectedLevel + received.params.data shouldBe expectedData + } + + @ParameterizedTest + @EnumSource(LoggingLevel::class) + fun `notification should filter logging messages below level`(minLevel: LoggingLevel): Unit = runBlocking { + val receivedMessages = mutableListOf() + client.setNotificationHandler(Method.Defined.NotificationsMessage) { + receivedMessages.add(it) + CompletableDeferred(Unit) + } + + client.setLoggingLevel(minLevel) + + addTool("test-notification-filtered") { + LoggingLevel.entries.forEach { level -> + notification( + LoggingMessageNotification( + LoggingMessageNotificationParams( + level = level, + data = JsonPrimitive(level.name), + ), + ), + ) + } + } + + client.callTool(CallToolRequest(CallToolRequestParams("test-notification-filtered"))) + + val expectedLevels = LoggingLevel.entries.filter { it >= minLevel } + receivedMessages shouldHaveSize expectedLevels.size + receivedMessages.map { it.params.level } shouldBe expectedLevels + } + + @ParameterizedTest + @EnumSource(LoggingLevel::class) + fun `sendLoggingMessage should send message at level`(expectedLevel: LoggingLevel): Unit = runBlocking { + val notificationReceived = CompletableDeferred() + client.setNotificationHandler(Method.Defined.NotificationsMessage) { + notificationReceived.complete(it) + CompletableDeferred(Unit) + } + + val expectedData = JsonObject(mapOf("key" to JsonPrimitive("value"))) + + addTool("test-logging") { + sendLoggingMessage( + LoggingMessageNotification( + LoggingMessageNotificationParams( + level = expectedLevel, + data = expectedData, + ), + ), + ) + } + + client.callTool(CallToolRequest(CallToolRequestParams("test-logging"))) + val received = notificationReceived.await() + received.params.level shouldBe expectedLevel + received.params.data shouldBe expectedData + } + + @ParameterizedTest + @EnumSource(LoggingLevel::class) + fun `sendLoggingMessage should filter messages below level`(minLevel: LoggingLevel): Unit = runBlocking { + val receivedMessages = mutableListOf() + client.setNotificationHandler(Method.Defined.NotificationsMessage) { + receivedMessages.add(it) + CompletableDeferred(Unit) + } + + client.setLoggingLevel(minLevel) + + addTool("test-logging-level") { + LoggingLevel.entries.forEach { level -> + sendLoggingMessage( + LoggingMessageNotification( + LoggingMessageNotificationParams( + level = level, + data = JsonPrimitive(level.name), + ), + ), + ) + } + } + + client.callTool(CallToolRequest(CallToolRequestParams("test-logging-level"))) + + val expectedLevels = LoggingLevel.entries.filter { it >= minLevel } + receivedMessages shouldHaveSize expectedLevels.size + receivedMessages.map { it.params.level } shouldBe expectedLevels + } + + @Test + fun `sendLoggingMessage should send no messages when level is set to highest`(): Unit = runBlocking { + val receivedMessages = mutableListOf() + client.setNotificationHandler(Method.Defined.NotificationsMessage) { + receivedMessages.add(it) + CompletableDeferred(Unit) + } + + client.setLoggingLevel(LoggingLevel.Emergency) + + addTool("test-logging-highest") { + LoggingLevel.entries.dropLast(1).forEach { level -> + sendLoggingMessage( + LoggingMessageNotification( + LoggingMessageNotificationParams( + level = level, + data = JsonPrimitive(level.name), + ), + ), + ) + } + } + + client.callTool(CallToolRequest(CallToolRequestParams("test-logging-highest"))) + receivedMessages.shouldBeEmpty() + } +} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnectionTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnectionTest.kt new file mode 100644 index 0000000..71f8129 --- /dev/null +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnectionTest.kt @@ -0,0 +1,163 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.kotest.assertions.assertSoftly +import io.kotest.assertions.withClue +import io.kotest.matchers.shouldBe +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.GetPromptRequest +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.ListRootsRequest +import io.modelcontextprotocol.kotlin.sdk.types.ListRootsResult +import io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel +import io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotification +import io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotificationParams +import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.Notification +import io.modelcontextprotocol.kotlin.sdk.types.PromptListChangedNotification +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.Request +import io.modelcontextprotocol.kotlin.sdk.types.RequestResult +import io.modelcontextprotocol.kotlin.sdk.types.ResourceListChangedNotification +import io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotification +import io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotificationParams +import io.modelcontextprotocol.kotlin.sdk.types.Root +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.ToolListChangedNotification +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.test.runTest +import kotlinx.serialization.json.JsonPrimitive +import org.junit.jupiter.api.Test + +class ClientConnectionTest : AbstractServerFeaturesTest() { + + private inline fun onClientNotification(method: Method): CompletableDeferred { + val deferred = CompletableDeferred() + client.setNotificationHandler(method) { + deferred.complete(it) + CompletableDeferred(Unit) + } + return deferred + } + + private inline fun onClientRequest( + method: Method, + crossinline response: (Req) -> Res, + ) { + client.setRequestHandler(method) { req, _ -> response(req) } + } + + override fun getServerCapabilities(): ServerCapabilities = ServerCapabilities( + tools = ServerCapabilities.Tools(listChanged = true), + prompts = ServerCapabilities.Prompts(listChanged = true), + resources = ServerCapabilities.Resources(listChanged = true), + logging = ServerCapabilities.Logging, + ) + + override fun getClientCapabilities(): ClientCapabilities = ClientCapabilities( + roots = ClientCapabilities.Roots(listChanged = true), + ) + + private val sampleRoots = listOf(Root("file:///project", "Project Root")) + + /** Holds all deferreds that capture what a server-side handler emits during [callAllMethods]. */ + private inner class Captures { + val pingComplete = CompletableDeferred() + val rootsResult = CompletableDeferred() + val sessionId = CompletableDeferred() + val toolListChanged = + onClientNotification(Method.Defined.NotificationsToolsListChanged) + val resourceListChanged = + onClientNotification(Method.Defined.NotificationsResourcesListChanged) + val promptListChanged = + onClientNotification(Method.Defined.NotificationsPromptsListChanged) + val loggingMessage = onClientNotification(Method.Defined.NotificationsMessage) + val resourceUpdated = + onClientNotification(Method.Defined.NotificationsResourcesUpdated) + + init { + onClientRequest(Method.Defined.RootsList) { + ListRootsResult(sampleRoots) + } + } + + suspend fun assertAll() { + val roots = rootsResult.await() + val log = loggingMessage.await() + val resourceUri = resourceUpdated.await().params.uri + val capturedSessionId = sessionId.await() + assertSoftly { + withClue("ping() was not called inside the handler") { + pingComplete.isCompleted shouldBe true + } + roots.roots shouldBe sampleRoots + withClue("sendToolListChanged() did not deliver a notification to the client") { + toolListChanged.isCompleted shouldBe true + } + withClue("sendResourceListChanged() did not deliver a notification to the client") { + resourceListChanged.isCompleted shouldBe true + } + withClue("sendPromptListChanged() did not deliver a notification to the client") { + promptListChanged.isCompleted shouldBe true + } + withClue("sendLoggingMessage() delivered unexpected payload") { + log.params.data shouldBe JsonPrimitive("log") + } + withClue("sendResourceUpdated() delivered wrong URI") { + resourceUri shouldBe "test://res" + } + capturedSessionId.shouldNotBeBlank() + } + } + } + + /** Exercises every [ClientConnection] method, recording results into [cap]. */ + private suspend fun ClientConnection.callAllMethods(cap: Captures) { + ping() + cap.pingComplete.complete(Unit) + cap.rootsResult.complete(listRoots()) + sendToolListChanged() + sendResourceListChanged() + sendPromptListChanged() + sendLoggingMessage( + LoggingMessageNotification( + LoggingMessageNotificationParams(level = LoggingLevel.Info, data = JsonPrimitive("log")), + ), + ) + sendResourceUpdated(ResourceUpdatedNotification(ResourceUpdatedNotificationParams("test://res"))) + cap.sessionId.complete(sessionId) + } + + @Test + fun `all ClientConnection methods are callable from tool handler`() = runTest { + val cap = Captures() + addTool("test") { callAllMethods(cap) } + + client.callTool(CallToolRequest(CallToolRequestParams("test"))) + + cap.assertAll() + } + + @Test + fun `all ClientConnection methods are callable from prompt handler`() = runTest { + val cap = Captures() + addPrompt("test") { callAllMethods(cap) } + + client.getPrompt(GetPromptRequest(GetPromptRequestParams("test"))) + + cap.assertAll() + } + + @Test + fun `all ClientConnection methods are callable from resource handler`() = runTest { + val cap = Captures() + addResource("test://resource") { callAllMethods(cap) } + + client.readResource(ReadResourceRequest(ReadResourceRequestParams("test://resource"))) + + cap.assertAll() + } +} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerBulkFeaturesTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerBulkFeaturesTest.kt new file mode 100644 index 0000000..1eb828f --- /dev/null +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerBulkFeaturesTest.kt @@ -0,0 +1,183 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.kotest.matchers.collections.shouldBeEmpty +import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder +import io.kotest.matchers.collections.shouldHaveSize +import io.kotest.matchers.shouldBe +import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult +import io.modelcontextprotocol.kotlin.sdk.types.Prompt +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult +import io.modelcontextprotocol.kotlin.sdk.types.Resource +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.Tool +import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Test + +class ServerBulkFeaturesTest : AbstractServerFeaturesTest() { + + override fun getServerCapabilities(): ServerCapabilities = ServerCapabilities( + tools = ServerCapabilities.Tools(listChanged = null), + prompts = ServerCapabilities.Prompts(listChanged = null), + resources = ServerCapabilities.Resources(listChanged = null, subscribe = null), + ) + + // ── addTools ─────────────────────────────────────────────────────────────── + + @Test + fun `addTools should register all provided tools`() = runTest { + server.addTools( + listOf( + RegisteredTool(Tool("bulk-a", ToolSchema(), "Tool A")) { CallToolResult(emptyList()) }, + RegisteredTool(Tool("bulk-b", ToolSchema(), "Tool B")) { CallToolResult(emptyList()) }, + RegisteredTool(Tool("bulk-c", ToolSchema(), "Tool C")) { CallToolResult(emptyList()) }, + ), + ) + + val tools = client.listTools().tools + + tools shouldHaveSize 3 + tools.map { it.name } shouldContainExactlyInAnyOrder listOf("bulk-a", "bulk-b", "bulk-c") + } + + @Test + fun `addTools should append to existing tools`() = runTest { + server.addTool("existing", "Existing") { CallToolResult(emptyList()) } + server.addTools( + listOf( + RegisteredTool(Tool("new-a", ToolSchema())) { CallToolResult(emptyList()) }, + RegisteredTool(Tool("new-b", ToolSchema())) { CallToolResult(emptyList()) }, + ), + ) + + val tools = client.listTools().tools + + tools shouldHaveSize 3 + tools.map { it.name } shouldContainExactlyInAnyOrder listOf("existing", "new-a", "new-b") + } + + // ── addPrompts ───────────────────────────────────────────────────────────── + + @Test + fun `addPrompts should register all provided prompts`() = runTest { + server.addPrompts( + listOf( + RegisteredPrompt(Prompt("bulk-p1", "Prompt 1")) { GetPromptResult(messages = emptyList()) }, + RegisteredPrompt(Prompt("bulk-p2", "Prompt 2")) { GetPromptResult(messages = emptyList()) }, + ), + ) + + val prompts = client.listPrompts().prompts + + prompts shouldHaveSize 2 + prompts.map { it.name } shouldContainExactlyInAnyOrder listOf("bulk-p1", "bulk-p2") + } + + @Test + fun `addPrompts should append to existing prompts`() = runTest { + server.addPrompt("existing-prompt", "Existing") { GetPromptResult(messages = emptyList()) } + server.addPrompts( + listOf( + RegisteredPrompt(Prompt("new-p")) { GetPromptResult(messages = emptyList()) }, + ), + ) + + val prompts = client.listPrompts().prompts + + prompts shouldHaveSize 2 + prompts.map { it.name } shouldContainExactlyInAnyOrder listOf("existing-prompt", "new-p") + } + + // ── addResources ─────────────────────────────────────────────────────────── + + @Test + fun `addResources should register all provided resources`() = runTest { + server.addResources( + listOf( + RegisteredResource(Resource("test://bulk-r1", "Resource 1")) { ReadResourceResult(emptyList()) }, + RegisteredResource(Resource("test://bulk-r2", "Resource 2")) { ReadResourceResult(emptyList()) }, + ), + ) + + val resources = client.listResources().resources + + resources shouldHaveSize 2 + resources.map { it.uri } shouldContainExactlyInAnyOrder listOf("test://bulk-r1", "test://bulk-r2") + } + + @Test + fun `addResources should append to existing resources`() = runTest { + server.addResource("test://existing-res", "Existing", "Existing resource") { + ReadResourceResult(emptyList()) + } + server.addResources( + listOf( + RegisteredResource(Resource("test://new-res", "New")) { ReadResourceResult(emptyList()) }, + ), + ) + + val resources = client.listResources().resources + + resources shouldHaveSize 2 + resources.map { it.uri } shouldContainExactlyInAnyOrder listOf("test://existing-res", "test://new-res") + } + + // ── Partial-batch remove ─────────────────────────────────────────────────── + + @Test + fun `removeTools should return count of actually removed tools`() = runTest { + server.addTool("present", "Present") { CallToolResult(emptyList()) } + client.listTools().tools shouldHaveSize 1 + + val count = server.removeTools(listOf("present", "absent-1", "absent-2")) + + count shouldBe 1 + client.listTools().tools.shouldBeEmpty() + } + + @Test + fun `removeTools on empty list should return zero`() = runTest { + val count = server.removeTools(emptyList()) + + count shouldBe 0 + } + + @Test + fun `removePrompts should return count of actually removed prompts`() = runTest { + server.addPrompt("present-p", "Present") { GetPromptResult(messages = emptyList()) } + client.listPrompts().prompts shouldHaveSize 1 + + val count = server.removePrompts(listOf("present-p", "absent-p")) + + count shouldBe 1 + client.listPrompts().prompts.shouldBeEmpty() + } + + @Test + fun `removePrompts on empty list should return zero`() = runTest { + val count = server.removePrompts(emptyList()) + + count shouldBe 0 + } + + @Test + fun `removeResources should return count of actually removed resources`() = runTest { + server.addResource("test://present-r", "Present", "Present resource") { + ReadResourceResult(emptyList()) + } + client.listResources().resources shouldHaveSize 1 + + val count = server.removeResources(listOf("test://present-r", "test://absent-r")) + + count shouldBe 1 + client.listResources().resources.shouldBeEmpty() + } + + @Test + fun `removeResources on empty list should return zero`() = runTest { + val count = server.removeResources(emptyList()) + + count shouldBe 0 + } +} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerFeaturesInvocationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerFeaturesInvocationTest.kt new file mode 100644 index 0000000..52db367 --- /dev/null +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerFeaturesInvocationTest.kt @@ -0,0 +1,222 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.kotest.assertions.assertSoftly +import io.kotest.matchers.collections.shouldHaveSize +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest +import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult +import io.modelcontextprotocol.kotlin.sdk.types.McpException +import io.modelcontextprotocol.kotlin.sdk.types.PromptMessage +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult +import io.modelcontextprotocol.kotlin.sdk.types.Role +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.test.runTest +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.jsonPrimitive +import org.junit.jupiter.api.Test +import kotlin.test.assertFailsWith + +class ServerFeaturesInvocationTest : AbstractServerFeaturesTest() { + + override fun getServerCapabilities(): ServerCapabilities = ServerCapabilities( + tools = ServerCapabilities.Tools(listChanged = null), + prompts = ServerCapabilities.Prompts(listChanged = null), + resources = ServerCapabilities.Resources(listChanged = null, subscribe = null), + ) + + // ── Tool invocation ──────────────────────────────────────────────────────── + + @Test + fun `callTool should return tool result content`() = runTest { + server.addTool("echo", "Echo tool") { + CallToolResult(listOf(TextContent("echo response"))) + } + + val result = client.callTool(CallToolRequest(CallToolRequestParams("echo"))) + + assertSoftly(result) { + isError shouldNotBe true + (content.single() as TextContent).text shouldBe "echo response" + } + } + + @Test + fun `callTool should pass arguments to handler`() = runTest { + server.addTool("greet", "Greeting tool") { request -> + val name = request.params.arguments?.get("name")?.jsonPrimitive?.content ?: "stranger" + CallToolResult(listOf(TextContent("Hello, $name!"))) + } + + val result = client.callTool( + CallToolRequest( + CallToolRequestParams( + name = "greet", + arguments = JsonObject(mapOf("name" to JsonPrimitive("World"))), + ), + ), + ) + + (result.content.single() as TextContent).text shouldBe "Hello, World!" + } + + @Test + fun `callTool should return error result when tool not found`() = runTest { + val result = client.callTool(CallToolRequest(CallToolRequestParams("nonexistent"))) + + assertSoftly(result) { + isError shouldBe true + (content.single() as TextContent).text shouldBe "Tool nonexistent not found" + } + } + + @Test + fun `callTool should return error result when handler throws`() = runTest { + server.addTool("failing", "Failing tool") { + throw IllegalStateException("handler failure") + } + + val result = client.callTool(CallToolRequest(CallToolRequestParams("failing"))) + + assertSoftly(result) { + isError shouldBe true + (content.single() as TextContent).text shouldBe "Error executing tool failing: handler failure" + } + } + + @Test + fun `callTool should pass empty arguments map to handler`() = runTest { + server.addTool("greet", "Greeting tool") { request -> + val name = request.params.arguments?.get("name")?.jsonPrimitive?.content ?: "stranger" + CallToolResult(listOf(TextContent("Hello, $name!"))) + } + + val result = client.callTool( + CallToolRequest( + CallToolRequestParams( + name = "greet", + arguments = JsonObject(emptyMap()), + ), + ), + ) + + (result.content.single() as TextContent).text shouldBe "Hello, stranger!" + } + + // ── Prompt invocation ────────────────────────────────────────────────────── + + @Test + fun `getPrompt should return prompt description and messages`() = runTest { + server.addPrompt("my-prompt", "My prompt") { + GetPromptResult( + description = "Prompt result description", + messages = listOf( + PromptMessage(role = Role.User, content = TextContent("User message")), + ), + ) + } + + val result = client.getPrompt(GetPromptRequest(GetPromptRequestParams("my-prompt"))) + + assertSoftly(result) { + description shouldBe "Prompt result description" + messages shouldHaveSize 1 + (messages.single().content as TextContent).text shouldBe "User message" + } + } + + @Test + fun `getPrompt should pass arguments to handler`() = runTest { + server.addPrompt("templated", "Templated prompt") { request -> + val topic = request.params.arguments?.get("topic") ?: "unknown" + GetPromptResult( + messages = listOf( + PromptMessage(role = Role.User, content = TextContent("Tell me about $topic")), + ), + ) + } + + val result = client.getPrompt( + GetPromptRequest( + GetPromptRequestParams( + name = "templated", + arguments = mapOf("topic" to "Kotlin coroutines"), + ), + ), + ) + + (result.messages.single().content as TextContent).text shouldBe "Tell me about Kotlin coroutines" + } + + @Test + fun `getPrompt should use default when arguments are absent`() = runTest { + server.addPrompt("templated", "Templated prompt") { request -> + val topic = request.params.arguments?.get("topic") ?: "unknown" + GetPromptResult( + messages = listOf( + PromptMessage(role = Role.User, content = TextContent("Tell me about $topic")), + ), + ) + } + + val result = client.getPrompt(GetPromptRequest(GetPromptRequestParams("templated"))) + + (result.messages.single().content as TextContent).text shouldBe "Tell me about unknown" + } + + @Test + fun `getPrompt should throw when prompt not found`() = runTest { + assertFailsWith { + client.getPrompt(GetPromptRequest(GetPromptRequestParams("nonexistent"))) + } + } + + // ── Resource invocation ──────────────────────────────────────────────────── + + @Test + fun `readResource should return resource content`() = runTest { + val uri = "test://my-resource" + server.addResource(uri, "My Resource", "Test resource") { + ReadResourceResult( + contents = listOf(TextResourceContents(text = "resource content", uri = uri)), + ) + } + + val result = client.readResource(ReadResourceRequest(ReadResourceRequestParams(uri))) + + (result.contents.single() as TextResourceContents).text shouldBe "resource content" + } + + @Test + fun `readResource should pass request URI to handler`() = runTest { + val uri = "test://uri-check" + val receivedUri = CompletableDeferred() + server.addResource(uri, "URI Check", "Test") { request -> + receivedUri.complete(request.params.uri) + ReadResourceResult( + contents = listOf(TextResourceContents(text = "ok", uri = uri)), + ) + } + + client.readResource(ReadResourceRequest(ReadResourceRequestParams(uri))) + + receivedUri.await() shouldBe uri + } + + @Test + fun `readResource should throw when resource not found`() = runTest { + assertFailsWith { + client.readResource(ReadResourceRequest(ReadResourceRequestParams("test://nonexistent"))) + } + } +} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerFeaturesListTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerFeaturesListTest.kt new file mode 100644 index 0000000..e6aca7f --- /dev/null +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerFeaturesListTest.kt @@ -0,0 +1,221 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.kotest.assertions.assertSoftly +import io.kotest.matchers.collections.shouldBeEmpty +import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder +import io.kotest.matchers.collections.shouldHaveSize +import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.shouldBe +import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult +import io.modelcontextprotocol.kotlin.sdk.types.ListResourceTemplatesRequest +import io.modelcontextprotocol.kotlin.sdk.types.PromptArgument +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult +import io.modelcontextprotocol.kotlin.sdk.types.Resource +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Test + +class ServerFeaturesListTest : AbstractServerFeaturesTest() { + + override fun getServerCapabilities(): ServerCapabilities = ServerCapabilities( + tools = ServerCapabilities.Tools(listChanged = null), + prompts = ServerCapabilities.Prompts(listChanged = null), + resources = ServerCapabilities.Resources(listChanged = null, subscribe = null), + ) + + // ── listTools ────────────────────────────────────────────────────────────── + + @Test + fun `listTools should return empty list when no tools registered`() = runTest { + client.listTools().tools.shouldBeEmpty() + } + + @Test + fun `listTools should return all registered tools`() = runTest { + server.addTool("alpha", "First tool") { CallToolResult(emptyList()) } + server.addTool("beta", "Second tool") { CallToolResult(emptyList()) } + + val tools = client.listTools().tools + + tools shouldHaveSize 2 + tools.map { it.name } shouldContainExactlyInAnyOrder listOf("alpha", "beta") + } + + @Test + fun `listTools should expose tool name and description`() = runTest { + server.addTool("my-tool", "Detailed description") { CallToolResult(emptyList()) } + + val tool = client.listTools().tools.single() + + assertSoftly(tool) { + name shouldBe "my-tool" + description shouldBe "Detailed description" + } + } + + @Test + fun `listTools should reflect tool removal`() = runTest { + server.addTool("keep", "Kept") { CallToolResult(emptyList()) } + server.addTool("drop", "Dropped") { CallToolResult(emptyList()) } + client.listTools().tools shouldHaveSize 2 + + server.removeTool("drop") shouldBe true + + val tools = client.listTools().tools + + tools shouldHaveSize 1 + tools.single().name shouldBe "keep" + } + + @Test + fun `removeTool should return false when tool not found`() = runTest { + server.removeTool("ghost") shouldBe false + } + + // ── listPrompts ──────────────────────────────────────────────────────────── + + @Test + fun `listPrompts should return empty list when no prompts registered`() = runTest { + client.listPrompts().prompts.shouldBeEmpty() + } + + @Test + fun `listPrompts should return all registered prompts`() = runTest { + server.addPrompt("prompt-a", "First prompt") { GetPromptResult(messages = emptyList()) } + server.addPrompt("prompt-b", "Second prompt") { GetPromptResult(messages = emptyList()) } + + val prompts = client.listPrompts().prompts + + prompts shouldHaveSize 2 + prompts.map { it.name } shouldContainExactlyInAnyOrder listOf("prompt-a", "prompt-b") + } + + @Test + fun `listPrompts should expose prompt description`() = runTest { + server.addPrompt("my-prompt", "Helpful context prompt") { GetPromptResult(messages = emptyList()) } + + val prompt = client.listPrompts().prompts.single() + + assertSoftly(prompt) { + name shouldBe "my-prompt" + description shouldBe "Helpful context prompt" + } + } + + @Test + fun `listPrompts should include prompt arguments`() = runTest { + val args = listOf( + PromptArgument(name = "topic", description = "The topic to discuss", required = true), + PromptArgument(name = "tone", description = "Tone of voice", required = false), + ) + server.addPrompt("templated", "A templated prompt", args) { GetPromptResult(messages = emptyList()) } + + val prompt = client.listPrompts().prompts.single() + + val promptArgs = prompt.arguments.shouldNotBeNull() + promptArgs shouldHaveSize 2 + promptArgs.map { it.name } shouldContainExactlyInAnyOrder listOf("topic", "tone") + promptArgs.first { it.name == "topic" }.required shouldBe true + promptArgs.first { it.name == "tone" }.required shouldBe false + } + + @Test + fun `listPrompts should expose null description`() = runTest { + server.addPrompt("no-desc", null) { GetPromptResult(messages = emptyList()) } + + client.listPrompts().prompts.single().description shouldBe null + } + + @Test + fun `listPrompts should reflect prompt removal`() = runTest { + server.addPrompt("keep", "Kept") { GetPromptResult(messages = emptyList()) } + server.addPrompt("drop", "Dropped") { GetPromptResult(messages = emptyList()) } + client.listPrompts().prompts shouldHaveSize 2 + + server.removePrompt("drop") shouldBe true + + val prompts = client.listPrompts().prompts + + prompts shouldHaveSize 1 + prompts.single().name shouldBe "keep" + } + + @Test + fun `removePrompt should return false when prompt not found`() = runTest { + server.removePrompt("ghost") shouldBe false + } + + // ── listResources ────────────────────────────────────────────────────────── + + @Test + fun `listResources should return empty list when no resources registered`() = runTest { + client.listResources().resources.shouldBeEmpty() + } + + @Test + fun `listResources should return all registered resources`() = runTest { + server.addResource("test://res-1", "Resource 1", "First") { ReadResourceResult(emptyList()) } + server.addResource("test://res-2", "Resource 2", "Second") { ReadResourceResult(emptyList()) } + + val resources = client.listResources().resources + + resources shouldHaveSize 2 + resources.map { it.uri } shouldContainExactlyInAnyOrder listOf("test://res-1", "test://res-2") + } + + @Test + fun `listResources should expose resource metadata`() = runTest { + server.addResource("test://my-res", "My Resource", "A detailed resource", "application/json") { + ReadResourceResult(emptyList()) + } + + val resource = client.listResources().resources.single() + + assertSoftly(resource) { + uri shouldBe "test://my-res" + name shouldBe "My Resource" + description shouldBe "A detailed resource" + mimeType shouldBe "application/json" + } + } + + @Test + fun `listResources should expose resource without mimeType`() = runTest { + server.addResources( + listOf( + RegisteredResource(Resource("test://no-mime", "No Mime")) { + ReadResourceResult(emptyList()) + }, + ), + ) + + client.listResources().resources.single().mimeType shouldBe null + } + + @Test + fun `listResources should reflect resource removal`() = runTest { + server.addResource("test://keep", "Keep", "Kept") { ReadResourceResult(emptyList()) } + server.addResource("test://drop", "Drop", "Dropped") { ReadResourceResult(emptyList()) } + client.listResources().resources shouldHaveSize 2 + + server.removeResource("test://drop") shouldBe true + + val resources = client.listResources().resources + + resources shouldHaveSize 1 + resources.single().uri shouldBe "test://keep" + } + + @Test + fun `removeResource should return false when resource not found`() = runTest { + server.removeResource("test://ghost") shouldBe false + } + + // ── listResourceTemplates ────────────────────────────────────────────────── + + @Test + fun `listResourceTemplates should return empty list`() = runTest { + client.listResourceTemplates(ListResourceTemplatesRequest()).resourceTemplates.shouldBeEmpty() + } +} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerHandlerNotificationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerHandlerNotificationTest.kt new file mode 100644 index 0000000..655d1f1 --- /dev/null +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerHandlerNotificationTest.kt @@ -0,0 +1,124 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.kotest.matchers.shouldBe +import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest +import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult +import io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel +import io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotification +import io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotificationParams +import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.test.runTest +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import org.junit.jupiter.api.Test + +class ServerHandlerNotificationTest : AbstractServerFeaturesTest() { + + override fun getServerCapabilities(): ServerCapabilities = ServerCapabilities( + tools = ServerCapabilities.Tools(true), + prompts = ServerCapabilities.Prompts(true), + resources = ServerCapabilities.Resources(true, null), + logging = JsonObject(emptyMap()), + ) + + @Test + fun `tool handler can send notification to client`() = runTest { + val notificationReceived = CompletableDeferred() + client.setNotificationHandler(Method.Defined.NotificationsMessage) { notification -> + notificationReceived.complete(notification) + CompletableDeferred(Unit) + } + + val notificationText = "Notification from tool" + server.addTool("notify-tool", "A tool that notifies") { + sendLoggingMessage( + LoggingMessageNotification( + LoggingMessageNotificationParams( + level = LoggingLevel.Info, + data = JsonPrimitive(notificationText), + ), + ), + ) + CallToolResult(listOf(TextContent("Tool executed"))) + } + + val result = client.callTool(CallToolRequest(CallToolRequestParams(name = "notify-tool"))) + + (result.content[0] as TextContent).text shouldBe "Tool executed" + val received = notificationReceived.await() + received.params.level shouldBe LoggingLevel.Info + received.params.data shouldBe JsonPrimitive(notificationText) + } + + @Test + fun `prompt handler can send notification to client`() = runTest { + val notificationReceived = CompletableDeferred() + client.setNotificationHandler(Method.Defined.NotificationsMessage) { notification -> + notificationReceived.complete(notification) + CompletableDeferred(Unit) + } + + val notificationText = "Notification from prompt" + server.addPrompt("notify-prompt", "A prompt that notifies") { + sendLoggingMessage( + LoggingMessageNotification( + LoggingMessageNotificationParams( + level = LoggingLevel.Info, + data = JsonPrimitive(notificationText), + ), + ), + ) + GetPromptResult(messages = listOf(), description = "Prompt executed") + } + + val result = client.getPrompt(GetPromptRequest(GetPromptRequestParams(name = "notify-prompt"))) + + result.description shouldBe "Prompt executed" + val received = notificationReceived.await() + received.params.level shouldBe LoggingLevel.Info + received.params.data shouldBe JsonPrimitive(notificationText) + } + + @Test + fun `resource handler can send notification to client`() = runTest { + val notificationReceived = CompletableDeferred() + client.setNotificationHandler(Method.Defined.NotificationsMessage) { notification -> + notificationReceived.complete(notification) + CompletableDeferred(Unit) + } + + val resourceUri = "test://notify-resource" + val notificationText = "Notification from resource" + server.addResource(resourceUri, "Notify Resource", "A resource that notifies") { + sendLoggingMessage( + LoggingMessageNotification( + LoggingMessageNotificationParams( + level = LoggingLevel.Info, + data = JsonPrimitive(notificationText), + ), + ), + ) + ReadResourceResult( + contents = listOf(TextResourceContents(uri = resourceUri, text = "Resource read")), + ) + } + + val result = client.readResource(ReadResourceRequest(ReadResourceRequestParams(uri = resourceUri))) + + (result.contents[0] as TextResourceContents).text shouldBe "Resource read" + val received = notificationReceived.await() + received.params.level shouldBe LoggingLevel.Info + received.params.data shouldBe JsonPrimitive(notificationText) + } +} diff --git a/kotlin-sdk-server/api/kotlin-sdk-server.api b/kotlin-sdk-server/api/kotlin-sdk-server.api index f97845b..15bbe5e 100644 --- a/kotlin-sdk-server/api/kotlin-sdk-server.api +++ b/kotlin-sdk-server/api/kotlin-sdk-server.api @@ -1,3 +1,33 @@ +public abstract interface class io/modelcontextprotocol/kotlin/sdk/server/ClientConnection { + public abstract fun createElicitation (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun createElicitation (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun createElicitation$default (Lio/modelcontextprotocol/kotlin/sdk/server/ClientConnection;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun createElicitation$default (Lio/modelcontextprotocol/kotlin/sdk/server/ClientConnection;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public abstract fun createMessage (Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun createMessage$default (Lio/modelcontextprotocol/kotlin/sdk/server/ClientConnection;Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public abstract fun getSessionId ()Ljava/lang/String; + public abstract fun listRoots (Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun listRoots$default (Lio/modelcontextprotocol/kotlin/sdk/server/ClientConnection;Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public abstract fun notification (Lio/modelcontextprotocol/kotlin/sdk/types/ServerNotification;Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + 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 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; + public abstract fun sendResourceUpdated (Lio/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotification;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun sendToolListChanged (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class io/modelcontextprotocol/kotlin/sdk/server/ClientConnection$DefaultImpls { + public static synthetic fun createElicitation$default (Lio/modelcontextprotocol/kotlin/sdk/server/ClientConnection;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun createElicitation$default (Lio/modelcontextprotocol/kotlin/sdk/server/ClientConnection;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun createMessage$default (Lio/modelcontextprotocol/kotlin/sdk/server/ClientConnection;Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun listRoots$default (Lio/modelcontextprotocol/kotlin/sdk/server/ClientConnection;Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + 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 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 interface class io/modelcontextprotocol/kotlin/sdk/server/EventStore { public abstract fun getStreamIdForEventId (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun replayEventsAfter (Ljava/lang/String;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -15,41 +45,41 @@ public final class io/modelcontextprotocol/kotlin/sdk/server/KtorServerKt { } public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredPrompt : io/modelcontextprotocol/kotlin/sdk/server/Feature { - public fun (Lio/modelcontextprotocol/kotlin/sdk/types/Prompt;Lkotlin/jvm/functions/Function2;)V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/Prompt;Lkotlin/jvm/functions/Function3;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/Prompt; - public final fun component2 ()Lkotlin/jvm/functions/Function2; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/Prompt;Lkotlin/jvm/functions/Function2;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredPrompt; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredPrompt;Lio/modelcontextprotocol/kotlin/sdk/types/Prompt;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredPrompt; + public final fun component2 ()Lkotlin/jvm/functions/Function3; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/Prompt;Lkotlin/jvm/functions/Function3;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredPrompt; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredPrompt;Lio/modelcontextprotocol/kotlin/sdk/types/Prompt;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredPrompt; public fun equals (Ljava/lang/Object;)Z public fun getKey ()Ljava/lang/String; - public final fun getMessageProvider ()Lkotlin/jvm/functions/Function2; + public final fun getMessageProvider ()Lkotlin/jvm/functions/Function3; public final fun getPrompt ()Lio/modelcontextprotocol/kotlin/sdk/types/Prompt; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredResource : io/modelcontextprotocol/kotlin/sdk/server/Feature { - public fun (Lio/modelcontextprotocol/kotlin/sdk/types/Resource;Lkotlin/jvm/functions/Function2;)V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/Resource;Lkotlin/jvm/functions/Function3;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/Resource; - public final fun component2 ()Lkotlin/jvm/functions/Function2; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/Resource;Lkotlin/jvm/functions/Function2;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredResource; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredResource;Lio/modelcontextprotocol/kotlin/sdk/types/Resource;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredResource; + public final fun component2 ()Lkotlin/jvm/functions/Function3; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/Resource;Lkotlin/jvm/functions/Function3;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredResource; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredResource;Lio/modelcontextprotocol/kotlin/sdk/types/Resource;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredResource; public fun equals (Ljava/lang/Object;)Z public fun getKey ()Ljava/lang/String; - public final fun getReadHandler ()Lkotlin/jvm/functions/Function2; + public final fun getReadHandler ()Lkotlin/jvm/functions/Function3; public final fun getResource ()Lio/modelcontextprotocol/kotlin/sdk/types/Resource; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredTool : io/modelcontextprotocol/kotlin/sdk/server/Feature { - public fun (Lio/modelcontextprotocol/kotlin/sdk/types/Tool;Lkotlin/jvm/functions/Function2;)V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/Tool;Lkotlin/jvm/functions/Function3;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/Tool; - public final fun component2 ()Lkotlin/jvm/functions/Function2; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/Tool;Lkotlin/jvm/functions/Function2;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredTool; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredTool;Lio/modelcontextprotocol/kotlin/sdk/types/Tool;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredTool; + public final fun component2 ()Lkotlin/jvm/functions/Function3; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/Tool;Lkotlin/jvm/functions/Function3;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredTool; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredTool;Lio/modelcontextprotocol/kotlin/sdk/types/Tool;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredTool; public fun equals (Ljava/lang/Object;)Z - public final fun getHandler ()Lkotlin/jvm/functions/Function2; + public final fun getHandler ()Lkotlin/jvm/functions/Function3; public fun getKey ()Ljava/lang/String; public final fun getTool ()Lio/modelcontextprotocol/kotlin/sdk/types/Tool; public fun hashCode ()I @@ -61,17 +91,18 @@ public class io/modelcontextprotocol/kotlin/sdk/server/Server { public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/Implementation;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun (Lio/modelcontextprotocol/kotlin/sdk/types/Implementation;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)V public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/Implementation;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun addPrompt (Lio/modelcontextprotocol/kotlin/sdk/types/Prompt;Lkotlin/jvm/functions/Function2;)V - public final fun addPrompt (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lkotlin/jvm/functions/Function2;)V - public static synthetic fun addPrompt$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V + public final fun addPrompt (Lio/modelcontextprotocol/kotlin/sdk/types/Prompt;Lkotlin/jvm/functions/Function3;)V + public final fun addPrompt (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lkotlin/jvm/functions/Function3;)V + public static synthetic fun addPrompt$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)V public final fun addPrompts (Ljava/util/List;)V - public final fun addResource (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function2;)V - public static synthetic fun addResource$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V + public final fun addResource (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function3;)V + public static synthetic fun addResource$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)V public final fun addResources (Ljava/util/List;)V - public final fun addTool (Lio/modelcontextprotocol/kotlin/sdk/types/Tool;Lkotlin/jvm/functions/Function2;)V - public final fun addTool (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/functions/Function2;)V - public static synthetic fun addTool$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V + public final fun addTool (Lio/modelcontextprotocol/kotlin/sdk/types/Tool;Lkotlin/jvm/functions/Function3;)V + public final fun addTool (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/functions/Function3;)V + public static synthetic fun addTool$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)V public final fun addTools (Ljava/util/List;)V + public final fun clientConnection (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/server/ClientConnection; public final fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun connect (Lio/modelcontextprotocol/kotlin/sdk/shared/Transport;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun createElicitation (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; diff --git a/kotlin-sdk-server/detekt-baseline-commonMainSourceSet.xml b/kotlin-sdk-server/detekt-baseline-commonMainSourceSet.xml index 67d5e73..d383ec9 100644 --- a/kotlin-sdk-server/detekt-baseline-commonMainSourceSet.xml +++ b/kotlin-sdk-server/detekt-baseline-commonMainSourceSet.xml @@ -5,17 +5,9 @@ MagicNumber:StdioServerTransport.kt:StdioServerTransport$8192 MaxLineLength:SSEServerTransport.kt:SseServerTransport$"SSEServerTransport already started! If using Server class, note that connect() calls start() automatically." MaxLineLength:SSEServerTransport.kt:SseServerTransport$* - MaxLineLength:ServerSession.kt:ServerSession$"Client requested unsupported protocol version $requestedVersion, falling back to $LATEST_PROTOCOL_VERSION" - MaxLineLength:ServerSession.kt:ServerSession$"Creating message with ${params.params.messages.size} messages, maxTokens=${params.params.maxTokens}, temperature=${params.params.temperature}, systemPrompt=${if (params.params.systemPrompt != null) "present" else "absent"}" ReturnCount:KtorServer.kt:private suspend fun existingStreamableTransport: StreamableHttpServerTransport? - ThrowsCount:ServerSession.kt:ServerSession$override fun assertCapabilityForMethod - ThrowsCount:ServerSession.kt:ServerSession$override fun assertNotificationCapability - ThrowsCount:ServerSession.kt:ServerSession$override fun assertRequestHandlerCapability TooGenericExceptionCaught:SSEServerTransport.kt:SseServerTransport$e: Exception - TooGenericExceptionCaught:Server.kt:Server$e: Exception TooGenericExceptionCaught:StdioServerTransport.kt:StdioServerTransport$e: Throwable TooManyFunctions:KtorServer.kt:io.modelcontextprotocol.kotlin.sdk.server.KtorServer.kt - TooManyFunctions:Server.kt:Server - TooManyFunctions:ServerSession.kt:ServerSession : Protocol diff --git a/kotlin-sdk-server/detekt-baseline-main.xml b/kotlin-sdk-server/detekt-baseline-main.xml index c016bed..141c7d0 100644 --- a/kotlin-sdk-server/detekt-baseline-main.xml +++ b/kotlin-sdk-server/detekt-baseline-main.xml @@ -4,24 +4,14 @@ InjectDispatcher:FeatureNotificationService.kt:FeatureNotificationService$Default LongParameterList:KtorServer.kt:private suspend fun RoutingContext.streamableTransport: StreamableHttpServerTransport? - LongParameterList:Server.kt:Server$public fun addTool MagicNumber:StdioServerTransport.kt:StdioServerTransport$8192 MaxLineLength:SSEServerTransport.kt:SseServerTransport$"SSEServerTransport already started! If using Server class, note that connect() calls start() automatically." MaxLineLength:SSEServerTransport.kt:SseServerTransport$* - MaxLineLength:ServerSession.kt:ServerSession$"Client requested unsupported protocol version $requestedVersion, falling back to $LATEST_PROTOCOL_VERSION" - MaxLineLength:ServerSession.kt:ServerSession$"Creating message with ${params.params.messages.size} messages, maxTokens=${params.params.maxTokens}, temperature=${params.params.temperature}, systemPrompt=${if (params.params.systemPrompt != null) "present" else "absent"}" NoNameShadowing:FeatureNotificationService.kt:FeatureNotificationService${ it.remove(job) } - RedundantSuspendModifier:ServerSession.kt:ServerSession$suspend ReturnCount:KtorServer.kt:private suspend fun existingStreamableTransport: StreamableHttpServerTransport? - ThrowsCount:ServerSession.kt:ServerSession$override fun assertCapabilityForMethod - ThrowsCount:ServerSession.kt:ServerSession$override fun assertNotificationCapability - ThrowsCount:ServerSession.kt:ServerSession$override fun assertRequestHandlerCapability TooGenericExceptionCaught:SSEServerTransport.kt:SseServerTransport$e: Exception - TooGenericExceptionCaught:Server.kt:Server$e: Exception TooGenericExceptionCaught:StdioServerTransport.kt:StdioServerTransport$e: Throwable TooManyFunctions:KtorServer.kt:io.modelcontextprotocol.kotlin.sdk.server.KtorServer.kt - TooManyFunctions:Server.kt:Server - TooManyFunctions:ServerSession.kt:ServerSession : Protocol UnsafeCallOnNullableType:StreamableHttpServerTransport.kt:StreamableHttpServerTransport$responseRequestId!! UseCheckNotNull:Server.kt:Server$check(options.capabilities.prompts != null) { logger.error { "Failed to add prompt '${prompt.name}': Server does not support prompts capability" } "Server does not support prompts capability." } UseCheckNotNull:Server.kt:Server$check(options.capabilities.prompts != null) { logger.error { "Failed to add prompts: Server does not support prompts capability" } "Server does not support prompts capability." } @@ -35,17 +25,5 @@ UseCheckNotNull:Server.kt:Server$check(options.capabilities.tools != null) { logger.error { "Failed to add tools: Server does not support tools capability" } "Server does not support tools capability." } UseCheckNotNull:Server.kt:Server$check(options.capabilities.tools != null) { logger.error { "Failed to remove tool '$name': Server does not support tools capability" } "Server does not support tools capability." } UseCheckNotNull:Server.kt:Server$check(options.capabilities.tools != null) { logger.error { "Failed to remove tools: Server does not support tools capability" } "Server does not support tools capability." } - UseCheckOrError:ServerSession.kt:ServerSession$throw IllegalStateException( "Server does not support notifying about resources (required for ${method.value})", ) - UseCheckOrError:ServerSession.kt:ServerSession$throw IllegalStateException( "Server does not support notifying of prompt list changes (required for ${method.value})", ) - UseCheckOrError:ServerSession.kt:ServerSession$throw IllegalStateException( "Server does not support notifying of tool list changes (required for ${method.value})", ) - UseCheckOrError:ServerSession.kt:ServerSession$throw IllegalStateException("Client does not support elicitation (required for ${method.value})") - UseCheckOrError:ServerSession.kt:ServerSession$throw IllegalStateException("Client does not support listing roots (required for ${method.value})") - UseCheckOrError:ServerSession.kt:ServerSession$throw IllegalStateException("Client does not support sampling (required for ${method.value})") - UseCheckOrError:ServerSession.kt:ServerSession$throw IllegalStateException("Server does not support logging (required for $method)") - UseCheckOrError:ServerSession.kt:ServerSession$throw IllegalStateException("Server does not support logging (required for ${method.value})") - UseCheckOrError:ServerSession.kt:ServerSession$throw IllegalStateException("Server does not support prompts (required for $method)") - UseCheckOrError:ServerSession.kt:ServerSession$throw IllegalStateException("Server does not support resources (required for $method)") - UseCheckOrError:ServerSession.kt:ServerSession$throw IllegalStateException("Server does not support sampling (required for $method)") - UseCheckOrError:ServerSession.kt:ServerSession$throw IllegalStateException("Server does not support tools (required for $method)") diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnection.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnection.kt new file mode 100644 index 0000000..998d62c --- /dev/null +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnection.kt @@ -0,0 +1,249 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.github.oshai.kotlinlogging.KotlinLogging +import io.modelcontextprotocol.kotlin.sdk.shared.RequestOptions +import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest +import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageResult +import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequest +import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.ElicitResult +import io.modelcontextprotocol.kotlin.sdk.types.EmptyResult +import io.modelcontextprotocol.kotlin.sdk.types.ListRootsRequest +import io.modelcontextprotocol.kotlin.sdk.types.ListRootsResult +import io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel +import io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotification +import io.modelcontextprotocol.kotlin.sdk.types.PingRequest +import io.modelcontextprotocol.kotlin.sdk.types.PromptListChangedNotification +import io.modelcontextprotocol.kotlin.sdk.types.Request +import io.modelcontextprotocol.kotlin.sdk.types.RequestId +import io.modelcontextprotocol.kotlin.sdk.types.RequestResult +import io.modelcontextprotocol.kotlin.sdk.types.ResourceListChangedNotification +import io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotification +import io.modelcontextprotocol.kotlin.sdk.types.ServerNotification +import io.modelcontextprotocol.kotlin.sdk.types.ToolListChangedNotification + +private val logger = KotlinLogging.logger {} + +/** + * Represents a connection interface between a server and a client, enabling + * communication through notifications, requests, and other operations. + * This interface defines various methods to facilitate the interaction. + */ +@Suppress("TooManyFunctions") +public interface ClientConnection { + + /** + * A unique identifier identifying the current session. + * Has no inherent meaning. + */ + public val sessionId: String + + /** + * Sends a server-side notification to the client. + * + * @param notification The notification to be sent to the client. This typically represents + * a specific event or update from the server. + * @param relatedRequestId An optional identifier linking this notification to a prior request. + * Useful for correlating notifications with originating requests. + */ + public suspend fun notification(notification: ServerNotification, relatedRequestId: RequestId? = null) + + /** + * Sends a ping request to the client to check connectivity. + * + * @param request Optional request parameters for the ping operation. + * @param options Optional request options, such as timeout or progress callback settings. + * @return The result of the ping request. + * @throws IllegalStateException If for some reason the method is not supported or the connection is closed. + */ + public suspend fun ping(request: PingRequest = PingRequest(), options: RequestOptions? = null): EmptyResult + + /** + * Creates a message using the server's sampling capability. + * + * @param request The parameters for creating a message. + * @param options Optional request options. + * @return The created message result. + * @throws IllegalStateException If the server does not support sampling or if the request fails. + */ + public suspend fun createMessage( + request: CreateMessageRequest, + options: RequestOptions? = null, + ): CreateMessageResult + + /** + * Lists the available "roots" from the client's perspective (if supported). + * + * Roots define the boundaries of where servers can operate within the filesystem, + * allowing them to understand which directories and files they have access to + * + * @param request Optional request parameters containing metadata for the list roots operation. + * @param options Optional request options, such as timeout or progress callback settings. + * @return The list of roots. + * @throws IllegalStateException If the server or client does not support roots. + */ + public suspend fun listRoots( + request: ListRootsRequest = ListRootsRequest(), + options: RequestOptions? = null, + ): ListRootsResult + + /** + * Sends a message to the client requesting elicitation. + * This typically results in a form being displayed to the end user. + * + * @param message The message for the elicitation to display. + * @param requestedSchema The schema requested by the client for the elicitation result. + * Influences the form displayed to the user. + * @param options Optional request options. + * @return The result of the elicitation request. + * @throws IllegalStateException If the server or client does not support elicitation. + */ + public suspend fun createElicitation( + message: String, + requestedSchema: ElicitRequestParams.RequestedSchema, + options: RequestOptions? = null, + ): ElicitResult + + /** + * Sends a message to the client requesting elicitation. + * This typically results in a form being displayed to the user. + * + * @param request The elicitation request parameters. + * @param options Optional request options. + * @return The result of the elicitation request. + * @throws IllegalStateException If the server or client does not support elicitation. + */ + public suspend fun createElicitation(request: ElicitRequest, options: RequestOptions? = null): ElicitResult + + /** + * Sends a logging message notification to the client. + * Messages are filtered based on the current logging level set by the client. + * If no logging level is set, all messages are sent. + * + * @param notification The logging message notification. + */ + public suspend fun sendLoggingMessage(notification: LoggingMessageNotification) + + /** + * Sends a resource-updated notification to the client, indicating that a specific resource has changed. + * + * @param notification Details of the updated resource. + */ + public suspend fun sendResourceUpdated(notification: ResourceUpdatedNotification) + + /** + * Sends a notification to the client indicating that the list of resources has changed. + */ + public suspend fun sendResourceListChanged() + + /** + * Sends a notification to the client indicating that the list of tools has changed. + */ + public suspend fun sendToolListChanged() + + /** + * Sends a notification to the client indicating that the list of prompts has changed. + */ + public suspend fun sendPromptListChanged() +} + +@Suppress("TooManyFunctions") +internal class ClientConnectionImpl(private val session: ServerSession) : ClientConnection { + + override val sessionId: String get() = session.sessionId + + private suspend fun request(request: Request, options: RequestOptions? = null): T { + logger.trace { "Sending request to client for session $sessionId: $request" } + return session.request(request, options) + } + + override suspend fun notification(notification: ServerNotification, relatedRequestId: RequestId?) { + if (notification is LoggingMessageNotification && !isMessageAccepted(notification.params.level)) { + logger.trace { "Filtering out logging message with level ${notification.params.level}" } + return + } + logger.trace { "Sending notification to client for session $sessionId: $notification" } + session.notification(notification, relatedRequestId) + } + + override suspend fun ping(request: PingRequest, options: RequestOptions?): EmptyResult { + logger.trace { "Sending ping request: $request" } + return request(request, options) + } + + override suspend fun createMessage(request: CreateMessageRequest, options: RequestOptions?): CreateMessageResult { + with(request.params) { + logger.debug { + "Creating message with ${messages.size} messages, maxTokens=$maxTokens, " + + "temperature=$temperature, " + + "systemPrompt=${if (systemPrompt != null) "present" else "absent"}" + } + } + logger.trace { "Full createMessage params: $request" } + return request(request, options) + } + + override suspend fun listRoots(request: ListRootsRequest, options: RequestOptions?): ListRootsResult { + logger.debug { "Listing roots" } + return request(request, options) + } + + override suspend fun createElicitation(request: ElicitRequest, options: RequestOptions?): ElicitResult { + with(request.params) { + logger.debug { + "Creating elicitation with message length=${message.length}, " + + "schema properties count=${requestedSchema.properties.size}" + } + } + logger.trace { "ElicitRequest: $request" } + return request(request, options) + } + + override suspend fun createElicitation( + message: String, + requestedSchema: ElicitRequestParams.RequestedSchema, + options: RequestOptions?, + ): ElicitResult = createElicitation( + request = ElicitRequest(params = ElicitRequestParams(message, requestedSchema)), + options = options, + ) + + override suspend fun sendLoggingMessage(notification: LoggingMessageNotification) { + notification(notification) + } + + override suspend fun sendResourceUpdated(notification: ResourceUpdatedNotification) { + logger.debug { "Sending resource updated notification for: ${notification.params.uri}" } + notification(notification) + } + + override suspend fun sendResourceListChanged() { + logger.debug { "Sending resource list changed notification" } + notification(ResourceListChangedNotification()) + } + + override suspend fun sendToolListChanged() { + logger.debug { "Sending tool list changed notification" } + notification(ToolListChangedNotification()) + } + + override suspend fun sendPromptListChanged() { + logger.debug { "Sending prompt list changed notification" } + notification(PromptListChangedNotification()) + } + + /** + * Determines whether a message with the specified logging level is accepted + * based on the current logging level of the session. + * + * @param level The logging level of the message to be evaluated. + * @return True if the message is accepted (i.e., its level meets or exceeds + * the current session's logging level), or if no current logging + * level is set. Otherwise, false. + */ + private fun isMessageAccepted(level: LoggingLevel): Boolean { + val current = session.currentLoggingLevel.value ?: return true // If no level is set, don't filter + + return level >= current + } +} diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Feature.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Feature.kt index 78bed9c..609aaa2 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Feature.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Feature.kt @@ -25,7 +25,10 @@ internal interface Feature { * @property tool The tool definition. * @property handler A suspend function to handle the tool call requests. */ -public data class RegisteredTool(val tool: Tool, val handler: suspend (CallToolRequest) -> CallToolResult) : Feature { +public data class RegisteredTool( + val tool: Tool, + val handler: suspend ClientConnection.(CallToolRequest) -> CallToolResult, +) : Feature { override val key: String = tool.name } @@ -37,7 +40,7 @@ public data class RegisteredTool(val tool: Tool, val handler: suspend (CallToolR */ public data class RegisteredPrompt( val prompt: Prompt, - val messageProvider: suspend (GetPromptRequest) -> GetPromptResult, + val messageProvider: suspend ClientConnection.(GetPromptRequest) -> GetPromptResult, ) : Feature { override val key: String = prompt.name } @@ -50,7 +53,7 @@ public data class RegisteredPrompt( */ public data class RegisteredResource( val resource: Resource, - val readHandler: suspend (ReadResourceRequest) -> ReadResourceResult, + val readHandler: suspend ClientConnection.(ReadResourceRequest) -> ReadResourceResult, ) : Feature { override val key: String = resource.uri } diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt index 1b035b4..2401709 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt @@ -74,6 +74,7 @@ public class ServerOptions(public val capabilities: ServerCapabilities, enforceS * this server. The provider is called each time a new session is started to support dynamic instructions. * @param block A block to configure the mcp server. */ +@Suppress("TooManyFunctions") public open class Server( protected val serverInfo: Implementation, protected val options: ServerOptions, @@ -193,7 +194,7 @@ public open class Server( handleListTools() } session.setRequestHandler(Method.Defined.ToolsCall) { request, _ -> - handleCallTool(request) + handleCallTool(session, request) } } @@ -203,7 +204,7 @@ public open class Server( handleListPrompts() } session.setRequestHandler(Method.Defined.PromptsGet) { request, _ -> - handleGetPrompt(request) + handleGetPrompt(session, request) } } @@ -213,7 +214,7 @@ public open class Server( handleListResources() } session.setRequestHandler(Method.Defined.ResourcesRead) { request, _ -> - handleReadResource(request) + handleReadResource(session, request) } session.setRequestHandler(Method.Defined.ResourcesTemplatesList) { _, _ -> handleListResourceTemplates() @@ -294,7 +295,7 @@ public open class Server( * @param handler A suspend function that handles executing the tool when called by the client. * @throws IllegalStateException If the server does not support tools. */ - public fun addTool(tool: Tool, handler: suspend (CallToolRequest) -> CallToolResult) { + public fun addTool(tool: Tool, handler: suspend ClientConnection.(CallToolRequest) -> CallToolResult) { check(options.capabilities.tools != null) { logger.error { "Failed to add tool '${tool.name}': Server does not support tools capability" } "Server does not support tools capability. Enable it in ServerOptions." @@ -316,6 +317,7 @@ public open class Server( * @param handler A suspend function that handles executing the tool when called by the client. * @throws IllegalStateException If the server does not support tools. */ + @Suppress("LongParameterList") public fun addTool( name: String, description: String, @@ -324,7 +326,7 @@ public open class Server( outputSchema: ToolSchema? = null, toolAnnotations: ToolAnnotations? = null, meta: JsonObject? = null, - handler: suspend (CallToolRequest) -> CallToolResult, + handler: suspend ClientConnection.(CallToolRequest) -> CallToolResult, ) { val tool = Tool( name = name, @@ -391,7 +393,10 @@ public open class Server( * @param promptProvider A suspend function that returns the prompt content when requested by the client. * @throws IllegalStateException If the server does not support prompts. */ - public fun addPrompt(prompt: Prompt, promptProvider: suspend (GetPromptRequest) -> GetPromptResult) { + public fun addPrompt( + prompt: Prompt, + promptProvider: suspend ClientConnection.(GetPromptRequest) -> GetPromptResult, + ) { check(options.capabilities.prompts != null) { logger.error { "Failed to add prompt '${prompt.name}': Server does not support prompts capability" } "Server does not support prompts capability." @@ -412,7 +417,7 @@ public open class Server( name: String, description: String? = null, arguments: List? = null, - promptProvider: suspend (GetPromptRequest) -> GetPromptResult, + promptProvider: suspend ClientConnection.(GetPromptRequest) -> GetPromptResult, ) { val prompt = Prompt(name = name, description = description, arguments = arguments) addPrompt(prompt, promptProvider) @@ -479,7 +484,7 @@ public open class Server( name: String, description: String, mimeType: String = "text/html", - readHandler: suspend (ReadResourceRequest) -> ReadResourceResult, + readHandler: suspend ClientConnection.(ReadResourceRequest) -> ReadResourceResult, ) { check(options.capabilities.resources != null) { logger.error { "Failed to add resource '$name': Server does not support resources capability" } @@ -557,7 +562,7 @@ public open class Server( return ListToolsResult(tools = toolList, nextCursor = null) } - private suspend fun handleCallTool(request: CallToolRequest): CallToolResult { + private suspend fun handleCallTool(session: ServerSession, request: CallToolRequest): CallToolResult { val requestParams = request.params logger.debug { "Handling tool call request for tool: ${requestParams.name}" } @@ -571,9 +576,12 @@ public open class Server( } // Execute the tool handler and catch any errors + @Suppress("TooGenericExceptionCaught") return try { logger.trace { "Executing tool ${requestParams.name} with input: ${requestParams.arguments}" } - tool.handler(request) + tool.run { + session.clientConnection.handler(request) + } } catch (e: CancellationException) { throw e } catch (e: Exception) { @@ -590,7 +598,7 @@ public open class Server( return ListPromptsResult(prompts = prompts.values.map { it.prompt }) } - private suspend fun handleGetPrompt(request: GetPromptRequest): GetPromptResult { + private suspend fun handleGetPrompt(session: ServerSession, request: GetPromptRequest): GetPromptResult { val requestParams = request.params logger.debug { "Handling get prompt request for: ${requestParams.name}" } val prompt = promptRegistry.get(requestParams.name) @@ -598,7 +606,9 @@ public open class Server( logger.error { "Prompt not found: ${requestParams.name}" } throw IllegalArgumentException("Prompt not found: ${requestParams.name}") } - return prompt.messageProvider(request) + return prompt.run { + session.clientConnection.messageProvider(request) + } } private fun handleListResources(): ListResourcesResult { @@ -606,7 +616,7 @@ public open class Server( return ListResourcesResult(resources = resources.values.map { it.resource }) } - private suspend fun handleReadResource(request: ReadResourceRequest): ReadResourceResult { + private suspend fun handleReadResource(session: ServerSession, request: ReadResourceRequest): ReadResourceResult { val requestParams = request.params logger.debug { "Handling read resource request for: ${requestParams.uri}" } val resource = resourceRegistry.get(requestParams.uri) @@ -614,7 +624,9 @@ public open class Server( logger.error { "Resource not found: ${requestParams.uri}" } throw IllegalArgumentException("Resource not found: ${requestParams.uri}") } - return resource.readHandler(request) + return resource.run { + session.clientConnection.readHandler(request) + } } private fun handleListResourceTemplates(): ListResourceTemplatesResult { @@ -622,18 +634,19 @@ public open class Server( return ListResourceTemplatesResult(listOf()) } - // Start the ServerSession redirection section + // Start the ServerSession / ClientConnection redirection section + + public fun clientConnection(sessionId: String): ClientConnection = + sessionRegistry.getSession(sessionId).clientConnection /** - * Triggers [ServerSession.ping] request for session by provided [sessionId]. + * Triggers [ClientConnection.ping] request for session by provided [sessionId]. * @param sessionId The session ID to ping */ - public suspend fun ping(sessionId: String): EmptyResult = with(sessionRegistry.getSession(sessionId)) { - ping() - } + public suspend fun ping(sessionId: String): EmptyResult = clientConnection(sessionId).ping() /** - * Triggers [ServerSession.createMessage] request for session by provided [sessionId]. + * Triggers [ClientConnection.createMessage] request for session by provided [sessionId]. * * @param sessionId The session ID to create a message. * @param params The parameters for creating a message. @@ -645,12 +658,10 @@ public open class Server( sessionId: String, params: CreateMessageRequest, options: RequestOptions? = null, - ): CreateMessageResult = with(sessionRegistry.getSession(sessionId)) { - request(params, options) - } + ): CreateMessageResult = clientConnection(sessionId).createMessage(params, options) /** - * Triggers [ServerSession.listRoots] request for session by provided [sessionId]. + * Triggers [ClientConnection.listRoots] request for session by provided [sessionId]. * * @param sessionId The session ID to list roots for. * @param params JSON parameters for the request, usually empty. @@ -662,12 +673,10 @@ public open class Server( sessionId: String, params: JsonObject = EmptyJsonObject, options: RequestOptions? = null, - ): ListRootsResult = with(sessionRegistry.getSession(sessionId)) { - listRoots(params, options) - } + ): ListRootsResult = sessionRegistry.getSession(sessionId).listRoots(params, options) /** - * Triggers [ServerSession.createElicitation] request for session by provided [sessionId]. + * Triggers [ClientConnection.createElicitation] request for session by provided [sessionId]. * * @param sessionId The session ID to create elicitation for. * @param message The elicitation message. @@ -681,67 +690,59 @@ public open class Server( message: String, requestedSchema: ElicitRequestParams.RequestedSchema, options: RequestOptions? = null, - ): ElicitResult = with(sessionRegistry.getSession(sessionId)) { - createElicitation(message, requestedSchema, options) - } + ): ElicitResult = clientConnection(sessionId).createElicitation( + message = message, + requestedSchema = requestedSchema, + options = options, + ) /** - * Triggers [ServerSession.sendLoggingMessage] for session by provided [sessionId]. + * Triggers [ClientConnection.sendLoggingMessage] for session by provided [sessionId]. * * @param sessionId The session ID to send the logging message to. * @param notification The logging message notification. */ public suspend fun sendLoggingMessage(sessionId: String, notification: LoggingMessageNotification) { - with(sessionRegistry.getSession(sessionId)) { - sendLoggingMessage(notification) - } + clientConnection(sessionId).sendLoggingMessage(notification) } /** - * Triggers [ServerSession.sendResourceUpdated] for session by provided [sessionId]. + * Triggers [ClientConnection.sendResourceUpdated] for session by provided [sessionId]. * * @param sessionId The session ID to send the resource updated notification to. * @param notification Details of the updated resource. */ public suspend fun sendResourceUpdated(sessionId: String, notification: ResourceUpdatedNotification) { - with(sessionRegistry.getSession(sessionId)) { - sendResourceUpdated(notification) - } + clientConnection(sessionId).sendResourceUpdated(notification) } /** - * Triggers [ServerSession.sendResourceListChanged] for session by provided [sessionId]. + * Triggers [ClientConnection.sendResourceListChanged] for session by provided [sessionId]. * * @param sessionId The session ID to send the resource list changed notification to. */ public suspend fun sendResourceListChanged(sessionId: String) { - with(sessionRegistry.getSession(sessionId)) { - sendResourceListChanged() - } + clientConnection(sessionId).sendResourceListChanged() } /** - * Triggers [ServerSession.sendToolListChanged] for session by provided [sessionId]. + * Triggers [ClientConnection.sendToolListChanged] for session by provided [sessionId]. * * @param sessionId The session ID to send the tool list changed notification to. */ public suspend fun sendToolListChanged(sessionId: String) { - with(sessionRegistry.getSession(sessionId)) { - sendToolListChanged() - } + clientConnection(sessionId).sendToolListChanged() } /** - * Triggers [ServerSession.sendPromptListChanged] for session by provided [sessionId]. + * Triggers [ClientConnection.sendPromptListChanged] for session by provided [sessionId]. * * @param sessionId The session ID to send the prompt list changed notification to. */ public suspend fun sendPromptListChanged(sessionId: String) { - with(sessionRegistry.getSession(sessionId)) { - sendPromptListChanged() - } + clientConnection(sessionId).sendPromptListChanged() } - // End the ServerSession redirection section + // End the ServerSession / ClientConnection redirection section // Start the notification handling section public fun setNotificationHandler(method: Method, handler: (notification: T) -> Deferred) { diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt index ec20db2..69d20d1 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt @@ -7,7 +7,6 @@ import io.modelcontextprotocol.kotlin.sdk.types.BaseRequestParams import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageResult -import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequest import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestParams import io.modelcontextprotocol.kotlin.sdk.types.ElicitResult import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject @@ -23,14 +22,10 @@ import io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel import io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotification import io.modelcontextprotocol.kotlin.sdk.types.Method import io.modelcontextprotocol.kotlin.sdk.types.Method.Defined -import io.modelcontextprotocol.kotlin.sdk.types.PingRequest -import io.modelcontextprotocol.kotlin.sdk.types.PromptListChangedNotification import io.modelcontextprotocol.kotlin.sdk.types.RequestMeta -import io.modelcontextprotocol.kotlin.sdk.types.ResourceListChangedNotification import io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotification import io.modelcontextprotocol.kotlin.sdk.types.SUPPORTED_PROTOCOL_VERSIONS import io.modelcontextprotocol.kotlin.sdk.types.SetLevelRequest -import io.modelcontextprotocol.kotlin.sdk.types.ToolListChangedNotification import kotlinx.atomicfu.AtomicRef import kotlinx.atomicfu.atomic import kotlinx.coroutines.CompletableDeferred @@ -43,6 +38,7 @@ private val logger = KotlinLogging.logger {} /** * Represents a server session. */ +@Suppress("TooManyFunctions") public open class ServerSession( protected val serverInfo: Implementation, options: ServerOptions, @@ -77,7 +73,7 @@ public open class ServerSession( * The current logging level set by the client. * When null, all messages are sent (no filtering). */ - private val currentLoggingLevel: AtomicRef = atomic(null) + internal val currentLoggingLevel: AtomicRef = atomic(null) init { // Core protocol handlers @@ -99,6 +95,8 @@ public open class ServerSession( } } + internal val clientConnection: ClientConnection = ClientConnectionImpl(this) + /** * Registers a callback to be invoked when the server has completed initialization. */ @@ -129,114 +127,6 @@ public open class ServerSession( _onClose() } - /** - * Sends a ping request to the client to check connectivity. - * - * @return The result of the ping request. - * @throws IllegalStateException If for some reason the method is not supported or the connection is closed. - */ - public suspend fun ping(): EmptyResult = request(PingRequest()) - - /** - * Creates a message using the server's sampling capability. - * - * @param params The parameters for creating a message. - * @param options Optional request options. - * @return The created message result. - * @throws IllegalStateException If the server does not support sampling or if the request fails. - */ - public suspend fun createMessage( - params: CreateMessageRequest, - options: RequestOptions? = null, - ): CreateMessageResult { - logger.debug { - "Creating message with ${params.params.messages.size} messages, maxTokens=${params.params.maxTokens}, temperature=${params.params.temperature}, systemPrompt=${if (params.params.systemPrompt != null) "present" else "absent"}" - } - logger.trace { "Full createMessage params: $params" } - return request(params, options) - } - - /** - * Lists the available "roots" from the client's perspective (if supported). - * - * @param params JSON parameters for the request, usually empty. - * @param options Optional request options. - * @return The list of roots. - * @throws IllegalStateException If the server or client does not support roots. - */ - public suspend fun listRoots( - params: JsonObject = EmptyJsonObject, - options: RequestOptions? = null, - ): ListRootsResult { - logger.debug { "Listing roots with params: $params" } - return request(ListRootsRequest(BaseRequestParams(RequestMeta(params))), options) - } - - public suspend fun createElicitation( - message: String, - requestedSchema: ElicitRequestParams.RequestedSchema, - options: RequestOptions? = null, - ): ElicitResult { - logger.debug { - "Creating elicitation with message length=${message.length}, " + - "schema properties count=${requestedSchema.properties.size}" - } - logger.trace { "Full elicitation message: $message, requestedSchema: $requestedSchema" } - return request(ElicitRequest(ElicitRequestParams(message, requestedSchema)), options) - } - - /** - * Sends a logging message notification to the client. - * Messages are filtered based on the current logging level set by the client. - * If no logging level is set, all messages are sent. - * - * @param notification The logging message notification. - */ - public suspend fun sendLoggingMessage(notification: LoggingMessageNotification) { - if (serverCapabilities.logging != null) { - if (isMessageAccepted(notification.params.level)) { - logger.trace { "Sending logging message: ${notification.params.data}" } - notification(notification) - } else { - logger.trace { "Filtering out logging message with level ${notification.params.level}" } - } - } - } - - /** - * Sends a resource-updated notification to the client, indicating that a specific resource has changed. - * - * @param notification Details of the updated resource. - */ - public suspend fun sendResourceUpdated(notification: ResourceUpdatedNotification) { - logger.debug { "Sending resource updated notification for: ${notification.params.uri}" } - notification(notification) - } - - /** - * Sends a notification to the client indicating that the list of resources has changed. - */ - public suspend fun sendResourceListChanged() { - logger.debug { "Sending resource list changed notification" } - notification(ResourceListChangedNotification()) - } - - /** - * Sends a notification to the client indicating that the list of tools has changed. - */ - public suspend fun sendToolListChanged() { - logger.debug { "Sending tool list changed notification" } - notification(ToolListChangedNotification()) - } - - /** - * Sends a notification to the client indicating that the list of prompts has changed. - */ - public suspend fun sendPromptListChanged() { - logger.debug { "Sending prompt list changed notification" } - notification(PromptListChangedNotification()) - } - /** * Asserts that the client supports the capability required for the given [method]. * @@ -251,21 +141,21 @@ public open class ServerSession( Defined.SamplingCreateMessage -> { if (clientCapabilities?.sampling == null) { logger.error { "Client capability assertion failed: sampling not supported" } - throw IllegalStateException("Client does not support sampling (required for ${method.value})") + error("Client does not support sampling (required for ${method.value})") } } Defined.RootsList -> { if (clientCapabilities?.roots == null) { logger.error { "Client capability assertion failed: listing roots not supported" } - throw IllegalStateException("Client does not support listing roots (required for ${method.value})") + error("Client does not support listing roots (required for ${method.value})") } } Defined.ElicitationCreate -> { if (clientCapabilities?.elicitation == null) { logger.error { "Client capability assertion failed: elicitation not supported" } - throw IllegalStateException("Client does not support elicitation (required for ${method.value})") + error("Client does not support elicitation (required for ${method.value})") } } @@ -292,7 +182,7 @@ public open class ServerSession( Defined.NotificationsMessage -> { if (serverCapabilities.logging == null) { logger.error { "Server capability assertion failed: logging not supported" } - throw IllegalStateException("Server does not support logging (required for ${method.value})") + error("Server does not support logging (required for ${method.value})") } } @@ -300,7 +190,7 @@ public open class ServerSession( Defined.NotificationsResourcesListChanged, -> { if (serverCapabilities.resources == null) { - throw IllegalStateException( + error( "Server does not support notifying about resources (required for ${method.value})", ) } @@ -308,7 +198,7 @@ public open class ServerSession( Defined.NotificationsToolsListChanged -> { if (serverCapabilities.tools == null) { - throw IllegalStateException( + error( "Server does not support notifying of tool list changes (required for ${method.value})", ) } @@ -316,7 +206,7 @@ public open class ServerSession( Defined.NotificationsPromptsListChanged -> { if (serverCapabilities.prompts == null) { - throw IllegalStateException( + error( "Server does not support notifying of prompt list changes (required for ${method.value})", ) } @@ -347,14 +237,14 @@ public open class ServerSession( Defined.SamplingCreateMessage -> { if (serverCapabilities.experimental?.get("sampling") == null) { logger.error { "Server capability assertion failed: sampling not supported" } - throw IllegalStateException("Server does not support sampling (required for $method)") + error("Server does not support sampling (required for $method)") } } Defined.LoggingSetLevel -> { if (serverCapabilities.logging == null) { logger.error { "Server does not support logging (required for $method)" } - throw IllegalStateException("Server does not support logging (required for $method)") + error("Server does not support logging (required for $method)") } } @@ -362,7 +252,7 @@ public open class ServerSession( Defined.PromptsList, -> { if (serverCapabilities.prompts == null) { - throw IllegalStateException("Server does not support prompts (required for $method)") + error("Server does not support prompts (required for $method)") } } @@ -373,7 +263,7 @@ public open class ServerSession( Defined.ResourcesUnsubscribe, -> { if (serverCapabilities.resources == null) { - throw IllegalStateException("Server does not support resources (required for $method)") + error("Server does not support resources (required for $method)") } } @@ -381,7 +271,7 @@ public open class ServerSession( Defined.ToolsList, -> { if (serverCapabilities.tools == null) { - throw IllegalStateException("Server does not support tools (required for $method)") + error("Server does not support tools (required for $method)") } } @@ -395,7 +285,7 @@ public open class ServerSession( } } - private suspend fun handleInitialize(request: InitializeRequest): InitializeResult { + private fun handleInitialize(request: InitializeRequest): InitializeResult { logger.debug { "Handling initialization request from client" } clientCapabilities = request.params.capabilities clientVersion = request.params.clientInfo @@ -405,7 +295,8 @@ public open class ServerSession( requestedVersion } else { logger.warn { - "Client requested unsupported protocol version $requestedVersion, falling back to $LATEST_PROTOCOL_VERSION" + "Client requested unsupported protocol version $requestedVersion, " + + "falling back to $LATEST_PROTOCOL_VERSION" } LATEST_PROTOCOL_VERSION } @@ -418,26 +309,6 @@ public open class ServerSession( ) } - /** - * Checks if a message with the given level should be ignored based on the current logging level. - * - * @param level The level of the message to check. - * @return true if the message should be ignored (filtered out), false otherwise. - */ - private fun isMessageIgnored(level: LoggingLevel): Boolean { - val current = currentLoggingLevel.value ?: return false // If no level is set, don't filter - - return level < current - } - - /** - * Checks if a message with the given level should be accepted based on the current logging level. - * - * @param level The level of the message to check. - * @return true if the message should be accepted (not filtered out), false otherwise. - */ - private fun isMessageAccepted(level: LoggingLevel): Boolean = !isMessageIgnored(level) - override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is ServerSession) return false @@ -445,4 +316,94 @@ public open class ServerSession( } override fun hashCode(): Int = sessionId.hashCode() + + // Start the ClientConnection redirection section + + /** + * Sends a ping request to the client to check connectivity. + * + * @return The result of the ping request. + * @throws IllegalStateException If for some reason the method is not supported or the connection is closed. + */ + public suspend fun ping(): EmptyResult = clientConnection.ping() + + /** + * Creates a message using the server's sampling capability. + * + * @param params The parameters for creating a message. + * @param options Optional request options. + * @return The created message result. + * @throws IllegalStateException If the server does not support sampling or if the request fails. + */ + public suspend fun createMessage( + params: CreateMessageRequest, + options: RequestOptions? = null, + ): CreateMessageResult = clientConnection.createMessage(params, options) + + /** + * Lists the available "roots" from the client's perspective (if supported). + * + * @param params JSON parameters for the request, usually empty. + * @param options Optional request options. + * @return The list of roots. + * @throws IllegalStateException If the server or client does not support roots. + */ + public suspend fun listRoots( + params: JsonObject = EmptyJsonObject, + options: RequestOptions? = null, + ): ListRootsResult { + logger.debug { "Listing roots with params: $params" } + return request(ListRootsRequest(BaseRequestParams(RequestMeta(params))), options) + } + + /** + * Sends a message to the client requesting an elicitation. + * This typically results in a form being displayed to the end user. + * + * @param message The message for the elicitation to display. + * @param requestedSchema The schema requested by the client for the elicitation result. + * Influences the form displayed to the user. + * @param options Optional request options. + * @return The result of the elicitation request. + * @throws IllegalStateException If the server or client does not support elicitation. + */ + public suspend fun createElicitation( + message: String, + requestedSchema: ElicitRequestParams.RequestedSchema, + options: RequestOptions? = null, + ): ElicitResult = clientConnection.createElicitation(message, requestedSchema, options) + + /** + * Sends a logging message notification to the client. + * Messages are filtered based on the current logging level set by the client. + * If no logging level is set, all messages are sent. + * + * @param notification The logging message notification. + */ + public suspend fun sendLoggingMessage(notification: LoggingMessageNotification): Unit = + clientConnection.sendLoggingMessage(notification) + + /** + * Sends a resource-updated notification to the client, indicating that a specific resource has changed. + * + * @param notification Details of the updated resource. + */ + public suspend fun sendResourceUpdated(notification: ResourceUpdatedNotification): Unit = + clientConnection.sendResourceUpdated(notification) + + /** + * Sends a notification to the client indicating that the list of resources has changed. + */ + public suspend fun sendResourceListChanged(): Unit = clientConnection.sendResourceListChanged() + + /** + * Sends a notification to the client indicating that the list of tools has changed. + */ + public suspend fun sendToolListChanged(): Unit = clientConnection.sendToolListChanged() + + /** + * Sends a notification to the client indicating that the list of prompts has changed. + */ + public suspend fun sendPromptListChanged(): Unit = clientConnection.sendPromptListChanged() + // End the ClientConnection redirection section } From 27b419c4462eddddc47135c506817d37667baf2f Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Thu, 26 Feb 2026 09:04:26 +0200 Subject: [PATCH 161/449] test: Add basic authentication integration tests for SSE transport (#561) --- gradle/libs.versions.toml | 1 + integration-test/build.gradle.kts | 1 + .../integration/AbstractAuthenticationTest.kt | 188 ++++++++++++++++++ .../integration/sse/SseAuthenticationTest.kt | 41 ++++ 4 files changed, 231 insertions(+) create mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/AbstractAuthenticationTest.kt create mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/SseAuthenticationTest.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 08bf61e..ccb649d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -49,6 +49,7 @@ ktor-client-logging = { group = "io.ktor", name = "ktor-client-logging", version ktor-server-content-negotiation = { group = "io.ktor", name = "ktor-server-content-negotiation", version.ref = "ktor" } ktor-client-content-negotiation = { group = "io.ktor", name = "ktor-client-content-negotiation", version.ref = "ktor" } ktor-serialization = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" } +ktor-server-auth = { group = "io.ktor", name = "ktor-server-auth", version.ref = "ktor" } ktor-server-core = { group = "io.ktor", name = "ktor-server-core", version.ref = "ktor" } ktor-server-sse = { group = "io.ktor", name = "ktor-server-sse", version.ref = "ktor" } ktor-server-websockets = { group = "io.ktor", name = "ktor-server-websockets", version.ref = "ktor" } diff --git a/integration-test/build.gradle.kts b/integration-test/build.gradle.kts index 82078e7..30afe9e 100644 --- a/integration-test/build.gradle.kts +++ b/integration-test/build.gradle.kts @@ -22,6 +22,7 @@ kotlin { implementation(libs.ktor.server.content.negotiation) implementation(libs.ktor.serialization) implementation(libs.ktor.server.websockets) + implementation(libs.ktor.server.auth) implementation(libs.ktor.server.test.host) implementation(libs.ktor.server.content.negotiation) implementation(libs.ktor.serialization) diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/AbstractAuthenticationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/AbstractAuthenticationTest.kt new file mode 100644 index 0000000..1e88558 --- /dev/null +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/AbstractAuthenticationTest.kt @@ -0,0 +1,188 @@ +package io.modelcontextprotocol.kotlin.sdk.integration + +import io.kotest.matchers.shouldBe +import io.ktor.client.HttpClient +import io.ktor.client.request.basicAuth +import io.ktor.client.request.get +import io.ktor.http.HttpStatusCode +import io.ktor.serialization.kotlinx.json.json +import io.ktor.server.application.Application +import io.ktor.server.application.ApplicationCall +import io.ktor.server.application.install +import io.ktor.server.auth.Authentication +import io.ktor.server.auth.UserIdPrincipal +import io.ktor.server.auth.authenticate +import io.ktor.server.auth.basic +import io.ktor.server.auth.principal +import io.ktor.server.engine.embeddedServer +import io.ktor.server.plugins.contentnegotiation.ContentNegotiation +import io.ktor.server.routing.Route +import io.ktor.server.routing.routing +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.server.Server +import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions +import io.modelcontextprotocol.kotlin.sdk.shared.Transport +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.McpJson +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents +import io.modelcontextprotocol.kotlin.test.utils.actualPort +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeout +import kotlin.test.Test +import kotlin.time.Duration.Companion.seconds +import io.ktor.client.engine.cio.CIO as ClientCIO +import io.ktor.server.cio.CIO as ServerCIO +import io.ktor.server.sse.SSE as ServerSSE + +/** + * Base class for MCP authentication integration tests. + */ +@Suppress("InjectDispatcher") +abstract class AbstractAuthenticationTest { + + protected companion object { + const val HOST = "127.0.0.1" + const val AUTH_REALM = "mcp-auth" + const val WHOAMI_URI = "whoami://me" + const val VALID_USER = "test-user" + const val VALID_PASSWORD = "valid-password-123" + const val INVALID_USER = "invalid-user" + const val INVALID_PASSWORD = "invalid-password" + } + + /** + * Installs Ktor plugins required by the transport under test. + */ + protected open fun Application.configurePlugins() { + install(ServerSSE) + // ContentNegotiation is required by the StreamableHttp transport for JSON body handling. + // Installing it for SSE tests as well is harmless. + install(ContentNegotiation) { json(McpJson) } + } + + /** + * Registers the MCP server on the given route. + */ + abstract fun Route.registerMcpServer(serverFactory: ApplicationCall.() -> Server) + + /** + * Creates a client transport configured with the given credentials. + */ + abstract fun createClientTransport(baseUrl: String, user: String, pass: String): Transport + + @Test + fun `mcp behind basic auth rejects unauthenticated requests with 401`(): Unit = runBlocking(Dispatchers.IO) { + val server = startAuthenticatedServer() + + val httpClient = HttpClient(ClientCIO) { expectSuccess = false } + try { + httpClient.get("http://$HOST:${server.actualPort()}").status shouldBe HttpStatusCode.Unauthorized + } finally { + httpClient.close() + server.stopSuspend(1000, 2000) + } + } + + @Test + fun `mcp rejects requests with invalid credentials`(): Unit = runBlocking(Dispatchers.IO) { + val server = startAuthenticatedServer() + + val httpClient = HttpClient(ClientCIO) { + expectSuccess = false + } + try { + httpClient.get("http://$HOST:${server.actualPort()}") { + basicAuth(INVALID_USER, INVALID_PASSWORD) + }.status shouldBe HttpStatusCode.Unauthorized + } finally { + httpClient.close() + server.stopSuspend(1000, 2000) + } + } + + @Test + fun `authenticated mcp client can read resource scoped to principal`(): Unit = runBlocking(Dispatchers.IO) { + val server = startAuthenticatedServer() + + val baseUrl = "http://$HOST:${server.actualPort()}" + var mcpClient: Client? = null + try { + mcpClient = Client(Implementation(name = "test-client", version = "1.0.0")) + withTimeout(5.seconds) { + mcpClient.connect(createClientTransport(baseUrl, VALID_USER, VALID_PASSWORD)) + } + + val result = mcpClient.readResource( + ReadResourceRequest(ReadResourceRequestParams(uri = WHOAMI_URI)), + ) + + result.contents shouldBe listOf( + TextResourceContents( + text = VALID_USER, + uri = WHOAMI_URI, + mimeType = "text/plain", + ), + ) + } finally { + mcpClient?.close() + server.stopSuspend(1000, 2000) + } + } + + private suspend fun startAuthenticatedServer() = embeddedServer(ServerCIO, host = HOST, port = 0) { + configurePlugins() + installBasicAuth() + routing { + authenticate(AUTH_REALM) { + registerMcpServer { + createMcpServer { principal()?.name } + } + } + } + }.startSuspend(wait = false) + + private fun Application.installBasicAuth() { + install(Authentication) { + basic(AUTH_REALM) { + validate { credentials -> + if (credentials.name == VALID_USER && credentials.password == VALID_PASSWORD) { + UserIdPrincipal(credentials.name) + } else { + null + } + } + } + } + } + + protected fun createMcpServer(principalProvider: () -> String?): Server = Server( + serverInfo = Implementation(name = "test-server", version = "1.0.0"), + options = ServerOptions( + capabilities = ServerCapabilities( + resources = ServerCapabilities.Resources(), + ), + ), + ).apply { + addResource( + uri = WHOAMI_URI, + name = "Current User", + description = "Returns the name of the authenticated user", + mimeType = "text/plain", + ) { + ReadResourceResult( + contents = listOf( + TextResourceContents( + text = principalProvider() ?: "anonymous", + uri = WHOAMI_URI, + mimeType = "text/plain", + ), + ), + ) + } + } +} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/SseAuthenticationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/SseAuthenticationTest.kt new file mode 100644 index 0000000..da76319 --- /dev/null +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/SseAuthenticationTest.kt @@ -0,0 +1,41 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.sse + +import io.ktor.client.HttpClient +import io.ktor.client.plugins.sse.SSE +import io.ktor.client.request.basicAuth +import io.ktor.server.application.ApplicationCall +import io.ktor.server.routing.Route +import io.modelcontextprotocol.kotlin.sdk.client.SseClientTransport +import io.modelcontextprotocol.kotlin.sdk.integration.AbstractAuthenticationTest +import io.modelcontextprotocol.kotlin.sdk.server.Server +import io.modelcontextprotocol.kotlin.sdk.server.mcp +import io.modelcontextprotocol.kotlin.sdk.shared.Transport +import kotlin.test.AfterTest +import io.ktor.client.engine.cio.CIO as ClientCIO + +class SseAuthenticationTest : AbstractAuthenticationTest() { + + private var httpClient: HttpClient? = null + + @AfterTest + fun closeHttpClient() { + httpClient?.close() + httpClient = null + } + + override fun Route.registerMcpServer(serverFactory: ApplicationCall.() -> Server) { + mcp { + serverFactory(call) + } + } + + override fun createClientTransport(baseUrl: String, user: String, pass: String): Transport { + val client = HttpClient(ClientCIO) { install(SSE) } + httpClient = client + return SseClientTransport( + client = client, + urlString = baseUrl, + requestBuilder = { basicAuth(user, pass) }, + ) + } +} From 43b59c9fc1121002833d191aaaea8563538ae015 Mon Sep 17 00:00:00 2001 From: Jakub Amanowicz Date: Thu, 26 Feb 2026 12:00:46 +0100 Subject: [PATCH 162/449] fix: SseTransport endpoint resolution (#329) (#565) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Fix SSE endpoint resolution so absolute paths resolve against the origin, and add tests covering absolute vs relative endpoint events. ## Motivation and Context The SSE transport currently resolves all endpoint events against the SSE URL’s base path. When the server sends an absolute endpoint (starting with `/`), the client incorrectly prefixes it with the SSE path. This change resolves absolute endpoints against the origin while keeping relative behavior unchanged. addresses: https://github.com/modelcontextprotocol/kotlin-sdk/issues/329 ## How Has This Been Tested? Unit tests added in `SseClientTransportTest` for absolute and relative endpoint events. ## Breaking Changes No. ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed ## Additional context The endpoint resolution logic now treats paths starting with `/` as origin-relative. Relative paths continue to resolve against the SSE base URL. --- .../kotlin/sdk/client/SseClientTransport.kt | 27 +++-- .../sdk/client/sse/MockSseClientEngine.kt | 99 ++++++++++++++++ .../sdk/client/sse/SseClientTransportTest.kt | 106 ++++++++++++++++++ 3 files changed, 225 insertions(+), 7 deletions(-) create mode 100644 kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/sse/MockSseClientEngine.kt create mode 100644 kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/sse/SseClientTransportTest.kt diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseClientTransport.kt index 19480c7..3c9d88b 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseClientTransport.kt @@ -11,7 +11,6 @@ import io.ktor.client.request.setBody import io.ktor.client.statement.bodyAsText import io.ktor.http.ContentType import io.ktor.http.HttpHeaders -import io.ktor.http.Url import io.ktor.http.append import io.ktor.http.isSuccess import io.ktor.http.protocolWithAuthority @@ -54,13 +53,17 @@ public class SseClientTransport( private lateinit var scope: CoroutineScope private var job: Job? = null + private val origin: String by lazy { + session.call.request.url.protocolWithAuthority + } + private val baseUrl: String by lazy { session.call.request.url.let { url -> val path = url.encodedPath when { - path.isEmpty() -> url.protocolWithAuthority - path.endsWith("/") -> url.protocolWithAuthority + path.removeSuffix("/") - else -> url.protocolWithAuthority + path.take(path.lastIndexOf("/")) + path.isEmpty() -> origin + path.endsWith("/") -> origin + path.removeSuffix("/") + else -> origin + path.take(path.lastIndexOf("/")) } } } @@ -135,11 +138,21 @@ public class SseClientTransport( } } + /** + * Resolves and completes [endpoint] based on [eventData]. + * Uses full URLs as-is, treats absolute paths as origin-relative, + * and relative paths as relative to [baseUrl]. + */ private fun handleEndpoint(eventData: String) { try { - val path = if (eventData.startsWith("/")) eventData.substring(1) else eventData - val endpointUrl = Url("$baseUrl/$path") - endpoint.complete(endpointUrl.toString()) + val endpointUrl = if (eventData.startsWith("http://") || eventData.startsWith("https://")) { + eventData + } else if (eventData.startsWith("/")) { + origin + eventData + } else { + "$baseUrl/$eventData" + } + endpoint.complete(endpointUrl) logger.debug { "Client connected to endpoint: $endpointUrl" } } catch (e: Throwable) { _onError(e) diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/sse/MockSseClientEngine.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/sse/MockSseClientEngine.kt new file mode 100644 index 0000000..3627b6c --- /dev/null +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/sse/MockSseClientEngine.kt @@ -0,0 +1,99 @@ +package io.modelcontextprotocol.kotlin.sdk.client.sse + +import io.ktor.client.engine.HttpClientEngine +import io.ktor.client.engine.HttpClientEngineConfig +import io.ktor.client.plugins.sse.SSECapability +import io.ktor.client.request.HttpRequestData +import io.ktor.client.request.HttpResponseData +import io.ktor.client.request.ResponseAdapterAttributeKey +import io.ktor.http.Headers +import io.ktor.http.HttpMethod +import io.ktor.http.HttpProtocolVersion +import io.ktor.http.HttpStatusCode +import io.ktor.http.headersOf +import io.ktor.util.date.GMTDate +import io.ktor.utils.io.ByteChannel +import io.ktor.utils.io.ByteReadChannel +import io.ktor.utils.io.InternalAPI +import io.ktor.utils.io.writeStringUtf8 +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import kotlin.coroutines.CoroutineContext + +@OptIn(InternalAPI::class) +internal open class MockSseClientEngine( + private val endpoint: String, + private val onPostRequest: (postRequest: HttpRequestData) -> Unit, + override val dispatcher: CoroutineDispatcher = Dispatchers.Default, +) : HttpClientEngine { + + override val config = HttpClientEngineConfig() + override val supportedCapabilities = setOf(SSECapability) + + private val job = SupervisorJob() + override val coroutineContext: CoroutineContext = dispatcher + job + + private val channel by lazy { ByteChannel(true) } + + override suspend fun execute(data: HttpRequestData): HttpResponseData = when (data.method) { + HttpMethod.Get -> respondWithSseEndpointEvent(data) + + HttpMethod.Post -> { + onPostRequest(data) + respondWithAccepted() + } + + else -> error("Unsupported method: ${data.method}") + } + + override fun close() { + channel.close() + job.cancel() + } + + private fun responseContext(): CoroutineContext = dispatcher + Job() + + private suspend fun respondWithSseEndpointEvent(data: HttpRequestData): HttpResponseData { + val sseResponseAdapter = requireNotNull(data.attributes.getOrNull(ResponseAdapterAttributeKey)) { + "Missing SSE response adapter." + } + channel.writeStringUtf8(endpointEvent()) + channel.flush() + val responseContext = responseContext() + val responseBody = sseResponseAdapter.adapt( + data, + HttpStatusCode.OK, + eventStreamHeader, + channel, + data.body, + responseContext, + ) ?: error("Failed to adapt SSE response.") + return HttpResponseData( + statusCode = HttpStatusCode.OK, + requestTime = GMTDate(), + headers = eventStreamHeader, + version = HttpProtocolVersion.HTTP_1_1, + body = responseBody, + callContext = responseContext, + ) + } + + private val eventStreamHeader by lazy { headersOf("Content-Type", "text/event-stream") } + + private fun endpointEvent(): String = buildString { + appendLine("event: endpoint") + appendLine("data: $endpoint") + appendLine() + } + + private fun respondWithAccepted(): HttpResponseData = HttpResponseData( + statusCode = HttpStatusCode.Accepted, + requestTime = GMTDate(), + headers = Headers.Empty, + version = HttpProtocolVersion.HTTP_1_1, + body = ByteReadChannel.Empty, + callContext = responseContext(), + ) +} diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/sse/SseClientTransportTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/sse/SseClientTransportTest.kt new file mode 100644 index 0000000..6cc7f6e --- /dev/null +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/sse/SseClientTransportTest.kt @@ -0,0 +1,106 @@ +package io.modelcontextprotocol.kotlin.sdk.client.sse + +import io.kotest.matchers.collections.shouldHaveSize +import io.kotest.matchers.shouldBe +import io.ktor.client.HttpClient +import io.ktor.client.plugins.sse.SSE +import io.ktor.client.request.HttpRequestData +import io.modelcontextprotocol.kotlin.sdk.client.SseClientTransport +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification +import kotlinx.coroutines.test.runTest +import kotlin.test.Test + +class SseClientTransportTest { + + @Test + fun `absolute path endpoint resolves against origin`() = runTest { + // Given + val sseUrl = "http://example.com/api/mcp/sse" + + // And + val endpointEvent = "/messages?sessionId=abc" + + // And + val engine = CapturingSseClientEngine(endpoint = endpointEvent) + val transport = sseTransport(sseUrl, engine) + + // When + transport.start() + transport.send(JSONRPCNotification(method = "test")) + + // Then + val capturedPosts = engine.capturedPosts + capturedPosts shouldHaveSize 1 + capturedPosts[0].url.toString() shouldBe "http://example.com/messages?sessionId=abc" + + // Cleanup + transport.close() + engine.close() + } + + @Test + fun `relative path endpoint resolves against baseUrl`() = runTest { + // Given + val sseUrl = "http://example.com/api/mcp/sse" + + // And + val endpointEvent = "post?sessionId=xyz" + + // And + val engine = CapturingSseClientEngine(endpoint = endpointEvent) + val transport = sseTransport(sseUrl, engine) + + // When + transport.start() + transport.send(JSONRPCNotification(method = "test")) + + // Then + val capturedPosts = engine.capturedPosts + capturedPosts shouldHaveSize 1 + capturedPosts[0].url.toString() shouldBe "http://example.com/api/mcp/post?sessionId=xyz" + + // Cleanup + transport.close() + engine.close() + } + + @Test + fun `full url endpoint is used as-is`() = runTest { + // Given + val sseUrl = "http://example.com/api/mcp/sse" + + // And + val endpointEvent = "https://example.com/messages?sessionId=abc" + + // And + val engine = CapturingSseClientEngine(endpoint = endpointEvent) + val transport = sseTransport(sseUrl, engine) + + // When + transport.start() + transport.send(JSONRPCNotification(method = "test")) + + // Then + val capturedPosts = engine.capturedPosts + capturedPosts shouldHaveSize 1 + capturedPosts[0].url.toString() shouldBe "https://example.com/messages?sessionId=abc" + + // Cleanup + transport.close() + engine.close() + } + + private fun sseTransport(sseUrl: String, engine: CapturingSseClientEngine) = + SseClientTransport(HttpClient(engine) { install(SSE) }, sseUrl) + + private class CapturingSseClientEngine private constructor( + endpoint: String, + private val capturedPostRequests: MutableList = mutableListOf(), + ) : MockSseClientEngine(endpoint, capturedPostRequests::add) { + + constructor(endpoint: String) : this(endpoint, mutableListOf()) + + val capturedPosts: List + get() = capturedPostRequests + } +} From ec53adc317ce13af54ab4d43ce7d16e826d0c268 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Thu, 26 Feb 2026 13:21:49 +0200 Subject: [PATCH 163/449] feat: introduce StreamableHttpServerTransport.Configuration (#560) ## Introduce Configuration class for StreamableHttpServerTransport Replace the six-parameter flat constructor of `StreamableHttpServerTransport` with a typed `Configuration` class. This improves API ergonomics, enables structural equality and copy(), and provides a stable extension point for future options without further breaking the constructor signature. ### Changes: - Add `Configuration` as a public class nested directly on `StreamableHttpServerTransport`, with `enableJsonResponse` as the first parameter (most commonly set) - Change the primary constructor to accept `Configuration`. - Rename `retryIntervalMillis: Long?` to `retryInterval: Duration?` in Configuration, aligning with Kotlin's type-safe time API - Deprecate the old flat constructor with a compatibility bridge - Update KotlinTestBase integration test to use the new constructor - Enable test AbstractResourceIntegrationTest.testSubscribeAndUnsubscribe() since #249 is closed ## Motivation and Context The current StreamableHttpServerTransport API cannot be easily extended: adding more parameters would be a breaking change. But this is already needed for #521. This PR is a prerequisite for #535 ## How Has This Been Tested? Regression tests ## Breaking Changes No. Current StreamableHttpServerTransport constructor was deprecated ## 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 --- .../kotlin/AbstractResourceIntegrationTest.kt | 2 - .../sdk/integration/kotlin/KotlinTestBase.kt | 4 +- kotlin-sdk-server/api/kotlin-sdk-server.api | 12 ++ .../kotlin/sdk/server/KtorServer.kt | 24 +-- .../server/StreamableHttpServerTransport.kt | 137 +++++++++++++----- 5 files changed, 128 insertions(+), 51 deletions(-) diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt index fc08810..40b9552 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt @@ -19,7 +19,6 @@ import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import java.util.concurrent.atomic.AtomicBoolean -import kotlin.test.Ignore import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue @@ -165,7 +164,6 @@ abstract class AbstractResourceIntegrationTest : KotlinTestBase() { assertEquals(testResourceContent, content.text, "Resource content should match") } - @Ignore("Blocked by https://github.com/modelcontextprotocol/kotlin-sdk/issues/249") @Test fun testSubscribeAndUnsubscribe() { runBlocking(Dispatchers.IO) { diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/KotlinTestBase.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/KotlinTestBase.kt index f8e8740..2f8b387 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/KotlinTestBase.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/KotlinTestBase.kt @@ -148,7 +148,9 @@ abstract class KotlinTestBase { // Create StreamableHTTP server transport // Using JSON response mode for simpler testing (no SSE session required) val transport = StreamableHttpServerTransport( - enableJsonResponse = true, // Use JSON response mode for testing + StreamableHttpServerTransport.Configuration( + enableJsonResponse = true, // Use JSON response mode for testing + ), ) // Use stateless mode to skip session validation for simpler testing transport.setSessionIdGenerator(null) diff --git a/kotlin-sdk-server/api/kotlin-sdk-server.api b/kotlin-sdk-server/api/kotlin-sdk-server.api index 15bbe5e..c0b0199 100644 --- a/kotlin-sdk-server/api/kotlin-sdk-server.api +++ b/kotlin-sdk-server/api/kotlin-sdk-server.api @@ -195,6 +195,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/server/StdioServerTranspor public final class io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport : io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport { public static final field STANDALONE_SSE_STREAM_ID Ljava/lang/String; public fun ()V + public fun (Lio/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport$Configuration;)V public fun (ZZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Ljava/lang/Long;)V public synthetic fun (ZZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Ljava/lang/Long;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -211,6 +212,17 @@ public final class io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServe public fun start (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } +public final class io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport$Configuration { + public synthetic fun (ZZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/time/Duration;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (ZZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/time/Duration;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getAllowedHosts ()Ljava/util/List; + public final fun getAllowedOrigins ()Ljava/util/List; + public final fun getEnableDnsRebindingProtection ()Z + public final fun getEnableJsonResponse ()Z + public final fun getEventStore ()Lio/modelcontextprotocol/kotlin/sdk/server/EventStore; + public final fun getRetryInterval-FghU774 ()Lkotlin/time/Duration; +} + public final class io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensionsKt { public static final fun mcpWebSocket (Lio/ktor/server/application/Application;Ljava/lang/String;Lkotlin/jvm/functions/Function0;)V public static final fun mcpWebSocket (Lio/ktor/server/application/Application;Lkotlin/jvm/functions/Function0;)V diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt index 5a750a5..d3cbf40 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt @@ -225,11 +225,13 @@ private suspend fun RoutingContext.mcpStatelessStreamableHttpEndpoint( block: RoutingContext.() -> Server, ) { val transport = StreamableHttpServerTransport( - enableDnsRebindingProtection = enableDnsRebindingProtection, - allowedHosts = allowedHosts, - allowedOrigins = allowedOrigins, - eventStore = eventStore, - enableJsonResponse = true, + StreamableHttpServerTransport.Configuration( + enableDnsRebindingProtection = enableDnsRebindingProtection, + allowedHosts = allowedHosts, + allowedOrigins = allowedOrigins, + eventStore = eventStore, + enableJsonResponse = true, + ), ).also { it.setSessionIdGenerator(null) } logger.info { "New stateless StreamableHttp connection established without sessionId" } @@ -305,11 +307,13 @@ private suspend fun RoutingContext.streamableTransport( } val transport = StreamableHttpServerTransport( - enableDnsRebindingProtection = enableDnsRebindingProtection, - allowedHosts = allowedHosts, - allowedOrigins = allowedOrigins, - eventStore = eventStore, - enableJsonResponse = true, + StreamableHttpServerTransport.Configuration( + enableDnsRebindingProtection = enableDnsRebindingProtection, + allowedHosts = allowedHosts, + allowedOrigins = allowedOrigins, + eventStore = eventStore, + enableJsonResponse = true, + ), ) transport.setOnSessionInitialized { initializedSessionId -> diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt index 1894cdc..580fcc0 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt @@ -36,6 +36,8 @@ import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.decodeFromJsonElement import kotlin.concurrent.atomics.AtomicBoolean import kotlin.concurrent.atomics.ExperimentalAtomicApi +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds import kotlin.uuid.ExperimentalUuidApi import kotlin.uuid.Uuid @@ -46,8 +48,8 @@ private const val MAXIMUM_MESSAGE_SIZE = 4 * 1024 * 1024 // 4 MB /** * A holder for an active request call. - * If enableJsonResponse is true, session is null. - * Otherwise, session is not null. + * If [StreamableHttpServerTransport.Configuration.enableJsonResponse] is true, the session is null. + * Otherwise, the session is not null. */ private data class SessionContext(val session: ServerSSESession?, val call: ApplicationCall) @@ -66,32 +68,87 @@ private data class SessionContext(val session: ServerSSESession?, val call: Appl * - No Session ID is included in any responses * - No session validation is performed * - * @param enableJsonResponse If true, the server will return JSON responses instead of starting an SSE stream. - * This can be useful for simple request/response scenarios without streaming. - * Default is false (SSE streams are preferred). - * @param enableDnsRebindingProtection Enable DNS rebinding protection - * (requires allowedHosts and/or allowedOrigins to be configured). - * Default is false for backwards compatibility. - * @param allowedHosts List of allowed host header values for DNS rebinding protection. - * If not specified, host validation is disabled. - * @param allowedOrigins List of allowed origin header values for DNS rebinding protection. - * If not specified, origin validation is disabled. - * @param eventStore Event store for resumability support - * If provided, resumability will be enabled, allowing clients to reconnect and resume messages - * @param retryIntervalMillis Retry interval (in milliseconds) advertised via SSE priming events - * to hint the client when to reconnect. Applies only when an [eventStore] is configured. - * Defaults to `null` (no retry hint). + * @param configuration Transport configuration. See [Configuration] for available options. */ @OptIn(ExperimentalUuidApi::class, ExperimentalAtomicApi::class) @Suppress("TooManyFunctions") -public class StreamableHttpServerTransport( - private val enableJsonResponse: Boolean = false, - private val enableDnsRebindingProtection: Boolean = false, - private val allowedHosts: List? = null, - private val allowedOrigins: List? = null, - private val eventStore: EventStore? = null, - private val retryIntervalMillis: Long? = null, -) : AbstractTransport() { +public class StreamableHttpServerTransport(private val configuration: Configuration) : AbstractTransport() { + + @Deprecated("Use default constructor with explicit Configuration()") + public constructor() : this(configuration = Configuration()) + + /** + * Secondary constructor for `StreamableHttpServerTransport` that simplifies initialization by directly taking the + * configurable parameters without requiring a `Configuration` instance. + * + * @param enableJsonResponse Determines whether the server should return JSON responses. + * Defaults to `false`. + * @param enableDnsRebindingProtection Enables DNS rebinding protection. + * Defaults to `false`. + * @param allowedHosts A list of hosts allowed for server communication. + * Defaults to `null`, allowing all hosts. + * @param allowedOrigins A list of allowed origins for CORS (Cross-Origin Resource Sharing). + * Defaults to `null`, allowing all origins. + * @param eventStore The `EventStore` instance for handling resumable events. + * Defaults to `null`, disabling resumability. + * @param retryIntervalMillis Retry interval in milliseconds for event handling or reconnection attempts. + * Defaults to `null`. + */ + @Suppress("MaxLineLength") + @Deprecated( + "Use constructor with Configuration: StreamableHttpServerTransport(Configuration(enableJsonResponse = ...))", + replaceWith = ReplaceWith( + "StreamableHttpServerTransport(Configuration(enableJsonResponse = enableJsonResponse, enableDnsRebindingProtection = enableDnsRebindingProtection, allowedHosts = allowedHosts, allowedOrigins = allowedOrigins, eventStore = eventStore, retryIntervalMillis = retryIntervalMillis))", + ), + ) + public constructor( + enableJsonResponse: Boolean = false, + enableDnsRebindingProtection: Boolean = false, + allowedHosts: List? = null, + allowedOrigins: List? = null, + eventStore: EventStore? = null, + retryIntervalMillis: Long? = null, + ) : this( + Configuration( + enableJsonResponse = enableJsonResponse, + enableDnsRebindingProtection = enableDnsRebindingProtection, + allowedHosts = allowedHosts, + allowedOrigins = allowedOrigins, + eventStore = eventStore, + retryInterval = retryIntervalMillis?.milliseconds, + ), + ) + + /** + * Configuration for managing various aspects of the StreamableHttpServerTransport. + * + * @property enableJsonResponse Determines whether the server should return JSON responses. + * Defaults to `false`. + * + * @property enableDnsRebindingProtection Enables DNS rebinding protection. + * Defaults to `false`. + * + * @property allowedHosts A list of hosts allowed for server communication. + * Defaults to `null`, allowing all hosts. + * + * @property allowedOrigins A list of allowed origins for CORS (Cross-Origin Resource Sharing). + * Defaults to `null`, allowing all origins. + * + * @property eventStore The `EventStore` instance for handling resumable events. + * Defaults to `null`, disabling resumability. + * + * @property retryInterval Retry interval for event handling or reconnection attempts. + * Defaults to `null`. + */ + public class Configuration( + public val enableJsonResponse: Boolean = false, + public val enableDnsRebindingProtection: Boolean = false, + public val allowedHosts: List? = null, + public val allowedOrigins: List? = null, + public val eventStore: EventStore? = null, + public val retryInterval: Duration? = null, + ) + public var sessionId: String? = null private set @@ -177,7 +234,7 @@ public class StreamableHttpServerTransport( ?: error("No connection established for request id $routingRequestId") val activeStream = streamsMapping[streamId] - if (!enableJsonResponse) { + if (!configuration.enableJsonResponse) { activeStream?.let { stream -> emitOnStream(streamId, stream.session, message) } @@ -194,7 +251,7 @@ public class StreamableHttpServerTransport( streamMutex.withLock { if (activeStream == null) error("No connection established for request ID: $routingRequestId") - if (enableJsonResponse) { + if (configuration.enableJsonResponse) { activeStream.call.response.header(HttpHeaders.ContentType, ContentType.Application.Json.toString()) sessionId?.let { activeStream.call.response.header(MCP_SESSION_ID_HEADER, it) } val responses = relatedIds.mapNotNull { requestToResponseMapping[it] } @@ -261,7 +318,7 @@ public class StreamableHttpServerTransport( @Suppress("CyclomaticComplexMethod", "LongMethod", "ReturnCount", "TooGenericExceptionCaught") public suspend fun handlePostRequest(session: ServerSSESession?, call: ApplicationCall) { try { - if (!enableJsonResponse && session == null) { + if (!configuration.enableJsonResponse && session == null) { error("Server session can't be null for SSE responses") } @@ -328,7 +385,7 @@ public class StreamableHttpServerTransport( } val streamId = Uuid.random().toString() - if (!enableJsonResponse) { + if (!configuration.enableJsonResponse) { call.appendSseHeaders() flushSse(session) // flush headers immediately maybeSendPrimingEvent(streamId, session) @@ -353,7 +410,7 @@ public class StreamableHttpServerTransport( @Suppress("ReturnCount") public suspend fun handleGetRequest(session: ServerSSESession?, call: ApplicationCall) { - if (enableJsonResponse) { + if (configuration.enableJsonResponse) { call.reject( HttpStatusCode.MethodNotAllowed, RPCError.ErrorCode.CONNECTION_CLOSED, @@ -375,7 +432,7 @@ public class StreamableHttpServerTransport( if (!validateSession(call) || !validateProtocolVersion(call)) return - eventStore?.let { store -> + configuration.eventStore?.let { store -> call.request.header(MCP_RESUMPTION_TOKEN_HEADER)?.let { lastEventId -> replayEvents(store, lastEventId, sseSession) return @@ -413,7 +470,7 @@ public class StreamableHttpServerTransport( */ @Suppress("ReturnCount", "TooGenericExceptionCaught") public suspend fun closeSseStream(requestId: RequestId) { - if (enableJsonResponse) return + if (configuration.enableJsonResponse) return val streamId = requestToStreamMapping[requestId] ?: return val sessionContext = streamsMapping[streamId] ?: return @@ -562,9 +619,9 @@ public class StreamableHttpServerTransport( @Suppress("ReturnCount") private fun validateHeaders(call: ApplicationCall): String? { - if (!enableDnsRebindingProtection) return null + if (!configuration.enableDnsRebindingProtection) return null - allowedHosts?.let { hosts -> + configuration.allowedHosts?.let { hosts -> val hostHeader = call.request.headers[HttpHeaders.Host]?.lowercase() val allowedHostsLowercase = hosts.map { it.lowercase() } @@ -573,7 +630,7 @@ public class StreamableHttpServerTransport( } } - allowedOrigins?.let { origins -> + configuration.allowedOrigins?.let { origins -> val originHeader = call.request.headers[HttpHeaders.Origin]?.lowercase() val allowedOriginsLowercase = origins.map { it.lowercase() } @@ -636,7 +693,7 @@ public class StreamableHttpServerTransport( this?.lowercase()?.contains(mime.toString().lowercase()) == true private suspend fun emitOnStream(streamId: String, session: ServerSSESession?, message: JSONRPCMessage) { - val eventId = eventStore?.storeEvent(streamId, message) + val eventId = configuration.eventStore?.storeEvent(streamId, message) try { session?.send(event = "message", id = eventId, data = McpJson.encodeToString(message)) } catch (_: Exception) { @@ -646,11 +703,15 @@ public class StreamableHttpServerTransport( @Suppress("TooGenericExceptionCaught") private suspend fun maybeSendPrimingEvent(streamId: String, session: ServerSSESession?) { - val store = eventStore ?: return + val store = configuration.eventStore ?: return val sseSession = session ?: return try { val primingEventId = store.storeEvent(streamId, JSONRPCEmptyMessage) - sseSession.send(id = primingEventId, retry = retryIntervalMillis, data = "") + sseSession.send( + id = primingEventId, + retry = configuration.retryInterval?.inWholeMilliseconds, + data = "", + ) } catch (e: Exception) { _onError(e) } From 8a9922be793aa7086bcfce4c1ae022309990cf70 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 07:11:14 +0000 Subject: [PATCH 164/449] chore(deps): bump actions/upload-artifact from 6 to 7 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 6 +++--- .github/workflows/conformance.yml | 2 +- .github/workflows/samples.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0041174..8ab52f4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -88,7 +88,7 @@ jobs: - name: Upload Reports if: always() - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: reports-build-${{ matrix.os }} path: | @@ -114,7 +114,7 @@ jobs: - name: Upload Maven Local Repository if: ${{ success() && matrix.os == 'ubuntu-latest' }} - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: local-maven-repository path: local-maven-repository.zip @@ -206,7 +206,7 @@ jobs: - name: Upload Reports if: ${{ !cancelled() }} - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: reports-samples-snapshot-${{ matrix.sample }} path: | diff --git a/.github/workflows/conformance.yml b/.github/workflows/conformance.yml index 2c7b1ef..1c9ba19 100644 --- a/.github/workflows/conformance.yml +++ b/.github/workflows/conformance.yml @@ -67,7 +67,7 @@ jobs: - name: Upload Conformance Results if: always() - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: conformance-results-${{ matrix.os }} path: conformance-test/results/ diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index 7fac388..cdd98bb 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -57,7 +57,7 @@ jobs: - name: Upload Reports if: ${{ !cancelled() }} - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: reports-samples-${{ matrix.sample }} path: | From e124262982a61e0f28dc70c5103c967d5b0ab8f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 07:11:17 +0000 Subject: [PATCH 165/449] chore(deps): bump actions/download-artifact from 7 to 8 Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 7 to 8. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v7...v8) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '8' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8ab52f4..bb28aaf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -190,7 +190,7 @@ jobs: ../.konan/** - name: Download Maven Local Repository - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: name: local-maven-repository path: . From 45fcc17a23b5a1984e602c4289f9e177d5f40ec8 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Mon, 2 Mar 2026 15:47:05 +0200 Subject: [PATCH 166/449] doc: update API reference URL in README.md (#578) Update link in readme.md ## Motivation and Context Kotlin SDK has moved from https://modelcontextprotocol.github.io/kotlin-sdk/ to https://kotlin.sdk.modelcontextprotocol.io/ - [Announcement](https://discord.com/channels/1358869848138059966/1399986970851282954/1476078438266830931) - DNS change PR: https://github.com/modelcontextprotocol/dns/pull/5 ## Breaking Changes No ## Types of changes - [x] Documentation update ## Checklist - [x] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [ ] My code follows the repository's style guidelines - [ ] New and existing tests pass locally - [ ] I have added appropriate error handling - [x] I have added or updated documentation as needed --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 50beb54..505a592 100644 --- a/README.md +++ b/README.md @@ -887,7 +887,7 @@ allowing for easy testing of MCP functionality without the need for network setu ## Documentation -- [API Reference](https://modelcontextprotocol.github.io/kotlin-sdk/) +- [API Reference](https://kotlin.sdk.modelcontextprotocol.io/) - [Model Context Protocol documentation](https://modelcontextprotocol.io) - [MCP specification](https://modelcontextprotocol.io/specification/latest) From c36881282f977e7469115b7b980987b17c4f4aa5 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Mon, 2 Mar 2026 16:19:53 +0200 Subject: [PATCH 167/449] ci: add Codecov coverage upload and XML report generation (#579) ## Add Codecov coverage upload and XML report generation - Add `koverXmlReport` to Gradle tasks in Ubuntu workflow - Configure Codecov action to upload coverage reports Reports will be available on https://app.codecov.io/gh/modelcontextprotocol/kotlin-sdk ## Motivation and Context To provide better test coverage visibility ## How Has This Been Tested? CI ## Breaking Changes No ## Types of changes - [x] CI 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 ## Additional context --- .github/workflows/build.yml | 20 ++++++++++++++- README.md | 2 ++ codecov.yml | 51 +++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 codecov.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bb28aaf..d41454c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -36,7 +36,7 @@ jobs: include: - os: ubuntu-latest job-name: "Linux Tests" - gradle-tasks: "build ktlintCheck koverLog koverHtmlReport publishToMavenLocal -Pversion=1-SNAPSHOT" + gradle-tasks: "build ktlintCheck koverLog koverHtmlReport koverXmlReport publishToMavenLocal -Pversion=1-SNAPSHOT" max-workers: 3 - os: windows-latest @@ -107,6 +107,24 @@ jobs: report_paths: '**/test-results/**/TEST-*.xml' truncate_stack_traces: false + - name: Upload coverage reports to Codecov + if: matrix.os == 'ubuntu-latest' + uses: codecov/codecov-action@v5 + with: + directory: ./build/reports/kover + files: report.xml + + - name: Upload coverage reports to Codecov + if: ${{ !cancelled() && matrix.os == 'ubuntu-latest' }} + uses: codecov/codecov-action@v5 + with: + directory: ./build/reports/kover + files: report.xml + + - name: Upload test results to Codecov + if: ${{ !cancelled() && matrix.os == 'ubuntu-latest' }} + uses: codecov/test-results-action@v1 + - name: Archive Maven Local Repository if: ${{ success() && matrix.os == 'ubuntu-latest' }} run: | diff --git a/README.md b/README.md index 505a592..30d1b40 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ [![Maven Central](https://img.shields.io/maven-central/v/io.modelcontextprotocol/kotlin-sdk.svg?label=Maven%20Central)](https://central.sonatype.com/artifact/io.modelcontextprotocol/kotlin-sdk) [![Build](https://github.com/modelcontextprotocol/kotlin-sdk/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/modelcontextprotocol/kotlin-sdk/actions/workflows/build.yml) [![Conformance Tests](https://github.com/modelcontextprotocol/kotlin-sdk/actions/workflows/conformance.yml/badge.svg)](https://github.com/modelcontextprotocol/kotlin-sdk/actions/workflows/conformance.yml) +[![codecov](https://codecov.io/github/modelcontextprotocol/kotlin-sdk/graph/badge.svg?token=O2CFHNSFYM)](https://codecov.io/github/modelcontextprotocol/kotlin-sdk) + [![Kotlin](https://img.shields.io/badge/kotlin-2.2+-blueviolet.svg?logo=kotlin)](http://kotlinlang.org) [![Kotlin Multiplatform](https://img.shields.io/badge/Platforms-%20JVM%20%7C%20Wasm%2FJS%20%7C%20Native%20-blueviolet?logo=kotlin)](https://kotlinlang.org/docs/multiplatform.html) [![JVM](https://img.shields.io/badge/JVM-11+-red.svg?logo=jvm)](http://java.com) diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..9135aae --- /dev/null +++ b/codecov.yml @@ -0,0 +1,51 @@ +# Codecov configuration for Mokksy and AI-Mocks +# https://docs.codecov.com/docs/codecov-yaml + +codecov: + require_ci_to_pass: yes + +coverage: + precision: 2 + round: down + status: + project: + default: + target: auto + threshold: 1% + patch: + default: + target: auto + threshold: 1% + +comment: + layout: "header, diff, flags, components" + behavior: default + require_changes: true + +component_management: + default_rules: + statuses: + - type: project + target: auto + individual_components: + + - component_id: kotlin-sdk-core + name: SDK Core + paths: + - kotlin-sdk-core/** + + - component_id: kotlin-sdk-client + name: SDK Client + paths: + - kotlin-sdk-client/** + + - component_id: kotlin-sdk-server + name: SDK Server + paths: + - kotlin-sdk-server/** + + - component_id: kotlin-sdk-testing + name: SDK Testing + paths: + - kotlin-sdk-testing/** + From eb94f1413254208aa2a96e37b2de93d10a775826 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 16:41:14 +0200 Subject: [PATCH 168/449] chore(deps): bump dev.mokksy:mokksy from 0.6.2 to 0.8.0 (#557) Bumps [dev.mokksy:mokksy](https://github.com/mokksy/ai-mocks) from 0.6.2 to 0.8.0. - Update dependency and migrate to new API --------- Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- .../AbstractStreamableHttpClientTest.kt | 9 +++++- .../kotlin/sdk/client/MockMcp.kt | 30 ++++++++++++++----- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ccb649d..f86685d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,7 +21,7 @@ kotlinx-io = "0.8.2" ktor = "3.2.3" logging = "7.0.14" mockk = "1.14.9" -mokksy = "0.6.2" +mokksy = "0.8.0" serialization = "1.9.0" slf4j = "2.0.17" junit = "5.14.2" diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractStreamableHttpClientTest.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractStreamableHttpClientTest.kt index 77ba553..4a04e81 100644 --- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractStreamableHttpClientTest.kt +++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractStreamableHttpClientTest.kt @@ -5,6 +5,7 @@ import io.ktor.client.engine.apache5.Apache5 import io.ktor.client.plugins.logging.LogLevel import io.ktor.client.plugins.logging.Logging import io.ktor.client.plugins.sse.SSE +import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.TestInstance @@ -16,7 +17,13 @@ internal abstract class AbstractStreamableHttpClientTest { @AfterEach fun afterEach() { - mockMcp.checkForUnmatchedRequests() + mockMcp.verifyNoUnexpectedRequests() + } + + @AfterAll + fun afterAll() { + mockMcp.verifyNoUnmatchedStubs() + mockMcp.close() } protected suspend fun connect(client: Client) { diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt index ff39973..fb71904 100644 --- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt +++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt @@ -3,6 +3,8 @@ package io.modelcontextprotocol.kotlin.sdk.client import dev.mokksy.mokksy.BuildingStep import dev.mokksy.mokksy.Mokksy import dev.mokksy.mokksy.StubConfiguration +import dev.mokksy.mokksy.shutdown +import dev.mokksy.mokksy.start import io.ktor.http.ContentType import io.ktor.http.HttpMethod import io.ktor.http.HttpStatusCode @@ -21,16 +23,23 @@ import kotlinx.serialization.json.putJsonObject const val MCP_SESSION_ID_HEADER = "MCP-Session-Id" -internal class MockMcp(verbose: Boolean = false) { +internal class MockMcp(verbose: Boolean = false) : AutoCloseable { - private val mokksy: Mokksy = Mokksy(verbose = verbose) + private val mokksy: Mokksy = Mokksy(verbose = verbose).apply { + start() + } - fun checkForUnmatchedRequests() { - mokksy.checkForUnmatchedRequests() + fun verifyNoUnexpectedRequests() { + mokksy.verifyNoUnexpectedRequests() + } + + fun verifyNoUnmatchedStubs() { + mokksy.verifyNoUnmatchedStubs() } val url = "${mokksy.baseUrl()}/mcp" + @Suppress("LongParameterList") fun onInitialize( clientName: String? = null, sessionId: String, @@ -108,6 +117,7 @@ internal class MockMcp(verbose: Boolean = false) { } } + @Suppress("LongParameterList") fun handleWithResult( httpMethod: HttpMethod = HttpMethod.Post, jsonRpcMethod: String, @@ -124,9 +134,9 @@ internal class MockMcp(verbose: Boolean = false) { expectedSessionId = expectedSessionId, bodyPredicates = bodyPredicates, ) respondsWith { - val requestId = when (request.body.id) { - is RequestId.NumberId -> (request.body.id as RequestId.NumberId).value.toString() - is RequestId.StringId -> "\"${(request.body.id as RequestId.StringId).value}\"" + val requestId = when (val id = request.body().id) { + is RequestId.NumberId -> id.value.toString() + is RequestId.StringId -> "\"${id.value}\"" } val resultObject = result.invoke() // language=json @@ -143,6 +153,7 @@ internal class MockMcp(verbose: Boolean = false) { } } + @Suppress("LongParameterList") fun handleWithResult( httpMethod: HttpMethod = HttpMethod.Post, jsonRpcMethod: String, @@ -167,6 +178,7 @@ internal class MockMcp(verbose: Boolean = false) { ) } + @Suppress("LongParameterList") fun handleJSONRPCRequest( httpMethod: HttpMethod = HttpMethod.Post, jsonRpcMethod: String, @@ -222,4 +234,8 @@ internal class MockMcp(verbose: Boolean = false) { body = null } } + + override fun close() { + mokksy.shutdown() + } } From 66d31d32a90d377a832dfc8a0343fabc4a723efd Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Mon, 2 Mar 2026 18:00:11 +0200 Subject: [PATCH 169/449] ci: update Codecov configuration for test and coverage upload (#580) ci: update Codecov configuration for test and coverage upload - Unified usage of `codecov/codecov-action@v5` for both test results and coverage upload. - Added `CODECOV_TOKEN` and `report_type` parameters for improved configuration. ## Motivation and Context Actually, CODECOV_TOKEN is required, even though the CodeCov banner previously said the project can upload without a token. ## How Has This Been Tested? CI ## Breaking Changes No ## Types of changes - [x] CI fix ## 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 --- .github/workflows/build.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d41454c..2b769d1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -107,23 +107,22 @@ jobs: report_paths: '**/test-results/**/TEST-*.xml' truncate_stack_traces: false - - name: Upload coverage reports to Codecov - if: matrix.os == 'ubuntu-latest' - uses: codecov/codecov-action@v5 - with: - directory: ./build/reports/kover - files: report.xml - - name: Upload coverage reports to Codecov if: ${{ !cancelled() && matrix.os == 'ubuntu-latest' }} uses: codecov/codecov-action@v5 with: + token: ${{ secrets.CODECOV_TOKEN }} + report_type: coverage directory: ./build/reports/kover files: report.xml - name: Upload test results to Codecov if: ${{ !cancelled() && matrix.os == 'ubuntu-latest' }} - uses: codecov/test-results-action@v1 + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + report_type: test_results + - name: Archive Maven Local Repository if: ${{ success() && matrix.os == 'ubuntu-latest' }} From 9eabb54cb5bef458bb3161ada0c616c4118265b9 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Tue, 3 Mar 2026 08:51:37 +0200 Subject: [PATCH 170/449] refactor: refactor Streamable HTTP ktor extensions (#562) (#568) --- .../kotlin/sdk/server/KtorServer.kt | 202 +++++++++++------- .../kotlin/sdk/server/TransportManager.kt | 22 ++ 2 files changed, 142 insertions(+), 82 deletions(-) create mode 100644 kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/TransportManager.kt diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt index d3cbf40..739f8d3 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt @@ -1,3 +1,5 @@ +@file:Suppress("TooManyFunctions") + package io.modelcontextprotocol.kotlin.sdk.server import io.github.oshai.kotlinlogging.KotlinLogging @@ -20,38 +22,22 @@ import io.ktor.server.sse.SSE import io.ktor.server.sse.ServerSSESession import io.ktor.server.sse.sse import io.ktor.utils.io.KtorDsl -import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport import io.modelcontextprotocol.kotlin.sdk.types.RPCError -import kotlinx.atomicfu.AtomicRef -import kotlinx.atomicfu.atomic -import kotlinx.atomicfu.update -import kotlinx.collections.immutable.PersistentMap -import kotlinx.collections.immutable.toPersistentMap import kotlinx.coroutines.awaitCancellation private val logger = KotlinLogging.logger {} -internal class TransportManager(transports: Map = emptyMap()) { - private val transports: AtomicRef> = atomic(transports.toPersistentMap()) - - fun hasTransport(sessionId: String): Boolean = transports.value.containsKey(sessionId) - - fun getTransport(sessionId: String): AbstractTransport? = transports.value[sessionId] - - fun addTransport(sessionId: String, transport: AbstractTransport) { - transports.update { it.put(sessionId, transport) } - } - - fun removeTransport(sessionId: String) { - transports.update { it.remove(sessionId) } - } -} - /** - * Registers a server-sent events (SSE) route at the specified path. + * Registers MCP over [Server-Sent Events (SSE) Transport](https://modelcontextprotocol.io/specification/2024-11-05/basic/transports#http-with-sse) + * at the specified [path] on this [Route]. * - * @param path the URL path to register the route for SSE. - * @param block the block of code that defines the server's behavior for the SSE session. + * **Precondition:** the [SSE] plugin must be installed on the application before calling this function. + * Use [Application.mcp] if you want SSE to be installed automatically. + * + * @param path the URL path to register the SSE endpoint. + * @param block factory block with access to the [ServerSSESession] + * that creates and returns the [Server] to handle the connection. + * @throws IllegalStateException if the [SSE] plugin is not installed. */ @KtorDsl public fun Route.mcp(path: String, block: ServerSSESession.() -> Server) { @@ -61,11 +47,14 @@ public fun Route.mcp(path: String, block: ServerSSESession.() -> Server) { } /** - * Configures the Ktor Application to handle Model Context Protocol (MCP) over Server-Sent Events (SSE). + * Registers MCP over [Server-Sent Events (SSE) Transport](https://modelcontextprotocol.io/specification/2024-11-05/basic/transports#http-with-sse) + * endpoints on this [Route]. * * **Precondition:** the [SSE] plugin must be installed on the application before calling this function. * Use [Application.mcp] if you want SSE to be installed automatically. * + * @param block factory block with access to the [ServerSSESession] + * that creates and returns the [Server] to handle the connection. * @throws IllegalStateException if the [SSE] plugin is not installed. */ @KtorDsl @@ -81,7 +70,7 @@ public fun Route.mcp(block: ServerSSESession.() -> Server) { ) } - val transportManager = TransportManager() + val transportManager = TransportManager() sse { mcpSseEndpoint("", transportManager, block) @@ -92,6 +81,14 @@ public fun Route.mcp(block: ServerSSESession.() -> Server) { } } +/** + * Configures the Ktor Application to handle Model Context Protocol (MCP) + * over [Server-Sent Events (SSE) Transport](https://modelcontextprotocol.io/specification/2024-11-05/basic/transports#http-with-sse) + * and sets up routing with the provided configuration block. + * + * @param block factory block with access to the [ServerSSESession] + * that creates and returns the [Server] to handle the connection. + */ @KtorDsl public fun Application.mcp(block: ServerSSESession.() -> Server) { install(SSE) @@ -101,19 +98,14 @@ public fun Application.mcp(block: ServerSSESession.() -> Server) { } } -@KtorDsl -@Suppress("LongParameterList") -public fun Application.mcpStreamableHttp( +private fun Application.mcpStreamableHttp( path: String = "/mcp", - enableDnsRebindingProtection: Boolean = false, - allowedHosts: List? = null, - allowedOrigins: List? = null, - eventStore: EventStore? = null, + configuration: StreamableHttpServerTransport.Configuration, block: RoutingContext.() -> Server, ) { install(SSE) - val transportManager = TransportManager() + val transportManager = TransportManager() routing { route(path) { @@ -125,13 +117,9 @@ public fun Application.mcpStreamableHttp( post { val transport = streamableTransport( transportManager = transportManager, - enableDnsRebindingProtection = enableDnsRebindingProtection, - allowedHosts = allowedHosts, - allowedOrigins = allowedOrigins, - eventStore = eventStore, + configuration = configuration, block = block, - ) - ?: return@post + ) ?: return@post transport.handleRequest(null, call) } @@ -144,15 +132,50 @@ public fun Application.mcpStreamableHttp( } } +/** + * Configures the Ktor Application to handle Model Context Protocol (MCP) + * over [Streamable HTTP Transport](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#streamable-http) + * + * Sets up SSE, HTTP POST, and DELETE endpoints at the specified [path]. + * Simple request/response pairs are returned as JSON (not SSE streams). + * + * @param path The base path for the MCP Streamable HTTP endpoint. Defaults to "/mcp". + * @param enableDnsRebindingProtection Enables DNS rebinding attack protection for the endpoint. Defaults to false. + * @param allowedHosts A list of hostnames allowed to access the endpoint. If `null`, no restrictions are applied. + * @param allowedOrigins A list of origins allowed to perform cross-origin requests (CORS). + * If `null`, no restrictions are applied. + * @param eventStore An optional [EventStore] instance to enable resumable event stream functionality. + * Allows storing and replaying events. + * @param block factory block with access to the [RoutingContext] (for reading request headers) + * that creates and returns the [Server] to handle the connection. + */ @KtorDsl @Suppress("LongParameterList") -public fun Application.mcpStatelessStreamableHttp( +public fun Application.mcpStreamableHttp( path: String = "/mcp", enableDnsRebindingProtection: Boolean = false, allowedHosts: List? = null, allowedOrigins: List? = null, eventStore: EventStore? = null, block: RoutingContext.() -> Server, +) { + mcpStreamableHttp( + path = path, + configuration = StreamableHttpServerTransport.Configuration( + enableDnsRebindingProtection = enableDnsRebindingProtection, + allowedHosts = allowedHosts, + allowedOrigins = allowedOrigins, + eventStore = eventStore, + enableJsonResponse = true, + ), + block = block, + ) +} + +private fun Application.mcpStatelessStreamableHttp( + path: String = "/mcp", + configuration: StreamableHttpServerTransport.Configuration, + block: RoutingContext.() -> Server, ) { install(SSE) @@ -160,10 +183,7 @@ public fun Application.mcpStatelessStreamableHttp( route(path) { post { mcpStatelessStreamableHttpEndpoint( - enableDnsRebindingProtection = enableDnsRebindingProtection, - allowedHosts = allowedHosts, - allowedOrigins = allowedOrigins, - eventStore = eventStore, + configuration = configuration, block = block, ) } @@ -185,9 +205,47 @@ public fun Application.mcpStatelessStreamableHttp( } } +/** + * Configures the Ktor Application to handle Model Context Protocol (MCP) + * over _stateless_ [Streamable HTTP Transport](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#streamable-http) + * + * Sets up an HTTP POST endpoint at [path]. GET and DELETE requests return 405 Method Not Allowed. + * Simple request/response pairs are returned as JSON (not SSE streams). + * + * @param path The URL path where the server listens for incoming JSON-RPC requests. Defaults to "/mcp". + * @param enableDnsRebindingProtection Determines whether DNS rebinding protection is enabled. Defaults to `false`. + * @param allowedHosts A list of allowed hostnames. If null, host filtering is disabled. + * @param allowedOrigins A list of allowed origins for CORS. If null, origin filtering is disabled. + * @param eventStore An optional [EventStore] implementation to provide resumability and event replay support. + * @param block factory block with access to the [RoutingContext] (for reading request headers) + * that creates and returns the [Server] to handle the connection. + */ +@KtorDsl +@Suppress("LongParameterList") +public fun Application.mcpStatelessStreamableHttp( + path: String = "/mcp", + enableDnsRebindingProtection: Boolean = false, + allowedHosts: List? = null, + allowedOrigins: List? = null, + eventStore: EventStore? = null, + block: RoutingContext.() -> Server, +) { + mcpStatelessStreamableHttp( + path = path, + configuration = StreamableHttpServerTransport.Configuration( + enableDnsRebindingProtection = enableDnsRebindingProtection, + allowedHosts = allowedHosts, + allowedOrigins = allowedOrigins, + eventStore = eventStore, + enableJsonResponse = true, + ), + block = block, + ) +} + private suspend fun ServerSSESession.mcpSseEndpoint( postEndpoint: String, - transportManager: TransportManager, + transportManager: TransportManager, block: ServerSSESession.() -> Server, ) { val transport = mcpSseTransport(postEndpoint, transportManager) @@ -208,7 +266,7 @@ private suspend fun ServerSSESession.mcpSseEndpoint( private fun ServerSSESession.mcpSseTransport( postEndpoint: String, - transportManager: TransportManager, + transportManager: TransportManager, ): SseServerTransport { val transport = SseServerTransport(postEndpoint, this) transportManager.addTransport(transport.sessionId, transport) @@ -218,20 +276,11 @@ private fun ServerSSESession.mcpSseTransport( } private suspend fun RoutingContext.mcpStatelessStreamableHttpEndpoint( - enableDnsRebindingProtection: Boolean = false, - allowedHosts: List? = null, - allowedOrigins: List? = null, - eventStore: EventStore? = null, + configuration: StreamableHttpServerTransport.Configuration, block: RoutingContext.() -> Server, ) { val transport = StreamableHttpServerTransport( - StreamableHttpServerTransport.Configuration( - enableDnsRebindingProtection = enableDnsRebindingProtection, - allowedHosts = allowedHosts, - allowedOrigins = allowedOrigins, - eventStore = eventStore, - enableJsonResponse = true, - ), + configuration, ).also { it.setSessionIdGenerator(null) } logger.info { "New stateless StreamableHttp connection established without sessionId" } @@ -244,7 +293,7 @@ private suspend fun RoutingContext.mcpStatelessStreamableHttpEndpoint( logger.debug { "Server connected to transport without sessionId" } } -private suspend fun RoutingContext.mcpPostEndpoint(transportManager: TransportManager) { +private suspend fun RoutingContext.mcpPostEndpoint(transportManager: TransportManager) { val sessionId: String = call.request.queryParameters["sessionId"] ?: run { call.respond(HttpStatusCode.BadRequest, "sessionId query parameter is not provided") return @@ -252,7 +301,7 @@ private suspend fun RoutingContext.mcpPostEndpoint(transportManager: TransportMa logger.debug { "Received message for sessionId: $sessionId" } - val transport = transportManager.getTransport(sessionId) as SseServerTransport? + val transport = transportManager.getTransport(sessionId) if (transport == null) { logger.warn { "Session not found for sessionId: $sessionId" } call.respond(HttpStatusCode.NotFound, "Session not found") @@ -267,7 +316,7 @@ private fun ApplicationRequest.sessionId(): String? = header(MCP_SESSION_ID_HEAD private suspend fun existingStreamableTransport( call: ApplicationCall, - transportManager: TransportManager, + transportManager: TransportManager, ): StreamableHttpServerTransport? { val sessionId = call.request.sessionId() if (sessionId.isNullOrEmpty()) { @@ -279,42 +328,31 @@ private suspend fun existingStreamableTransport( return null } - val transport = transportManager.getTransport(sessionId) as? StreamableHttpServerTransport - if (transport == null) { + val transport = transportManager.getTransport(sessionId) + return if (transport == null) { call.reject( HttpStatusCode.NotFound, RPCError.ErrorCode.CONNECTION_CLOSED, "Session not found", ) - return null + null + } else { + transport } - - return transport } private suspend fun RoutingContext.streamableTransport( - transportManager: TransportManager, - enableDnsRebindingProtection: Boolean, - allowedHosts: List?, - allowedOrigins: List?, - eventStore: EventStore?, + transportManager: TransportManager, + configuration: StreamableHttpServerTransport.Configuration, block: RoutingContext.() -> Server, ): StreamableHttpServerTransport? { val sessionId = call.request.sessionId() if (sessionId != null) { - val transport = transportManager.getTransport(sessionId) as? StreamableHttpServerTransport + val transport = transportManager.getTransport(sessionId) return transport ?: existingStreamableTransport(call, transportManager) } - val transport = StreamableHttpServerTransport( - StreamableHttpServerTransport.Configuration( - enableDnsRebindingProtection = enableDnsRebindingProtection, - allowedHosts = allowedHosts, - allowedOrigins = allowedOrigins, - eventStore = eventStore, - enableJsonResponse = true, - ), - ) + val transport = StreamableHttpServerTransport(configuration) transport.setOnSessionInitialized { initializedSessionId -> transportManager.addTransport(initializedSessionId, transport) diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/TransportManager.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/TransportManager.kt new file mode 100644 index 0000000..e02931f --- /dev/null +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/TransportManager.kt @@ -0,0 +1,22 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport +import kotlinx.atomicfu.AtomicRef +import kotlinx.atomicfu.atomic +import kotlinx.atomicfu.update +import kotlinx.collections.immutable.PersistentMap +import kotlinx.collections.immutable.toPersistentMap + +internal class TransportManager { + private val transports: AtomicRef> = atomic(emptyMap().toPersistentMap()) + + fun getTransport(sessionId: String): T? = transports.value[sessionId] + + fun addTransport(sessionId: String, transport: T) { + transports.update { it.put(sessionId, transport) } + } + + fun removeTransport(sessionId: String) { + transports.update { it.remove(sessionId) } + } +} From a38818018ac8570973b502600938544696fa6248 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Tue, 3 Mar 2026 16:10:25 +0200 Subject: [PATCH 171/449] fix!(server): #575 Rethrow CancellationException in StdioServerTransport, fix ReadBuffer (#571) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Rethrow CancellationException in StdioServerTransport, fix ReadBuffer ### Changes 1. StdioServerTransport - Replace generic `Throwable` catches with specific `CancellationException` and `Exception` for clarity and correctness. **breaking change** `Throwable` is not handled any more! - Refactor launching jobs to separate methods - Introduce `READ_BUFFER_SIZE` constant to replace inline buffer size literals. - Add suppress annotations for clearer intent and constructor documentation for better usability. - Extend StdioServerTransportTest 2. ReadBuffer Previously, readMessage() returned null after consuming an unparseable line even when more complete lines existed in the buffer. This caused valid messages following a bad line in the same chunk to be silently dropped until the next chunk arrived (or forever, in tests). Fix the loop so null means only "no complete line available", not "encountered a parse failure". Blank/whitespace-only lines are now silently skipped via isBlank() rather than forwarded to the deserializer and logged as errors. Refactor the method into three focused helpers: - readMessage() — outer loop over lines - readLine() — consume the next newline-delimited line from the buffer - tryRecover() — attempt deserialization from the first '{' onward 3. Add utility method `runIntegrationTest` for integration testing (`runBlocking` + `withTimeout`). Use it in StdioServerTransportTest 4. Store processing Job reference and `calcelAndJoin()` it on close. Partially addressing #574 ## Motivation and Context See #575, #564, #242 ## How Has This Been Tested? Unit test added ## Breaking Changes Semantic change: `Throwable` and CancellationException are not handled any more! ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 ## Additional context --- .../kotlin/sdk/shared/ReadBuffer.kt | 91 +++--- .../kotlin/sdk/shared/ReadBufferTest.kt | 16 ++ kotlin-sdk-server/build.gradle.kts | 2 + .../kotlin/sdk/server/StdioServerTransport.kt | 93 ++++++- .../sdk/server/StdioServerTransportTest.kt | 262 ++++++++++++++++-- .../kotlin/test/utils/testing.kt | 29 ++ 6 files changed, 416 insertions(+), 77 deletions(-) create mode 100644 test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/testing.kt diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBuffer.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBuffer.kt index d991e7f..d3fc61f 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBuffer.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBuffer.kt @@ -20,45 +20,70 @@ public class ReadBuffer { buffer.write(chunk) } + /** + * Reads and deserializes a JSON-RPC message from the input buffer. + * + * The method attempts to read lines from the buffer until a valid `JSONRPCMessage` is successfully + * deserialized. Blank lines are ignored, and if a deserialization error occurs, the method attempts + * to recover and process the message. + * + * Recovery involves attempting to parse from the first detected JSON object in the line when an + * error is encountered during deserialization. + * + * @return A deserialized `JSONRPCMessage` if successfully processed; otherwise, `null` if the + * input buffer is exhausted or no valid message is found. + */ public fun readMessage(): JSONRPCMessage? { - if (buffer.exhausted()) return null - var lfIndex = buffer.indexOf('\n'.code.toByte()) - val line = when (lfIndex) { - -1L -> return null + while (true) { + val line = readNextLine() ?: return null + if (line.isBlank()) continue - 0L -> { - buffer.skip(1) - return null + @Suppress("TooGenericExceptionCaught") + val message = try { + deserializeMessage(line) + } catch (e: Exception) { + logger.error(e) { "Failed to deserialize message from line: $line\nAttempting to recover..." } + tryRecover(line) } - - else -> { - var skipBytes = 1 - if (buffer[lfIndex - 1] == '\r'.code.toByte()) { - lfIndex -= 1 - skipBytes += 1 - } - val string = buffer.readString(lfIndex) - buffer.skip(skipBytes.toLong()) - string - } - } - try { - return deserializeMessage(line) - } catch (e: Exception) { - logger.error(e) { "Failed to deserialize message from line: $line\nAttempting to recover..." } - // if there is a non-JSON object prefix, try to parse from the first '{' onward. - val braceIndex = line.indexOf('{') - if (braceIndex != -1) { - val trimmed = line.substring(braceIndex) - try { - return deserializeMessage(trimmed) - } catch (ignored: Exception) { - logger.error(ignored) { "Deserialization failed for line: $line\nSkipping..." } - } + if (message != null) { + return message } } + } - return null + private fun readNextLine(): String? { + val lfIndex = if (buffer.exhausted()) -1L else buffer.indexOf('\n'.code.toByte()) + if (lfIndex == -1L) return null + + return if (lfIndex == 0L) { + buffer.skip(1) + "" + } else { + var skipBytes = 1 + var messageLength = lfIndex + if (buffer[lfIndex - 1] == '\r'.code.toByte()) { + messageLength -= 1 + skipBytes += 1 + } + val string = buffer.readString(messageLength) + buffer.skip(skipBytes.toLong()) + string + } + } + + private fun tryRecover(line: String): JSONRPCMessage? { + // if there is a non-JSON object prefix, try to parse from the first '{' onward. + val braceIndex = line.indexOf('{') + if (braceIndex == -1) return null + + val trimmed = line.substring(braceIndex) + @Suppress("TooGenericExceptionCaught") + return try { + deserializeMessage(trimmed) + } catch (ignored: Exception) { + logger.error(ignored) { "Deserialization failed for line: $line\nSkipping..." } + null + } } public fun clear() { diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBufferTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBufferTest.kt index a49ff3d..7b4f053 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBufferTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBufferTest.kt @@ -31,6 +31,8 @@ class ReadBufferTest { val messageBytes = json.encodeToString(testMessage).encodeToByteArray() readBuffer.append(messageBytes) assertNull(readBuffer.readMessage()) + readBuffer.append("\r".encodeToByteArray()) + assertNull(readBuffer.readMessage()) // Append a newline and verify message is now available readBuffer.append("\n".encodeToByteArray()) @@ -45,6 +47,20 @@ class ReadBufferTest { assertNull(readBuffer.readMessage()) } + @Test + fun `skip blank line`() { + val readBuffer = ReadBuffer() + readBuffer.append(" \n".toByteArray()) + assertNull(readBuffer.readMessage()) + } + + @Test + fun `skip invalid json line`() { + val readBuffer = ReadBuffer() + readBuffer.append(" {ah=oh\n".toByteArray()) + assertNull(readBuffer.readMessage()) + } + @Test fun `should be reusable after clearing`() { val readBuffer = ReadBuffer() diff --git a/kotlin-sdk-server/build.gradle.kts b/kotlin-sdk-server/build.gradle.kts index 9d0bddf..3a0a398 100644 --- a/kotlin-sdk-server/build.gradle.kts +++ b/kotlin-sdk-server/build.gradle.kts @@ -23,11 +23,13 @@ kotlin { implementation(libs.kotest.assertions.core) implementation(libs.kotest.assertions.json) implementation(libs.kotlinx.coroutines.test) + implementation(project(":test-utils")) } } jvmTest { dependencies { + implementation(libs.junit.jupiter.params) implementation(libs.kotest.assertions.ktor) implementation(libs.ktor.client.content.negotiation) implementation(libs.ktor.client.logging) diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport.kt index b3ec4d6..8fd46dd 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport.kt @@ -7,6 +7,7 @@ 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.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.NonCancellable @@ -26,25 +27,32 @@ import kotlin.concurrent.atomics.AtomicBoolean import kotlin.concurrent.atomics.ExperimentalAtomicApi import kotlin.coroutines.CoroutineContext +private const val READ_BUFFER_SIZE = 8192L + /** * A server transport that communicates with a client via standard I/O. * - * Reads from System.in and writes to System.out. + * Reads from input [Source] and writes to output [Sink]. + * + * @constructor Creates a new instance of [StdioServerTransport]. + * @param inputStream The input [Source] used to receive data. + * @param outputStream The output [Sink] used to send data. */ @OptIn(ExperimentalAtomicApi::class) public class StdioServerTransport(private val inputStream: Source, outputStream: Sink) : AbstractTransport() { - private val logger = KotlinLogging.logger {} + private val logger = KotlinLogging.logger {} private val readBuffer = ReadBuffer() private val initialized: AtomicBoolean = AtomicBoolean(false) private var readingJob: Job? = null private var sendingJob: Job? = null + private var processingJob: Job? = null private val coroutineContext: CoroutineContext = IODispatcher + SupervisorJob() private val scope = CoroutineScope(coroutineContext) private val readChannel = Channel(Channel.UNLIMITED) private val writeChannel = Channel(Channel.UNLIMITED) - private val outputWriter = outputStream.buffered() + private val outputSink = outputStream.buffered() override suspend fun start() { if (!initialized.compareAndSet(expectedValue = false, newValue = true)) { @@ -52,11 +60,22 @@ public class StdioServerTransport(private val inputStream: Source, outputStream: } // Launch a coroutine to read from stdin - readingJob = scope.launch { + readingJob = launchReadingJob() + + // Launch a coroutine to process messages from readChannel + processingJob = launchProcessingJob() + + // Launch a coroutine to handle message sending + sendingJob = launchSendingJob() + } + + private fun launchReadingJob(): Job { + val job = scope.launch { val buf = Buffer() + @Suppress("TooGenericExceptionCaught") try { while (isActive) { - val bytesRead = inputStream.readAtMostTo(buf, 8192) + val bytesRead = inputStream.readAtMostTo(buf, READ_BUFFER_SIZE) if (bytesRead == -1L) { // EOF reached break @@ -66,6 +85,8 @@ public class StdioServerTransport(private val inputStream: Source, outputStream: readChannel.send(chunk) } } + } catch (e: CancellationException) { + throw e } catch (e: Throwable) { logger.error(e) { "Error reading from stdin" } _onError.invoke(e) @@ -74,35 +95,59 @@ public class StdioServerTransport(private val inputStream: Source, outputStream: close() } } + job.invokeOnCompletion { cause -> + logJobCompletion("Message reading", cause) + } + return job + } - // Launch a coroutine to process messages from readChannel - scope.launch { + private fun launchProcessingJob(): Job { + val job = scope.launch { + @Suppress("TooGenericExceptionCaught") try { for (chunk in readChannel) { readBuffer.append(chunk) processReadBuffer() } + } catch (e: CancellationException) { + throw e } catch (e: Throwable) { _onError.invoke(e) } } + job.invokeOnCompletion { cause -> + logJobCompletion("Processing", cause) + } + return job + } - // Launch a coroutine to handle message sending - sendingJob = scope.launch { + private fun launchSendingJob(): Job { + val job = scope.launch { + @Suppress("TooGenericExceptionCaught") try { for (message in writeChannel) { val json = serializeMessage(message) - outputWriter.writeString(json) - outputWriter.flush() + outputSink.writeString(json) + outputSink.flush() } + } catch (e: CancellationException) { + throw e } catch (e: Throwable) { logger.error(e) { "Error writing to stdout" } _onError.invoke(e) } } + job.invokeOnCompletion { cause -> + logJobCompletion("Message sending", cause) + if (cause is CancellationException) { + readingJob?.cancel(cause) + } + } + return job } private suspend fun processReadBuffer() { + @Suppress("TooGenericExceptionCaught") while (true) { val message = try { readBuffer.readMessage() @@ -115,12 +160,30 @@ public class StdioServerTransport(private val inputStream: Source, outputStream: // Async invocation broke delivery order try { _onMessage.invoke(message) + } catch (e: CancellationException) { + throw e } catch (e: Throwable) { + logger.error(e) { "Error processing message" } _onError.invoke(e) } } } + private fun logJobCompletion(jobName: String, cause: Throwable?) { + when (cause) { + is CancellationException -> { + } + + null -> { + logger.debug { "$jobName job completed" } + } + + else -> { + logger.debug(cause) { "$jobName job completed exceptionally" } + } + } + } + override suspend fun close() { if (!initialized.compareAndSet(expectedValue = true, newValue = false)) return @@ -133,13 +196,15 @@ public class StdioServerTransport(private val inputStream: Source, outputStream: }.onFailure { logger.warn(it) { "Failed to close stdin" } } readingJob?.cancel() - readChannel.close() + + processingJob?.cancelAndJoin() + readBuffer.clear() runCatching { - outputWriter.flush() - outputWriter.close() + outputSink.flush() + outputSink.close() }.onFailure { logger.warn(it) { "Failed to close stdout" } } invokeOnCloseCallback() diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransportTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransportTest.kt index ff46263..dcbb7a4 100644 --- a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransportTest.kt +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransportTest.kt @@ -1,28 +1,44 @@ package io.modelcontextprotocol.kotlin.sdk.server +import io.kotest.assertions.nondeterministic.eventually +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.assertions.withClue +import io.kotest.matchers.collections.shouldContain +import io.kotest.matchers.shouldBe import io.modelcontextprotocol.kotlin.sdk.shared.ReadBuffer import io.modelcontextprotocol.kotlin.sdk.shared.serializeMessage import io.modelcontextprotocol.kotlin.sdk.types.InitializedNotification import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage import io.modelcontextprotocol.kotlin.sdk.types.PingRequest import io.modelcontextprotocol.kotlin.sdk.types.toJSON +import io.modelcontextprotocol.kotlin.test.utils.runIntegrationTest +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.TimeoutCancellationException +import kotlinx.coroutines.withTimeout +import kotlinx.io.Buffer +import kotlinx.io.RawSink +import kotlinx.io.RawSource import kotlinx.io.Sink import kotlinx.io.Source import kotlinx.io.asSink import kotlinx.io.asSource import kotlinx.io.buffered +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource import java.io.ByteArrayOutputStream +import java.io.IOException import java.io.PipedInputStream import java.io.PipedOutputStream -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue +import kotlin.test.fail +import kotlin.time.Duration.Companion.seconds +@TestInstance(TestInstance.Lifecycle.PER_CLASS) class StdioServerTransportTest { private lateinit var input: PipedInputStream private lateinit var inputWriter: PipedOutputStream @@ -33,7 +49,7 @@ class StdioServerTransportTest { private lateinit var bufferedInput: Source private lateinit var printOutput: Sink - @BeforeTest + @BeforeEach fun setUp() { // Simulate an input stream that we can push data into using inputWriter. input = PipedInputStream() @@ -50,33 +66,36 @@ class StdioServerTransportTest { } bufferedInput = input.asSource().buffered() - printOutput = output.asSink().buffered() } @Test - fun `should start then close cleanly`() { - runBlocking { - val server = StdioServerTransport(bufferedInput, printOutput) - server.onError { error -> - throw error - } - - var didClose = false - server.onClose { - didClose = true - } - - server.start() - assertFalse(didClose, "Should not have closed yet") - - server.close() - assertTrue(didClose, "Should have closed after calling close()") - } + fun `should be safe to close before start`() = runIntegrationTest { + val server = StdioServerTransport(bufferedInput, printOutput) + server.close() // initialized guard makes this a no-op; must not throw } @Test - fun `should not read until started`() = runTest { + fun `should start then close cleanly`() = runIntegrationTest { + val server = StdioServerTransport(bufferedInput, printOutput) + server.onError { error -> + throw error + } + + var didClose = false + server.onClose { + didClose = true + } + + server.start() + assertFalse(didClose, "Should not have closed yet") + + server.close() + assertTrue(didClose, "Should have closed after calling close()") + } + + @Test + fun `should not read until started`() = runIntegrationTest { val server = StdioServerTransport(bufferedInput, printOutput) server.onError { error -> throw error @@ -101,11 +120,11 @@ class StdioServerTransportTest { server.start() val received = readMessage.await() - assertEquals(message, received) + received shouldBe message } @Test - fun `should read multiple messages`() = runTest { + fun `should read multiple messages`() = runIntegrationTest { val server = StdioServerTransport(bufferedInput, printOutput) server.onError { error -> throw error @@ -135,10 +154,193 @@ class StdioServerTransportTest { server.start() finished.await() - assertEquals(messages, readMessages) + readMessages shouldBe messages + } + + // region: Exception handling + + @ParameterizedTest(name = "[{index}] input throws {0}") + @MethodSource("inputErrors") + fun `should invoke onError when input stream throws`(throwable: Throwable): Unit = runIntegrationTest { + val server = StdioServerTransport(FaultyRawSource(throwable).buffered(), printOutput) + val capturedError = CompletableDeferred() + server.onError { capturedError.complete(it) } + server.onMessage {} + + server.start() + + capturedError.await() shouldBe throwable + server.close() + } + + @ParameterizedTest(name = "[{index}] output throws {0}") + @MethodSource("outputErrors") + fun `should invoke onError when output sink throws`(throwable: Throwable): Unit = runIntegrationTest { + val server = StdioServerTransport(bufferedInput, FaultyRawSink(throwable).buffered()) + val capturedError = CompletableDeferred() + server.onError { capturedError.complete(it) } + server.onMessage {} + + server.start() + server.send(PingRequest().toJSON()) + + capturedError.await() shouldBe throwable + server.close() + } + + @Test + fun `should call onClose when input EOF is reached`(): Unit = runIntegrationTest { + val server = StdioServerTransport(bufferedInput, printOutput) + val didClose = CompletableDeferred() + server.onError { throw it } + server.onClose { didClose.complete(Unit) } + server.onMessage {} + + server.start() + inputWriter.close() // signal EOF to the reading loop + + eventually(2.seconds) { + didClose.isCompleted shouldBe true + } + } + + @Test + fun `should throw when starting twice`(): Unit = runIntegrationTest { + val server = StdioServerTransport(bufferedInput, printOutput) + server.onMessage {} + server.start() + withClue("Server should not start twice") { + shouldThrow { + server.start() + } + } + server.close() + } + + @ParameterizedTest(name = "[{index}] handler throws {0}") + @MethodSource("handlerErrors") + fun `should continue processing messages after handler throws`(throwable: Throwable) = runIntegrationTest { + val server = StdioServerTransport(bufferedInput, printOutput) + val capturedErrors = mutableListOf() + val receivedMessages = mutableListOf() + val secondMessageProcessed = CompletableDeferred() + + val message1 = PingRequest().toJSON() + val message2 = InitializedNotification().toJSON() + + server.onError { capturedErrors.add(it) } + server.onMessage { message -> + if (message == message1) { + throw throwable + } else { + receivedMessages.add(message) + secondMessageProcessed.complete(Unit) + } + } + + server.start() + + inputWriter.write(serializeMessage(message1)) + inputWriter.write(serializeMessage(message2)) + inputWriter.flush() + + secondMessageProcessed.await() + + capturedErrors shouldContain throwable + receivedMessages shouldBe listOf(message2) + server.close() + } + + @Test + fun `should not invoke onError for CancellationException in handler`() = runIntegrationTest { + val server = StdioServerTransport(bufferedInput, printOutput) + val capturedError = CompletableDeferred() + server.onError { capturedError.complete(it) } + + server.onMessage { throw CancellationException("cancelled") } + server.start() + + inputWriter.write(serializeMessage(PingRequest().toJSON())) + inputWriter.flush() + + // We expect onError NOT to be called. + // We wait a bit to make sure it's not called, then close. + try { + withTimeout(1.seconds) { + capturedError.await() + } + fail("Should not have captured an error for CancellationException") + } catch (_: TimeoutCancellationException) { + // Success - timeout reached without error captured + } finally { + server.close() + } + } + + // endregion + + @Test + fun `should continue receiving valid messages after malformed JSON is skipped`() = runIntegrationTest { + val server = StdioServerTransport(bufferedInput, printOutput) + val received = CompletableDeferred() + // ReadBuffer silently skips unparseable lines — no onError callback expected + server.onError {} + server.onMessage { received.complete(it) } + + val validMessage = PingRequest().toJSON() + inputWriter.write("not-valid-json\n".toByteArray()) + inputWriter.write(" \t \r \n".toByteArray()) // blank line + inputWriter.write(serializeMessage(validMessage).toByteArray()) + inputWriter.flush() + + server.start() + + received.await() shouldBe validMessage + server.close() + } + + @Suppress("unused") + private fun inputErrors() = listOf( + IOException("simulated read failure"), + RuntimeException("unexpected read exception"), + OutOfMemoryError("unexpected read error"), + ) + + @Suppress("unused") + private fun outputErrors() = listOf( + IOException("simulated write failure"), + RuntimeException("unexpected write exception"), + OutOfMemoryError("unexpected write error"), + ) + + @Suppress("unused") + private fun handlerErrors() = listOf( + RuntimeException("handler failure"), + IOException("handler IO failure"), + OutOfMemoryError("handler error"), + ) + + /** A [RawSource] that immediately throws [throwable] on every read attempt. */ + private class FaultyRawSource(private val throwable: Throwable) : RawSource { + override fun readAtMostTo(sink: Buffer, byteCount: Long): Long = throw throwable + override fun close() { + // noop + } + } + + /** A [RawSink] that throws [throwable] on every [write] call. */ + private class FaultyRawSink(private val throwable: Throwable) : RawSink { + override fun write(source: Buffer, byteCount: Long): Unit = throw throwable + override fun flush() { + // noop + } + + override fun close() { + // noop + } } } -fun PipedOutputStream.write(s: String) { +private fun PipedOutputStream.write(s: String) { write(s.toByteArray()) } diff --git a/test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/testing.kt b/test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/testing.kt new file mode 100644 index 0000000..ee73162 --- /dev/null +++ b/test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/testing.kt @@ -0,0 +1,29 @@ +package io.modelcontextprotocol.kotlin.test.utils + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeout +import kotlin.coroutines.CoroutineContext +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds + +@Suppress("InjectDispatcher") // this is constant +private val defaultTestDispatcher: CoroutineContext = Dispatchers.IO.limitedParallelism(100) +private val defaultTestTimeout: Duration = 5.seconds + +/** + * Executes a suspendable integration test block within a coroutine scope, applying a timeout constraint. + * + * @param timeout The maximum duration allowed for the test execution. Defaults to 5 seconds. + * @param context The coroutine context within which the test block is executed. + * Defaults to [Dispatchers.IO] with 100 parallel tasks. + * @param block The suspendable test logic to be executed. + */ +public fun runIntegrationTest( + context: CoroutineContext = defaultTestDispatcher, + timeout: Duration = defaultTestTimeout, + block: suspend CoroutineScope.() -> Unit, +): Unit = runBlocking(context) { + withTimeout(timeout, block) +} From 3e7ea39bf70556154699bdfe483ca049a16b99aa Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Tue, 3 Mar 2026 23:46:32 +0200 Subject: [PATCH 172/449] chore: relax Detekt rules for tests (#582) - Updated `detekt.yml` to exclude test folders from `EmptyFunctionBlock`, `LargeClass`, and `LongMethod` checks. - Cleanup detekt-baseline files. - Simplified `.idea/detekt.xml`. ## Motivation and Context To reduce linter noise and reduce tension ## How Has This Been Tested? `./gradlew detekt` ## Breaking Changes No ## Types of changes - [x] Build tool/ci 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 ## Additional context #567 --- .idea/detekt.xml | 7 +------ config/detekt/detekt.yml | 15 +++++++++++++++ .../detekt-baseline-commonTestSourceSet.xml | 11 ----------- integration-test/detekt-baseline-test.xml | 15 --------------- .../detekt-baseline-commonTestSourceSet.xml | 1 - kotlin-sdk-client/detekt-baseline-test.xml | 2 -- 6 files changed, 16 insertions(+), 35 deletions(-) delete mode 100644 integration-test/detekt-baseline-commonTestSourceSet.xml diff --git a/.idea/detekt.xml b/.idea/detekt.xml index a408ca9..d89f908 100644 --- a/.idea/detekt.xml +++ b/.idea/detekt.xml @@ -1,14 +1,9 @@ - - \ No newline at end of file + diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml index a7504c1..8c19749 100644 --- a/config/detekt/detekt.yml +++ b/config/detekt/detekt.yml @@ -3,3 +3,18 @@ config: warningsAsErrors: false checkExhaustiveness: false +complexity: + LargeClass: + excludes: &testFolders + - '**/test/**' + - '**/commonTest/**' + - '**/jvmTest/**' + - '**/jsTest/**' + - '**/iosTest/**' + LongMethod: + excludes: *testFolders + +empty-blocks: + EmptyFunctionBlock: + excludes: *testFolders + diff --git a/integration-test/detekt-baseline-commonTestSourceSet.xml b/integration-test/detekt-baseline-commonTestSourceSet.xml deleted file mode 100644 index 660fa66..0000000 --- a/integration-test/detekt-baseline-commonTestSourceSet.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - EmptyFunctionBlock:ClientTest.kt:ClientTest.<no name provided>${ } - EmptyFunctionBlock:ClientTest.kt:ClientTest.<no name provided>${} - LargeClass:ClientTest.kt:ClientTest - LongMethod:ClientTest.kt:ClientTest$@Test fun `JSONRPCRequest with ToolsList method and default params returns list of tools` - LongMethod:ClientTest.kt:ClientTest$@Test fun `should handle logging setLevel request` - - diff --git a/integration-test/detekt-baseline-test.xml b/integration-test/detekt-baseline-test.xml index 1e93fe0..710b729 100644 --- a/integration-test/detekt-baseline-test.xml +++ b/integration-test/detekt-baseline-test.xml @@ -7,8 +7,6 @@ AbstractClassCanBeConcreteClass:AbstractToolIntegrationTest.kt:AbstractToolIntegrationTest$AbstractToolIntegrationTest AbstractClassCanBeConcreteClass:BaseTransportTest.kt:BaseTransportTest$BaseTransportTest CyclomaticComplexMethod:AbstractToolIntegrationTest.kt:AbstractToolIntegrationTest$private fun setupCalculatorTool - EmptyFunctionBlock:ClientTest.kt:ClientTest.<no name provided>${ } - EmptyFunctionBlock:ClientTest.kt:ClientTest.<no name provided>${} ForbiddenComment:StdioClientTransportTest.kt:StdioClientTransportTest$// TODO: fix running on windows InjectDispatcher:AbstractKotlinClientTsServerTest.kt:AbstractKotlinClientTsServerTest$IO InjectDispatcher:AbstractPromptIntegrationTest.kt:AbstractPromptIntegrationTest$IO @@ -21,19 +19,6 @@ InjectDispatcher:StreamableHttpIntegrationTest.kt:StreamableHttpIntegrationTest$IO InjectDispatcher:TsEdgeCasesTestSse.kt:TsEdgeCasesTestSse$IO InjectDispatcher:WebSocketIntegrationTest.kt:WebSocketIntegrationTest$IO - LargeClass:AbstractToolIntegrationTest.kt:AbstractToolIntegrationTest : KotlinTestBase - LargeClass:ClientTest.kt:ClientTest - LongMethod:AbstractPromptIntegrationTest.kt:AbstractPromptIntegrationTest$@Test fun testMissingRequiredArguments - LongMethod:AbstractPromptIntegrationTest.kt:AbstractPromptIntegrationTest$override fun configureServer - LongMethod:AbstractResourceIntegrationTest.kt:AbstractResourceIntegrationTest$override fun configureServer - LongMethod:AbstractToolIntegrationTest.kt:AbstractToolIntegrationTest$private fun setupCalculatorTool - LongMethod:ClientTest.kt:ClientTest$@Test fun `JSONRPCRequest with ToolsList method and default params returns list of tools` - LongMethod:ClientTest.kt:ClientTest$@Test fun `should handle logging setLevel request` - LongMethod:KotlinServerForTsClientSse.kt:HttpServerTransport$suspend fun handleRequest - LongMethod:KotlinServerForTsClientSse.kt:KotlinServerForTsClient$fun createMcpServer: Server - LongMethod:KotlinServerForTsClientSse.kt:KotlinServerForTsClient$fun start - LongMethod:ServerResourcesNotificationSubscribeTest.kt:ServerResourcesNotificationSubscribeTest$@Test fun `should send resource notifications` - LongMethod:TsEdgeCasesTestSse.kt:TsEdgeCasesTestSse$@Test @Timeout(30, unit = TimeUnit.SECONDS) fun testComplexConcurrentRequests: Unit MatchingDeclarationName:PromptIntegrationTestSse.kt:SchemaPromptIntegrationTestSse : AbstractPromptIntegrationTest SleepInsteadOfDelay:KotlinServerForTsClientSse.kt:KotlinServerForTsClient$sleep(500) ThrowsCount:AbstractPromptIntegrationTest.kt:AbstractPromptIntegrationTest$override fun configureServer diff --git a/kotlin-sdk-client/detekt-baseline-commonTestSourceSet.xml b/kotlin-sdk-client/detekt-baseline-commonTestSourceSet.xml index b73eb9f..cab4322 100644 --- a/kotlin-sdk-client/detekt-baseline-commonTestSourceSet.xml +++ b/kotlin-sdk-client/detekt-baseline-commonTestSourceSet.xml @@ -2,7 +2,6 @@ - LongMethod:StreamableHttpClientTransportTest.kt:StreamableHttpClientTransportTest$@Ignore @Test fun testNotificationSchemaE2E MaxLineLength:StreamableHttpClientTransportTest.kt:StreamableHttpClientTransportTest$"""data: {"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"upload-123","progress":50,"total":100}}""" MaxLineLength:StreamableHttpClientTransportTest.kt:StreamableHttpClientTransportTest$"""data: {"jsonrpc":"2.0","method":"notifications/resumed","params":{"fromToken":"$lastEventIdSent"}}""" diff --git a/kotlin-sdk-client/detekt-baseline-test.xml b/kotlin-sdk-client/detekt-baseline-test.xml index 30a384a..b16e8eb 100644 --- a/kotlin-sdk-client/detekt-baseline-test.xml +++ b/kotlin-sdk-client/detekt-baseline-test.xml @@ -6,8 +6,6 @@ ForbiddenComment:StreamableHttpClientTest.kt:StreamableHttpClientTest$// TODO: how to get notifications via Client API? InjectDispatcher:StdioClientTransportErrorHandlingTest.kt:StdioClientTransportErrorHandlingTest$IO InjectDispatcher:StreamableHttpClientTransportTest.kt:StreamableHttpClientTransportTest$Default - LongMethod:StreamableHttpClientTest.kt:StreamableHttpClientTest$@Test fun `test streamableHttpClient` - LongMethod:StreamableHttpClientTransportTest.kt:StreamableHttpClientTransportTest$@Ignore @Test fun testNotificationSchemaE2E LongParameterList:MockMcp.kt:MockMcp$fun handleJSONRPCRequest LongParameterList:MockMcp.kt:MockMcp$fun handleWithResult LongParameterList:MockMcp.kt:MockMcp$fun onInitialize From 1454229ad92293c8533fb0f3fcefea08ade3bbb2 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Wed, 4 Mar 2026 18:59:07 +0200 Subject: [PATCH 173/449] test: add serialization tests and guidelines for handling `null` and omitted fields (#583) --- AGENTS.md | 4 +++ .../kotlin/sdk/types/InitializeTest.kt | 28 +++++++++++++++++++ .../kotlin/sdk/types/JsonRpcTest.kt | 20 +++++++++++++ 3 files changed, 52 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 7b723e8..1646c55 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -92,6 +92,10 @@ Follow these rules to keep changes safe, comprehensible, and easy to maintain. - **mockk**: Mocking library - **Ktor MockEngine**: For HTTP client mocking (`io.ktor:ktor-client-mock`) - **Java tests**: Use JUnit5, Mockito, AssertJ core +- **Serialization test utilities** (`io.modelcontextprotocol.kotlin.test.utils`): + - `verifySerialization(value, json, expectedJson)` — serializes, asserts match, round-trips back; use for most serialization tests + - `verifyDeserialization(json, payload)` — deserializes from JSON, re-serializes, asserts match; returns the object for further assertions + - Always test both empty/null/omitted and non-null cases for nullable fields; `McpJson` has `explicitNulls = false` so null properties must be absent from JSON, not `null` ### Kotest Patterns diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/InitializeTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/InitializeTest.kt index 69c0878..3da2a8b 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/InitializeTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/InitializeTest.kt @@ -189,6 +189,34 @@ class InitializeTest { ) } + @Test + fun `should serialize InitializeResult without meta - meta must be omitted`() { + val result = InitializeResult( + protocolVersion = "2025-03-26", + capabilities = ServerCapabilities(tools = ServerCapabilities.Tools(listChanged = false)), + serverInfo = Implementation(name = "test-server", version = "1.0.0"), + ) + + verifySerialization( + result, + McpJson, + """ + { + "protocolVersion": "2025-03-26", + "capabilities": { + "tools": { + "listChanged": false + } + }, + "serverInfo": { + "name": "test-server", + "version": "1.0.0" + } + } + """.trimIndent(), + ) + } + @Test fun `should deserialize InitializeResult from JSON`() { val json = """ diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/JsonRpcTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/JsonRpcTest.kt index db111fe..7de360d 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/JsonRpcTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/JsonRpcTest.kt @@ -265,6 +265,26 @@ class JsonRpcTest { ) } + @Test + fun `should serialize JSONRPCResponse with EmptyResult - null meta must be omitted`() { + val response = JSONRPCResponse( + id = RequestId("call-2"), + result = EmptyResult(), // meta = null by default + ) + + verifySerialization( + response, + McpJson, + """ + { + "id": "call-2", + "result": {}, + "jsonrpc": "2.0" + } + """.trimIndent(), + ) + } + @Test fun `should deserialize JSONRPCResponse with EmptyResult`() { val json = """ From 5b58814613069babb1bbd5cab4572331eaed4b45 Mon Sep 17 00:00:00 2001 From: Jakub Amanowicz Date: Thu, 5 Mar 2026 13:24:07 +0100 Subject: [PATCH 174/449] feat: bump version to 0.9.0 (#576) bumps version to 0.9.0 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index c4128f5..45b067c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,4 +14,4 @@ kotlin.mpp.enableCInteropCommonization=true kotlin.native.ignoreDisabledTargets=true group=io.modelcontextprotocol -version=0.8.3 +version=0.9.0 From 0b39f57cffe3affc55ee6d591910b8d356468cd1 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Tue, 10 Mar 2026 12:45:21 +0100 Subject: [PATCH 175/449] docs: streamable server sample (#595) - add a simple streamable http server example with auth - update readme: replace quickstart example from sse server to streamable server - add readme files for samples, simple-streamable-server, notebooks closes #170 ## How Has This Been Tested? knit and inspector ## Breaking Changes NaN ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] 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 - [x] I have added or updated documentation as needed --- .github/workflows/build.yml | 1 + .github/workflows/samples.yml | 1 + README.md | 84 ++--- docs/build.gradle.kts | 2 + samples/README.md | 55 ++++ samples/kotlin-mcp-client/README.md | 73 ++--- samples/kotlin-mcp-server/README.md | 112 +++---- samples/notebooks/README.md | 31 ++ samples/simple-streamable-server/README.md | 75 +++++ .../simple-streamable-server/build.gradle.kts | 29 ++ .../gradle.properties | 5 + .../gradle/libs.versions.toml | 22 ++ .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 46175 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 + samples/simple-streamable-server/gradlew | 248 +++++++++++++++ samples/simple-streamable-server/gradlew.bat | 93 ++++++ .../settings.gradle.kts | 25 ++ .../sample/server/main.kt | 28 ++ .../sample/server/server.kt | 297 ++++++++++++++++++ samples/weather-stdio-server/README.md | 153 +++------ 20 files changed, 1086 insertions(+), 255 deletions(-) create mode 100644 samples/README.md create mode 100644 samples/notebooks/README.md create mode 100644 samples/simple-streamable-server/README.md create mode 100644 samples/simple-streamable-server/build.gradle.kts create mode 100644 samples/simple-streamable-server/gradle.properties create mode 100644 samples/simple-streamable-server/gradle/libs.versions.toml create mode 100644 samples/simple-streamable-server/gradle/wrapper/gradle-wrapper.jar create mode 100644 samples/simple-streamable-server/gradle/wrapper/gradle-wrapper.properties create mode 100755 samples/simple-streamable-server/gradlew create mode 100644 samples/simple-streamable-server/gradlew.bat create mode 100644 samples/simple-streamable-server/settings.gradle.kts create mode 100644 samples/simple-streamable-server/src/main/kotlin/io/modelcontextprotocol/sample/server/main.kt create mode 100644 samples/simple-streamable-server/src/main/kotlin/io/modelcontextprotocol/sample/server/server.kt diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2b769d1..f97721b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -180,6 +180,7 @@ jobs: sample: - kotlin-mcp-client - kotlin-mcp-server + - simple-streamable-server - weather-stdio-server name: "Build Sample: ${{ matrix.sample }}" diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index cdd98bb..373f00a 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -29,6 +29,7 @@ jobs: sample: - kotlin-mcp-client - kotlin-mcp-server + - simple-streamable-server - weather-stdio-server name: Build Sample (${{ matrix.sample }}) diff --git a/README.md b/README.md index 30d1b40..acb5c05 100644 --- a/README.md +++ b/README.md @@ -21,34 +21,34 @@ standardized protocol interface. * [Overview](#overview) * [Installation](#installation) - * [Artifacts](#artifacts) - * [Gradle setup (JVM)](#gradle-setup-jvm) - * [Multiplatform](#multiplatform) - * [Ktor dependencies](#ktor-dependencies) + * [Artifacts](#artifacts) + * [Gradle setup (JVM)](#gradle-setup-jvm) + * [Multiplatform](#multiplatform) + * [Ktor dependencies](#ktor-dependencies) * [Quickstart](#quickstart) - * [Creating a Client](#creating-a-client) - * [Creating a Server](#creating-a-server) + * [Creating a Client](#creating-a-client) + * [Creating a Server](#creating-a-server) * [Core Concepts](#core-concepts) - * [MCP Primitives](#mcp-primitives) - * [Capabilities](#capabilities) - * [Server Capabilities](#server-capabilities) - * [Client Capabilities](#client-capabilities) - * [Server Features](#server-features) - * [Prompts](#prompts) - * [Resources](#resources) - * [Tools](#tools) - * [Completion](#completion) - * [Logging](#logging) - * [Pagination](#pagination) - * [Client Features](#client-features) - * [Roots](#roots) - * [Sampling](#sampling) + * [MCP Primitives](#mcp-primitives) + * [Capabilities](#capabilities) + * [Server Capabilities](#server-capabilities) + * [Client Capabilities](#client-capabilities) + * [Server Features](#server-features) + * [Prompts](#prompts) + * [Resources](#resources) + * [Tools](#tools) + * [Completion](#completion) + * [Logging](#logging) + * [Pagination](#pagination) + * [Client Features](#client-features) + * [Roots](#roots) + * [Sampling](#sampling) * [Transports](#transports) - * [STDIO Transport](#stdio-transport) - * [Streamable HTTP Transport](#streamable-http-transport) - * [SSE Transport](#sse-transport) - * [WebSocket Transport](#websocket-transport) - * [ChannelTransport (testing)](#channeltransport-testing) + * [STDIO Transport](#stdio-transport) + * [Streamable HTTP Transport](#streamable-http-transport) + * [SSE Transport](#sse-transport) + * [WebSocket Transport](#websocket-transport) + * [ChannelTransport (testing)](#channeltransport-testing) * [Connecting your server](#connecting-your-server) * [Examples](#examples) * [Documentation](#documentation) @@ -183,17 +183,24 @@ fun main(args: Array) = runBlocking { ### Creating a Server -Create an MCP server that exposes a simple tool and runs on an embedded Ktor server with SSE transport: +Create an MCP server that exposes a simple tool and runs on an embedded Ktor server with Streamable HTTP transport. +For a full working project with all required dependencies, see +the [simple-streamable-server](samples/simple-streamable-server) sample. + ```kotlin +import io.ktor.serialization.kotlinx.json.json +import io.ktor.server.application.install import io.ktor.server.cio.CIO import io.ktor.server.engine.embeddedServer +import io.ktor.server.plugins.contentnegotiation.ContentNegotiation import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions -import io.modelcontextprotocol.kotlin.sdk.server.mcp +import io.modelcontextprotocol.kotlin.sdk.server.mcpStreamableHttp import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.McpJson import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.types.TextContent import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema @@ -228,7 +235,10 @@ fun main(args: Array) { } embeddedServer(CIO, host = "127.0.0.1", port = port) { - mcp { + install(ContentNegotiation) { + json(McpJson) + } + mcpStreamableHttp { mcpServer } }.start(wait = true) @@ -243,7 +253,7 @@ You can run the server and then connect to it using the client or test with the npx -y @modelcontextprotocol/inspector ``` -In the inspector UI, connect to `http://localhost:3000`. +In the inspector UI, connect to `http://localhost:3000/mcp`. ## Core Concepts @@ -787,6 +797,7 @@ private class MyServer : fun main() { --> + ```kotlin embeddedServer(CIO, port = 3000) { mcpStreamableHttp(path = "/api/mcp") { @@ -794,6 +805,7 @@ embeddedServer(CIO, port = 3000) { } }.start(wait = true) ``` + @@ -831,6 +843,7 @@ private class MyServer : fun main() { --> + ```kotlin embeddedServer(CIO, port = 3000) { install(SSE) @@ -841,6 +854,7 @@ embeddedServer(CIO, port = 3000) { } }.start(wait = true) ``` + @@ -880,12 +894,9 @@ allowing for easy testing of MCP functionality without the need for network setu ## Examples -| Scenario | Description | Example | -|--------------------------|-----------------------------------------------------------------|--------------------------------------------------------------------------| -| Streamable HTTP server | Full MCP server with prompts, resources, tools, completions | [samples/kotlin-mcp-server](./samples/kotlin-mcp-server) | -| STDIO weather server | Minimal STDIO transport server exposing weather info and alerts | [samples/weather-stdio-server](./samples/weather-stdio-server) | -| Interactive STDIO client | MCP client that connects over STDIO and pipes requests to LLMs | [samples/kotlin-mcp-client](./samples/kotlin-mcp-client) | -| Streamable HTTP client | MCP client demo in a runnable notebook | [samples/notebooks/McpClient.ipynb](./samples/notebooks/McpClient.ipynb) | +The [samples](./samples) directory contains runnable projects demonstrating +MCP server and client implementations with various transports. +See the [samples overview](./samples/README.md) for a comparison table and detailed descriptions. ## Documentation @@ -899,4 +910,5 @@ Please see the [contribution guide](CONTRIBUTING.md) and the [Code of conduct](C ## License -This project is licensed under Apache 2.0 for new contributions, with existing code under MIT—see the [LICENSE](LICENSE) file for details. +This project is licensed under Apache 2.0 for new contributions, with existing code under MIT—see the [LICENSE](LICENSE) +file for details. diff --git a/docs/build.gradle.kts b/docs/build.gradle.kts index ac24979..1857ef1 100644 --- a/docs/build.gradle.kts +++ b/docs/build.gradle.kts @@ -6,6 +6,8 @@ plugins { dependencies { implementation(project(":kotlin-sdk")) implementation(libs.ktor.server.cio) + implementation(libs.ktor.serialization) + implementation(libs.ktor.server.content.negotiation) } tasks.matching { diff --git a/samples/README.md b/samples/README.md new file mode 100644 index 0000000..9789378 --- /dev/null +++ b/samples/README.md @@ -0,0 +1,55 @@ +# Kotlin MCP SDK Samples + +Runnable projects demonstrating MCP server and client implementations with the +[Kotlin MCP SDK](https://github.com/modelcontextprotocol/kotlin-sdk). +For background on the protocol itself, see the [MCP documentation](https://modelcontextprotocol.io/introduction). + +## Overview + +| Sample | Type | Transport | MCP Features | +|--------------------------------------------------------|-------------------|-----------------|------------------------------------| +| [simple-streamable-server](./simple-streamable-server) | Server | Streamable HTTP | Tools, Resources, Prompts, Logging | +| [kotlin-mcp-server](./kotlin-mcp-server) | Server | STDIO, SSE | Tools, Resources, Prompts | +| [weather-stdio-server](./weather-stdio-server) | Server | STDIO | Tools | +| [kotlin-mcp-client](./kotlin-mcp-client) | Client | STDIO | Tool discovery & invocation | +| [notebooks](./notebooks) | Client (Notebook) | Streamable HTTP | Tool discovery & invocation | + +## Getting Started + +- **Building a server?** Start with [simple-streamable-server](./simple-streamable-server) — it + uses the recommended Streamable HTTP transport and covers tools, resources, prompts, and logging. +- **Building a client?** Open the [notebooks](./notebooks) sample for a step-by-step walkthrough, + or see [kotlin-mcp-client](./kotlin-mcp-client) for a full CLI client with Anthropic API + integration. + +## Samples + +### Simple Streamable HTTP Server + +A minimal Streamable HTTP server with optional Bearer token authentication. Demonstrates tools +(`greet`, `multi-greet`), a prompt template, a resource, and server-to-client logging notifications. +[Read more →](./simple-streamable-server) + +### Kotlin MCP Server + +A multi-transport server supporting STDIO, SSE (plain), and SSE (Ktor plugin). Useful for exploring +different transport modes side by side. +[Read more →](./kotlin-mcp-server) + +### Weather STDIO Server + +A focused STDIO server that exposes weather forecast and alert tools backed by the weather.gov API. +Includes Claude Desktop integration instructions. +[Read more →](./weather-stdio-server) + +### Kotlin MCP Client + +An interactive CLI client that connects to any MCP server over STDIO and routes queries through +Anthropic's Claude API, bridging MCP tools with LLM conversations. +[Read more →](./kotlin-mcp-client) + +### MCP Client Notebook + +A Kotlin notebook that connects to a remote MCP server via Streamable HTTP and demonstrates ping, +tool listing, and tool invocation — all in an interactive cell-by-cell format. +[Read more →](./notebooks) diff --git a/samples/kotlin-mcp-client/README.md b/samples/kotlin-mcp-client/README.md index fd9e143..b10f925 100644 --- a/samples/kotlin-mcp-client/README.md +++ b/samples/kotlin-mcp-client/README.md @@ -1,69 +1,50 @@ # Kotlin MCP Client -This project demonstrates how to build a Model Context Protocol (MCP) client in Kotlin that interacts with an MCP server -via a STDIO transport layer while leveraging Anthropic's API for natural language processing. The client uses the MCP -Kotlin SDK to communicate with an MCP server that exposes various tools, and it uses Anthropic's API to process user -queries and integrate tool responses into the conversation. - -For more information about the MCP SDK and protocol, please refer to -the [MCP documentation](https://modelcontextprotocol.io/introduction). - -## Prerequisites - -- **Java 17 or later** -- **Gradle** (or the Gradle wrapper provided with the project) -- An Anthropic API key set in your environment variable `ANTHROPIC_API_KEY` -- Basic understanding of MCP concepts and Kotlin programming +An interactive CLI client that connects to any MCP server over STDIO and pipes queries through +Anthropic's Claude API. ## Overview -The client application performs the following tasks: +This sample demonstrates a complete MCP client workflow: launching an MCP server as a subprocess, +discovering its tools, converting them to Anthropic's tool format, and running an interactive chat +loop where Claude can call server tools on behalf of the user. -- **Connecting to an MCP server** — - launches an MCP server process (implemented in JavaScript, Python, or Java) using STDIO transport. - It connects to the server, retrieves available tools, and converts them to Anthropic’s tool format. -- **Processing queries** — - accepts user queries, sends them to Anthropic’s API along with the registered tools, and handles responses. - If the response indicates a tool should be called, it invokes the corresponding MCP tool and continues the - conversation based on the tool’s result. -- **Interactive chat loop** — - runs an interactive command-line loop, allowing users to continuously submit queries and receive responses. +## Prerequisites -## Building and Running +- JDK 17+ +- An `ANTHROPIC_API_KEY` environment variable set with a valid Anthropic API key +- An MCP server script to connect to (`.js`, `.py`, or `.jar`) -Use the Gradle wrapper to build the application. In a terminal, run: +## Build & Run + +Run the client, passing the path to an MCP server: ```shell -./gradlew clean build -``` +# Connect to a JVM server +./gradlew run --args="path/to/server.jar" -To run the client, execute the jar file and provide the path to your MCP server script. +# Connect to a Python server +./gradlew run --args="path/to/server.py" -To run the client with any MCP server: - -```shell -java -jar build/libs/.jar path/to/server.jar # jvm server -java -jar build/libs/.jar path/to/server.py # python server -java -jar build/libs/.jar path/to/build/index.js # node server +# Connect to a Node.js server +./gradlew run --args="path/to/build/index.js" ``` > [!NOTE] -> The client uses STDIO transport, so it launches the MCP server as a separate process. +> The client uses STDIO transport, so it launches the MCP server as a subprocess. > Ensure the server script is executable and is a valid `.js`, `.py`, or `.jar` file. -## Configuration for Anthropic +## MCP Capabilities -Ensure your Anthropic API key is available in your environment: +From the **client** perspective, this sample demonstrates: -```shell -export ANTHROPIC_API_KEY=your_anthropic_api_key_here -``` - -The client uses `AnthropicOkHttpClient.fromEnv()` to automatically load the API key from `ANTHROPIC_API_KEY` and -`ANTHROPIC_AUTH_TOKEN` environment variables. +- **Tool discovery** — lists tools from the connected server and converts them to Anthropic's tool + format. +- **Tool invocation** — when Claude's response requests a tool call, the client invokes the + corresponding MCP tool and feeds the result back into the conversation. ## Additional Resources -- [MCP Specification](https://spec.modelcontextprotocol.io/) +- [MCP Specification](https://modelcontextprotocol.io/specification/latest) - [Kotlin MCP SDK](https://github.com/modelcontextprotocol/kotlin-sdk) -- [Anthropic Java SDK](https://github.com/anthropics/anthropic-sdk-java/tree/main) +- [Anthropic Java SDK](https://github.com/anthropics/anthropic-sdk-java) diff --git a/samples/kotlin-mcp-server/README.md b/samples/kotlin-mcp-server/README.md index fa56b66..82dc5e7 100644 --- a/samples/kotlin-mcp-server/README.md +++ b/samples/kotlin-mcp-server/README.md @@ -1,93 +1,85 @@ -# MCP Kotlin Server Sample +# Kotlin MCP Server -A sample implementation of an MCP (Model Context Protocol) server in Kotlin that demonstrates different server -configurations and transport methods. +A sample MCP server demonstrating multiple transport modes: STDIO, SSE (plain), and SSE (Ktor +plugin). -## Features +> **Note:** The SSE transport modes are provided for backward compatibility. For new projects, +> consider using the [simple-streamable-server](../simple-streamable-server) sample, which uses the +> recommended Streamable HTTP transport. -- Multiple server operation modes: - - Standard I/O server - - SSE (Server-Sent Events) server with plain configuration - - SSE server using Ktor plugin -- Built-in capabilities for: - - Prompts management - - Resources handling - - Tools integration +## Overview -## Getting Started +This sample registers a prompt, a tool, and a resource, then lets you choose how to expose them. +STDIO mode is the default and is best for process-based clients. The two SSE modes show how to +serve MCP over HTTP using either a manual Ktor routing setup or the built-in `mcp { }` Ktor plugin. -### Running the Server +## Prerequisites -The server defaults [STDIO transport](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#stdio). +- JDK 17+ +- [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) (optional, for testing) -You can customize the behavior using command-line arguments. -Logs are printed to [./build/stdout.log](./build/stdout.log) +## Build & Run -#### Standard I/O mode (STDIO): - -```bash -./gradlew clean build -``` -Use the [MCP inspector](https://modelcontextprotocol.io/docs/tools/inspector) -to connect to MCP via STDIO (Click the "▶️ Connect" button): +### STDIO mode (default) ```shell -npx @modelcontextprotocol/inspector --config mcp-inspector-config.json --server stdio-server +./gradlew run ``` -#### SSE with plain configuration: +Or explicitly: -**NB!: 🐞 This configuration may not work ATM** - -```bash -./gradlew run --args="--sse-server 3001" -``` -or ```shell -./gradlew clean build -java -jar ./build/libs/kotlin-mcp-server-0.1.0-all.jar --sse-server 3001 +./gradlew run --args="--stdio" ``` -Use the [MCP inspector](https://modelcontextprotocol.io/docs/tools/inspector) -to connect to `http://localhost:3002/` via SSE Transport (Click the "▶️ Connect" button): +Connect with the MCP Inspector: + ```shell -npx @modelcontextprotocol/inspector --config mcp-inspector-config.json --server sse-server +npx @modelcontextprotocol/inspector --config samples/kotlin-mcp-server/mcp-inspector-config.json --server stdio-server ``` -#### SSE with Ktor plugin: +### SSE with Ktor plugin -```bash +```shell ./gradlew run --args="--sse-server-ktor 3002" ``` -or + +Connect with the MCP Inspector: + ```shell -./gradlew clean build -java -jar ./build/libs/kotlin-mcp-server-0.1.0-all.jar --sse-server-ktor 3002 +npx @modelcontextprotocol/inspector --config samples/kotlin-mcp-server/mcp-inspector-config.json --server sse-ktor-server ``` -Use the [MCP inspector](https://modelcontextprotocol.io/docs/tools/inspector) -to connect to `http://localhost:3002/` via SSE transport (Click the "▶️ Connect" button): +### SSE with plain configuration + +> **Known issue:** This mode may not work correctly at this time. + ```shell -npx @modelcontextprotocol/inspector --config mcp-inspector-config.json --server sse-ktor-server +./gradlew run --args="--sse-server 3001" ``` -## Server Capabilities +Connect with the MCP Inspector: -- **Prompts**: Supports prompt management with list change notifications -- **Resources**: Includes subscription support and list change notifications -- **Tools**: Supports tool management with list change notifications +```shell +npx @modelcontextprotocol/inspector --config samples/kotlin-mcp-server/mcp-inspector-config.json --server sse-server +``` -## Implementation Details +## MCP Capabilities -The server is implemented using: -- Ktor for HTTP server functionality (SSE modes) -- Kotlin coroutines for asynchronous operations -- SSE for real-time communication in web contexts -- Standard I/O for command-line interface and process-based communication +### Tools -## Example Capabilities +| Name | Description | +|-------------------|------------------------------------------------------| +| `kotlin-sdk-tool` | A test tool that returns a "Hello, world!" greeting. | -The sample server demonstrates: -- **Prompt**: "Kotlin Developer" - helps develop small Kotlin applications with a configurable project name -- **Tool**: "kotlin-sdk-tool" - a simple test tool that returns a greeting -- **Resource**: "Web Search" - a placeholder resource demonstrating resource handling +### Prompts + +| Name | Description | +|--------------------|------------------------------------------------------------------------------------| +| `Kotlin Developer` | Generates a prompt to develop a small Kotlin application for a given project name. | + +### Resources + +| Name | URI | Description | +|--------------|-----------------------|---------------------------------------------------------| +| `Web Search` | `https://search.com/` | A placeholder resource demonstrating resource handling. | diff --git a/samples/notebooks/README.md b/samples/notebooks/README.md new file mode 100644 index 0000000..8db4f3b --- /dev/null +++ b/samples/notebooks/README.md @@ -0,0 +1,31 @@ +# MCP Client Notebook + +An interactive Kotlin notebook that demonstrates connecting to a remote MCP server and calling tools. + +## Overview + +This notebook walks through building an MCP client step by step: creating a Ktor HTTP client, +initializing an MCP `Client`, connecting via `StreamableHttpClientTransport`, and interacting with +the server (ping, list tools, call tools). It connects to the public +[Microsoft Learn MCP Server](https://learn.microsoft.com/api/mcp) as an example. + +## Prerequisites + +- [Kotlin Jupyter kernel](https://github.com/Kotlin/kotlin-jupyter) **or** IntelliJ IDEA with the + [Kotlin Notebook plugin](https://plugins.jetbrains.com/plugin/16340-kotlin-notebook) +- Internet access (the notebook connects to an external MCP server) + +## How to Run + +1. Open `McpClient.ipynb` in IntelliJ IDEA (with Kotlin Notebook plugin) or in Jupyter with the + Kotlin kernel installed. +2. Run cells sequentially from top to bottom. + +## What It Demonstrates + +- Adding MCP SDK dependencies in a notebook environment +- Creating a Ktor `HttpClient` with SSE and logging plugins +- Creating an MCP `Client` and connecting via `StreamableHttpClientTransport` +- Sending a `ping` request +- Listing available tools from the remote server +- Calling a tool (`microsoft_docs_search`) and displaying results \ No newline at end of file diff --git a/samples/simple-streamable-server/README.md b/samples/simple-streamable-server/README.md new file mode 100644 index 0000000..27856d7 --- /dev/null +++ b/samples/simple-streamable-server/README.md @@ -0,0 +1,75 @@ +# Simple Streamable HTTP Server + +A minimal MCP server using the recommended Streamable HTTP transport with optional Bearer token authentication. + +## Overview + +This sample demonstrates a Streamable HTTP MCP server built with Ktor. It exposes tools, a prompt +template, and a resource over HTTP, and optionally supports Bearer token authentication. The +`multi-greet` tool showcases server-to-client logging notifications with streaming delays. + +## Prerequisites + +- JDK 17+ + +## Build & Run + +### Without authentication + +```shell +./gradlew run +``` + +The server starts on `http://localhost:3001/mcp` by default. + +Connect with the [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector): + +```shell +npx @modelcontextprotocol/inspector +``` + +In the Inspector UI, select **Streamable HTTP** transport and enter `http://localhost:3001/mcp`. + +Pass a port number as an argument to change it: + +```shell +./gradlew run --args="8080" +``` + +### With authentication + +```shell +MCP_AUTH_TOKEN=my-secret ./gradlew run --args="--auth" +``` + +When `--auth` is passed, clients must include an `Authorization: Bearer ` header. +The token is read from the `MCP_AUTH_TOKEN` environment variable (required when `--auth` is used). + +### Authentication caveats + +This sample is intended **for demonstration only**. In production you should: + +- Use a proper identity provider instead of a static token. +- Restrict CORS origins to your deployment domain — the sample allows all origins. +- Serve the endpoint over HTTPS. + +## MCP Capabilities + +### Tools + +| Name | Description | +|------|-------------| +| `greet` | Returns a greeting for a given `name`. | +| `multi-greet` | Sends logging notifications between delayed greetings, demonstrating streaming. | + +### Prompts + +| Name | Description | +|------|-------------| +| `greeting-template` | Generates a friendly greeting message for a given `name`. | + +### Resources + +| Name | URI | Description | +|------|-----|-------------| +| `Default Greeting` | `https://example.com/greetings/default` | Returns a static "Hello, world!" text. | \ No newline at end of file diff --git a/samples/simple-streamable-server/build.gradle.kts b/samples/simple-streamable-server/build.gradle.kts new file mode 100644 index 0000000..8cb908e --- /dev/null +++ b/samples/simple-streamable-server/build.gradle.kts @@ -0,0 +1,29 @@ +plugins { + alias(libs.plugins.kotlin.jvm) + alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.shadow) + application +} + +group = "org.example" +version = "0.1.0" + +application { + mainClass.set("io.modelcontextprotocol.sample.server.MainKt") +} + +dependencies { + implementation(dependencies.platform(libs.ktor.bom)) + implementation(libs.mcp.kotlin.server) + implementation(libs.ktor.server.netty) + implementation(libs.ktor.server.auth) + implementation(libs.ktor.server.cors) + implementation(libs.ktor.server.content.negotiation) + implementation(libs.ktor.server.sse) + implementation(libs.ktor.serialization.kotlinx.json) + implementation(libs.slf4j.simple) +} + +kotlin { + jvmToolchain(17) +} diff --git a/samples/simple-streamable-server/gradle.properties b/samples/simple-streamable-server/gradle.properties new file mode 100644 index 0000000..32cead6 --- /dev/null +++ b/samples/simple-streamable-server/gradle.properties @@ -0,0 +1,5 @@ +org.gradle.configuration-cache=true +org.gradle.parallel=true +org.gradle.caching=true + +#mcp.kotlin.overrideVersion=0.0.1-SNAPSHOT diff --git a/samples/simple-streamable-server/gradle/libs.versions.toml b/samples/simple-streamable-server/gradle/libs.versions.toml new file mode 100644 index 0000000..7240ff2 --- /dev/null +++ b/samples/simple-streamable-server/gradle/libs.versions.toml @@ -0,0 +1,22 @@ +[versions] +kotlin = "2.2.21" +ktor = "3.2.3" +mcp-kotlin = "0.9.0" +slf4j = "2.0.17" +shadow = "9.2.2" + +[libraries] +ktor-bom = { group = "io.ktor", name = "ktor-bom", version.ref = "ktor" } +ktor-server-netty = { group = "io.ktor", name = "ktor-server-netty" } +ktor-server-cors = { group = "io.ktor", name = "ktor-server-cors" } +ktor-server-content-negotiation = { group = "io.ktor", name = "ktor-server-content-negotiation" } +ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json" } +ktor-server-auth = { group = "io.ktor", name = "ktor-server-auth" } +ktor-server-sse = { group = "io.ktor", name = "ktor-server-sse" } +mcp-kotlin-server = { group = "io.modelcontextprotocol", name = "kotlin-sdk-server", version.ref = "mcp-kotlin" } +slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" } + +[plugins] +kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } +shadow = { id = "com.gradleup.shadow", version.ref = "shadow" } diff --git a/samples/simple-streamable-server/gradle/wrapper/gradle-wrapper.jar b/samples/simple-streamable-server/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..61285a659d17295f1de7c53e24fdf13ad755c379 GIT binary patch literal 46175 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

pVX@HRij*jw$g4?}Pn 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 literal 0 HcmV?d00001 diff --git a/samples/simple-streamable-server/gradle/wrapper/gradle-wrapper.properties b/samples/simple-streamable-server/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..37f78a6 --- /dev/null +++ b/samples/simple-streamable-server/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/samples/simple-streamable-server/gradlew b/samples/simple-streamable-server/gradlew new file mode 100755 index 0000000..adff685 --- /dev/null +++ b/samples/simple-streamable-server/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/samples/simple-streamable-server/gradlew.bat b/samples/simple-streamable-server/gradlew.bat new file mode 100644 index 0000000..c4bdd3a --- /dev/null +++ b/samples/simple-streamable-server/gradlew.bat @@ -0,0 +1,93 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/samples/simple-streamable-server/settings.gradle.kts b/samples/simple-streamable-server/settings.gradle.kts new file mode 100644 index 0000000..3c36808 --- /dev/null +++ b/samples/simple-streamable-server/settings.gradle.kts @@ -0,0 +1,25 @@ +rootProject.name = "simple-streamable-server" + +plugins { + // Apply the foojay-resolver plugin to allow automatic download of JDKs + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" +} + +dependencyResolutionManagement { + repositories { + mavenLocal() + mavenCentral() + } + + versionCatalogs { + create("libs") { + val mcpKotlinVersion = providers.gradleProperty( + "mcp.kotlin.overrideVersion", + ).orNull + if (mcpKotlinVersion != null) { + logger.lifecycle("Using the override version $mcpKotlinVersion of MCP Kotlin SDK") + version("mcp-kotlin", mcpKotlinVersion) + } + } + } +} diff --git a/samples/simple-streamable-server/src/main/kotlin/io/modelcontextprotocol/sample/server/main.kt b/samples/simple-streamable-server/src/main/kotlin/io/modelcontextprotocol/sample/server/main.kt new file mode 100644 index 0000000..161edc7 --- /dev/null +++ b/samples/simple-streamable-server/src/main/kotlin/io/modelcontextprotocol/sample/server/main.kt @@ -0,0 +1,28 @@ +package io.modelcontextprotocol.sample.server + +import io.ktor.server.engine.embeddedServer +import io.ktor.server.netty.Netty + +fun main(vararg args: String) { + val authEnabled = args.any { it == "--auth" } + val port = args.firstOrNull { it != "--auth" }?.toIntOrNull() ?: 3001 + val authToken = if (authEnabled) { + val envToken = System.getenv("MCP_AUTH_TOKEN") + requireNotNull(envToken) { + "MCP_AUTH_TOKEN environment variable must be set when using --auth" + } + envToken + } else { + null + } + + println("Starting MCP Streamable HTTP server on port $port") + println("Use MCP inspector to connect to http://localhost:$port/mcp") + if (authToken != null) { + println("Bearer auth enabled (token sourced from MCP_AUTH_TOKEN)") + } + + embeddedServer(Netty, host = "127.0.0.1", port = port) { + configureServer(authToken) + }.start(wait = true) +} diff --git a/samples/simple-streamable-server/src/main/kotlin/io/modelcontextprotocol/sample/server/server.kt b/samples/simple-streamable-server/src/main/kotlin/io/modelcontextprotocol/sample/server/server.kt new file mode 100644 index 0000000..cdffdae --- /dev/null +++ b/samples/simple-streamable-server/src/main/kotlin/io/modelcontextprotocol/sample/server/server.kt @@ -0,0 +1,297 @@ +package io.modelcontextprotocol.sample.server + +import io.ktor.http.HttpHeaders +import io.ktor.http.HttpMethod +import io.ktor.http.HttpStatusCode +import io.ktor.serialization.kotlinx.json.json +import io.ktor.server.application.Application +import io.ktor.server.application.ApplicationCall +import io.ktor.server.application.install +import io.ktor.server.auth.Authentication +import io.ktor.server.auth.authenticate +import io.ktor.server.auth.bearer +import io.ktor.server.plugins.contentnegotiation.ContentNegotiation +import io.ktor.server.plugins.cors.routing.CORS +import io.ktor.server.request.header +import io.ktor.server.response.respond +import io.ktor.server.routing.delete +import io.ktor.server.routing.post +import io.ktor.server.routing.route +import io.ktor.server.routing.routing +import io.ktor.server.sse.SSE +import io.ktor.server.sse.sse +import io.ktor.util.collections.ConcurrentMap +import io.modelcontextprotocol.kotlin.sdk.server.Server +import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions +import io.modelcontextprotocol.kotlin.sdk.server.StreamableHttpServerTransport +import io.modelcontextprotocol.kotlin.sdk.server.mcpStreamableHttp +import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel +import io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotification +import io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotificationParams +import io.modelcontextprotocol.kotlin.sdk.types.McpJson +import io.modelcontextprotocol.kotlin.sdk.types.PromptArgument +import io.modelcontextprotocol.kotlin.sdk.types.PromptMessage +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult +import io.modelcontextprotocol.kotlin.sdk.types.Role +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents +import io.modelcontextprotocol.kotlin.sdk.types.ToolAnnotations +import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema +import kotlinx.coroutines.delay +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import kotlinx.serialization.json.putJsonObject +import kotlin.time.Duration.Companion.milliseconds + +private const val MCP_SESSION_ID_HEADER = "mcp-session-id" + +fun Application.configureServer(authToken: String? = null) { + installCors(authEnabled = authToken != null) + install(ContentNegotiation) { + json(McpJson) + } + + if (authToken == null) { + mcpStreamableHttp { + createMcpServer() + } + } else { + configureAuthenticatedMcp(authToken) + } +} + +private fun Application.configureAuthenticatedMcp(authToken: String) { + install(SSE) + install(Authentication) { + bearer("mcp-bearer") { + authenticate { credential -> + if (credential.token == authToken) { + io.ktor.server.auth.UserIdPrincipal("mcp-client") + } else { + null + } + } + } + } + + val transports = ConcurrentMap() + + routing { + authenticate("mcp-bearer") { + route("/mcp") { + sse { + val transport = findTransport(call, transports) ?: return@sse + transport.handleRequest(this, call) + } + + post { + val transport = getOrCreateTransport(call, transports) ?: return@post + transport.handleRequest(null, call) + } + + delete { + val transport = findTransport(call, transports) ?: return@delete + transport.handleRequest(null, call) + } + } + } + } +} + +private suspend fun findTransport( + call: ApplicationCall, + transports: ConcurrentMap, +): StreamableHttpServerTransport? { + val sessionId = call.request.header(MCP_SESSION_ID_HEADER) + if (sessionId.isNullOrEmpty()) { + call.respond(HttpStatusCode.BadRequest, "Bad Request: No valid session ID provided") + return null + } + val transport = transports[sessionId] + if (transport == null) { + call.respond(HttpStatusCode.NotFound, "Session not found") + return null + } + return transport +} + +private suspend fun getOrCreateTransport( + call: ApplicationCall, + transports: ConcurrentMap, +): StreamableHttpServerTransport? { + val sessionId = call.request.header(MCP_SESSION_ID_HEADER) + if (sessionId != null) { + val transport = transports[sessionId] + if (transport == null) { + call.respond(HttpStatusCode.NotFound, "Session not found") + } + return transport + } + + val configuration = StreamableHttpServerTransport.Configuration( + enableJsonResponse = true, + ) + val transport = StreamableHttpServerTransport(configuration) + + transport.setOnSessionInitialized { initializedSessionId -> + transports[initializedSessionId] = transport + } + transport.setOnSessionClosed { closedSessionId -> + transports.remove(closedSessionId) + } + + val server = createMcpServer() + server.onClose { + transport.sessionId?.let { transports.remove(it) } + } + server.createSession(transport) + + return transport +} + +private fun Application.installCors(authEnabled: Boolean = false) { + install(CORS) { + anyHost() // Don't do this in production if possible. Try to limit it. + allowMethod(HttpMethod.Options) + allowMethod(HttpMethod.Get) + allowMethod(HttpMethod.Post) + allowMethod(HttpMethod.Delete) + allowNonSimpleContentTypes = true + allowHeader("Mcp-Session-Id") + allowHeader("Mcp-Protocol-Version") + exposeHeader("Mcp-Session-Id") + exposeHeader("Mcp-Protocol-Version") + if (authEnabled) { + allowHeader(HttpHeaders.Authorization) + } + } +} + +private fun createMcpServer(): Server { + val server = Server( + Implementation( + name = "simple-streamable-http-server", + version = "1.0.0", + ), + ServerOptions( + capabilities = ServerCapabilities( + prompts = ServerCapabilities.Prompts(listChanged = true), + resources = ServerCapabilities.Resources(subscribe = true, listChanged = true), + tools = ServerCapabilities.Tools(listChanged = true), + logging = ServerCapabilities.Logging, + ), + ), + ) + + // Tool: greet + server.addTool( + name = "greet", + description = "A simple greeting tool", + inputSchema = ToolSchema( + properties = buildJsonObject { + putJsonObject("name") { + put("type", "string") + put("description", "Name to greet") + } + }, + required = listOf("name"), + ), + ) { request -> + val name = request.arguments?.get("name")?.jsonPrimitive?.content ?: "World" + CallToolResult(content = listOf(TextContent("Hello, $name!"))) + } + + // Tool: multi-greet (demonstrates logging notifications) + server.addTool( + name = "multi-greet", + description = "A tool that sends different greetings with delays between them", + inputSchema = ToolSchema( + properties = buildJsonObject { + putJsonObject("name") { + put("type", "string") + put("description", "Name to greet") + } + }, + required = listOf("name"), + ), + toolAnnotations = ToolAnnotations(readOnlyHint = true, openWorldHint = false), + ) { request -> + val name = request.arguments?.get("name")?.jsonPrimitive?.content ?: "World" + + sendLoggingMessage( + LoggingMessageNotification( + LoggingMessageNotificationParams( + level = LoggingLevel.Debug, + data = JsonPrimitive("Starting multi-greet for $name") + ) + ) + ) + delay(1000.milliseconds) + + sendLoggingMessage( + LoggingMessageNotification( + LoggingMessageNotificationParams( + level = LoggingLevel.Info, + data = JsonPrimitive("Sending first greeting to $name") + ) + ) + ) + delay(1000.milliseconds) + + sendLoggingMessage( + LoggingMessageNotification( + LoggingMessageNotificationParams( + level = LoggingLevel.Info, + data = JsonPrimitive("Sending second greeting to $name") + ) + ) + ) + + CallToolResult(content = listOf(TextContent("Good morning, $name!"))) + } + + // Prompt: greeting-template + server.addPrompt( + name = "greeting-template", + description = "A simple greeting prompt template", + arguments = listOf( + PromptArgument( + name = "name", + description = "Name to include in greeting", + required = true, + ), + ), + ) { request -> + val name = request.arguments?.get("name") ?: "World" + GetPromptResult( + messages = listOf( + PromptMessage( + role = Role.User, + content = TextContent("Please greet $name in a friendly manner."), + ), + ), + ) + } + + // Resource: greeting-resource + server.addResource( + uri = "https://example.com/greetings/default", + name = "Default Greeting", + description = "A simple greeting resource", + mimeType = "text/plain", + ) { request -> + ReadResourceResult( + contents = listOf( + TextResourceContents("Hello, world!", request.uri, "text/plain"), + ), + ) + } + + return server +} diff --git a/samples/weather-stdio-server/README.md b/samples/weather-stdio-server/README.md index 0e8efc0..4c13040 100644 --- a/samples/weather-stdio-server/README.md +++ b/samples/weather-stdio-server/README.md @@ -1,140 +1,67 @@ -# Kotlin MCP Weather STDIO Server +# Weather STDIO Server -This project demonstrates how to build a Model Context Protocol (MCP) server in Kotlin that provides weather-related -tools by consuming the National Weather Service (weather.gov) API. The server uses STDIO as the transport layer and -leverages the Kotlin MCP SDK to expose weather forecast and alert tools. +A minimal MCP server that exposes weather forecast and alert tools using the National Weather +Service API over STDIO transport. -For more information about the MCP SDK and protocol, please refer to -the [MCP documentation](https://modelcontextprotocol.io/introduction). +## Overview + +This sample shows how to build a STDIO-based MCP server in Kotlin. It registers two tools that +query the [weather.gov](https://www.weather.gov/) API — one for weather forecasts by +latitude/longitude and one for active alerts by US state. Because it uses STDIO, the server is +launched as a subprocess by an MCP client or a desktop application like Claude Desktop. ## Prerequisites -- Java 17 or later -- Gradle (or the Gradle wrapper provided with the project) -- Basic understanding of MCP concepts -- Basic understanding of Kotlin and Kotlin ecosystems (sush as kotlinx-serialization, coroutines, ktor) +- JDK 17+ +- Internet access (the server calls the weather.gov API at runtime) -## MCP Weather Server +## Build & Run -The project provides: - -- A lightweight MCP server built with Kotlin. -- STDIO transport layer implementation for server-client communication. -- Two weather tools: - - **Weather Forecast Tool** — returns details such as temperature, wind information, and a detailed forecast for a - given latitude/longitude. - - **Weather Alerts Tool** — returns active weather alerts for a given US state. - -## Building and running - -Use the Gradle wrapper to build the application. In a terminal run: +Run the server (it communicates via stdin/stdout): ```shell -./gradlew clean build +./gradlew run ``` -To run the server: +### MCP Inspector + +Build the fat JAR first, then connect with the +[MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector): ```shell -java -jar build/libs/weather-stdio-server-0.1.0-all.jar +./gradlew build +npx @modelcontextprotocol/inspector -- java -jar samples/weather-stdio-server/build/libs/weather-stdio-server-0.1.0-all.jar ``` -> [!NOTE] -> The server uses STDIO transport, so it is typically launched in an environment where the client connects via standard -> input/output. +### Claude Desktop integration -## Tool Implementation - -The project registers two MCP tools using the Kotlin MCP SDK. Below is an overview of the core tool implementations: - -### 1. Weather Forecast Tool - -This tool fetches the weather forecast for a specific latitude and longitude using the `weather.gov` API. - -Example tool registration in Kotlin: - -```kotlin -server.addTool( - name = "get_forecast", - description = """ - Get weather forecast for a specific latitude/longitude - """.trimIndent(), - inputSchema = Tool.Input( - properties = JsonObject( - mapOf( - "latitude" to JsonObject(mapOf("type" to JsonPrimitive("number"))), - "longitude" to JsonObject(mapOf("type" to JsonPrimitive("number"))), - ) - ), - required = listOf("latitude", "longitude") - ) -) { request -> - // Implementation tool -} -``` - -### 2. Weather Alerts Tool - -This tool retrieves active weather alerts for a US state. - -Example tool registration in Kotlin: - -```kotlin -server.addTool( - name = "get_alerts", - description = """ - Get weather alerts for a US state. Input is Two-letter US state code (e.g. CA, NY) - """.trimIndent(), - inputSchema = Tool.Input( - properties = JsonObject( - mapOf( - "state" to JsonObject( - mapOf( - "type" to JsonPrimitive("string"), - "description" to JsonPrimitive("Two-letter US state code (e.g. CA, NY)") - ) - ), - ) - ), - required = listOf("state") - ) -) { request -> - // Implementation tool -} -``` - -## Client Integration - -### Kotlin Client Example - -Since the server uses STDIO for transport, the client typically connects via standard input/output streams. A sample -client implementation can be found in the tests, demonstrating how to send tool requests and process responses. - -### Claude for Desktop - -To integrate with Claude Desktop, add the following configuration to your Claude Desktop settings: +Add the following to your Claude Desktop configuration: ```json { - "mcpServers": { - "weather": { - "command": "java", - "args": [ - "-jar", - "/absolute/path/to/.jar" - ] + "mcpServers": { + "weather": { + "command": "java", + "args": [ + "-jar", + "/absolute/path/to/samples/weather-stdio-server/build/libs/weather-stdio-server-0.1.0-all.jar" + ] + } } - } } ``` -> [!NOTE] -> Replace `/absolute/path/to/.jar` with the actual absolute path to your built jar file. +## MCP Capabilities + +### Tools + +| Name | Description | +|----------------|------------------------------------------------------------------------------------------| +| `get_forecast` | Returns weather forecast for a given `latitude` / `longitude` using the weather.gov API. | +| `get_alerts` | Returns active weather alerts for a two-letter US `state` code (e.g. `CA`, `NY`). | ## Additional Resources -- [MCP Specification](https://spec.modelcontextprotocol.io/) +- [MCP Specification](https://modelcontextprotocol.io/specification/latest) - [Kotlin MCP SDK](https://github.com/modelcontextprotocol/kotlin-sdk) -- [Ktor Client Documentation](https://ktor.io/docs/welcome.html) -- [Kotlinx Serialization](https://kotlinlang.org/docs/serialization.html) - +- [National Weather Service API](https://www.weather.gov/documentation/services-web-api) From 338f94804f0df9925d4ef48752266f7bf0e1c40b Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Tue, 10 Mar 2026 20:05:44 +0100 Subject: [PATCH 176/449] feat: add tool execution properties to Tool (#567) closes #543 ## How Has This Been Tested? unit ## Breaking Changes binary compatibility in the Tool class ## 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 --------- Co-authored-by: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> --- kotlin-sdk-core/api/kotlin-sdk-core.api | 55 ++++++++++- .../modelcontextprotocol/kotlin/sdk/types.kt | 2 +- .../kotlin/sdk/types/tools.kt | 35 +++++++ .../kotlin/sdk/types/ToolsTest.kt | 96 +++++++++++++++++++ kotlin-sdk-server/api/kotlin-sdk-server.api | 4 +- .../kotlin/sdk/server/Server.kt | 4 + 6 files changed, 188 insertions(+), 8 deletions(-) diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index 0607df5..af22323 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -4335,6 +4335,20 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/SubscribeRequestPara public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/TaskSupport : java/lang/Enum { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/TaskSupport$Companion; + public static final field Forbidden Lio/modelcontextprotocol/kotlin/sdk/types/TaskSupport; + public static final field Optional Lio/modelcontextprotocol/kotlin/sdk/types/TaskSupport; + public static final field Required Lio/modelcontextprotocol/kotlin/sdk/types/TaskSupport; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/TaskSupport; + public static fun values ()[Lio/modelcontextprotocol/kotlin/sdk/types/TaskSupport; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/TaskSupport$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/TextContent : io/modelcontextprotocol/kotlin/sdk/types/MediaContent { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/TextContent$Companion; public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Lkotlinx/serialization/json/JsonObject;)V @@ -4413,8 +4427,8 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/TextResourceContents public final class io/modelcontextprotocol/kotlin/sdk/types/Tool : io/modelcontextprotocol/kotlin/sdk/types/WithMeta { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Tool$Companion; - public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ToolExecution;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ToolExecution;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema; public final fun component3 ()Ljava/lang/String; @@ -4422,12 +4436,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/Tool : io/modelconte public final fun component5 ()Ljava/lang/String; public final fun component6 ()Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations; public final fun component7 ()Ljava/util/List; - public final fun component8 ()Lkotlinx/serialization/json/JsonObject; - public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/Tool; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/Tool;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/Tool; + public final fun component8 ()Lio/modelcontextprotocol/kotlin/sdk/types/ToolExecution; + public final fun component9 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ToolExecution;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/Tool; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/Tool;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ToolExecution;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/Tool; public fun equals (Ljava/lang/Object;)Z public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations; public final fun getDescription ()Ljava/lang/String; + public final fun getExecution ()Lio/modelcontextprotocol/kotlin/sdk/types/ToolExecution; public final fun getIcons ()Ljava/util/List; public final fun getInputSchema ()Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema; public fun getMeta ()Lkotlinx/serialization/json/JsonObject; @@ -4491,6 +4507,35 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations$Comp public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ToolExecution { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ToolExecution$Companion; + public fun ()V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/TaskSupport;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/TaskSupport;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/TaskSupport; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/TaskSupport;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolExecution; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ToolExecution;Lio/modelcontextprotocol/kotlin/sdk/types/TaskSupport;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolExecution; + public fun equals (Ljava/lang/Object;)Z + public final fun getTaskSupport ()Lio/modelcontextprotocol/kotlin/sdk/types/TaskSupport; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ToolExecution$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ToolExecution$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolExecution; + 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/ToolExecution;)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/ToolExecution$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/ToolListChangedNotification : io/modelcontextprotocol/kotlin/sdk/types/ServerNotification { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ToolListChangedNotification$Companion; public fun ()V diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt index 1b43482..3b2b2d2 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt @@ -1618,7 +1618,7 @@ public fun Tool( title, annotations, icons, - _meta, + meta = _meta, ) /** diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt index b7aade5..c121f9d 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt @@ -55,6 +55,8 @@ public fun CallToolResult.Companion.error(content: String, meta: JsonObject? = n * @property icons Optional set of sized icons that clients can display in their user interface. * Clients MUST support at least PNG and JPEG formats. * Clients SHOULD also support SVG and WebP formats. + * @property execution Optional execution-related properties for this tool, + * such as task-augmented execution support. * @property meta Optional metadata for this tool. */ @Serializable @@ -66,6 +68,7 @@ public data class Tool( val title: String? = null, val annotations: ToolAnnotations? = null, val icons: List? = null, + val execution: ToolExecution? = null, @SerialName("_meta") override val meta: JsonObject? = null, ) : WithMeta @@ -92,6 +95,38 @@ public data class ToolSchema( val type: String = "object" } +/** + * Execution-related properties for a tool. + * + * @property taskSupport Indicates whether this tool supports task-augmented execution. + * This allows clients to handle long-running operations through polling the task system. + * If omitted (`null`), consumers should treat it as [TaskSupport.Forbidden] per the spec. + */ +@Serializable +public data class ToolExecution(val taskSupport: TaskSupport? = null) + +/** + * Indicates whether a tool supports task-augmented execution. + * + * - [Forbidden]: Tool does not support task-augmented execution (default when absent). + * - [Optional]: Tool may support task-augmented execution. + * - [Required]: Tool requires task-augmented execution. + */ +@Serializable +public enum class TaskSupport { + /** Tool does not support task-augmented execution. */ + @SerialName("forbidden") + Forbidden, + + /** Tool may support task-augmented execution. */ + @SerialName("optional") + Optional, + + /** Tool requires task-augmented execution. */ + @SerialName("required") + Required, +} + /** * Additional properties describing a Tool to clients. * diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ToolsTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ToolsTest.kt index 4663fbd..543e12d 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ToolsTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ToolsTest.kt @@ -475,6 +475,102 @@ class ToolsTest { assertEquals(null, result.structuredContent) } + @Test + fun `should serialize ToolExecution with taskSupport`() { + val execution = ToolExecution(taskSupport = TaskSupport.Required) + + verifySerialization( + execution, + McpJson, + """ + { + "taskSupport": "required" + } + """.trimIndent(), + ) + } + + @Test + fun `should serialize ToolExecution without taskSupport`() { + val execution = ToolExecution() + + verifySerialization( + execution, + McpJson, + "{}", + ) + } + + @Test + fun `should deserialize ToolExecution with taskSupport`() { + val json = """ + { + "taskSupport": "optional" + } + """.trimIndent() + + val execution = verifyDeserialization(McpJson, json) + + assertEquals(TaskSupport.Optional, execution.taskSupport) + } + + @Test + fun `should deserialize ToolExecution without taskSupport`() { + val json = "{}" + + val execution = verifyDeserialization(McpJson, json) + + assertEquals(null, execution.taskSupport) + } + + @Test + fun `should serialize Tool with execution`() { + val tool = Tool( + name = "long-running-task", + inputSchema = ToolSchema(), + description = "A tool that supports task-augmented execution", + execution = ToolExecution(taskSupport = TaskSupport.Required), + ) + + verifySerialization( + tool, + McpJson, + """ + { + "name": "long-running-task", + "inputSchema": { + "type": "object" + }, + "description": "A tool that supports task-augmented execution", + "execution": { + "taskSupport": "required" + } + } + """.trimIndent(), + ) + } + + @Test + fun `should deserialize Tool with execution`() { + val json = """ + { + "name": "long-running-task", + "inputSchema": { + "type": "object" + }, + "execution": { + "taskSupport": "forbidden" + } + } + """.trimIndent() + + val tool = verifyDeserialization(McpJson, json) + + assertEquals("long-running-task", tool.name) + assertNotNull(tool.execution) + assertEquals(TaskSupport.Forbidden, tool.execution.taskSupport) + } + @Test fun `should serialize tool schema type even when defaults disabled`() { val tool = weatherTool() diff --git a/kotlin-sdk-server/api/kotlin-sdk-server.api b/kotlin-sdk-server/api/kotlin-sdk-server.api index c0b0199..a6f3b6d 100644 --- a/kotlin-sdk-server/api/kotlin-sdk-server.api +++ b/kotlin-sdk-server/api/kotlin-sdk-server.api @@ -99,8 +99,8 @@ public class io/modelcontextprotocol/kotlin/sdk/server/Server { public static synthetic fun addResource$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)V public final fun addResources (Ljava/util/List;)V public final fun addTool (Lio/modelcontextprotocol/kotlin/sdk/types/Tool;Lkotlin/jvm/functions/Function3;)V - public final fun addTool (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/functions/Function3;)V - public static synthetic fun addTool$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)V + public final fun addTool (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations;Lio/modelcontextprotocol/kotlin/sdk/types/ToolExecution;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/functions/Function3;)V + public static synthetic fun addTool$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations;Lio/modelcontextprotocol/kotlin/sdk/types/ToolExecution;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)V public final fun addTools (Ljava/util/List;)V public final fun clientConnection (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/server/ClientConnection; public final fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt index 2401709..7842c2b 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt @@ -38,6 +38,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.SubscribeRequest import io.modelcontextprotocol.kotlin.sdk.types.TextContent import io.modelcontextprotocol.kotlin.sdk.types.Tool import io.modelcontextprotocol.kotlin.sdk.types.ToolAnnotations +import io.modelcontextprotocol.kotlin.sdk.types.ToolExecution import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema import io.modelcontextprotocol.kotlin.sdk.types.UnsubscribeRequest import kotlinx.coroutines.CancellationException @@ -313,6 +314,7 @@ public open class Server( * @param inputSchema The expected input schema for the tool. * @param outputSchema The optional expected output schema for the tool. * @param toolAnnotations Optional additional tool information. + * @param execution Optional execution-related properties, such as task-augmented execution support. * @param meta Optional metadata as a [JsonObject]. * @param handler A suspend function that handles executing the tool when called by the client. * @throws IllegalStateException If the server does not support tools. @@ -325,6 +327,7 @@ public open class Server( title: String? = null, outputSchema: ToolSchema? = null, toolAnnotations: ToolAnnotations? = null, + execution: ToolExecution? = null, meta: JsonObject? = null, handler: suspend ClientConnection.(CallToolRequest) -> CallToolResult, ) { @@ -335,6 +338,7 @@ public open class Server( description = description, title = title, annotations = toolAnnotations, + execution = execution, meta = meta, ) addTool(tool, handler) From e88b34170cd832fe5f50c8b6da02bb5ffe1e6ab1 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Thu, 12 Mar 2026 14:44:59 +0100 Subject: [PATCH 177/449] feat!: add mcp conformance test infra (#585); add SSE reconnection with retry support (#596) (#585) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a comprehensive conformance test suite for the Kotlin MCP SDK, covering core protocol operations, tool calls, elicitation, resources, prompts, and 20 OAuth/auth scenarios - Conformance server and client implementations - OAuth/auth test scenarios: JWT, authorization code flow, client credentials, PKCE, scope handling, cross-app access, client registration - CI workflow - Baseline file for tracking expected failures - Shell script fixes: - #592 - #593 - #596 ## Remaining known failures (tracked issues, will be fixed directly in `main`) - [x] `tools-call-with-logging`, `tools-call-with-progress`, `tools-call-sampling`, `tools-call-elicitation`, `elicitation-sep1034-defaults`- see #599, - [x] `elicitation-sep1330-enums` - #587 #600 - [x] `initialize` - #588 - [x] `tools_call`, `auth/scope-step-up`, `auth/scope-retry-limit` - #589 - [ ] `elicitation-sep1034-client-defaults` - #414 - [x] `sse-retry` - #590 - [ ] `resources-templates-read` - #591 ## Breaking Changes from #596 - `StreamableHttpClientTransport` and `mcpStreamableHttp`/`mcpStreamableHttpTransport`: old constructors accepting `Duration` timeout are now `@Deprecated` — use the new overloads with `ReconnectionOptions` instead - `StreamableHttpClientTransport.close()` no longer calls `terminateSession()` automatically ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [x] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --------- Co-authored-by: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> --- .github/workflows/conformance.yml | 185 ++++-- build.gradle.kts | 13 +- conformance-test/.gitignore | 1 + conformance-test/README.md | 130 ++++ conformance-test/build.gradle.kts | 61 +- conformance-test/conformance-baseline.yml | 13 + conformance-test/detekt-baseline.xml | 9 - conformance-test/run-conformance.sh | 194 ++++++ .../sdk/conformance/ConformanceClient.kt | 199 ++++++ .../sdk/conformance/ConformanceCompletions.kt | 15 + .../sdk/conformance/ConformancePrompts.kt | 114 ++++ .../sdk/conformance/ConformanceResources.kt | 123 ++++ .../sdk/conformance/ConformanceServer.kt | 47 ++ .../sdk/conformance/ConformanceTools.kt | 604 ++++++++++++++++++ .../sdk/conformance/InMemoryEventStore.kt | 46 ++ .../sdk/conformance/auth/JWTScenario.kt | 189 ++++++ .../sdk/conformance/auth/authCodeFlow.kt | 154 +++++ .../sdk/conformance/auth/basicScenario.kt | 54 ++ .../conformance/auth/clientRegistration.kt | 73 +++ .../auth/crossAppAccessScenario.kt | 82 +++ .../kotlin/sdk/conformance/auth/discovery.kt | 48 ++ .../kotlin/sdk/conformance/auth/pkce.kt | 30 + .../sdk/conformance/auth/registration.kt | 32 + .../sdk/conformance/auth/scopeHandling.kt | 29 + .../sdk/conformance/auth/tokenExchange.kt | 174 +++++ .../kotlin/sdk/conformance/auth/utils.kt | 120 ++++ .../sdk/conformance/ConformanceClient.kt | 85 --- .../sdk/conformance/ConformanceServer.kt | 426 ------------ .../kotlin/sdk/conformance/ConformanceTest.kt | 343 ---------- .../conformance/WebSocketConformanceClient.kt | 105 --- .../test/resources/simplelogger.properties | 10 - gradle/libs.versions.toml | 1 + kotlin-sdk-client/api/kotlin-sdk-client.api | 18 + .../kotlin/sdk/client/ReconnectionOptions.kt | 44 ++ .../client/StreamableHttpClientTransport.kt | 223 +++++-- .../StreamableHttpMcpKtorClientExtensions.kt | 67 +- .../http/StreamableHttpClientTransportTest.kt | 199 ++++++ .../sdk/client/StreamableHttpClientTest.kt | 27 +- .../kotlin/sdk/types/common.kt | 5 +- .../kotlin/sdk/types/serializers.kt | 18 +- .../kotlin/sdk/types/CommonTypeTest.kt | 3 +- .../server/StreamableHttpServerTransport.kt | 21 +- 42 files changed, 3199 insertions(+), 1135 deletions(-) create mode 100644 conformance-test/.gitignore create mode 100644 conformance-test/README.md create mode 100644 conformance-test/conformance-baseline.yml delete mode 100644 conformance-test/detekt-baseline.xml create mode 100755 conformance-test/run-conformance.sh create mode 100644 conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceClient.kt create mode 100644 conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceCompletions.kt create mode 100644 conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformancePrompts.kt create mode 100644 conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceResources.kt create mode 100644 conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceServer.kt create mode 100644 conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTools.kt create mode 100644 conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/InMemoryEventStore.kt create mode 100644 conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/JWTScenario.kt create mode 100644 conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/authCodeFlow.kt create mode 100644 conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/basicScenario.kt create mode 100644 conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/clientRegistration.kt create mode 100644 conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/crossAppAccessScenario.kt create mode 100644 conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/discovery.kt create mode 100644 conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/pkce.kt create mode 100644 conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/registration.kt create mode 100644 conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/scopeHandling.kt create mode 100644 conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/tokenExchange.kt create mode 100644 conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/utils.kt delete mode 100644 conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceClient.kt delete mode 100644 conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceServer.kt delete mode 100644 conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTest.kt delete mode 100644 conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/WebSocketConformanceClient.kt delete mode 100644 conformance-test/src/test/resources/simplelogger.properties create mode 100644 kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ReconnectionOptions.kt diff --git a/.github/workflows/conformance.yml b/.github/workflows/conformance.yml index 1c9ba19..b218b4c 100644 --- a/.github/workflows/conformance.yml +++ b/.github/workflows/conformance.yml @@ -7,67 +7,166 @@ on: push: branches: [ main ] +env: + JAVA_VERSION: '21' + JAVA_DISTRIBUTION: temurin + NODE_VERSION: '22' + + concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} # Cancel only when the run is NOT on `main` branch cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} jobs: - run-conformance: - runs-on: ${{ matrix.os }} - name: Run Conformance Tests on ${{ matrix.os }} + server: + runs-on: ubuntu-latest + name: Conformance Server Tests timeout-minutes: 20 - env: - JAVA_OPTS: "-Xmx8g -Dfile.encoding=UTF-8 -Djava.awt.headless=true -Dkotlin.daemon.jvm.options=-Xmx6g" - - strategy: - fail-fast: false - matrix: - include: - - os: ubuntu-latest - max-workers: 3 - - os: windows-latest - max-workers: 3 - - os: macos-latest - max-workers: 2 steps: - uses: actions/checkout@v6 - - name: Set up JDK 21 + - name: Set up JDK uses: actions/setup-java@v5 with: - java-version: '21' - distribution: 'temurin' - - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: '22' # increase only after https://github.com/nodejs/node/issues/56645 will be fixed - - - name: Setup Conformance Tests - working-directory: conformance-test - run: |- - npm install -g @modelcontextprotocol/conformance@0.1.8 + java-version: ${{ env.JAVA_VERSION }} + distribution: ${{ env.JAVA_DISTRIBUTION }} - name: Setup Gradle uses: gradle/actions/setup-gradle@v5 with: - add-job-summary: 'always' cache-read-only: ${{ github.ref != 'refs/heads/main' }} - gradle-home-cache-includes: | - caches - notifications - sdks - ../.konan/** - - name: Run Conformance Tests - run: |- - ./gradlew :conformance-test:test --no-daemon --max-workers ${{ matrix.max-workers }} + - name: Build + run: ./gradlew :conformance-test:installDist - - name: Upload Conformance Results - if: always() - uses: actions/upload-artifact@v7 + - name: Start server + run: | + MCP_PORT=3001 conformance-test/build/install/conformance-test/bin/conformance-test & + for i in $(seq 1 30); do + if curl -s -o /dev/null http://localhost:3001/mcp; then + echo "Server is ready" + break + fi + sleep 1 + done + + - name: Run conformance tests + uses: modelcontextprotocol/conformance@v0.1.15 with: - name: conformance-results-${{ matrix.os }} - path: conformance-test/results/ + mode: server + url: http://localhost:3001/mcp + suite: active + node-version: ${{ env.NODE_VERSION }} + expected-failures: ./conformance-test/conformance-baseline.yml + + client: + runs-on: ubuntu-latest + name: "Conformance Client Tests: ${{ matrix.scenario }}" + timeout-minutes: 20 + + strategy: + fail-fast: false + matrix: + scenario: + - initialize + - tools_call + - elicitation-sep1034-client-defaults + - sse-retry + + steps: + - uses: actions/checkout@v6 + + - name: Set up JDK + uses: actions/setup-java@v5 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: ${{ env.JAVA_DISTRIBUTION }} + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + with: + cache-read-only: ${{ github.ref != 'refs/heads/main' }} + + - name: Build + run: ./gradlew :conformance-test:installDist + + - name: Run conformance tests + uses: modelcontextprotocol/conformance@v0.1.15 + with: + mode: client + command: conformance-test/build/install/conformance-test/bin/conformance-client + scenario: ${{ matrix.scenario }} + node-version: ${{ env.NODE_VERSION }} + expected-failures: ./conformance-test/conformance-baseline.yml + + auth: + runs-on: ubuntu-latest + name: Conformance Auth Tests + timeout-minutes: 20 + + steps: + - uses: actions/checkout@v6 + + - name: Set up JDK + uses: actions/setup-java@v5 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: ${{ env.JAVA_DISTRIBUTION }} + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + with: + cache-read-only: ${{ github.ref != 'refs/heads/main' }} + + - name: Build + run: ./gradlew :conformance-test:installDist + + - name: Run conformance tests + uses: modelcontextprotocol/conformance@v0.1.15 + with: + mode: client + command: conformance-test/build/install/conformance-test/bin/conformance-client + suite: auth + node-version: ${{ env.NODE_VERSION }} + expected-failures: ./conformance-test/conformance-baseline.yml + + auth-scenarios: + runs-on: ubuntu-latest + name: "Conformance Auth Scenario: ${{ matrix.scenario }}" + timeout-minutes: 20 + + strategy: + fail-fast: false + matrix: + scenario: + - auth/client-credentials-jwt + - auth/client-credentials-basic + - auth/cross-app-access-complete-flow + + steps: + - uses: actions/checkout@v6 + + - name: Set up JDK + uses: actions/setup-java@v5 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: ${{ env.JAVA_DISTRIBUTION }} + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + with: + cache-read-only: ${{ github.ref != 'refs/heads/main' }} + + - name: Build + run: ./gradlew :conformance-test:installDist + + - name: Run conformance tests + uses: modelcontextprotocol/conformance@v0.1.15 + with: + mode: client + command: conformance-test/build/install/conformance-test/bin/conformance-client + scenario: ${{ matrix.scenario }} + node-version: ${{ env.NODE_VERSION }} + expected-failures: ./conformance-test/conformance-baseline.yml diff --git a/build.gradle.kts b/build.gradle.kts index f4ddd44..72c5553 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -23,12 +23,15 @@ dependencies { subprojects { apply(plugin = "org.jlleitschuh.gradle.ktlint") apply(plugin = "org.jetbrains.kotlinx.kover") - apply(plugin = "dev.detekt") - detekt { - config = files("$rootDir/config/detekt/detekt.yml") - buildUponDefaultConfig = true - failOnSeverity.set(FailOnSeverity.Error) + if (name != "conformance-test" && name != "docs") { + apply(plugin = "dev.detekt") + + detekt { + config = files("$rootDir/config/detekt/detekt.yml") + buildUponDefaultConfig = true + failOnSeverity.set(FailOnSeverity.Error) + } } } diff --git a/conformance-test/.gitignore b/conformance-test/.gitignore new file mode 100644 index 0000000..6628455 --- /dev/null +++ b/conformance-test/.gitignore @@ -0,0 +1 @@ +/results/ diff --git a/conformance-test/README.md b/conformance-test/README.md new file mode 100644 index 0000000..d14fcfe --- /dev/null +++ b/conformance-test/README.md @@ -0,0 +1,130 @@ +# MCP Conformance Tests + +Conformance tests for the Kotlin MCP SDK. Uses the external +[`@modelcontextprotocol/conformance`](https://www.npmjs.com/package/@modelcontextprotocol/conformance) +runner (pinned to **0.1.15**) to validate compliance with the MCP specification. + +## Prerequisites + +- **JDK 17+** +- **Node.js 18+** and `npx` (for the conformance runner) +- **curl** (used to poll server readiness) + +## Quick Start + +Run **all** suites (server, client core, client auth) from the project root: + +```bash +./conformance-test/run-conformance.sh all +``` + +## Commands + +``` +./conformance-test/run-conformance.sh [extra-args...] +``` + +| Command | What it does | +|---------------|--------------------------------------------------------------------------------------| +| `list` | [List scenarios available in MCP Conformance Test Framework][list-scenarios-command] | +| `server` | Starts the Ktor conformance server, runs the server test suite against it | +| `client` | Runs the client test suite (`initialize`, `tools_call`, `elicitation`, `sse-retry`) | +| `client-auth` | Runs the client auth test suite (20 OAuth scenarios) | +| `all` | Runs all three suites sequentially | + +Any `[extra-args]` are forwarded to the conformance runner (e.g. `--verbose`). + +## What the Script Does + +1. **Builds** the module via `./gradlew :conformance-test:installDist` +2. For `server` — starts the conformance server on `localhost:3001`, polls until ready +3. Invokes `npx @modelcontextprotocol/conformance@0.1.15` with the appropriate arguments +4. Saves results to `conformance-test/results//` +5. Cleans up the server process on exit +6. Exits non-zero if any suite fails + +## Environment Variables + +| Variable | Default | Description | +|------------|---------|---------------------------------| +| `MCP_PORT` | `3001` | Port for the conformance server | + +## Project Structure + +``` +conformance-test/ +├── run-conformance.sh # Single entry point script +├── conformance-baseline.yml # Expected failures for known SDK limitations +└── src/main/kotlin/.../conformance/ + ├── ConformanceServer.kt # Ktor server entry point (StreamableHTTP, DNS rebinding, EventStore) + ├── ConformanceClient.kt # Scenario-based client entry point (MCP_CONFORMANCE_SCENARIO routing) + ├── ConformanceTools.kt # 18 tool registrations + ├── ConformanceResources.kt # 5 resource registrations (static, binary, template, watched, dynamic) + ├── ConformancePrompts.kt # 5 prompt registrations (simple, args, image, embedded, dynamic) + ├── ConformanceCompletions.kt # completion/complete handler + ├── InMemoryEventStore.kt # EventStore impl for SSE resumability (SEP-1699) + └── auth/ # OAuth client for 20 auth scenarios + ├── registration.kt # Scenario handler registration + ├── utils.kt # Shared utilities: JSON instance, constants, extractOrigin() + ├── discovery.kt # Protected Resource Metadata + AS Metadata discovery + ├── pkce.kt # PKCE code verifier/challenge generation + AS capability check + ├── tokenExchange.kt # Token endpoint interaction (exchange code, error handling) + ├── authCodeFlow.kt # Main Authorization Code flow handler (runAuthClient + interceptor) + ├── scopeHandling.kt # Scope selection strategy + step-up 403 handling + ├── clientRegistration.kt # Client registration logic (pre-reg, CIMD, dynamic) + ├── JWTScenario.kt # Client Credentials JWT scenario + ├── basicScenario.kt # Client Credentials Basic scenario + └── crossAppAccessScenario.kt # Cross-App Access (SEP-990) scenario +``` + +## Test Suites + +### Server Suite + +Tests the conformance server against all server scenarios: + +| Category | Scenarios | +|-------------|-------------------------------------------------------------------------------------------------------------------------------------| +| Lifecycle | initialize, ping | +| Tools | text, image, audio, embedded, multiple, progress, logging, error, sampling, elicitation, dynamic, reconnection, JSON Schema 2020-12 | +| Resources | list, read-text, read-binary, templates, subscribe, dynamic | +| Prompts | simple, with-args, with-image, with-embedded-resource, dynamic | +| Completions | complete | +| Security | DNS rebinding protection | + +### Client Core Suite + +| Scenario | Description | +|---------------------------------------|-----------------------------------------------| +| `initialize` | Connect, list tools, close | +| `tools_call` | Connect, call `add_numbers(a=5, b=3)`, close | +| `elicitation-sep1034-client-defaults` | Elicitation with `applyDefaults` capability | +| `sse-retry` | Call `test_reconnection`, verify reconnection | + +### Client Auth Suite + +17 OAuth Authorization Code scenarios + 2 Client Credentials scenarios (`jwt`, `basic`) + 1 Cross-App Access scenario = 20 total. + +> [!NOTE] +> Auth scenarios are implemented using Ktor's `HttpClient` plugins (`HttpSend` interceptor, +> `ktor-client-auth`) as a standalone OAuth client. They do not use the SDK's built-in auth support. + +## Known SDK Limitations + +8 scenarios are expected to fail due to current SDK limitations (tracked in [ +`conformance-baseline.yml`](conformance-baseline.yml). + +| Scenario | Suite | Root Cause | +|---------------------------------------|--------|--------------------------------------------------------------------------------------------------------------------------------------------------------| +| `tools-call-with-logging` | server | Notifications from tool handlers have no `relatedRequestId`; transport routes them to the standalone SSE stream instead of the request-specific stream | +| `tools-call-with-progress` | server | *(same as above)* | +| `tools-call-sampling` | server | *(same as above)* | +| `tools-call-elicitation` | server | *(same as above)* | +| `elicitation-sep1034-defaults` | server | *(same as above)* | +| `elicitation-sep1330-enums` | server | *(same as above)* | +| `resources-templates-read` | server | SDK does not implement `addResourceTemplate()` with URI pattern matching; resources are looked up by exact URI | +| `elicitation-sep1034-client-defaults` | client | SDK does not fill in `default` values from the elicitation request schema before sending the response | + +These failures reveal SDK gaps and are intentionally not fixed in this module. + +[list-scenarios-command]: https://github.com/modelcontextprotocol/conformance/tree/main?tab=readme-ov-file#list-available-scenarios diff --git a/conformance-test/build.gradle.kts b/conformance-test/build.gradle.kts index 1877d09..a96f4fb 100644 --- a/conformance-test/build.gradle.kts +++ b/conformance-test/build.gradle.kts @@ -1,39 +1,34 @@ -import org.gradle.api.tasks.testing.logging.TestExceptionFormat - plugins { kotlin("jvm") + application +} + +application { + mainClass.set("io.modelcontextprotocol.kotlin.sdk.conformance.ConformanceServerKt") +} + +tasks.register("conformanceClientScripts") { + mainClass.set("io.modelcontextprotocol.kotlin.sdk.conformance.ConformanceClientKt") + applicationName = "conformance-client" + outputDir = tasks.named("startScripts").get().outputDir + classpath = tasks.named("jar").get().outputs.files + configurations.named("runtimeClasspath").get() +} + +tasks.named("installDist") { + dependsOn("conformanceClientScripts") +} + +tasks.named("clean") { + delete("results") } dependencies { - testImplementation(project(":kotlin-sdk")) - testImplementation(project(":test-utils")) - testImplementation(kotlin("test")) - testImplementation(libs.kotlin.logging) - testImplementation(libs.ktor.client.cio) - testImplementation(libs.ktor.server.cio) - testImplementation(libs.ktor.server.websockets) - testRuntimeOnly(libs.slf4j.simple) -} - -tasks.test { - useJUnitPlatform() - - testLogging { - events("passed", "skipped", "failed") - showStandardStreams = true - showExceptions = true - showCauses = true - showStackTraces = true - exceptionFormat = TestExceptionFormat.FULL - } - - doFirst { - systemProperty("test.classpath", classpath.asPath) - - println("\n" + "=".repeat(60)) - println("MCP CONFORMANCE TESTS") - println("=".repeat(60)) - println("These tests validate compliance with the MCP specification.") - println("=".repeat(60) + "\n") - } + implementation(project(":kotlin-sdk")) + implementation(libs.ktor.server.cio) + implementation(libs.ktor.server.content.negotiation) + implementation(libs.ktor.serialization) + implementation(libs.ktor.client.cio) + implementation(libs.ktor.client.auth) + implementation(libs.kotlin.logging) + runtimeOnly(libs.slf4j.simple) } diff --git a/conformance-test/conformance-baseline.yml b/conformance-test/conformance-baseline.yml new file mode 100644 index 0000000..cc06a38 --- /dev/null +++ b/conformance-test/conformance-baseline.yml @@ -0,0 +1,13 @@ +# Conformance test baseline - expected failures +# Add entries here as tests are identified as known SDK limitations +server: + - tools-call-with-logging + - tools-call-with-progress + - tools-call-sampling + - tools-call-elicitation + - elicitation-sep1034-defaults + - elicitation-sep1330-enums + - resources-templates-read + +client: + - elicitation-sep1034-client-defaults diff --git a/conformance-test/detekt-baseline.xml b/conformance-test/detekt-baseline.xml deleted file mode 100644 index fe120f5..0000000 --- a/conformance-test/detekt-baseline.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - BracesOnWhenStatements:ConformanceServer.kt:HttpServerTransport$when - BracesOnWhenStatements:ConformanceTest.kt:ConformanceTest$when - ForbiddenComment:ConformanceTest.kt:ConformanceTest.Companion$// TODO: Fix - - diff --git a/conformance-test/run-conformance.sh b/conformance-test/run-conformance.sh new file mode 100755 index 0000000..8f15ac0 --- /dev/null +++ b/conformance-test/run-conformance.sh @@ -0,0 +1,194 @@ +#!/bin/bash +# Script to run MCP conformance tests for the Kotlin SDK. +# +# Usage: ./conformance-test/run-conformance.sh [extra-args...] +# Commands: server | client | client-auth | all + +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" || exit 1; pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." || exit 1; pwd)" + +CONFORMANCE_VERSION="0.1.15" +PORT="${MCP_PORT:-3001}" +SERVER_URL="http://localhost:${PORT}/mcp" +RESULTS_DIR="$SCRIPT_DIR/results" +SERVER_DIST="$SCRIPT_DIR/build/install/conformance-test/bin/conformance-test" +CLIENT_DIST="$SCRIPT_DIR/build/install/conformance-test/bin/conformance-client" + +SERVER_PID="" + +# shellcheck disable=SC2317 +cleanup() { + if [ -n "$SERVER_PID" ] && kill -0 "$SERVER_PID" 2>/dev/null; then + echo "Stopping server (PID: $SERVER_PID)..." + kill "$SERVER_PID" 2>/dev/null || true + wait "$SERVER_PID" 2>/dev/null || true + fi +} +trap cleanup EXIT + +build() { + echo "Building conformance-test distributions..." + cd "$PROJECT_ROOT" || return 1 + ./gradlew :conformance-test:installDist --quiet + cd "$SCRIPT_DIR" || return 1 + echo "Build complete." +} + +start_server() { + echo "Starting conformance server on port $PORT..." + MCP_PORT="$PORT" "$SERVER_DIST" & + SERVER_PID=$! + + echo "Waiting for server to be ready..." + local retries=0 + local max_retries=30 + while ! curl -sf "$SERVER_URL" > /dev/null 2>&1; do + retries=$((retries + 1)) + if [ "$retries" -ge "$max_retries" ]; then + echo "ERROR: Server failed to start after $max_retries attempts" + return 1 + fi + sleep 0.5 + done + echo "Server is ready (PID: $SERVER_PID)." +} + +stop_server() { + if [ -n "$SERVER_PID" ] && kill -0 "$SERVER_PID" 2>/dev/null; then + echo "Stopping server (PID: $SERVER_PID)..." + kill "$SERVER_PID" 2>/dev/null || true + wait "$SERVER_PID" 2>/dev/null || true + SERVER_PID="" + fi +} + +run_list_scenarios() { + local output_dir="$RESULTS_DIR/list" + mkdir -p "$output_dir" + echo "" + echo "==========================================" + echo " List Available Scenarios" + echo "==========================================" + local rc=0 + npx "@modelcontextprotocol/conformance@$CONFORMANCE_VERSION" list \ + "$@" > "$output_dir/scenarios.txt" || rc=$? + + cat "$output_dir/scenarios.txt" + return $rc +} + +run_server_suite() { + local output_dir="$RESULTS_DIR/server" + mkdir -p "$output_dir" + echo "" + echo "==========================================" + echo " Running SERVER conformance tests" + echo "==========================================" + start_server || return 1 + local rc=0 + npx "@modelcontextprotocol/conformance@$CONFORMANCE_VERSION" server \ + --url "$SERVER_URL" \ + --output-dir "$output_dir" \ + --expected-failures "$SCRIPT_DIR/conformance-baseline.yml" \ + "$@" || rc=$? + stop_server + return $rc +} + +run_client_suite() { + local output_dir="$RESULTS_DIR/client" + mkdir -p "$output_dir" + echo "" + echo "==========================================" + echo " Running CLIENT conformance tests" + echo "==========================================" + local scenarios=("initialize" "tools_call" "elicitation-sep1034-client-defaults" "sse-retry") + local rc=0 + for scenario in "${scenarios[@]}"; do + npx "@modelcontextprotocol/conformance@$CONFORMANCE_VERSION" client \ + --command "$CLIENT_DIST" \ + --scenario "$scenario" \ + --output-dir "$output_dir" \ + --expected-failures "$SCRIPT_DIR/conformance-baseline.yml" \ + "$@" || rc=$? + done + return $rc +} + +run_client_auth_suite() { + local output_dir="$RESULTS_DIR/client-auth" + mkdir -p "$output_dir" + echo "" + echo "==========================================" + echo " Running CLIENT (auth) conformance tests" + echo "==========================================" + local rc=0 + npx "@modelcontextprotocol/conformance@$CONFORMANCE_VERSION" client \ + --command "$CLIENT_DIST" \ + --suite auth \ + --output-dir "$output_dir" \ + --expected-failures "$SCRIPT_DIR/conformance-baseline.yml" \ + "$@" || rc=$? + + local extra_scenarios=("auth/client-credentials-jwt" "auth/client-credentials-basic" "auth/cross-app-access-complete-flow") + for scenario in "${extra_scenarios[@]}"; do + npx "@modelcontextprotocol/conformance@$CONFORMANCE_VERSION" client \ + --command "$CLIENT_DIST" \ + --scenario "$scenario" \ + --output-dir "$output_dir" \ + --expected-failures "$SCRIPT_DIR/conformance-baseline.yml" \ + "$@" || rc=$? + done + return $rc +} + +# ============================================================================ +# Main +# ============================================================================ + +COMMAND="${1:-}" +shift 2>/dev/null || true + +if [ -z "$COMMAND" ]; then + echo "Usage: $0 [extra-args...]" + echo "Commands: list | server | client | client-auth | all" + exit 1 +fi + +build + +EXIT_CODE=0 + +case "$COMMAND" in + list) + run_list_scenarios "$@" || EXIT_CODE=1 + ;; + server) + run_server_suite "$@" || EXIT_CODE=1 + ;; + client) + run_client_suite "$@" || EXIT_CODE=1 + ;; + client-auth) + run_client_auth_suite "$@" || EXIT_CODE=1 + ;; + all) + run_server_suite "$@" || EXIT_CODE=1 + run_client_suite "$@" || EXIT_CODE=1 + run_client_auth_suite "$@" || EXIT_CODE=1 + ;; + *) + echo "Unknown command: $COMMAND" + echo "Commands: list | server | client | client-auth | all" + exit 1 + ;; +esac + +echo "" +echo "==========================================" +echo " Results saved to: $RESULTS_DIR" +echo "==========================================" + +exit $EXIT_CODE diff --git a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceClient.kt b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceClient.kt new file mode 100644 index 0000000..dba24ff --- /dev/null +++ b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceClient.kt @@ -0,0 +1,199 @@ +package io.modelcontextprotocol.kotlin.sdk.conformance + +import io.github.oshai.kotlinlogging.KotlinLogging +import io.ktor.client.HttpClient +import io.ktor.client.engine.cio.CIO +import io.ktor.client.plugins.HttpTimeout +import io.ktor.client.plugins.sse.SSE +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.client.ClientOptions +import io.modelcontextprotocol.kotlin.sdk.client.StreamableHttpClientTransport +import io.modelcontextprotocol.kotlin.sdk.conformance.auth.registerAuthScenarios +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.ElicitResult +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import kotlin.system.exitProcess +import kotlin.time.Duration.Companion.seconds + +private val logger = KotlinLogging.logger {} + +typealias ScenarioHandler = suspend (serverUrl: String) -> Unit + +internal val scenarioHandlers = mutableMapOf() + +// ============================================================================ +// Main entry point +// ============================================================================ + +fun main(args: Array) { + val scenarioName = System.getenv("MCP_CONFORMANCE_SCENARIO") + val serverUrl = args.lastOrNull() + + // Register all scenario handlers + registerCoreScenarios() + registerAuthScenarios() + + if (scenarioName == null || serverUrl == null) { + logger.error { "Usage: MCP_CONFORMANCE_SCENARIO= conformance-client " } + logger.error { "\nThe MCP_CONFORMANCE_SCENARIO env var is set automatically by the conformance runner." } + logger.error { "\nAvailable scenarios:" } + for (name in scenarioHandlers.keys.sorted()) { + logger.error { " - $name" } + } + exitProcess(1) + } + + val handler = scenarioHandlers[scenarioName] + if (handler == null) { + logger.error { "Unknown scenario: $scenarioName" } + logger.error { "\nAvailable scenarios:" } + for (name in scenarioHandlers.keys.sorted()) { + logger.error { " - $name" } + } + exitProcess(1) + } + + try { + runBlocking { + handler(serverUrl) + } + exitProcess(0) + } catch (e: Exception) { + logger.error(e) { "Error: ${e.message}" } + exitProcess(1) + } +} + +// ============================================================================ +// Shared HTTP client factory +// ============================================================================ + +private fun createHttpClient(): HttpClient = HttpClient(CIO) { + install(SSE) + install(HttpTimeout) { + requestTimeoutMillis = 30.seconds.inWholeMilliseconds + } +} + +// ============================================================================ +// Basic scenarios (initialize, tools_call) +// ============================================================================ + +private suspend fun runBasicClient(serverUrl: String) { + createHttpClient().use { httpClient -> + val transport = StreamableHttpClientTransport(httpClient, serverUrl) + val client = Client( + clientInfo = Implementation("test-client", "1.0.0"), + options = ClientOptions(capabilities = ClientCapabilities()), + ) + client.connect(transport) + client.close() + } +} + +private suspend fun runToolsCallClient(serverUrl: String) { + createHttpClient().use { httpClient -> + val transport = StreamableHttpClientTransport(httpClient, serverUrl) + val client = Client( + clientInfo = Implementation("test-client", "1.0.0"), + options = ClientOptions(capabilities = ClientCapabilities()), + ) + client.connect(transport) + + val tools = client.listTools() + val addTool = tools.tools.find { it.name == "add_numbers" } + if (addTool != null) { + client.callTool( + CallToolRequest( + CallToolRequestParams( + name = "add_numbers", + arguments = buildJsonObject { + put("a", 5) + put("b", 3) + }, + ), + ), + ) + } + + client.close() + } +} + +// ============================================================================ +// Elicitation defaults scenario +// ============================================================================ + +private suspend fun runElicitationDefaultsClient(serverUrl: String) { + createHttpClient().use { httpClient -> + val transport = StreamableHttpClientTransport(httpClient, serverUrl) + val client = Client( + clientInfo = Implementation("elicitation-defaults-test-client", "1.0.0"), + options = ClientOptions( + capabilities = ClientCapabilities( + elicitation = ClientCapabilities.elicitation, + ), + ), + ) + + // Register elicitation handler that returns empty content — SDK should fill in defaults + client.setElicitationHandler { _ -> + ElicitResult( + action = ElicitResult.Action.Accept, + content = JsonObject(emptyMap()), + ) + } + + client.connect(transport) + + val tools = client.listTools() + val testTool = tools.tools.find { it.name == "test_client_elicitation_defaults" } + ?: error("Test tool not found: test_client_elicitation_defaults") + + client.callTool( + CallToolRequest(CallToolRequestParams(name = testTool.name)), + ) + + client.close() + } +} + +// ============================================================================ +// SSE retry scenario +// ============================================================================ + +private suspend fun runSSERetryClient(serverUrl: String) { + createHttpClient().use { httpClient -> + val transport = StreamableHttpClientTransport(httpClient, serverUrl) + val client = Client( + clientInfo = Implementation("sse-retry-test-client", "1.0.0"), + options = ClientOptions(capabilities = ClientCapabilities()), + ) + client.connect(transport) + + client.listTools() + + client.callTool( + CallToolRequest(CallToolRequestParams(name = "test_reconnection")), + ) + + client.close() + } +} + +// ============================================================================ +// Register core scenarios +// ============================================================================ + +private fun registerCoreScenarios() { + scenarioHandlers["initialize"] = ::runBasicClient + scenarioHandlers["tools_call"] = ::runToolsCallClient + scenarioHandlers["elicitation-sep1034-client-defaults"] = ::runElicitationDefaultsClient + scenarioHandlers["sse-retry"] = ::runSSERetryClient +} diff --git a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceCompletions.kt b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceCompletions.kt new file mode 100644 index 0000000..6297139 --- /dev/null +++ b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceCompletions.kt @@ -0,0 +1,15 @@ +package io.modelcontextprotocol.kotlin.sdk.conformance + +import io.modelcontextprotocol.kotlin.sdk.server.Server +import io.modelcontextprotocol.kotlin.sdk.types.CompleteRequest +import io.modelcontextprotocol.kotlin.sdk.types.CompleteResult +import io.modelcontextprotocol.kotlin.sdk.types.Method + +fun Server.registerConformanceCompletions() { + onConnect { + val session = sessions.values.lastOrNull() ?: return@onConnect + session.setRequestHandler(Method.Defined.CompletionComplete) { _, _ -> + CompleteResult(CompleteResult.Completion(values = emptyList(), total = 0, hasMore = false)) + } + } +} diff --git a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformancePrompts.kt b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformancePrompts.kt new file mode 100644 index 0000000..6c591e4 --- /dev/null +++ b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformancePrompts.kt @@ -0,0 +1,114 @@ +package io.modelcontextprotocol.kotlin.sdk.conformance + +import io.modelcontextprotocol.kotlin.sdk.server.Server +import io.modelcontextprotocol.kotlin.sdk.types.EmbeddedResource +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult +import io.modelcontextprotocol.kotlin.sdk.types.ImageContent +import io.modelcontextprotocol.kotlin.sdk.types.PromptArgument +import io.modelcontextprotocol.kotlin.sdk.types.PromptMessage +import io.modelcontextprotocol.kotlin.sdk.types.Role +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents +import kotlinx.coroutines.delay +import kotlin.time.Duration.Companion.milliseconds + +fun Server.registerConformancePrompts() { + // 1. Simple prompt + addPrompt( + name = "test_simple_prompt", + description = "test_simple_prompt", + ) { + GetPromptResult( + messages = listOf( + PromptMessage(Role.User, TextContent("This is a simple prompt for testing.")), + ), + ) + } + + // 2. Prompt with arguments + addPrompt( + name = "test_prompt_with_arguments", + description = "test_prompt_with_arguments", + arguments = listOf( + PromptArgument(name = "arg1", description = "First test argument", required = true), + PromptArgument(name = "arg2", description = "Second test argument", required = true), + ), + ) { request -> + val arg1 = request.arguments?.get("arg1") ?: "" + val arg2 = request.arguments?.get("arg2") ?: "" + GetPromptResult( + messages = listOf( + PromptMessage( + Role.User, + TextContent("Prompt with arguments: arg1='$arg1', arg2='$arg2'"), + ), + ), + ) + } + + // 3. Prompt with image + addPrompt( + name = "test_prompt_with_image", + description = "test_prompt_with_image", + ) { + GetPromptResult( + messages = listOf( + PromptMessage(Role.User, ImageContent(data = PNG_BASE64, mimeType = "image/png")), + PromptMessage(Role.User, TextContent("Please analyze the image above.")), + ), + ) + } + + // 4. Prompt with embedded resource + addPrompt( + name = "test_prompt_with_embedded_resource", + description = "test_prompt_with_embedded_resource", + arguments = listOf( + PromptArgument(name = "resourceUri", description = "URI of the resource to embed", required = true), + ), + ) { request -> + val resourceUri = request.arguments?.get("resourceUri") ?: "test://embedded-resource" + GetPromptResult( + messages = listOf( + PromptMessage( + Role.User, + EmbeddedResource( + resource = TextResourceContents( + text = "Embedded resource content for testing", + uri = resourceUri, + mimeType = "text/plain", + ), + ), + ), + PromptMessage(Role.User, TextContent("Please process the embedded resource above.")), + ), + ) + } + + // 5. Dynamic prompt + val server = this + addPrompt( + name = "test_dynamic_prompt", + description = "test_dynamic_prompt", + ) { + // Add a temporary prompt, triggering listChanged + server.addPrompt( + name = "test_dynamic_prompt_temp", + description = "Temporary dynamic prompt", + ) { + GetPromptResult( + messages = listOf( + PromptMessage(Role.User, TextContent("Temporary prompt response")), + ), + ) + } + delay(100.milliseconds) + // Remove the temporary prompt, triggering listChanged again + server.removePrompt("test_dynamic_prompt_temp") + GetPromptResult( + messages = listOf( + PromptMessage(Role.User, TextContent("Dynamic prompt executed successfully")), + ), + ) + } +} diff --git a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceResources.kt b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceResources.kt new file mode 100644 index 0000000..a099037 --- /dev/null +++ b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceResources.kt @@ -0,0 +1,123 @@ +package io.modelcontextprotocol.kotlin.sdk.conformance + +import io.modelcontextprotocol.kotlin.sdk.server.Server +import io.modelcontextprotocol.kotlin.sdk.types.BlobResourceContents +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult +import io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents +import kotlinx.coroutines.delay +import kotlin.time.Duration.Companion.milliseconds + +fun Server.registerConformanceResources() { + // 1. Static text resource + addResource( + uri = "test://static-text", + name = "static-text", + description = "A static text resource for testing", + mimeType = "text/plain", + ) { + ReadResourceResult( + listOf( + TextResourceContents( + text = "This is the content of the static text resource.", + uri = "test://static-text", + mimeType = "text/plain", + ), + ), + ) + } + + // 2. Static binary resource + addResource( + uri = "test://static-binary", + name = "static-binary", + description = "A static binary resource for testing", + mimeType = "image/png", + ) { + ReadResourceResult( + listOf( + BlobResourceContents( + blob = PNG_BASE64, + uri = "test://static-binary", + mimeType = "image/png", + ), + ), + ) + } + + // 3. Template resource + // Note: The SDK does not currently support addResourceTemplate(). + // Register as a static resource; template listing is handled separately. + addResource( + uri = "test://template/{id}/data", + name = "template", + description = "A template resource for testing", + mimeType = "application/json", + ) { request -> + ReadResourceResult( + listOf( + TextResourceContents( + text = "content for ${request.uri}", + uri = request.uri, + mimeType = "application/json", + ), + ), + ) + } + + // 4. Watched resource + addResource( + uri = "test://watched-resource", + name = "watched-resource", + description = "A watched resource for testing", + mimeType = "text/plain", + ) { + ReadResourceResult( + listOf( + TextResourceContents( + text = "Watched resource content.", + uri = "test://watched-resource", + mimeType = "text/plain", + ), + ), + ) + } + + // 5. Dynamic resource + val server = this + addResource( + uri = "test://dynamic-resource", + name = "dynamic-resource", + description = "A dynamic resource for testing", + mimeType = "text/plain", + ) { + // Add a temporary resource, triggering listChanged + server.addResource( + uri = "test://dynamic-resource-temp", + name = "dynamic-resource-temp", + description = "Temporary dynamic resource", + mimeType = "text/plain", + ) { + ReadResourceResult( + listOf( + TextResourceContents( + text = "Temporary resource content.", + uri = "test://dynamic-resource-temp", + mimeType = "text/plain", + ), + ), + ) + } + delay(100.milliseconds) + // Remove the temporary resource, triggering listChanged again + server.removeResource("test://dynamic-resource-temp") + ReadResourceResult( + listOf( + TextResourceContents( + text = "Dynamic resource content.", + uri = "test://dynamic-resource", + mimeType = "text/plain", + ), + ), + ) + } +} diff --git a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceServer.kt b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceServer.kt new file mode 100644 index 0000000..b420775 --- /dev/null +++ b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceServer.kt @@ -0,0 +1,47 @@ +package io.modelcontextprotocol.kotlin.sdk.conformance + +import io.ktor.serialization.kotlinx.json.json +import io.ktor.server.application.install +import io.ktor.server.cio.CIO +import io.ktor.server.engine.embeddedServer +import io.ktor.server.plugins.contentnegotiation.ContentNegotiation +import io.modelcontextprotocol.kotlin.sdk.server.Server +import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions +import io.modelcontextprotocol.kotlin.sdk.server.mcpStreamableHttp +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.McpJson +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities + +fun main() { + val port = System.getenv("MCP_PORT")?.toIntOrNull() ?: 3001 + embeddedServer(CIO, port = port) { + install(ContentNegotiation) { + json(McpJson) + } + mcpStreamableHttp( + enableDnsRebindingProtection = true, + allowedHosts = listOf("localhost", "127.0.0.1", "localhost:$port", "127.0.0.1:$port"), + eventStore = InMemoryEventStore(), + ) { + createConformanceServer() + } + }.start(wait = true) +} + +fun createConformanceServer(): Server = Server( + serverInfo = Implementation("mcp-kotlin-sdk-conformance", "0.1.0"), + options = ServerOptions( + ServerCapabilities( + tools = ServerCapabilities.Tools(listChanged = true), + resources = ServerCapabilities.Resources(subscribe = true, listChanged = true), + prompts = ServerCapabilities.Prompts(listChanged = true), + logging = ServerCapabilities.Logging, + completions = ServerCapabilities.Completions, + ), + ), +) { + registerConformanceTools() + registerConformanceResources() + registerConformancePrompts() + registerConformanceCompletions() +} diff --git a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTools.kt b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTools.kt new file mode 100644 index 0000000..ebe7a57 --- /dev/null +++ b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTools.kt @@ -0,0 +1,604 @@ +package io.modelcontextprotocol.kotlin.sdk.conformance + +import io.modelcontextprotocol.kotlin.sdk.server.Server +import io.modelcontextprotocol.kotlin.sdk.types.AudioContent +import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult +import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest +import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.EmbeddedResource +import io.modelcontextprotocol.kotlin.sdk.types.ImageContent +import io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel +import io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotification +import io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotificationParams +import io.modelcontextprotocol.kotlin.sdk.types.ProgressNotification +import io.modelcontextprotocol.kotlin.sdk.types.ProgressNotificationParams +import io.modelcontextprotocol.kotlin.sdk.types.Role +import io.modelcontextprotocol.kotlin.sdk.types.SamplingMessage +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents +import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema +import kotlinx.coroutines.delay +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.double +import kotlinx.serialization.json.jsonPrimitive +import kotlin.time.Duration.Companion.milliseconds + +// Minimal 1x1 PNG (base64) +internal const val PNG_BASE64 = + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg==" + +// Minimal WAV (base64) +internal const val WAV_BASE64 = "UklGRiYAAABXQVZFZm10IBAAAAABAAEAQB8AAAB9AAACABAAZGF0YQIAAAA=" + +@Suppress("LongMethod") +fun Server.registerConformanceTools() { + // 1. Simple text + addTool( + name = "test_simple_text", + description = "test_simple_text", + ) { + CallToolResult(listOf(TextContent("Simple text content"))) + } + + // 2. Image content + addTool( + name = "test_image_content", + description = "test_image_content", + ) { + CallToolResult(listOf(ImageContent(data = PNG_BASE64, mimeType = "image/png"))) + } + + // 3. Audio content + addTool( + name = "test_audio_content", + description = "test_audio_content", + ) { + CallToolResult(listOf(AudioContent(data = WAV_BASE64, mimeType = "audio/wav"))) + } + + // 4. Embedded resource + addTool( + name = "test_embedded_resource", + description = "test_embedded_resource", + ) { + CallToolResult( + listOf( + EmbeddedResource( + resource = TextResourceContents( + text = "This is an embedded resource content.", + uri = "test://embedded-resource", + mimeType = "text/plain", + ), + ), + ), + ) + } + + // 5. Multiple content types + addTool( + name = "test_multiple_content_types", + description = "test_multiple_content_types", + ) { + CallToolResult( + listOf( + TextContent("Simple text content"), + ImageContent(data = PNG_BASE64, mimeType = "image/png"), + EmbeddedResource( + resource = TextResourceContents( + text = "This is an embedded resource content.", + uri = "test://embedded-resource", + mimeType = "text/plain", + ), + ), + ), + ) + } + + // 6. Progress tool + addTool( + name = "test_tool_with_progress", + description = "test_tool_with_progress", + ) { request -> + val progressToken = request.meta?.progressToken + if (progressToken != null) { + notification( + ProgressNotification( + ProgressNotificationParams( + progressToken, + 0.0, + 100.0, + "Completed step 0 of 100", + ), + ), + ) + delay(50.milliseconds) + notification( + ProgressNotification( + ProgressNotificationParams( + progressToken, + 50.0, + 100.0, + "Completed step 50 of 100", + ), + ), + ) + delay(50.milliseconds) + notification( + ProgressNotification( + ProgressNotificationParams( + progressToken, + 100.0, + 100.0, + "Completed step 100 of 100", + ), + ), + ) + } + CallToolResult(listOf(TextContent("Simple text content"))) + } + + // 7. Error handling + addTool( + name = "test_error_handling", + description = "test_error_handling", + ) { + throw Exception("This tool intentionally returns an error for testing") + } + + // 8. Sampling + addTool( + name = "test_sampling", + description = "test_sampling", + inputSchema = ToolSchema( + properties = buildJsonObject { + put("prompt", buildJsonObject { put("type", JsonPrimitive("string")) }) + }, + required = listOf("prompt"), + ), + ) { request -> + val prompt = request.arguments?.get("prompt")?.jsonPrimitive?.content ?: "Hello" + val result = createMessage( + CreateMessageRequest( + CreateMessageRequestParams( + maxTokens = 10000, + messages = listOf(SamplingMessage(Role.User, TextContent(prompt))), + ), + ), + ) + CallToolResult(listOf(TextContent(result.content.toString()))) + } + + // 9. Elicitation + addTool( + name = "test_elicitation", + description = "test_elicitation", + inputSchema = ToolSchema( + properties = buildJsonObject { + put("message", buildJsonObject { put("type", JsonPrimitive("string")) }) + }, + required = listOf("message"), + ), + ) { request -> + val message = request.arguments?.get("message")?.jsonPrimitive?.content ?: "Please provide input" + val schema = ElicitRequestParams.RequestedSchema( + properties = buildJsonObject { + put( + "username", + buildJsonObject { + put("type", JsonPrimitive("string")) + put("description", JsonPrimitive("User's response")) + }, + ) + put( + "email", + buildJsonObject { + put("type", JsonPrimitive("string")) + put("description", JsonPrimitive("User's email address")) + }, + ) + }, + required = listOf("username", "email"), + ) + val result = createElicitation(message, schema) + CallToolResult(listOf(TextContent("User response: "))) + } + + // 10. Elicitation SEP1034 (defaults) + addTool( + name = "test_elicitation_sep1034_defaults", + description = "test_elicitation_sep1034_defaults", + ) { + val schema = ElicitRequestParams.RequestedSchema( + properties = buildJsonObject { + put( + "name", + buildJsonObject { + put("type", JsonPrimitive("string")) + put("description", JsonPrimitive("User name")) + put("default", JsonPrimitive("John Doe")) + }, + ) + put( + "age", + buildJsonObject { + put("type", JsonPrimitive("integer")) + put("description", JsonPrimitive("User age")) + put("default", JsonPrimitive(30)) + }, + ) + put( + "score", + buildJsonObject { + put("type", JsonPrimitive("number")) + put("description", JsonPrimitive("User score")) + put("default", JsonPrimitive(95.5)) + }, + ) + put( + "status", + buildJsonObject { + put("type", JsonPrimitive("string")) + put("description", JsonPrimitive("User status")) + put("default", JsonPrimitive("active")) + put( + "enum", + JsonArray( + listOf(JsonPrimitive("active"), JsonPrimitive("inactive"), JsonPrimitive("pending")), + ), + ) + }, + ) + put( + "verified", + buildJsonObject { + put("type", JsonPrimitive("boolean")) + put("description", JsonPrimitive("Verification status")) + put("default", JsonPrimitive(true)) + }, + ) + }, + required = listOf("name", "age", "score", "status", "verified"), + ) + val result = createElicitation( + "Please review and update the form fields with defaults", + schema, + ) + CallToolResult(listOf(TextContent(result.content.toString()))) + } + + // 11. Elicitation SEP1330 enums + addTool( + name = "test_elicitation_sep1330_enums", + description = "test_elicitation_sep1330_enums", + ) { + val schema = ElicitRequestParams.RequestedSchema( + properties = buildJsonObject { + // Untitled single-select + put( + "untitledSingle", + buildJsonObject { + put("type", JsonPrimitive("string")) + put( + "enum", + JsonArray( + listOf(JsonPrimitive("option1"), JsonPrimitive("option2"), JsonPrimitive("option3")), + ), + ) + }, + ) + // Titled single-select + put( + "titledSingle", + buildJsonObject { + put("type", JsonPrimitive("string")) + put( + "oneOf", + JsonArray( + listOf( + buildJsonObject { + put("const", JsonPrimitive("value1")) + put("title", JsonPrimitive("First Option")) + }, + buildJsonObject { + put("const", JsonPrimitive("value2")) + put("title", JsonPrimitive("Second Option")) + }, + buildJsonObject { + put("const", JsonPrimitive("value3")) + put("title", JsonPrimitive("Third Option")) + }, + ), + ), + ) + }, + ) + // Legacy titled (deprecated) + put( + "legacyEnum", + buildJsonObject { + put("type", JsonPrimitive("string")) + put( + "oneOf", + JsonArray( + listOf( + buildJsonObject { + put("const", JsonPrimitive("opt1")) + put("title", JsonPrimitive("Option One")) + }, + buildJsonObject { + put("const", JsonPrimitive("opt2")) + put("title", JsonPrimitive("Option Two")) + }, + buildJsonObject { + put("const", JsonPrimitive("opt3")) + put("title", JsonPrimitive("Option Three")) + }, + ), + ), + ) + }, + ) + // Untitled multi-select + put( + "untitledMulti", + buildJsonObject { + put("type", JsonPrimitive("array")) + put( + "items", + buildJsonObject { + put("type", JsonPrimitive("string")) + put( + "enum", + JsonArray( + listOf( + JsonPrimitive("option1"), + JsonPrimitive("option2"), + JsonPrimitive("option3"), + ), + ), + ) + }, + ) + }, + ) + // Titled multi-select + put( + "titledMulti", + buildJsonObject { + put("type", JsonPrimitive("array")) + put( + "items", + buildJsonObject { + put("type", JsonPrimitive("string")) + put( + "oneOf", + JsonArray( + listOf( + buildJsonObject { + put("const", JsonPrimitive("value1")) + put("title", JsonPrimitive("First Choice")) + }, + buildJsonObject { + put("const", JsonPrimitive("value2")) + put("title", JsonPrimitive("Second Choice")) + }, + buildJsonObject { + put("const", JsonPrimitive("value3")) + put("title", JsonPrimitive("Third Choice")) + }, + ), + ), + ) + }, + ) + }, + ) + }, + ) + val result = createElicitation( + "Please review and update the form fields with defaults", + schema, + ) + CallToolResult(listOf(TextContent(result.content.toString()))) + } + + // 12. Dynamic tool + val server = this + addTool( + name = "test_dynamic_tool", + description = "test_dynamic_tool", + ) { + // Add a temporary tool, triggering listChanged + server.addTool( + name = "test_dynamic_tool_temp", + description = "Temporary dynamic tool", + ) { + CallToolResult(listOf(TextContent("Temporary tool response"))) + } + delay(100.milliseconds) + // Remove the temporary tool, triggering listChanged again + server.removeTool("test_dynamic_tool_temp") + CallToolResult(listOf(TextContent("Dynamic tool executed successfully"))) + } + + // 13. Logging tool + addTool( + name = "test_tool_with_logging", + description = "test_tool_with_logging", + ) { + sendLoggingMessage( + LoggingMessageNotification( + LoggingMessageNotificationParams( + level = LoggingLevel.Info, + data = JsonPrimitive("Tool execution started"), + logger = "conformance", + ), + ), + ) + delay(50.milliseconds) + sendLoggingMessage( + LoggingMessageNotification( + LoggingMessageNotificationParams( + level = LoggingLevel.Info, + data = JsonPrimitive("Tool processing data"), + logger = "conformance", + ), + ), + ) + delay(50.milliseconds) + sendLoggingMessage( + LoggingMessageNotification( + LoggingMessageNotificationParams( + level = LoggingLevel.Info, + data = JsonPrimitive("Tool execution completed"), + logger = "conformance", + ), + ), + ) + CallToolResult(listOf(TextContent("Simple text content"))) + } + + // 14. add_numbers — used by tools_call client scenario + addTool( + name = "add_numbers", + description = "Adds two numbers together", + inputSchema = ToolSchema( + properties = buildJsonObject { + put("a", buildJsonObject { put("type", JsonPrimitive("number")) }) + put("b", buildJsonObject { put("type", JsonPrimitive("number")) }) + }, + required = listOf("a", "b"), + ), + ) { request -> + val a = request.arguments?.get("a")?.jsonPrimitive?.double ?: 0.0 + val b = request.arguments?.get("b")?.jsonPrimitive?.double ?: 0.0 + val sum = a + b + CallToolResult(listOf(TextContent("The sum of $a and $b is $sum"))) + } + + // 15. test_reconnection — SEP-1699, closes SSE stream to test client reconnection + addTool( + name = "test_reconnection", + description = "Tests SSE stream disconnection and client reconnection (SEP-1699)", + ) { + // SDK limitation: cannot access the JSONRPC request ID from the tool handler + // to close the SSE stream. Return success text; this test may fail at the + // conformance runner level because the stream isn't actually closed. + delay(100.milliseconds) + CallToolResult( + listOf( + TextContent( + "Reconnection test completed successfully. " + + "If you received this, the client properly reconnected after stream closure.", + ), + ), + ) + } + + // 16. json_schema_2020_12_tool — SEP-1613 + addTool( + name = "json_schema_2020_12_tool", + description = "Tool with JSON Schema 2020-12 features for conformance testing (SEP-1613)", + inputSchema = ToolSchema( + properties = buildJsonObject { + put( + "name", + buildJsonObject { + put("type", JsonPrimitive("string")) + }, + ) + put( + "address", + buildJsonObject { + put("type", JsonPrimitive("object")) + put( + "properties", + buildJsonObject { + put("street", buildJsonObject { put("type", JsonPrimitive("string")) }) + put("city", buildJsonObject { put("type", JsonPrimitive("string")) }) + }, + ) + }, + ) + }, + ), + ) { request -> + CallToolResult( + listOf(TextContent("JSON Schema 2020-12 tool called with: ${request.arguments}")), + ) + } + + // 17. test_client_elicitation_defaults — used by elicitation-sep1034-client-defaults scenario + addTool( + name = "test_client_elicitation_defaults", + description = "test_client_elicitation_defaults", + ) { + val schema = ElicitRequestParams.RequestedSchema( + properties = buildJsonObject { + put( + "name", + buildJsonObject { + put("type", JsonPrimitive("string")) + put("description", JsonPrimitive("User name")) + put("default", JsonPrimitive("John Doe")) + }, + ) + put( + "age", + buildJsonObject { + put("type", JsonPrimitive("integer")) + put("description", JsonPrimitive("User age")) + put("default", JsonPrimitive(30)) + }, + ) + put( + "score", + buildJsonObject { + put("type", JsonPrimitive("number")) + put("description", JsonPrimitive("User score")) + put("default", JsonPrimitive(95.5)) + }, + ) + put( + "status", + buildJsonObject { + put("type", JsonPrimitive("string")) + put("description", JsonPrimitive("User status")) + put("default", JsonPrimitive("active")) + put( + "enum", + JsonArray( + listOf(JsonPrimitive("active"), JsonPrimitive("inactive"), JsonPrimitive("pending")), + ), + ) + }, + ) + put( + "verified", + buildJsonObject { + put("type", JsonPrimitive("boolean")) + put("description", JsonPrimitive("Verification status")) + put("default", JsonPrimitive(true)) + }, + ) + }, + required = emptyList(), + ) + val result = createElicitation( + "Please review and update the form fields with defaults", + schema, + ) + CallToolResult(listOf(TextContent("Elicitation completed: action=${result.action}, content=${result.content}"))) + } + + // 18. test-tool — simple tool used by auth scenarios + addTool( + name = "test-tool", + description = "Simple test tool for auth scenarios", + ) { + CallToolResult(listOf(TextContent("Test tool executed successfully"))) + } +} diff --git a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/InMemoryEventStore.kt b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/InMemoryEventStore.kt new file mode 100644 index 0000000..ca7abb3 --- /dev/null +++ b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/InMemoryEventStore.kt @@ -0,0 +1,46 @@ +package io.modelcontextprotocol.kotlin.sdk.conformance + +import io.modelcontextprotocol.kotlin.sdk.server.EventStore +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import java.util.concurrent.ConcurrentHashMap +import kotlin.uuid.ExperimentalUuidApi +import kotlin.uuid.Uuid + +@OptIn(ExperimentalUuidApi::class) +class InMemoryEventStore : EventStore { + + private val events = ConcurrentHashMap>() + private val streamEvents = ConcurrentHashMap>() + + override suspend fun storeEvent(streamId: String, message: JSONRPCMessage): String { + val eventId = "$streamId::${System.currentTimeMillis()}_${Uuid.random()}" + events[eventId] = message to streamId + streamEvents.getOrPut(streamId) { mutableListOf() }.add(eventId) + return eventId + } + + override suspend fun replayEventsAfter( + lastEventId: String, + sender: suspend (eventId: String, message: JSONRPCMessage) -> Unit, + ): String { + val streamId = getStreamIdForEventId(lastEventId) + ?: error("Unknown event ID: $lastEventId") + val eventIds = streamEvents[streamId] ?: return streamId + + var found = false + for (eventId in eventIds) { + if (!found) { + if (eventId == lastEventId) found = true + continue + } + val (message, _) = events[eventId] ?: continue + sender(eventId, message) + } + return streamId + } + + override suspend fun getStreamIdForEventId(eventId: String): String? { + val idx = eventId.indexOf("::") + return if (idx >= 0) eventId.substring(0, idx) else null + } +} diff --git a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/JWTScenario.kt b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/JWTScenario.kt new file mode 100644 index 0000000..9a20681 --- /dev/null +++ b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/JWTScenario.kt @@ -0,0 +1,189 @@ +package io.modelcontextprotocol.kotlin.sdk.conformance.auth + +import io.ktor.client.HttpClient +import io.ktor.client.engine.cio.CIO +import io.ktor.client.plugins.sse.SSE +import io.ktor.client.request.forms.submitForm +import io.ktor.http.Parameters +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.client.ClientOptions +import io.modelcontextprotocol.kotlin.sdk.client.StreamableHttpClientTransport +import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import java.security.KeyFactory +import java.security.Signature +import java.security.spec.PKCS8EncodedKeySpec +import java.util.Base64 +import kotlin.uuid.ExperimentalUuidApi +import kotlin.uuid.Uuid + +// Client Credentials JWT scenario +internal suspend fun runClientCredentialsJwt(serverUrl: String) { + val ctx = conformanceContext() + val clientId = ctx.requiredString("client_id") + val privateKeyPem = ctx.requiredString("private_key_pem") + val signingAlgorithm = ctx["signing_algorithm"]?.jsonPrimitive?.content ?: "ES256" + + val httpClient = HttpClient(CIO) { + install(SSE) + followRedirects = false + } + + httpClient.use { client -> + val resourceMetadata = discoverResourceMetadata(client, serverUrl) + val authServer = resourceMetadata["authorization_servers"]?.jsonArray?.firstOrNull()?.jsonPrimitive?.content + ?: error("No authorization_servers in resource metadata") + val oauthMetadata = fetchOAuthMetadata(client, authServer) + val tokenEndpoint = oauthMetadata["token_endpoint"]?.jsonPrimitive?.content + ?: error("No token_endpoint in AS metadata") + val issuer = oauthMetadata["issuer"]?.jsonPrimitive?.content + ?: error("No issuer in AS metadata") + + // Create JWT client assertion + val assertion = createJwtAssertion(clientId, issuer, privateKeyPem, signingAlgorithm) + + // Exchange for token + val tokenResponse = client.submitForm( + url = tokenEndpoint, + formParameters = Parameters.build { + append("grant_type", "client_credentials") + append("client_id", clientId) + append("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer") + append("client_assertion", assertion) + }, + ) + val accessToken = extractAccessToken(tokenResponse) + + withBearerToken(accessToken) { authedClient -> + val transport = StreamableHttpClientTransport(authedClient, serverUrl) + val client = Client( + clientInfo = Implementation("conformance-client-credentials-jwt", "1.0.0"), + options = ClientOptions(capabilities = ClientCapabilities()), + ) + client.connect(transport) + client.listTools() + client.close() + } + } +} + +// JWT Assertion +@OptIn(ExperimentalUuidApi::class) +private fun createJwtAssertion( + clientId: String, + audience: String, + privateKeyPem: String, + algorithm: String, +): String { + val header = buildJsonObject { + put("alg", algorithm) + put("typ", "JWT") + }.toString() + + val now = System.currentTimeMillis() / 1000 + val payload = buildJsonObject { + put("iss", clientId) + put("sub", clientId) + put("aud", audience) + put("iat", now) + put("exp", now + 300) + put("jti", Uuid.random().toString()) + }.toString() + + val headerB64 = Base64.getUrlEncoder().withoutPadding().encodeToString(header.toByteArray()) + val payloadB64 = Base64.getUrlEncoder().withoutPadding().encodeToString(payload.toByteArray()) + val signingInput = "$headerB64.$payloadB64" + + val signature = signJwt(signingInput, privateKeyPem, algorithm) + return "$signingInput.$signature" +} + +private fun signJwt(input: String, privateKeyPem: String, algorithm: String): String { + val pemBody = privateKeyPem + .replace("-----BEGIN PRIVATE KEY-----", "") + .replace("-----END PRIVATE KEY-----", "") + .replace("-----BEGIN EC PRIVATE KEY-----", "") + .replace("-----END EC PRIVATE KEY-----", "") + .replace("-----BEGIN RSA PRIVATE KEY-----", "") + .replace("-----END RSA PRIVATE KEY-----", "") + .replace("\n", "") + .replace("\r", "") + .trim() + + val keyBytes = Base64.getDecoder().decode(pemBody) + val keySpec = PKCS8EncodedKeySpec(keyBytes) + + val (keyAlgorithm, signatureAlgorithm) = when (algorithm) { + "ES256" -> "EC" to "SHA256withECDSA" + "RS256" -> "RSA" to "SHA256withRSA" + else -> error("Unsupported signing algorithm: $algorithm") + } + + val keyFactory = KeyFactory.getInstance(keyAlgorithm) + val privateKey = keyFactory.generatePrivate(keySpec) + + val sig = Signature.getInstance(signatureAlgorithm) + sig.initSign(privateKey) + sig.update(input.toByteArray()) + val rawSignature = sig.sign() + + // For EC, convert DER to raw r||s format for JWS + val signatureBytes = if (keyAlgorithm == "EC") { + derToRawEcSignature(rawSignature) + } else { + rawSignature + } + + return Base64.getUrlEncoder().withoutPadding().encodeToString(signatureBytes) +} + +private fun derToRawEcSignature(der: ByteArray): ByteArray { + // DER format: 0x30 len 0x02 rLen r 0x02 sLen s + require(der.size >= 2) { "DER signature too short" } + + var offset = 2 // skip SEQUENCE tag and length + if (der[1].toInt() and 0x80 != 0) { + offset += (der[1].toInt() and 0x7f) + } + + // Read r + require(offset < der.size) { "DER signature truncated before r tag" } + check(der[offset] == 0x02.toByte()) { "Expected INTEGER tag for r" } + offset++ + require(offset < der.size) { "DER signature truncated before r length" } + val rLen = der[offset].toInt() and 0xff + offset++ + require(offset + rLen <= der.size) { "DER signature truncated in r value" } + val r = der.copyOfRange(offset, offset + rLen) + offset += rLen + + // Read s + require(offset < der.size) { "DER signature truncated before s tag" } + check(der[offset] == 0x02.toByte()) { "Expected INTEGER tag for s" } + offset++ + require(offset < der.size) { "DER signature truncated before s length" } + val sLen = der[offset].toInt() and 0xff + offset++ + require(offset + sLen <= der.size) { "DER signature truncated in s value" } + val s = der.copyOfRange(offset, offset + sLen) + + // Each component should be 32 bytes for P-256 + val componentLen = 32 + val result = ByteArray(componentLen * 2) + + // Copy r (may need padding or trimming of leading zero) + val rStart = if (r.size > componentLen) r.size - componentLen else 0 + val rDest = if (r.size < componentLen) componentLen - r.size else 0 + r.copyInto(result, rDest, rStart, r.size) + + // Copy s + val sStart = if (s.size > componentLen) s.size - componentLen else 0 + val sDest = componentLen + if (s.size < componentLen) componentLen - s.size else 0 + s.copyInto(result, sDest, sStart, s.size) + + return result +} diff --git a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/authCodeFlow.kt b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/authCodeFlow.kt new file mode 100644 index 0000000..c59e8e0 --- /dev/null +++ b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/authCodeFlow.kt @@ -0,0 +1,154 @@ +package io.modelcontextprotocol.kotlin.sdk.conformance.auth + +import io.ktor.client.HttpClient +import io.ktor.client.engine.cio.CIO +import io.ktor.client.plugins.HttpSend +import io.ktor.client.plugins.plugin +import io.ktor.client.plugins.sse.SSE +import io.ktor.http.HttpHeaders +import io.ktor.http.HttpStatusCode +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.client.ClientOptions +import io.modelcontextprotocol.kotlin.sdk.client.StreamableHttpClientTransport +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.Implementation +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonPrimitive +import java.util.UUID + +internal suspend fun runAuthClient(serverUrl: String) { + val httpClient = HttpClient(CIO) { + install(SSE) + followRedirects = false + } + + var accessToken: String? = null + var authAttempts = 0 + // Cache discovery and credentials across retries + var cachedDiscovery: DiscoveryResult? = null + var cachedCredentials: ClientCredentials? = null + + httpClient.plugin(HttpSend).intercept { request -> + // Add existing token if available + if (accessToken != null) { + request.headers.remove(HttpHeaders.Authorization) + request.headers.append(HttpHeaders.Authorization, "Bearer $accessToken") + } + + val response = execute(request) + val status = response.response.status + + // Determine if we need to (re-)authorize + val needsAuth = status == HttpStatusCode.Unauthorized + val wwwAuth = response.response.headers[HttpHeaders.WWWAuthenticate] ?: "" + val stepUpScope = if (status == HttpStatusCode.Forbidden) parseStepUpScope(wwwAuth) else null + val needsStepUp = stepUpScope != null + + if ((needsAuth || needsStepUp) && authAttempts < 3) { + authAttempts++ + + // Discover metadata (cache across retries) + if (cachedDiscovery == null) { + val resourceMetadataUrl = extractParam(wwwAuth, "resource_metadata") + cachedDiscovery = discoverOAuthMetadata(httpClient, serverUrl, resourceMetadataUrl) + } + val discovery: DiscoveryResult = cachedDiscovery + + // Validate PRM resource matches server URL (RFC 8707) + val discoveredResource = discovery.resourceUrl + if (discoveredResource != null) { + val normalizedResource = discoveredResource.trimEnd('/') + val normalizedServerUrl = serverUrl.trimEnd('/') + val matches = normalizedServerUrl == normalizedResource || + normalizedServerUrl.startsWith("$normalizedResource/") + require(matches) { + "PRM resource mismatch: resource='$discoveredResource' does not match server URL='$serverUrl'" + } + } + + val metadata = discovery.asMetadata + + val authEndpoint = metadata["authorization_endpoint"]?.jsonPrimitive?.content + ?: error("No authorization_endpoint in metadata") + val tokenEndpoint = metadata["token_endpoint"]?.jsonPrimitive?.content + ?: error("No token_endpoint in metadata") + + val tokenEndpointAuthMethods = metadata["token_endpoint_auth_methods_supported"] + ?.jsonArray?.map { it.jsonPrimitive.content } + ?: listOf("client_secret_post") + val tokenAuthMethod = tokenEndpointAuthMethods.firstOrNull() ?: "client_secret_post" + + // Verify PKCE support + verifyPkceSupport(metadata) + + // Resolve client credentials (cache across retries) + if (cachedCredentials == null) { + cachedCredentials = resolveClientCredentials(httpClient, metadata) + } + val creds: ClientCredentials = cachedCredentials + + // Determine scope + val scope = if (needsStepUp) { + stepUpScope + } else { + val wwwAuthScope = extractParam(wwwAuth, "scope") + selectScope(wwwAuthScope, discovery.scopesSupported) + } + + // PKCE + val codeVerifier = generateCodeVerifier() + val codeChallenge = generateCodeChallenge(codeVerifier) + + // CSRF state parameter + val state = UUID.randomUUID().toString() + + // Build authorization URL + val authUrl = buildAuthorizationUrl( + authEndpoint, + creds.clientId, + CALLBACK_URL, + codeChallenge, + scope, + discovery.resourceUrl, + state, + ) + + // Follow the authorization redirect to get auth code + val authCode = followAuthorizationRedirect(httpClient, authUrl, CALLBACK_URL, state) + + // Exchange code for tokens + accessToken = exchangeCodeForTokens( + httpClient, + tokenEndpoint, + authCode, + creds.clientId, + creds.clientSecret, + CALLBACK_URL, + codeVerifier, + tokenAuthMethod, + discovery.resourceUrl, + ) + + // Retry the original request with the token + request.headers.remove(HttpHeaders.Authorization) + request.headers.append(HttpHeaders.Authorization, "Bearer $accessToken") + execute(request) + } else { + response + } + } + + httpClient.use { client -> + val transport = StreamableHttpClientTransport(client, serverUrl) + val mcpClient = Client( + clientInfo = Implementation("test-auth-client", "1.0.0"), + options = ClientOptions(capabilities = ClientCapabilities()), + ) + mcpClient.connect(transport) + mcpClient.listTools() + mcpClient.callTool(CallToolRequest(CallToolRequestParams(name = "test-tool"))) + mcpClient.close() + } +} diff --git a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/basicScenario.kt b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/basicScenario.kt new file mode 100644 index 0000000..a0d6d86 --- /dev/null +++ b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/basicScenario.kt @@ -0,0 +1,54 @@ +package io.modelcontextprotocol.kotlin.sdk.conformance.auth + +import io.ktor.client.HttpClient +import io.ktor.client.engine.cio.CIO +import io.ktor.client.plugins.sse.SSE +import io.ktor.client.request.forms.submitForm +import io.ktor.client.request.header +import io.ktor.http.HttpHeaders +import io.ktor.http.Parameters +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.client.ClientOptions +import io.modelcontextprotocol.kotlin.sdk.client.StreamableHttpClientTransport +import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import java.util.Base64 + +// Client Credentials Basic scenario +internal suspend fun runClientCredentialsBasic(serverUrl: String) { + val ctx = conformanceContext() + val clientId = ctx.requiredString("client_id") + val clientSecret = ctx.requiredString("client_secret") + + val httpClient = HttpClient(CIO) { + install(SSE) + followRedirects = false + } + + httpClient.use { client -> + val tokenEndpoint = discoverTokenEndpoint(client, serverUrl) + + // Exchange credentials for token using Basic auth + val basicAuth = Base64.getEncoder().encodeToString("$clientId:$clientSecret".toByteArray()) + val tokenResponse = client.submitForm( + url = tokenEndpoint, + formParameters = Parameters.build { + append("grant_type", "client_credentials") + }, + ) { + header(HttpHeaders.Authorization, "Basic $basicAuth") + } + val accessToken = extractAccessToken(tokenResponse) + + withBearerToken(accessToken) { authedClient -> + val transport = StreamableHttpClientTransport(authedClient, serverUrl) + val client = Client( + clientInfo = Implementation("conformance-client-credentials-basic", "1.0.0"), + options = ClientOptions(capabilities = ClientCapabilities()), + ) + client.connect(transport) + client.listTools() + client.close() + } + } +} diff --git a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/clientRegistration.kt b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/clientRegistration.kt new file mode 100644 index 0000000..8e84a15 --- /dev/null +++ b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/clientRegistration.kt @@ -0,0 +1,73 @@ +package io.modelcontextprotocol.kotlin.sdk.conformance.auth + +import io.ktor.client.HttpClient +import io.ktor.client.request.post +import io.ktor.client.request.setBody +import io.ktor.client.statement.bodyAsText +import io.ktor.http.ContentType +import io.ktor.http.contentType +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.buildJsonArray +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put + +internal data class ClientCredentials(val clientId: String, val clientSecret: String?) + +/** + * Resolve client credentials per spec priority: + * 1. Pre-registered (from MCP_CONFORMANCE_CONTEXT) + * 2. CIMD (client_id_metadata_document_supported) + * 3. Dynamic registration (registration_endpoint) + * 4. Error + */ +internal suspend fun resolveClientCredentials(httpClient: HttpClient, asMetadata: JsonObject): ClientCredentials { + // 1. Pre-registered + val contextJson = System.getenv("MCP_CONFORMANCE_CONTEXT") + if (contextJson != null) { + val ctx = json.parseToJsonElement(contextJson).jsonObject + val clientId = ctx["client_id"]?.jsonPrimitive?.content + if (clientId != null) { + val clientSecret = ctx["client_secret"]?.jsonPrimitive?.content + return ClientCredentials(clientId, clientSecret) + } + } + + // 2. CIMD + val cimdSupported = asMetadata["client_id_metadata_document_supported"] + ?.jsonPrimitive?.content?.toBoolean() ?: false + if (cimdSupported) { + return ClientCredentials(CIMD_CLIENT_METADATA_URL, null) + } + + // 3. Dynamic registration + val registrationEndpoint = asMetadata["registration_endpoint"]?.jsonPrimitive?.content + if (registrationEndpoint != null) { + return dynamicClientRegistration(httpClient, registrationEndpoint) + } + + error("No way to register client: no pre-registered credentials, CIMD not supported, and no registration_endpoint") +} + +private suspend fun dynamicClientRegistration( + httpClient: HttpClient, + registrationEndpoint: String, +): ClientCredentials { + val regBody = buildJsonObject { + put("client_name", "test-auth-client") + put("redirect_uris", buildJsonArray { add(kotlinx.serialization.json.JsonPrimitive(CALLBACK_URL)) }) + put("grant_types", buildJsonArray { add(kotlinx.serialization.json.JsonPrimitive("authorization_code")) }) + put("response_types", buildJsonArray { add(kotlinx.serialization.json.JsonPrimitive("code")) }) + put("token_endpoint_auth_method", "client_secret_post") + } + + val response = httpClient.post(registrationEndpoint) { + contentType(ContentType.Application.Json) + setBody(regBody.toString()) + } + val regJson = json.parseToJsonElement(response.bodyAsText()).jsonObject + val clientId = regJson["client_id"]?.jsonPrimitive?.content ?: error("No client_id in registration response") + val clientSecret = regJson["client_secret"]?.jsonPrimitive?.content + return ClientCredentials(clientId, clientSecret) +} diff --git a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/crossAppAccessScenario.kt b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/crossAppAccessScenario.kt new file mode 100644 index 0000000..db4b7f0 --- /dev/null +++ b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/crossAppAccessScenario.kt @@ -0,0 +1,82 @@ +package io.modelcontextprotocol.kotlin.sdk.conformance.auth + +import io.ktor.client.HttpClient +import io.ktor.client.engine.cio.CIO +import io.ktor.client.plugins.sse.SSE +import io.ktor.client.request.forms.submitForm +import io.ktor.client.request.header +import io.ktor.http.HttpHeaders +import io.ktor.http.Parameters +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.client.ClientOptions +import io.modelcontextprotocol.kotlin.sdk.client.StreamableHttpClientTransport +import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonPrimitive +import java.util.Base64 + +// SEP-990 Enterprise Managed OAuth: Cross-App Access complete flow +internal suspend fun runCrossAppAccess(serverUrl: String) { + val ctx = conformanceContext() + val clientId = ctx.requiredString("client_id") + val clientSecret = ctx.requiredString("client_secret") + val idpIdToken = ctx.requiredString("idp_id_token") + val idpTokenEndpoint = ctx.requiredString("idp_token_endpoint") + + val httpClient = HttpClient(CIO) { + install(SSE) + followRedirects = false + } + + httpClient.use { client -> + // Discover PRM + AS metadata + val resourceMeta = discoverResourceMetadata(client, serverUrl) + val resourceUrl = resourceMeta["resource"]?.jsonPrimitive?.content + ?: error("No resource in resource metadata") + val authServer = resourceMeta["authorization_servers"]?.jsonArray?.firstOrNull()?.jsonPrimitive?.content + ?: error("No authorization_servers in resource metadata") + val asMeta = fetchOAuthMetadata(client, authServer) + val tokenEndpoint = asMeta["token_endpoint"]?.jsonPrimitive?.content + ?: error("No token_endpoint in AS metadata") + + // RFC 8693 Token Exchange at IDP: exchange ID token for ID-JAG + val idpResponse = client.submitForm( + url = idpTokenEndpoint, + formParameters = Parameters.build { + append("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange") + append("subject_token", idpIdToken) + append("subject_token_type", "urn:ietf:params:oauth:token-type:id_token") + append("requested_token_type", "urn:ietf:params:oauth:token-type:id-jag") + append("audience", authServer) + append("resource", resourceUrl) + }, + ) + val idJag = extractAccessToken(idpResponse) + + // RFC 7523 JWT Bearer Grant at AS with Basic auth + val basicAuth = Base64.getEncoder().encodeToString("$clientId:$clientSecret".toByteArray()) + val asResponse = client.submitForm( + url = tokenEndpoint, + formParameters = Parameters.build { + append("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer") + append("assertion", idJag) + }, + ) { + header(HttpHeaders.Authorization, "Basic $basicAuth") + } + val accessToken = extractAccessToken(asResponse) + + // Use access token for MCP requests + withBearerToken(accessToken) { authedClient -> + val transport = StreamableHttpClientTransport(authedClient, serverUrl) + val mcpClient = Client( + clientInfo = Implementation("conformance-cross-app-access", "1.0.0"), + options = ClientOptions(capabilities = ClientCapabilities()), + ) + mcpClient.connect(transport) + mcpClient.listTools() + mcpClient.close() + } + } +} diff --git a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/discovery.kt b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/discovery.kt new file mode 100644 index 0000000..73979db --- /dev/null +++ b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/discovery.kt @@ -0,0 +1,48 @@ +package io.modelcontextprotocol.kotlin.sdk.conformance.auth + +import io.ktor.client.HttpClient +import io.ktor.client.request.get +import io.ktor.client.statement.bodyAsText +import io.ktor.http.isSuccess +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive + +internal data class DiscoveryResult( + val asMetadata: JsonObject, + val resourceUrl: String?, + val scopesSupported: List?, +) + +internal suspend fun discoverOAuthMetadata( + httpClient: HttpClient, + serverUrl: String, + resourceMetadataUrl: String?, +): DiscoveryResult { + // Get resource metadata + val resourceMeta = if (resourceMetadataUrl != null) { + val resp = httpClient.get(resourceMetadataUrl) + if (!resp.status.isSuccess()) { + error("Failed to fetch resource metadata from $resourceMetadataUrl: ${resp.status}") + } + json.parseToJsonElement(resp.bodyAsText()).jsonObject + } else { + discoverResourceMetadata(httpClient, serverUrl) + } + + val resourceUrl = resourceMeta["resource"]?.jsonPrimitive?.content + val scopesSupported = resourceMeta["scopes_supported"] + ?.jsonArray?.map { it.jsonPrimitive.content } + val authServer = resourceMeta["authorization_servers"]?.jsonArray?.firstOrNull()?.jsonPrimitive?.content + + val oauthMeta = if (authServer != null) { + fetchOAuthMetadata(httpClient, authServer) + } else { + // Fallback: try well-known on server URL origin + val origin = extractOrigin(serverUrl) + fetchOAuthMetadata(httpClient, origin) + } + + return DiscoveryResult(oauthMeta, resourceUrl, scopesSupported) +} diff --git a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/pkce.kt b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/pkce.kt new file mode 100644 index 0000000..e1c5419 --- /dev/null +++ b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/pkce.kt @@ -0,0 +1,30 @@ +package io.modelcontextprotocol.kotlin.sdk.conformance.auth + +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonPrimitive +import java.security.MessageDigest +import java.util.Base64 + +internal fun generateCodeVerifier(): String { + val bytes = ByteArray(32) + java.security.SecureRandom().nextBytes(bytes) + return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes) +} + +internal fun generateCodeChallenge(verifier: String): String { + val digest = MessageDigest.getInstance("SHA-256").digest(verifier.toByteArray(Charsets.US_ASCII)) + return Base64.getUrlEncoder().withoutPadding().encodeToString(digest) +} + +/** + * Verify that the AS metadata advertises S256 in code_challenge_methods_supported. + * Abort if PKCE S256 is not supported. + */ +internal fun verifyPkceSupport(asMetadata: JsonObject) { + val methods = asMetadata["code_challenge_methods_supported"] + ?.jsonArray?.map { it.jsonPrimitive.content } + require(methods != null && "S256" in methods) { + "Authorization server does not support PKCE S256 (code_challenge_methods_supported: $methods)" + } +} diff --git a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/registration.kt b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/registration.kt new file mode 100644 index 0000000..4964f20 --- /dev/null +++ b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/registration.kt @@ -0,0 +1,32 @@ +package io.modelcontextprotocol.kotlin.sdk.conformance.auth + +import io.modelcontextprotocol.kotlin.sdk.conformance.scenarioHandlers + +// Registration +fun registerAuthScenarios() { + val authScenarios = listOf( + "auth/metadata-default", + "auth/metadata-var1", + "auth/metadata-var2", + "auth/metadata-var3", + "auth/basic-cimd", + "auth/scope-from-www-authenticate", + "auth/scope-from-scopes-supported", + "auth/scope-omitted-when-undefined", + "auth/scope-step-up", + "auth/scope-retry-limit", + "auth/token-endpoint-auth-basic", + "auth/token-endpoint-auth-post", + "auth/token-endpoint-auth-none", + "auth/resource-mismatch", + "auth/pre-registration", + "auth/2025-03-26-oauth-metadata-backcompat", + "auth/2025-03-26-oauth-endpoint-fallback", + ) + for (name in authScenarios) { + scenarioHandlers[name] = ::runAuthClient + } + scenarioHandlers["auth/client-credentials-jwt"] = ::runClientCredentialsJwt + scenarioHandlers["auth/client-credentials-basic"] = ::runClientCredentialsBasic + scenarioHandlers["auth/cross-app-access-complete-flow"] = ::runCrossAppAccess +} diff --git a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/scopeHandling.kt b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/scopeHandling.kt new file mode 100644 index 0000000..33b69ce --- /dev/null +++ b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/scopeHandling.kt @@ -0,0 +1,29 @@ +package io.modelcontextprotocol.kotlin.sdk.conformance.auth + +internal fun extractParam(wwwAuth: String, param: String): String? { + val regex = Regex("""$param="([^"]+)"""") + return regex.find(wwwAuth)?.groupValues?.get(1) +} + +/** + * Select scope per MCP spec priority: + * 1. scope from WWW-Authenticate header + * 2. scopes_supported from Protected Resource Metadata (space-joined) + * 3. null (omit scope entirely) + */ +internal fun selectScope(wwwAuthScope: String?, scopesSupported: List?): String? { + if (wwwAuthScope != null) return wwwAuthScope + if (!scopesSupported.isNullOrEmpty()) return scopesSupported.joinToString(" ") + return null +} + +/** + * Detect 403 with error="insufficient_scope" and extract the new scope. + * Returns the scope string if step-up is needed, null otherwise. + */ +internal fun parseStepUpScope(wwwAuth: String?): String? { + if (wwwAuth == null) return null + val error = extractParam(wwwAuth, "error") + if (error != "insufficient_scope") return null + return extractParam(wwwAuth, "scope") +} diff --git a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/tokenExchange.kt b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/tokenExchange.kt new file mode 100644 index 0000000..0c79bb9 --- /dev/null +++ b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/tokenExchange.kt @@ -0,0 +1,174 @@ +package io.modelcontextprotocol.kotlin.sdk.conformance.auth + +import io.ktor.client.HttpClient +import io.ktor.client.request.forms.submitForm +import io.ktor.client.request.get +import io.ktor.client.request.header +import io.ktor.client.statement.bodyAsText +import io.ktor.http.HttpHeaders +import io.ktor.http.HttpStatusCode +import io.ktor.http.Parameters +import io.ktor.http.isSuccess +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import java.net.URI +import java.net.URLEncoder +import java.util.Base64 + +internal fun buildAuthorizationUrl( + authEndpoint: String, + clientId: String, + redirectUri: String, + codeChallenge: String, + scope: String?, + resource: String?, + state: String, +): String { + val params = buildString { + append("response_type=code") + append("&client_id=${URLEncoder.encode(clientId, "UTF-8")}") + append("&redirect_uri=${URLEncoder.encode(redirectUri, "UTF-8")}") + append("&code_challenge=${URLEncoder.encode(codeChallenge, "UTF-8")}") + append("&code_challenge_method=S256") + append("&state=${URLEncoder.encode(state, "UTF-8")}") + if (scope != null) { + append("&scope=${URLEncoder.encode(scope, "UTF-8")}") + } + if (resource != null) { + append("&resource=${URLEncoder.encode(resource, "UTF-8")}") + } + } + return if (authEndpoint.contains("?")) "$authEndpoint&$params" else "$authEndpoint?$params" +} + +internal suspend fun followAuthorizationRedirect( + httpClient: HttpClient, + authUrl: String, + expectedCallbackUrl: String, + expectedState: String, +): String { + val response = httpClient.get(authUrl) + + if (response.status == HttpStatusCode.Found || + response.status == HttpStatusCode.MovedPermanently || + response.status == HttpStatusCode.TemporaryRedirect || + response.status == HttpStatusCode.SeeOther + ) { + val location = response.headers[HttpHeaders.Location] + ?: error("No Location header in redirect response") + + require(location.startsWith(expectedCallbackUrl)) { + "Redirect location does not match expected callback URL" + } + + val uri = URI(location) + val queryParams = uri.query?.split("&")?.mapNotNull { + val parts = it.split("=", limit = 2) + if (parts.size == 2) parts[0] to java.net.URLDecoder.decode(parts[1], "UTF-8") else null + }?.toMap() ?: emptyMap() + + val returnedState = queryParams["state"] + require(returnedState == expectedState) { + "State parameter mismatch in authorization redirect" + } + + return queryParams["code"] ?: error("No authorization code in redirect response") + } + + error("Expected redirect from auth endpoint, got ${response.status}") +} + +internal suspend fun exchangeCodeForTokens( + httpClient: HttpClient, + tokenEndpoint: String, + code: String, + clientId: String, + clientSecret: String?, + redirectUri: String, + codeVerifier: String, + tokenAuthMethod: String, + resource: String?, +): String { + val response = when (tokenAuthMethod) { + "client_secret_basic" -> { + val basicAuth = Base64.getEncoder() + .encodeToString("$clientId:${clientSecret ?: ""}".toByteArray()) + httpClient.submitForm( + url = tokenEndpoint, + formParameters = Parameters.build { + append("grant_type", "authorization_code") + append("code", code) + append("redirect_uri", redirectUri) + append("code_verifier", codeVerifier) + if (resource != null) { + append("resource", resource) + } + }, + ) { + header(HttpHeaders.Authorization, "Basic $basicAuth") + } + } + + "none" -> { + httpClient.submitForm( + url = tokenEndpoint, + formParameters = Parameters.build { + append("grant_type", "authorization_code") + append("code", code) + append("client_id", clientId) + append("redirect_uri", redirectUri) + append("code_verifier", codeVerifier) + if (resource != null) { + append("resource", resource) + } + }, + ) + } + + else -> { + // client_secret_post (default) + httpClient.submitForm( + url = tokenEndpoint, + formParameters = Parameters.build { + append("grant_type", "authorization_code") + append("code", code) + append("client_id", clientId) + if (clientSecret != null) { + append("client_secret", clientSecret) + } + append("redirect_uri", redirectUri) + append("code_verifier", codeVerifier) + if (resource != null) { + append("resource", resource) + } + }, + ) + } + } + + // Check HTTP status + if (!response.status.isSuccess()) { + val body = response.bodyAsText() + val errorDetail = try { + val obj = json.parseToJsonElement(body).jsonObject + val err = obj["error"]?.jsonPrimitive?.content ?: "unknown" + val desc = obj["error_description"]?.jsonPrimitive?.content + if (desc != null) "$err: $desc" else err + } catch (_: Exception) { + body + } + error("Token exchange failed (${response.status}): $errorDetail") + } + + val tokenJson = json.parseToJsonElement(response.bodyAsText()).jsonObject + + // Check for error field in response body (some servers return 200 with error) + val errorField = tokenJson["error"]?.jsonPrimitive?.content + if (errorField != null) { + val desc = tokenJson["error_description"]?.jsonPrimitive?.content + error("Token exchange error: $errorField${if (desc != null) " - $desc" else ""}") + } + + return tokenJson["access_token"]?.jsonPrimitive?.content + ?: error("No access_token in token response") +} diff --git a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/utils.kt b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/utils.kt new file mode 100644 index 0000000..4bc75a0 --- /dev/null +++ b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/auth/utils.kt @@ -0,0 +1,120 @@ +package io.modelcontextprotocol.kotlin.sdk.conformance.auth + +import io.ktor.client.HttpClient +import io.ktor.client.engine.cio.CIO +import io.ktor.client.plugins.HttpSend +import io.ktor.client.plugins.plugin +import io.ktor.client.plugins.sse.SSE +import io.ktor.client.request.get +import io.ktor.client.statement.HttpResponse +import io.ktor.client.statement.bodyAsText +import io.ktor.http.HttpHeaders +import io.ktor.http.isSuccess +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import java.net.URI +import kotlin.text.ifEmpty + +internal val json = Json { ignoreUnknownKeys = true } + +internal fun conformanceContext(): JsonObject { + val contextJson = System.getenv("MCP_CONFORMANCE_CONTEXT") + ?: error("MCP_CONFORMANCE_CONTEXT not set") + return json.parseToJsonElement(contextJson).jsonObject +} + +internal fun JsonObject.requiredString(key: String): String = this[key]?.jsonPrimitive?.content ?: error("Missing $key") + +internal const val CIMD_CLIENT_METADATA_URL = "https://conformance-test.local/client-metadata.json" +internal const val CALLBACK_URL = "http://localhost:3000/callback" + +internal fun extractOrigin(url: String): String { + val uri = URI(url) + return "${uri.scheme}://${uri.host}${if (uri.port > 0) ":${uri.port}" else ""}" +} + +internal suspend fun discoverResourceMetadata(httpClient: HttpClient, serverUrl: String): JsonObject { + val origin = extractOrigin(serverUrl) + val path = URI(serverUrl).path.ifEmpty { "/" } + + // Try RFC 9728 format first: /.well-known/oauth-protected-resource/ + val wellKnownUrl = "$origin/.well-known/oauth-protected-resource$path" + val response = httpClient.get(wellKnownUrl) + if (response.status.isSuccess()) { + return json.parseToJsonElement(response.bodyAsText()).jsonObject + } + + // Fallback: try root + val fallbackUrl = "$origin/.well-known/oauth-protected-resource" + val fallbackResponse = httpClient.get(fallbackUrl) + if (!fallbackResponse.status.isSuccess()) { + error( + "Failed to discover resource metadata at $wellKnownUrl (${response.status}) and $fallbackUrl (${fallbackResponse.status})", + ) + } + return json.parseToJsonElement(fallbackResponse.bodyAsText()).jsonObject +} + +internal suspend fun fetchOAuthMetadata(httpClient: HttpClient, authServerUrl: String): JsonObject { + val origin = extractOrigin(authServerUrl) + val path = URI(authServerUrl).path.ifEmpty { "/" } + + // RFC 8414 §3: /.well-known/oauth-authorization-server/ + val oauthUrl = "$origin/.well-known/oauth-authorization-server$path" + val oauthResponse = httpClient.get(oauthUrl) + if (oauthResponse.status.isSuccess()) { + return json.parseToJsonElement(oauthResponse.bodyAsText()).jsonObject + } + + // OIDC Discovery with path insertion: /.well-known/openid-configuration/ + val oidcPathUrl = "$origin/.well-known/openid-configuration$path" + val oidcPathResponse = httpClient.get(oidcPathUrl) + if (oidcPathResponse.status.isSuccess()) { + return json.parseToJsonElement(oidcPathResponse.bodyAsText()).jsonObject + } + + // Fallback: OpenID Connect discovery (issuer + /.well-known/openid-configuration) + val oidcUrl = "$authServerUrl/.well-known/openid-configuration" + val oidcResponse = httpClient.get(oidcUrl) + if (oidcResponse.status.isSuccess()) { + return json.parseToJsonElement(oidcResponse.bodyAsText()).jsonObject + } + + error( + "Failed to fetch OAuth metadata from $oauthUrl (${oauthResponse.status}) and $oidcUrl (${oidcResponse.status})", + ) +} + +internal suspend fun discoverTokenEndpoint(httpClient: HttpClient, serverUrl: String): String { + val resourceMetadata = discoverResourceMetadata(httpClient, serverUrl) + val authServer = resourceMetadata["authorization_servers"]?.jsonArray?.firstOrNull()?.jsonPrimitive?.content + ?: error("No authorization_servers in resource metadata") + + val oauthMetadata = fetchOAuthMetadata(httpClient, authServer) + return oauthMetadata["token_endpoint"]?.jsonPrimitive?.content + ?: error("No token_endpoint") +} + +internal suspend fun extractAccessToken(tokenResponse: HttpResponse): String { + if (!tokenResponse.status.isSuccess()) { + error("Token request failed: ${tokenResponse.status}") + } + val tokenJson = json.parseToJsonElement(tokenResponse.bodyAsText()).jsonObject + return tokenJson["access_token"]?.jsonPrimitive?.content + ?: error("No access_token in token response") +} + +internal suspend fun withBearerToken(accessToken: String, block: suspend (HttpClient) -> T): T { + val client = HttpClient(CIO) { + install(SSE) + } + client.plugin(HttpSend).intercept { request -> + request.headers.remove(HttpHeaders.Authorization) + request.headers.append(HttpHeaders.Authorization, "Bearer $accessToken") + execute(request) + } + return client.use { block(it) } +} diff --git a/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceClient.kt b/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceClient.kt deleted file mode 100644 index 09ea50b..0000000 --- a/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceClient.kt +++ /dev/null @@ -1,85 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.conformance - -import io.github.oshai.kotlinlogging.KotlinLogging -import io.ktor.client.HttpClient -import io.ktor.client.engine.cio.CIO -import io.ktor.client.plugins.sse.SSE -import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.client.StreamableHttpClientTransport -import io.modelcontextprotocol.kotlin.sdk.shared.Transport -import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest -import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequestParams -import io.modelcontextprotocol.kotlin.sdk.types.Implementation -import kotlinx.coroutines.runBlocking -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.buildJsonObject - -private val logger = KotlinLogging.logger {} - -fun main(args: Array) { - require(args.isNotEmpty()) { - "Server URL must be provided as an argument" - } - - val serverUrl = args.last() - logger.info { "Connecting to test server at: $serverUrl" } - - val httpClient = HttpClient(CIO) { - install(SSE) - } - val transport: Transport = StreamableHttpClientTransport(httpClient, serverUrl) - - val client = Client( - clientInfo = Implementation( - name = "kotlin-conformance-client", - version = "1.0.0", - ), - ) - - var exitCode = 0 - - runBlocking { - try { - client.connect(transport) - logger.info { "✅ Connected to server successfully" } - - try { - val tools = client.listTools() - logger.info { "Available tools: ${tools.tools.map { it.name }}" } - - if (tools.tools.isNotEmpty()) { - val toolName = tools.tools.first().name - logger.info { "Calling tool: $toolName" } - - val result = client.callTool( - CallToolRequest( - params = CallToolRequestParams( - name = toolName, - arguments = buildJsonObject { - put("input", JsonPrimitive("test")) - }, - ), - ), - ) - logger.info { "Tool result: ${result.content}" } - } - } catch (e: Exception) { - logger.debug(e) { "Error during tool operations (may be expected for some scenarios)" } - } - - logger.info { "✅ Client operations completed successfully" } - } catch (e: Exception) { - logger.error(e) { "❌ Client failed" } - exitCode = 1 - } finally { - try { - transport.close() - } catch (e: Exception) { - logger.warn(e) { "Error closing transport" } - } - httpClient.close() - } - } - - kotlin.system.exitProcess(exitCode) -} diff --git a/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceServer.kt b/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceServer.kt deleted file mode 100644 index b3331dd..0000000 --- a/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceServer.kt +++ /dev/null @@ -1,426 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.conformance - -import io.github.oshai.kotlinlogging.KotlinLogging -import io.ktor.http.ContentType -import io.ktor.http.HttpStatusCode -import io.ktor.server.application.ApplicationCall -import io.ktor.server.application.install -import io.ktor.server.cio.CIO -import io.ktor.server.engine.embeddedServer -import io.ktor.server.request.header -import io.ktor.server.request.receiveText -import io.ktor.server.response.header -import io.ktor.server.response.respond -import io.ktor.server.response.respondText -import io.ktor.server.response.respondTextWriter -import io.ktor.server.routing.delete -import io.ktor.server.routing.get -import io.ktor.server.routing.post -import io.ktor.server.routing.routing -import io.ktor.server.websocket.WebSockets -import io.ktor.server.websocket.webSocket -import io.modelcontextprotocol.kotlin.sdk.server.Server -import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions -import io.modelcontextprotocol.kotlin.sdk.server.WebSocketMcpServerTransport -import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport -import io.modelcontextprotocol.kotlin.sdk.shared.TransportSendOptions -import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult -import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult -import io.modelcontextprotocol.kotlin.sdk.types.Implementation -import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCError -import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage -import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest -import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse -import io.modelcontextprotocol.kotlin.sdk.types.McpJson -import io.modelcontextprotocol.kotlin.sdk.types.PromptArgument -import io.modelcontextprotocol.kotlin.sdk.types.PromptMessage -import io.modelcontextprotocol.kotlin.sdk.types.RPCError -import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult -import io.modelcontextprotocol.kotlin.sdk.types.RequestId -import io.modelcontextprotocol.kotlin.sdk.types.Role -import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities -import io.modelcontextprotocol.kotlin.sdk.types.TextContent -import io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents -import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.launch -import kotlinx.coroutines.withTimeoutOrNull -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonNull -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.contentOrNull -import kotlinx.serialization.json.decodeFromJsonElement -import kotlinx.serialization.json.jsonPrimitive -import kotlinx.serialization.json.put -import java.util.UUID -import java.util.concurrent.ConcurrentHashMap - -private val logger = KotlinLogging.logger {} -private val serverTransports = ConcurrentHashMap() -private val jsonFormat = Json { ignoreUnknownKeys = true } - -private const val SESSION_CREATION_TIMEOUT_MS = 2000L -private const val REQUEST_TIMEOUT_MS = 10_000L -private const val MESSAGE_QUEUE_CAPACITY = 256 - -private fun isInitializeRequest(json: JsonElement): Boolean = - json is JsonObject && json["method"]?.jsonPrimitive?.contentOrNull == "initialize" - -@Suppress("CyclomaticComplexMethod", "LongMethod") -fun main(args: Array) { - val port = args.getOrNull(0)?.toIntOrNull() ?: 3000 - - logger.info { "Starting MCP Conformance Server on port $port" } - - embeddedServer(CIO, port = port, host = "127.0.0.1") { - install(WebSockets) - - routing { - webSocket("/ws") { - logger.info { "WebSocket connection established" } - val transport = WebSocketMcpServerTransport(this) - val server = createConformanceServer() - - try { - server.createSession(transport) - } catch (e: Exception) { - logger.error(e) { "Error in WebSocket session" } - throw e - } - } - - get("/mcp") { - val sessionId = call.request.header("mcp-session-id") - ?: run { - call.respond(HttpStatusCode.BadRequest, "Missing mcp-session-id header") - return@get - } - val transport = serverTransports[sessionId] - ?: run { - call.respond(HttpStatusCode.BadRequest, "Invalid mcp-session-id") - return@get - } - transport.stream(call) - } - - post("/mcp") { - val sessionId = call.request.header("mcp-session-id") - val requestBody = call.receiveText() - - logger.debug { "Received request with sessionId: $sessionId" } - logger.trace { "Request body: $requestBody" } - - val jsonElement = try { - jsonFormat.parseToJsonElement(requestBody) - } catch (e: Exception) { - logger.error(e) { "Failed to parse request body as JSON" } - call.respond( - HttpStatusCode.BadRequest, - jsonFormat.encodeToString( - JsonObject.serializer(), - buildJsonObject { - put("jsonrpc", "2.0") - put( - "error", - buildJsonObject { - put("code", -32700) - put("message", "Parse error: ${e.message}") - }, - ) - put("id", JsonNull) - }, - ), - ) - return@post - } - - val transport = sessionId?.let { serverTransports[it] } - if (transport != null) { - logger.debug { "Using existing transport for session: $sessionId" } - transport.handleRequest(call, jsonElement) - } else { - if (isInitializeRequest(jsonElement)) { - val newSessionId = UUID.randomUUID().toString() - logger.info { "Creating new session with ID: $newSessionId" } - - val newTransport = HttpServerTransport(newSessionId) - serverTransports[newSessionId] = newTransport - - val mcpServer = createConformanceServer() - call.response.header("mcp-session-id", newSessionId) - - val sessionReady = CompletableDeferred() - CoroutineScope(Dispatchers.IO).launch { - try { - mcpServer.createSession(newTransport) - sessionReady.complete(Unit) - } catch (e: Exception) { - logger.error(e) { "Failed to create session" } - serverTransports.remove(newSessionId) - newTransport.close() - sessionReady.completeExceptionally(e) - } - } - - val sessionCreated = withTimeoutOrNull(SESSION_CREATION_TIMEOUT_MS) { - sessionReady.await() - } - - if (sessionCreated == null) { - logger.error { "Session creation timed out" } - serverTransports.remove(newSessionId) - call.respond( - HttpStatusCode.InternalServerError, - jsonFormat.encodeToString( - JsonObject.serializer(), - buildJsonObject { - put("jsonrpc", "2.0") - put( - "error", - buildJsonObject { - put("code", -32000) - put("message", "Session creation timed out") - }, - ) - put("id", JsonNull) - }, - ), - ) - return@post - } - - newTransport.handleRequest(call, jsonElement) - } else { - logger.warn { "Invalid request: no session ID or not an initialization request" } - call.respond( - HttpStatusCode.BadRequest, - jsonFormat.encodeToString( - JsonObject.serializer(), - buildJsonObject { - put("jsonrpc", "2.0") - put( - "error", - buildJsonObject { - put("code", -32000) - put("message", "Bad Request: No valid session ID provided") - }, - ) - put("id", JsonNull) - }, - ), - ) - } - } - } - - delete("/mcp") { - val sessionId = call.request.header("mcp-session-id") - val transport = sessionId?.let { serverTransports[it] } - if (transport != null) { - logger.info { "Terminating session: $sessionId" } - serverTransports.remove(sessionId) - transport.close() - call.respond(HttpStatusCode.OK) - } else { - logger.warn { "Invalid session termination request: $sessionId" } - call.respond(HttpStatusCode.BadRequest, "Invalid or missing session ID") - } - } - } - }.start(wait = true) -} - -@Suppress("LongMethod") -private fun createConformanceServer(): Server { - val server = Server( - Implementation( - name = "kotlin-conformance-server", - version = "1.0.0", - ), - ServerOptions( - capabilities = ServerCapabilities( - tools = ServerCapabilities.Tools(listChanged = true), - resources = ServerCapabilities.Resources(subscribe = true, listChanged = true), - prompts = ServerCapabilities.Prompts(listChanged = true), - ), - ), - ) - - server.addTool( - name = "test-tool", - description = "A test tool for conformance testing", - inputSchema = ToolSchema( - properties = buildJsonObject { - put( - "input", - buildJsonObject { - put("type", "string") - put("description", "Test input parameter") - }, - ) - }, - required = listOf("input"), - ), - ) { request -> - val input = (request.params.arguments?.get("input") as? JsonPrimitive)?.content ?: "no input" - CallToolResult( - content = listOf(TextContent("Tool executed with input: $input")), - ) - } - - server.addResource( - uri = "test://test-resource", - name = "Test Resource", - description = "A test resource for conformance testing", - mimeType = "text/plain", - ) { request -> - ReadResourceResult( - contents = listOf( - TextResourceContents("Test resource content", request.params.uri, "text/plain"), - ), - ) - } - - server.addPrompt( - name = "test-prompt", - description = "A test prompt for conformance testing", - arguments = listOf( - PromptArgument( - name = "arg", - description = "Test argument", - required = false, - ), - ), - ) { - GetPromptResult( - messages = listOf( - PromptMessage( - role = Role.User, - content = TextContent("Test prompt content"), - ), - ), - description = "Test prompt description", - ) - } - - return server -} - -private class HttpServerTransport(private val sessionId: String) : AbstractTransport() { - private val logger = KotlinLogging.logger {} - private val pendingResponses = ConcurrentHashMap>() - private val messageQueue = Channel(MESSAGE_QUEUE_CAPACITY) - - suspend fun stream(call: ApplicationCall) { - logger.debug { "Starting SSE stream for session $sessionId" } - call.response.apply { - header("Cache-Control", "no-cache") - header("Connection", "keep-alive") - } - call.respondTextWriter(ContentType.Text.EventStream) { - try { - while (true) { - val msg = messageQueue.receiveCatching().getOrNull() ?: break - write("event: message\ndata: ${McpJson.encodeToString(msg)}\n\n") - flush() - } - } catch (e: Exception) { - logger.warn(e) { "SSE stream terminated for session $sessionId" } - } finally { - logger.debug { "SSE stream closed for session $sessionId" } - } - } - } - - suspend fun handleRequest(call: ApplicationCall, requestBody: JsonElement) { - try { - val message = McpJson.decodeFromJsonElement(requestBody) - logger.debug { "Handling ${message::class.simpleName}: $requestBody" } - - when (message) { - is JSONRPCRequest -> { - val idKey = when (val id = message.id) { - is RequestId.NumberId -> id.value.toString() - is RequestId.StringId -> id.value - } - val responseDeferred = CompletableDeferred() - pendingResponses[idKey] = responseDeferred - - _onMessage.invoke(message) - - val response = withTimeoutOrNull(REQUEST_TIMEOUT_MS) { responseDeferred.await() } - if (response != null) { - call.respondText(McpJson.encodeToString(response), ContentType.Application.Json) - } else { - pendingResponses.remove(idKey) - logger.warn { "Timeout for request $idKey" } - call.respondText( - McpJson.encodeToString( - JSONRPCError( - message.id, - RPCError(RPCError.ErrorCode.REQUEST_TIMEOUT, "Request timed out"), - ), - ), - ContentType.Application.Json, - ) - } - } - - else -> { - call.respond(HttpStatusCode.Accepted) - } - } - } catch (e: CancellationException) { - throw e - } catch (e: Exception) { - logger.error(e) { "Error handling request" } - if (!call.response.isCommitted) { - call.respondText( - McpJson.encodeToString( - JSONRPCError( - RequestId(0), - RPCError(RPCError.ErrorCode.INTERNAL_ERROR, "Internal error: ${e.message}"), - ), - ), - ContentType.Application.Json, - HttpStatusCode.InternalServerError, - ) - } - } - } - - override suspend fun start() { - logger.debug { "Started transport for session $sessionId" } - } - - override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { - when (message) { - is JSONRPCResponse -> { - val idKey = when (val id = message.id) { - is RequestId.NumberId -> id.value.toString() - is RequestId.StringId -> id.value - } - pendingResponses.remove(idKey)?.complete(message) ?: run { - logger.warn { "No pending response for ID $idKey, queueing" } - messageQueue.send(message) - } - } - - else -> messageQueue.send(message) - } - } - - override suspend fun close() { - logger.debug { "Closing transport for session $sessionId" } - messageQueue.close() - pendingResponses.clear() - invokeOnCloseCallback() - } -} diff --git a/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTest.kt b/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTest.kt deleted file mode 100644 index 41c9c3c..0000000 --- a/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTest.kt +++ /dev/null @@ -1,343 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.conformance - -import io.github.oshai.kotlinlogging.KotlinLogging -import io.modelcontextprotocol.kotlin.test.utils.NPX -import io.modelcontextprotocol.kotlin.test.utils.findFreePort -import io.modelcontextprotocol.kotlin.test.utils.startLogging -import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.BeforeAll -import org.junit.jupiter.api.DynamicTest -import org.junit.jupiter.api.TestFactory -import org.junit.jupiter.api.TestInstance -import java.io.BufferedReader -import java.io.InputStreamReader -import java.lang.management.ManagementFactory -import java.net.HttpURLConnection -import java.net.URI -import java.util.concurrent.TimeUnit -import kotlin.io.path.createTempFile -import kotlin.properties.Delegates -import kotlin.test.fail - -private val logger = KotlinLogging.logger {} - -val processStderrLogger = KotlinLogging.logger(name = "stderr") -val processStdoutLogger = KotlinLogging.logger(name = "stdout") - -private const val CONFORMANCE_VERSION = "0.1.8" - -enum class TransportType { - SSE, - WEBSOCKET, -} - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -class ConformanceTest { - - private var serverProcess: Process? = null - private var serverPort: Int by Delegates.notNull() - private val serverErrorOutput = mutableListOf() - private val maxErrorLines = 500 - - companion object { - private val SERVER_SCENARIOS = listOf( - "server-initialize", - "tools-list", - "tools-call-simple-text", - "resources-list", - "prompts-list", - // TODO: Fix - // - resources-read-text - // - prompts-get-simple - ) - - private val CLIENT_SCENARIOS = listOf( - "initialize", - // TODO: Fix - // "tools-call", - ) - - private val SERVER_TRANSPORT_TYPES = listOf( - TransportType.SSE, - // TODO: Fix -// TransportType.WEBSOCKET, - ) - - private val CLIENT_TRANSPORT_TYPES = listOf( - TransportType.SSE, - TransportType.WEBSOCKET, - ) - - private const val DEFAULT_TEST_TIMEOUT_SECONDS = 30L - private const val DEFAULT_SERVER_STARTUP_TIMEOUT_SECONDS = 10 - private const val INITIAL_BACKOFF_MS = 50L - private const val MAX_BACKOFF_MS = 500L - private const val BACKOFF_MULTIPLIER = 1.5 - private const val CONNECTION_TIMEOUT_MS = 500 - private const val GRACEFUL_SHUTDOWN_SECONDS = 5L - private const val FORCE_SHUTDOWN_SECONDS = 2L - - private fun getRuntimeClasspath(): String = ManagementFactory.getRuntimeMXBean().classPath - - private fun getTestClasspath(): String = System.getProperty("test.classpath") ?: getRuntimeClasspath() - - private fun waitForServerReady( - url: String, - timeoutSeconds: Int = DEFAULT_SERVER_STARTUP_TIMEOUT_SECONDS, - ): Boolean { - val deadline = System.currentTimeMillis() + (timeoutSeconds * 1000) - var lastError: Exception? = null - var backoffMs = INITIAL_BACKOFF_MS - - while (System.currentTimeMillis() < deadline) { - try { - val connection = URI(url).toURL().openConnection() as HttpURLConnection - connection.requestMethod = "GET" - connection.connectTimeout = CONNECTION_TIMEOUT_MS - connection.readTimeout = CONNECTION_TIMEOUT_MS - connection.connect() - - val responseCode = connection.responseCode - connection.disconnect() - logger.debug { "Server responded with code: $responseCode" } - return true - } catch (e: Exception) { - lastError = e - Thread.sleep(backoffMs) - backoffMs = (backoffMs * BACKOFF_MULTIPLIER).toLong().coerceAtMost(MAX_BACKOFF_MS) - } - } - - logger.error { "Server did not start within $timeoutSeconds seconds. Last error: ${lastError?.message}" } - return false - } - } - - @BeforeAll - fun startServer() { - serverPort = findFreePort() - val serverUrl = "http://127.0.0.1:$serverPort/mcp" - - logger.info { "Starting conformance test server on port $serverPort" } - - val processBuilder = ProcessBuilder( - "java", - "-cp", - getRuntimeClasspath(), - "io.modelcontextprotocol.kotlin.sdk.conformance.ConformanceServerKt", - serverPort.toString(), - ) - - val process = processBuilder.start() - serverProcess = process - - // capture stderr in the background - Thread { - try { - BufferedReader(InputStreamReader(process.errorStream)).use { reader -> - reader.lineSequence().forEach { line -> - synchronized(serverErrorOutput) { - if (serverErrorOutput.size >= maxErrorLines) { - serverErrorOutput.removeAt(0) - } - serverErrorOutput.add(line) - } - logger.debug { "Server stderr: $line" } - } - } - } catch (e: Exception) { - logger.trace(e) { "Error reading server stderr" } - } - }.apply { - name = "server-stderr-reader" - isDaemon = true - }.start() - - logger.info { "Waiting for server to start..." } - val serverReady = waitForServerReady(serverUrl) - - if (!serverReady) { - val errorInfo = synchronized(serverErrorOutput) { - if (serverErrorOutput.isNotEmpty()) { - "\n\nServer error output:\n${serverErrorOutput.joinToString("\n")}" - } else { - "" - } - } - serverProcess?.destroyForcibly() - throw IllegalStateException( - "Server failed to start within $DEFAULT_SERVER_STARTUP_TIMEOUT_SECONDS seconds. " + - "Check if port $serverPort is available.$errorInfo", - ) - } - - logger.info { "Server started successfully at $serverUrl" } - } - - @AfterAll - fun stopServer() { - serverProcess?.also { process -> - logger.info { "Stopping conformance test server (PID: ${process.pid()})" } - - try { - process.destroy() - val terminated = process.waitFor(GRACEFUL_SHUTDOWN_SECONDS, TimeUnit.SECONDS) - - if (!terminated) { - logger.warn { "Server did not terminate gracefully, forcing shutdown..." } - process.destroyForcibly() - process.waitFor(FORCE_SHUTDOWN_SECONDS, TimeUnit.SECONDS) - } else { - logger.info { "Server stopped gracefully" } - } - } catch (e: Exception) { - logger.error(e) { "Error stopping server process" } - } finally { - serverProcess = null - } - } ?: logger.debug { "No server process to stop" } - } - - @TestFactory - fun `MCP Server Conformance Tests`(): List = SERVER_TRANSPORT_TYPES.flatMap { transportType -> - SERVER_SCENARIOS.map { scenario -> - DynamicTest.dynamicTest("Server [$transportType]: $scenario") { - runServerConformanceTest(scenario, transportType) - } - } - } - - @TestFactory - fun `MCP Client Conformance Tests`(): List = CLIENT_TRANSPORT_TYPES.flatMap { transportType -> - CLIENT_SCENARIOS.map { scenario -> - DynamicTest.dynamicTest("Client [$transportType]: $scenario") { - runClientConformanceTest(scenario, transportType) - } - } - } - - private fun runServerConformanceTest(scenario: String, transportType: TransportType) { - val serverUrl = when (transportType) { - TransportType.SSE -> { - "http://127.0.0.1:$serverPort/mcp" - } - - TransportType.WEBSOCKET -> { - "ws://127.0.0.1:$serverPort/ws" - } - } - - val processBuilder = ProcessBuilder( - NPX, - "@modelcontextprotocol/conformance@$CONFORMANCE_VERSION", - "server", - "--url", - serverUrl, - "--scenario", - scenario, - ) - - runConformanceTest("server", scenario, processBuilder, transportType) - } - - private fun runClientConformanceTest(scenario: String, transportType: TransportType) { - val testClasspath = getTestClasspath() - - // Create an argfile to avoid Windows command line length limits - val argFile = createTempFile(suffix = ".args").toFile() - argFile.deleteOnExit() - - val mainClass = when (transportType) { - TransportType.SSE -> { - argFile.writeText( - buildString { - appendLine("-cp") - appendLine(testClasspath) - appendLine("io.modelcontextprotocol.kotlin.sdk.conformance.ConformanceClientKt") - }, - ) - "http://127.0.0.1:$serverPort/mcp" - } - - TransportType.WEBSOCKET -> { - argFile.writeText( - buildString { - appendLine("-cp") - appendLine(testClasspath) - appendLine("io.modelcontextprotocol.kotlin.sdk.conformance.WebSocketConformanceClientKt") - }, - ) - "ws://127.0.0.1:$serverPort/ws" - } - } - - val clientCommand = listOf( - "java", - "@${argFile.absolutePath}", - mainClass, - ) - - val processBuilder = ProcessBuilder( - NPX, - "@modelcontextprotocol/conformance@$CONFORMANCE_VERSION", - "client", - "--command", - clientCommand.joinToString(" "), - "--scenario", - scenario, - ) - - runConformanceTest("client", scenario, processBuilder, transportType) - } - - private fun runConformanceTest( - type: String, - scenario: String, - processBuilder: ProcessBuilder, - transportType: TransportType, - ) { - val capitalizedType = type.replaceFirstChar { it.uppercase() } - logger.info { "Running $type conformance test [$transportType]: $scenario" } - - val timeoutSeconds = - System.getenv("CONFORMANCE_TEST_TIMEOUT_SECONDS")?.toLongOrNull() ?: DEFAULT_TEST_TIMEOUT_SECONDS - - val process = processBuilder.start() - - process.errorStream.startLogging( - logger = processStderrLogger, - name = "test(PID=${process.pid()})", - ) - process.inputStream.startLogging( - logger = processStdoutLogger, - name = "test(PID=${process.pid()})", - ) - - val completed = process.waitFor(timeoutSeconds, TimeUnit.SECONDS) - - if (!completed) { - logger.error { - "$capitalizedType conformance test [$transportType] '$scenario' timed out after $timeoutSeconds seconds" - } - process.destroyForcibly() - throw AssertionError( - "❌ $capitalizedType conformance test [$transportType] '$scenario' " + - "timed out after $timeoutSeconds seconds", - ) - } - - when (val exitCode = process.exitValue()) { - 0 -> logger.info { "✅ $capitalizedType conformance test [$transportType] '$scenario' passed!" } - - else -> { - logger.error { - "$capitalizedType conformance test [$transportType] '$scenario' failed with exit code: $exitCode" - } - fail( - "❌ $capitalizedType conformance test [$transportType] '$scenario' " + - "failed (exit code: $exitCode). Check test output above for details.", - ) - } - } - } -} diff --git a/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/WebSocketConformanceClient.kt b/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/WebSocketConformanceClient.kt deleted file mode 100644 index f385ddd..0000000 --- a/conformance-test/src/test/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/WebSocketConformanceClient.kt +++ /dev/null @@ -1,105 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.conformance - -import io.github.oshai.kotlinlogging.KotlinLogging -import io.ktor.client.HttpClient -import io.ktor.client.engine.cio.CIO -import io.ktor.client.plugins.websocket.WebSockets -import io.ktor.client.plugins.websocket.webSocket -import io.ktor.websocket.WebSocketSession -import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.shared.MCP_SUBPROTOCOL -import io.modelcontextprotocol.kotlin.sdk.shared.WebSocketMcpTransport -import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest -import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequestParams -import io.modelcontextprotocol.kotlin.sdk.types.Implementation -import kotlinx.coroutines.runBlocking -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.buildJsonObject - -private val logger = KotlinLogging.logger {} - -class WebSocketClientTransport(override val session: WebSocketSession) : WebSocketMcpTransport() { - override suspend fun initializeSession() { - logger.debug { "WebSocket client session initialized" } - } -} - -@Suppress("LongMethod") -fun main(args: Array) { - require(args.isNotEmpty()) { - "Server WebSocket URL must be provided as an argument" - } - - val serverUrl = args.last() - logger.info { "Connecting to WebSocket test server at: $serverUrl" } - - val httpClient = HttpClient(CIO) { - install(WebSockets) - } - - var exitCode = 0 - - runBlocking { - try { - httpClient.webSocket(serverUrl, request = { - headers.append("Sec-WebSocket-Protocol", MCP_SUBPROTOCOL) - }) { - val transport = WebSocketClientTransport(this) - - val client = Client( - clientInfo = Implementation( - name = "kotlin-conformance-client-websocket", - version = "1.0.0", - ), - ) - - try { - client.connect(transport) - logger.info { "✅ Connected to server successfully" } - - try { - val tools = client.listTools() - logger.info { "Available tools: ${tools.tools.map { it.name }}" } - - if (tools.tools.isNotEmpty()) { - val toolName = tools.tools.first().name - logger.info { "Calling tool: $toolName" } - - val result = client.callTool( - CallToolRequest( - params = CallToolRequestParams( - name = toolName, - arguments = buildJsonObject { - put("input", JsonPrimitive("test")) - }, - ), - ), - ) - logger.info { "Tool result: ${result.content}" } - } - } catch (e: Exception) { - logger.debug(e) { "Error during tool operations (may be expected for some scenarios)" } - } - - logger.info { "✅ Client operations completed successfully" } - } catch (e: Exception) { - logger.error(e) { "❌ Client failed" } - exitCode = 1 - } finally { - try { - transport.close() - } catch (e: Exception) { - logger.warn(e) { "Error closing transport" } - } - } - } - } catch (e: Exception) { - logger.error(e) { "❌ WebSocket connection failed" } - exitCode = 1 - } finally { - httpClient.close() - } - } - - kotlin.system.exitProcess(exitCode) -} diff --git a/conformance-test/src/test/resources/simplelogger.properties b/conformance-test/src/test/resources/simplelogger.properties deleted file mode 100644 index c6f3b90..0000000 --- a/conformance-test/src/test/resources/simplelogger.properties +++ /dev/null @@ -1,10 +0,0 @@ -# Level of logging for the ROOT logger: ERROR, WARN, INFO, DEBUG, TRACE (default is INFO) -org.slf4j.simpleLogger.defaultLogLevel=INFO -org.slf4j.simpleLogger.showThreadName=true -org.slf4j.simpleLogger.showDateTime=false - -# Log level for specific packages or classes -org.slf4j.simpleLogger.log.io.ktor.server=DEBUG -org.slf4j.simpleLogger.log.stdout=TRACE -org.slf4j.simpleLogger.log.stderr=TRACE -org.slf4j.simpleLogger.log.io.modelcontextprotocol=DEBUG diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f86685d..313c8f6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -44,6 +44,7 @@ kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx- # Ktor ktor-client-apache5 = { group = "io.ktor", name = "ktor-client-apache5", version.ref = "ktor" } +ktor-client-auth = { group = "io.ktor", name = "ktor-client-auth", version.ref = "ktor" } ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" } ktor-client-logging = { group = "io.ktor", name = "ktor-client-logging", version.ref = "ktor" } ktor-server-content-negotiation = { group = "io.ktor", name = "ktor-server-content-negotiation", version.ref = "ktor" } diff --git a/kotlin-sdk-client/api/kotlin-sdk-client.api b/kotlin-sdk-client/api/kotlin-sdk-client.api index 268a2b8..da0e752 100644 --- a/kotlin-sdk-client/api/kotlin-sdk-client.api +++ b/kotlin-sdk-client/api/kotlin-sdk-client.api @@ -62,6 +62,18 @@ public final class io/modelcontextprotocol/kotlin/sdk/client/KtorClientKt { public static synthetic fun mcpSseTransport-5_5nbZA$default (Lio/ktor/client/HttpClient;Ljava/lang/String;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/client/SseClientTransport; } +public final class io/modelcontextprotocol/kotlin/sdk/client/ReconnectionOptions { + public synthetic fun (JJDIILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (JJDILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun equals (Ljava/lang/Object;)Z + public final fun getInitialReconnectionDelay-UwyO8pc ()J + public final fun getMaxReconnectionDelay-UwyO8pc ()J + public final fun getMaxRetries ()I + public final fun getReconnectionDelayMultiplier ()D + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public final class io/modelcontextprotocol/kotlin/sdk/client/SseClientTransport : io/modelcontextprotocol/kotlin/sdk/shared/AbstractClientTransport { public synthetic fun (Lio/ktor/client/HttpClient;Ljava/lang/String;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun (Lio/ktor/client/HttpClient;Ljava/lang/String;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -88,6 +100,8 @@ public final class io/modelcontextprotocol/kotlin/sdk/client/StdioClientTranspor } public final class io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport : io/modelcontextprotocol/kotlin/sdk/shared/AbstractClientTransport { + public fun (Lio/ktor/client/HttpClient;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/client/ReconnectionOptions;Lkotlin/jvm/functions/Function1;)V + public synthetic fun (Lio/ktor/client/HttpClient;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/client/ReconnectionOptions;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun (Lio/ktor/client/HttpClient;Ljava/lang/String;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun (Lio/ktor/client/HttpClient;Ljava/lang/String;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getProtocolVersion ()Ljava/lang/String; @@ -106,8 +120,12 @@ public final class io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpError } public final class io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpMcpKtorClientExtensionsKt { + public static final fun mcpStreamableHttp (Lio/ktor/client/HttpClient;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/client/ReconnectionOptions;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun mcpStreamableHttp$default (Lio/ktor/client/HttpClient;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/client/ReconnectionOptions;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static final fun mcpStreamableHttp-BZiP2OM (Lio/ktor/client/HttpClient;Ljava/lang/String;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun mcpStreamableHttp-BZiP2OM$default (Lio/ktor/client/HttpClient;Ljava/lang/String;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun mcpStreamableHttpTransport (Lio/ktor/client/HttpClient;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/client/ReconnectionOptions;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport; + public static synthetic fun mcpStreamableHttpTransport$default (Lio/ktor/client/HttpClient;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/client/ReconnectionOptions;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport; public static final fun mcpStreamableHttpTransport-5_5nbZA (Lio/ktor/client/HttpClient;Ljava/lang/String;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport; public static synthetic fun mcpStreamableHttpTransport-5_5nbZA$default (Lio/ktor/client/HttpClient;Ljava/lang/String;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport; } diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ReconnectionOptions.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ReconnectionOptions.kt new file mode 100644 index 0000000..95c5bde --- /dev/null +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ReconnectionOptions.kt @@ -0,0 +1,44 @@ +package io.modelcontextprotocol.kotlin.sdk.client + +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds + +/** + * Options for controlling SSE reconnection behavior. + * + * @property initialReconnectionDelay The initial delay before the first reconnection attempt. + * @property maxReconnectionDelay The maximum delay between reconnection attempts. + * @property reconnectionDelayMultiplier The factor by which the delay grows on each attempt. + * @property maxRetries The maximum number of reconnection attempts per disconnect. + */ +public class ReconnectionOptions( + public val initialReconnectionDelay: Duration = 1.seconds, + public val maxReconnectionDelay: Duration = 30.seconds, + public val reconnectionDelayMultiplier: Double = 1.5, + public val maxRetries: Int = 2, +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + + other as ReconnectionOptions + + if (reconnectionDelayMultiplier != other.reconnectionDelayMultiplier) return false + if (maxRetries != other.maxRetries) return false + if (initialReconnectionDelay != other.initialReconnectionDelay) return false + if (maxReconnectionDelay != other.maxReconnectionDelay) return false + + return true + } + + override fun hashCode(): Int { + var result = reconnectionDelayMultiplier.hashCode() + result = 31 * result + maxRetries + result = 31 * result + initialReconnectionDelay.hashCode() + result = 31 * result + maxReconnectionDelay.hashCode() + return result + } + + override fun toString(): String = + "ReconnectionOptions(initialReconnectionDelay=$initialReconnectionDelay, maxReconnectionDelay=$maxReconnectionDelay, reconnectionDelayMultiplier=$reconnectionDelayMultiplier, maxRetries=$maxRetries)" +} diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt index a3b4190..80f37ae 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt @@ -38,9 +38,13 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.cancelAndJoin +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive import kotlinx.coroutines.launch -import kotlin.concurrent.atomics.ExperimentalAtomicApi +import kotlin.math.pow import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds private const val MCP_SESSION_ID_HEADER = "mcp-session-id" private const val MCP_PROTOCOL_VERSION_HEADER = "mcp-protocol-version" @@ -52,31 +56,58 @@ private const val MCP_RESUMPTION_TOKEN_HEADER = "Last-Event-ID" public class StreamableHttpError(public val code: Int? = null, message: String? = null) : Exception("Streamable HTTP error: $message") +private sealed interface ConnectResult { + data class Success(val session: ClientSSESession) : ConnectResult + data object NonRetryable : ConnectResult + data object Failed : ConnectResult +} + /** * Client transport for Streamable HTTP: this implements the MCP Streamable HTTP transport specification. * It will connect to a server using HTTP POST for sending messages and HTTP GET with Server-Sent Events * for receiving messages. */ -@OptIn(ExperimentalAtomicApi::class) +@Suppress("TooManyFunctions") public class StreamableHttpClientTransport( private val client: HttpClient, private val url: String, - private val reconnectionTime: Duration? = null, + private val reconnectionOptions: ReconnectionOptions = ReconnectionOptions(), private val requestBuilder: HttpRequestBuilder.() -> Unit = {}, ) : AbstractClientTransport() { + @Deprecated( + "Use constructor with ReconnectionOptions", + replaceWith = ReplaceWith( + "StreamableHttpClientTransport(client, url, " + + "ReconnectionOptions(initialReconnectionDelay = reconnectionTime ?: 1.seconds), requestBuilder)", + "kotlin.time.Duration.Companion.seconds", + "io.modelcontextprotocol.kotlin.sdk.client.ReconnectionOptions", + ), + ) + public constructor( + client: HttpClient, + url: String, + reconnectionTime: Duration?, + requestBuilder: HttpRequestBuilder.() -> Unit = {}, + ) : this(client, url, ReconnectionOptions(initialReconnectionDelay = reconnectionTime ?: 1.seconds), requestBuilder) + override val logger: KLogger = KotlinLogging.logger {} public var sessionId: String? = null private set public var protocolVersion: String? = null - private var sseSession: ClientSSESession? = null private var sseJob: Job? = null private val scope by lazy { CoroutineScope(SupervisorJob() + Dispatchers.Default) } - private var lastEventId: String? = null + /** Result of an SSE stream collection. Reconnect when [hasPrimingEvent] is true and [receivedResponse] is false. */ + private data class SseStreamResult( + val hasPrimingEvent: Boolean, + val receivedResponse: Boolean, + val lastEventId: String? = null, + val serverRetryDelay: Duration? = null, + ) override suspend fun initialize() { logger.debug { "Client transport is starting..." } @@ -85,7 +116,7 @@ public class StreamableHttpClientTransport( /** * Sends a single message with optional resumption support */ - @Suppress("ReturnCount", "CyclomaticComplexMethod") + @Suppress("ReturnCount", "CyclomaticComplexMethod", "LongMethod", "TooGenericExceptionCaught", "ThrowsCount") override suspend fun performSend(message: JSONRPCMessage, options: TransportSendOptions?) { logger.debug { "Client sending message via POST to $url: ${McpJson.encodeToString(message)}" } @@ -133,18 +164,25 @@ public class StreamableHttpClientTransport( } } - ContentType.Text.EventStream -> handleInlineSse( - response, - onResumptionToken = options?.onResumptionToken, - replayMessageId = if (message is JSONRPCRequest) message.id else null, - ) + ContentType.Text.EventStream -> { + val replayMessageId = if (message is JSONRPCRequest) message.id else null + val result = handleInlineSse(response, replayMessageId, options?.onResumptionToken) + if (result.hasPrimingEvent && !result.receivedResponse) { + startSseSession( + resumptionToken = result.lastEventId, + replayMessageId = replayMessageId, + onResumptionToken = options?.onResumptionToken, + initialServerRetryDelay = result.serverRetryDelay, + ) + } + } else -> { val body = response.bodyAsText() if (response.contentType() == null && body.isBlank()) return val ct = response.contentType()?.toString() ?: "" - val error = StreamableHttpError(-1, "Unexpected content type: $$ct") + val error = StreamableHttpError(-1, "Unexpected content type: $ct") _onError(error) throw error } @@ -169,11 +207,6 @@ public class StreamableHttpClientTransport( override suspend fun closeResources() { logger.debug { "Client transport closing." } - - // Try to terminate session if we have one - terminateSession() - - sseSession?.cancel() sseJob?.cancelAndJoin() scope.cancel() } @@ -201,55 +234,120 @@ public class StreamableHttpClientTransport( } sessionId = null - lastEventId = null logger.debug { "Session terminated successfully" } } - private suspend fun startSseSession( + private fun startSseSession( resumptionToken: String? = null, replayMessageId: RequestId? = null, onResumptionToken: ((String) -> Unit)? = null, + initialServerRetryDelay: Duration? = null, ) { - sseSession?.cancel() - sseJob?.cancelAndJoin() + // Cancel-and-replace: cancel() signals the previous job, join() inside + // the new coroutine ensures it completes before we start collecting. + // This is intentionally non-suspend to avoid blocking performSend. + val previousJob = sseJob + previousJob?.cancel() + sseJob = scope.launch(CoroutineName("StreamableHttpTransport.collect#${hashCode()}")) { + previousJob?.join() + var lastEventId = resumptionToken + var serverRetryDelay = initialServerRetryDelay + var attempt = 0 + var needsDelay = initialServerRetryDelay != null + @Suppress("LoopWithTooManyJumpStatements") + while (isActive) { + // Delay before (re)connection: skip only for first fresh SSE connection + if (needsDelay) { + delay(getNextReconnectionDelay(attempt, serverRetryDelay)) + } + needsDelay = true + + // Connect + val session = when (val cr = connectSse(lastEventId)) { + is ConnectResult.Success -> { + attempt = 0 + cr.session + } + + ConnectResult.NonRetryable -> return@launch + + ConnectResult.Failed -> { + // Give up after maxRetries consecutive failed connection attempts + if (++attempt >= reconnectionOptions.maxRetries) { + _onError(StreamableHttpError(null, "Maximum reconnection attempts exceeded")) + return@launch + } + continue + } + } + + // Collect + val result = collectSse(session, replayMessageId, onResumptionToken) + lastEventId = result.lastEventId ?: lastEventId + serverRetryDelay = result.serverRetryDelay ?: serverRetryDelay + if (result.receivedResponse) break + } + } + } + + @Suppress("TooGenericExceptionCaught") + private suspend fun connectSse(lastEventId: String?): ConnectResult { logger.debug { "Client attempting to start SSE session at url: $url" } - try { - sseSession = client.sseSession( - urlString = url, - reconnectionTime = reconnectionTime, - ) { + return try { + val session = client.sseSession(urlString = url, showRetryEvents = true) { method = HttpMethod.Get applyCommonHeaders(this) - // sseSession will add ContentType.Text.EventStream automatically accept(ContentType.Application.Json) - (resumptionToken ?: lastEventId)?.let { headers.append(MCP_RESUMPTION_TOKEN_HEADER, it) } + lastEventId?.let { headers.append(MCP_RESUMPTION_TOKEN_HEADER, it) } requestBuilder() } logger.debug { "Client SSE session started successfully." } - } catch (e: SSEClientException) { - val responseStatus = e.response?.status - val responseContentType = e.response?.contentType() - - // 405 means server doesn't support SSE at GET endpoint - this is expected and valid - if (responseStatus == HttpStatusCode.MethodNotAllowed) { - logger.info { "Server returned 405 for GET/SSE, stream disabled." } - return - } - - // If server returns application/json, it means it doesn't support SSE for this session - // This is valid per spec - server can choose to only use JSON responses - if (responseContentType?.match(ContentType.Application.Json) == true) { - logger.info { "Server returned application/json for GET/SSE, using JSON-only mode." } - return - } - - _onError(e) + ConnectResult.Success(session) + } catch (e: CancellationException) { throw e + } catch (e: SSEClientException) { + if (isNonRetryableSseError(e)) { + ConnectResult.NonRetryable + } else { + logger.debug { "SSE connection failed: ${e.message}" } + ConnectResult.Failed + } + } catch (e: Exception) { + logger.debug { "SSE connection failed: ${e.message}" } + ConnectResult.Failed } + } - sseJob = scope.launch(CoroutineName("StreamableHttpTransport.collect#${hashCode()}")) { - sseSession?.let { collectSse(it, replayMessageId, onResumptionToken) } + private fun getNextReconnectionDelay(attempt: Int, serverRetryDelay: Duration?): Duration { + // Per SSE specification, the server-sent `retry` field sets the reconnection time + // for all subsequent attempts, taking priority over exponential backoff. + serverRetryDelay?.let { return it } + val delay = reconnectionOptions.initialReconnectionDelay * + reconnectionOptions.reconnectionDelayMultiplier.pow(attempt) + return delay.coerceAtMost(reconnectionOptions.maxReconnectionDelay) + } + + /** + * Checks if an SSE session error is non-retryable (404, 405, JSON-only). + * Returns `true` if non-retryable (should stop trying), `false` otherwise. + */ + private fun isNonRetryableSseError(e: SSEClientException): Boolean { + val responseStatus = e.response?.status + val responseContentType = e.response?.contentType() + + return when { + responseStatus == HttpStatusCode.NotFound || responseStatus == HttpStatusCode.MethodNotAllowed -> { + logger.info { "Server returned ${responseStatus.value} for GET/SSE, stream disabled." } + true + } + + responseContentType?.match(ContentType.Application.Json) == true -> { + logger.info { "Server returned application/json for GET/SSE, using JSON-only mode." } + true + } + + else -> false } } @@ -265,11 +363,17 @@ public class StreamableHttpClientTransport( session: ClientSSESession, replayMessageId: RequestId?, onResumptionToken: ((String) -> Unit)?, - ) { + ): SseStreamResult { + var hasPrimingEvent = false + var receivedResponse = false + var localLastEventId: String? = null + var localServerRetryDelay: Duration? = null try { session.incoming.collect { event -> + event.retry?.let { localServerRetryDelay = it.milliseconds } event.id?.let { - lastEventId = it + localLastEventId = it + hasPrimingEvent = true onResumptionToken?.invoke(it) } logger.trace { "Client received SSE event: event=${event.event}, data=${event.data}, id=${event.id}" } @@ -278,6 +382,7 @@ public class StreamableHttpClientTransport( event.data?.takeIf { it.isNotEmpty() }?.let { json -> runCatching { McpJson.decodeFromString(json) } .onSuccess { msg -> + if (msg is JSONRPCResponse) receivedResponse = true if (replayMessageId != null && msg is JSONRPCResponse) { _onMessage(msg.copy(id = replayMessageId)) } else { @@ -295,6 +400,7 @@ public class StreamableHttpClientTransport( } catch (t: Throwable) { _onError(t) } + return SseStreamResult(hasPrimingEvent, receivedResponse, localLastEventId, localServerRetryDelay) } @Suppress("CyclomaticComplexMethod") @@ -302,17 +408,22 @@ public class StreamableHttpClientTransport( response: HttpResponse, replayMessageId: RequestId?, onResumptionToken: ((String) -> Unit)?, - ) { + ): SseStreamResult { logger.trace { "Handling inline SSE from POST response" } val channel = response.bodyAsChannel() + var hasPrimingEvent = false + var receivedResponse = false + var localLastEventId: String? = null + var localServerRetryDelay: Duration? = null val sb = StringBuilder() var id: String? = null var eventName: String? = null suspend fun dispatch(id: String?, eventName: String?, data: String) { id?.let { - lastEventId = it + localLastEventId = it + hasPrimingEvent = true onResumptionToken?.invoke(it) } if (data.isBlank()) { @@ -321,6 +432,7 @@ public class StreamableHttpClientTransport( if (eventName == null || eventName == "message") { runCatching { McpJson.decodeFromString(data) } .onSuccess { msg -> + if (msg is JSONRPCResponse) receivedResponse = true if (replayMessageId != null && msg is JSONRPCResponse) { _onMessage(msg.copy(id = replayMessageId)) } else { @@ -351,9 +463,16 @@ public class StreamableHttpClientTransport( } when { line.startsWith("id:") -> id = line.substringAfter("id:").trim() + line.startsWith("event:") -> eventName = line.substringAfter("event:").trim() + line.startsWith("data:") -> sb.append(line.substringAfter("data:").trim()) + + line.startsWith("retry:") -> line.substringAfter("retry:").trim().toLongOrNull()?.let { + localServerRetryDelay = it.milliseconds + } } } + return SseStreamResult(hasPrimingEvent, receivedResponse, localLastEventId, localServerRetryDelay) } } diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpMcpKtorClientExtensions.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpMcpKtorClientExtensions.kt index b64a220..a618f18 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpMcpKtorClientExtensions.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpMcpKtorClientExtensions.kt @@ -6,6 +6,22 @@ import io.modelcontextprotocol.kotlin.sdk.LIB_VERSION import io.modelcontextprotocol.kotlin.sdk.shared.IMPLEMENTATION_NAME import io.modelcontextprotocol.kotlin.sdk.types.Implementation import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds + +/** + * Returns a new Streamable HTTP transport for the Model Context Protocol using the provided HttpClient. + * + * @param url URL of the MCP server. + * @param reconnectionOptions Options for controlling SSE reconnection behavior. + * @param requestBuilder Optional lambda to configure the HTTP request. + * @return A [StreamableHttpClientTransport] configured for MCP communication. + */ +public fun HttpClient.mcpStreamableHttpTransport( + url: String, + reconnectionOptions: ReconnectionOptions = ReconnectionOptions(), + requestBuilder: HttpRequestBuilder.() -> Unit = {}, +): StreamableHttpClientTransport = + StreamableHttpClientTransport(this, url, reconnectionOptions, requestBuilder = requestBuilder) /** * Returns a new Streamable HTTP transport for the Model Context Protocol using the provided HttpClient. @@ -15,12 +31,42 @@ import kotlin.time.Duration * @param requestBuilder Optional lambda to configure the HTTP request. * @return A [StreamableHttpClientTransport] configured for MCP communication. */ +@Deprecated( + "Use overload with ReconnectionOptions", + replaceWith = ReplaceWith( + "mcpStreamableHttpTransport(url, " + + "ReconnectionOptions(initialReconnectionDelay = reconnectionTime ?: 1.seconds), requestBuilder)", + ), +) public fun HttpClient.mcpStreamableHttpTransport( url: String, - reconnectionTime: Duration? = null, + reconnectionTime: Duration?, requestBuilder: HttpRequestBuilder.() -> Unit = {}, -): StreamableHttpClientTransport = - StreamableHttpClientTransport(this, url, reconnectionTime, requestBuilder = requestBuilder) +): StreamableHttpClientTransport = StreamableHttpClientTransport( + this, + url, + ReconnectionOptions(initialReconnectionDelay = reconnectionTime ?: 1.seconds), + requestBuilder = requestBuilder, +) + +/** + * Creates and connects an MCP client over Streamable HTTP using the provided HttpClient. + * + * @param url URL of the MCP server. + * @param reconnectionOptions Options for controlling SSE reconnection behavior. + * @param requestBuilder Optional lambda to configure the HTTP request. + * @return A connected [Client] ready for MCP communication. + */ +public suspend fun HttpClient.mcpStreamableHttp( + url: String, + reconnectionOptions: ReconnectionOptions = ReconnectionOptions(), + requestBuilder: HttpRequestBuilder.() -> Unit = {}, +): Client { + val transport = mcpStreamableHttpTransport(url, reconnectionOptions, requestBuilder) + val client = Client(Implementation(name = IMPLEMENTATION_NAME, version = LIB_VERSION)) + client.connect(transport) + return client +} /** * Creates and connects an MCP client over Streamable HTTP using the provided HttpClient. @@ -30,12 +76,23 @@ public fun HttpClient.mcpStreamableHttpTransport( * @param requestBuilder Optional lambda to configure the HTTP request. * @return A connected [Client] ready for MCP communication. */ +@Deprecated( + "Use overload with ReconnectionOptions", + replaceWith = ReplaceWith( + "mcpStreamableHttp(url, " + + "ReconnectionOptions(initialReconnectionDelay = reconnectionTime ?: 1.seconds), requestBuilder)", + ), +) public suspend fun HttpClient.mcpStreamableHttp( url: String, - reconnectionTime: Duration? = null, + reconnectionTime: Duration?, requestBuilder: HttpRequestBuilder.() -> Unit = {}, ): Client { - val transport = mcpStreamableHttpTransport(url, reconnectionTime, requestBuilder) + val transport = mcpStreamableHttpTransport( + url, + ReconnectionOptions(initialReconnectionDelay = reconnectionTime ?: 1.seconds), + requestBuilder, + ) val client = Client(Implementation(name = IMPLEMENTATION_NAME, version = LIB_VERSION)) client.connect(transport) return client diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt index e303326..55df3e0 100644 --- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt @@ -23,9 +23,11 @@ import io.modelcontextprotocol.kotlin.sdk.types.Implementation import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse import io.modelcontextprotocol.kotlin.sdk.types.McpException import io.modelcontextprotocol.kotlin.sdk.types.McpJson import io.modelcontextprotocol.kotlin.sdk.types.RPCError +import io.modelcontextprotocol.kotlin.sdk.types.RequestId import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -657,6 +659,203 @@ class StreamableHttpClientTransportTest { receivedErrors shouldHaveSize 0 } + @Test + fun testInlineSseRetryParsing() = runTest { + val transport = createTransport { request -> + if (request.method == HttpMethod.Post) { + val sseContent = buildString { + appendLine("retry: 5000") + appendLine("id: ev-1") + appendLine("event: message") + appendLine("""data: {"jsonrpc":"2.0","id":"req-1","result":{"tools":[]}}""") + appendLine() + } + + respond( + content = ByteReadChannel(sseContent), + status = HttpStatusCode.OK, + headers = headersOf( + HttpHeaders.ContentType, + ContentType.Text.EventStream.toString(), + ), + ) + } else { + respond("", HttpStatusCode.OK) + } + } + + val receivedMessages = mutableListOf() + val responseReceived = CompletableDeferred() + + transport.onMessage { message -> + receivedMessages.add(message) + if (message is JSONRPCResponse && !responseReceived.isCompleted) { + responseReceived.complete(Unit) + } + } + + transport.start() + + transport.send( + JSONRPCRequest( + id = "req-1", + method = "test", + params = buildJsonObject { }, + ), + ) + + eventually { + responseReceived.await() + } + + receivedMessages shouldHaveSize 1 + val response = receivedMessages[0] as JSONRPCResponse + response.id shouldBe RequestId.StringId("req-1") + + transport.close() + } + + @Test + fun testInlineSseHasPrimingEventTracking() = runTest { + val transport = createTransport { request -> + if (request.method == HttpMethod.Post) { + val sseContent = buildString { + // Event with id = priming event + appendLine("id: priming-1") + appendLine("event: message") + appendLine( + """data: {"jsonrpc":"2.0","method":"notifications/progress",""" + + """"params":{"progressToken":"t1","progress":50}}""", + ) + appendLine() + // Notification without id + appendLine("event: message") + appendLine("""data: {"jsonrpc":"2.0","method":"notifications/tools/list_changed"}""") + appendLine() + } + + respond( + content = ByteReadChannel(sseContent), + status = HttpStatusCode.OK, + headers = headersOf( + HttpHeaders.ContentType, + ContentType.Text.EventStream.toString(), + ), + ) + } else { + respond("", HttpStatusCode.OK) + } + } + + val receivedMessages = mutableListOf() + val twoMessagesReceived = CompletableDeferred() + + transport.onMessage { message -> + receivedMessages.add(message) + if (receivedMessages.size >= 2 && !twoMessagesReceived.isCompleted) { + twoMessagesReceived.complete(Unit) + } + } + + transport.start() + + transport.send( + JSONRPCRequest( + id = "test-1", + method = "test", + params = buildJsonObject { }, + ), + ) + + eventually { + twoMessagesReceived.await() + } + + receivedMessages shouldHaveSize 2 + // Both should be notifications (no JSONRPCResponse → POST-to-GET reconnect would be triggered) + receivedMessages[0].shouldBeInstanceOf() + receivedMessages[1].shouldBeInstanceOf() + + transport.close() + } + + @Test + fun testInlineSseResponseStopsReconnection() = runTest { + val transport = createTransport { request -> + if (request.method == HttpMethod.Post) { + val sseContent = buildString { + appendLine("id: ev-1") + appendLine("event: message") + appendLine("""data: {"jsonrpc":"2.0","id":"req-1","result":{"tools":[]}}""") + appendLine() + } + + respond( + content = ByteReadChannel(sseContent), + status = HttpStatusCode.OK, + headers = headersOf( + HttpHeaders.ContentType, + ContentType.Text.EventStream.toString(), + ), + ) + } else { + respond("", HttpStatusCode.OK) + } + } + + val receivedMessages = mutableListOf() + val responseReceived = CompletableDeferred() + + transport.onMessage { message -> + receivedMessages.add(message) + if (message is JSONRPCResponse && !responseReceived.isCompleted) { + responseReceived.complete(Unit) + } + } + + transport.start() + + transport.send( + JSONRPCRequest( + id = "req-1", + method = "tools/list", + params = buildJsonObject { }, + ), + ) + + eventually { + responseReceived.await() + } + + receivedMessages shouldHaveSize 1 + // Response received → no reconnection triggered (hasPrimingEvent=true, receivedResponse=true) + val response = receivedMessages[0] as JSONRPCResponse + response.id shouldBe RequestId.StringId("req-1") + + transport.close() + } + + @Suppress("DEPRECATION") + @Test + fun testDeprecatedConstructorStillWorks() = runTest { + val mockEngine = MockEngine { _ -> + respond( + content = "", + status = HttpStatusCode.Accepted, + ) + } + val httpClient = HttpClient(mockEngine) { + install(SSE) + } + + val transport = + StreamableHttpClientTransport(httpClient, url = "http://localhost:8080/mcp", reconnectionTime = 2.seconds) + + transport.start() + transport.send(JSONRPCNotification(method = "test")) + transport.close() + } + private suspend fun setupTransportAndCollectMessages( transport: StreamableHttpClientTransport, ): Pair, MutableList> { diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt index 6513b46..a1fe89e 100644 --- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt +++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt @@ -1,6 +1,7 @@ package io.modelcontextprotocol.kotlin.sdk.client import io.kotest.matchers.collections.shouldContain +import io.kotest.matchers.shouldBe import io.ktor.http.ContentType import io.ktor.http.HttpMethod import io.ktor.http.HttpStatusCode @@ -203,7 +204,31 @@ internal class StreamableHttpClientTest : AbstractStreamableHttpClientTest() { meta = EmptyJsonObject, ) + client.close() + } + + @Test + fun `terminateSession sends DELETE request`() = runBlocking { + val client = Client( + clientInfo = Implementation(name = "client1", version = "1.0.0"), + options = ClientOptions(capabilities = ClientCapabilities()), + ) + val sessionId = Uuid.random().toString() + + mockMcp.onInitialize(clientName = "client1", sessionId = sessionId) + mockMcp.handleJSONRPCRequest( + jsonRpcMethod = "notifications/initialized", + expectedSessionId = sessionId, + sessionId = sessionId, + statusCode = HttpStatusCode.Accepted, + ) + mockMcp.handleSubscribeWithGet(sessionId) { emptyFlow() } + + connect(client) + mockMcp.mockUnsubscribeRequest(sessionId = sessionId) + (client.transport as StreamableHttpClientTransport).terminateSession() + (client.transport as StreamableHttpClientTransport).sessionId shouldBe null client.close() } @@ -257,8 +282,6 @@ internal class StreamableHttpClientTest : AbstractStreamableHttpClientTest() { buildJsonObject {} } - mockMcp.mockUnsubscribeRequest(sessionId = sessionId) - connect(client) delay(1.seconds) diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt index 5e39d1f..8ddee0a 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt @@ -8,12 +8,13 @@ import kotlinx.serialization.json.JsonObject // Protocol Version Constants // ============================================================================ -public const val LATEST_PROTOCOL_VERSION: String = "2025-06-18" +public const val LATEST_PROTOCOL_VERSION: String = "2025-11-25" -public const val DEFAULT_NEGOTIATED_PROTOCOL_VERSION: String = "2025-03-26" +public const val DEFAULT_NEGOTIATED_PROTOCOL_VERSION: String = "2025-06-18" public val SUPPORTED_PROTOCOL_VERSIONS: List = listOf( LATEST_PROTOCOL_VERSION, + "2025-06-18", "2025-03-26", "2024-11-05", ) diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt index a00b26b..4d5974d 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt @@ -371,21 +371,23 @@ internal object ServerResultPolymorphicSerializer : * Polymorphic serializer for [JSONRPCMessage] types. * Determines the message type based on the presence of specific fields: * - "error" -> JSONRPCError - * - "result" -> JSONRPCResponse + * - "result" + "id" -> JSONRPCResponse + * - "result" -> JSONRPCEmptyMessage * - "method" + "id" -> JSONRPCRequest * - "method" -> JSONRPCNotification */ internal object JSONRPCMessagePolymorphicSerializer : JsonContentPolymorphicSerializer(JSONRPCMessage::class) { override fun selectDeserializer(element: JsonElement): DeserializationStrategy { - val jsonObject = element.jsonObject + val jsonObj = element.jsonObject return when { - "error" in jsonObject -> JSONRPCError.serializer() - "result" in jsonObject -> JSONRPCResponse.serializer() - "method" in jsonObject && "id" in jsonObject -> JSONRPCRequest.serializer() - "method" in jsonObject -> JSONRPCNotification.serializer() - jsonObject.isEmpty() || jsonObject.keys == setOf("jsonrpc") -> JSONRPCEmptyMessage.serializer() - else -> throw SerializationException("Invalid JSONRPCMessage type: ${jsonObject.keys}") + "error" in jsonObj -> JSONRPCError.serializer() + "result" in jsonObj && "id" in jsonObj -> JSONRPCResponse.serializer() + "result" in jsonObj && jsonObj["result"]?.jsonObject?.isEmpty() == true -> JSONRPCEmptyMessage.serializer() + "method" in jsonObj && "id" in jsonObj -> JSONRPCRequest.serializer() + "method" in jsonObj -> JSONRPCNotification.serializer() + jsonObj.isEmpty() || jsonObj.keys == setOf("jsonrpc") -> JSONRPCEmptyMessage.serializer() + else -> throw SerializationException("Invalid JSONRPCMessage type: ${jsonObj.keys}") } } } diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CommonTypeTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CommonTypeTest.kt index 44e88ac..a29a19c 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CommonTypeTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CommonTypeTest.kt @@ -14,13 +14,14 @@ class CommonTypeTest { @Test fun `should have correct latest protocol version`() { - LATEST_PROTOCOL_VERSION shouldBe "2025-06-18" + LATEST_PROTOCOL_VERSION shouldBe "2025-11-25" } @Test fun `should have correct supported protocol versions`() { SUPPORTED_PROTOCOL_VERSIONS shouldContainExactlyInAnyOrder listOf( LATEST_PROTOCOL_VERSION, + "2025-06-18", "2025-03-26", "2024-11-05", ) diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt index 580fcc0..9074c4c 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt @@ -45,6 +45,7 @@ internal const val MCP_SESSION_ID_HEADER = "mcp-session-id" private const val MCP_PROTOCOL_VERSION_HEADER = "mcp-protocol-version" private const val MCP_RESUMPTION_TOKEN_HEADER = "Last-Event-ID" private const val MAXIMUM_MESSAGE_SIZE = 4 * 1024 * 1024 // 4 MB +private const val MIN_PRIMING_EVENT_PROTOCOL_VERSION = "2025-11-25" /** * A holder for an active request call. @@ -388,7 +389,7 @@ public class StreamableHttpServerTransport(private val configuration: Configurat if (!configuration.enableJsonResponse) { call.appendSseHeaders() flushSse(session) // flush headers immediately - maybeSendPrimingEvent(streamId, session) + maybeSendPrimingEvent(streamId, session, call.request.header(MCP_PROTOCOL_VERSION_HEADER)) } streamMutex.withLock { @@ -451,7 +452,7 @@ public class StreamableHttpServerTransport(private val configuration: Configurat call.appendSseHeaders() flushSse(sseSession) // flush headers immediately streamsMapping[STANDALONE_SSE_STREAM_ID] = SessionContext(sseSession, call) - maybeSendPrimingEvent(STANDALONE_SSE_STREAM_ID, sseSession) + maybeSendPrimingEvent(STANDALONE_SSE_STREAM_ID, sseSession, call.request.header(MCP_PROTOCOL_VERSION_HEADER)) sseSession.coroutineContext.job.invokeOnCompletion { streamsMapping.remove(STANDALONE_SSE_STREAM_ID) } @@ -702,12 +703,20 @@ public class StreamableHttpServerTransport(private val configuration: Configurat } @Suppress("TooGenericExceptionCaught") - private suspend fun maybeSendPrimingEvent(streamId: String, session: ServerSSESession?) { - val store = configuration.eventStore ?: return - val sseSession = session ?: return + private suspend fun maybeSendPrimingEvent( + streamId: String, + session: ServerSSESession?, + clientProtocolVersion: String? = null, + ) { + val store = configuration.eventStore + if (store == null || session == null) return + // Priming events have empty data which older clients cannot handle. + // Only send priming events to clients with protocol version >= 2025-11-25 + // which includes the fix for handling empty SSE data. + if (clientProtocolVersion != null && clientProtocolVersion < MIN_PRIMING_EVENT_PROTOCOL_VERSION) return try { val primingEventId = store.storeEvent(streamId, JSONRPCEmptyMessage) - sseSession.send( + session.send( id = primingEventId, retry = configuration.retryInterval?.inWholeMilliseconds, data = "", From 736efe9dc08ae083b568c7049aca511cafe6e7b8 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Thu, 12 Mar 2026 16:52:27 +0200 Subject: [PATCH 178/449] fix(server): notifications not delivered over Streamable HTTP (#587) (#599) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Changes - fix(server): keep SSE connection open until explicitly cancelled - conformance: add logging to test_tool_with_logging and update troubleshooting documentation - chore(conformance): remove 5 passing server tests from conformance baselines: - tools-call-with-logging - tools-call-with-progress - tools-call-sampling - tools-call-elicitation - elicitation-sep1034-defaults ## Motivation and Context Two bugs in StreamableHttpServerTransport caused server-to-client notifications (e.g. notifications/message during tool calls) to be silently dropped. **Bug 1 — GET SSE stream closed immediately** `handleGetRequest` rejected GET requests with 405 when `enableJsonResponse = true` (which `mcpStreamableHttp` always sets). Since the Ktor `sse {}` handler commits response headers before the body runs, the reject failed silently and the function returned, causing Ktor to close the SSE stream. There was also no `awaitCancellation()` to keep the connection alive. **Bug 2 — Notifications discarded in JSON mode** In `send()`, notifications with a relatedRequestId entered the POST-stream path but hit `if (!isTerminated) return` without being forwarded anywhere. **Fix** - Remove the enableJsonResponse guard from handleGetRequest. The GET SSE stream is an always-required notification channel, orthogonal to how POST responses are delivered. - Add `awaitCancellation()` to keep the GET SSE connection open until the client disconnects or the transport closes. - In `send()`, route notifications with a relatedRequestId to the standalone GET SSE stream when `enableJsonResponse = true`. ## How Has This Been Tested? Verified against the MCP conformance test suite — the ToolsCallWithLogging scenario now passes. ngrep confirms the `GET /mcp` stream stays open and `notifications/message` events arrive before the `tools/call` response. ## Breaking Changes ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 - [ ] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed ## Additional context Fixes #587 Requires #585 to be merged first. --------- Co-authored-by: devcrocod --- conformance-test/README.md | 15 ++++------- conformance-test/conformance-baseline.yml | 5 ---- .../sdk/conformance/ConformanceTools.kt | 7 ++++++ .../main/resources/simplelogger.properties | 9 +++++++ .../server/StreamableHttpServerTransport.kt | 25 ++++++++++++------- 5 files changed, 37 insertions(+), 24 deletions(-) create mode 100644 conformance-test/src/main/resources/simplelogger.properties diff --git a/conformance-test/README.md b/conformance-test/README.md index d14fcfe..3f1e366 100644 --- a/conformance-test/README.md +++ b/conformance-test/README.md @@ -114,16 +114,11 @@ Tests the conformance server against all server scenarios: 8 scenarios are expected to fail due to current SDK limitations (tracked in [ `conformance-baseline.yml`](conformance-baseline.yml). -| Scenario | Suite | Root Cause | -|---------------------------------------|--------|--------------------------------------------------------------------------------------------------------------------------------------------------------| -| `tools-call-with-logging` | server | Notifications from tool handlers have no `relatedRequestId`; transport routes them to the standalone SSE stream instead of the request-specific stream | -| `tools-call-with-progress` | server | *(same as above)* | -| `tools-call-sampling` | server | *(same as above)* | -| `tools-call-elicitation` | server | *(same as above)* | -| `elicitation-sep1034-defaults` | server | *(same as above)* | -| `elicitation-sep1330-enums` | server | *(same as above)* | -| `resources-templates-read` | server | SDK does not implement `addResourceTemplate()` with URI pattern matching; resources are looked up by exact URI | -| `elicitation-sep1034-client-defaults` | client | SDK does not fill in `default` values from the elicitation request schema before sending the response | +| Scenario | Suite | Root Cause | +|---------------------------------------|--------|----------------------------------------------------------------------------------------------------------------| +| `elicitation-sep1330-enums` | server | *(same as above)* | +| `resources-templates-read` | server | SDK does not implement `addResourceTemplate()` with URI pattern matching; resources are looked up by exact URI | +| `elicitation-sep1034-client-defaults` | client | SDK does not fill in `default` values from the elicitation request schema before sending the response | These failures reveal SDK gaps and are intentionally not fixed in this module. diff --git a/conformance-test/conformance-baseline.yml b/conformance-test/conformance-baseline.yml index cc06a38..c15041f 100644 --- a/conformance-test/conformance-baseline.yml +++ b/conformance-test/conformance-baseline.yml @@ -1,11 +1,6 @@ # Conformance test baseline - expected failures # Add entries here as tests are identified as known SDK limitations server: - - tools-call-with-logging - - tools-call-with-progress - - tools-call-sampling - - tools-call-elicitation - - elicitation-sep1034-defaults - elicitation-sep1330-enums - resources-templates-read diff --git a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTools.kt b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTools.kt index ebe7a57..def59f8 100644 --- a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTools.kt +++ b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTools.kt @@ -1,5 +1,6 @@ package io.modelcontextprotocol.kotlin.sdk.conformance +import io.github.oshai.kotlinlogging.KotlinLogging import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.types.AudioContent import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult @@ -33,6 +34,8 @@ internal const val PNG_BASE64 = // Minimal WAV (base64) internal const val WAV_BASE64 = "UklGRiYAAABXQVZFZm10IBAAAAABAAEAQB8AAAB9AAACABAAZGF0YQIAAAA=" +private val logger = KotlinLogging.logger {} + @Suppress("LongMethod") fun Server.registerConformanceTools() { // 1. Simple text @@ -429,6 +432,7 @@ fun Server.registerConformanceTools() { name = "test_tool_with_logging", description = "test_tool_with_logging", ) { + logger.debug { "[test_tool_with_logging] Sending message 1" } sendLoggingMessage( LoggingMessageNotification( LoggingMessageNotificationParams( @@ -439,6 +443,7 @@ fun Server.registerConformanceTools() { ), ) delay(50.milliseconds) + logger.debug { "[test_tool_with_logging] Sending message #2" } sendLoggingMessage( LoggingMessageNotification( LoggingMessageNotificationParams( @@ -449,6 +454,7 @@ fun Server.registerConformanceTools() { ), ) delay(50.milliseconds) + logger.debug { "[test_tool_with_logging] Sending message 3" } sendLoggingMessage( LoggingMessageNotification( LoggingMessageNotificationParams( @@ -458,6 +464,7 @@ fun Server.registerConformanceTools() { ), ), ) + CallToolResult(listOf(TextContent("Simple text content"))) } diff --git a/conformance-test/src/main/resources/simplelogger.properties b/conformance-test/src/main/resources/simplelogger.properties new file mode 100644 index 0000000..16141d3 --- /dev/null +++ b/conformance-test/src/main/resources/simplelogger.properties @@ -0,0 +1,9 @@ +# Level of logging for the ROOT logger: ERROR, WARN, INFO, DEBUG, TRACE (default is INFO) +org.slf4j.simpleLogger.defaultLogLevel=INFO +org.slf4j.simpleLogger.showThreadName=true +org.slf4j.simpleLogger.showDateTime=false + +# Log level for specific packages or classes +org.slf4j.simpleLogger.log.io.ktor.server=DEBUG +org.slf4j.simpleLogger.log.io.modelcontextprotocol=DEBUG +org.slf4j.simpleLogger.log.io.modelcontextprotocol.kotlin.sdk.conformance=INFO diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt index 9074c4c..d0bbcfb 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt @@ -28,6 +28,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.RPCError import io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.REQUEST_TIMEOUT import io.modelcontextprotocol.kotlin.sdk.types.RequestId import io.modelcontextprotocol.kotlin.sdk.types.SUPPORTED_PROTOCOL_VERSIONS +import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.job import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -242,7 +243,15 @@ public class StreamableHttpServerTransport(private val configuration: Configurat } val isTerminated = message is JSONRPCResponse || message is JSONRPCError - if (!isTerminated) return + if (!isTerminated) { + if (configuration.enableJsonResponse) { + // In JSON response mode there is no per-request SSE stream, so route notifications + // that are logically associated with a request to the standalone GET SSE stream. + val standaloneStream = streamsMapping[STANDALONE_SSE_STREAM_ID] + standaloneStream?.let { emitOnStream(STANDALONE_SSE_STREAM_ID, it.session, message) } + } + return + } requestToResponseMapping[responseRequestId!!] = message val relatedIds = requestToStreamMapping.filterValues { it == streamId }.keys @@ -411,14 +420,9 @@ public class StreamableHttpServerTransport(private val configuration: Configurat @Suppress("ReturnCount") public suspend fun handleGetRequest(session: ServerSSESession?, call: ApplicationCall) { - if (configuration.enableJsonResponse) { - call.reject( - HttpStatusCode.MethodNotAllowed, - RPCError.ErrorCode.CONNECTION_CLOSED, - "Method not allowed.", - ) - return - } + // NOTE: enableJsonResponse only controls how POST responses are delivered (JSON vs. SSE). + // The standalone GET SSE stream is always supported — it is the only channel available + // for server-to-client notifications when enableJsonResponse = true. val sseSession = session ?: error("Server session can't be null for streaming GET requests") val acceptHeader = call.request.header(HttpHeaders.Accept) @@ -456,6 +460,9 @@ public class StreamableHttpServerTransport(private val configuration: Configurat sseSession.coroutineContext.job.invokeOnCompletion { streamsMapping.remove(STANDALONE_SSE_STREAM_ID) } + // Keep the SSE connection open until the client disconnects or the transport is closed. + // Without this, the Ktor sse{} handler returns immediately, closing the stream. + awaitCancellation() } public suspend fun handleDeleteRequest(call: ApplicationCall) { From 4ecd95218a4b33b9551ff9e26d7cb4f1f5f35764 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Thu, 12 Mar 2026 17:25:14 +0200 Subject: [PATCH 179/449] fix(conformance): correct SEP-1330 enum schemas in test_elicitation_sep1330_enums (#600) ## Correct SEP-1330 enum schemas in test_elicitation_sep1330_enums **NB! This PR contains changes from #599 and should be rebased and merged after #599 is merged.** Update conformance test according to [test requirements](https://github.com/modelcontextprotocol/conformance/blob/main/src/scenarios/server/elicitation-enums.ts#L14C1-L35C9). - Fix legacyEnum: was using `oneOf` with const/title pairs; now correctly uses `enum` + `enumNames` arrays per LegacyEnumSchema spec - Fix titledMulti: items were using `oneOf` with extra `type:"string"`; now correctly uses `anyOf` per TitledMultiSelectEnumSchema spec - Fix return text format to match expected "Elicitation completed: action=..., content=..." - Remove _elicitation-sep1330-enums_ from conformance baseline (test now passes) ## How Has This Been Tested? ```shell ./conformance-test/run-conformance.sh server ``` ## Breaking Changes No ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 --------- Co-authored-by: devcrocod --- conformance-test/README.md | 11 ++----- conformance-test/conformance-baseline.yml | 1 - .../sdk/conformance/ConformanceTools.kt | 30 ++++++++----------- 3 files changed, 14 insertions(+), 28 deletions(-) diff --git a/conformance-test/README.md b/conformance-test/README.md index 3f1e366..9b827bc 100644 --- a/conformance-test/README.md +++ b/conformance-test/README.md @@ -111,15 +111,8 @@ Tests the conformance server against all server scenarios: ## Known SDK Limitations -8 scenarios are expected to fail due to current SDK limitations (tracked in [ -`conformance-baseline.yml`](conformance-baseline.yml). - -| Scenario | Suite | Root Cause | -|---------------------------------------|--------|----------------------------------------------------------------------------------------------------------------| -| `elicitation-sep1330-enums` | server | *(same as above)* | -| `resources-templates-read` | server | SDK does not implement `addResourceTemplate()` with URI pattern matching; resources are looked up by exact URI | -| `elicitation-sep1034-client-defaults` | client | SDK does not fill in `default` values from the elicitation request schema before sending the response | - +Scenarios, expected to fail due to current SDK limitations, are tracked in +[`conformance-baseline.yml`](conformance-baseline.yml). These failures reveal SDK gaps and are intentionally not fixed in this module. [list-scenarios-command]: https://github.com/modelcontextprotocol/conformance/tree/main?tab=readme-ov-file#list-available-scenarios diff --git a/conformance-test/conformance-baseline.yml b/conformance-test/conformance-baseline.yml index c15041f..3788847 100644 --- a/conformance-test/conformance-baseline.yml +++ b/conformance-test/conformance-baseline.yml @@ -1,7 +1,6 @@ # Conformance test baseline - expected failures # Add entries here as tests are identified as known SDK limitations server: - - elicitation-sep1330-enums - resources-templates-read client: diff --git a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTools.kt b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTools.kt index def59f8..579b222 100644 --- a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTools.kt +++ b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTools.kt @@ -318,27 +318,22 @@ fun Server.registerConformanceTools() { ) }, ) - // Legacy titled (deprecated) + // Legacy titled (deprecated) - uses enum + enumNames arrays put( "legacyEnum", buildJsonObject { put("type", JsonPrimitive("string")) put( - "oneOf", + "enum", + JsonArray(listOf(JsonPrimitive("opt1"), JsonPrimitive("opt2"), JsonPrimitive("opt3"))), + ) + put( + "enumNames", JsonArray( listOf( - buildJsonObject { - put("const", JsonPrimitive("opt1")) - put("title", JsonPrimitive("Option One")) - }, - buildJsonObject { - put("const", JsonPrimitive("opt2")) - put("title", JsonPrimitive("Option Two")) - }, - buildJsonObject { - put("const", JsonPrimitive("opt3")) - put("title", JsonPrimitive("Option Three")) - }, + JsonPrimitive("Option One"), + JsonPrimitive("Option Two"), + JsonPrimitive("Option Three"), ), ), ) @@ -367,7 +362,7 @@ fun Server.registerConformanceTools() { ) }, ) - // Titled multi-select + // Titled multi-select - uses items.anyOf with const/title pairs put( "titledMulti", buildJsonObject { @@ -375,9 +370,8 @@ fun Server.registerConformanceTools() { put( "items", buildJsonObject { - put("type", JsonPrimitive("string")) put( - "oneOf", + "anyOf", JsonArray( listOf( buildJsonObject { @@ -405,7 +399,7 @@ fun Server.registerConformanceTools() { "Please review and update the form fields with defaults", schema, ) - CallToolResult(listOf(TextContent(result.content.toString()))) + CallToolResult(listOf(TextContent("Elicitation completed: action=${result.action}, content=${result.content}"))) } // 12. Dynamic tool From 921b1fd30ee5fb1a8774c652a24f55bb793ef29d Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Fri, 13 Mar 2026 10:23:51 +0100 Subject: [PATCH 180/449] docs: update client and server samples (#598) Updated the client and server examples for further use in the following guides: https://modelcontextprotocol.io/docs/develop/build-server and https://modelcontextprotocol.io/docs/develop/build-client ## How Has This Been Tested? claude desktop, cli ## Breaking Changes NaN ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] Example, 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 --- .../gradle/libs.versions.toml | 4 +- .../sample/client/MCPClient.kt | 213 +++++++++++------- .../sample/client/main.kt | 13 +- samples/weather-stdio-server/README.md | 2 +- samples/weather-stdio-server/build.gradle.kts | 4 +- .../gradle/libs.versions.toml | 5 +- .../sample/server/McpWeatherServer.kt | 168 +++++++++----- .../sample/server/WeatherApi.kt | 136 +++++------ .../sample/client/ClientStdio.kt | 6 +- 9 files changed, 317 insertions(+), 234 deletions(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index fa8b4e9..ab34b7e 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -1,8 +1,8 @@ [versions] -anthropic = "2.9.0" +anthropic = "2.15.0" kotlin = "2.2.21" ktor = "3.2.3" -mcp-kotlin = "0.8.1" +mcp-kotlin = "0.9.0" shadow = "9.2.2" slf4j = "2.0.17" diff --git a/samples/kotlin-mcp-client/src/main/kotlin/io/modelcontextprotocol/sample/client/MCPClient.kt b/samples/kotlin-mcp-client/src/main/kotlin/io/modelcontextprotocol/sample/client/MCPClient.kt index 8360316..23fdfcc 100644 --- a/samples/kotlin-mcp-client/src/main/kotlin/io/modelcontextprotocol/sample/client/MCPClient.kt +++ b/samples/kotlin-mcp-client/src/main/kotlin/io/modelcontextprotocol/sample/client/MCPClient.kt @@ -2,10 +2,11 @@ package io.modelcontextprotocol.sample.client import com.anthropic.client.okhttp.AnthropicOkHttpClient import com.anthropic.core.JsonValue +import com.anthropic.models.messages.ContentBlockParam import com.anthropic.models.messages.MessageCreateParams import com.anthropic.models.messages.MessageParam -import com.anthropic.models.messages.Model import com.anthropic.models.messages.Tool +import com.anthropic.models.messages.ToolResultBlockParam import com.anthropic.models.messages.ToolUnion import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.databind.ObjectMapper @@ -14,24 +15,28 @@ import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject import io.modelcontextprotocol.kotlin.sdk.types.Implementation import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.yield +import kotlinx.coroutines.withContext import kotlinx.io.asSink import kotlinx.io.asSource import kotlinx.io.buffered import kotlinx.serialization.json.JsonObject -import kotlin.jvm.optionals.getOrNull +import java.util.concurrent.TimeUnit -class MCPClient : AutoCloseable { - // Configures using the `ANTHROPIC_API_KEY` and `ANTHROPIC_AUTH_TOKEN` environment variables - private val anthropic = AnthropicOkHttpClient.fromEnv() +private const val MODEL = "claude-sonnet-4-20250514" +private const val MAX_TOKENS = 1024L + +class MCPClient(apiKey: String) : AutoCloseable { + private val anthropic = AnthropicOkHttpClient.builder() + .apiKey(apiKey) + .build() // Initialize MCP client private val mcp: Client = Client(clientInfo = Implementation(name = "mcp-client-cli", version = "1.0.0")) - private val messageParamsBuilder: MessageCreateParams.Builder = MessageCreateParams.builder() - .model(Model.CLAUDE_4_SONNET_20250514) - .maxTokens(1024) + // Server process reference for cleanup + private var serverProcess: Process? = null // List of tools offered by the server private lateinit var tools: List @@ -44,52 +49,52 @@ class MCPClient : AutoCloseable { // Connect to the server using the path to the server suspend fun connectToServer(serverScriptPath: String) { - try { - // Build the command based on the file extension of the server script - val command = buildList { - when (serverScriptPath.substringAfterLast(".")) { - "js" -> add("node") - "py" -> add(if (System.getProperty("os.name").lowercase().contains("win")) "python" else "python3") - "jar" -> addAll(listOf("java", "-jar")) - else -> throw IllegalArgumentException("Server script must be a .js, .py or .jar file") - } - add(serverScriptPath) + // Build the command based on the file extension of the server script + val command = buildList { + when (serverScriptPath.substringAfterLast(".")) { + "js" -> add("node") + "py" -> add(if (System.getProperty("os.name").lowercase().contains("win")) "python" else "python3") + "jar" -> addAll(listOf("java", "-jar")) + else -> throw IllegalArgumentException("Server script must be a .js, .py or .jar file") } - - // Start the server process - val process = ProcessBuilder(command).start() - - // Setup I/O transport using the process streams - val transport = StdioClientTransport( - input = process.inputStream.asSource().buffered(), - output = process.outputStream.asSink().buffered(), - ) - - // Connect the MCP client to the server using the transport - mcp.connect(transport) - - // Request the list of available tools from the server - val toolsResult = mcp.listTools() - tools = toolsResult.tools.map { tool -> - ToolUnion.ofTool( - Tool.builder() - .name(tool.name) - .description(tool.description ?: "") - .inputSchema( - Tool.InputSchema.builder() - .type(JsonValue.from(tool.inputSchema.type)) - .properties(tool.inputSchema.properties?.toJsonValue() ?: EmptyJsonObject.toJsonValue()) - .putAdditionalProperty("required", JsonValue.from(tool.inputSchema.required)) - .build(), - ) - .build(), - ) - } - println("Connected to server with tools: ${tools.joinToString(", ") { it.tool().get().name() }}") - } catch (e: Exception) { - println("Failed to connect to MCP server: $e") - throw e + add(serverScriptPath) } + + // Start the server process + val process = withContext(Dispatchers.IO) { + ProcessBuilder(command) + .redirectError(ProcessBuilder.Redirect.INHERIT) + .start() + } + serverProcess = process + + // Setup I/O transport using the process streams + val transport = StdioClientTransport( + input = process.inputStream.asSource().buffered(), + output = process.outputStream.asSink().buffered(), + ) + + // Connect the MCP client to the server using the transport + mcp.connect(transport) + + // Request the list of available tools from the server + val toolsResult = mcp.listTools() + tools = toolsResult.tools.map { tool -> + ToolUnion.ofTool( + Tool.builder() + .name(tool.name) + .description(tool.description ?: "") + .inputSchema( + Tool.InputSchema.builder() + .type(JsonValue.from(tool.inputSchema.type)) + .properties(tool.inputSchema.properties?.toJsonValue() ?: EmptyJsonObject.toJsonValue()) + .putAdditionalProperty("required", JsonValue.from(tool.inputSchema.required)) + .build(), + ) + .build(), + ) + } + println("Connected to server with tools: ${tools.joinToString(", ") { it.tool().get().name() }}") } // Process a user query and return a string response @@ -104,23 +109,29 @@ class MCPClient : AutoCloseable { // Send the query to the Anthropic model and get the response val response = anthropic.messages().create( - messageParamsBuilder + MessageCreateParams.builder() + .model(MODEL) + .maxTokens(MAX_TOKENS) .messages(messages) .tools(tools) .build(), ) val finalText = mutableListOf() + val toolResults = mutableListOf() + response.content().forEach { content -> when { // Append text outputs from the response - content.isText() -> finalText.add(content.text().getOrNull()?.text() ?: "") + content.isText() -> finalText.add(content.text().get().text()) // If the response indicates a tool use, process it further content.isToolUse() -> { - val toolName = content.toolUse().get().name() + val toolUse = content.toolUse().get() + val toolName = toolUse.name() + val toolUseId = toolUse.id() val toolArgs = - content.toolUse().get()._input().convert(object : TypeReference>() {}) + toolUse._input().convert(object : TypeReference>() {}) // Call the tool with provided arguments val result = mcp.callTool( @@ -129,37 +140,55 @@ class MCPClient : AutoCloseable { ) finalText.add("[Calling tool $toolName with args $toolArgs]") - // Add the tool result message to the conversation - messages.add( - MessageParam.builder() - .role(MessageParam.Role.USER) - .content( - """ - "type": "tool_result", - "tool_name": $toolName, - "result": ${ - result.content.joinToString("\n") { - (it as TextContent).text - } - } - """.trimIndent(), - ) - .build(), - ) + // Build tool_result content block with tool_use_id + val toolResultContent = result.content + .filterIsInstance() + .joinToString("\n") { it.text } - // Retrieve an updated response after tool execution - val aiResponse = anthropic.messages().create( - messageParamsBuilder - .messages(messages) - .build(), - ) + val toolResultBlock = ToolResultBlockParam.builder() + .toolUseId(toolUseId) + .content(toolResultContent) + .apply { if (result.isError == true) isError(true) } + .build() - // Append the updated response to final text - finalText.add(aiResponse.content().first().text().getOrNull()?.text() ?: "") + toolResults.add(ContentBlockParam.ofToolResult(toolResultBlock)) } } } + // If there were tool calls, send tool results back and get final response + if (toolResults.isNotEmpty()) { + // Add the full assistant response (includes tool_use blocks) + messages.add( + MessageParam.builder() + .role(MessageParam.Role.ASSISTANT) + .contentOfBlockParams(response.content().map { it.toParam() }) + .build(), + ) + + // Add user message with tool results + messages.add( + MessageParam.builder() + .role(MessageParam.Role.USER) + .contentOfBlockParams(toolResults) + .build(), + ) + + // Retrieve an updated response after tool execution (without tools) + val aiResponse = anthropic.messages().create( + MessageCreateParams.builder() + .model(MODEL) + .maxTokens(MAX_TOKENS) + .messages(messages) + .build(), + ) + + // Append the updated response to final text + aiResponse.content() + .filter { it.isText() } + .forEach { finalText.add(it.text().get().text()) } + } + return finalText.joinToString("\n", prefix = "", postfix = "") } @@ -171,17 +200,27 @@ class MCPClient : AutoCloseable { while (true) { print("\nQuery: ") val message = readlnOrNull() ?: break - if (message.lowercase() == "quit") break - val response = processQuery(message) - println("\n$response") - yield() + if (message.trim().lowercase() == "quit") break + + try { + val response = processQuery(message) + println("\n$response") + } catch (e: Exception) { + println("\nError: ${e.message}") + } } } override fun close() { runBlocking { mcp.close() - anthropic.close() } + serverProcess?.let { process -> + process.destroy() + if (!process.waitFor(5, TimeUnit.SECONDS)) { + process.destroyForcibly() + } + } + anthropic.close() } } diff --git a/samples/kotlin-mcp-client/src/main/kotlin/io/modelcontextprotocol/sample/client/main.kt b/samples/kotlin-mcp-client/src/main/kotlin/io/modelcontextprotocol/sample/client/main.kt index 19a3102..001a255 100644 --- a/samples/kotlin-mcp-client/src/main/kotlin/io/modelcontextprotocol/sample/client/main.kt +++ b/samples/kotlin-mcp-client/src/main/kotlin/io/modelcontextprotocol/sample/client/main.kt @@ -3,13 +3,14 @@ package io.modelcontextprotocol.sample.client import kotlinx.coroutines.runBlocking fun main(args: Array) = runBlocking { - require(args.isNotEmpty()) { - "Usage: java -jar /build/libs/kotlin-mcp-client-0.1.0-all.jar " - } - val serverPath = args.first() - val client = MCPClient() + require(args.isNotEmpty()) { "Usage: java -jar " } + + val apiKey = System.getenv("ANTHROPIC_API_KEY") + require(!apiKey.isNullOrBlank()) { "ANTHROPIC_API_KEY environment variable is not set" } + + val client = MCPClient(apiKey) client.use { - client.connectToServer(serverPath) + client.connectToServer(args.first()) client.chatLoop() } } diff --git a/samples/weather-stdio-server/README.md b/samples/weather-stdio-server/README.md index 4c13040..f377d55 100644 --- a/samples/weather-stdio-server/README.md +++ b/samples/weather-stdio-server/README.md @@ -57,8 +57,8 @@ Add the following to your Claude Desktop configuration: | Name | Description | |----------------|------------------------------------------------------------------------------------------| -| `get_forecast` | Returns weather forecast for a given `latitude` / `longitude` using the weather.gov API. | | `get_alerts` | Returns active weather alerts for a two-letter US `state` code (e.g. `CA`, `NY`). | +| `get_forecast` | Returns weather forecast for a given `latitude` / `longitude` using the weather.gov API. | ## Additional Resources diff --git a/samples/weather-stdio-server/build.gradle.kts b/samples/weather-stdio-server/build.gradle.kts index 0420cc0..d6f5fc2 100644 --- a/samples/weather-stdio-server/build.gradle.kts +++ b/samples/weather-stdio-server/build.gradle.kts @@ -14,14 +14,12 @@ version = "0.1.0" dependencies { implementation(dependencies.platform(libs.ktor.bom)) + implementation(libs.mcp.kotlin.server) implementation(libs.ktor.client.content.negotiation) implementation(libs.ktor.serialization.kotlinx.json) - implementation(libs.mcp.kotlin.server) - implementation(libs.ktor.server.cio) implementation(libs.ktor.client.cio) implementation(libs.slf4j.simple) runtimeOnly(libs.kotlin.logging) - runtimeOnly(libs.kotlinx.collections.immutable) testImplementation(kotlin("test")) diff --git a/samples/weather-stdio-server/gradle/libs.versions.toml b/samples/weather-stdio-server/gradle/libs.versions.toml index 6056b94..dab7f7d 100644 --- a/samples/weather-stdio-server/gradle/libs.versions.toml +++ b/samples/weather-stdio-server/gradle/libs.versions.toml @@ -1,22 +1,19 @@ [versions] -collections-immutable = "0.4.0" coroutines = "1.10.2" kotlin = "2.2.21" ktor = "3.2.3" logging = "7.0.13" -mcp-kotlin = "0.8.1" +mcp-kotlin = "0.9.0" shadow = "9.2.2" slf4j = "2.0.17" [libraries] kotlin-logging = { group = "io.github.oshai", name = "kotlin-logging-jvm", version.ref = "logging" } -kotlinx-collections-immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable-jvm", version.ref = "collections-immutable" } kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines" } ktor-bom = { group = "io.ktor", name = "ktor-bom", version.ref = "ktor" } ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio" } ktor-client-content-negotiation = { group = "io.ktor", name = "ktor-client-content-negotiation" } ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json-jvm" } -ktor-server-cio = { group = "io.ktor", name = "ktor-server-cio", version.ref = "ktor" } mcp-kotlin-client = { group = "io.modelcontextprotocol", name = "kotlin-sdk-client", version.ref = "mcp-kotlin" } mcp-kotlin-server = { group = "io.modelcontextprotocol", name = "kotlin-sdk-server", version.ref = "mcp-kotlin" } slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" } diff --git a/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt b/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt index f08c436..1dee706 100644 --- a/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt +++ b/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt @@ -1,6 +1,7 @@ package io.modelcontextprotocol.sample.server import io.ktor.client.HttpClient +import io.ktor.client.engine.cio.CIO import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.client.plugins.defaultRequest import io.ktor.http.ContentType @@ -15,6 +16,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult import io.modelcontextprotocol.kotlin.sdk.types.Implementation import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.sdk.types.ToolAnnotations import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema import kotlinx.coroutines.Job import kotlinx.coroutines.runBlocking @@ -32,47 +34,59 @@ import kotlinx.serialization.json.putJsonObject * weather alerts by state and weather forecasts by latitude/longitude. */ fun runMcpServer() { - // Base URL for the Weather API - val baseUrl = "https://api.weather.gov" + createHttpClient().use { httpClient -> + val server = Server( + Implementation( + name = "weather", + version = "1.0.0", + ), + ServerOptions( + capabilities = ServerCapabilities(tools = ServerCapabilities.Tools(listChanged = true)), + ), + ) - // Create an HTTP client with a default request configuration and JSON content negotiation - val httpClient = HttpClient { - defaultRequest { - url(baseUrl) - headers { - append("Accept", "application/geo+json") - append("User-Agent", "WeatherApiClient/1.0") + server.registerTools(httpClient) + + val transport = StdioServerTransport( + System.`in`.asInput(), + System.out.asSink().buffered(), + ) + + runBlocking { + val session = server.createSession(transport) + val done = Job() + session.onClose { + done.complete() } - contentType(ContentType.Application.Json) - } - // Install content negotiation plugin for JSON serialization/deserialization - install(ContentNegotiation) { - json( - Json { - ignoreUnknownKeys = true - prettyPrint = true - }, - ) + done.join() } } +} - // Create the MCP Server instance with a basic implementation - val server = Server( - Implementation( - name = "weather", // Tool name is "weather" - version = "1.0.0", // Version of the implementation - ), - ServerOptions( - capabilities = ServerCapabilities(tools = ServerCapabilities.Tools(listChanged = true)), - ), - ) +private fun createHttpClient(): HttpClient = HttpClient(CIO) { + defaultRequest { + url("https://api.weather.gov") + headers { + append("Accept", "application/geo+json") + append("User-Agent", "WeatherApiClient/1.0") + } + contentType(ContentType.Application.Json) + } + install(ContentNegotiation) { + json( + Json { + ignoreUnknownKeys = true + prettyPrint = true + }, + ) + } +} +private fun Server.registerTools(httpClient: HttpClient) { // Register a tool to fetch weather alerts by state - server.addTool( + addTool( name = "get_alerts", - description = """ - Get weather alerts for a US state. Input is Two-letter US state code (e.g. CA, NY) - """.trimIndent(), + description = "Get weather alerts for a US state. Input is a two-letter US state code (e.g. CA, NY)", inputSchema = ToolSchema( properties = buildJsonObject { putJsonObject("state") { @@ -82,33 +96,62 @@ fun runMcpServer() { }, required = listOf("state"), ), + toolAnnotations = ToolAnnotations(readOnlyHint = true, openWorldHint = true), ) { request -> - val state = request.arguments?.get("state")?.jsonPrimitive?.content ?: return@addTool CallToolResult( - content = listOf(TextContent("The 'state' parameter is required.")), + val state = request.arguments?.get("state")?.jsonPrimitive?.content + ?: return@addTool CallToolResult( + content = listOf(TextContent("The 'state' parameter is required.")), + ) + + if (state.length != 2) { + return@addTool CallToolResult( + content = listOf(TextContent("Invalid state code: '$state'. Must be a two-letter US state code.")), + isError = true, + ) + } + + val stateCode = state.uppercase() + + httpClient.getAlerts(stateCode).fold( + onSuccess = { alerts -> + if (alerts.isEmpty()) { + CallToolResult(content = listOf(TextContent("No active alerts for $stateCode"))) + } else { + val alertsText = "Active alerts for $stateCode:\n\n${alerts.joinToString("\n---\n")}" + CallToolResult(content = listOf(TextContent(alertsText))) + } + }, + onFailure = { e -> + CallToolResult( + content = listOf(TextContent("Failed to retrieve alerts data: ${e.message}")), + isError = true, + ) + }, ) - - val alerts = httpClient.getAlerts(state) - - CallToolResult(content = alerts.map { TextContent(it) }) } // Register a tool to fetch weather forecast by latitude and longitude - server.addTool( + addTool( name = "get_forecast", - description = """ - Get weather forecast for a specific latitude/longitude - """.trimIndent(), + description = "Get weather forecast for a location. Note: only US locations are supported by the NWS API.", inputSchema = ToolSchema( properties = buildJsonObject { putJsonObject("latitude") { put("type", "number") + put("description", "Latitude of the location") + put("minimum", -90) + put("maximum", 90) } putJsonObject("longitude") { put("type", "number") + put("description", "Longitude of the location") + put("minimum", -180) + put("maximum", 180) } }, required = listOf("latitude", "longitude"), ), + toolAnnotations = ToolAnnotations(readOnlyHint = true, openWorldHint = true), ) { request -> val latitude = request.arguments?.get("latitude")?.jsonPrimitive?.doubleOrNull val longitude = request.arguments?.get("longitude")?.jsonPrimitive?.doubleOrNull @@ -118,23 +161,26 @@ fun runMcpServer() { ) } - val forecast = httpClient.getForecast(latitude, longitude) - - CallToolResult(content = forecast.map { TextContent(it) }) - } - - // Create a transport using standard IO for server communication - val transport = StdioServerTransport( - System.`in`.asInput(), - System.out.asSink().buffered(), - ) - - runBlocking { - val session = server.createSession(transport) - val done = Job() - session.onClose { - done.complete() - } - done.join() + httpClient.getForecast(latitude, longitude).fold( + onSuccess = { periods -> + if (periods.isEmpty()) { + CallToolResult(content = listOf(TextContent("No forecast periods available"))) + } else { + val forecastText = periods.joinToString("\n---\n") + CallToolResult(content = listOf(TextContent(forecastText))) + } + }, + onFailure = { _ -> + CallToolResult( + content = listOf( + TextContent( + "Failed to retrieve grid point data for coordinates: $latitude, $longitude. " + + "This location may not be supported by the NWS API (only US locations are supported).", + ), + ), + isError = true, + ) + }, + ) } } diff --git a/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/WeatherApi.kt b/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/WeatherApi.kt index 671186c..000ac12 100644 --- a/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/WeatherApi.kt +++ b/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/WeatherApi.kt @@ -4,88 +4,90 @@ import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.request.get import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonObject - -// Extension function to fetch forecast information for given latitude and longitude -suspend fun HttpClient.getForecast(latitude: Double, longitude: Double): List { - // Build the URI using provided latitude and longitude - val uri = "/points/$latitude,$longitude" - // Request the points data from the API - val points = this.get(uri).body() - - // Request the forecast using the URL provided in the points response - val forecast = this.get(points.properties.forecast).body() - - // Map each forecast period to a formatted string - return forecast.properties.periods.map { period -> - """ - ${period.name}: - Temperature: ${period.temperature} ${period.temperatureUnit} - Wind: ${period.windSpeed} ${period.windDirection} - Forecast: ${period.detailedForecast} - """.trimIndent() - } -} +import java.util.Locale // Extension function to fetch weather alerts for a given state -suspend fun HttpClient.getAlerts(state: String): List { - // Build the URI using the given state code +suspend fun HttpClient.getAlerts(state: String): Result> = runCatching { val uri = "/alerts/active/area/$state" - // Request the alerts data from the API - val alerts = this.get(uri).body() + val alerts = this.get(uri).body() - // Map each alert feature to a formatted string - return alerts.features.map { feature -> + alerts.features.map { feature -> """ Event: ${feature.properties.event} Area: ${feature.properties.areaDesc} Severity: ${feature.properties.severity} - Description: ${feature.properties.description} - Instruction: ${feature.properties.instruction} + Status: ${feature.properties.status} + Headline: ${feature.properties.headline} """.trimIndent() } } -// Data class representing the points response from the API -@Serializable -data class Points( - val properties: Properties -) { - @Serializable - data class Properties(val forecast: String) +// Extension function to fetch forecast information for given latitude and longitude +suspend fun HttpClient.getForecast(latitude: Double, longitude: Double): Result> = runCatching { + val lat = String.format(Locale.US, "%.4f", latitude) + val lon = String.format(Locale.US, "%.4f", longitude) + val uri = "/points/$lat,$lon" + val points = this.get(uri).body() + + val forecastUrl = points.properties.forecast + ?: error("No forecast URL available for coordinates: $latitude, $longitude") + val forecast = this.get(forecastUrl).body() + + forecast.properties.periods.map { period -> + """ + ${period.name}: + Temperature: ${period.temperature}°${period.temperatureUnit} + Wind: ${period.windSpeed} ${period.windDirection} + ${period.shortForecast} + """.trimIndent() + } } -// Data class representing the forecast response from the API @Serializable -data class Forecast( - val properties: Properties -) { - @Serializable - data class Properties(val periods: List) +data class PointsResponse( + val properties: PointsProperties, +) - @Serializable - data class Period( - val number: Int, val name: String, val startTime: String, val endTime: String, - val isDaytime: Boolean, val temperature: Int, val temperatureUnit: String, - val temperatureTrend: String, val probabilityOfPrecipitation: JsonObject, - val windSpeed: String, val windDirection: String, - val shortForecast: String, val detailedForecast: String, - ) -} - -// Data class representing the alerts response from the API @Serializable -data class Alert( - val features: List -) { - @Serializable - data class Feature( - val properties: Properties - ) +data class PointsProperties( + val forecast: String? = null, +) - @Serializable - data class Properties( - val event: String, val areaDesc: String, val severity: String, - val description: String, val instruction: String?, - ) -} +@Serializable +data class ForecastResponse( + val properties: ForecastProperties, +) + +@Serializable +data class ForecastProperties( + val periods: List = emptyList(), +) + +@Serializable +data class ForecastPeriod( + val name: String? = null, + val temperature: Int? = null, + val temperatureUnit: String? = null, + val windSpeed: String? = null, + val windDirection: String? = null, + val shortForecast: String? = null, +) + +@Serializable +data class AlertsResponse( + val features: List = emptyList(), +) + +@Serializable +data class AlertFeature( + val properties: AlertProperties, +) + +@Serializable +data class AlertProperties( + val event: String? = null, + val areaDesc: String? = null, + val severity: String? = null, + val status: String? = null, + val headline: String? = null, +) diff --git a/samples/weather-stdio-server/src/test/kotlin/io/modelcontextprotocol/sample/client/ClientStdio.kt b/samples/weather-stdio-server/src/test/kotlin/io/modelcontextprotocol/sample/client/ClientStdio.kt index 5934ee2..32fd550 100644 --- a/samples/weather-stdio-server/src/test/kotlin/io/modelcontextprotocol/sample/client/ClientStdio.kt +++ b/samples/weather-stdio-server/src/test/kotlin/io/modelcontextprotocol/sample/client/ClientStdio.kt @@ -13,8 +13,8 @@ fun main(): Unit = runBlocking { val process = ProcessBuilder( "java", "-jar", - "${System.getProperty("user.dir")}/build/libs/weather-stdio-server-0.1.0-all.jar", - ).redirectErrorStream(true) + "${System.getProperty("user.dir")}/samples/weather-stdio-server/build/libs/weather-stdio-server-0.1.0-all.jar", + ).redirectError(ProcessBuilder.Redirect.INHERIT) .start() val transport = StdioClientTransport( @@ -44,7 +44,7 @@ fun main(): Unit = runBlocking { val alertResult = client.callTool( - name = "get_alert", + name = "get_alerts", arguments = mapOf("state" to "TX"), ).content.map { if (it is TextContent) it.text else it.toString() } From c136ad9bbe7bc429015952389ee636f39c1f7ac0 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Wed, 18 Mar 2026 00:34:50 +0100 Subject: [PATCH 181/449] docs: context7 config file (#605) ## Motivation and Context - removed unnecessary badges from readme - add context7 json file ## How Has This Been Tested? ## Breaking Changes ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update ## Checklist - [ ] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [ ] My code follows the repository's style guidelines - [ ] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed ## Additional context --- README.md | 5 ++--- context7.json | 30 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 context7.json diff --git a/README.md b/README.md index acb5c05..3e65b26 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,12 @@ [![Maven Central](https://img.shields.io/maven-central/v/io.modelcontextprotocol/kotlin-sdk.svg?label=Maven%20Central)](https://central.sonatype.com/artifact/io.modelcontextprotocol/kotlin-sdk) [![Build](https://github.com/modelcontextprotocol/kotlin-sdk/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/modelcontextprotocol/kotlin-sdk/actions/workflows/build.yml) -[![Conformance Tests](https://github.com/modelcontextprotocol/kotlin-sdk/actions/workflows/conformance.yml/badge.svg)](https://github.com/modelcontextprotocol/kotlin-sdk/actions/workflows/conformance.yml) -[![codecov](https://codecov.io/github/modelcontextprotocol/kotlin-sdk/graph/badge.svg?token=O2CFHNSFYM)](https://codecov.io/github/modelcontextprotocol/kotlin-sdk) [![Kotlin](https://img.shields.io/badge/kotlin-2.2+-blueviolet.svg?logo=kotlin)](http://kotlinlang.org) [![Kotlin Multiplatform](https://img.shields.io/badge/Platforms-%20JVM%20%7C%20Wasm%2FJS%20%7C%20Native%20-blueviolet?logo=kotlin)](https://kotlinlang.org/docs/multiplatform.html) [![JVM](https://img.shields.io/badge/JVM-11+-red.svg?logo=jvm)](http://java.com) [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE) -[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/modelcontextprotocol/kotlin-sdk) + Kotlin Multiplatform SDK for the [Model Context Protocol](https://modelcontextprotocol.io). It enables Kotlin applications targeting JVM, Native, JS, and Wasm to implement MCP clients and servers using a @@ -899,6 +897,7 @@ MCP server and client implementations with various transports. See the [samples overview](./samples/README.md) for a comparison table and detailed descriptions. ## Documentation +[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/modelcontextprotocol/kotlin-sdk) - [API Reference](https://kotlin.sdk.modelcontextprotocol.io/) - [Model Context Protocol documentation](https://modelcontextprotocol.io) diff --git a/context7.json b/context7.json new file mode 100644 index 0000000..a28c0fc --- /dev/null +++ b/context7.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://context7.com/schema/context7.json", + "projectTitle": "Model Context Protocol Kotlin SDK", + "description": "Kotlin Multiplatform SDK for MCP — client and server implementations", + "excludeFolders": [ + ".github", + "buildSrc", + "config", + "gradle/wrapper" + ], + "excludeFiles": [ + "CONTRIBUTING.md", + "LICENSE", + "SECURITY.md", + "codecov.yml", + "gradlew", + "gradlew.bat", + ".editorconfig", + "knit.properties", + "gradle.properties", + "settings.gradle.kts", + "conformance-baseline.yml", + "run-conformance.sh", + "detekt-baseline.xml", + "detekt-baseline-test.xml", + "detekt-baseline-main.xml", + "detekt-baseline-commonMainSourceSet.xml", + "detekt-baseline-commonTestSourceSet.xml" + ] +} \ No newline at end of file From 2542c9cae152b0800b7f4ca1957635545b397f0a Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Wed, 18 Mar 2026 17:53:26 +0100 Subject: [PATCH 182/449] feat: add mcp documentation skill for spec-aware development (#609) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a Claude Code skill (`.claude/skills/mcp-docs/`) that provides live MCP documentation and specification context ## Motivation and Context The modelcontextprotocol.io site provides both an [MCP server](https://modelcontextprotocol.io/mcp) and an [llms.txt](https://modelcontextprotocol.io/llms.txt) index for machine-readable access to the documentation. This skill leverages both to give Claude live access to the current spec during development. ## How Has This Been Tested? The skill was evaluated on 3 realistic scenarios (implementation, code review, spec question) comparing with-skill vs without-skill baseline: - **Pass rate**: 100% vs 67% — skill consistently cites spec sources and uses RFC 2119 language - **Token efficiency**: 41% fewer tokens on average (44k vs 75k) — goes directly to spec instead of reverse-engineering from codebase - **Accuracy**: with-skill discovered URL mode elicitation (new in spec 2025-11-25) that baseline missed entirely - **Description triggering**: optimized through 5 iterations of automated trigger evaluation against 20 test prompts ## Breaking Changes None ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- .claude/skills/mcp-docs/SKILL.md | 76 ++++++++++++++++++++++++++++++++ .gitignore | 2 +- 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 .claude/skills/mcp-docs/SKILL.md diff --git a/.claude/skills/mcp-docs/SKILL.md b/.claude/skills/mcp-docs/SKILL.md new file mode 100644 index 0000000..074e6f9 --- /dev/null +++ b/.claude/skills/mcp-docs/SKILL.md @@ -0,0 +1,76 @@ +--- +name: mcp-docs +description: > + Fetch live MCP specification and docs from modelcontextprotocol.io. Training data may be + outdated — default to using this skill when the query touches MCP protocol in any way. + Trigger for: implementing or debugging MCP features (sampling, elicitation, completion, tools, + resources, prompts, transports); SEPs or spec requirements; MCP transport behavior (SSE, + Streamable HTTP, reconnection, retry); authorization/OAuth; conformance test failures; + capability negotiation; message schemas. Skip only for pure Kotlin, build system, or + refactoring tasks with zero MCP protocol dependency. When in doubt, trigger. +user_invocable: true +--- + +# MCP Documentation Skill + +Provides access to the official MCP specification and docs from modelcontextprotocol.io. The spec evolves faster than training data — always consult live documentation for protocol specifics like message schemas, required fields, capability negotiation, error handling, and transport requirements. + +## When to use + +**Proactively** (without being asked): +- Implementing a feature defined in the MCP spec +- Reviewing code that implements MCP protocol behavior +- Fixing conformance test failures + +**On demand** (user asks or invokes `/mcp-docs`): +- Questions about how a specific MCP feature works +- Clarifying spec ambiguities +- Looking up SEPs (Specification Enhancement Proposals) + +## How to fetch documentation + +### Strategy: MCP tool first, llms.txt fallback + +**Step 1 — Try the MCP search tool.** If `search_model_context_protocol` is available, use it: + +``` +search_model_context_protocol(query: "") +``` + +One call, semantically ranked results. Fastest path. + +**Step 2 — Fallback to llms.txt + WebFetch.** If the MCP tool is not available: + +1. Fetch the documentation index: + ``` + WebFetch("https://modelcontextprotocol.io/llms.txt", "Return the full content as-is") + ``` + +2. Identify the relevant page URL(s) from the index. Key sections: + - **Specification**: `specification/...` — protocol details, message formats, transports, lifecycle + - **SEPs**: `seps/...` — specification enhancement proposals + - **Learning**: `docs/learn/...` — architecture, concepts + - **Development**: `docs/develop/...` — building clients/servers + +3. Fetch the specific page: + ``` + WebFetch("", "Return the full content of this documentation page") + ``` + +### Version handling + +Always use the latest spec version by default. Only use a specific version or draft when the user explicitly requests it (e.g., "check the 2025-03-26 spec" or "what does the draft say"). + +### Search guidance + +- Protocol message formats, required fields, capability negotiation → **Specification** section +- Feature motivation, design rationale → relevant **SEP** +- High-level concepts and architecture → **Learning** resources +- Use your judgment on query count. A field-level question may need one page; implementing a feature may need 2-3. + +## Presenting information + +- **Always cite sources.** Include links to spec pages used: `(source: )` +- **Spec over training data** for specific protocol details (schemas, fields, error codes, capabilities). Training data is fine for general MCP concepts. +- **Preserve RFC 2119 language.** The spec uses MUST/SHOULD/MAY — keep this distinction when explaining requirements. +- **Be concise.** Focus on the details relevant to the user's task. Avoid dumping entire spec pages — extract what matters for the specific question or implementation at hand. diff --git a/.gitignore b/.gitignore index 805579c..d9f0fde 100644 --- a/.gitignore +++ b/.gitignore @@ -56,7 +56,7 @@ node_modules dist ### SWE agents ### -.claude/ +.claude/settings.local.json .junie/ ### Conformance test results ### From ee6561c18582ed5992d2f45de218a6aec2fef9d9 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Thu, 19 Mar 2026 12:30:22 +0200 Subject: [PATCH 183/449] feat: #502 resource templates with basic template matcher (#606) --- conformance-test/conformance-baseline.yml | 3 +- .../sdk/conformance/ConformanceResources.kt | 11 +- .../kotlin/sdk/client/ClientTest.kt | 5 +- .../kotlin/AbstractPromptIntegrationTest.kt | 16 +- .../kotlin/AbstractResourceIntegrationTest.kt | 7 +- .../sdk/server/ServerResourceTemplateTest.kt | 222 ++++++++++++++++ kotlin-sdk-core/api/kotlin-sdk-core.api | 35 +++ .../kotlin/sdk/shared/Protocol.kt | 21 +- .../kotlin/sdk/types/McpException.kt | 7 +- .../kotlin/sdk/types/jsonRpc.kt | 3 + .../sdk/utils/PathSegmentTemplateMatcher.kt | 157 ++++++++++++ .../sdk/utils/ResourceTemplateMatcher.kt | 67 +++++ .../kotlin/sdk/utils/UriUtils.kt | 16 ++ .../PathSegmentTemplateMatcherSecurityTest.kt | 221 ++++++++++++++++ .../utils/PathSegmentTemplateMatcherTest.kt | 236 ++++++++++++++++++ .../kotlin/sdk/utils/UriUtils.js.kt | 16 ++ .../kotlin/sdk/utils/UriUtils.jvm.kt | 14 ++ .../kotlin/sdk/utils/UriUtils.native.kt | 3 + .../kotlin/sdk/utils/UriUtils.wasmJs.kt | 4 + kotlin-sdk-server/api/kotlin-sdk-server.api | 9 + .../kotlin/sdk/server/Feature.kt | 17 ++ .../kotlin/sdk/server/Server.kt | 160 ++++++++++-- 22 files changed, 1190 insertions(+), 60 deletions(-) create mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourceTemplateTest.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/PathSegmentTemplateMatcher.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/ResourceTemplateMatcher.kt create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/UriUtils.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/PathSegmentTemplateMatcherSecurityTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/PathSegmentTemplateMatcherTest.kt create mode 100644 kotlin-sdk-core/src/jsMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/UriUtils.js.kt create mode 100644 kotlin-sdk-core/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/UriUtils.jvm.kt create mode 100644 kotlin-sdk-core/src/nativeMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/UriUtils.native.kt create mode 100644 kotlin-sdk-core/src/wasmJsMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/UriUtils.wasmJs.kt diff --git a/conformance-test/conformance-baseline.yml b/conformance-test/conformance-baseline.yml index 3788847..337d8ce 100644 --- a/conformance-test/conformance-baseline.yml +++ b/conformance-test/conformance-baseline.yml @@ -1,7 +1,6 @@ # Conformance test baseline - expected failures # Add entries here as tests are identified as known SDK limitations -server: - - resources-templates-read +server: [] client: - elicitation-sep1034-client-defaults diff --git a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceResources.kt b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceResources.kt index a099037..89d83f6 100644 --- a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceResources.kt +++ b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceResources.kt @@ -45,18 +45,17 @@ fun Server.registerConformanceResources() { } // 3. Template resource - // Note: The SDK does not currently support addResourceTemplate(). - // Register as a static resource; template listing is handled separately. - addResource( - uri = "test://template/{id}/data", + addResourceTemplate( + uriTemplate = "test://template/{id}/data", name = "template", description = "A template resource for testing", mimeType = "application/json", - ) { request -> + ) { request, variables -> + val id = variables["id"] ReadResourceResult( listOf( TextResourceContents( - text = "content for ${request.uri}", + text = """{"id": "$id"}""", uri = request.uri, mimeType = "application/json", ), diff --git a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt index 0726fa8..b117dbf 100644 --- a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt +++ b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt @@ -1,5 +1,6 @@ package io.modelcontextprotocol.kotlin.sdk.client +import io.kotest.matchers.shouldBe import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions import io.modelcontextprotocol.kotlin.sdk.server.ServerSession @@ -266,8 +267,8 @@ class ClientTest { client.connect(failingTransport) } - assertEquals(-32600, exception.code) - assertEquals("MCP error -32600: Invalid Request", exception.message) + exception.code shouldBe -32600 + exception.message shouldBe "Invalid Request" assertTrue(closed) } diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt index 9380401..d14ae88 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt @@ -1,6 +1,7 @@ package io.modelcontextprotocol.kotlin.sdk.integration.kotlin import io.kotest.assertions.withClue +import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequestParams @@ -8,7 +9,6 @@ import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult import io.modelcontextprotocol.kotlin.sdk.types.McpException import io.modelcontextprotocol.kotlin.sdk.types.PromptArgument import io.modelcontextprotocol.kotlin.sdk.types.PromptMessage -import io.modelcontextprotocol.kotlin.sdk.types.RPCError import io.modelcontextprotocol.kotlin.sdk.types.Role import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.types.TextContent @@ -688,13 +688,13 @@ abstract class AbstractPromptIntegrationTest : KotlinTestBase() { } } - val expectedMessage = "MCP error -32603: Prompt not found: non-existent-prompt" + val expectedMessage = "Prompt not found: non-existent-prompt" - assertEquals( - RPCError.ErrorCode.INTERNAL_ERROR, - exception.code, - "Exception code should be INTERNAL_ERROR: ${RPCError.ErrorCode.INTERNAL_ERROR}", - ) - assertEquals(expectedMessage, exception.message, "Unexpected error message for non-existent prompt") + withClue("Exception code should be INTERNAL_ERROR: -32603") { + exception.code shouldBe -32603 + } + withClue("Unexpected error message for non-existent prompt") { + exception.message shouldBe expectedMessage + } } } diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt index 40b9552..4fc53a5 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt @@ -213,14 +213,11 @@ abstract class AbstractResourceIntegrationTest : KotlinTestBase() { } } - val expectedMessage = "MCP error -32603: Resource not found: test://nonexistent.txt" - assertEquals( - RPCError.ErrorCode.INTERNAL_ERROR, + RPCError.ErrorCode.RESOURCE_NOT_FOUND, exception.code, - "Exception code should be INTERNAL_ERROR: ${RPCError.ErrorCode.INTERNAL_ERROR}", + "Exception code should be RESOURCE_NOT_FOUND: ${RPCError.ErrorCode.RESOURCE_NOT_FOUND}", ) - assertEquals(expectedMessage, exception.message, "Unexpected error message for invalid resource URI") } @Test diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourceTemplateTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourceTemplateTest.kt new file mode 100644 index 0000000..c8ace19 --- /dev/null +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourceTemplateTest.kt @@ -0,0 +1,222 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.kotest.matchers.collections.shouldBeEmpty +import io.kotest.matchers.collections.shouldHaveSize +import io.kotest.matchers.maps.shouldContainKey +import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.shouldBe +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.ListResourceTemplatesRequest +import io.modelcontextprotocol.kotlin.sdk.types.McpException +import io.modelcontextprotocol.kotlin.sdk.types.RPCError +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult +import io.modelcontextprotocol.kotlin.sdk.types.ResourceTemplate +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +class ServerResourceTemplateTest : AbstractServerFeaturesTest() { + + override fun getServerCapabilities(): ServerCapabilities = ServerCapabilities( + resources = ServerCapabilities.Resources(listChanged = null, subscribe = null), + ) + + @Test + fun `listResourceTemplates should return registered templates`() = runTest { + server.addResourceTemplate("test://data/{id}", "Test Data", mimeType = "text/plain") { _, _ -> + ReadResourceResult(listOf(TextResourceContents("content", "test://data/1"))) + } + + val result = client.listResourceTemplates(ListResourceTemplatesRequest()) + + result.resourceTemplates shouldHaveSize 1 + result.resourceTemplates[0] shouldNotBeNull { + uriTemplate shouldBe "test://data/{id}" + name shouldBe "Test Data" + mimeType shouldBe "text/plain" + } + } + + @Test + fun `listResourceTemplates should return empty list when none registered`() = runTest { + val result = client.listResourceTemplates(ListResourceTemplatesRequest()) + + result.resourceTemplates.shouldBeEmpty() + } + + @Test + fun `readResource should match URI against template and invoke handler`() = runTest { + server.addResourceTemplate("test://items/{itemId}", "Item", mimeType = "text/plain") { request, variables -> + val itemId = variables["itemId"] ?: "unknown" + ReadResourceResult( + listOf(TextResourceContents(text = "item=$itemId", uri = request.uri, mimeType = "text/plain")), + ) + } + + val result = client.readResource(ReadResourceRequest(ReadResourceRequestParams("test://items/42"))) + + result.contents shouldBe + listOf(TextResourceContents(uri = "test://items/42", mimeType = "text/plain", text = "item=42")) + } + + @Test + fun `readResource should extract multiple URI template variables`() = runTest { + val capturedVars = CompletableDeferred>() + server.addResourceTemplate( + uriTemplate = "test://users/{userId}/posts/{postId}", + name = "User Post", + mimeType = "text/plain", + ) { _, variables -> + capturedVars.complete(variables) + ReadResourceResult(listOf(TextResourceContents("ok", "test://users/alice/posts/99"))) + } + + client.readResource(ReadResourceRequest(ReadResourceRequestParams("test://users/alice/posts/99"))) + + val vars = capturedVars.await() + vars shouldContainKey "userId" + vars shouldContainKey "postId" + vars["userId"] shouldBe "alice" + vars["postId"] shouldBe "99" + } + + @Test + fun `readResource should prefer exact resource match over template`() = runTest { + var exactHandlerCalled = false + server.addResource("test://items/special", "Special Item", "An exact resource") { + exactHandlerCalled = true + ReadResourceResult(listOf(TextResourceContents("exact", "test://items/special"))) + } + server.addResourceTemplate("test://items/{itemId}", "Item Template") { _, _ -> + ReadResourceResult(listOf(TextResourceContents("template", "test://items/special"))) + } + + val result = client.readResource(ReadResourceRequest(ReadResourceRequestParams("test://items/special"))) + + exactHandlerCalled shouldBe true + (result.contents[0] as TextResourceContents).text shouldBe "exact" + } + + @Test + fun `readResource should select most specific template when multiple match`() = runTest { + // "test://users/profile" has more literal chars than "test://users/{id}" — should win + server.addResourceTemplate("test://users/{id}", "Generic User") { _, variables -> + ReadResourceResult(listOf(TextResourceContents("generic:${variables["id"]}", "test://users/profile"))) + } + server.addResourceTemplate("test://users/profile", "Profile") { _, _ -> + ReadResourceResult(listOf(TextResourceContents("profile-page", "test://users/profile"))) + } + + val result = client.readResource(ReadResourceRequest(ReadResourceRequestParams("test://users/profile"))) + + (result.contents[0] as TextResourceContents).text shouldBe "profile-page" + } + + @Test + fun `readResource should return RESOURCE_NOT_FOUND error when no match`() = runTest { + val exception = assertThrows { + client.readResource(ReadResourceRequest(ReadResourceRequestParams("test://nonexistent/uri"))) + } + + exception.code shouldBe RPCError.ErrorCode.RESOURCE_NOT_FOUND + } + + @Test + fun `resourceTemplates property should reflect registered templates`() { + server.addResourceTemplate(ResourceTemplate("test://a/{x}", "A")) { _, _ -> + ReadResourceResult(emptyList()) + } + server.addResourceTemplate(ResourceTemplate("test://b/{y}", "B")) { _, _ -> + ReadResourceResult(emptyList()) + } + + val templates = server.resourceTemplates + + templates shouldBe listOf( + ResourceTemplate("test://a/{x}", "A"), + ResourceTemplate("test://b/{y}", "B"), + ) + } + + @Test + fun `removeResourceTemplate should remove a registered template`() { + server.addResourceTemplate("test://items/{id}", "Item") { _, _ -> + ReadResourceResult(emptyList()) + } + + val removed = server.removeResourceTemplate("test://items/{id}") + + removed shouldBe true + server.resourceTemplates.size shouldBe 0 + } + + @Test + fun `removeResourceTemplate should return false when template does not exist`() { + val removed = server.removeResourceTemplate("test://nonexistent/{id}") + + removed shouldBe false + } + + @Test + fun `addResourceTemplate should throw when resources capability is not supported`() { + val noResourcesServer = Server( + serverInfo = Implementation("test", "1.0"), + options = ServerOptions(capabilities = ServerCapabilities()), + ) + + assertThrows { + noResourcesServer.addResourceTemplate("test://{id}", "Test") { _, _ -> + ReadResourceResult(emptyList()) + } + } + } + + @Test + fun `addResourceTemplate with ResourceTemplate object should register correctly`() = runTest { + val template = ResourceTemplate( + uriTemplate = "test://docs/{section}", + name = "Documentation", + description = "API docs", + mimeType = "text/html", + ) + server.addResourceTemplate(template) { request, variables -> + val section = variables["section"] ?: "index" + ReadResourceResult( + listOf(TextResourceContents("docs for $section", request.uri, mimeType = "text/html")), + ) + } + + val result = client.readResource(ReadResourceRequest(ReadResourceRequestParams("test://docs/api"))) + + result.contents shouldHaveSize 1 + (result.contents[0] as TextResourceContents).text shouldBe "docs for api" + } + + @Test + fun `listResourceTemplates should include description from template`() = runTest { + server.addResourceTemplate( + uriTemplate = "test://data/{id}", + name = "Data", + description = "Parameterized data resource", + mimeType = "application/json", + ) { _, _ -> + ReadResourceResult(emptyList()) + } + + val result = client.listResourceTemplates(ListResourceTemplatesRequest()) + + result.resourceTemplates shouldBe listOf( + ResourceTemplate( + name = "Data", + uriTemplate = "test://data/{id}", + description = "Parameterized data resource", + mimeType = "application/json", + ), + ) + } +} diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index af22323..dc3da3e 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -2713,6 +2713,7 @@ public abstract interface annotation class io/modelcontextprotocol/kotlin/sdk/ty } public final class io/modelcontextprotocol/kotlin/sdk/types/McpException : java/lang/Exception { + public fun (I)V public fun (ILjava/lang/String;)V public fun (ILjava/lang/String;Lkotlinx/serialization/json/JsonElement;)V public fun (ILjava/lang/String;Lkotlinx/serialization/json/JsonElement;Ljava/lang/Throwable;)V @@ -3310,6 +3311,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/RPCError$ErrorCode { public static final field METHOD_NOT_FOUND I public static final field PARSE_ERROR I public static final field REQUEST_TIMEOUT I + public static final field RESOURCE_NOT_FOUND I } public final class io/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest { @@ -4731,3 +4733,36 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/WithMeta$DefaultImpl public static fun get_meta (Lio/modelcontextprotocol/kotlin/sdk/types/WithMeta;)Lkotlinx/serialization/json/JsonObject; } +public final class io/modelcontextprotocol/kotlin/sdk/utils/MatchResult { + public fun (Ljava/util/Map;I)V + public fun equals (Ljava/lang/Object;)Z + public final fun getScore ()I + public final fun getVariables ()Ljava/util/Map; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/modelcontextprotocol/kotlin/sdk/utils/PathSegmentTemplateMatcher : io/modelcontextprotocol/kotlin/sdk/utils/ResourceTemplateMatcher { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/utils/PathSegmentTemplateMatcher$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ResourceTemplate;)V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ResourceTemplate;I)V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ResourceTemplate;II)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/ResourceTemplate;IIILkotlin/jvm/internal/DefaultConstructorMarker;)V + public static final fun getFactory ()Lio/modelcontextprotocol/kotlin/sdk/utils/ResourceTemplateMatcherFactory; + public fun getResourceTemplate ()Lio/modelcontextprotocol/kotlin/sdk/types/ResourceTemplate; + public fun match (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/utils/MatchResult; +} + +public final class io/modelcontextprotocol/kotlin/sdk/utils/PathSegmentTemplateMatcher$Companion { + public final fun getFactory ()Lio/modelcontextprotocol/kotlin/sdk/utils/ResourceTemplateMatcherFactory; +} + +public abstract interface class io/modelcontextprotocol/kotlin/sdk/utils/ResourceTemplateMatcher { + public abstract fun getResourceTemplate ()Lio/modelcontextprotocol/kotlin/sdk/types/ResourceTemplate; + public abstract fun match (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/utils/MatchResult; +} + +public abstract interface class io/modelcontextprotocol/kotlin/sdk/utils/ResourceTemplateMatcherFactory { + public abstract fun create (Lio/modelcontextprotocol/kotlin/sdk/types/ResourceTemplate;)Lio/modelcontextprotocol/kotlin/sdk/utils/ResourceTemplateMatcher; +} + diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt index e9d833a..0e3db35 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt @@ -39,6 +39,7 @@ import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.encodeToJsonElement +import kotlin.coroutines.cancellation.CancellationException import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds @@ -312,6 +313,7 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio return } + @Suppress("TooGenericExceptionCaught", "InstanceOfCheckForException") try { val result = handler(request, RequestHandlerExtra()) logger.trace { "Request handled successfully: ${request.method} (id: ${request.id})" } @@ -322,19 +324,20 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio result = result ?: EmptyResult(), ), ) + } catch (e: CancellationException) { + throw e } catch (cause: Throwable) { logger.error(cause) { "Error handling request: ${request.method} (id: ${request.id})" } try { - transport?.send( - JSONRPCError( - id = request.id, - error = RPCError( - code = RPCError.ErrorCode.INTERNAL_ERROR, - message = cause.message ?: "Internal error", - ), - ), - ) + val rpcError = if (cause is McpException) { + RPCError(code = cause.code, message = cause.message.orEmpty(), data = cause.data) + } else { + RPCError(code = RPCError.ErrorCode.INTERNAL_ERROR, message = cause.message ?: "Internal error") + } + transport?.send(JSONRPCError(id = request.id, error = rpcError)) + } catch (e: CancellationException) { + throw e } catch (sendError: Throwable) { logger.error(sendError) { "Failed to send error response for request: ${request.method} (id: ${request.id})" diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/McpException.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/McpException.kt index f63f4a9..6b12492 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/McpException.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/McpException.kt @@ -8,12 +8,13 @@ import kotlin.jvm.JvmOverloads * * @property code The MCP/JSON‑RPC error code. * @property data Optional additional error payload as a JSON element; `null` when not provided. - * @param message The error message. + * @param message The error message. Used verbatim as [Exception.message] — no error code prefix is prepended. + * Defaults to `"MCP error $code"` when not provided. * @param cause The original cause. */ public class McpException @JvmOverloads public constructor( public val code: Int, - message: String, + message: String = "MCP error $code", public val data: JsonElement? = null, cause: Throwable? = null, -) : Exception("MCP error $code: $message", cause) +) : Exception(message, cause) diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt index 68b2f02..6a53d35 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt @@ -251,6 +251,9 @@ public data class RPCError(val code: Int, val message: String, val data: JsonEle /** Request timed out */ public const val REQUEST_TIMEOUT: Int = -32001 + /** Resource not found */ + public const val RESOURCE_NOT_FOUND: Int = -32002 + // Standard JSON-RPC 2.0 error codes /** Invalid JSON was received */ diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/PathSegmentTemplateMatcher.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/PathSegmentTemplateMatcher.kt new file mode 100644 index 0000000..c5619d5 --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/PathSegmentTemplateMatcher.kt @@ -0,0 +1,157 @@ +package io.modelcontextprotocol.kotlin.sdk.utils + +import io.github.oshai.kotlinlogging.KotlinLogging +import io.ktor.http.decodeURLPart +import io.modelcontextprotocol.kotlin.sdk.types.ResourceTemplate +import kotlinx.collections.immutable.toImmutableMap +import kotlin.jvm.JvmOverloads +import kotlin.jvm.JvmStatic + +// Max URL/path length to prevent DoS or unexpected large payloads. +private const val MAX_URL_LENGTH = 2048 + +// Max template/uri depth to prevent overly nested or complex templates. +private const val MAX_DEPTH = 50 + +// Literal segments are more specific than variable captures. +private const val LITERAL_MATCH_SCORE = 2 +private const val VARIABLE_MATCH_SCORE = 1 + +/** + * A [ResourceTemplateMatcher] that matches resource URIs against an RFC 6570 Level 1 + * URI template by splitting both the URI and the template on `/` and comparing each + * segment in order. + * + * ### Supported template syntax + * + * Only RFC 6570 **Level 1** is supported: simple `{variable}` expressions where the + * entire path segment is a single variable. Operator expressions (`{+var}`, `{#var}`, + * `{.var}`, `{/var}`, etc.) and multi-variable expressions (`{a,b}`) are **not** + * recognized — segments containing them are treated as literals. + * + * ### Matching rules + * + * - The URI and template must have the same number of `/`-delimited segments. + * - Literal segments must match exactly (after percent-decoding the URI segment). + * - `{variable}` segments capture the percent-decoded URI segment value. + * - Query strings and fragments (`?`, `#`) are **not** stripped — they become part of + * the captured variable value for the segment that contains them. + * + * ### Specificity scoring + * + * When multiple templates match the same URI, each matched literal segment contributes + * 2 points and each variable capture contributes 1 point. The highest-scoring match wins. + * + * ### Safety limits + * + * - URIs longer than [maxUriLength] characters are rejected. + * - Templates and URIs with more than [maxDepth] segments are rejected. + * + * ### Security contract for handler authors + * + * Values in [MatchResult.variables] are attacker-controlled strings extracted from + * the incoming URI. They are percent-decoded (one pass only) before being returned. + * Handlers **must** treat them as untrusted input and validate or sanitize them + * before using them to construct file paths, database queries, downstream URLs, or + * any other security-sensitive operation. + * + * In particular: + * - A decoded value may contain `/`, `..`, null bytes (`\u0000`), `?`, or `#`. + * - `%252F` in the URI becomes `%2F` in the variable — exactly one decode pass is applied. + * + * ### Platform limitations + * + * Dot-segment normalization (resolving `..` and `.` in the URI path) is performed + * on JVM and JS targets using the platform-native URI parser. On native and WASM targets + * no normalizer is available, so dot-segment traversal is **not** mitigated at this + * layer — handlers on those targets must normalize paths themselves. + * + * @param resourceTemplate The resource template to match against. Must follow RFC 6570 Level 1 syntax. + * @param maxUriLength Maximum allowed length for incoming URIs. Defaults to 2048. + * @param maxDepth Maximum allowed segment count for the template/uri. Defaults to 50. + * + * @property resourceTemplate The resource template against which resource URIs will be matched. + */ +public class PathSegmentTemplateMatcher @JvmOverloads constructor( + override val resourceTemplate: ResourceTemplate, + private val maxDepth: Int = MAX_DEPTH, + private val maxUriLength: Int = MAX_URL_LENGTH, +) : ResourceTemplateMatcher { + + public companion object { + /** + * A [ResourceTemplateMatcherFactory] that creates [PathSegmentTemplateMatcher] instances + * with default limits. Pass this to [io.modelcontextprotocol.kotlin.sdk.server.ServerOptions] + * to use path-segment matching, or supply a custom factory to override the matching strategy. + */ + @JvmStatic + public val factory: ResourceTemplateMatcherFactory = ResourceTemplateMatcherFactory { + PathSegmentTemplateMatcher(it) + } + + @JvmStatic + private val logger = KotlinLogging.logger {} + } + + private val templateParts: List + + // Maps segment index to variable name; indices absent from this map are literal segments. + private val variableIndices: Map + + init { + val template = resourceTemplate.uriTemplate + require(template.isNotBlank()) { "Resource template cannot be blank" } + templateParts = template.trim('/').split("/") + require(templateParts.size <= maxDepth) { + "Template is too complex (max depth=$maxDepth)" + } + + val vars = mutableMapOf() + for (i in templateParts.indices) { + val segment = templateParts[i] + if (segment.startsWith("{") && segment.endsWith("}")) { + val name = segment.removeSurrounding("{", "}").trim() + require(name.isNotEmpty()) { "Invalid variable name in template: $segment" } + vars[i] = name + } + } + variableIndices = vars.toImmutableMap() + } + + @Suppress("ReturnCount") + override fun match(resourceUri: String): MatchResult? { + if (resourceUri.length > maxUriLength) { + logger.debug { "URL is too long (max=$maxUriLength)" } + return null + } + + // Resolve dot-segments (e.g. /a/../b → /a/b) before splitting. + // Prevents traversal attacks on JVM/JS; no-op on native/WASM targets. + val normalized = normalizeUri(resourceUri) + + val urlParts = normalized.trim('/').split("/") + if (urlParts.size > maxDepth) { + logger.debug { "URI has too many segments (max=$maxDepth)" } + return null + } + if (urlParts.size != templateParts.size) return null + + val variables = mutableMapOf() + var score = 0 + + for (i in templateParts.indices) { + val urlSegment = urlParts[i].decodeURLPart() + val variableName = variableIndices[i] + if (variableName != null) { + variables[variableName] = urlSegment + score += VARIABLE_MATCH_SCORE + } else if (templateParts[i] == urlSegment) { + score += LITERAL_MATCH_SCORE + } else { + return null + } + } + + return MatchResult(variables = variables.toImmutableMap(), score = score) + } +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/ResourceTemplateMatcher.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/ResourceTemplateMatcher.kt new file mode 100644 index 0000000..5ba956d --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/ResourceTemplateMatcher.kt @@ -0,0 +1,67 @@ +package io.modelcontextprotocol.kotlin.sdk.utils + +import io.modelcontextprotocol.kotlin.sdk.types.ResourceTemplate + +/** + * Represents the result of a successful template match. + * + * A higher [score] indicates a more specific match. Implementations must ensure that + * literal segment matches contribute more to the score than variable captures, so that + * a fully literal template (e.g., `users/profile`) always outscores a parameterized + * template (e.g., `users/{id}`) for the same URI. + * + * @property variables A mapping of variable names in the template to their matched values in the URL. + * @property score A non-negative measure of match specificity — higher means more literal segments matched. + */ +public class MatchResult(public val variables: Map, public val score: Int) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is MatchResult) return false + return score == other.score && variables == other.variables + } + + override fun hashCode(): Int = 31 * variables.hashCode() + score + + override fun toString(): String = "MatchResult(variables=$variables, score=$score)" +} + +/** + * Matches resource URIs against a [ResourceTemplate]. + * + * Implementations parse a URI template once at construction time and then match + * candidate URIs against that template via [match]. The returned [MatchResult.score] + * must reflect match specificity so that a selection algorithm can prefer the most + * specific template when multiple templates match the same URI. + */ +public interface ResourceTemplateMatcher { + + public val resourceTemplate: ResourceTemplate + + /** + * Matches a given resource URI against the defined resource template. + * + * @param resourceUri The resource URI to be matched. + * @return A [MatchResult] containing the mapping of variables and a match score + * if the URI matches the template, or null if no match is found. + */ + public fun match(resourceUri: String): MatchResult? +} + +/** + * Factory interface for creating instances of [ResourceTemplateMatcher]. + * + * A [ResourceTemplateMatcher] is used to match resource URIs against a given + * [ResourceTemplate], which adheres to the RFC 6570 URI Template specification. + * This factory abstracts the creation process of a matcher, allowing different + * implementations to define custom matching logic or safeguards (e.g., security + * measures or restrictions on template complexity). + */ +public fun interface ResourceTemplateMatcherFactory { + /** + * Creates a resource template matcher for the given resource template. + * + * @param resourceTemplate The resource template to create a matcher for. + * @return A [ResourceTemplateMatcher] instance that can match URIs against the provided template. + */ + public fun create(resourceTemplate: ResourceTemplate): ResourceTemplateMatcher +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/UriUtils.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/UriUtils.kt new file mode 100644 index 0000000..452333b --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/UriUtils.kt @@ -0,0 +1,16 @@ +package io.modelcontextprotocol.kotlin.sdk.utils + +/** + * Normalizes [uri] by resolving dot-segments (`/a/b/../c` → `/a/c`) using the + * platform-native URI parser. + * + * On JVM uses [java.net.URI.normalize]. On JS uses the browser/Node.js `URL` API. + * On native and WASM targets [uri] is returned unchanged — no platform normalizer + * is available, so callers on those targets do not receive dot-segment resolution. + * + * Returns [uri] unchanged if it cannot be parsed or normalized. + * + * **Security note**: call this before splitting a URI into segments to prevent + * dot-segment traversal attacks (e.g. `public/../private/secret`). + */ +internal expect fun normalizeUri(uri: String): String diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/PathSegmentTemplateMatcherSecurityTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/PathSegmentTemplateMatcherSecurityTest.kt new file mode 100644 index 0000000..fe3fca4 --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/PathSegmentTemplateMatcherSecurityTest.kt @@ -0,0 +1,221 @@ +package io.modelcontextprotocol.kotlin.sdk.utils + +import io.kotest.assertions.withClue +import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import io.modelcontextprotocol.kotlin.sdk.types.ResourceTemplate +import kotlin.test.Test + +/** + * Security regression tests for [PathSegmentTemplateMatcher]. + * + * Every test **fails** when an attack succeeds — green = defended. + * + * Findings addressed: + * - F1: Exactly one percent-decode pass (no double-decoding). + * - F2: Variable values are attacker-controlled; handlers must validate them. + * - F3: URI segment depth cap prevents allocation amplification. + * - F4: Dot-segment normalization blocks traversal on JVM/JS (no-op on native/WASM). + * - F5: Double-slash — collapsed by JVM normalization or causes size mismatch on native/WASM. + */ +class PathSegmentTemplateMatcherSecurityTest { + + // region: F4: Dot-segment traversal + + /** + * URIs containing `..` or `.` path segments. + * + * On JVM/JS: [normalizeUri] resolves these to their canonical target path, so the + * traversal URI correctly routes to the same template the normalized path would match. + * On native/WASM: [normalizeUri] is a no-op — the extra segments produce a size mismatch + * and the match returns null (different reason, same safety outcome). + */ + private data class TraversalCase(val label: String, val template: String, val uri: String) + + private val traversalNoMatchCases = listOf( + TraversalCase( + label = "single .. traversal to reach private template", + template = "app://host/private/{name}", + uri = "app://host/public/../private/secret", + ), + TraversalCase( + label = "double .. traversal", + template = "app://host/admin/{name}", + uri = "app://host/a/b/../../admin/config", + ), + TraversalCase( + label = ". current-segment in path", + template = "app://host/users/{id}", + uri = "app://host/./users/42", + ), + ) + + @Test + fun `dot-segment traversal resolves to correct target without raw path markers in variables`() { + // On JVM/JS: normalizeUri() resolves `..` and `.` before segment matching. + // → The traversal URI correctly routes to the normalized target template. + // On native/WASM: no normalization — extra segments cause a size mismatch → null. + // In all cases, captured variable values must not contain raw traversal markers. + for ((label, template, uri) in traversalNoMatchCases) { + withClue("traversal '$label': uri='$uri', template='$template'") { + val result = matcher(template).match(uri) + result?.variables?.values?.forEach { value -> + withClue("variable value '$value' must not contain raw '..' or '.' traversal marker") { + value.contains("..") shouldBe false + value shouldNotBe "." + } + } + } + } + } + + @Test + fun `dot-segment URI matches the template it normalizes to`() { + // "public/../users/42" normalizes to "users/42" on JVM/JS, no-op on native/WASM. + // On JVM/JS this must match; the test is platform-conditional via expect/actual. + val result = matcher("users/{id}").match("users/./42") + // After normalization "users/./42" → "users/42" (JVM/JS) or stays 3 segments (native/WASM). + // Either null (native/WASM — 3 segments vs 2) or a valid match (JVM/JS) is acceptable. + // What must NOT happen is a wrong-template match. We check the value if it matches. + result?.variables?.get("id") shouldBe result?.let { "42" } + } + + // endregion + // region: F1: Single percent-decode pass (no double-decoding) + + /** + * %25 is the encoding of '%'. %252F = "%2F" after one decode pass — must NOT become "/". + * If two decode passes were applied, %252F → %2F → /, enabling path traversal. + */ + @Test + fun `double-encoded percent-sign is decoded only once`() { + val result = matcher("files/{path}").match("files/%252Fetc%252Fpasswd") + result.shouldNotBeNull { + // One decode pass: %25 → %, %2F → /... but only %25 is decoded first. + // %252F → %2F (the %25 decodes to %, leaving literal "%2F") + withClue("double-encoded %2F must not become a slash after one decode pass") { + variables["path"]?.contains('/') shouldBe false + } + } + } + + @Test + fun `percent-encoded slash percent-2F in variable is decoded to slash`() { + // %2F IS decoded (one pass) — the result is "/" in the variable value. + // This documents the known behavior: handlers receive decoded values and must + // validate that path separators are not present when constructing file paths. + val result = matcher("files/{path}").match("files/..%2Fetc%2Fpasswd") + result.shouldNotBeNull { + withClue("decoded %2F becomes a path separator — handler must validate") { + variables["path"] shouldBe "../etc/passwd" + } + } + } + + @Test + fun `null byte percent-00 is decoded and passed to handler`() { + // %00 decodes to the null character. Handlers using C-string APIs or file paths + // must reject values containing null bytes. + val result = matcher("items/{id}").match("items/foo%00bar") + result.shouldNotBeNull { + withClue("decoded null byte is present — handler must reject") { + variables["id"] shouldBe "foo\u0000bar" + } + } + } + + @Test + fun `pct-encoded identity bypass`() { + // %69 = 'i'. "adm%69n" decodes to "admin". If a handler compares the raw URI + // against a deny-list of "admin", it must use the decoded value. + val result = matcher("svc/{role}").match("svc/adm%69n") + result.shouldNotBeNull { + variables["role"] shouldBe "admin" + } + } + + // endregion + // region: F3: URI segment depth cap + + @Test + fun `URI exceeding MAX_DEPTH segments returns null`() { + // 51 slash-separated single-char segments → well over the depth cap of 50 + val deepUri = (1..51).joinToString("/") { "x" } + matcher("{a}").match(deepUri) shouldBe null + } + + @Test + fun `URI at exactly MAX_DEPTH segments is not rejected by depth cap`() { + // A URI with exactly 50 segments must be evaluated normally (still non-matching + // against a 1-segment template, but not rejected by the depth guard itself). + val atLimit = (1..50).joinToString("/") { "x" } + // Template has 1 segment, URI has 50 — size mismatch, not depth rejection. + // Both code paths return null; this test verifies no exception is thrown. + matcher("{id}").match(atLimit) shouldBe null + } + + @Test + fun `many-segment URI against many templates completes quickly`() { + val deepUri = (1..49).joinToString("/") { "seg$it" } + val matchers = (1..100).map { matcher("{v$it}") } + // Must not allocate or compute excessively — just verify it finishes. + matchers.forEach { it.match(deepUri) } + } + + // endregion + // region: F5: Double-slash does not produce spurious matches + + @Test + fun `double-slash in URI path is normalized on JVM or causes size mismatch elsewhere`() { + // On JVM: URI("users//42").normalize() collapses "//" to "/" → "users/42" → matches. + // On native/WASM: no normalization — split produces 3 segments vs 2 in template → null. + // Either outcome is safe: no empty-segment injection reaches the handler unchecked. + val result = matcher("users/{id}").match("users//42") + // If matched (JVM), the captured value must be the legitimate path segment. + result?.variables?.get("id") shouldBe result?.let { "42" } + } + + @Test + fun `double-slash in scheme authority path is normalized on JVM or causes size mismatch elsewhere`() { + // On JVM: URI("app://host//injected").normalize() collapses the path "//" to "/" + // → "app://host/injected" → matches "app://host/{id}". + // On native/WASM: no normalization — extra empty segment causes size mismatch → null. + val result = matcher("app://host/{id}").match("app://host//injected") + // If matched (JVM), the captured value must be the legitimate path segment. + result?.variables?.get("id") shouldBe result?.let { "injected" } + } + + // endregion + // region: Query string and fragment pass-through + + @Test + fun `query string and fragment are captured in variable values - handlers must validate`() { + // PathSegmentTemplateMatcher splits on '/' only and does not strip query + // strings or fragments. They are captured verbatim inside the variable value. + // Handlers MUST reject or strip '?' and '#' before using values in file paths, + // database queries, or any other security-sensitive operation. + + // Segment split of "api://host/foo?bar=baz" → ["api:", "", "host", "foo?bar=baz"] (4). + // Template "api://host/{id}" → ["api:", "", "host", "{id}"] (4). + // → Matches on all platforms; query string ends up in the variable value. + val queryResult = matcher("api://host/{id}").match("api://host/foo?bar=baz") + queryResult.shouldNotBeNull { + variables["id"] shouldBe "foo?bar=baz" + } + + // Fragment is also captured verbatim — same reasoning. + val fragmentResult = matcher("api://host/{id}").match("api://host/foo#section") + fragmentResult.shouldNotBeNull { + variables["id"] shouldBe "foo#section" + } + } + + // endregion + // region: Helpers + + private fun matcher(uriTemplate: String): PathSegmentTemplateMatcher = PathSegmentTemplateMatcher( + resourceTemplate = ResourceTemplate(uriTemplate, "Test"), + ) + // endregion +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/PathSegmentTemplateMatcherTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/PathSegmentTemplateMatcherTest.kt new file mode 100644 index 0000000..2f43b5e --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/PathSegmentTemplateMatcherTest.kt @@ -0,0 +1,236 @@ +package io.modelcontextprotocol.kotlin.sdk.utils + +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.maps.shouldBeEmpty +import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.shouldBe +import io.modelcontextprotocol.kotlin.sdk.types.ResourceTemplate +import kotlin.test.Test + +class PathSegmentTemplateMatcherTest { + + // region: Construction + + @Test + fun `should throw on blank template`() { + shouldThrow { + matcher(" ") + } + } + + @Test + fun `should throw on empty variable name`() { + shouldThrow { + matcher("users/{}") + } + } + + @Test + fun `should throw when depth exceeds maxTemplateDepth`() { + val deep = "a/b/c/d/e/f/g/h/i/j/k" // 11 segments + shouldThrow { + matcher(deep, maxDepth = 10) + } + } + + @Test + fun `should accept template at exactly maxTemplateDepth`() { + val atLimit = "a/b/c/d/e/f/g/h/i/j" // 10 segments + matcher(atLimit, maxDepth = 10) // must not throw + } + + // endregion + // region: Basic matching + + @Test + fun `should return null for URI with fewer segments than template`() { + matcher("users/{id}/posts").match("users/42") shouldBe null + } + + @Test + fun `should return null for URI with more segments than template`() { + matcher("users/{id}").match("users/42/extra") shouldBe null + } + + @Test + fun `should match all-literal template`() { + val result = matcher("users/profile").match("users/profile") + result.shouldNotBeNull { + variables.shouldBeEmpty() + } + } + + @Test + fun `should return null when literal segment does not match`() { + matcher("users/profile").match("users/settings") shouldBe null + } + + @Test + fun `should extract single variable`() { + val result = matcher("users/{id}").match("users/42") + result.shouldNotBeNull { + variables["id"] shouldBe "42" + } + } + + @Test + fun `should extract multiple variables`() { + val result = matcher("users/{userId}/posts/{postId}").match("users/alice/posts/99") + result.shouldNotBeNull { + variables["userId"] shouldBe "alice" + variables["postId"] shouldBe "99" + } + } + + @Test + fun `should match template with scheme`() { + val result = matcher("test://items/{id}").match("test://items/42") + result.shouldNotBeNull { + variables["id"] shouldBe "42" + } + } + + // endregion + // region:Scoring + + @Test + fun `all-literal template scores higher than parameterized template for same URI`() { + val literal = matcher("users/profile").match("users/profile")!! + val parameterized = matcher("users/{id}").match("users/profile")!! + (literal.score > parameterized.score) shouldBe true + } + + @Test + fun `score increases with number of segments`() { + val short = matcher("a/b").match("a/b")!! + val long = matcher("a/b/c").match("a/b/c")!! + (long.score > short.score) shouldBe true + } + + @Test + fun `all-literal two-segment template score is 4`() { + // 2 literal segments × LITERAL_MATCH_SCORE(2) = 4 + matcher("users/profile").match("users/profile")!!.score shouldBe 4 + } + + @Test + fun `one-variable two-segment template score is 3`() { + // 1 literal × 2 + 1 variable × 1 = 3 + matcher("users/{id}").match("users/42")!!.score shouldBe 3 + } + + @Test + fun `all-variable template scores one per segment`() { + // 2 variables × VARIABLE_MATCH_SCORE(1) = 2 + matcher("{a}/{b}").match("x/y")!!.score shouldBe 2 + } + + // endregion + // region:URL decoding + + @Test + fun `should URL-decode percent-encoded variable value`() { + val result = matcher("search/{query}").match("search/hello%20world") + result.shouldNotBeNull { + variables["query"] shouldBe "hello world" + } + } + + @Test + fun `should URL-decode percent-encoded literal segment before comparing`() { + // %66 decodes to 'f', so "pro%66ile" == "profile" after decoding — it matches + matcher("users/profile").match("users/pro%66ile").shouldNotBeNull() + } + + // endregion + // region:Length guard + + @Test + fun `should return null when URI exceeds maxUrlLength`() { + val longUri = "a/" + "x".repeat(2048) + matcher("a/{id}", maxUrlLength = 2048).match(longUri) shouldBe null + } + + @Test + fun `should match URI at exactly maxUrlLength`() { + // URI of exactly maxUrlLength characters must be accepted + val uri = "a/" + "x".repeat(2046) // length = 2048 + matcher("a/{id}", maxUrlLength = 2048).match(uri).shouldNotBeNull() + } + + // endregion + // region:Edge cases + + @Test + fun `should match root-level single segment template`() { + val result = matcher("{id}").match("42") + result.shouldNotBeNull { + variables["id"] shouldBe "42" + } + } + + @Test + fun `should treat leading and trailing slashes as equivalent`() { + val result = matcher("/users/{id}/").match("/users/7/") + result.shouldNotBeNull { + variables["id"] shouldBe "7" + } + } + + @Test + fun `should capture empty string for single-segment variable when URI is empty`() { + // "".trim('/').split("/") == [""] — one segment — so {id} captures "" + val result = matcher("{id}").match("") + result.shouldNotBeNull { + variables["id"] shouldBe "" + } + } + + @Test + fun `factory creates matcher equal to direct construction`() { + val template = ResourceTemplate("items/{id}", "Items") + val fromFactory = PathSegmentTemplateMatcher.factory.create(template) + val direct = PathSegmentTemplateMatcher(template) + + val uriToMatch = "items/99" + fromFactory.match(uriToMatch) shouldBe direct.match(uriToMatch) + } + + // endregion + // region: MatchResult equality + + @Test + fun `MatchResult equals by value`() { + val a = MatchResult(mapOf("id" to "1"), score = 3) + val b = MatchResult(mapOf("id" to "1"), score = 3) + a shouldBe b + } + + @Test + fun `MatchResult not equal when score differs`() { + val a = MatchResult(mapOf("id" to "1"), score = 3) + val b = MatchResult(mapOf("id" to "1"), score = 2) + (a == b) shouldBe false + } + + @Test + fun `MatchResult not equal when variables differ`() { + val a = MatchResult(mapOf("id" to "1"), score = 3) + val b = MatchResult(mapOf("id" to "2"), score = 3) + (a == b) shouldBe false + } + + // endregion + // region: Helpers + + private fun matcher( + uriTemplate: String, + maxUrlLength: Int = 2048, + maxDepth: Int = 10, + ): PathSegmentTemplateMatcher = PathSegmentTemplateMatcher( + resourceTemplate = ResourceTemplate(uriTemplate, "Test"), + maxUriLength = maxUrlLength, + maxDepth = maxDepth, + ) + // endregion +} diff --git a/kotlin-sdk-core/src/jsMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/UriUtils.js.kt b/kotlin-sdk-core/src/jsMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/UriUtils.js.kt new file mode 100644 index 0000000..fb8aaf4 --- /dev/null +++ b/kotlin-sdk-core/src/jsMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/UriUtils.js.kt @@ -0,0 +1,16 @@ +package io.modelcontextprotocol.kotlin.sdk.utils + +private external class URL(url: String) { + val href: String +} + +/** + * Normalizes [uri] via the browser/Node.js `URL` API, resolving dot segments and + * normalizing the scheme, host, and path. Returns [uri] unchanged if it cannot be + * parsed (e.g. relative URIs or schemes unsupported by the runtime). + */ +internal actual fun normalizeUri(uri: String): String = try { + URL(uri).href +} catch (_: Throwable) { + uri +} diff --git a/kotlin-sdk-core/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/UriUtils.jvm.kt b/kotlin-sdk-core/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/UriUtils.jvm.kt new file mode 100644 index 0000000..0cde506 --- /dev/null +++ b/kotlin-sdk-core/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/UriUtils.jvm.kt @@ -0,0 +1,14 @@ +package io.modelcontextprotocol.kotlin.sdk.utils + +import java.net.URI +import java.net.URISyntaxException + +/** + * Normalizes [uri] by resolving dot segments (e.g. `/a/b/../c` → `/a/c`) via [URI.normalize]. + * Returns [uri] unchanged if it is not a valid URI. + */ +internal actual fun normalizeUri(uri: String): String = try { + URI(uri).normalize().toString() +} catch (_: URISyntaxException) { + uri +} diff --git a/kotlin-sdk-core/src/nativeMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/UriUtils.native.kt b/kotlin-sdk-core/src/nativeMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/UriUtils.native.kt new file mode 100644 index 0000000..623fdfc --- /dev/null +++ b/kotlin-sdk-core/src/nativeMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/UriUtils.native.kt @@ -0,0 +1,3 @@ +package io.modelcontextprotocol.kotlin.sdk.utils +/** No platform-native URI normalizer is available on native targets; [uri] is returned unchanged. */ +internal actual fun normalizeUri(uri: String): String = uri diff --git a/kotlin-sdk-core/src/wasmJsMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/UriUtils.wasmJs.kt b/kotlin-sdk-core/src/wasmJsMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/UriUtils.wasmJs.kt new file mode 100644 index 0000000..7ad7bce --- /dev/null +++ b/kotlin-sdk-core/src/wasmJsMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/UriUtils.wasmJs.kt @@ -0,0 +1,4 @@ +package io.modelcontextprotocol.kotlin.sdk.utils + +/** No platform-native URI normalizer is available on WASM targets; [uri] is returned unchanged. */ +internal actual fun normalizeUri(uri: String): String = uri diff --git a/kotlin-sdk-server/api/kotlin-sdk-server.api b/kotlin-sdk-server/api/kotlin-sdk-server.api index a6f3b6d..b1b898a 100644 --- a/kotlin-sdk-server/api/kotlin-sdk-server.api +++ b/kotlin-sdk-server/api/kotlin-sdk-server.api @@ -97,6 +97,9 @@ public class io/modelcontextprotocol/kotlin/sdk/server/Server { public final fun addPrompts (Ljava/util/List;)V public final fun addResource (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function3;)V public static synthetic fun addResource$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)V + public final fun addResourceTemplate (Lio/modelcontextprotocol/kotlin/sdk/types/ResourceTemplate;Lkotlin/jvm/functions/Function4;)V + public final fun addResourceTemplate (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function4;)V + public static synthetic fun addResourceTemplate$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function4;ILjava/lang/Object;)V public final fun addResources (Ljava/util/List;)V public final fun addTool (Lio/modelcontextprotocol/kotlin/sdk/types/Tool;Lkotlin/jvm/functions/Function3;)V public final fun addTool (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations;Lio/modelcontextprotocol/kotlin/sdk/types/ToolExecution;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/functions/Function3;)V @@ -113,6 +116,7 @@ public class io/modelcontextprotocol/kotlin/sdk/server/Server { protected final fun getInstructionsProvider ()Lkotlin/jvm/functions/Function0; protected final fun getOptions ()Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions; public final fun getPrompts ()Ljava/util/Map; + public final fun getResourceTemplates ()Ljava/util/List; public final fun getResources ()Ljava/util/Map; protected final fun getServerInfo ()Lio/modelcontextprotocol/kotlin/sdk/types/Implementation; public final fun getSessions ()Ljava/util/Map; @@ -128,6 +132,7 @@ public class io/modelcontextprotocol/kotlin/sdk/server/Server { public final fun removePrompt (Ljava/lang/String;)Z public final fun removePrompts (Ljava/util/List;)I public final fun removeResource (Ljava/lang/String;)Z + public final fun removeResourceTemplate (Ljava/lang/String;)Z public final fun removeResources (Ljava/util/List;)I public final fun removeTool (Ljava/lang/String;)Z public final fun removeTools (Ljava/util/List;)I @@ -141,9 +146,13 @@ public class io/modelcontextprotocol/kotlin/sdk/server/Server { } public final class io/modelcontextprotocol/kotlin/sdk/server/ServerOptions : io/modelcontextprotocol/kotlin/sdk/shared/ProtocolOptions { + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;)V public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;Z)V public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;ZLio/modelcontextprotocol/kotlin/sdk/utils/ResourceTemplateMatcherFactory;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;ZLio/modelcontextprotocol/kotlin/sdk/utils/ResourceTemplateMatcherFactory;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getCapabilities ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities; + public final fun getResourceTemplateMatcherFactory ()Lio/modelcontextprotocol/kotlin/sdk/utils/ResourceTemplateMatcherFactory; } public class io/modelcontextprotocol/kotlin/sdk/server/ServerSession : io/modelcontextprotocol/kotlin/sdk/shared/Protocol { diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Feature.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Feature.kt index 609aaa2..ce55f3e 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Feature.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Feature.kt @@ -8,7 +8,9 @@ import io.modelcontextprotocol.kotlin.sdk.types.Prompt import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult import io.modelcontextprotocol.kotlin.sdk.types.Resource +import io.modelcontextprotocol.kotlin.sdk.types.ResourceTemplate import io.modelcontextprotocol.kotlin.sdk.types.Tool +import io.modelcontextprotocol.kotlin.sdk.utils.ResourceTemplateMatcher internal typealias FeatureKey = String @@ -57,3 +59,18 @@ public data class RegisteredResource( ) : Feature { override val key: String = resource.uri } + +/** + * A registered resource template with its associated read handler. + * + * @property resourceTemplate The [ResourceTemplate] definition (RFC 6570 URI template). + * @property readHandler A suspend function invoked when a client reads a URI that matches + * this template. The second parameter contains the URI variables extracted from the match. + */ +internal class RegisteredResourceTemplate( + val resourceTemplate: ResourceTemplate, + val matcher: ResourceTemplateMatcher, + val readHandler: suspend ClientConnection.(ReadResourceRequest, Map) -> ReadResourceResult, +) : Feature { + override val key: String = resourceTemplate.uriTemplate +} diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt index 7842c2b..e221482 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt @@ -25,13 +25,16 @@ import io.modelcontextprotocol.kotlin.sdk.types.ListRootsResult import io.modelcontextprotocol.kotlin.sdk.types.ListToolsRequest import io.modelcontextprotocol.kotlin.sdk.types.ListToolsResult import io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotification +import io.modelcontextprotocol.kotlin.sdk.types.McpException import io.modelcontextprotocol.kotlin.sdk.types.Method import io.modelcontextprotocol.kotlin.sdk.types.Notification import io.modelcontextprotocol.kotlin.sdk.types.Prompt import io.modelcontextprotocol.kotlin.sdk.types.PromptArgument +import io.modelcontextprotocol.kotlin.sdk.types.RPCError import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult import io.modelcontextprotocol.kotlin.sdk.types.Resource +import io.modelcontextprotocol.kotlin.sdk.types.ResourceTemplate import io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotification import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.types.SubscribeRequest @@ -41,9 +44,16 @@ import io.modelcontextprotocol.kotlin.sdk.types.ToolAnnotations import io.modelcontextprotocol.kotlin.sdk.types.ToolExecution import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema import io.modelcontextprotocol.kotlin.sdk.types.UnsubscribeRequest +import io.modelcontextprotocol.kotlin.sdk.utils.MatchResult +import io.modelcontextprotocol.kotlin.sdk.utils.PathSegmentTemplateMatcher +import io.modelcontextprotocol.kotlin.sdk.utils.ResourceTemplateMatcher +import io.modelcontextprotocol.kotlin.sdk.utils.ResourceTemplateMatcherFactory import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Deferred import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import kotlin.jvm.JvmOverloads import kotlin.time.ExperimentalTime private val logger = KotlinLogging.logger {} @@ -53,9 +63,20 @@ private val logger = KotlinLogging.logger {} * * @property capabilities The capabilities this server supports. * @property enforceStrictCapabilities Whether to strictly enforce capabilities when interacting with clients. + * @property resourceTemplateMatcherFactory The factory used to create [ResourceTemplateMatcher] instances + * for matching resource URIs against registered templates. Defaults to [PathSegmentTemplateMatcher.factory]. */ -public class ServerOptions(public val capabilities: ServerCapabilities, enforceStrictCapabilities: Boolean = true) : - ProtocolOptions(enforceStrictCapabilities = enforceStrictCapabilities) +public class ServerOptions( + public val capabilities: ServerCapabilities, + enforceStrictCapabilities: Boolean = true, + public val resourceTemplateMatcherFactory: ResourceTemplateMatcherFactory = PathSegmentTemplateMatcher.factory, +) : ProtocolOptions(enforceStrictCapabilities = enforceStrictCapabilities) { + @JvmOverloads + public constructor( + capabilities: ServerCapabilities, + enforceStrictCapabilities: Boolean = true, + ) : this(capabilities, enforceStrictCapabilities, PathSegmentTemplateMatcher.factory) +} /** * An MCP server is responsible for storing features and handling new connections. @@ -126,6 +147,11 @@ public open class Server( addListener(notificationService.resourceUpdatedListener) } } + private val resourceTemplateRegistry = FeatureRegistry("ResourceTemplate").apply { + if (options.capabilities.resources?.listChanged == true) { + addListener(notificationService.resourceListChangedListener) + } + } /** * Provides a snapshot of all sessions currently registered in the server @@ -151,6 +177,12 @@ public open class Server( public val resources: Map get() = resourceRegistry.values + /** + * Provides a snapshot of all resource templates currently registered in the server. + */ + public val resourceTemplates: List + get() = resourceTemplateRegistry.values.values.map { it.resourceTemplate } + init { block(this) } @@ -297,7 +329,7 @@ public open class Server( * @throws IllegalStateException If the server does not support tools. */ public fun addTool(tool: Tool, handler: suspend ClientConnection.(CallToolRequest) -> CallToolResult) { - check(options.capabilities.tools != null) { + checkNotNull(options.capabilities.tools) { logger.error { "Failed to add tool '${tool.name}': Server does not support tools capability" } "Server does not support tools capability. Enable it in ServerOptions." } @@ -351,7 +383,7 @@ public open class Server( * @throws IllegalStateException If the server does not support tools. */ public fun addTools(toolsToAdd: List) { - check(options.capabilities.tools != null) { + checkNotNull(options.capabilities.tools) { logger.error { "Failed to add tools: Server does not support tools capability" } "Server does not support tools capability." } @@ -366,7 +398,7 @@ public open class Server( * @throws IllegalStateException If the server does not support tools. */ public fun removeTool(name: String): Boolean { - check(options.capabilities.tools != null) { + checkNotNull(options.capabilities.tools) { logger.error { "Failed to remove tool '$name': Server does not support tools capability" } "Server does not support tools capability." } @@ -381,7 +413,7 @@ public open class Server( * @throws IllegalStateException If the server does not support tools. */ public fun removeTools(toolNames: List): Int { - check(options.capabilities.tools != null) { + checkNotNull(options.capabilities.tools) { logger.error { "Failed to remove tools: Server does not support tools capability" } "Server does not support tools capability." } @@ -401,7 +433,7 @@ public open class Server( prompt: Prompt, promptProvider: suspend ClientConnection.(GetPromptRequest) -> GetPromptResult, ) { - check(options.capabilities.prompts != null) { + checkNotNull(options.capabilities.prompts) { logger.error { "Failed to add prompt '${prompt.name}': Server does not support prompts capability" } "Server does not support prompts capability." } @@ -434,7 +466,7 @@ public open class Server( * @throws IllegalStateException If the server does not support prompts. */ public fun addPrompts(promptsToAdd: List) { - check(options.capabilities.prompts != null) { + checkNotNull(options.capabilities.prompts) { logger.error { "Failed to add prompts: Server does not support prompts capability" } "Server does not support prompts capability." } @@ -449,7 +481,7 @@ public open class Server( * @throws IllegalStateException If the server does not support prompts. */ public fun removePrompt(name: String): Boolean { - check(options.capabilities.prompts != null) { + checkNotNull(options.capabilities.prompts) { logger.error { "Failed to remove prompt '$name': Server does not support prompts capability" } "Server does not support prompts capability." } @@ -465,7 +497,7 @@ public open class Server( * @throws IllegalStateException If the server does not support prompts. */ public fun removePrompts(promptNames: List): Int { - check(options.capabilities.prompts != null) { + checkNotNull(options.capabilities.prompts) { logger.error { "Failed to remove prompts: Server does not support prompts capability" } "Server does not support prompts capability." } @@ -490,7 +522,7 @@ public open class Server( mimeType: String = "text/html", readHandler: suspend ClientConnection.(ReadResourceRequest) -> ReadResourceResult, ) { - check(options.capabilities.resources != null) { + checkNotNull(options.capabilities.resources) { logger.error { "Failed to add resource '$name': Server does not support resources capability" } "Server does not support resources capability." } @@ -505,7 +537,7 @@ public open class Server( * @throws IllegalStateException If the server does not support resources. */ public fun addResources(resourcesToAdd: List) { - check(options.capabilities.resources != null) { + checkNotNull(options.capabilities.resources) { logger.error { "Failed to add resources: Server does not support resources capability" } "Server does not support resources capability." } @@ -520,7 +552,7 @@ public open class Server( * @throws IllegalStateException If the server does not support resources. */ public fun removeResource(uri: String): Boolean { - check(options.capabilities.resources != null) { + checkNotNull(options.capabilities.resources) { logger.error { "Failed to remove resource '$uri': Server does not support resources capability" } "Server does not support resources capability." } @@ -535,13 +567,74 @@ public open class Server( * @throws IllegalStateException If the server does not support resources. */ public fun removeResources(uris: List): Int { - check(options.capabilities.resources != null) { + checkNotNull(options.capabilities.resources) { logger.error { "Failed to remove resources: Server does not support resources capability" } "Server does not support resources capability." } return resourceRegistry.removeAll(uris) } + /** + * Registers a resource template. Clients can discover it via `resources/templates/list` + * and read matching URIs via `resources/read`. + * + * @param template The [ResourceTemplate] describing the URI template pattern. + * @param readHandler A suspend function invoked when a client reads a URI that matches + * the template. The second parameter contains the URI variables extracted from the match. + * @throws IllegalStateException If the server does not support resources. + */ + public fun addResourceTemplate( + template: ResourceTemplate, + readHandler: suspend ClientConnection.(ReadResourceRequest, Map) -> ReadResourceResult, + ) { + checkNotNull(options.capabilities.resources) { + "Server does not support resources capability." + } + val matcher = options.resourceTemplateMatcherFactory.create(template) + resourceTemplateRegistry.add( + RegisteredResourceTemplate( + template, + matcher, + readHandler = readHandler, + ), + ) + } + + /** + * Registers a resource template by constructing a [ResourceTemplate] from given parameters. + * + * @param uriTemplate The RFC 6570 URI template string (e.g. `"file:///{path}"`). + * @param name A human-readable name for the template. + * @param description A human-readable description of the resource template. + * @param mimeType The MIME type of resource content served by this template. + * @param readHandler A suspend function invoked when a client reads a URI that matches + * the template. The second parameter contains the URI variables extracted from the match. + * @throws IllegalStateException If the server does not support resources. + */ + public fun addResourceTemplate( + uriTemplate: String, + name: String, + description: String? = null, + mimeType: String? = null, + readHandler: suspend ClientConnection.(ReadResourceRequest, Map) -> ReadResourceResult, + ) { + addResourceTemplate(ResourceTemplate(uriTemplate, name, description, mimeType), readHandler) + } + + /** + * Removes a resource template by its URI template string. + * + * @param uriTemplate The URI template string identifying the template to remove. + * @return True if the template was removed, false if it was not found. + * @throws IllegalStateException If the server does not support resources. + */ + public fun removeResourceTemplate(uriTemplate: String): Boolean { + checkNotNull(options.capabilities.resources) { + "Server does not support resources capability." + } + return resourceTemplateRegistry.remove(uriTemplate) + } + // --- Internal Handlers --- private fun handleSubscribeResources(session: ServerSession, request: SubscribeRequest) { if (options.capabilities.resources?.subscribe ?: false) { @@ -621,21 +714,38 @@ public open class Server( } private suspend fun handleReadResource(session: ServerSession, request: ReadResourceRequest): ReadResourceResult { - val requestParams = request.params - logger.debug { "Handling read resource request for: ${requestParams.uri}" } - val resource = resourceRegistry.get(requestParams.uri) - ?: run { - logger.error { "Resource not found: ${requestParams.uri}" } - throw IllegalArgumentException("Resource not found: ${requestParams.uri}") - } - return resource.run { - session.clientConnection.readHandler(request) + val uri = request.params.uri + logger.debug { "Handling read resource request for: $uri" } + + // Priority 1: exact URI match + resourceRegistry.get(uri)?.let { resource -> + return resource.run { session.clientConnection.readHandler(request) } } + + // Priority 2: most-specific matching template. + // Selection: highest score wins; on tie, fewest variables wins; on tie, registration order wins. + val (template, matchResult) = resourceTemplateRegistry.values.values + .mapNotNull { tmpl -> tmpl.matcher.match(uri)?.let { tmpl to it } } + .maxWithOrNull( + compareBy> { (_, result) -> result.score } + .thenByDescending { (_, result) -> result.variables.size }, + ) + ?: run { + logger.error { "Resource not found: $uri" } + throw McpException( + code = RPCError.ErrorCode.RESOURCE_NOT_FOUND, + message = "Resource not found", + data = buildJsonObject { put("uri", uri) }, + ) + } + + logger.debug { "Matched resource template '${template.key}' for URI: $uri" } + return template.run { session.clientConnection.readHandler(request, matchResult.variables) } } private fun handleListResourceTemplates(): ListResourceTemplatesResult { - // If you have resource templates, return them here. For now, return empty. - return ListResourceTemplatesResult(listOf()) + logger.debug { "Handling list resource templates request" } + return ListResourceTemplatesResult(resourceTemplateRegistry.values.values.map { it.resourceTemplate }) } // Start the ServerSession / ClientConnection redirection section From 62e4e50c676405214bb238f10250f2d20b812444 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Fri, 20 Mar 2026 15:01:58 +0100 Subject: [PATCH 184/449] docs: update AGENTS.md (#612) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Improve AGENTS.md ## Motivation and Context The previous AGENTS.md contained many elements contradicting [best practices](https://code.claude.com/docs/en/best-practices). AGENTS.md/CLAUDE.md is loaded into the context on every request, so a ~300+ line file is too large and fills the context window. > Keep it concise. For each line, ask: “Would removing this cause Claude to make mistakes?” If not, cut it. Bloated CLAUDE.md files cause Claude to ignore your actual instructions! > Most best practices are based on one constraint: Claude’s context window fills up fast, and performance degrades as it fills. > The over-specified CLAUDE.md. If your CLAUDE.md is too long, Claude ignores half of it because important rules get lost in the noise. Since the file is loaded on every request, it should be general-purpose rather than targeting specific use cases. > CLAUDE.md is loaded every session, so only include things that apply broadly. AI coding agents are used not only for writing tests and implementations. - AGENTS.md should not contain behavioral instructions, especially like `Tests first, always.`. This causes the agent to trigger test runs even when not required, increasing request time and token consumption significantly due to large test outputs. For example, on a simple codebase question, the agent ran the full test suite and I waited over 10 minutes for a response. - `Uphold SOLID principles` — self-evident rules, explaining what SOLID stands for is redundant since AI coding agents are already trained on this knowledge. > Exclude: "Standard language conventions Claude already knows" > Exclude: "Self-evident practices like 'write clean code'" - Mentioning specific MCP servers (jetbrains, intellij-index) can mislead the agent if they are not installed in the environment. Additionally, [Anthropic recommends](https://resources.anthropic.com/hubfs/The-Complete-Guide-to-Building-Skill-for-Claude.pdf) mentioning the use of MCP in skills. - The large Kotest patterns block targets a specific use case and is unnecessary for other agent interactions. Moreover, the agent already looks at neighboring test classes and can learn patterns from existing tests - The large Module documentation block covers a very rare use case. - The KDoc block — the agent can derive this information from the codebase, this should be a skill instead These and other changes follow the best practices. ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed ## Additional context https://code.claude.com/docs/en/best-practices https://resources.anthropic.com/hubfs/The-Complete-Guide-to-Building-Skill-for-Claude.pdf https://agents.md/#examples --- AGENTS.md | 372 +++++++++--------------------------------------------- CLAUDE.md | 2 +- 2 files changed, 61 insertions(+), 313 deletions(-) mode change 100644 => 120000 CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md index 1646c55..6ef38be 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,334 +1,82 @@ # AGENTS: Development Guidelines for AI Contributors -This document is for autonomous agents and AI copilots contributing code to this repository. -Follow these rules to keep changes safe, comprehensible, and easy to maintain. +This file provides guidance to AI coding agents working with code in this repository. -## Repository Layout +## Project -- `kotlin-sdk-core`: Core protocol types, transport abstractions, serialization utilities -- `kotlin-sdk-client`: Client transports (STDIO, SSE, StreamableHttp, WebSocket) -- `kotlin-sdk-server`: Server transports + Ktor integration (STDIO, SSE, WebSocket) -- `kotlin-sdk`: Umbrella module that aggregates all three above -- `test-utils`: Shared test utilities -- `integration-test`: Integration tests with TypeScript/other implementations -- `conformance-test`: Protocol conformance tests -- `samples/`: Sample projects (composite builds) -- Package structure: `io.modelcontextprotocol.kotlin.sdk.*` -- JVM 11+, Kotlin 2.2+ +Kotlin Multiplatform SDK for the [Model Context Protocol (MCP)](https://modelcontextprotocol.io). Targets JVM, Native (macOS/Linux/Windows), JS, and Wasm. Implements both MCP clients and servers with pluggable transports. -## Prime Directives - -1. **Tests first, always.** - - Before changing code, identify or add tests that express the desired behavior. - - Prefer readable, minimal tests over clever ones. Tests are documentation. -2. **Keep tests simple and explicit.** - - Arrange/Act/Assert structure; avoid hidden magic and overuse of helpers. - - Prefer concrete inputs/outputs; avoid randomness and time dependence. -3. **Uphold SOLID principles** in production code: - - Single Responsibility: each class/function should do one thing well. - - Open/Closed: extend via new code, avoid risky edits to stable code paths. - - Liskov Substitution: honor contracts; keep types substitutable. - - Interface Segregation: keep abstractions small and focused. - - Dependency Inversion: depend on abstractions, not concretions. -4. **Make the minimal change** that satisfies the tests and the issue. - Stay focused on a single concern and avoid "drive-by" changes. -5. **Never commit changes!** It's human who should verify and commit to git. -6. **Keep the build green.** Do not merge changes that break existing tests. -7. **Prefer clarity** over micro-optimizations and cleverness. -8. **Ask when uncertain.** If requirements are ambiguous, request clarification with a concise question. -9. **Write code with the quality of a Kotlin Champion.** -10. **Use MCP servers** like `jetbrains` and `intellij-index` to explore the codebase, for code navigation, analysis, - refactoring, and running tests. -11. **Use the Bash tool for terminal commands** instead of MCP terminal execution features. -12. **Suggest updates** to AGENTS.md/CLAUDE.md with best practices and guidelines. - -## Code Style - -### Kotlin - -- Avoid changing public API signatures. Run `./gradlew apiCheck` before every commit. -- **Explicit API mode** is strict: all public APIs must have explicit visibility modifiers and return types. -- Anything under an `internal` directory is not part of the public API and may change freely. -- Package structure follows: `io.modelcontextprotocol.kotlin.sdk.*` -- Follow Kotlin coding conventions -- Use the provided `.editorconfig` for consistent formatting -- Use Kotlin typesafe DSL builders where possible; prioritize fluent builder style over standard builder methods -- Prefer DSL builder style (method with lambda blocks) over constructors, if possible -- Use `val` for immutable properties and `var` for mutable; consider `lateinit var` instead of nullable types when - appropriate -- Use multi-dollar interpolation prefix for strings where applicable -- Use fully qualified imports instead of star imports -- Ensure backward compatibility when making changes - -## Testing Guidance - -### Test Organization - -- **Location**: `src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/` -- **Platform-specific**: `src/jvmTest/`, `src/jsTest/`, etc. -- **Naming**: Use function `Names with backticks`, e.g., `fun \`should return 200 OK\`()` -- **Structure**: Arrange/Act/Assert pattern -- **Documentation**: Keep tests self-documenting; don't add KDocs to tests. - -### Test Principles - -- Write comprehensive tests for new features. Always add tests for new features or bug fixes, even if not explicitly - requested. -- **Prioritize test readability** -- Avoid creating too many test methods; use parametrized tests when testing multiple similar scenarios -- When running tests on Kotlin Multiplatform projects, run JVM tests only unless asked for other platforms - -### Test Framework Stack - -- **kotlin-test**: Core test framework -- **Kotest assertions**: Use infix style (`shouldBe`, `shouldContain`) instead of `assertEquals`. - - Use `shouldMatchJson` from kotest-assertions-json for JSON validation. - - For nullable objects with nested properties, prefer `shouldNotBeNull { ... }` blocks: - ```kotlin - content.annotations shouldNotBeNull { - priority shouldBe 1.0 - } - ``` -- **mockk**: Mocking library -- **Ktor MockEngine**: For HTTP client mocking (`io.ktor:ktor-client-mock`) -- **Java tests**: Use JUnit5, Mockito, AssertJ core -- **Serialization test utilities** (`io.modelcontextprotocol.kotlin.test.utils`): - - `verifySerialization(value, json, expectedJson)` — serializes, asserts match, round-trips back; use for most serialization tests - - `verifyDeserialization(json, payload)` — deserializes from JSON, re-serializes, asserts match; returns the object for further assertions - - Always test both empty/null/omitted and non-null cases for nullable fields; `McpJson` has `explicitNulls = false` so null properties must be absent from JSON, not `null` - -### Kotest Patterns - -- Basic assertions: Use infix style (`shouldBe`, `shouldContain`) instead of `assertEquals`. - ```kotlin - result shouldBe expected - list shouldContain item - ``` -- Nullable Assertions - ```kotlin - // Preferred: lambda-style for multiple assertions - content.annotations shouldNotBeNull { - priority shouldBe 1.0 - } - ``` - -- JSON Assertions (kotest-assertions-json): - ```kotlin - // language=json - val expected = """ - { - "id": 123 - } - """.trimIndent() - actual shouldEqualJson expected - ``` - -- Soft Assertions - ```kotlin - // Use for multiple assertions on SAME subject - assertSoftly(subject) { - name shouldBe "test" - value shouldBe 42 - } - // DON'T use for different subjects or single assertions - ``` -- Clues (Use Sparingly) - ```kotlin - // Only when assertion is NOT obvious - withClue("Failed to parse response") { - result shouldNotBe null - } - ``` - -#### Avoiding `this.` in Lambda Blocks - -```kotlin -// Avoid explicit `this.` unless necessary for disambiguation -prop.shouldNotBeNull { - enum shouldHaveSize 2 // Preferred - this.enum shouldHaveSize 2 // Avoid -} -``` - -### Multiplatform Patterns - -- Use `expect`/`actual` pattern for platform-specific implementations in `utils.*` files. -- Test changes on JVM first, then verify platform-specific behavior if needed. -- Supported targets: JVM (11+), JS/Wasm, iOS, watchOS, tvOS. - -## Coding Guidance - -### Module Boundaries - -- Respect separation of concerns between modules -- Keep abstractions framework-agnostic -- Avoid leaking implementation details across module boundaries - -### Serialization - -- Use Kotlinx Serialization with explicit `@Serializable` annotations -- JSON config is defined in `jsonUtils.kt` as `McpJson` — use it consistently -- Register custom serializers in companion objects - -### Error Handling - -- Use sealed classes for result states -- Map errors to JSON-RPC error codes in protocol handlers -- Log errors using `io.github.oshai.kotlinlogging.KotlinLogging` -- Never log sensitive data (credentials, tokens) - -### Code Quality - -- Handle nullability and references as established patterns -- Keep changes small and reversible; prefer adding functions over editing many call sites -- Add focused comments **only** where intent is non-obvious -- Use `git mv` command when moving version-controlled files -- Never commit or push changes. This is a Human responsibility. - -## Workflow for AI Agents - -1. **Understand the issue** - - Summarize requirement in 1-2 sentences - - Identify affected files and tests; ask if unclear - -2. **Plan minimal changes** - - Use TaskCreate/TaskUpdate to track work, or EnterPlanMode for complex changes - - Document affected files and approach - -3. **Work with code** - - View, edit, and run tests through MCP tools - -4. **Start with tests** - - Update existing or add new tests in relevant modules - -5. **Implement the change** - - Prefer composition and small functions - - Avoid breaking public APIs without tests and discussion - -6. **Run tests locally** - ```bash - ./gradlew test - ./gradlew :kotlin-sdk-core:test # Specific module - ./gradlew :integration-test:test # Integration tests - ``` - -7. **Verify results** - - Use Kotest JSON matchers for schema validation - - Double-check the readability of code and tests - -8. **Document key decisions** - - Brief notes in markdown document, if requested - -## Build and Run Commands +## Build Commands ```bash -# Build all modules -./gradlew build - -# Run all tests -./gradlew test - -# Run specific module tests -./gradlew :kotlin-sdk-core:test -./gradlew :kotlin-sdk-client:test -./gradlew :kotlin-sdk-server:test - -# Check API compatibility (required before commit) -./gradlew apiCheck - -# Update API dump (after intentional API changes) -./gradlew apiDump - -# Platform-specific tests -./gradlew jsTest -./gradlew wasmJsTest +./gradlew ktlintCheck # Lint check +./gradlew detekt # Static analysis +./gradlew ktlintFormat # Auto-fix lint issues +./gradlew apiCheck # Check public API compatibility (run before committing) +./gradlew apiDump # Update API dump after intentional API changes +./gradlew koverLog # Print coverage summary +./gradlew koverXmlReport # Generate XML coverage report ``` -## Definition of done +Run a single test class: +```bash +./gradlew :kotlin-sdk-server:jvmTest --tests "io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransportTest" +``` -- Tests for affected functionality exist -- All tests pass across all targets -- Changes are focused on a single concern. -- Code respects module boundaries -- `./gradlew apiCheck` passes -- Documentation updated for user-facing changes +Run a single test method: +```bash +./gradlew :kotlin-sdk-core:jvmTest --tests "*.ProtocolTest.should preserve existing meta when adding progress token" +``` -## Documentation +Conformance tests (requires Node.js + npx): +```bash +./conformance-test/run-conformance.sh all # All suites sequentially +./conformance-test/run-conformance.sh server # Server suite only +./conformance-test/run-conformance.sh client # Client core suite (initialize, tools_call, elicitation, sse-retry) +./conformance-test/run-conformance.sh client-auth # Client auth suite (20 OAuth scenarios) +``` -### General Guidelines +## Module Structure -- **Never make up documentation/facts** -- Update README files when adding new features -- Document API changes in the appropriate module -- **Keep documentation concise and straight to the point** -- Always verify documentation matches code; fix discrepancies -- Write tutorials in Markdown format in README.md +``` +kotlin-sdk-core Core: protocol types, JSON-RPC, Transport abstraction, Protocol base class +kotlin-sdk-client Client: MCP client implementation (tools, prompts, resources, sampling) +kotlin-sdk-server Server: MCP server implementation (feature registries, sessions) +kotlin-sdk-testing ChannelTransport for in-memory client-server testing +kotlin-sdk Umbrella module aggregating client + server +test-utils Shared test helpers (mockk, kotest, awaitility) +conformance-test MCP specification compliance tests (external runner) +integration-test End-to-end tests +samples/ Runnable examples (servers and clients) +``` -### KDoc Guidelines +## Architecture -- Properly document interfaces and abstract classes in production code -- Avoid KDocs on override functions to reduce verbosity -- Update KDocs when API changes -- Use references: `[SendMessageRequest]` instead of `SendMessageRequest` -- Add brief code examples to KDocs -- Add links to specifications with verified URLs; never add broken links +Layered design: -### Module.md Files for Dokka +``` +Application (tools, prompts, resources) + → Client / Server (typed MCP operations, capability enforcement) + → Protocol (JSON-RPC framing, request correlation, timeouts) + → Transport (pluggable I/O: stdio, SSE, Streamable HTTP, WebSocket) +``` -Each module must have a `Module.md` file at the module root for Dokka-generated API documentation. +## Code Conventions -**Format Requirements** (per [Dokka specification](https://kotlinlang.org/docs/dokka-module-and-package-docs.html)): +- **Explicit API mode (Strict)**: all public declarations must have explicit visibility modifiers and return types +- **DSL builders**: prefer DSL builder style (lambda blocks) over direct constructors +- **Multi-dollar interpolation**: use `$$"""..."""` for strings containing literal `$` (e.g., JSON with `$ref`, `$defs`) +- **`McpJson` has `explicitNulls = false`**: null properties must be absent from JSON, not `"field": null` +- **Logging**: use `io.github.oshai.kotlinlogging.KotlinLogging` +- **Structured concurrency**: every coroutine must have a bounded scope and lifetime; always re-throw `CancellationException`; never hold `Mutex` across suspension points -- Must start with `# Module module-name` (level 1 header) -- Package documentation uses `# Package package.name` (level 1 header) -- All other content uses level 2+ headers (`##`, `###`, etc.) +## Testing -**Content Guidelines**: +- **JVM first**: run `jvmTest` instead of `test` — full multiplatform test runs are slow and rarely needed during development +- **Test naming**: use backtick syntax — `fun \`should return 200 OK\`()` +- **Frameworks**: JUnit 5 (runner) + Kotest (assertions) + MockK (mocking) + Awaitility (async waits) +- **In-memory testing**: `ChannelTransport.createLinkedPair()` connects client and server without network -- **Purpose**: Provide module-level overview in generated API docs -- **Detail level**: API documentation style (concise, focused on classes/interfaces) -- **Module section**: - - Brief module description (1-2 sentences) - - Platform support (format: "Multiplatform • Kotlin 2.2+" or "JVM only • Kotlin 2.2+") - - Key classes/components (use Dokka references like `[ClassName]`) - - Example (minimal, demonstrating primary usage) -- **Package sections**: Use `# Package package.name` headers with brief description -- **Optional sections**: Features, Limitations, Related Specifications -- **Style**: - - Use bullet points over prose - - Keep examples short (5-15 lines) - - Reference related modules using Dokka links - - Avoid duplicating README content +## Skills -**Example structure (markdown)**: - - # Module module-name - - Brief description. - - **Platform Support:** Multiplatform • Kotlin 2.2+ - - ## Key Classes - - - [MainClass] - what it does - - [HelperClass] - what it does - - ## Example - - ```kotlin - val example = MainClass() - ``` - - # Package package.name - - Package description. - - # Package another.package - - Another package description. - -## When to Ask for Help - -- Requirements conflict with existing tests or documentation -- The smallest change still requires altering core abstractions -- Unclear expected schema shape for a new feature—ask for a concrete, readable test case to anchor implementation +- `/mcp-docs` — fetches live MCP specification and docs; use when implementing or debugging MCP protocol features diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 43c994c..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1 +0,0 @@ -@AGENTS.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000..47dc3e3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file From fd3a8583b848c3ccadfd66c7b187e3aa86a5152d Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Fri, 20 Mar 2026 18:07:32 +0100 Subject: [PATCH 185/449] feat: add tasks models (#566) ## Motivation and Context closes #421 #406 ## How Has This Been Tested? Unit tests ## Breaking Changes NaN ## 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- kotlin-sdk-core/api/kotlin-sdk-core.api | 564 ++++++++++++ .../kotlin/sdk/types/methods.kt | 5 + .../kotlin/sdk/types/notification.kt | 45 + .../kotlin/sdk/types/request.kt | 12 + .../kotlin/sdk/types/serializers.kt | 17 + .../kotlin/sdk/types/tasks.kt | 347 ++++++++ .../kotlin/sdk/types/NotificationTest.kt | 112 +++ .../kotlin/sdk/types/TasksTest.kt | 802 ++++++++++++++++++ 8 files changed, 1904 insertions(+) create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tasks.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/TasksTest.kt diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index dc3da3e..7e1b98c 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -848,6 +848,67 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CallToolResult$Compa public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/CancelTaskRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest, io/modelcontextprotocol/kotlin/sdk/types/ServerRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CancelTaskRequest$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/CancelTaskRequestParams;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/CancelTaskRequestParams; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/CancelTaskRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/CancelTaskRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/CancelTaskRequest;Lio/modelcontextprotocol/kotlin/sdk/types/CancelTaskRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CancelTaskRequest; + public fun equals (Ljava/lang/Object;)Z + public final fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/CancelTaskRequestParams; + public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; + public final fun getTaskId ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/CancelTaskRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/CancelTaskRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/CancelTaskRequest; + 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/CancelTaskRequest;)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/CancelTaskRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/CancelTaskRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CancelTaskRequestParams$Companion; + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun copy-D-90kyg (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CancelTaskRequestParams; + public static synthetic fun copy-D-90kyg$default (Lio/modelcontextprotocol/kotlin/sdk/types/CancelTaskRequestParams;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CancelTaskRequestParams; + public fun equals (Ljava/lang/Object;)Z + public fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun getTaskId ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/CancelTaskRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/CancelTaskRequestParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/CancelTaskRequestParams; + 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/CancelTaskRequestParams;)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/CancelTaskRequestParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/CancelledNotification : io/modelcontextprotocol/kotlin/sdk/types/ClientNotification, io/modelcontextprotocol/kotlin/sdk/types/ServerNotification { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotification$Companion; public fun (Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotificationParams;)V @@ -1403,6 +1464,37 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageResult$ public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/CreateTaskResult : io/modelcontextprotocol/kotlin/sdk/types/ClientResult, io/modelcontextprotocol/kotlin/sdk/types/ServerResult { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CreateTaskResult$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/Task;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/Task;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/Task; + public final fun component2 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/Task;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateTaskResult; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/CreateTaskResult;Lio/modelcontextprotocol/kotlin/sdk/types/Task;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateTaskResult; + public fun equals (Ljava/lang/Object;)Z + public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public final fun getTask ()Lio/modelcontextprotocol/kotlin/sdk/types/Task; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/CreateTaskResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/CreateTaskResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateTaskResult; + 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/CreateTaskResult;)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/CreateTaskResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/CustomNotification : io/modelcontextprotocol/kotlin/sdk/types/ClientNotification, io/modelcontextprotocol/kotlin/sdk/types/ServerNotification { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CustomNotification$Companion; public fun (Lio/modelcontextprotocol/kotlin/sdk/types/Method;Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;)V @@ -1798,6 +1890,206 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/GetPromptResult$Comp public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/GetTaskPayloadRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest, io/modelcontextprotocol/kotlin/sdk/types/ServerRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskPayloadRequest$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskPayloadRequestParams;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskPayloadRequestParams; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskPayloadRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskPayloadRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskPayloadRequest;Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskPayloadRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskPayloadRequest; + public fun equals (Ljava/lang/Object;)Z + public final fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskPayloadRequestParams; + public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; + public final fun getTaskId ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/GetTaskPayloadRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskPayloadRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskPayloadRequest; + 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/GetTaskPayloadRequest;)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/GetTaskPayloadRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/GetTaskPayloadRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskPayloadRequestParams$Companion; + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun copy-D-90kyg (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskPayloadRequestParams; + public static synthetic fun copy-D-90kyg$default (Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskPayloadRequestParams;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskPayloadRequestParams; + public fun equals (Ljava/lang/Object;)Z + public fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun getTaskId ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/GetTaskPayloadRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskPayloadRequestParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskPayloadRequestParams; + 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/GetTaskPayloadRequestParams;)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/GetTaskPayloadRequestParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/GetTaskPayloadResult : io/modelcontextprotocol/kotlin/sdk/types/ClientResult, io/modelcontextprotocol/kotlin/sdk/types/ServerResult { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskPayloadResult$Companion; + public static final synthetic fun box-impl (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskPayloadResult; + public static fun constructor-impl (Lkotlinx/serialization/json/JsonObject;)Lkotlinx/serialization/json/JsonObject; + public fun equals (Ljava/lang/Object;)Z + public static fun equals-impl (Lkotlinx/serialization/json/JsonObject;Ljava/lang/Object;)Z + public static final fun equals-impl0 (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Z + public static final fun get-impl (Lkotlinx/serialization/json/JsonObject;Ljava/lang/String;)Lkotlinx/serialization/json/JsonElement; + public final fun getJson ()Lkotlinx/serialization/json/JsonObject; + public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public static fun getMeta-impl (Lkotlinx/serialization/json/JsonObject;)Lkotlinx/serialization/json/JsonObject; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; + public static fun get_meta-impl (Lkotlinx/serialization/json/JsonObject;)Lkotlinx/serialization/json/JsonObject; + public fun hashCode ()I + public static fun hashCode-impl (Lkotlinx/serialization/json/JsonObject;)I + public fun toString ()Ljava/lang/String; + public static fun toString-impl (Lkotlinx/serialization/json/JsonObject;)Ljava/lang/String; + public final synthetic fun unbox-impl ()Lkotlinx/serialization/json/JsonObject; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/GetTaskPayloadResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskPayloadResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun deserialize-iveUaak (Lkotlinx/serialization/encoding/Decoder;)Lkotlinx/serialization/json/JsonObject; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public final fun serialize-kradpfw (Lkotlinx/serialization/encoding/Encoder;Lkotlinx/serialization/json/JsonObject;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/GetTaskPayloadResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/GetTaskRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest, io/modelcontextprotocol/kotlin/sdk/types/ServerRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskRequest$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskRequestParams;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskRequestParams; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskRequest;Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskRequest; + public fun equals (Ljava/lang/Object;)Z + public final fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskRequestParams; + public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; + public final fun getTaskId ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/GetTaskRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskRequest; + 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/GetTaskRequest;)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/GetTaskRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/GetTaskRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskRequestParams$Companion; + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun copy-D-90kyg (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskRequestParams; + public static synthetic fun copy-D-90kyg$default (Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskRequestParams;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskRequestParams; + public fun equals (Ljava/lang/Object;)Z + public fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun getTaskId ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/GetTaskRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskRequestParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskRequestParams; + 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/GetTaskRequestParams;)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/GetTaskRequestParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/GetTaskResult : io/modelcontextprotocol/kotlin/sdk/types/ClientResult, io/modelcontextprotocol/kotlin/sdk/types/ServerResult, io/modelcontextprotocol/kotlin/sdk/types/TaskFields { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskResult$Companion; + public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatus;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatus;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatus; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/Long; + public final fun component7 ()Ljava/lang/Long; + public final fun component8 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatus;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskResult; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskResult;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatus;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskResult; + public fun equals (Ljava/lang/Object;)Z + public fun getCreatedAt ()Ljava/lang/String; + public fun getLastUpdatedAt ()Ljava/lang/String; + public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public fun getPollInterval ()Ljava/lang/Long; + public fun getStatus ()Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatus; + public fun getStatusMessage ()Ljava/lang/String; + public fun getTaskId ()Ljava/lang/String; + public fun getTtl ()Ljava/lang/Long; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/GetTaskResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/GetTaskResult; + 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/GetTaskResult;)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/GetTaskResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/Icon { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Icon$Companion; public fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/Icon$Theme;)V @@ -2548,6 +2840,72 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListRootsResult$Comp public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ListTasksRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest, io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequest, io/modelcontextprotocol/kotlin/sdk/types/ServerRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ListTasksRequest$Companion; + public fun ()V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ListTasksRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ListTasksRequest;Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ListTasksRequest; + public fun equals (Ljava/lang/Object;)Z + public fun getCursor ()Ljava/lang/String; + public fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams; + public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ListTasksRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ListTasksRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ListTasksRequest; + 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/ListTasksRequest;)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/ListTasksRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ListTasksResult : io/modelcontextprotocol/kotlin/sdk/types/ClientResult, io/modelcontextprotocol/kotlin/sdk/types/PaginatedResult, io/modelcontextprotocol/kotlin/sdk/types/ServerResult { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ListTasksResult$Companion; + public fun (Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ListTasksResult; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ListTasksResult;Ljava/util/List;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ListTasksResult; + public fun equals (Ljava/lang/Object;)Z + public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public fun getNextCursor ()Ljava/lang/String; + public final fun getTasks ()Ljava/util/List; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ListTasksResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ListTasksResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ListTasksResult; + 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/ListTasksResult;)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/ListTasksResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest, io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequest { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest$Companion; public fun ()V @@ -2797,6 +3155,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/Method$Defined : jav public static final field NotificationsResourcesListChanged Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; public static final field NotificationsResourcesUpdated Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; public static final field NotificationsRootsListChanged Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public static final field NotificationsTasksStatus Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; public static final field NotificationsToolsListChanged Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; public static final field Ping Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; public static final field PromptsGet Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; @@ -2808,6 +3167,10 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/Method$Defined : jav public static final field ResourcesUnsubscribe Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; public static final field RootsList Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; public static final field SamplingCreateMessage Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public static final field TasksCancel Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public static final field TasksGet Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public static final field TasksList Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public static final field TasksResult Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; public static final field ToolsCall Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; public static final field ToolsList Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; public static fun getEntries ()Lkotlin/enums/EnumEntries; @@ -3439,6 +3802,33 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ReferenceType$Compan public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/RelatedTaskMetadata { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/RelatedTaskMetadata$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/RelatedTaskMetadata; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/RelatedTaskMetadata;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/RelatedTaskMetadata; + public fun equals (Ljava/lang/Object;)Z + public final fun getTaskId ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/RelatedTaskMetadata$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/RelatedTaskMetadata$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/RelatedTaskMetadata; + 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/RelatedTaskMetadata;)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/RelatedTaskMetadata$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/Request { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Request$Companion; public abstract fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; @@ -3534,6 +3924,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/RequestMeta { public static final fun get-impl (Lkotlinx/serialization/json/JsonObject;Ljava/lang/String;)Lkotlinx/serialization/json/JsonElement; public final fun getJson ()Lkotlinx/serialization/json/JsonObject; public static final fun getProgressToken-impl (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/RequestId; + public static final fun getRelatedTask-impl (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/RelatedTaskMetadata; public fun hashCode ()I public static fun hashCode-impl (Lkotlinx/serialization/json/JsonObject;)I public fun toString ()Ljava/lang/String; @@ -4337,6 +4728,175 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/SubscribeRequestPara public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/Task : io/modelcontextprotocol/kotlin/sdk/types/TaskFields { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Task$Companion; + public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatus;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;)V + public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatus;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatus; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/Long; + public final fun component7 ()Ljava/lang/Long; + public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatus;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;)Lio/modelcontextprotocol/kotlin/sdk/types/Task; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/Task;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatus;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/Task; + public fun equals (Ljava/lang/Object;)Z + public fun getCreatedAt ()Ljava/lang/String; + public fun getLastUpdatedAt ()Ljava/lang/String; + public fun getPollInterval ()Ljava/lang/Long; + public fun getStatus ()Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatus; + public fun getStatusMessage ()Ljava/lang/String; + public fun getTaskId ()Ljava/lang/String; + public fun getTtl ()Ljava/lang/Long; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/Task$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/Task$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/Task; + 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/Task;)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/Task$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/TaskFields { + public abstract fun getCreatedAt ()Ljava/lang/String; + public abstract fun getLastUpdatedAt ()Ljava/lang/String; + public abstract fun getPollInterval ()Ljava/lang/Long; + public abstract fun getStatus ()Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatus; + public abstract fun getStatusMessage ()Ljava/lang/String; + public abstract fun getTaskId ()Ljava/lang/String; + public abstract fun getTtl ()Ljava/lang/Long; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/TaskMetadata { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata$Companion; + public fun ()V + public fun (Ljava/lang/Long;)V + public synthetic fun (Ljava/lang/Long;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Long; + public final fun copy (Ljava/lang/Long;)Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;Ljava/lang/Long;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata; + public fun equals (Ljava/lang/Object;)Z + public final fun getTtl ()Ljava/lang/Long; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/TaskMetadata$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata; + 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/TaskMetadata;)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/TaskMetadata$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/TaskStatus : java/lang/Enum { + public static final field Cancelled Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatus; + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatus$Companion; + public static final field Completed Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatus; + public static final field Failed Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatus; + public static final field InputRequired Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatus; + public static final field Working Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatus; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatus; + public static fun values ()[Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatus; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/TaskStatus$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/TaskStatusNotification : io/modelcontextprotocol/kotlin/sdk/types/ClientNotification, io/modelcontextprotocol/kotlin/sdk/types/ServerNotification { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatusNotification$Companion; + public fun ()V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatusNotificationParams;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatusNotificationParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatusNotificationParams; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatusNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatusNotification; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatusNotification;Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatusNotificationParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatusNotification; + public fun equals (Ljava/lang/Object;)Z + public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/NotificationParams; + public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatusNotificationParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/TaskStatusNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatusNotification$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatusNotification; + 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/TaskStatusNotification;)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/TaskStatusNotification$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/TaskStatusNotificationParams : io/modelcontextprotocol/kotlin/sdk/types/NotificationParams, io/modelcontextprotocol/kotlin/sdk/types/TaskFields { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatusNotificationParams$Companion; + public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatus;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatus;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatus; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/Long; + public final fun component7 ()Ljava/lang/Long; + public final fun component8 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatus;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatusNotificationParams; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatusNotificationParams;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatus;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatusNotificationParams; + public fun equals (Ljava/lang/Object;)Z + public fun getCreatedAt ()Ljava/lang/String; + public fun getLastUpdatedAt ()Ljava/lang/String; + public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public fun getPollInterval ()Ljava/lang/Long; + public fun getStatus ()Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatus; + public fun getStatusMessage ()Ljava/lang/String; + public fun getTaskId ()Ljava/lang/String; + public fun getTtl ()Ljava/lang/Long; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/TaskStatusNotificationParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatusNotificationParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/TaskStatusNotificationParams; + 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/TaskStatusNotificationParams;)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/TaskStatusNotificationParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/TaskSupport : java/lang/Enum { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/TaskSupport$Companion; public static final field Forbidden Lio/modelcontextprotocol/kotlin/sdk/types/TaskSupport; @@ -4351,6 +4911,10 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/TaskSupport$Companio public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/TasksKt { + public static final field RELATED_TASK_META_KEY Ljava/lang/String; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/TextContent : io/modelcontextprotocol/kotlin/sdk/types/MediaContent { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/TextContent$Companion; public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Lkotlinx/serialization/json/JsonObject;)V diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/methods.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/methods.kt index 9970015..1d96fea 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/methods.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/methods.kt @@ -32,6 +32,7 @@ public sealed interface Method { NotificationsToolsListChanged("notifications/tools/list_changed"), NotificationsRootsListChanged("notifications/roots/list_changed"), NotificationsPromptsListChanged("notifications/prompts/list_changed"), + NotificationsTasksStatus("notifications/tasks/status"), ToolsList("tools/list"), ToolsCall("tools/call"), LoggingSetLevel("logging/setLevel"), @@ -39,6 +40,10 @@ public sealed interface Method { CompletionComplete("completion/complete"), RootsList("roots/list"), ElicitationCreate("elicitation/create"), + TasksGet("tasks/get"), + TasksResult("tasks/result"), + TasksList("tasks/list"), + TasksCancel("tasks/cancel"), } /** diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/notification.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/notification.kt index 8eb4e4b..cb1aa99 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/notification.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/notification.kt @@ -348,3 +348,48 @@ public data class ToolListChangedNotification(override val params: BaseNotificat @EncodeDefault override val method: Method = Method.Defined.NotificationsToolsListChanged } + +// ============================================================================ +// Task Status Notification +// ============================================================================ + +/** + * An optional notification from the receiver to the requestor, informing them that a task’s status has changed. + * Receivers are not required to send these notifications. + * + * This notification can be sent by either side (both [ClientNotification] and [ServerNotification]). + * + * @property params The task status notification parameters containing the current task state. + */ +@Serializable +public data class TaskStatusNotification(override val params: TaskStatusNotificationParams? = null) : + ClientNotification, + ServerNotification { + @EncodeDefault + override val method: Method = Method.Defined.NotificationsTasksStatus +} + +/** + * Parameters for a notifications/tasks/status notification. + * + * @property taskId The task identifier. + * @property status Current task state. + * @property statusMessage Optional human-readable message describing the current task state. + * @property createdAt ISO 8601 timestamp when the task was created. + * @property lastUpdatedAt ISO 8601 timestamp when the task was last updated. + * @property ttl Actual retention duration from creation in milliseconds, null for unlimited. + * @property pollInterval Suggested polling interval in milliseconds. + * @property meta Optional metadata for this notification. + */ +@Serializable +public data class TaskStatusNotificationParams( + override val taskId: String, + override val status: TaskStatus, + override val statusMessage: String? = null, + override val createdAt: String, + override val lastUpdatedAt: String, + override val ttl: Long?, + override val pollInterval: Long? = null, + @SerialName("_meta") override val meta: JsonObject? = null, +) : NotificationParams, + TaskFields diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.kt index 699dfce..dc7bd3b 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.kt @@ -6,6 +6,7 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.decodeFromJsonElement import kotlinx.serialization.json.long import kotlinx.serialization.json.longOrNull import kotlin.jvm.JvmInline @@ -22,6 +23,17 @@ public value class RequestMeta(public val json: JsonObject) { } } + /** + * The related task metadata, if this request is associated with a task. + * + * @see RelatedTaskMetadata + * @see RELATED_TASK_META_KEY + */ + public val relatedTask: RelatedTaskMetadata? + get() = json[RELATED_TASK_META_KEY]?.let { element -> + McpJson.decodeFromJsonElement(element) + } + /** * Retrieves the value associated with the specified key from the JSON object. * diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt index 4d5974d..2d20d42 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt @@ -159,6 +159,10 @@ private val clientRequestDeserializers: Map CreateMessageResult.serializer() "roots" in jsonObject -> ListRootsResult.serializer() "action" in jsonObject -> ElicitResult.serializer() + "task" in jsonObject -> CreateTaskResult.serializer() + "tasks" in jsonObject -> ListTasksResult.serializer() + "taskId" in jsonObject -> GetTaskResult.serializer() else -> null } } @@ -309,6 +322,7 @@ private fun selectClientResultDeserializer(element: JsonElement): Deserializatio * Selects the appropriate deserializer for server results based on JSON content. * Returns null if the structure doesn't match any known server result type. */ +@Suppress("CyclomaticComplexMethod") private fun selectServerResultDeserializer(element: JsonElement): DeserializationStrategy? { val jsonObject = element.jsonObject return when { @@ -321,6 +335,9 @@ private fun selectServerResultDeserializer(element: JsonElement): Deserializatio "messages" in jsonObject -> GetPromptResult.serializer() "contents" in jsonObject -> ReadResourceResult.serializer() "content" in jsonObject -> CallToolResult.serializer() + "task" in jsonObject -> CreateTaskResult.serializer() + "tasks" in jsonObject -> ListTasksResult.serializer() + "taskId" in jsonObject -> GetTaskResult.serializer() else -> null } } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tasks.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tasks.kt new file mode 100644 index 0000000..42b5752 --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tasks.kt @@ -0,0 +1,347 @@ +@file:OptIn(ExperimentalSerializationApi::class) + +package io.modelcontextprotocol.kotlin.sdk.types + +import kotlinx.serialization.EncodeDefault +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlin.jvm.JvmInline + +/** + * The key used in `_meta` to associate a message with a task. + */ +public const val RELATED_TASK_META_KEY: String = "io.modelcontextprotocol/related-task" + +/** + * Common fields shared by all types representing the task state. + */ +public sealed interface TaskFields { + public val taskId: String + public val status: TaskStatus + public val statusMessage: String? + public val createdAt: String + public val lastUpdatedAt: String + public val ttl: Long? + public val pollInterval: Long? +} + +/** + * Data associated with a task. + * + * @property taskId The task identifier. + * @property status Current task state. + * @property statusMessage Optional human-readable message describing the current task state. + * This can provide context for any status, including reasons for "cancelled" status, + * summaries for "completed" status, or diagnostic information for "failed" status. + * @property createdAt ISO 8601 timestamp when the task was created. + * @property lastUpdatedAt ISO 8601 timestamp when the task was last updated. + * @property ttl Actual retention duration from creation in milliseconds, null for unlimited. + * @property pollInterval Suggested polling interval in milliseconds. + */ +@Serializable +public data class Task( + override val taskId: String, + override val status: TaskStatus, + override val statusMessage: String? = null, + override val createdAt: String, + override val lastUpdatedAt: String, + override val ttl: Long?, + override val pollInterval: Long? = null, +) : TaskFields + +/** + * Metadata for augmenting a request with task execution. + * Include this in the `task` field of the request parameters. + * + * @property ttl Requested duration in milliseconds to retain task from creation. + */ +@Serializable +public data class TaskMetadata(val ttl: Long? = null) + +/** + * The status of a task. + */ +@Serializable +public enum class TaskStatus { + @SerialName("working") + Working, + + @SerialName("input_required") + InputRequired, + + @SerialName("completed") + Completed, + + @SerialName("failed") + Failed, + + @SerialName("cancelled") + Cancelled, +} + +/** + * Metadata for associating messages with a task. + * Include this in the `_meta` field under the key `io.modelcontextprotocol/related-task`. + * + * @property taskId The task identifier this message is associated with. + */ +@Serializable +public data class RelatedTaskMetadata(val taskId: String) + +/** + * A response to a task-augmented request. + * + * @property task The task data associated with the response. + * @property meta Optional metadata for this response. + */ +@Serializable +public data class CreateTaskResult( + val task: Task, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : ClientResult, + ServerResult + +// ============================================================================ +// tasks/get +// ============================================================================ + +/** + * A request to retrieve the state of a task. + * + * This request can be sent by either side (both [ClientRequest] and [ServerRequest]). + * + * @property params The parameters specifying which task to retrieve. + */ +@Serializable +public data class GetTaskRequest(override val params: GetTaskRequestParams) : + ClientRequest, + ServerRequest { + @EncodeDefault + override val method: Method = Method.Defined.TasksGet + + /** + * The task identifier to query. + */ + public val taskId: String + get() = params.taskId + + /** + * Metadata for this request. May include a progressToken for out-of-band progress notifications. + */ + public val meta: RequestMeta? + get() = params.meta +} + +/** + * Parameters for a tasks/get request. + * + * @property taskId The task identifier to query. + * @property meta Optional metadata for this request. + */ +@Serializable +public data class GetTaskRequestParams( + val taskId: String, + @SerialName("_meta") + override val meta: RequestMeta? = null, +) : RequestParams + +/** + * The response to a tasks/get request. + * + * Contains all [Task] fields flattened into the result (intersection of Result and Task). + * + * @property taskId The task identifier. + * @property status Current task state. + * @property statusMessage Optional human-readable message describing the current task state. + * @property createdAt ISO 8601 timestamp when the task was created. + * @property lastUpdatedAt ISO 8601 timestamp when the task was last updated. + * @property ttl Actual retention duration from creation in milliseconds, null for unlimited. + * @property pollInterval Suggested polling interval in milliseconds. + * @property meta Optional metadata for this response. + */ +@Serializable +public data class GetTaskResult( + override val taskId: String, + override val status: TaskStatus, + override val statusMessage: String? = null, + override val createdAt: String, + override val lastUpdatedAt: String, + override val ttl: Long?, + override val pollInterval: Long? = null, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : ClientResult, + ServerResult, + TaskFields + +// ============================================================================ +// tasks/result +// ============================================================================ + +/** + * A request to retrieve the result of a completed task. + * + * This request can be sent by either side (both [ClientRequest] and [ServerRequest]). + * + * @property params The parameters specifying which task to retrieve results for. + */ +@Serializable +public data class GetTaskPayloadRequest(override val params: GetTaskPayloadRequestParams) : + ClientRequest, + ServerRequest { + @EncodeDefault + override val method: Method = Method.Defined.TasksResult + + /** + * The task identifier to retrieve results for. + */ + public val taskId: String + get() = params.taskId + + /** + * Metadata for this request. May include a progressToken for out-of-band progress notifications. + */ + public val meta: RequestMeta? + get() = params.meta +} + +/** + * Parameters for a tasks/result request. + * + * @property taskId The task identifier to retrieve results for. + * @property meta Optional metadata for this request. + */ +@Serializable +public data class GetTaskPayloadRequestParams( + val taskId: String, + @SerialName("_meta") + override val meta: RequestMeta? = null, +) : RequestParams + +/** + * The response to a tasks/result request. + * + * The structure matches the result type of the original request. + * For example, a tools/call task would return the [CallToolResult] structure. + * + * @property meta Optional metadata for this response. + */ +@JvmInline +@Serializable +public value class GetTaskPayloadResult(public val json: JsonObject) : + ClientResult, + ServerResult { + override val meta: JsonObject? + get() = json["_meta"]?.takeIf { it is JsonObject } as? JsonObject + + /** + * Retrieves the value associated with the specified key from the result payload. + * + * @param key the key whose corresponding value is to be returned + * @return the [JsonElement] associated with the specified key, or null if the key does not exist + */ + public operator fun get(key: String): JsonElement? = json[key] +} + +// ============================================================================ +// tasks/list +// ============================================================================ + +/** + * A request to retrieve a list of tasks. + * + * This request can be sent by either side (both [ClientRequest] and [ServerRequest]). + * Supports pagination through the cursor parameter. + * + * @property params Optional pagination parameters to control which page of results to return. + */ +@Serializable +public data class ListTasksRequest(override val params: PaginatedRequestParams? = null) : + ClientRequest, + ServerRequest, + PaginatedRequest { + @EncodeDefault + override val method: Method = Method.Defined.TasksList +} + +/** + * The response to a tasks/list request. + * + * @property tasks The list of tasks. + * @property nextCursor An opaque token representing the pagination position after the last returned result. + * If present, there may be more results available. + * @property meta Optional metadata for this response. + */ +@Serializable +public data class ListTasksResult( + val tasks: List, + override val nextCursor: String? = null, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : ClientResult, + ServerResult, + PaginatedResult + +// ============================================================================ +// tasks/cancel +// ============================================================================ + +/** + * A request to cancel a task. + * + * This request can be sent by either side (both [ClientRequest] and [ServerRequest]). + * + * @property params The parameters specifying which task to cancel. + */ +@Serializable +public data class CancelTaskRequest(override val params: CancelTaskRequestParams) : + ClientRequest, + ServerRequest { + @EncodeDefault + override val method: Method = Method.Defined.TasksCancel + + /** + * The task identifier to cancel. + */ + public val taskId: String + get() = params.taskId + + /** + * Metadata for this request. May include a progressToken for out-of-band progress notifications. + */ + public val meta: RequestMeta? + get() = params.meta +} + +/** + * Parameters for a tasks/cancel request. + * + * @property taskId The task identifier to cancel. + * @property meta Optional metadata for this request. + */ +@Serializable +public data class CancelTaskRequestParams( + val taskId: String, + @SerialName("_meta") + override val meta: RequestMeta? = null, +) : RequestParams + +/** + * The response to a tasks/cancel request. + * + * Contains all [Task] fields flattened into the result (intersection of Result and Task). + * + * @property taskId The task identifier. + * @property status Current task state. + * @property statusMessage Optional human-readable message describing the current task state. + * @property createdAt ISO 8601 timestamp when the task was created. + * @property lastUpdatedAt ISO 8601 timestamp when the task was last updated. + * @property ttl Actual retention duration from creation in milliseconds, null for unlimited. + * @property pollInterval Suggested polling interval in milliseconds. + * @property meta Optional metadata for this response. + */ +public typealias CancelTaskResult = GetTaskResult diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/NotificationTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/NotificationTest.kt index b55a441..01c6b87 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/NotificationTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/NotificationTest.kt @@ -310,4 +310,116 @@ class NotificationTest { """.trimIndent(), ) } + + @Test + fun `should serialize TaskStatusNotification with all fields`() { + val notification = TaskStatusNotification( + TaskStatusNotificationParams( + taskId = "task-1", + status = TaskStatus.Working, + statusMessage = "Processing data", + createdAt = "2025-01-01T00:00:00Z", + lastUpdatedAt = "2025-01-01T00:01:00Z", + ttl = 60000, + pollInterval = 5000, + meta = buildJsonObject { put("source", "worker-1") }, + ), + ) + + verifySerialization( + notification, + McpJson, + """ + { + "method": "notifications/tasks/status", + "params": { + "taskId": "task-1", + "status": "working", + "statusMessage": "Processing data", + "createdAt": "2025-01-01T00:00:00Z", + "lastUpdatedAt": "2025-01-01T00:01:00Z", + "ttl": 60000, + "pollInterval": 5000, + "_meta": { + "source": "worker-1" + } + } + } + """.trimIndent(), + ) + } + + @Test + fun `should serialize TaskStatusNotification with minimal fields`() { + val notification = TaskStatusNotification( + TaskStatusNotificationParams( + taskId = "task-2", + status = TaskStatus.Completed, + createdAt = "2025-01-01T00:00:00Z", + lastUpdatedAt = "2025-01-01T00:02:00Z", + ttl = null, + ), + ) + + verifySerialization( + notification, + McpJson, + """ + { + "method": "notifications/tasks/status", + "params": { + "taskId": "task-2", + "status": "completed", + "createdAt": "2025-01-01T00:00:00Z", + "lastUpdatedAt": "2025-01-01T00:02:00Z" + } + } + """.trimIndent(), + ) + } + + @Test + fun `should deserialize TaskStatusNotification`() { + val json = """ + { + "method": "notifications/tasks/status", + "params": { + "taskId": "task-3", + "status": "failed", + "statusMessage": "Connection lost", + "createdAt": "2025-01-01T00:00:00Z", + "lastUpdatedAt": "2025-01-01T00:03:00Z", + "ttl": 30000, + "pollInterval": 1000 + } + } + """.trimIndent() + + val notification = verifyDeserialization(McpJson, json) + val params = notification.params!! + + assertEquals(Method.Defined.NotificationsTasksStatus, notification.method) + assertEquals("task-3", params.taskId) + assertEquals(TaskStatus.Failed, params.status) + assertEquals("Connection lost", params.statusMessage) + assertEquals("2025-01-01T00:00:00Z", params.createdAt) + assertEquals("2025-01-01T00:03:00Z", params.lastUpdatedAt) + assertEquals(30000L, params.ttl) + assertEquals(1000L, params.pollInterval) + assertNull(params.meta) + } + + @Test + fun `should deserialize TaskStatusNotification without params`() { + val json = """ + { + "method": "notifications/tasks/status" + } + """.trimIndent() + + val notification = verifyDeserialization(McpJson, json) + + assertEquals(Method.Defined.NotificationsTasksStatus, notification.method) + assertNull(notification.params) + } } diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/TasksTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/TasksTest.kt new file mode 100644 index 0000000..14bd248 --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/TasksTest.kt @@ -0,0 +1,802 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import io.kotest.matchers.nulls.shouldBeNull +import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.shouldBe +import io.kotest.matchers.types.shouldBeInstanceOf +import io.modelcontextprotocol.kotlin.test.utils.verifyDeserialization +import io.modelcontextprotocol.kotlin.test.utils.verifySerialization +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import kotlin.test.Test + +class TasksTest { + + // ======================================================================== + // Task + // ======================================================================== + + @Test + fun `should serialize Task with minimal fields`() { + val task = Task( + taskId = "task-1", + status = TaskStatus.Working, + createdAt = "2025-01-01T00:00:00Z", + lastUpdatedAt = "2025-01-01T00:00:00Z", + ttl = null, + ) + + verifySerialization( + task, + McpJson, + """ + { + "taskId": "task-1", + "status": "working", + "createdAt": "2025-01-01T00:00:00Z", + "lastUpdatedAt": "2025-01-01T00:00:00Z" + } + """.trimIndent(), + ) + } + + @Test + fun `should serialize Task with all fields`() { + val task = Task( + taskId = "task-2", + status = TaskStatus.Completed, + statusMessage = "Processing complete", + createdAt = "2025-01-01T00:00:00Z", + lastUpdatedAt = "2025-01-01T00:01:00Z", + ttl = 60000, + pollInterval = 5000, + ) + + verifySerialization( + task, + McpJson, + """ + { + "taskId": "task-2", + "status": "completed", + "statusMessage": "Processing complete", + "createdAt": "2025-01-01T00:00:00Z", + "lastUpdatedAt": "2025-01-01T00:01:00Z", + "ttl": 60000, + "pollInterval": 5000 + } + """.trimIndent(), + ) + } + + @Test + fun `should deserialize Task and verify TaskFields contract`() { + val json = """ + { + "taskId": "task-3", + "status": "failed", + "statusMessage": "Connection lost", + "createdAt": "2025-01-01T00:00:00Z", + "lastUpdatedAt": "2025-01-01T00:02:00Z", + "ttl": 30000, + "pollInterval": 1000 + } + """.trimIndent() + + val task = verifyDeserialization(McpJson, json) + task.shouldBeInstanceOf() + task.taskId shouldBe "task-3" + task.status shouldBe TaskStatus.Failed + task.statusMessage shouldBe "Connection lost" + task.createdAt shouldBe "2025-01-01T00:00:00Z" + task.lastUpdatedAt shouldBe "2025-01-01T00:02:00Z" + task.ttl shouldBe 30000L + task.pollInterval shouldBe 1000L + } + + // ======================================================================== + // TaskStatus + // ======================================================================== + + @Test + fun `should serialize all TaskStatus values`() { + verifySerialization(TaskStatus.Working, McpJson, "\"working\"") + verifySerialization(TaskStatus.InputRequired, McpJson, "\"input_required\"") + verifySerialization(TaskStatus.Completed, McpJson, "\"completed\"") + verifySerialization(TaskStatus.Failed, McpJson, "\"failed\"") + verifySerialization(TaskStatus.Cancelled, McpJson, "\"cancelled\"") + } + + // ======================================================================== + // TaskMetadata + // ======================================================================== + + @Test + fun `should serialize TaskMetadata with ttl`() { + val metadata = TaskMetadata(ttl = 120000) + + verifySerialization( + metadata, + McpJson, + """ + { + "ttl": 120000 + } + """.trimIndent(), + ) + } + + @Test + fun `should serialize TaskMetadata without ttl`() { + val metadata = TaskMetadata() + + verifySerialization( + metadata, + McpJson, + "{}", + ) + } + + // ======================================================================== + // RelatedTaskMetadata + // ======================================================================== + + @Test + fun `should serialize RelatedTaskMetadata`() { + val related = RelatedTaskMetadata(taskId = "task-42") + + verifySerialization( + related, + McpJson, + """ + { + "taskId": "task-42" + } + """.trimIndent(), + ) + } + + @Test + fun `should deserialize RequestMeta with RelatedTaskMetadata`() { + val json = """ + { + "io.modelcontextprotocol/related-task": { + "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840" + } + } + """.trimIndent() + + val meta = McpJson.decodeFromString(json) + val related = meta.relatedTask + related.shouldNotBeNull() + related.taskId shouldBe "786512e2-9e0d-44bd-8f29-789f320fe840" + } + + // ======================================================================== + // CreateTaskResult + // ======================================================================== + + @Test + fun `should serialize CreateTaskResult`() { + val result = CreateTaskResult( + task = Task( + taskId = "task-1", + status = TaskStatus.Working, + createdAt = "2025-01-01T00:00:00Z", + lastUpdatedAt = "2025-01-01T00:00:00Z", + ttl = null, + ), + ) + + verifySerialization( + result, + McpJson, + """ + { + "task": { + "taskId": "task-1", + "status": "working", + "createdAt": "2025-01-01T00:00:00Z", + "lastUpdatedAt": "2025-01-01T00:00:00Z" + } + } + """.trimIndent(), + ) + } + + @Test + fun `should serialize CreateTaskResult with meta`() { + val result = CreateTaskResult( + task = Task( + taskId = "task-1", + status = TaskStatus.Working, + createdAt = "2025-01-01T00:00:00Z", + lastUpdatedAt = "2025-01-01T00:00:00Z", + ttl = 60000, + ), + meta = buildJsonObject { put("trace", "abc") }, + ) + + verifySerialization( + result, + McpJson, + """ + { + "task": { + "taskId": "task-1", + "status": "working", + "createdAt": "2025-01-01T00:00:00Z", + "lastUpdatedAt": "2025-01-01T00:00:00Z", + "ttl": 60000 + }, + "_meta": { + "trace": "abc" + } + } + """.trimIndent(), + ) + } + + @Test + fun `should deserialize CreateTaskResult`() { + val json = """ + { + "task": { + "taskId": "task-5", + "status": "input_required", + "statusMessage": "Need more data", + "createdAt": "2025-01-01T00:00:00Z", + "lastUpdatedAt": "2025-01-01T00:01:00Z", + "pollInterval": 2000 + } + } + """.trimIndent() + + val result = verifyDeserialization(McpJson, json) + result.task.taskId shouldBe "task-5" + result.task.status shouldBe TaskStatus.InputRequired + result.task.statusMessage shouldBe "Need more data" + result.task.pollInterval shouldBe 2000L + result.task.ttl.shouldBeNull() + result.meta.shouldBeNull() + } + + // ======================================================================== + // GetTaskRequest + // ======================================================================== + + @Test + fun `should serialize GetTaskRequest`() { + val request = GetTaskRequest( + GetTaskRequestParams(taskId = "task-10"), + ) + + verifySerialization( + request, + McpJson, + """ + { + "method": "tasks/get", + "params": { + "taskId": "task-10" + } + } + """.trimIndent(), + ) + } + + @Test + fun `should serialize GetTaskRequest with meta`() { + val request = GetTaskRequest( + GetTaskRequestParams( + taskId = "task-10", + meta = RequestMeta( + buildJsonObject { put("progressToken", "pt-1") }, + ), + ), + ) + + verifySerialization( + request, + McpJson, + """ + { + "method": "tasks/get", + "params": { + "taskId": "task-10", + "_meta": { + "progressToken": "pt-1" + } + } + } + """.trimIndent(), + ) + } + + @Test + fun `should deserialize GetTaskRequest`() { + val json = """ + { + "method": "tasks/get", + "params": { + "taskId": "task-11" + } + } + """.trimIndent() + + val request = verifyDeserialization(McpJson, json) + request.method shouldBe Method.Defined.TasksGet + request.taskId shouldBe "task-11" + request.meta.shouldBeNull() + } + + // ======================================================================== + // GetTaskResult + // ======================================================================== + + @Test + fun `should serialize GetTaskResult with all fields`() { + val result = GetTaskResult( + taskId = "task-20", + status = TaskStatus.Completed, + statusMessage = "Done", + createdAt = "2025-01-01T00:00:00Z", + lastUpdatedAt = "2025-01-01T00:05:00Z", + ttl = 300000, + pollInterval = 10000, + meta = buildJsonObject { put("server", "main") }, + ) + + verifySerialization( + result, + McpJson, + """ + { + "taskId": "task-20", + "status": "completed", + "statusMessage": "Done", + "createdAt": "2025-01-01T00:00:00Z", + "lastUpdatedAt": "2025-01-01T00:05:00Z", + "ttl": 300000, + "pollInterval": 10000, + "_meta": { + "server": "main" + } + } + """.trimIndent(), + ) + } + + @Test + fun `should deserialize GetTaskResult and verify TaskFields contract`() { + val json = """ + { + "taskId": "task-21", + "status": "working", + "createdAt": "2025-01-01T00:00:00Z", + "lastUpdatedAt": "2025-01-01T00:00:30Z" + } + """.trimIndent() + + val result = verifyDeserialization(McpJson, json) + result.shouldBeInstanceOf() + result.taskId shouldBe "task-21" + result.status shouldBe TaskStatus.Working + result.statusMessage.shouldBeNull() + result.ttl.shouldBeNull() + result.pollInterval.shouldBeNull() + result.meta.shouldBeNull() + } + + // ======================================================================== + // GetTaskPayloadRequest + // ======================================================================== + + @Test + fun `should serialize GetTaskPayloadRequest`() { + val request = GetTaskPayloadRequest( + GetTaskPayloadRequestParams(taskId = "task-30"), + ) + + verifySerialization( + request, + McpJson, + """ + { + "method": "tasks/result", + "params": { + "taskId": "task-30" + } + } + """.trimIndent(), + ) + } + + @Test + fun `should deserialize GetTaskPayloadRequest`() { + val json = """ + { + "method": "tasks/result", + "params": { + "taskId": "task-31" + } + } + """.trimIndent() + + val request = verifyDeserialization(McpJson, json) + request.method shouldBe Method.Defined.TasksResult + request.taskId shouldBe "task-31" + request.meta.shouldBeNull() + } + + // ======================================================================== + // GetTaskPayloadResult + // ======================================================================== + + @Test + fun `should serialize GetTaskPayloadResult with meta`() { + val result = GetTaskPayloadResult( + buildJsonObject { + put("_meta", buildJsonObject { put("origin", "task-30") }) + }, + ) + + verifySerialization( + result, + McpJson, + """ + { + "_meta": { + "origin": "task-30" + } + } + """.trimIndent(), + ) + } + + @Test + fun `should serialize GetTaskPayloadResult without meta`() { + val result = GetTaskPayloadResult(buildJsonObject { }) + + verifySerialization(result, McpJson, "{}") + } + + @Test + fun `should preserve arbitrary payload fields in GetTaskPayloadResult`() { + val json = """ + { + "_meta": { + "origin": "task-42" + }, + "content": [ + { + "type": "text", + "text": "hello" + } + ], + "isError": false + } + """.trimIndent() + + val result = verifyDeserialization(McpJson, json) + result.meta.shouldNotBeNull() + result.meta?.get("origin")?.jsonPrimitive?.content shouldBe "task-42" + result["content"].shouldNotBeNull() + result["isError"].shouldNotBeNull() + } + + // ======================================================================== + // ListTasksRequest + // ======================================================================== + + @Test + fun `should serialize ListTasksRequest without params`() { + val request = ListTasksRequest() + + verifySerialization( + request, + McpJson, + """ + { + "method": "tasks/list" + } + """.trimIndent(), + ) + } + + @Test + fun `should serialize ListTasksRequest with cursor`() { + val request = ListTasksRequest( + params = PaginatedRequestParams(cursor = "page-2"), + ) + + verifySerialization( + request, + McpJson, + """ + { + "method": "tasks/list", + "params": { + "cursor": "page-2" + } + } + """.trimIndent(), + ) + } + + @Test + fun `should deserialize ListTasksRequest`() { + val json = """ + { + "method": "tasks/list", + "params": { + "cursor": "next-page" + } + } + """.trimIndent() + + val request = verifyDeserialization(McpJson, json) + request.method shouldBe Method.Defined.TasksList + request.cursor shouldBe "next-page" + } + + // ======================================================================== + // ListTasksResult + // ======================================================================== + + @Test + fun `should serialize ListTasksResult with empty list`() { + val result = ListTasksResult(tasks = emptyList()) + + verifySerialization( + result, + McpJson, + """ + { + "tasks": [] + } + """.trimIndent(), + ) + } + + @Test + fun `should serialize ListTasksResult with tasks and pagination`() { + val result = ListTasksResult( + tasks = listOf( + Task( + taskId = "task-1", + status = TaskStatus.Working, + createdAt = "2025-01-01T00:00:00Z", + lastUpdatedAt = "2025-01-01T00:00:00Z", + ttl = null, + ), + Task( + taskId = "task-2", + status = TaskStatus.Completed, + createdAt = "2025-01-01T00:00:00Z", + lastUpdatedAt = "2025-01-01T00:01:00Z", + ttl = 60000, + ), + ), + nextCursor = "cursor-abc", + ) + + verifySerialization( + result, + McpJson, + """ + { + "tasks": [ + { + "taskId": "task-1", + "status": "working", + "createdAt": "2025-01-01T00:00:00Z", + "lastUpdatedAt": "2025-01-01T00:00:00Z" + }, + { + "taskId": "task-2", + "status": "completed", + "createdAt": "2025-01-01T00:00:00Z", + "lastUpdatedAt": "2025-01-01T00:01:00Z", + "ttl": 60000 + } + ], + "nextCursor": "cursor-abc" + } + """.trimIndent(), + ) + } + + @Test + fun `should deserialize ListTasksResult`() { + val json = """ + { + "tasks": [ + { + "taskId": "task-99", + "status": "cancelled", + "statusMessage": "User cancelled", + "createdAt": "2025-01-01T00:00:00Z", + "lastUpdatedAt": "2025-01-01T00:03:00Z" + } + ] + } + """.trimIndent() + + val result = verifyDeserialization(McpJson, json) + result.tasks.size shouldBe 1 + result.tasks[0].taskId shouldBe "task-99" + result.tasks[0].status shouldBe TaskStatus.Cancelled + result.tasks[0].statusMessage shouldBe "User cancelled" + result.tasks[0].ttl.shouldBeNull() + result.nextCursor.shouldBeNull() + } + + // ======================================================================== + // CancelTaskRequest + // ======================================================================== + + @Test + fun `should serialize CancelTaskRequest`() { + val request = CancelTaskRequest( + CancelTaskRequestParams(taskId = "task-40"), + ) + + verifySerialization( + request, + McpJson, + """ + { + "method": "tasks/cancel", + "params": { + "taskId": "task-40" + } + } + """.trimIndent(), + ) + } + + @Test + fun `should deserialize CancelTaskRequest`() { + val json = """ + { + "method": "tasks/cancel", + "params": { + "taskId": "task-41" + } + } + """.trimIndent() + + val request = verifyDeserialization(McpJson, json) + request.method shouldBe Method.Defined.TasksCancel + request.taskId shouldBe "task-41" + request.meta.shouldBeNull() + } + + // ======================================================================== + // CancelTaskResult + // ======================================================================== + + @Test + fun `should serialize CancelTaskResult`() { + val result = CancelTaskResult( + taskId = "task-40", + status = TaskStatus.Cancelled, + statusMessage = "Cancelled by user", + createdAt = "2025-01-01T00:00:00Z", + lastUpdatedAt = "2025-01-01T00:02:00Z", + ttl = null, + ) + + verifySerialization( + result, + McpJson, + """ + { + "taskId": "task-40", + "status": "cancelled", + "statusMessage": "Cancelled by user", + "createdAt": "2025-01-01T00:00:00Z", + "lastUpdatedAt": "2025-01-01T00:02:00Z" + } + """.trimIndent(), + ) + } + + @Test + fun `should deserialize CancelTaskResult and verify TaskFields contract`() { + val json = """ + { + "taskId": "task-42", + "status": "cancelled", + "createdAt": "2025-01-01T00:00:00Z", + "lastUpdatedAt": "2025-01-01T00:04:00Z", + "ttl": 120000, + "pollInterval": 3000 + } + """.trimIndent() + + val result = verifyDeserialization(McpJson, json) + result.shouldBeInstanceOf() + result.taskId shouldBe "task-42" + result.status shouldBe TaskStatus.Cancelled + result.statusMessage.shouldBeNull() + result.ttl shouldBe 120000L + result.pollInterval shouldBe 3000L + } + + // ======================================================================== + // Deserialization with unknown fields (ignoreUnknownKeys) + // ======================================================================== + + @Test + fun `should deserialize Task ignoring unknown fields`() { + val json = """ + { + "taskId": "task-100", + "status": "working", + "createdAt": "2025-01-01T00:00:00Z", + "lastUpdatedAt": "2025-01-01T00:00:00Z", + "unknownField": "should be ignored" + } + """.trimIndent() + + val task = McpJson.decodeFromString(json) + task.taskId shouldBe "task-100" + task.status shouldBe TaskStatus.Working + task.ttl.shouldBeNull() + } + + // ======================================================================== + // RequestMeta.relatedTask accessor + // ======================================================================== + + @Test + fun `should read relatedTask from RequestMeta`() { + val meta = RequestMeta( + buildJsonObject { + put( + RELATED_TASK_META_KEY, + buildJsonObject { put("taskId", "task-50") }, + ) + }, + ) + + val related = meta.relatedTask + related.shouldNotBeNull() + related.taskId shouldBe "task-50" + } + + @Test + fun `should return null relatedTask when key is absent`() { + val meta = RequestMeta( + buildJsonObject { put("progressToken", "pt-1") }, + ) + + meta.relatedTask.shouldBeNull() + } + + @Test + fun `GetTaskResult cast to CancelTaskResult via generic does not throw due to type erasure`() { + val result: RequestResult = GetTaskResult( + taskId = "task-1", + status = TaskStatus.Cancelled, + createdAt = "2025-01-01T00:00:00Z", + lastUpdatedAt = "2025-01-01T00:01:00Z", + ttl = null, + ) + + // Simulates what Protocol.request() does: + // the cast goes through a generic type parameter, so JVM erases T to RequestResult + fun uncheckedCast(value: RequestResult): T { + @Suppress("UNCHECKED_CAST") + return value as T + } + + val cancelResult: CancelTaskResult = uncheckedCast(result) + + cancelResult.taskId shouldBe "task-1" + cancelResult.status shouldBe TaskStatus.Cancelled + } +} From 55afb63c27f31920c75c51c091582861bdff21e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 13:18:41 +0200 Subject: [PATCH 186/449] chore(deps): bump gradle-wrapper from 9.3.1 to 9.4.1 (#614) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [gradle-wrapper](https://github.com/gradle/gradle) from 9.3.1 to 9.4.1.

Release notes

Sourced from gradle-wrapper's releases.

9.4.1

The Gradle team is excited to announce Gradle 9.4.1.

Here are the highlights of this release:

  • Java 26 support
  • Non-class-based JVM tests
  • Enhanced console progress bar

Read the Release Notes

We would like to thank the following community members for their contributions to this release of Gradle: akankshaa-00, Attila Kelemen, Björn Kautler, dblood, Dennis Rieks, duvvuvenkataramana, John Burns, Julian, kevinstembridge, Niels Doucet, Philip Wedemann, ploober, Richard Hernandez, Roberto Perez Alcolea, Sebastian Lövdahl, stephan2405, Stephane Landelle, Ujwal Suresh Vanjare, Victor Merkulov, Vincent Potuček, Vladimir Sitnikov.

Upgrade instructions

Switch your build to use Gradle 9.4.1 by updating your wrapper:

./gradlew wrapper --gradle-version=9.4.1 && ./gradlew
wrapper

See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

Reporting problems

If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=gradle-wrapper&package-manager=gradle&previous-version=9.3.1&new-version=9.4.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/wrapper/gradle-wrapper.jar | Bin 46175 -> 48966 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 61285a659d17295f1de7c53e24fdf13ad755c379..d997cfc60f4cff0e7451d19d49a82fa986695d07 100644 GIT binary patch delta 39855 zcmXVXQ+TCK*K{%yXUE#HZQHhO+vc9wwrx+$i8*m5wl%T&&+~r&Ngv!-pWN44UDd0q zdi&(t$mh2PCnV6y+L8_uoB`iaN$a}!Vy7BP$w_57W_S6jHBPo!x>*~H3E@!NHJR5n zxF3}>CVFmQ;Faa4z^^SqupNL0u)AhC`5XDvqE|eW zxDYB9iI_{E3$_gIvlD|{AHj^enK;3z&B%)#(R@Fow?F81U63)Bn1oKuO$0f29&ygL zJVL(^sX6+&1hl4Dgs%DC0U0Cgo0V#?m&-9$knN2@%cv6E$i_opz66&ZXFVUQSt_o% zAt3X+x+`1B(&?H=gM?$C(o3aNMEAX%6UbKAyfDlj{4scw@2;a}sZX%!SpcbPZzYl~ z>@NoDW1zM}tqD?2l4%jOLgJtT#~Iz^TnYGaUaW8s`irY13k|dLDknw)4hH6w+!%zP zoWo3z>|22WGFM$!KvPE74{rt7hs(l?Uk7m+SjozYJG7AZA~TYS$B-k(FqX51pZ2+x zWoDwrCVtHlUaQAS%?>?Zcs`@`M)*S6$a-E5SkXYjm`9L>8EtTzxP%`iXPCgUJhF)LmcO8N zeCq?6sCOM!>?In*g-Nf^!FLX_tD>tdP}Qu&LbWx+5!Z5l7?X!!hk3jRFlKDb!=Jb4 z7y6)re6Y!QE1a;yXoZC*S$_|pT`pA*(6Wwg%;_Q+d*jw;i=|e$DQU=EcB-K+hg9=O z{1{BQsH*V!6t5tw;`ONRF!yo~+cF4p}|xHPE&)@e@Lv4qTL%3}vh4G|Gb$6%Eu zF`@mf2gOj$jYquFnvFCfb9%(9@mOC4N7VWF#;_-4Hr`(ikV(L)V=*hH^P3I<8RXOBnd0%J)*S^v*+L=*srT zh$IKKg?&n5H(Rho@`U^AyL=sN%WY)ZC9U)pfGVfaJpz+_n0|qnri_sF-g>-w^_4A;{;3 z2zTOH6bxZt8k`rB(XAAo>wufzcNZRTJSseFF{MmVV&4XVmKoPC0qRQJG-r9i z#yqN9hrZoA&Zp?DMIJLUtN3A!LZ89wr@`lge7butX>Q;1Yyi18b3#kDs|o$Q-f=a? zS;F_#_D1zk={}uf4ziZ+zjshKO^HC9-@G@n%RhXcLA%&TP#874IHEe;@#u!C3X@nY zaHpT0mAZ-N7)vR8Z|0maGSnM=QxJ8gamH0hLc#sW`>p;KU>wz515s9BDjB0eaqI1( z-&+*wV~o4?ha@KJ;U1zi`2(eKXkxc`NMkKxnz>GSlA0~7IHQ4KQWUPKD<}r@FOC_{ zQIDL`U!eq4@;?!9qWmvk%A6XHbxRY5BPh%#HKP`2>-jhY*TfF#gwLOR~f=$-qCq2V;*bz#LtA+nS@}dcA9S9exiGl z^t`RA_OgVRSg5O!GyJTc)4w-v(m~t)U{2ti*am#Q9`)B^wNC!pE9&ktf6^Cgs(3X9 znK~S~S}nNMh1+T6K>hr}(e9VlKKdt<1`D@~mE;aSB-I=?S;M$lD9`O$<99XzLG2F4 zg8`M+SrA_Cb-Bfo#>)U*nB@lBkUE&<;vN{rnAmuX<|-}ae2*aJG4k@$v%Rc;IM}_v z)wgICOxg ze%Zi6xg$romfi!Wy}i| zT8L+Xa*7}ZVYkJGkOKG>+S57jEDu7AiCi}B5m-HgeIInYmDQX8g6_Liajf_Dx@k^H zg*_C0VY^d-Ta|p6or>0LP}E$ZB{BKT?Up&p1Y|j7746nM)xXv!Tbpbo+eiB_F>?By zkhP*}9ZfjtUYuZUHP^ z>k3^hW#o2WXM~+rrPq9-S8e7APJzY^smW%tJr+s9W{Vi(i`b0pOOfxG`?0-rvo|Fu z#?Do52Z*#pPec0jqtd!y(#T zT|aPAx4<9ST0a)9E5r8l8Y4V0L4;bA_y?{VLNbAme_|R39vQ}m8Ix2Ay0~v%g}07A z86rGJYvG6Be5-4ml(;u`uZMOHPvEiySJ7Jm+^Hu3@33Ko4X$4i= z`nC#q;)J6=<0x<*q_BM)Def2(Xf%!7=adUcN5IX)Yw?1f*V=O+4!h3b)2;N{b>uUxh6KU zFO)rh!~d~HK-z83C*6m5@*(L@qJC@#9TY`${f#|l=ZoRMp7&rBx+gM))6PcXsA0v! z5eQ5U2zyP2%erLHmg=vZbWV&{KE@|FET}xun4QZ+j8GfNg+mtsW-R6kjeuGyVnU=K zBiAQ(?wz7!cz3VX?;-Xic;#aO&xN z-%mu;`sXgYc3{cqb|L1|aGf5UQDzrp1yHOB(HMD^+cpK9SIuM4E5cl5UM~-mybU^`JdHZ6$#~n_V)iQ+PAHacfSa#|SN;k`n%p(7#uf)Q> zlHE8+)PczLFiHEnu~aXa{g_hI94R&V(ZF;Wxh%tFIgmzT8f&bA)>us* zNA*!XoNoV-UPx|T<+mz&aZktvj-_f#meX&88P?CcuJY<%Iz z9~lFd)ITw&2kg3C!vE$_NDd!s8Mn5lu-na9mcBg$=B^ioWX6p8iLP&hule^!6j67i0mYIxNfR>X!CfH?G;y9Tl5)Q+4#bAL!BH~e%- zPkNQrOZIc5s*qXJ;9&h7_s5AJYt*oo2A?tQ*WAM`iaFre%Av|~a>uh&Pzl}s%(oCEd$G1=Km=P=^Tf==pM>*RcAANEI6hw9Vl<3&v zSEdp|TFrt)z!kqdUdibz_*TSj9WEbzlm+6Oym9gQk~vz@*OmO2cWHk$mMEtd*b*r7 z)drx#>)3)0d`ZeHYcf+1exTAWv9*UhjwA1*)%MKl5*IH}epmne{i8njH@p|m(oyy( zD{I8)8qH_SnUA6WFkaH2e4`UtYtt5I_@a_w%%E(o8bb0;@{8i`s?+C zGTz{xBP2eyi~$TfW3N(-R|c))j)dk$yggJDLo-Ur;A@or+w#Fuaqk zx#9j&Vv2ob(sZQpA{>3KU?H*Hf87&w!P(9lj3uA8s_0vlDtUVyIOvgPV@#~%%rVt@ zw6BW$7zKDvf#*ftc& z`H~cLVIoq;Ffl<@kX=47^^aG^#9GFmQE6-w$GApb zd5u1D4@*oJ9mk=`1HaHs?x`)mSd1G??$5*?JEn_`4Ckr-e%Lv8 zcB#IIsb5(CF>u-E29hB(7#I%{7?_gmcZlQ@Vk=OvyPfz5I?DDe+*)JmOOPpev2s!5 zIK)0cqIa_;UB%ily_J+%A|T>dKT_6--1`pFwIsG;*K~n)&@9E%hVLui3^)JrM*gqf zFR%tc@a|xLfAk1%?bH-MF}=Myt7mhS#jC-nv-iRC{I#EKf*^9;PGLcO7a!YiedEhe zeMZothG#o&RMk==LcAw{a;bg2&b7K%WTk+4=gLh#9dDO`(_v0oYCTZ|BCdJ7i!ms{ zB=J|Hn`Nc3mWiQn{&&-{ws!}kD9Sim;8}pt^2HC`x{Ay?Roy54c-d-cnHg{7D5K9z zv@o)c)kswkaHTdvQly_s^g+sDyCjBAbP1%W229JAba?|uqOL*t$|KD^5g3dLKn=Xb z9IW_k?k*)kVn>2Rqj3QejshvLqXQ*1NVJuhKbcUhCA`nKZE_RACNfT&L* zI$YUQJO#8X!-yd3ATPe6yf7LIrHOsIX=b_STgI2a#J8f~@@ll&;%8Kx5|0McAwYlI zNs3D#p)W1q4pJN-#V@~&`C6yx!RKxhy`Cpk?OS$q4dS1IV;hOu-vH(l)%`YjbxgI-26N1|9c;#^ zv+fX)nq-IF#F{VG3bBNiglftne*B||U<63~qoRGb*J2JI7MaAxT6Pdd&(djcek2<= zsBapXlGbq_5`*;^l;cX+-Yulze+duS0ywRjUgkT)#(DTchjKp+>*L;RCt;mZ0$n-k z8u*%CMZ{sj|raK-MZ8XXWWlW)mEyE%K ztogoO4IMeUy1H89tZs(Vig2oUO8UKwC9>3rBxqq_g|@NvW(7NtqQTVfAn$BnHFI4O zZ}Lgk1PBRc%zl^=?B=SeX?x|xi9m0-pMZ}xi`&b{XcL+s=~>u6(+ldBR)}&hKUL9P zVzKOnJ?rBrkSm1gfFcFtn7^rsiJ5L4iyp}T`Y6l7WI}Urs8CuV<`%O12R%B%pvcko(+GnA~)yiUirPXJc=q1P_Rh-`zw_0r9tn*fwW6^V^o z)sML@p8m+~EowB=h?CjA+cr9xRfa$NmNxAalqixbE_s7ZUI!@;K82(r`=l&XyUwfq z!`lnA7>3ylx!48Wlgz>P-lb~w$b6a5+oec>)-d-M;nIHp7nFy0n24)&YO=>S0Z(Yp zO+c<;-(@g9FLsB2vu7RO!0A0{9UTU@frfuP7NgNzHlBvJ+!4@JygLpm{!|eyBtPp4 z3ymxmEb*`x(!{EU%z)C~WOHhb@J zfye(U_Ml~XTl7!d_W$<3ishk^C-c#ef)Ds^SywIDI{mDc9%P1WrBo{1tAiAHb$ zy&0#M4f-qfza8F84nQaWL~S&xNQzG|P>PQy{7o@?vfOk|$I}L{<>eEhVJ~=lJjGym zaWU54Hl1|b@B!8q_oTS?5{Gk{K&8em|M=<&KRlvg^r6cQJO zAu8~Z0eU3i>e=5qqP&$9=w_%xFYB^^LO7LLiRHA^|;S4F6ANMoL=;hZq->= zcSZ^2L)TMD99%?aFwzkZ2$=wMj1ihM{noHe=8-z}K}`R$`FI!B97|x@V}UbVRgO1y z5V37pra5X%7**FZt$6qSDskj3OMr8Dr{wqUpW?%Gj+WaI7IGC{QiQ_?6;BUws?iy9 zr?uCbV7fBv7#rQ!;fPu!Qv?;xMp~V;dS54b?$6MVY(Ljrd4$RVQ^uG=kJ!W`a>&%8 z{N;cW{8i2M^VZ4>D@LN0doB%ye<{pMpKn(ja8DnCG4Kjm?9foo%>}4B#jq zqVJ5aYS;aOeS$JPxW(!)UQWD%y-oS6x&B_=UC=)Wuf_ZRPE9$VPrx&G65;!18!SF# z8JNxYs%6L)e=H6SdCNvIkz)F0yeP*PMcXA6ZE&C~|S^US~Pw2fuW)yo8&XHYgy&QKWjlOsY|OFcq}iu28r z#83E>BRjZsGq~O-)*9))zhWJIa`hY?aJ)2j4|v$nY39=H+-39&s0#Ldiy?@So(>2a zR{k?D8-7N01QN4s>pMqB|38Z$v%);7COMHI81xK@5d)h9j70z{1BQk+E)CK`H@l`b z>1|^8B4&1w`%ov;oh^(Z^jTxcA;Af+EMfV9qa=RBm`SstuEtDq=!)Y%g~~VWxT;-_Q6;X z_oe!AJ3ptQr}_)qdK#%}cRtT*3%K zE>9)EnWh)2ol4C@>6=M89Wntx8XnICocs*JfbX5Y`^LX36EK&NUMp1dkspMN`wbHR&eKLgSS?2O;0?>XODKO444mdhRf z4lUz}Wk$%=Dbhd}WWZ;M!Aq@^tg~dG9u`#FVA5G+iaqaX55onBmg`B8VttXe%0v9! z)2!wlh{C+f#(~QiCyFPbH_hBa85E*3DNR0Nq6T>-KgacFeg|M7G1=f5z2nXf>GusU z{SEjTW2bp5OX~@XR;$;VDvN>Wd}vF{A6jjHT95|&jUMh6r5KbbNfCQ8!vAKi~a{NIp-4h91Q0|o|0oZLW$ z@Xsk_2kB~}X#zJ#At;Bm$P3so&9iJ^0~2Trkh_N?Qoq5XE=n}tGr3AhP_Q~%43ugR z>iJ*l2%MQ3`q@`Q>S)^Mzs(cQZO_d+TC`&XRcq6-9{XA5`}a2entZ>RVRQt~8TmFC zO{qBYMlf97!9ojQ-y+ns*xPg-u2Eyp<;}7#0nwDvj5)ySJL%4vWUf<}(xqs3X*BMC zuVa1ZGCpTAk!bSgk~{Z^&4rin?ifHAg~h^%oP_<2hA z^XcLK@xD}z84HB>%@hXfcUEb{c@_iEY=Nd!7E{wbQNxWsmz@^Fp@MXXZG>J|3pEG; z4I;ee&RgnGmN_mbgc(k3NH63T71RG0PflRE{`iTpJLKlGdx$2cs~ z#8YxgR93!?Pa_MMS#63_z!EY`1#~L?P>D>GPxrHj;_*!73POA4irGJjAPSLK24yNF zjbf$m>Y4l`Sij`np_S{rQk5Ir%`!%c77r8E&Anwc=~E{OCD7bp8)m~882=)R17(F6 zObD&-rkQTf<=k@Axu-{*1E#|&3#Jo+7?(=!T7Vwi##NR!xIJTeU{nR^c*UTl{I`83?m6Z#KF(`VcUkH02b)Y)4W%iXpCZe8&hQ%M_lTq3z3t~J&{mi=D-jX*b}n-W`RIpVQMDh z@!aALf&*Y#s!Ucb!7OQ(|JcqI!&O5v?qFBIfoQtNH(62KRLU$};@N$4wJCH+acP-o zZs3E@s(_cicL$IhaggsA{r;O`X6=&A)PucscLa{3d{<@}Ycbl*4MLX3Oh@q#PTRX? zK_mx>oFh4bh`WCU+K&<-t>f8i4K(g7XeJcjV2~LQp9bd_!fy&>438B;{iOHo=>fL8 zHUH)HOTFOnsSDZ$&-hPcTYIv>=V?%%BV|hoGD%R}-kh{wrM`o>N{)}Jl zdZ1P13p<^gUJY^wDb`)}x$+D9p?1SZ6qB5ZKSBI%SI zHb+Y1-B@PDFQ!I+*?GP@Hh|YfAn1Q4`~gZZo`_87mM9sM6AP&b z*s=0$xQNUsHdW%(JSmxvlMke+Y~=NLf7hFU4ew8I@JXm1Qjk zUp67_=$uQ-Q68@wg+JwRa}lRcv(lfLQ?$;9N_SKYSql6k7Gs-fEuPz}(5lhBn@@Yn zLw!L{&LdsFF=h*OoMv$#-8D&{?UE=Uz|4*kU**U7oC+NytdL1gI|*{M=COpy&=5## zLsvg;tf?Emq)D6lL*AsM1Yj4wA#2B0u%qpgk<*Ovv*T}?YKjXn1&mG=QH>h-CAo-c zge6B-8IRB1uSA(RlBe#`iGt?#I5=}2vb?*rqj(2???JkzS4&!ayf>Os!)x@a5jm;= z*k0(h(r(ELR|oD^azGYV)AC^pruZcBf<{iUv4YooTz)KM&)9zUT;w@P%wWH;2=4C- za4pwrs4_yDSf*iVv3my2=o!1&PwlI!zw^O@V`GI#6269RibKU8ImtT9$r2Gb2KjZ> zGm+LxJ8rVfO*3jTW(W6*`-ui~|w(Bq3D6>lIas>>v|P_BfK!>$rw&JI4Uk zbzAuareUX-UsUrAJrt%odUZL+jz0XeDn`YW21CxGW!{hMoQtEmmF?jP};#B*Pv*R!Z zxW%{;y$)-|J7&}p{gLIy8<6ij4$sJV-}~?hD=MsV*W@~!2_O4HUKhj9>r?>_2vkDz+5pwx|${|ob208d2 zxTyRewhZx#fEE{ZwmaPuL#?aM2QqLKX|i;i#? z%_<@1c$5G+c3(hEYS+BOe`J(aOWT^X0d8FrlZXz5sZNtX-2U}6qyQritVN{(o6MhbCh8Uo{X6V*; zCI+H%>Z8OjPDIkwlLI0f>t{!!{olryPV=7_|HvmpID}GqEU0Ul526k**RV*BhVHA- zC4rtOpUB?O#F+^?>VlXdTs=1DhNTD50kG@Twho=Ex9K};$f)HG_ zo;HdwX};3TWz{*5o71j>mBxT56XUMM$jp&oDKpG^54F4>cN_;a2sO5+9XR+CY+1T& zaf_o~I4A1QI;b!nLleQ|)=@Nqf4LeLBOP{%oHzK0Xg7%H6Gdu6u}n>QUUcdf4Z;gS z9%jHM9cg$^Fvi|W{3>*12;o8%9*|F}w48L4UEx-WmZD!wGRhxyuzveCXk%#j1YmVv zbbdBla;l8+#U4=Pr8y~RBi#xETz|&VQWvEmGdYf#y?aaAJs^|G@7;Xn5>#DX36ILjY`xqFFiDBSK!_ zSmrO)O?FnBtaWU<5)SF0%-@N95E(JkOS}-3HQw0_((7^3pcCz7Db#aH{Ztv}3c{F3 z9`wC};pA~_{8Nv%u8NQ)EV~Zn!|3B1S<9#=Hhz0=pi$PH6;ZSW1w{kSLFw~+8l1n2 z@c5=1c5B!zR?*TZWQ*zVSALXonhlVp=<@*W=WUf%JHU)yNGW5*(%xpj-C2&oI~JClY8V^7KfP>nN+>ti0V+ zaPvJbvYfidk?RUsBie4JyIZz@XzL!k#5pRJ&df8wTc)2yO!#{J`hK&*P+pUvdu3f{!mwdcnK{`y_r%EBVWa}+`47qTjA2|D3teK0ElsnzK2CN+rPqq z9%eLs7SjMK^wSB*F##!MXzvC!C!I7S?FT=JLUg*_2&Eyv8}F;-k6WnaW&a(w{92c; zyE2eo^_d!T>kPz~)8Bf*fAO2}lAtFTqw!Kr@q16OXJb`4uRAoS>1J_n0ViR;L{%XF z%LU-^5ZagUhsGmY9Eh)vIgC!<(4svy*7?;Zc31KO^g|VZa3FEXK{$-d)nwGxzBxrX$%|GWfsvxnAtX8#)L&Fe3H2f)4LMepvhiG7#&o?gx@u~Gf< zcvX1N6sW~u_p}wxi*Qw#pTc;8CqCKVAMRX6L#xWVjc zE4f~S`3&zbKj9!mk;{hL=Lg{@{cFlhaY50yE7rpZZ1CV2BlQG}W{`BgvclA_m2Gw` z47q{A??Iq$doUbf0|1h6f5EK&1^!+H<#!qQ_0I%_hJiw`vm${61Jn3F>M@f34;m4Z z73!El=F0sJ3qr{L>tyc9Bh7`S8~!%MotQ-k%F#51a0+TLQ4`)hd0gu?%W2DT704gR z0Y6+7VG!}Sua)~&X!iODEIhY-?=0Bf?v~rGzz}bgb{3|lvQNW_(rkn|VB@~C!#{pc zwG8F>Ip2ZM#78_L%R+|F%$?4l=Bfg(Y01C^%9Gx=5~P}EN*1rcjW6~hNghXAN?Z8# z(6k1G+RzJ&=OWLxkyW$FX6Y=McV-+ZhmJ=oGZvZL*~ba#+aal!6=!TF4ovQrD{fAS zERD$3@aH2GmE$02=lWoH^<3GH;k9AzXi7GY*VT-NpmkWgamq zxBv6<{lD_9mQ5b!{v$Su|I_+ukdTsT#4$jkF6L(D4sO=QcCHMjcE+x*>S~Z+|F(gF z#j0<*qN$^QZBm?4SpV=-q9Ig|ky?w_7>=eDz$iuQjt-g1)wsFylMJfBZiElIuG2d2_}13!Do&dKc9H z@wOaxB@rFfIS{MjMpl(p99dzbVVhOAl4VU+Z4sHgvB#r%mV=m{;-jL!cP7)LTq`L# z5oK^3X;qt4L(@`1;g`c`pd^FEkW|OsZEEOn!UKCID{~95?@*otOw&(QB)FyOx(|@N zT+gl+?wUo`OI&&P1K+)yj4SgIkoy$H5Bmy+697LVbv#u`;N zVAC|KaCIN>z47DhjXZc6Td%SI9Q=Og2O%mV)K2IOG*S@wvu-uhpzyj*7ii#bb(*yC zx-H<&@t~L7*@cl4ppH((zG)DH=rKXru1T>A6Kr;qRaY@|nz(Xc20aM2HJ~i`>SQ+> z`aO$XUHlkTfvLUz(8ZNe%I`GAZhM4R;C`P>G~V7~idPN$3_on4@na3Yzt~IhN509) zx-ZY%>^*ARzsM(>&J@#uI4GvD?R#*o$XEb?NTCH?-XsN>l&kg>xh93KfGRp59U0z&mBmzI?36&Oxw zhgbj?xh5uxdXCV|@^vhJIG}(NC=X4l>XE_G-i$jy5K}+YE&Pcey zExBLQ5&itH3SngF0tjFF17{oNLA?L)oDIED*(|}cvXhRFwu--aQQ@$~M*jHJrp1_6 zJXaB$O@u6ED?{{{Cgo$NK!~&pIN-USDZyTzWbwSVRp&paO*`w`5JQ79N7EnJEsuoc z!a`YO!j)3mFR)&L*>Na^Tog$;cUKmz!3JlIff}6f$zK2-2m<@aYUV}6>IoEeDZB=T z@5Lj_@QEByMx-N!&#h~)jVn=2kLdzs$NCF*OwdL_BVF>{`QBlHLES(CzZfwzLWuAz zF5Gf)G_3qR6|B7C`h?XW$t}4M=+m9sIJaaxmc5n85i9hDza1(%q%kCv2TPS5C+fjP+^*LHjt|vjQfB z*`RBRAhu&aR&Sm*wC51(E+f8k3DX;Icg%rhQhy=^sFx<@tKp+uD7yVMyPcfqZL=*) z$ud6>OJc+2mN_l1lU2-1DFDvL1J%^*(l|3@!-NwJD|&~2FWVzqp+`IpKH(FE57CbF z!ih(S&?tM)UG}>9ai|%Yd^f4jQ$462$mG1%*7TL_bIS38lw3@edk9l6^@{m7bAdqL z=>u8`;U6-}zzQU<|C_1K{*Tyj#f?CJDpr*CgMnyhFkw+;@e6`?23hR(e)e2%~Xk=5DYaZ}`sSzP$cjump=ohVk3j-md$Fw8pYUx&XTr)Q-Ct z#P!!wMz&l9?QsE-*+Dw_cO;T83(`Kpuw7Ksm@kW8A91D_Hc7SIz)6DLbPKS)o=>kb93KaYu#6aDV#>|P)TfdSc2PB3 zEHV{eey)!ipL%}`r?S{n!vcF1i^fx<1zLQcSEIf>jFoj*RN5#&6Vbe+RJy44kzsgx zFr`n0k0Lh-Zlm4-4_*xi;}0$f_t&Ak=KZD?foPasbJIr^@y-{vFBQBTzq&++<+s!` z!Fxyl=L~vNDA#Y6XfE=3w)wFP8tGqUZyBR6L4La>^D|3)bS{C0w-yqOXI0NF&C{dv zTCU1F(_aYqoNgU4aCId&Y_b zqBo6j1L>*9xS<^&!#Ye6A&&i4p-5EId%sY3*qIJ-wng%gxK!1wnXE_y{dMa`$Zd zU8az`#zNr^UbR7_&BZ&5cLGjfo43l=J;R#j4mueY~^Wdyr9a#Vj4H>+79(ew9F^8y)U zfVzm9)Q|CBdB!bP zHJ+OvP6<^mr?H}ndMAbak1>lO5i+x?v=90Bg!f`^)8EKz!Q3^oo^mboGN1M{Up`j% zDZ!?VLwCEnJeO?^vGE-oU}sp;5Snc1fMwf+TnzDe+q6&qvd9E5nxJc?S(Es1^CrsQ zwM>`cBQEJ(g<4Ed9vw5#=8}2Ny{d;A?vd@ne-A$$E;=DX_zeU^Rd-k8D8+WXI0{8k zLeQhH*Y;M2byiVD_s^A?plT0C1F7qH>WnJh0`(ieJ9HHN#J}zrf=H$PY(0M6;Bgjr z^S+Q^JkE#g#gAaJ;{h3y@u5^mv6^wdBxveguBNt3mobrIkOD~S9M?&VGVFUPgjls} zSYvb+zhz6Nj14cNd^u9ME$#{vg~btue>p*5oQeZ#gkSWW_$Xf^cD;7#VKF#?DxrH} zan5G!6&Z`nQF2glWo}kpl0Mw{JR>EZ8N`-75lc~C=;5^dXQ1E)V9LOmjkD>23hwwQ z(`S|ZviG8@bBxHt3%;~HTNDDmcX#zJ*AdyJ7tfZjfZ$C%W*Z50eN-~wETOAW>s$pj zRHE_4P(fc3TpZ!5c*yA>mc3f5;8JR+xLFbFF;{dLg8s&wj!$**3A#O}!Fv<~-3$c- z!91soC^WUL0VI%6(*#h39lW89ZBe|+Fd-rgiMj(w8rti}_l%uJ`=84KSl?W`R^i|O z9$XyT_*WE$na}$;qhq<@^()6hkn}9j-fI9yqzGNlc?dUBvVjy?_i7G9A8|0K5XoYi z(v|4mWZd4#D%WDXN!b_Rl_V5a-C|9A^C4iWrH{w)AgAj^#IjXH#8MBYJElZG6^fgn zcW8+d=-zS5OHe$cjNtC9qm^Y#4Z9~JXeNK;VyUfi-IwW+DgV#LdXI;?_Ya&K3zrF` ziWC>Pmj!Nfq;d~u3SL9?0AcR(i@gncxM$Llx{ny0u6vk=@|TV`BqoYeXhzhhG{92t zBP~m*{QCxjK!B9{^d8w-g^V(4S4efF{;-dUE}M)mSUUA7cF9*z_o$rs12zjyikr`# z;@L1IM4akqoO0&f&=y&~gX4Vl;{P*$P%Wlf_crFD{pm0*x*B@47dR<6 zJBPr(1kY@pgXj4LCfUEVDw4o!jfCvt&~r(opbX#SaC4|wmYe5M&Q;D`F6;Kim7w9T z@9h!RVVskbO&yv(iPoHzOX(X6e#HebSGXF;XPL}+vaD~cp!*J3l-$>T z3x5R7DD_~Cmol0FNe7E1;1=o2p$1^s~UgDkj$b3M(I$)vBt?c-{$CbkmJ6+}fhH z20e!9LZ`g3GKESCpRA=CF#1JG3b}0cGccXem79Uw(8P)pRq+;Q#94Hh>XvQXe&mkq zSKWE`zfi4;D3Z@$aF_h9cjxTly`IoE;Oq&UktgUK{{RYDdxAJy6}v>!dFq`G^6+nV zEN;u9t1(*Mu^bX4dVdJXUFGF?Kv;%XGa(Ug*S$)nZNCeMeL?3(DzwK? zL{YY4+a;`y2&7)rkBF#wz<7a2{EuD^;G;oM{~l8b|6eFERf!R#3G0RX2jw%L)Ye>F z+KwBR3oB~ecrtAmMWmqvHF>awUc`(tqC|dqeho9xvuNi-AuPPk|5}*2W%+n*w5$1{rq+`IFX5 zjr#Uly#-xuhX5z?cvXj#&KXy^V{Mj>FT--yxy(SWm%tek;)~r60K|D|dVulS(vG`M_4MTb6oNSE0 z&xn#L9N)J;npM7ktR((G7o|VySCZR98h|^F0D-e|6Q1(L1(TU}#ZJ>~P;yg0JLl7C zPgQn;P9bD?>)OT6HSe&y#2jk? zZkP5h48Vt~e=1aBLjVEHkzbbxwEZ7YSFlN7*-YlRDBI%4W^@GL$85Q4X8?0CPkwa^ zEFt3i(*t=^qxStn>+|*?5tmLnRVaWey!I`J3Bh3WCBHdw{?{KRU!of z<+OqxfhtBS&gzwAsJ6@a^;Muj?+TZ~{Yfn+-K-!Zu;_$>ZFxo@tCh{`OrlLHt8pr18=;(PT3U#De8>reXFgWXplR$= z`!ZV5e<0Hj11xBB2W>mol9NI2wKUU*{Dd0fl&pP>!hkG2tENeuY13o~SI@?NT*Hbh z^;_i|Tqn>n6WS*OP}ZMUur4)Bs@?86Ug^gTcoi$#xML@YzJ}MBrP;+CVg$-yJ7KA# z@O5~-AFst5SZ38!YGN7)G){tiIn~u}=sHi&e}&XEq4v9OVIhAD{cUPj<z@DOvY;`Ik^O)sjO<;EKq-fo!0jnd$eemn(a%e-I}fTt4W@U74{b9 zLiPkh;F0njigJ_~G*VksoiVXibQ#8;d~RlZPY~=G%4sid(%o`q*~Y1}?P?|y=fy^_ zf4v*G`tdH@HqVRO1u6-r3=i2d1utcEe_nSY72Q<)pqlsMeL*&6?oghY0e$>6A=|kFrn}bD)O@(|tI=Hlr*-9D~z3 z?_yoeM0dDL+f6Mck;(Q?!6yhS-ldyae;AAE1$zI7Dt8i>OndEq5})$pPJCKm^$Xg; z&C<_GnS-VBH~oGJ?jlf&u5e4mVaB4!*s59<`?Qn~1@>o?x7m zNarmOc|qA!l;`BsSpu8kaf2a-$ zzT{p`rNsd}BGZ30t*GhE3ja?s>=@S5q!;$HayBpVaNJyv5wg0P_IQB zLtA=!wuXH8#w5`R5&4$1``g^mmY`#Koi5nl#rLWhxbG998#L9_%uo@cKNP4tX}h7| z$JDz)`oo8x2xLPO>uAVeZyi$ge^6Stv?N=OP;%Tk@?J|7Z-NkoLYti(Lgg9R658s# zhNPG!lPHuQKX$yuhoAAf;-e#gpUYD|hF>r`(gMRwU+oy+!!OxK6i?*ClL0*79`rZ# zx??xFzbo~S4qD08)~-?T2i_(O-9|mhhm|QoQeIZvRV#|Kbl{)xXFvXkf4>MUcfpW0 zqRBydZ`<@TE1znn+FhD?{1n~R+p}pm+t)>1Q`Q&PQS0CFbQS)Ff4Gg$h9O(NOvc-> zX+#=#vf2C>o{?~QR^Zf=S*+kVONr(XJ>w1d!iJq2rmY3fW6Y1|_+&!(gvRxKj1+Gg z+2Y63*<42J$Y%4lY(3nLe_vEgsvRfqz$H?J$1i4yO8($X`9tRfd8Td54$T@bcmYu* zi_9_MFCEWOwBEAhBg)V>nkJh85nw^+D3;QYCV8!)UOr!P+>T9E@DPIm0`i4dc3hEMSQws@r#U1^0HR$6V& ze`DFFPw*kLTVNy3^ z7G;2VcoemX&S9KVz|s+%F3{C9f<}Sca2`J*0{0`DNOX_jEP(>n#zt_SV6pXy?gN<9 z>`-KPha=4eT(slB*n{DNR4YUie_P-gLl6}TY8Ad;@f^Ymf1(Q7#%PPj<&xq*m|9g# zg88_(Xy6$%SQ@w@oY=K%80(vkpuPDBHjZL*qO)ljF9{z(*U}@16>!-h$iFIVL%b+` z3n}TAi$>9#kQxfOyi;@)u(P{>-4_4r9;3&QTbN z;8o#a*!MX~e`fQcoTV3QoH2+6&bSbD&bS!MoH2ycopB}3az@t$0f;e@^oT-UjeG?b zO^h=Ff@4$oFg6DFj^Nq~`nATPu6L+os2Rl#3CS78tB>N1@|+cpS}!V=Jc~J^ncsd? zU`IIfipbF_NgO+&zrD3%IwswSX@~ z_))+YV^UA6ClY*+d)!Z$bIqYTPwW6f)cKV}thiOHM?~aSV^4}!&w;VWBM-rIh$}7+ zesy;Ne_y{HYa_J2y;E+~75wHfzH=BqI0k?4M_dji_|sNTxT%h@yf^r`yK@0gM1sHS zbe1iaVv*g!U%PVdg02GyM-Jn+$8fQn4*s5#NAXw5x(oj-;NJxyiYuE(#Vmq9+%zn_ z1)=a9%?07(P!O{Zjfy#mS}|`}1n(P**vGioI4OUyAWm+RWf7^|Fh&i^r)HcK23T*w>`5(E)~;Cv!$ zC$;1WfSU+`TPb}PtHYyAiYEw{r-%sb$BaDR(T973m7 ze=KnD$a8l(ZTv{SqJq~@^I9*xoy9Y{wo9t@!&Z-s5?`5#bA z2M9B)4G&NY0012p002-+0|XQR2nYxOldU8Wl7SbK-C8YwyZu11qM-Q2s!$TP8>3=_ z!~~_lLk*<0CO$Q{yVLE`{mR|l8e-&!_%DnJ8cqBG{wU+LXpG{6FZa%znKN@{?)~=t z^H%^5uq^QI__$enV|1lGpwKZk47+En8Fm!Jo-b1`3e6yLh;cS-^+F=$g)XB*QVI8B zyjHzmt(guDjkh|4K%o_7%BCI9CxMknxt6P>h7 zFncJ6((+~KTKnBYvQrJy0t?&qovn7`MQ69UwcV(HciOFbv$MDVye?2~{ARS$k+R1E z`ljuBp_e`p$W>Nf3e5kV^fdE)hm?kr!1U%gw}f*j7BGYJ0{M)kRr{<>$Av#swT_aM z0u2`hiY}!GD&l$4BZ1}0StYAyp%O0PashLg=fuC zf-PY23uaz@#B90z2@5BbBX^v`X57gxG`dC>(eI9tz=t@WJx`*}v_t?~hLaxPYmE_wDvReU%yN z4Y^z{r7q-5>ZWdu#m+QN)lE*!Jz2s)+^jGtU6Fs@guV`PS)dIxlWnPLY?T>zTxJW* z7gs#%(|>=_TgxC+sLoiDD~%)a#+6J5@_}zLPv__JROK|tw+RRV(}$+_nr@6G0jG^G zlhR{uDS7tTw&au5uYCGbw`knawI2VDVOPN68V5`)x-z-T)}*@__65ZBLb~sGVRU@* z$Y320Vi-fPWda9d1rg^Rh<*T2O9u!+{qJ}90000ild*ywlLK8hf6ZEXd{ouF|NYJ^ zcXBg8NC+@2GD47SlL#te5HVp5BmoIahef=Zxk*N5iL(UaLe*-mt=nsDD{A|!wM}d7 zW^oct743rB+EriezP#>>-B+vTeb2dfl9^-z`rbc}Pr|+ToZs(ve%tvi=j2PTJ@y0< zoh#nUbobGtJ62t_f4IvC9WvwL#Z8Mt-HYoNhZ3>ANYqG267fJR5jHWNG^3`GGBMd} zqynK{Gju4GiKP}dbsN!?S--fiClE9G0uf2$ysni-c;)$kO|Ht}cW0te45WIEz;b+= z@t#QBG?S5d4@UdVWD09xd{x6a4XXlSvw!h59%3fFGm%M#f6R@MsL527NcJ@LB#m&? zY&@Ja`ufad<0kdF$NFkFB5{qJOl6lF{YGQdi1##Z>$=Fvox8brY2`h-PeadnMFBV~p%$w+#jaU#rWFL`O2 zPNg)R>5Nmue`-|5Gz|-_gR(4%nHEf1Vtf|F%c(-AnKX-O?o?13&1NbE*|tPT854@h z5sjPa#$7wwKxi)cbeco+n7sKj8ZBUQr4ze$v`#{61=<<3NT-G5FGOqAXfaa>*6f6j z#30739BRI{y;Ma@by`Aa!7AM_u7|1%tY*P!RLkTxf3L{E$CxUs+a{WIbHr{#1mQ~Bh1jaGuCbi(q; zF}(mpjsSZVT~JErQxmu;;$|9MnDYiT+>ub8w%+XCn8?J#8;)%+spg67)gJ3G7w^1O7y}KizBkf4A&z z_g9+@Jq`ZA`q+S+T@xGVH=-G{2HWA?SRrhtLdl4&pYmdE@Lsx0@_8&5wbkm)$)quW zh&=V!-e&R?QdRshMtvh zUxL5JjDao_D<#w0Y!5G*Jwg0A`if3Z(N~#7AmE{|GX+j7NOL#Xwd0XS-;^8R_3Hcu zot~%vf{cN{zDw5}sPoW^fA~ONLMfH<(sv{`b@W{%g;b_1WxID}b!*W${eAj@g#IC7 zZX#YF?cUcJ{7);YMKI5DSoX*C6REQQW?J#a@iqDxqM6OEv~qJ25}sZCI(RAM;urKw zoqkTg0=4S3sTy0KYZ_`j^c$!&5)Ye4wspg2puAQu{f=Iey86BJf92Mx)cHpV@+Y(; ziFmUe#+h1*dCnW<_Am5T$?e~eAQZQfS;gx=5WT997i1!bJFSnT0efgdl{kH z#t0mc2(RS20mV;q4%03xU(;z+rq0q(0@X+)p4w^-c+q5`e14Dx)0~N-v}7XDFfuQr zrQ(2x-8#EuY2%g^e^opT%%b8?L1wj=OIQa9E=BxEC#*>?PeTcVL9|KJQ5_&G=G5!u zGWsGk!!woEp~k)_iaak@DDyIUA9oa;WV%;HgH|uk<~gtu&xMSMct^sn3%oo}YWOLh zkKM26A&c5s z@nd{e2`}Ykx!$G_K;s&nYh{4tH6E^?B9KW3=LV^lMkey`a%ihBGqDP^Bju@U-CQ{3 zbNF28H0L3GS`y|LoP0jhlIp@%Vv53$W%BdG&-zi=7rJm29k_pyp`Q%l6R5u`04bR*?;=isa2O za9~qLF0B=)v0p^Rj7G+8>(#X;O&Us1#D`(!)oPH*dJq6@5B;E zmK9#!$-7G6iMz4cavR>uZ<4$H0S?M2nA#BQlZ)-ce=g%%MoZ#MMXtpDx)j?80|zH% zmpo|<34vB*QC@+7vZu$0s<1ZR>M-KOe2Y~-lD9vWiKZji$bPH9YVdHk&ZZ12i)^TH z!c6&POV?}kn|>ocV1WV>oy@W+JIh@#%x2i7Es;2sfu;^27_Q&2v3Xb9&V!qFG_P;l zaBx@We})|gH*ag-;N=(!SdMbsIw8qveu6yT zf8HS@elw%1SzJU4`|x0cIx9d*<99)18MK!b4L1{YXo>wEo$uuLVogg5rlQ9j_EPI? ze@P81yz?=>y9DUyaOM|5T8~~dnlQo|zpuEb7Ne>$nx5%#GkrLbJhU?sGZQj6Gt$`y z`2G^UkI~l50k8d#Vsg-{tDZvEVr>t9h(E0J`x$M|it1ugTW+$t2yUyTypKxs2g?YN zX-?FLb%l+p!h@x%vzcxyN_&FwRu?;de>w$Ar%?CmV#XiK0=vEZasGr(F8<^UH=_+( zJicxu-k&&RHnu5A+Re1lZG^zvfW{9aFvP|On4ZfI3^pDxdJ|zQGo`Amz*8jEO@%0r z0seQB){>{jt(iQ#&WJ`kBeLk^l8pJzI7%8hqQot=&sdnIJ^6O4}78;-~>u`6TsebXl#;qx>6tPC&ciMi3k&%xoN zMk?KEHAi0ls#P?84b#xoH&8L8e~fN(R}xA1j44ji$4EcVFUUZFW_DUS(cHPNwKZ4m zzo-tc`P;|=?d#9;@ON`3rDGQu?Pe-v^qA`-J*F&izi(w|Wt6zQ7+F4bhAvJ6{QQuA zr1KB>$4stWJ2wVac^Dn42V`3Y(lUz9E=F@-iM7-A)hxdjh1DYG1V=UjyWokv@ej zNR0`$#uS`zSYv4L=9x!Af6+`T(ywmYnnNL|u-%A5izso{Ch#Q)LgYm}`yu zn9g38$V9^`4uz5?Jj&mv4#fT89JIQ&kZQGJmq(y)^~8;MLKX+A)!pJ13&k0z2E`&5 z$$v9iE_M*V@MNz4hqiVgMI>UDA=D+3K%cpA>_#ipYsBMbG^Mn<&ic^AS-Ja`Ng!?D zM-$adB6-*&YIU(xf3|J9RF(zCbY^wljao7KP+mYZ09Bw9)zZlUNmNFYsqo}Hkd})T zx>zR8VOsrva6?VVc2%AJt&1j7<|XoAJvuPH`LVj1$X&yT^TjG%tP~d%^lUqOVYRR( zRwELmqNdp=H}@6^zD8W6iwnitT(e$yv7?D*K!)I%Ua^jzf0f?09$K(3*1cjQZP!JO z*d&YNNS8;nqA)Gu!7YhI8k^ndlQ~cwl%eLr#@VWiHW@WaqKE}jcKB~i;ZBMhF{zcb zOceVj+*^tcu}wPY_S`X$eGROfz75$&>Tid5$G^0Cv1}(#+wiv z$9na=8F^wpe`#-7Q{ZK<*r$u2*zYC7db?E0vaj&wdJ1f7Ghe2QPGKPXARoxhWf^Va z>99451w$e%Er-ojnUc5g@T?>00(R$BPraV#5xo*!CPrAS!9FO68ku;g*Gx88rHize zM;wwC0;U~dmY$~D%*C9Th)X>rJmj(N1g$!c>EhE|e^^=s@<}GmZh1RlSBjvW6e*ob zMY`bZun;6NM^1G+dY(D}MTa<6&C)za@f#WhSD#v`NZ zG);9|Wp|f3ZThz~@5pO9^E01)p)1~u;A{6$^2*C2u9JUFQRG}V?_g5A1zA_zz|`o6 zPhg?2fB&!%Ndrhlu;J!C?GU zMBD8ad*Pi;Qe!``(xI?F%0QD;Rg+87g;WX-1YRvot?TX9nA{ zw5+@)OO3~XK+v~Eleuy^Lx5>%2M`;Jsr$=aK(D^uN!L5$E z&hp*0!?bsZ_MO-&$7_e^vJ-?#g{D)G4$yq6qH0=8Lfk3;WQm-k_!Jtg(P#;=Mr%g_ ze`tL-6OED%Tsei;*+2lq0r74{O)?MH#e56ib@{gnmS~y}Lh3}$hidC`JcsbxUEW)M zd6wcsbVZiZ)=%3A^#}Lw?--&Z&PV8K*W*+d3_8k>b~?+i?aa~*<#mtH+jFD0VDvUQ zx+gbs2S(m0M}p;d0!2bl(Wwe;;gej?e?az;XIWmOe2=pB|#)Ba{s`xdJ}t z5Iy=RonUHm``nMx(@e+sS)WV3f0^k?kZ#hl^tEIB5uaB64P}a%BlJ9QCF-{ZN1wy^ zx3l!UW8?#x1_S=cryb1FPqXyvCfDHTLzw@qns1QvWoxqZhm{hr5}<#!Kr3C&f6LU{ zkFxZ4iF6o9|5QkRiR2sy^=a;Lu^MfVBrUv;@m3bFX*ZQfs1gNrqt7+MuAr~vU8Q7r)Al9|7*v6f38Z8^D-%FrANuy)4=Kn9G@(*z2Gqffw6 zR~N7=i4VSJOwE}Mu~wpFd4YUC$LEx6EgGQ*gB?TcFTW$pOOA7Omg`_Vmt||(B;RtD zc2{s9%V!5yYWEU!gU=ONUb$y*^m%+#YCgB4Qj>zXotH^7yAN8kk4Vq1f2-hCL%e#J zo10v6$zb51&o#vBv%IN-TeI9|t#FdO`1HAl`I0?8XR!Pz#=zH}uY!<1*(5X^zjWz8qN&fil9tAekd<1}nH{hp0)Lb%fs^e{2ub9_I(J)-ZqM z;1GYT-si4+j7Nw*l@~1QJ1h9{T(m?qQ!$Zmrv;;QKWSDBR6qS1-LKJ88hxJV6)khH?Jw;&wCc&%l9HmV~fPS6>8b!b?nTiI>`SqkvHE;b$pgB_jAtYM> zXP%1FQ7R?(*fd#_e{y(!-mpdwstM41l^P{?|D=UdCEPhm+oe8qnKLFKa3|5304&AO zt5jo6T+E{s%2zbsAX!y;=OUS3)VoSICuunn4T@a+zXVeaU^R+=Slf2K^A$}U}9Be<%Uk-L4?W%1%ER) zr~BMZ+8|9E3tn1%5LAZwTUq{2lc$2eH_Sg#8?}NFMt_;*-;VH02(r$V*lK^O^kB>U zwX7=3f46tx5dQ=FPp$4fXzj!%O-3xwaef(u5KL5>f7E@>rV`XDK8(B~N5maIS5ry7 zj0locy`*%UN5_cC$StXO=+qk3GF zjEGW13gCGHwe>?{dRADqRB)?IBWXLmND--9k`S}TurVEM&x$#B({d~9Osmg|d5ST= z3?ve_fA(O7Sdbs1WHjM+?id#SS>nuCg;;W-h%HhWDL{=Sf577U5z!WG9}?~Oz9iUwlFI6zaNb9H zy<sdh|b{tt$^5>6?@tdH5UdEG>653tN^=R!=k%3D=x1P(X8mhY$;-D z`I^oOaRr7mV-+dm>#99jadf;;ZFAHD?Akgz_Dy=fOWW|jY;wEX{ zf06=S*VfyL8pHDGu!)s7fcTDa&@q6LDF9T>Tp@0+9TM+6fdJn}{f>8tTWNr9QqNoI z9{J=K`G?{HB#W2$uj=_Szbc=CMTvTr2(PHYbGn$Rp0mXw^;{xq)U!owav-paP2v&- z-zj#>r-L1(>N(9(rk>@FD)n6ESSz1)e~S7k%^gMM?a}yz44!-+D)d~qmHFaj(qExj zEYnJH7!~kerMQQNRCbz<%rOO=0#PA(HKKPO5RHN0#lvnpiA*L%`A`z%X4yt4k_%+U z0+lt0^`ca!4|`&k%!hJ96H7I*43nCuapq<(M%0%W%kaAt#6-&|M#eB|au`d;e=t1A z7i7`0;bqG*f&TdNyJQPw@%4&g_FvQ~^Q(J|hy=F?&6K^4J(?#$9XT;QHX&`H1SA_s zDa$W$Cl1LjOWdl`UOz3Qc}RPUkoKy;@GEB2C497Ec3A?>vy?cIVk zh3f9`zjzOxUSb}GZ$8AI=7;_VP)i30OlKHPf*1e*?J|?0Bpj3Db1{Dld|PD||9?r_ zdz)sjmTt=!qm&K0u4%_$Wdsq$DA9Is~PQvmWHx*90ahvODJ7HTHo0|hxCL9~E zV;5wy$xMBu&q`$MruxDDaMBtKJHlgiZ>tq=J(jfTHO2FN*+ha1nE@+&6j3|X@1$%y z?WFp-y4_A^D2wZBnvZT?6OP;4>)&faDFnLQY&vFda1yq{VmE)?-_oD9;t9KDN7@=3 zw9_r^sf=eO5=)OVP^K_1?z?G1SjQq zYZcNB6ZM`7E2=jW%eSoK@^gZihw4g{qc(^Ds^n`y5W)OcD2Q2@Enf!*F$Z(y>ktKh zgPg0up#d1EQz)bB>A!;-mUm2!A*~CR8ew3m!mNJVJKKMfK<1-0w|KB@dnv-T1k7d26=Ka3!_<>wb0YzgH&80-0()iH=Zqs zB8#K2N~9f4Xv}4Z= z;zX>K-IIT)u9FciL9EL!ouV*@#;)tlxQVQ1pKW;qL9EYPcdEjo=~KeMX}pkDEM{kz zkt>;#{S7l_(3@E?!{Ma`*d~RBzH7(Z0yrIKC>;3~4;eU<+U5yQcawC$S(1>QID0~w z=(;H5*+~N%={Y;idtG}#?X#(+M_p|zNewn(b0vSea1QTypXDU7Y5Pq2!RlwqR8N&K z??6AT`mWT73h9 zbbo)JE5+OPVgm|?PMOxlQY6-LK10j7MV zogDNo>fi~+qUZ@tDQk4ZyYZd?-i7y)G{F@SPp8dmSiWU)&3GT)FY-RXOEPKCzz2(= z)U4N~)0UQL;6njiCPl<=#p9D=S*T!gC9i+LhlTD+CeTC$4SbZrbUd3eaG8PgCz#M) zSf_Fy!^f*|6|Sb0Z`?Os%DQ?+YDYf6i9iq**nb6tPyPUxenG>c<=mTc(;2z}U;4sVT zc)-Y@g+s=vJ7e}>{?6T*??3rcJeq&E<8H1sXY}PWaW9dy&4Rt1)uw*>wo|-JL3{`I z3zzTG8%3>7$@cZxX*<5rwsht82J7aVbeY7p#UDl z4;0EbZ`u%EW8y~&jpKwRJf`hxj|8wEKbDeq;8+rQcwNf%>iVQ|)$vXZ z)UlE==YPXXGexEsQ_a9{8L5obXKzlkkS=MMRO2Q`=^6Y!fZyTSNwY+;Xv{cEJSR8r zj|!^U#GmO7Iw|9(B2@A(()WLCuh5=?_^Y_**Z3P%b2H5;PB|w2&apvKF6~l(k2Um& zw=~R9@;~r$fPL_v#hRZlV{#+tzJDwDHg_H9h$VYG`5(MmiC6GniuT+NcL#e9Ulik_ zOR1+6{Xe`Oz=as2Av>H@+})8e72gOZ$7|1WQY`5Qms-&_V5Ph43$uTADyFN7@~bkQ zSLO6iuahbS(Nu=Q!tqmdi3~W!2~kx_Rt@kKW2!0^vSU}THq|T|FU{9VxhaSG>YJ

SdO`o?U^bCPz6Ehh!k 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


Xdyd*p!l1Y@nCA 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37f78a6..c61a118 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index adff685..739907d 100755 --- a/gradlew +++ b/gradlew @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. From 558399039c37c10fc451601d27e1d0a07977e5a4 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Thu, 26 Mar 2026 11:41:58 +0100 Subject: [PATCH 187/449] chore: deprecation cycle (#615) Remove all deprecation with error level ## Motivation and Context next minor release ## How Has This Been Tested? - ## Breaking Changes Removing it is a binary-incompatible change ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- kotlin-sdk-core/api/kotlin-sdk-core.api | 495 ---- .../kotlin/sdk/shared/Protocol.kt | 19 - .../modelcontextprotocol/kotlin/sdk/types.kt | 2250 ----------------- .../kotlin/sdk/types.util.kt | 42 - .../kotlin/sdk/types/common.kt | 9 - .../kotlin/sdk/types/completion.kt | 12 - .../kotlin/sdk/types/prompts.kt | 37 - .../kotlin/sdk/types/resources.kt | 60 - .../kotlin/sdk/types/roots.kt | 9 - .../kotlin/sdk/types/tools.kt | 20 - kotlin-sdk-server/api/kotlin-sdk-server.api | 2 - .../kotlin/sdk/server/Server.kt | 32 - 12 files changed, 2987 deletions(-) delete mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt delete mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.util.kt diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index 7e1b98c..e7cd0b8 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -1,144 +1,6 @@ -public final class io/modelcontextprotocol/kotlin/sdk/CallToolResult { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/CallToolResult; -} - -public abstract interface class io/modelcontextprotocol/kotlin/sdk/CallToolResultBase { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/CallToolResultBase$Companion; - public abstract fun getContent ()Ljava/util/List; - public abstract fun getStructuredContent ()Lkotlinx/serialization/json/JsonObject; - public fun isError ()Ljava/lang/Boolean; -} - -public final class io/modelcontextprotocol/kotlin/sdk/CallToolResultBase$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/CallToolResultBase$DefaultImpls { - public static fun isError (Lio/modelcontextprotocol/kotlin/sdk/CallToolResultBase;)Ljava/lang/Boolean; -} - -public final class io/modelcontextprotocol/kotlin/sdk/CancelledNotification { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/CancelledNotification; - public final fun Params (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotificationParams; - public static synthetic fun Params$default (Lio/modelcontextprotocol/kotlin/sdk/CancelledNotification;Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotificationParams; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ClientCapabilities { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities; - public final fun Roots (Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots; - public static synthetic fun Roots$default (Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots; -} - -public final class io/modelcontextprotocol/kotlin/sdk/CompatibilityCallToolResult { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/CompatibilityCallToolResult$Companion; - public fun (Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getContent ()Ljava/util/List; - public final fun getStructuredContent ()Lkotlinx/serialization/json/JsonObject; - public final fun getToolResult ()Lkotlinx/serialization/json/JsonObject; - public final fun get_meta ()Lkotlinx/serialization/json/JsonObject; - public final fun isError ()Ljava/lang/Boolean; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/CompatibilityCallToolResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/CompatibilityCallToolResult$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/CompatibilityCallToolResult; - 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/CompatibilityCallToolResult;)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/CompatibilityCallToolResult$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/CompleteRequest { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/CompleteRequest; - public final fun Argument (Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Argument; -} - -public final class io/modelcontextprotocol/kotlin/sdk/CompleteResult { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/CompleteResult; - public final fun Completion (Ljava/util/List;Ljava/lang/Integer;Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult$Completion; - public static synthetic fun Completion$default (Lio/modelcontextprotocol/kotlin/sdk/CompleteResult;Ljava/util/List;Ljava/lang/Integer;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult$Completion; -} - -public final class io/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest; - public final fun RequestedSchema (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema; - public static synthetic fun RequestedSchema$default (Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationRequest;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema; -} - -public final class io/modelcontextprotocol/kotlin/sdk/CreateElicitationResult { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationResult; -} - -public final class io/modelcontextprotocol/kotlin/sdk/CreateElicitationResult$Action { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/CreateElicitationResult$Action; - public final fun getAccept ()Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Action; - public final fun getCancel ()Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Action; - public final fun getDecline ()Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Action; -} - -public final class io/modelcontextprotocol/kotlin/sdk/CreateMessageRequest { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/CreateMessageRequest; - public final fun getIncludeContext ()Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext$Companion; -} - -public abstract interface class io/modelcontextprotocol/kotlin/sdk/ErrorCode { - public abstract fun getCode ()I -} - -public final class io/modelcontextprotocol/kotlin/sdk/ErrorCode$Defined { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ErrorCode$Defined; - public final fun getConnectionClosed ()I - public final fun getInternalError ()I - public final fun getInvalidParams ()I - public final fun getInvalidRequest ()I - public final fun getMethodNotFound ()I - public final fun getParseError ()I - public final fun getRequestTimeout ()I -} - -public final class io/modelcontextprotocol/kotlin/sdk/ErrorCode$Unknown : io/modelcontextprotocol/kotlin/sdk/ErrorCode { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ErrorCode$Unknown$Companion; - public fun (I)V - public final fun component1 ()I - public final fun copy (I)Lio/modelcontextprotocol/kotlin/sdk/ErrorCode$Unknown; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/ErrorCode$Unknown;IILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/ErrorCode$Unknown; - public fun equals (Ljava/lang/Object;)Z - public fun getCode ()I - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/ErrorCode$Unknown$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ErrorCode$Unknown$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/ErrorCode$Unknown; - 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/ErrorCode$Unknown;)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/ErrorCode$Unknown$Companion { - 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; - public static synthetic fun Params$default (Lio/modelcontextprotocol/kotlin/sdk/InitializedNotification;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; -} - public abstract interface annotation class io/modelcontextprotocol/kotlin/sdk/InternalMcpApi : java/lang/annotation/Annotation { } @@ -146,267 +8,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/LibVersionKt { public static final field LIB_VERSION Ljava/lang/String; } -public final class io/modelcontextprotocol/kotlin/sdk/LoggingLevel { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/LoggingLevel; - public final fun getAlert ()Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; - public final fun getCritical ()Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; - public final fun getDebug ()Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; - public final fun getEmergency ()Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; - public final fun getError ()Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; - public final fun getInfo ()Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; - public final fun getNotice ()Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; - public final fun getWarning ()Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; -} - -public final class io/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification; - public final fun Params (Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel;Lkotlinx/serialization/json/JsonElement;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotificationParams; - public static synthetic fun Params$default (Lio/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification;Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel;Lkotlinx/serialization/json/JsonElement;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotificationParams; - public final fun SetLevelRequest (Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequest; -} - -public abstract interface class io/modelcontextprotocol/kotlin/sdk/Method { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/Method$Companion; - public abstract fun getValue ()Ljava/lang/String; -} - -public final class io/modelcontextprotocol/kotlin/sdk/Method$Companion { - public final fun Custom (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/Method$Custom; -} - -public final class io/modelcontextprotocol/kotlin/sdk/Method$Defined { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/Method$Defined; - public final fun getCompletionComplete ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; - public final fun getElicitationCreate ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; - public final fun getInitialize ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; - public final fun getLoggingSetLevel ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; - public final fun getNotificationsCancelled ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; - public final fun getNotificationsInitialized ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; - public final fun getNotificationsMessage ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; - public final fun getNotificationsProgress ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; - public final fun getNotificationsPromptsListChanged ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; - public final fun getNotificationsResourcesListChanged ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; - public final fun getNotificationsResourcesUpdated ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; - public final fun getNotificationsRootsListChanged ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; - public final fun getNotificationsToolsListChanged ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; - public final fun getPing ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; - public final fun getPromptsGet ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; - public final fun getPromptsList ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; - public final fun getResourcesList ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; - public final fun getResourcesRead ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; - public final fun getResourcesSubscribe ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; - public final fun getResourcesTemplatesList ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; - public final fun getResourcesUnsubscribe ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; - public final fun getRootsList ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; - public final fun getSamplingCreateMessage ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; - public final fun getToolsCall ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; - public final fun getToolsList ()Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; -} - -public abstract interface class io/modelcontextprotocol/kotlin/sdk/ProgressBase { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ProgressBase$Companion; - public abstract fun getMessage ()Ljava/lang/String; - public abstract fun getProgress ()D - public abstract fun getTotal ()Ljava/lang/Double; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ProgressBase$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ProgressNotification { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ProgressNotification; - public final fun Params (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;DLjava/lang/Double;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ProgressNotificationParams; - public static synthetic fun Params$default (Lio/modelcontextprotocol/kotlin/sdk/ProgressNotification;Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;DLjava/lang/Double;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ProgressNotificationParams; -} - -public final class io/modelcontextprotocol/kotlin/sdk/PromptListChangedNotification { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/PromptListChangedNotification; - public final fun Params (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; - public static synthetic fun Params$default (Lio/modelcontextprotocol/kotlin/sdk/PromptListChangedNotification;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; -} - -public final class io/modelcontextprotocol/kotlin/sdk/RequestId { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/RequestId; - public final fun NumberId-Nt6Vbyc (J)J - public final fun StringId-QNUQp7g (Ljava/lang/String;)Ljava/lang/String; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ResourceListChangedNotification { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ResourceListChangedNotification; - public final fun Params (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; - public static synthetic fun Params$default (Lio/modelcontextprotocol/kotlin/sdk/ResourceListChangedNotification;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ResourceUpdatedNotification { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ResourceUpdatedNotification; - public final fun Params (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotificationParams; - public static synthetic fun Params$default (Lio/modelcontextprotocol/kotlin/sdk/ResourceUpdatedNotification;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotificationParams; -} - -public final class io/modelcontextprotocol/kotlin/sdk/Role { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/Role; - public final fun getAssistant ()Lio/modelcontextprotocol/kotlin/sdk/types/Role; - public final fun getUser ()Lio/modelcontextprotocol/kotlin/sdk/types/Role; -} - -public final class io/modelcontextprotocol/kotlin/sdk/RootsListChangedNotification { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/RootsListChangedNotification; - public final fun Params (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; - public static synthetic fun Params$default (Lio/modelcontextprotocol/kotlin/sdk/RootsListChangedNotification;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ServerCapabilities { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities; - public final fun Prompts (Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts; - public static synthetic fun Prompts$default (Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts; - public final fun Resources (Ljava/lang/Boolean;Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources; - public static synthetic fun Resources$default (Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities;Ljava/lang/Boolean;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources; - public final fun Tools (Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools; - public static synthetic fun Tools$default (Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools; -} - -public final class io/modelcontextprotocol/kotlin/sdk/Tool { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/Tool; - public final fun Input (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema; - public static synthetic fun Input$default (Lio/modelcontextprotocol/kotlin/sdk/Tool;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema; - public final fun Output (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema; - public static synthetic fun Output$default (Lio/modelcontextprotocol/kotlin/sdk/Tool;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema; -} - -public final class io/modelcontextprotocol/kotlin/sdk/ToolListChangedNotification { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/ToolListChangedNotification; - public final fun Params (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; - public static synthetic fun Params$default (Lio/modelcontextprotocol/kotlin/sdk/ToolListChangedNotification;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; -} - -public final class io/modelcontextprotocol/kotlin/sdk/TypesKt { - public static final field JSONRPC_VERSION Ljava/lang/String; - public static final field LATEST_PROTOCOL_VERSION Ljava/lang/String; - public static final fun CallToolResult (Ljava/util/List;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult; - public static synthetic fun CallToolResult$default (Ljava/util/List;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult; - public static final fun CancelledNotification (Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotification; - public static final fun ClientCapabilities (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities; - public static synthetic fun ClientCapabilities$default (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities; - public static final fun CompleteRequest (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequest; - public static final fun CompleteResult (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult$Completion;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult; - public static synthetic fun CompleteResult$default (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult$Completion;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult; - public static final fun CreateElicitationRequest (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequest; - public static final fun CreateElicitationResult (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Action;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult; - public static synthetic fun CreateElicitationResult$default (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Action;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult; - public static final fun CreateMessageRequest (Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest; - public static final fun InitializedNotification (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/types/InitializedNotification; - public static synthetic fun InitializedNotification$default (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/InitializedNotification; - public static final fun LoggingMessageNotification (Lio/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotification; - public static final fun ProgressNotification (Lio/modelcontextprotocol/kotlin/sdk/types/ProgressNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ProgressNotification; - public static final fun PromptListChangedNotification (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/types/PromptListChangedNotification; - public static final fun ResourceListChangedNotification (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ResourceListChangedNotification; - public static synthetic fun ResourceListChangedNotification$default (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ResourceListChangedNotification; - public static final fun ResourceUpdatedNotification (Lio/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotification; - public static final fun RootsListChangedNotification (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/types/RootsListChangedNotification; - public static synthetic fun RootsListChangedNotification$default (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/RootsListChangedNotification; - public static final fun ServerCapabilities (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities; - public static synthetic fun ServerCapabilities$default (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities; - public static final fun Tool (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/Tool; - public static synthetic fun Tool$default (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/Tool; - public static final fun ToolListChangedNotification (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolListChangedNotification; - public static final fun getSUPPORTED_PROTOCOL_VERSIONS ()Ljava/util/List; - public static final fun toJSON (Lio/modelcontextprotocol/kotlin/sdk/types/Notification;)Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCNotification; - public static final fun toJSON (Lio/modelcontextprotocol/kotlin/sdk/types/Request;)Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCRequest; -} - -public final class io/modelcontextprotocol/kotlin/sdk/Types_utilKt { - public static final fun error (Lio/modelcontextprotocol/kotlin/sdk/CallToolResult;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult; - public static synthetic fun error$default (Lio/modelcontextprotocol/kotlin/sdk/CallToolResult;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult; - public static final fun getEmptyJsonObject ()Lkotlinx/serialization/json/JsonObject; - public static final fun ok (Lio/modelcontextprotocol/kotlin/sdk/CallToolResult;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult; - public static synthetic fun ok$default (Lio/modelcontextprotocol/kotlin/sdk/CallToolResult;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult; -} - -public final class io/modelcontextprotocol/kotlin/sdk/UnknownContent { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/UnknownContent$Companion; - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/UnknownContent; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/UnknownContent;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/UnknownContent; - public fun equals (Ljava/lang/Object;)Z - public final fun getType ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/UnknownContent$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/UnknownContent$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/UnknownContent; - 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/UnknownContent;)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/UnknownContent$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/UnknownMethodRequestOrNotification { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/UnknownMethodRequestOrNotification$Companion; - public fun (Lio/modelcontextprotocol/kotlin/sdk/Method;Lio/modelcontextprotocol/kotlin/sdk/types/NotificationParams;)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/Method;Lio/modelcontextprotocol/kotlin/sdk/types/NotificationParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/Method; - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/NotificationParams; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/Method;Lio/modelcontextprotocol/kotlin/sdk/types/NotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/UnknownMethodRequestOrNotification; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/UnknownMethodRequestOrNotification;Lio/modelcontextprotocol/kotlin/sdk/Method;Lio/modelcontextprotocol/kotlin/sdk/types/NotificationParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/UnknownMethodRequestOrNotification; - public fun equals (Ljava/lang/Object;)Z - public final fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/Method; - public final 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/UnknownMethodRequestOrNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/UnknownMethodRequestOrNotification$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/UnknownMethodRequestOrNotification; - 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/UnknownMethodRequestOrNotification;)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/UnknownMethodRequestOrNotification$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class io/modelcontextprotocol/kotlin/sdk/UnknownReference { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/UnknownReference$Companion; - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/UnknownReference; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/UnknownReference;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/UnknownReference; - public fun equals (Ljava/lang/Object;)Z - public final fun getType ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class io/modelcontextprotocol/kotlin/sdk/UnknownReference$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/UnknownReference$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/UnknownReference; - 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/UnknownReference;)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/UnknownReference$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - public final class io/modelcontextprotocol/kotlin/sdk/internal/Utils_jvmKt { public static final fun getIODispatcher ()Lkotlinx/coroutines/CoroutineDispatcher; } @@ -483,7 +84,6 @@ public abstract class io/modelcontextprotocol/kotlin/sdk/shared/Protocol { public final class io/modelcontextprotocol/kotlin/sdk/shared/ProtocolKt { public static final field IMPLEMENTATION_NAME Ljava/lang/String; public static final fun getDEFAULT_REQUEST_TIMEOUT ()J - public static final fun getMcpJson ()Lkotlinx/serialization/json/Json; } public class io/modelcontextprotocol/kotlin/sdk/shared/ProtocolOptions { @@ -617,7 +217,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/AudioContent : io/mo public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public final fun getMimeType ()Ljava/lang/String; public fun getType ()Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -656,7 +255,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/BaseNotificationPara public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams; public fun equals (Ljava/lang/Object;)Z public fun getMeta ()Lkotlinx/serialization/json/JsonObject; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -719,7 +317,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/BlobResourceContents public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public fun getMimeType ()Ljava/lang/String; public fun getUri ()Ljava/lang/String; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -827,7 +424,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CallToolResult : io/ public final fun getContent ()Ljava/util/List; public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public final fun getStructuredContent ()Lkotlinx/serialization/json/JsonObject; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public final fun isError ()Ljava/lang/Boolean; public fun toString ()Ljava/lang/String; @@ -954,7 +550,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CancelledNotificatio public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public final fun getReason ()Ljava/lang/String; public final fun getRequestId ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestId; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -1077,10 +672,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ClientResult$Compani public final fun serializer ()Lkotlinx/serialization/KSerializer; } -public final class io/modelcontextprotocol/kotlin/sdk/types/ClientResult$DefaultImpls { - public static fun get_meta (Lio/modelcontextprotocol/kotlin/sdk/types/ClientResult;)Lkotlinx/serialization/json/JsonObject; -} - public final class io/modelcontextprotocol/kotlin/sdk/types/CommonKt { public static final field DEFAULT_NEGOTIATED_PROTOCOL_VERSION Ljava/lang/String; public static final field LATEST_PROTOCOL_VERSION Ljava/lang/String; @@ -1091,8 +682,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CommonKt { public final class io/modelcontextprotocol/kotlin/sdk/types/CompleteRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequest$Companion; - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Argument;Lio/modelcontextprotocol/kotlin/sdk/types/Reference;Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Context;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Argument;Lio/modelcontextprotocol/kotlin/sdk/types/Reference;Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams$Context;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public fun (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams; public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequest; @@ -1237,7 +826,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CompleteResult : io/ public fun equals (Ljava/lang/Object;)Z public final fun getCompletion ()Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult$Completion; public fun getMeta ()Lkotlinx/serialization/json/JsonObject; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -1302,10 +890,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ContentBlock$Compani public final fun serializer ()Lkotlinx/serialization/KSerializer; } -public final class io/modelcontextprotocol/kotlin/sdk/types/ContentBlock$DefaultImpls { - public static fun get_meta (Lio/modelcontextprotocol/kotlin/sdk/types/ContentBlock;)Lkotlinx/serialization/json/JsonObject; -} - public final class io/modelcontextprotocol/kotlin/sdk/types/ContentTypes : java/lang/Enum { public static final field AUDIO Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes$Companion; @@ -1444,7 +1028,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageResult public final fun getModel ()Ljava/lang/String; public final fun getRole ()Lio/modelcontextprotocol/kotlin/sdk/types/Role; public final fun getStopReason-6olV-UY ()Ljava/lang/String; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -1475,7 +1058,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CreateTaskResult : i public fun equals (Ljava/lang/Object;)Z public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public final fun getTask ()Lio/modelcontextprotocol/kotlin/sdk/types/Task; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -1679,7 +1261,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitResult : io/mo public final fun getAction ()Lio/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Action; public final fun getContent ()Lkotlinx/serialization/json/JsonObject; public fun getMeta ()Lkotlinx/serialization/json/JsonObject; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -1731,7 +1312,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/EmbeddedResource : i public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public final fun getResource ()Lio/modelcontextprotocol/kotlin/sdk/types/ResourceContents; public fun getType ()Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -1761,7 +1341,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/EmptyResult : io/mod public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/EmptyResult;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/EmptyResult; public fun equals (Ljava/lang/Object;)Z public fun getMeta ()Lkotlinx/serialization/json/JsonObject; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -1784,8 +1363,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/EmptyResult$Companio public final class io/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest$Companion; public fun (Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequestParams;)V - public synthetic fun (Ljava/lang/String;Ljava/util/Map;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun (Ljava/lang/String;Ljava/util/Map;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequestParams; public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest;Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest; @@ -1870,7 +1447,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/GetPromptResult : io public final fun getDescription ()Ljava/lang/String; public final fun getMessages ()Ljava/util/List; public fun getMeta ()Lkotlinx/serialization/json/JsonObject; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -1962,8 +1538,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/GetTaskPayloadResult public final fun getJson ()Lkotlinx/serialization/json/JsonObject; public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public static fun getMeta-impl (Lkotlinx/serialization/json/JsonObject;)Lkotlinx/serialization/json/JsonObject; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; - public static fun get_meta-impl (Lkotlinx/serialization/json/JsonObject;)Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public static fun hashCode-impl (Lkotlinx/serialization/json/JsonObject;)I public fun toString ()Ljava/lang/String; @@ -2070,7 +1644,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/GetTaskResult : io/m public fun getStatusMessage ()Ljava/lang/String; public fun getTaskId ()Ljava/lang/String; public fun getTtl ()Ljava/lang/Long; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -2153,7 +1726,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ImageContent : io/mo public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public final fun getMimeType ()Ljava/lang/String; public fun getType ()Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -2329,7 +1901,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/InitializeResult : i public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public final fun getProtocolVersion ()Ljava/lang/String; public final fun getServerInfo ()Lio/modelcontextprotocol/kotlin/sdk/types/Implementation; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -2553,8 +2124,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest : public fun ()V public fun (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;)V public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams; public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest;Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest; @@ -2602,7 +2171,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListPromptsResult : public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public fun getNextCursor ()Ljava/lang/String; public final fun getPrompts ()Ljava/util/List; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -2627,8 +2195,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplate public fun ()V public fun (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;)V public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams; public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesRequest; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesRequest;Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesRequest; @@ -2676,7 +2242,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplate public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public fun getNextCursor ()Ljava/lang/String; public final fun getResourceTemplates ()Ljava/util/List; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -2701,8 +2266,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest public fun ()V public fun (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;)V public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams; public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest;Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest; @@ -2750,7 +2313,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourcesResult public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public fun getNextCursor ()Ljava/lang/String; public final fun getResources ()Ljava/util/List; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -2775,7 +2337,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest : i public fun ()V public fun (Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams;)V public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun (Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams; public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest;Lio/modelcontextprotocol/kotlin/sdk/types/BaseRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest; @@ -2820,7 +2381,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListRootsResult : io public fun equals (Ljava/lang/Object;)Z public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public final fun getRoots ()Ljava/util/List; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -2886,7 +2446,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListTasksResult : io public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public fun getNextCursor ()Ljava/lang/String; public final fun getTasks ()Ljava/util/List; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -2911,8 +2470,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest : i public fun ()V public fun (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;)V public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams; public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest;Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest; @@ -2960,7 +2517,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListToolsResult : io public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public fun getNextCursor ()Ljava/lang/String; public final fun getTools ()Ljava/util/List; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -3043,7 +2599,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotifi public final fun getLevel ()Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; public final fun getLogger ()Ljava/lang/String; public fun getMeta ()Lkotlinx/serialization/json/JsonObject; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -3088,10 +2643,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/MediaContent$Compani public final fun serializer ()Lkotlinx/serialization/KSerializer; } -public final class io/modelcontextprotocol/kotlin/sdk/types/MediaContent$DefaultImpls { - public static fun get_meta (Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;)Lkotlinx/serialization/json/JsonObject; -} - public abstract class io/modelcontextprotocol/kotlin/sdk/types/MediaContentBuilder { public fun ()V public final fun annotations (Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;)V @@ -3263,10 +2814,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/NotificationParams$C public final fun serializer ()Lkotlinx/serialization/KSerializer; } -public final class io/modelcontextprotocol/kotlin/sdk/types/NotificationParams$DefaultImpls { - public static fun get_meta (Lio/modelcontextprotocol/kotlin/sdk/types/NotificationParams;)Lkotlinx/serialization/json/JsonObject; -} - public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequest : io/modelcontextprotocol/kotlin/sdk/types/Request { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequest$Companion; public fun getCursor ()Ljava/lang/String; @@ -3328,10 +2875,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/PaginatedResult$Comp public final fun serializer ()Lkotlinx/serialization/KSerializer; } -public final class io/modelcontextprotocol/kotlin/sdk/types/PaginatedResult$DefaultImpls { - public static fun get_meta (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedResult;)Lkotlinx/serialization/json/JsonObject; -} - public final class io/modelcontextprotocol/kotlin/sdk/types/PingRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest, io/modelcontextprotocol/kotlin/sdk/types/ServerRequest { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/PingRequest$Companion; public fun ()V @@ -3444,7 +2987,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ProgressNotification public final fun getProgress ()D public final fun getProgressToken ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestId; public final fun getTotal ()Ljava/lang/Double; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -3483,7 +3025,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/Prompt : io/modelcon public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public final fun getName ()Ljava/lang/String; public final fun getTitle ()Ljava/lang/String; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -3680,8 +3221,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/RPCError$ErrorCode { public final class io/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest$Companion; public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequestParams;)V - public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequestParams; public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest;Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest; @@ -3759,7 +3298,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ReadResourceResult : public fun equals (Ljava/lang/Object;)Z public final fun getContents ()Ljava/util/List; public fun getMeta ()Lkotlinx/serialization/json/JsonObject; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -3977,10 +3515,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/RequestResult$Compan public final fun serializer ()Lkotlinx/serialization/KSerializer; } -public final class io/modelcontextprotocol/kotlin/sdk/types/RequestResult$DefaultImpls { - public static fun get_meta (Lio/modelcontextprotocol/kotlin/sdk/types/RequestResult;)Lkotlinx/serialization/json/JsonObject; -} - public final class io/modelcontextprotocol/kotlin/sdk/types/Resource : io/modelcontextprotocol/kotlin/sdk/types/ResourceLike { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Resource$Companion; public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)V @@ -4006,7 +3540,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/Resource : io/modelc public final fun getSize ()Ljava/lang/Long; public final fun getTitle ()Ljava/lang/String; public final fun getUri ()Ljava/lang/String; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -4036,10 +3569,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ResourceContents$Com public final fun serializer ()Lkotlinx/serialization/KSerializer; } -public final class io/modelcontextprotocol/kotlin/sdk/types/ResourceContents$DefaultImpls { - public static fun get_meta (Lio/modelcontextprotocol/kotlin/sdk/types/ResourceContents;)Lkotlinx/serialization/json/JsonObject; -} - public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/ResourceLike : io/modelcontextprotocol/kotlin/sdk/types/WithMeta { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ResourceLike$Companion; } @@ -4048,10 +3577,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ResourceLike$Compani public final fun serializer ()Lkotlinx/serialization/KSerializer; } -public final class io/modelcontextprotocol/kotlin/sdk/types/ResourceLike$DefaultImpls { - public static fun get_meta (Lio/modelcontextprotocol/kotlin/sdk/types/ResourceLike;)Lkotlinx/serialization/json/JsonObject; -} - public final class io/modelcontextprotocol/kotlin/sdk/types/ResourceLink : io/modelcontextprotocol/kotlin/sdk/types/ContentBlock, io/modelcontextprotocol/kotlin/sdk/types/ResourceLike { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ResourceLink$Companion; public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Lkotlinx/serialization/json/JsonObject;)V @@ -4078,7 +3603,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ResourceLink : io/mo public final fun getTitle ()Ljava/lang/String; public fun getType ()Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; public final fun getUri ()Ljava/lang/String; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -4152,7 +3676,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ResourceTemplate : i public final fun getName ()Ljava/lang/String; public final fun getTitle ()Ljava/lang/String; public final fun getUriTemplate ()Ljava/lang/String; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -4240,7 +3763,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotif public fun equals (Ljava/lang/Object;)Z public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public final fun getUri ()Ljava/lang/String; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -4294,7 +3816,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/Root : io/modelconte public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public final fun getName ()Ljava/lang/String; public final fun getUri ()Ljava/lang/String; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -4551,10 +4072,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ServerResult$Compani public final fun serializer ()Lkotlinx/serialization/KSerializer; } -public final class io/modelcontextprotocol/kotlin/sdk/types/ServerResult$DefaultImpls { - public static fun get_meta (Lio/modelcontextprotocol/kotlin/sdk/types/ServerResult;)Lkotlinx/serialization/json/JsonObject; -} - public final class io/modelcontextprotocol/kotlin/sdk/types/SetLevelRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequest$Companion; public fun (Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequestParams;)V @@ -4660,8 +4177,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/StopReason$Companion public final class io/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest$Companion; public fun (Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequestParams;)V - public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequestParams; public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest; public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest;Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest; @@ -4877,7 +4392,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/TaskStatusNotificati public fun getStatusMessage ()Ljava/lang/String; public fun getTaskId ()Ljava/lang/String; public fun getTtl ()Ljava/lang/Long; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -4929,7 +4443,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/TextContent : io/mod public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public final fun getText ()Ljava/lang/String; public fun getType ()Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -4971,7 +4484,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/TextResourceContents public fun getMimeType ()Ljava/lang/String; public final fun getText ()Ljava/lang/String; public fun getUri ()Ljava/lang/String; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -5016,7 +4528,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/Tool : io/modelconte public final fun getName ()Ljava/lang/String; public final fun getOutputSchema ()Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema; public final fun getTitle ()Ljava/lang/String; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -5192,7 +4703,6 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/UnknownResourceConte public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public fun getMimeType ()Ljava/lang/String; public fun getUri ()Ljava/lang/String; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -5286,17 +4796,12 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequestPa public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/WithMeta { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/WithMeta$Companion; public abstract fun getMeta ()Lkotlinx/serialization/json/JsonObject; - public fun get_meta ()Lkotlinx/serialization/json/JsonObject; } public final class io/modelcontextprotocol/kotlin/sdk/types/WithMeta$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } -public final class io/modelcontextprotocol/kotlin/sdk/types/WithMeta$DefaultImpls { - public static fun get_meta (Lio/modelcontextprotocol/kotlin/sdk/types/WithMeta;)Lkotlinx/serialization/json/JsonObject; -} - public final class io/modelcontextprotocol/kotlin/sdk/utils/MatchResult { public fun (Ljava/util/Map;I)V public fun equals (Ljava/lang/Object;)Z diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt index 0e3db35..20739e0 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt @@ -33,9 +33,6 @@ import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Deferred import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.withTimeout -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.json.ClassDiscriminatorMode -import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.encodeToJsonElement @@ -52,22 +49,6 @@ public const val IMPLEMENTATION_NAME: String = "mcp-ktor" */ public typealias ProgressCallback = (Progress) -> Unit -@OptIn(ExperimentalSerializationApi::class) -@Deprecated( - message = "Use McpJson instead", - replaceWith = ReplaceWith("McpJson", "io.modelcontextprotocol.kotlin.sdk.types.McpJson"), - level = DeprecationLevel.ERROR, -) -public val McpJson: Json by lazy { - Json { - ignoreUnknownKeys = true - encodeDefaults = true - isLenient = true - classDiscriminatorMode = ClassDiscriminatorMode.NONE - explicitNulls = false - } -} - /** * Additional initialization options. */ diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt deleted file mode 100644 index 3b2b2d2..0000000 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt +++ /dev/null @@ -1,2250 +0,0 @@ -@file:Suppress("FunctionName", "TooManyFunctions") - -package io.modelcontextprotocol.kotlin.sdk - -import io.modelcontextprotocol.kotlin.sdk.types.ContentBlock -import io.modelcontextprotocol.kotlin.sdk.types.fromJSON -import io.modelcontextprotocol.kotlin.sdk.types.toJSON -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonObject -import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities.Roots as TypesRoots - -@Deprecated( - message = "Use `LATEST_PROTOCOL_VERSION` instead", - replaceWith = ReplaceWith( - "LATEST_PROTOCOL_VERSION", - "io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION", - ), - level = DeprecationLevel.ERROR, -) -public const val LATEST_PROTOCOL_VERSION: String = io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION - -@Deprecated( - message = "Use `SUPPORTED_PROTOCOL_VERSIONS` instead", - replaceWith = ReplaceWith( - "SUPPORTED_PROTOCOL_VERSIONS", - "io.modelcontextprotocol.kotlin.sdk.types.SUPPORTED_PROTOCOL_VERSIONS", - ), - level = DeprecationLevel.ERROR, -) -public val SUPPORTED_PROTOCOL_VERSIONS: List = - io.modelcontextprotocol.kotlin.sdk.types.SUPPORTED_PROTOCOL_VERSIONS - -@Deprecated( - message = "Use `JSONRPC_VERSION` instead", - replaceWith = ReplaceWith("JSONRPC_VERSION", "io.modelcontextprotocol.kotlin.sdk.types.JSONRPC_VERSION"), - level = DeprecationLevel.ERROR, -) -public const val JSONRPC_VERSION: String = io.modelcontextprotocol.kotlin.sdk.types.JSONRPC_VERSION - -/** - * A progress token, used to associate progress notifications with the original request. - * Stores message ID. - */ -@Deprecated( - message = "Use `ProgressToken` instead", - replaceWith = ReplaceWith("ProgressToken", "io.modelcontextprotocol.kotlin.sdk.types.ProgressToken"), - level = DeprecationLevel.ERROR, -) -public typealias ProgressToken = io.modelcontextprotocol.kotlin.sdk.types.ProgressToken - -/** - * An opaque token used to represent a cursor for pagination. - */ -@Deprecated( - message = "This alias will be removed. Use String directly instead.", - replaceWith = ReplaceWith("String"), - level = DeprecationLevel.ERROR, -) -public typealias Cursor = String - -/** - * Represents an entity that includes additional metadata in its responses. - */ -@Deprecated( - message = "Use `WithMeta` instead", - replaceWith = ReplaceWith("WithMeta", "io.modelcontextprotocol.kotlin.sdk.types.WithMeta"), - level = DeprecationLevel.ERROR, -) -public typealias WithMeta = io.modelcontextprotocol.kotlin.sdk.types.WithMeta - -/** - * An implementation of [WithMeta] containing custom metadata. - */ -@Deprecated( - message = "This class will be removed. Use `WithMeta` instead", - replaceWith = ReplaceWith("WithMeta", "io.modelcontextprotocol.kotlin.sdk.types.WithMeta"), - level = DeprecationLevel.ERROR, -) -public typealias CustomMeta = io.modelcontextprotocol.kotlin.sdk.types.WithMeta - -/** - * Represents a method in the protocol, which can be predefined or custom. - */ -@Deprecated( - message = "Use `Method` instead", - replaceWith = ReplaceWith("Method", "io.modelcontextprotocol.kotlin.sdk.types.Method"), - level = DeprecationLevel.ERROR, -) -public sealed interface Method { - public val value: String - - public companion object { - /** - * Represents a custom method defined by the user. - */ - @Deprecated( - message = "Use `Method.Custom` instead", - replaceWith = ReplaceWith("Method.Custom", "io.modelcontextprotocol.kotlin.sdk.types.Method.Custom"), - level = DeprecationLevel.ERROR, - ) - public fun Custom(value: String): io.modelcontextprotocol.kotlin.sdk.types.Method.Custom = - io.modelcontextprotocol.kotlin.sdk.types.Method.Custom(value) - } - - public object Defined { - @Deprecated( - message = "Use `Method.Defined.Initialize` instead", - replaceWith = ReplaceWith( - "Method.Defined.Initialize", - "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.Initialize", - ), - level = DeprecationLevel.ERROR, - ) - public val Initialize: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = - io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.Initialize - - @Deprecated( - message = "Use `Method.Defined.Ping` instead", - replaceWith = ReplaceWith( - "Method.Defined.Ping", - "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.Ping", - ), - level = DeprecationLevel.ERROR, - ) - public val Ping: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = - io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.Ping - - @Deprecated( - message = "Use `Method.Defined.ResourcesList` instead", - replaceWith = ReplaceWith( - "Method.Defined.ResourcesList", - "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesList", - ), - level = DeprecationLevel.ERROR, - ) - public val ResourcesList: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = - io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesList - - @Deprecated( - message = "Use `Method.Defined.ResourcesTemplatesList` instead", - replaceWith = ReplaceWith( - "Method.Defined.ResourcesTemplatesList", - "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesTemplatesList", - ), - level = DeprecationLevel.ERROR, - ) - public val ResourcesTemplatesList: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = - io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesTemplatesList - - @Deprecated( - message = "Use `Method.Defined.ResourcesRead` instead", - replaceWith = ReplaceWith( - "Method.Defined.ResourcesRead", - "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesRead", - ), - level = DeprecationLevel.ERROR, - ) - public val ResourcesRead: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = - io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesRead - - @Deprecated( - message = "Use `Method.Defined.ResourcesSubscribe` instead", - replaceWith = ReplaceWith( - "Method.Defined.ResourcesSubscribe", - "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesSubscribe", - ), - level = DeprecationLevel.ERROR, - ) - public val ResourcesSubscribe: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = - io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesSubscribe - - @Deprecated( - message = "Use `Method.Defined.ResourcesUnsubscribe` instead", - replaceWith = ReplaceWith( - "Method.Defined.ResourcesUnsubscribe", - "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesUnsubscribe", - ), - level = DeprecationLevel.ERROR, - ) - public val ResourcesUnsubscribe: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = - io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesUnsubscribe - - @Deprecated( - message = "Use `Method.Defined.PromptsList` instead", - replaceWith = ReplaceWith( - "Method.Defined.PromptsList", - "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.PromptsList", - ), - level = DeprecationLevel.ERROR, - ) - public val PromptsList: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = - io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.PromptsList - - @Deprecated( - message = "Use `Method.Defined.PromptsGet` instead", - replaceWith = ReplaceWith( - "Method.Defined.PromptsGet", - "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.PromptsGet", - ), - level = DeprecationLevel.ERROR, - ) - public val PromptsGet: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = - io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.PromptsGet - - @Deprecated( - message = "Use `Method.Defined.NotificationsCancelled` instead", - replaceWith = ReplaceWith( - "Method.Defined.NotificationsCancelled", - "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsCancelled", - ), - level = DeprecationLevel.ERROR, - ) - public val NotificationsCancelled: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = - io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsCancelled - - @Deprecated( - message = "Use `Method.Defined.NotificationsInitialized` instead", - replaceWith = ReplaceWith( - "Method.Defined.NotificationsInitialized", - "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsInitialized", - ), - level = DeprecationLevel.ERROR, - ) - public val NotificationsInitialized: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = - io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsInitialized - - @Deprecated( - message = "Use `Method.Defined.NotificationsProgress` instead", - replaceWith = ReplaceWith( - "Method.Defined.NotificationsProgress", - "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsProgress", - ), - level = DeprecationLevel.ERROR, - ) - public val NotificationsProgress: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = - io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsProgress - - @Deprecated( - message = "Use `Method.Defined.NotificationsMessage` instead", - replaceWith = ReplaceWith( - "Method.Defined.NotificationsMessage", - "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsMessage", - ), - level = DeprecationLevel.ERROR, - ) - public val NotificationsMessage: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = - io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsMessage - - @Deprecated( - message = "Use `Method.Defined.NotificationsResourcesUpdated` instead", - replaceWith = ReplaceWith( - "Method.Defined.NotificationsResourcesUpdated", - "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsResourcesUpdated", - ), - level = DeprecationLevel.ERROR, - ) - public val NotificationsResourcesUpdated: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = - io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsResourcesUpdated - - @Deprecated( - message = "Use `Method.Defined.NotificationsResourcesListChanged` instead", - replaceWith = ReplaceWith( - "Method.Defined.NotificationsResourcesListChanged", - "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsResourcesListChanged", - ), - level = DeprecationLevel.ERROR, - ) - public val NotificationsResourcesListChanged: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = - io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsResourcesListChanged - - @Deprecated( - message = "Use `Method.Defined.NotificationsToolsListChanged` instead", - replaceWith = ReplaceWith( - "Method.Defined.NotificationsToolsListChanged", - "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsToolsListChanged", - ), - level = DeprecationLevel.ERROR, - ) - public val NotificationsToolsListChanged: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = - io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsToolsListChanged - - @Deprecated( - message = "Use `Method.Defined.NotificationsRootsListChanged` instead", - replaceWith = ReplaceWith( - "Method.Defined.NotificationsRootsListChanged", - "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsRootsListChanged", - ), - level = DeprecationLevel.ERROR, - ) - public val NotificationsRootsListChanged: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = - io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsRootsListChanged - - @Deprecated( - message = "Use `Method.Defined.NotificationsPromptsListChanged` instead", - replaceWith = ReplaceWith( - "Method.Defined.NotificationsPromptsListChanged", - "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsPromptsListChanged", - ), - level = DeprecationLevel.ERROR, - ) - public val NotificationsPromptsListChanged: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = - io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsPromptsListChanged - - @Deprecated( - message = "Use `Method.Defined.ToolsList` instead", - replaceWith = ReplaceWith( - "Method.Defined.ToolsList", - "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ToolsList", - ), - level = DeprecationLevel.ERROR, - ) - public val ToolsList: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = - io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ToolsList - - @Deprecated( - message = "Use `Method.Defined.ToolsCall` instead", - replaceWith = ReplaceWith( - "Method.Defined.ToolsCall", - "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ToolsCall", - ), - level = DeprecationLevel.ERROR, - ) - public val ToolsCall: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = - io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ToolsCall - - @Deprecated( - message = "Use `Method.Defined.LoggingSetLevel` instead", - replaceWith = ReplaceWith( - "Method.Defined.LoggingSetLevel", - "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.LoggingSetLevel", - ), - level = DeprecationLevel.ERROR, - ) - public val LoggingSetLevel: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = - io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.LoggingSetLevel - - @Deprecated( - message = "Use `Method.Defined.SamplingCreateMessage` instead", - replaceWith = ReplaceWith( - "Method.Defined.SamplingCreateMessage", - "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.SamplingCreateMessage", - ), - level = DeprecationLevel.ERROR, - ) - public val SamplingCreateMessage: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = - io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.SamplingCreateMessage - - @Deprecated( - message = "Use `Method.Defined.CompletionComplete` instead", - replaceWith = ReplaceWith( - "Method.Defined.CompletionComplete", - "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.CompletionComplete", - ), - level = DeprecationLevel.ERROR, - ) - public val CompletionComplete: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = - io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.CompletionComplete - - @Deprecated( - message = "Use `Method.Defined.RootsList` instead", - replaceWith = ReplaceWith( - "Method.Defined.RootsList", - "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.RootsList", - ), - level = DeprecationLevel.ERROR, - ) - public val RootsList: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = - io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.RootsList - - @Deprecated( - message = "Use `Method.Defined.ElicitationCreate` instead", - replaceWith = ReplaceWith( - "Method.Defined.ElicitationCreate", - "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ElicitationCreate", - ), - level = DeprecationLevel.ERROR, - ) - public val ElicitationCreate: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = - io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ElicitationCreate - } -} - -/** - * Represents a request in the protocol. - */ -@Deprecated( - message = "Use `Request` instead", - replaceWith = ReplaceWith("Request", "io.modelcontextprotocol.kotlin.sdk.types.Request"), - level = DeprecationLevel.ERROR, -) -public typealias Request = io.modelcontextprotocol.kotlin.sdk.types.Request - -/** - * Converts the request to a JSON-RPC request. - * - * @return The JSON-RPC request representation. - */ -@Deprecated( - message = "Use `toJSON` instead", - replaceWith = ReplaceWith("toJSON", "io.modelcontextprotocol.kotlin.sdk.types.toJSON"), - level = DeprecationLevel.ERROR, -) -@Suppress("DEPRECATION_ERROR") -public fun Request.toJSON(): JSONRPCRequest = this.toJSON() - -/** - * Decodes a JSON-RPC request into a protocol-specific [Request]. - * - * @return The decoded [Request] or null - */ -@Deprecated( - message = "Use `fromJSON` instead", - replaceWith = ReplaceWith("fromJSON", "io.modelcontextprotocol.kotlin.sdk.types.fromJSON"), - level = DeprecationLevel.ERROR, -) -@Suppress("DEPRECATION_ERROR") -internal fun JSONRPCRequest.fromJSON(): Request = this.fromJSON() - -/** - * A custom request with a specified method. - */ -@Deprecated( - message = "Use `CustomRequest` instead", - replaceWith = ReplaceWith("CustomRequest", "io.modelcontextprotocol.kotlin.sdk.types.CustomRequest"), - level = DeprecationLevel.ERROR, -) -public typealias CustomRequest = io.modelcontextprotocol.kotlin.sdk.types.CustomRequest - -/** - * Represents a notification in the protocol. - */ -@Deprecated( - message = "Use `Notification` instead", - replaceWith = ReplaceWith("Notification", "io.modelcontextprotocol.kotlin.sdk.types.Notification"), - level = DeprecationLevel.ERROR, -) -public typealias Notification = io.modelcontextprotocol.kotlin.sdk.types.Notification - -/** - * Converts the notification to a JSON-RPC notification. - * - * @return The JSON-RPC notification representation. - */ -@Deprecated( - message = "Use `toJSON` instead", - replaceWith = ReplaceWith("toJSON", "io.modelcontextprotocol.kotlin.sdk.types.toJSON"), - level = DeprecationLevel.ERROR, -) -@Suppress("DEPRECATION_ERROR") -public fun Notification.toJSON(): JSONRPCNotification = this.toJSON() - -/** - * Decodes a JSON-RPC notification into a protocol-specific [Notification]. - * - * @return The decoded [Notification]. - */ -@Deprecated( - message = "Use `fromJSON` instead", - replaceWith = ReplaceWith("toJSON", "io.modelcontextprotocol.kotlin.sdk.types.fromJSON"), - level = DeprecationLevel.ERROR, -) -@Suppress("DEPRECATION_ERROR") -internal fun JSONRPCNotification.fromJSON(): Notification = this.fromJSON() - -/** - * Represents the result of a request, including additional metadata. - */ -@Deprecated( - message = "Use `RequestResult` instead", - replaceWith = ReplaceWith("RequestResult", "io.modelcontextprotocol.kotlin.sdk.types.RequestResult"), - level = DeprecationLevel.ERROR, -) -public typealias RequestResult = io.modelcontextprotocol.kotlin.sdk.types.RequestResult - -/** - * An empty result for a request containing optional metadata. - */ -@Deprecated( - message = "Use `EmptyResult` instead", - replaceWith = ReplaceWith("EmptyResult", "io.modelcontextprotocol.kotlin.sdk.types.EmptyResult"), - level = DeprecationLevel.ERROR, -) -public typealias EmptyRequestResult = io.modelcontextprotocol.kotlin.sdk.types.EmptyResult - -/** - * A uniquely identifying ID for a request in JSON-RPC. - */ -@Deprecated( - message = "Use `RequestId` instead", - replaceWith = ReplaceWith("RequestId", "io.modelcontextprotocol.kotlin.sdk.types.RequestId"), - level = DeprecationLevel.ERROR, -) -public object RequestId { - @Deprecated( - message = "Use `RequestId.StringId` instead", - replaceWith = ReplaceWith("RequestId.StringId", "io.modelcontextprotocol.kotlin.sdk.types.RequestId.StringId"), - level = DeprecationLevel.ERROR, - ) - public fun StringId(value: String): io.modelcontextprotocol.kotlin.sdk.types.RequestId.StringId = - io.modelcontextprotocol.kotlin.sdk.types.RequestId.StringId(value) - - @Deprecated( - message = "Use `RequestId.NumberId` instead", - replaceWith = ReplaceWith("RequestId.NumberId", "io.modelcontextprotocol.kotlin.sdk.types.RequestId.NumberId"), - level = DeprecationLevel.ERROR, - ) - public fun NumberId(value: Long): io.modelcontextprotocol.kotlin.sdk.types.RequestId.NumberId = - io.modelcontextprotocol.kotlin.sdk.types.RequestId.NumberId(value) -} - -/** - * Represents a JSON-RPC message in the protocol. - */ -@Deprecated( - message = "Use `JSONRPCMessage` instead", - replaceWith = ReplaceWith("JSONRPCMessage", "io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage"), - level = DeprecationLevel.ERROR, -) -public typealias JSONRPCMessage = io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage - -/** - * A request that expects a response. - */ -@Deprecated( - message = "Use `JSONRPCRequest` instead", - replaceWith = ReplaceWith("JSONRPCRequest", "io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest"), - level = DeprecationLevel.ERROR, -) -public typealias JSONRPCRequest = io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest - -/** - * A notification which does not expect a response. - */ -@Deprecated( - message = "Use `JSONRPCNotification` instead", - replaceWith = ReplaceWith("JSONRPCNotification", "io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification"), - level = DeprecationLevel.ERROR, -) -public typealias JSONRPCNotification = io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification - -/** - * A successful (non-error) response to a request. - */ -@Deprecated( - message = "Use `JSONRPCResponse` instead", - replaceWith = ReplaceWith("JSONRPCResponse", "io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse"), - level = DeprecationLevel.ERROR, -) -public typealias JSONRPCResponse = io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse - -/** - * An incomplete set of error codes that may appear in JSON-RPC responses. - */ -@Deprecated( - message = "Use `RPCError` instead", - replaceWith = ReplaceWith("RPCError", "io.modelcontextprotocol.kotlin.sdk.types.RPCError"), - level = DeprecationLevel.ERROR, -) -public sealed interface ErrorCode { - public val code: Int - - public object Defined { - @Deprecated( - message = "Use `RPCError.ErrorCode.CONNECTION_CLOSED` instead", - replaceWith = ReplaceWith( - "RPCError.ErrorCode.CONNECTION_CLOSED", - "io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.CONNECTION_CLOSED", - ), - level = DeprecationLevel.ERROR, - ) - public val ConnectionClosed: Int = io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.CONNECTION_CLOSED - - @Deprecated( - message = "Use `RPCError.ErrorCode.REQUEST_TIMEOUT` instead", - replaceWith = ReplaceWith( - "RPCError.ErrorCode.REQUEST_TIMEOUT", - "io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.REQUEST_TIMEOUT", - ), - level = DeprecationLevel.ERROR, - ) - public val RequestTimeout: Int = io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.REQUEST_TIMEOUT - - @Deprecated( - message = "Use `RPCError.ErrorCode.PARSE_ERROR` instead", - replaceWith = ReplaceWith( - "RPCError.ErrorCode.PARSE_ERROR", - "io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.PARSE_ERROR", - ), - level = DeprecationLevel.ERROR, - ) - public val ParseError: Int = io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.PARSE_ERROR - - @Deprecated( - message = "Use `RPCError.ErrorCode.INVALID_REQUEST` instead", - replaceWith = ReplaceWith( - "RPCError.ErrorCode.INVALID_REQUEST", - "io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.INVALID_REQUEST", - ), - level = DeprecationLevel.ERROR, - ) - public val InvalidRequest: Int = io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.INVALID_REQUEST - - @Deprecated( - message = "Use `RPCError.ErrorCode.METHOD_NOT_FOUND` instead", - replaceWith = ReplaceWith( - "RPCError.ErrorCode.METHOD_NOT_FOUND", - "io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.METHOD_NOT_FOUND", - ), - level = DeprecationLevel.ERROR, - ) - public val MethodNotFound: Int = io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.METHOD_NOT_FOUND - - @Deprecated( - message = "Use `RPCError.ErrorCode.INVALID_PARAMS` instead", - replaceWith = ReplaceWith( - "RPCError.ErrorCode.INVALID_PARAMS", - "io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.INVALID_PARAMS", - ), - level = DeprecationLevel.ERROR, - ) - public val InvalidParams: Int = io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.INVALID_PARAMS - - @Deprecated( - message = "Use `RPCError.ErrorCode.INTERNAL_ERROR` instead", - replaceWith = ReplaceWith( - "RPCError.ErrorCode.INTERNAL_ERROR", - "io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.INTERNAL_ERROR", - ), - level = DeprecationLevel.ERROR, - ) - public val InternalError: Int = io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.INTERNAL_ERROR - } - - @Deprecated( - message = "Use `RPCError` instead", - replaceWith = ReplaceWith("RPCError", "io.modelcontextprotocol.kotlin.sdk.types.RPCError"), - level = DeprecationLevel.ERROR, - ) - @Serializable - @Suppress("DEPRECATION_ERROR") - public data class Unknown(override val code: Int) : ErrorCode -} - -/** - * A response to a request that indicates an error occurred. - */ -@Deprecated( - message = "Use `JSONRPCError` instead", - replaceWith = ReplaceWith("JSONRPCError", "io.modelcontextprotocol.kotlin.sdk.types.JSONRPCError"), - level = DeprecationLevel.ERROR, -) -public typealias JSONRPCError = io.modelcontextprotocol.kotlin.sdk.types.JSONRPCError - -/** - * Base interface for notification parameters with optional metadata. - */ -@Deprecated( - message = "Use `NotificationParams` instead", - replaceWith = ReplaceWith("NotificationParams", "io.modelcontextprotocol.kotlin.sdk.types.NotificationParams"), - level = DeprecationLevel.ERROR, -) -public typealias NotificationParams = io.modelcontextprotocol.kotlin.sdk.types.NotificationParams - -/* Cancellation */ - -@Deprecated( - message = "Use `CancelledNotification` instead", - replaceWith = ReplaceWith( - "CancelledNotification", - "io.modelcontextprotocol.kotlin.sdk.types.CancelledNotification", - ), - level = DeprecationLevel.ERROR, -) -public object CancelledNotification { - @Deprecated( - message = "Use `CancelledNotificationParams` instead", - replaceWith = ReplaceWith( - "CancelledNotificationParams", - "io.modelcontextprotocol.kotlin.sdk.types.CancelledNotificationParams", - ), - level = DeprecationLevel.ERROR, - ) - public fun Params( - requestId: io.modelcontextprotocol.kotlin.sdk.types.RequestId, - reason: String? = null, - meta: JsonObject? = null, - ): io.modelcontextprotocol.kotlin.sdk.types.CancelledNotificationParams = - io.modelcontextprotocol.kotlin.sdk.types.CancelledNotificationParams(requestId, reason, meta) -} - -/** - * This notification can be sent by either side to indicate that it is cancelling a previously issued request. - * - * The request SHOULD still be in-flight, but due to communication latency, it is always possible that - * this notification MAY arrive after the request has already finished. - * - * This notification indicates that the result will be unused, so any associated processing SHOULD cease. - * - * A client MUST NOT attempt to cancel its `initialize` request. - */ -@Deprecated( - message = "Use `CancelledNotification` instead", - replaceWith = ReplaceWith( - "CancelledNotification", - "io.modelcontextprotocol.kotlin.sdk.types.CancelledNotification", - ), - level = DeprecationLevel.ERROR, -) -public fun CancelledNotification( - params: io.modelcontextprotocol.kotlin.sdk.types.CancelledNotificationParams, -): io.modelcontextprotocol.kotlin.sdk.types.CancelledNotification = - io.modelcontextprotocol.kotlin.sdk.types.CancelledNotification(params) - -/* Initialization */ - -/** - * Describes the name and version of an MCP implementation. - */ -@Deprecated( - message = "Use `Implementation` instead", - replaceWith = ReplaceWith("Implementation", "io.modelcontextprotocol.kotlin.sdk.types.Implementation"), - level = DeprecationLevel.ERROR, -) -public typealias Implementation = io.modelcontextprotocol.kotlin.sdk.types.Implementation - -@Deprecated( - message = "Use `ClientCapabilities` instead", - replaceWith = ReplaceWith("ClientCapabilities", "io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities"), - level = DeprecationLevel.ERROR, -) -public object ClientCapabilities { - /** - * Creates an instance of [Roots], representing the client's capability - * to list roots and optionally listen for changes in the roots list. - * - * @param listChanged Optional flag indicating whether the client supports notifications - * for changes to the roots list. If set to true, the client will notify the server when - * roots are added or removed. - * @return A new instance of [Roots] configured with the provided [listChanged] value. - */ - @Deprecated( - message = "Use `ClientCapabilities.Roots` instead", - replaceWith = ReplaceWith( - "ClientCapabilities.Roots", - "io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities.Roots", - ), - level = DeprecationLevel.ERROR, - ) - public fun Roots(listChanged: Boolean? = null): TypesRoots = TypesRoots(listChanged) -} - -/** - * Capabilities a client may support. - * Known capabilities are defined here, in this, but this is not a closed set: - * any client can define its own, additional capabilities. - */ -@Deprecated( - message = "Use `ClientCapabilities` instead", - replaceWith = ReplaceWith("ClientCapabilities", "io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities"), - level = DeprecationLevel.ERROR, -) -public fun ClientCapabilities( - sampling: JsonObject? = null, - roots: TypesRoots? = null, - elicitation: JsonObject? = null, - experimental: JsonObject? = null, -): io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities = - io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities(sampling, roots, elicitation, experimental) - -/** - * Represents a request sent by the client. - */ -@Deprecated( - message = "Use `ClientRequest` instead", - replaceWith = ReplaceWith("ClientRequest", "io.modelcontextprotocol.kotlin.sdk.types.ClientRequest"), - level = DeprecationLevel.ERROR, -) -public typealias ClientRequest = io.modelcontextprotocol.kotlin.sdk.types.ClientRequest - -/** - * Represents a notification sent by the client. - */ -@Deprecated( - message = "Use `ClientNotification` instead", - replaceWith = ReplaceWith("ClientNotification", "io.modelcontextprotocol.kotlin.sdk.types.ClientNotification"), - level = DeprecationLevel.ERROR, -) -public typealias ClientNotification = io.modelcontextprotocol.kotlin.sdk.types.ClientNotification - -/** - * Represents a result returned to the client. - */ -@Deprecated( - message = "Use `ClientResult` instead", - replaceWith = ReplaceWith("ClientResult", "io.modelcontextprotocol.kotlin.sdk.types.ClientResult"), - level = DeprecationLevel.ERROR, -) -public typealias ClientResult = io.modelcontextprotocol.kotlin.sdk.types.ClientResult - -/** - * Represents a request sent by the server. - */ -@Deprecated( - message = "Use `ServerRequest` instead", - replaceWith = ReplaceWith("ServerRequest", "io.modelcontextprotocol.kotlin.sdk.types.ServerRequest"), - level = DeprecationLevel.ERROR, -) -public typealias ServerRequest = io.modelcontextprotocol.kotlin.sdk.types.ServerRequest - -/** - * Represents a notification sent by the server. - */ -@Deprecated( - message = "Use `ServerNotification` instead", - replaceWith = ReplaceWith("ServerNotification", "io.modelcontextprotocol.kotlin.sdk.types.ServerNotification"), - level = DeprecationLevel.ERROR, -) -public typealias ServerNotification = io.modelcontextprotocol.kotlin.sdk.types.ServerNotification - -/** - * Represents a result returned by the server. - */ -@Deprecated( - message = "Use `ServerResult` instead", - replaceWith = ReplaceWith("ServerResult", "io.modelcontextprotocol.kotlin.sdk.types.ServerResult"), - level = DeprecationLevel.ERROR, -) -public typealias ServerResult = io.modelcontextprotocol.kotlin.sdk.types.ServerResult - -/** - * Represents a request or notification for an unknown method. - * - * @param method The method that is unknown. - */ -@Serializable -@Deprecated( - message = "This class will be removed", - level = DeprecationLevel.ERROR, -) -@Suppress("DEPRECATION_ERROR") -public data class UnknownMethodRequestOrNotification(val method: Method, val params: NotificationParams? = null) - -/** - * This request is sent from the client to the server when it first connects, asking it to begin initialization. - */ -@Deprecated( - message = "Use `InitializeRequest` instead", - replaceWith = ReplaceWith("InitializeRequest", "io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest"), - level = DeprecationLevel.ERROR, -) -public typealias InitializeRequest = io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest - -@Deprecated( - message = "Use `ServerCapabilities` instead", - replaceWith = ReplaceWith("ServerCapabilities", "io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities"), - level = DeprecationLevel.ERROR, -) -public object ServerCapabilities { - - @Deprecated( - message = "Use `ServerCapabilities.Prompts` instead", - replaceWith = ReplaceWith( - "ServerCapabilities.Prompts", - "io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Prompts", - ), - level = DeprecationLevel.ERROR, - ) - public fun Prompts( - listChanged: Boolean? = null, - ): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Prompts = - io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Prompts(listChanged) - - @Deprecated( - message = "Use `ServerCapabilities.Resources` instead", - replaceWith = ReplaceWith( - "ServerCapabilities.Resources", - "io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Resources", - ), - level = DeprecationLevel.ERROR, - ) - public fun Resources( - listChanged: Boolean? = null, - subscribe: Boolean? = null, - ): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Resources = - io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Resources(listChanged, subscribe) - - @Deprecated( - message = "Use `ServerCapabilities.Tools` instead", - replaceWith = ReplaceWith( - "ServerCapabilities.Tools", - "io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Tools", - ), - level = DeprecationLevel.ERROR, - ) - public fun Tools(listChanged: Boolean? = null): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Tools = - io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Tools(listChanged) -} - -/** - * Represents the capabilities that a server can support. - * - * @property experimental Experimental, non-standard capabilities that the server supports. - * @property logging Present if the server supports sending log messages to the client. - * @property prompts Capabilities related to prompt templates offered by the server. - * @property resources Capabilities related to resources available on the server. - * @property tools Capabilities related to tools that can be called on the server. - */ -@Deprecated( - message = "Use `ServerCapabilities` instead", - replaceWith = ReplaceWith("ServerCapabilities", "io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities"), - level = DeprecationLevel.ERROR, -) -@Suppress("LongParameterList") -public fun ServerCapabilities( - tools: io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Tools? = null, - resources: io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Resources? = null, - prompts: io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Prompts? = null, - logging: JsonObject? = null, - completions: JsonObject? = null, - experimental: JsonObject? = null, -): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities = - io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities( - tools, - resources, - prompts, - logging, - completions, - experimental, - ) - -/** - * After receiving an initialized request from the client, the server sends this response. - */ -@Deprecated( - message = "Use `InitializeResult` instead", - replaceWith = ReplaceWith("InitializeResult", "io.modelcontextprotocol.kotlin.sdk.types.InitializeResult"), - level = DeprecationLevel.ERROR, -) -public typealias InitializeResult = io.modelcontextprotocol.kotlin.sdk.types.InitializeResult - -@Deprecated( - message = "Use `InitializedNotification` instead", - replaceWith = ReplaceWith( - "InitializedNotification", - "io.modelcontextprotocol.kotlin.sdk.types.InitializedNotification", - ), - level = DeprecationLevel.ERROR, -) -public object InitializedNotification { - @Deprecated( - message = "Use `BaseNotificationParams` instead", - replaceWith = ReplaceWith( - "BaseNotificationParams", - "io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams", - ), - level = DeprecationLevel.ERROR, - ) - @Suppress("DEPRECATION_ERROR") - public fun Params( - meta: JsonObject = EmptyJsonObject, - ): io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams = - io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams(meta) -} - -/** - * This notification is sent from the client to the server after initialization has finished. - */ -@Deprecated( - message = "Use `InitializedNotification` instead", - replaceWith = ReplaceWith( - "InitializedNotification", - "io.modelcontextprotocol.kotlin.sdk.types.InitializedNotification", - ), - level = DeprecationLevel.ERROR, -) -public fun InitializedNotification( - params: io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams? = null, -): io.modelcontextprotocol.kotlin.sdk.types.InitializedNotification = - io.modelcontextprotocol.kotlin.sdk.types.InitializedNotification(params) - -/* Ping */ - -/** - * A ping, issued by either the server or the client, to check that the other party is still alive. - * The receiver must promptly respond, or else it may be disconnected. - */ -@Deprecated( - message = "Use `PingRequest` instead", - replaceWith = ReplaceWith("PingRequest", "io.modelcontextprotocol.kotlin.sdk.types.PingRequest"), - level = DeprecationLevel.ERROR, -) -public typealias PingRequest = io.modelcontextprotocol.kotlin.sdk.types.PingRequest - -/** - * Represents the base interface for progress tracking. - */ -@Serializable -@Deprecated( - message = "This interface will be removed", - level = DeprecationLevel.ERROR, -) -public sealed interface ProgressBase { - /** - * The progress thus far. This should increase every time progress is made, even if the total is unknown. - */ - public val progress: Double - - /** - * Total number of items to a process (or total progress required), if known. - */ - public val total: Double? - - /** - * An optional message describing the current progress. - */ - public val message: String? -} - -/* Progress notifications */ - -/** - * Represents a progress notification. - */ -@Deprecated( - message = "Use `Progress` instead", - replaceWith = ReplaceWith("Progress", "io.modelcontextprotocol.kotlin.sdk.types.Progress"), - level = DeprecationLevel.ERROR, -) -public typealias Progress = io.modelcontextprotocol.kotlin.sdk.types.Progress - -@Deprecated( - message = "Use `ProgressNotification` instead", - replaceWith = ReplaceWith("ProgressNotification", "io.modelcontextprotocol.kotlin.sdk.types.ProgressNotification"), - level = DeprecationLevel.ERROR, -) -public object ProgressNotification { - @Deprecated( - message = "Use `ProgressNotificationParams` instead", - replaceWith = ReplaceWith( - "ProgressNotificationParams", - "io.modelcontextprotocol.kotlin.sdk.types.ProgressNotificationParams", - ), - level = DeprecationLevel.ERROR, - ) - public fun Params( - progressToken: io.modelcontextprotocol.kotlin.sdk.types.ProgressToken, - progress: Double, - total: Double? = null, - message: String? = null, - meta: JsonObject? = null, - ): io.modelcontextprotocol.kotlin.sdk.types.ProgressNotificationParams = - io.modelcontextprotocol.kotlin.sdk.types.ProgressNotificationParams( - progressToken, - progress, - total, - message, - meta, - ) -} - -/** - * An out-of-band notification used to inform the receiver of a progress update for a long-running request. - */ -@Deprecated( - message = "Use `ProgressNotification` instead", - replaceWith = ReplaceWith("ProgressNotification", "io.modelcontextprotocol.kotlin.sdk.types.ProgressNotification"), - level = DeprecationLevel.ERROR, -) -public fun ProgressNotification( - params: io.modelcontextprotocol.kotlin.sdk.types.ProgressNotificationParams, -): io.modelcontextprotocol.kotlin.sdk.types.ProgressNotification = - io.modelcontextprotocol.kotlin.sdk.types.ProgressNotification(params) - -/* Pagination */ - -/** - * Represents a request supporting pagination. - */ -@Deprecated( - message = "Use `PaginatedRequest` instead", - replaceWith = ReplaceWith("PaginatedRequest", "io.modelcontextprotocol.kotlin.sdk.types.PaginatedRequest"), - level = DeprecationLevel.ERROR, -) -public typealias PaginatedRequest = io.modelcontextprotocol.kotlin.sdk.types.PaginatedRequest - -/** - * Represents a paginated result of a request. - */ -@Deprecated( - message = "Use `PaginatedResult` instead", - replaceWith = ReplaceWith("PaginatedResult", "io.modelcontextprotocol.kotlin.sdk.types.PaginatedResult"), - level = DeprecationLevel.ERROR, -) -public typealias PaginatedResult = io.modelcontextprotocol.kotlin.sdk.types.PaginatedResult - -/* Resources */ - -/** - * The contents of a specific resource or sub-resource. - */ -@Deprecated( - message = "Use `ResourceContents` instead", - replaceWith = ReplaceWith("ResourceContents", "io.modelcontextprotocol.kotlin.sdk.types.ResourceContents"), - level = DeprecationLevel.ERROR, -) -public typealias ResourceContents = io.modelcontextprotocol.kotlin.sdk.types.ResourceContents - -/** - * Represents the text contents of a resource. - */ -@Deprecated( - message = "Use `TextResourceContents` instead", - replaceWith = ReplaceWith("TextResourceContents", "io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents"), - level = DeprecationLevel.ERROR, -) -public typealias TextResourceContents = io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents - -/** - * Represents the binary contents of a resource encoded as a base64 string. - */ -@Deprecated( - message = "Use `BlobResourceContents` instead", - replaceWith = ReplaceWith("BlobResourceContents", "io.modelcontextprotocol.kotlin.sdk.types.BlobResourceContents"), - level = DeprecationLevel.ERROR, -) -public typealias BlobResourceContents = io.modelcontextprotocol.kotlin.sdk.types.BlobResourceContents - -/** - * Represents resource contents with unknown or unspecified data. - */ -@Deprecated( - message = "Use `UnknownResourceContents` instead", - replaceWith = ReplaceWith( - "UnknownResourceContents", - "io.modelcontextprotocol.kotlin.sdk.types.UnknownResourceContents", - ), - level = DeprecationLevel.ERROR, -) -public typealias UnknownResourceContents = io.modelcontextprotocol.kotlin.sdk.types.UnknownResourceContents - -/** - * A known resource that the server is capable of reading. - */ -@Deprecated( - message = "Use `Resource` instead", - replaceWith = ReplaceWith("Resource", "io.modelcontextprotocol.kotlin.sdk.types.Resource"), - level = DeprecationLevel.ERROR, -) -public typealias Resource = io.modelcontextprotocol.kotlin.sdk.types.Resource - -/** - * A template description for resources available on the server. - */ -@Deprecated( - message = "Use `ResourceTemplate` instead", - replaceWith = ReplaceWith("ResourceTemplate", "io.modelcontextprotocol.kotlin.sdk.types.ResourceTemplate"), - level = DeprecationLevel.ERROR, -) -public typealias ResourceTemplate = io.modelcontextprotocol.kotlin.sdk.types.ResourceTemplate - -/** - * Sent from the client to request a list of resources the server has. - */ -@Deprecated( - message = "Use `ListResourcesRequest` instead", - replaceWith = ReplaceWith("ListResourcesRequest", "io.modelcontextprotocol.kotlin.sdk.types.ListResourcesRequest"), - level = DeprecationLevel.ERROR, -) -public typealias ListResourcesRequest = io.modelcontextprotocol.kotlin.sdk.types.ListResourcesRequest - -/** - * The server's response to a resources/list request from the client. - */ -@Deprecated( - message = "Use `ListResourcesResult` instead", - replaceWith = ReplaceWith("ListResourcesResult", "io.modelcontextprotocol.kotlin.sdk.types.ListResourcesResult"), - level = DeprecationLevel.ERROR, -) -public typealias ListResourcesResult = io.modelcontextprotocol.kotlin.sdk.types.ListResourcesResult - -/** - * Sent from the client to request a list of resource templates the server has. - */ -@Deprecated( - message = "Use `ListResourceTemplatesRequest` instead", - replaceWith = ReplaceWith( - "ListResourceTemplatesRequest", - "io.modelcontextprotocol.kotlin.sdk.types.ListResourceTemplatesRequest", - ), - level = DeprecationLevel.ERROR, -) -public typealias ListResourceTemplatesRequest = io.modelcontextprotocol.kotlin.sdk.types.ListResourceTemplatesRequest - -/** - * The server's response to a resources/templates/list request from the client. - */ -@Deprecated( - message = "Use `ListResourceTemplatesResult` instead", - replaceWith = ReplaceWith( - "ListResourceTemplatesResult", - "io.modelcontextprotocol.kotlin.sdk.types.ListResourceTemplatesResult", - ), - level = DeprecationLevel.ERROR, -) -public typealias ListResourceTemplatesResult = io.modelcontextprotocol.kotlin.sdk.types.ListResourceTemplatesResult - -/** - * Sent from the client to the server to read a specific resource URI. - */ -@Deprecated( - message = "Use `ReadResourceRequest` instead", - replaceWith = ReplaceWith("ReadResourceRequest", "io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest"), - level = DeprecationLevel.ERROR, -) -public typealias ReadResourceRequest = io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest - -/** - * The server's response to a resources/read request from the client. - */ -@Deprecated( - message = "Use `ReadResourceResult` instead", - replaceWith = ReplaceWith("ReadResourceResult", "io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult"), - level = DeprecationLevel.ERROR, -) -public typealias ReadResourceResult = io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult - -@Deprecated( - message = "Use `ResourceListChangedNotification` instead", - replaceWith = ReplaceWith( - "ResourceListChangedNotification", - "io.modelcontextprotocol.kotlin.sdk.types.ResourceListChangedNotification", - ), - level = DeprecationLevel.ERROR, -) -public object ResourceListChangedNotification { - @Deprecated( - message = "Use `BaseNotificationParams` instead", - replaceWith = ReplaceWith( - "BaseNotificationParams", - "io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams", - ), - level = DeprecationLevel.ERROR, - ) - public fun Params(meta: JsonObject? = null): io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams = - io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams(meta) -} - -/** - * An optional notification from the server to the client, - * informing it that the list of resources it can read from has changed. - * Servers may issue this without any previous subscription from the client. - */ -@Deprecated( - message = "Use `ResourceListChangedNotification` instead", - replaceWith = ReplaceWith( - "ResourceListChangedNotification", - "io.modelcontextprotocol.kotlin.sdk.types.ResourceListChangedNotification", - ), - level = DeprecationLevel.ERROR, -) -public fun ResourceListChangedNotification( - params: io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams? = null, -): io.modelcontextprotocol.kotlin.sdk.types.ResourceListChangedNotification = - io.modelcontextprotocol.kotlin.sdk.types.ResourceListChangedNotification(params) - -/** - * Sent from the client to request resources/updated notifications from the server - * whenever a particular resource changes. - */ -@Deprecated( - message = "Use `SubscribeRequest` instead", - replaceWith = ReplaceWith("SubscribeRequest", "io.modelcontextprotocol.kotlin.sdk.types.SubscribeRequest"), - level = DeprecationLevel.ERROR, -) -public typealias SubscribeRequest = io.modelcontextprotocol.kotlin.sdk.types.SubscribeRequest - -/** - * Sent from the client to request cancellation of resources/updated notifications from the server. - * This should follow a previous resources/subscribe request. - */ -@Deprecated( - message = "Use `UnsubscribeRequest` instead", - replaceWith = ReplaceWith("UnsubscribeRequest", "io.modelcontextprotocol.kotlin.sdk.types.UnsubscribeRequest"), - level = DeprecationLevel.ERROR, -) -public typealias UnsubscribeRequest = io.modelcontextprotocol.kotlin.sdk.types.UnsubscribeRequest - -@Deprecated( - message = "Use `ResourceUpdatedNotification` instead", - replaceWith = ReplaceWith( - "ResourceUpdatedNotification", - "io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotification", - ), - level = DeprecationLevel.ERROR, -) -public object ResourceUpdatedNotification { - @Deprecated( - message = "Use `ResourceUpdatedNotificationParams` instead", - replaceWith = ReplaceWith( - "ResourceUpdatedNotificationParams", - "io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotificationParams", - ), - level = DeprecationLevel.ERROR, - ) - public fun Params( - uri: String, - meta: JsonObject? = null, - ): io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotificationParams = - io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotificationParams(uri, meta) -} - -/** - * A notification from the server to the client, informing it that a resource has changed and may need to be read again. - * This should only be sent if the client previously sent a resources/subscribe request. - */ -@Deprecated( - message = "Use `ResourceUpdatedNotification` instead", - replaceWith = ReplaceWith( - "ResourceUpdatedNotification", - "io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotification", - ), - level = DeprecationLevel.ERROR, -) -public fun ResourceUpdatedNotification( - params: io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotificationParams, -): io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotification = - io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotification(params) - -/* Prompts */ - -/** - * Describes an argument that a prompt can accept. - */ -@Deprecated( - message = "Use `PromptArgument` instead", - replaceWith = ReplaceWith("PromptArgument", "io.modelcontextprotocol.kotlin.sdk.types.PromptArgument"), - level = DeprecationLevel.ERROR, -) -public typealias PromptArgument = io.modelcontextprotocol.kotlin.sdk.types.PromptArgument - -/** - * A prompt or prompt template that the server offers. - */ -@Deprecated( - message = "Use `Prompt` instead", - replaceWith = ReplaceWith("Prompt", "io.modelcontextprotocol.kotlin.sdk.types.Prompt"), - level = DeprecationLevel.ERROR, -) -public typealias Prompt = io.modelcontextprotocol.kotlin.sdk.types.Prompt - -/** - * Sent from the client to request a list of prompts and prompt templates the server has. - */ -@Deprecated( - message = "Use `ListPromptsRequest` instead", - replaceWith = ReplaceWith("ListPromptsRequest", "io.modelcontextprotocol.kotlin.sdk.types.ListPromptsRequest"), - level = DeprecationLevel.ERROR, -) -public typealias ListPromptsRequest = io.modelcontextprotocol.kotlin.sdk.types.ListPromptsRequest - -/** - * The server's response to a prompts/list request from the client. - */ -@Deprecated( - message = "Use `ListPromptsResult` instead", - replaceWith = ReplaceWith("ListPromptsResult", "io.modelcontextprotocol.kotlin.sdk.types.ListPromptsResult"), - level = DeprecationLevel.ERROR, -) -public typealias ListPromptsResult = io.modelcontextprotocol.kotlin.sdk.types.ListPromptsResult - -/** - * Used by the client to get a prompt provided by the server. - */ -@Deprecated( - message = "Use `GetPromptRequest` instead", - replaceWith = ReplaceWith("GetPromptRequest", "io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest"), - level = DeprecationLevel.ERROR, -) -public typealias GetPromptRequest = io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest - -/** - * Represents the content of a prompt message. - */ -@Deprecated( - message = "Use `ContentBlock` instead", - replaceWith = ReplaceWith("ContentBlock"), - level = DeprecationLevel.ERROR, -) -public typealias PromptMessageContent = ContentBlock - -/** - * Represents prompt message content that is either text, image or audio. - */ -@Deprecated( - message = "Use `MediaContent` instead", - replaceWith = ReplaceWith("MediaContent"), - level = DeprecationLevel.ERROR, -) -public typealias PromptMessageContentMultimodal = io.modelcontextprotocol.kotlin.sdk.types.MediaContent - -/** - * Text provided to or from an LLM. - */ -@Deprecated( - message = "Use `TextContent` instead", - replaceWith = ReplaceWith("TextContent", "io.modelcontextprotocol.kotlin.sdk.types.TextContent"), - level = DeprecationLevel.ERROR, -) -public typealias TextContent = io.modelcontextprotocol.kotlin.sdk.types.TextContent - -/** - * An image provided to or from an LLM. - */ -@Deprecated( - message = "Use `ImageContent` instead", - replaceWith = ReplaceWith("ImageContent", "io.modelcontextprotocol.kotlin.sdk.types.ImageContent"), - level = DeprecationLevel.ERROR, -) -public typealias ImageContent = io.modelcontextprotocol.kotlin.sdk.types.ImageContent - -/** - * Audio provided to or from an LLM. - */ -@Deprecated( - message = "Use `AudioContent` instead", - replaceWith = ReplaceWith("AudioContent", "io.modelcontextprotocol.kotlin.sdk.types.AudioContent"), - level = DeprecationLevel.ERROR, -) -public typealias AudioContent = io.modelcontextprotocol.kotlin.sdk.types.AudioContent - -/** - * Unknown content provided to or from an LLM. - */ -@Serializable -@Deprecated( - message = "This class will be removed.", - replaceWith = ReplaceWith("MediaContent"), - level = DeprecationLevel.ERROR, -) -public data class UnknownContent(val type: String) - -/** - * The contents of a resource, embedded into a prompt or tool call result. - */ -@Deprecated( - message = "Use `EmbeddedResource` instead", - replaceWith = ReplaceWith("EmbeddedResource", "io.modelcontextprotocol.kotlin.sdk.types.EmbeddedResource"), - level = DeprecationLevel.ERROR, -) -public typealias EmbeddedResource = io.modelcontextprotocol.kotlin.sdk.types.EmbeddedResource - -/** - * Enum representing the role of a participant. - */ -@Deprecated( - message = "Use `Role` instead", - replaceWith = ReplaceWith("Role", "io.modelcontextprotocol.kotlin.sdk.types.Role"), - level = DeprecationLevel.ERROR, -) -public object Role { - @Deprecated( - message = "Use `Role.User` instead", - replaceWith = ReplaceWith("Role.User", "io.modelcontextprotocol.kotlin.sdk.types.Role"), - level = DeprecationLevel.ERROR, - ) - public val user: io.modelcontextprotocol.kotlin.sdk.types.Role = io.modelcontextprotocol.kotlin.sdk.types.Role.User - - @Deprecated( - message = "Use `Role.Assistant` instead", - replaceWith = ReplaceWith("Role.Assistant", "io.modelcontextprotocol.kotlin.sdk.types.Role"), - level = DeprecationLevel.ERROR, - ) - public val assistant: io.modelcontextprotocol.kotlin.sdk.types.Role = - io.modelcontextprotocol.kotlin.sdk.types.Role.Assistant -} - -/** - * Optional annotations for the client. - * The client can use annotations to inform how objects are used or displayed. - */ -@Deprecated( - message = "Use `Annotations` instead", - replaceWith = ReplaceWith("Annotations", "io.modelcontextprotocol.kotlin.sdk.types.Annotations"), - level = DeprecationLevel.ERROR, -) -public typealias Annotations = io.modelcontextprotocol.kotlin.sdk.types.Annotations - -/** - * Describes a message returned as part of a prompt. - */ -@Deprecated( - message = "Use `PromptMessage` instead", - replaceWith = ReplaceWith("PromptMessage", "io.modelcontextprotocol.kotlin.sdk.types.PromptMessage"), - level = DeprecationLevel.ERROR, -) -public typealias PromptMessage = io.modelcontextprotocol.kotlin.sdk.types.PromptMessage - -/** - * The server's response to a prompts/get request from the client. - */ -@Deprecated( - message = "Use `GetPromptResult` instead", - replaceWith = ReplaceWith("GetPromptResult", "io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult"), - level = DeprecationLevel.ERROR, -) -public typealias GetPromptResult = io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult - -@Deprecated( - message = "Use `PromptListChangedNotification` instead", - replaceWith = ReplaceWith( - "PromptListChangedNotification", - "io.modelcontextprotocol.kotlin.sdk.types.PromptListChangedNotification", - ), - level = DeprecationLevel.ERROR, -) -public object PromptListChangedNotification { - @Deprecated( - message = "Use `BaseNotificationParams` instead", - replaceWith = ReplaceWith( - "BaseNotificationParams", - "io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams", - ), - level = DeprecationLevel.ERROR, - ) - public fun Params(meta: JsonObject? = null): io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams = - io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams(meta) -} - -/** - * An optional notification from the server to the client, informing it that the list of prompts it offers has changed. - * Servers may issue this without any previous subscription from the client. - */ -@Deprecated( - message = "Use `PromptListChangedNotification` instead", - replaceWith = ReplaceWith( - "PromptListChangedNotification", - "io.modelcontextprotocol.kotlin.sdk.types.PromptListChangedNotification", - ), - level = DeprecationLevel.ERROR, -) -public fun PromptListChangedNotification( - params: io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams, -): io.modelcontextprotocol.kotlin.sdk.types.PromptListChangedNotification = - io.modelcontextprotocol.kotlin.sdk.types.PromptListChangedNotification(params) - -/* Tools */ - -/** - * Additional properties describing a Tool to clients. - * - * NOTE: all properties in ToolAnnotations are **hints**. - * They are not guaranteed to provide a faithful description of - * tool behavior (including descriptive properties like `title`). - * - * Clients should never make tool use decisions based on ToolAnnotations - * received from untrusted servers. - */ -@Deprecated( - message = "Use `ToolAnnotations` instead", - replaceWith = ReplaceWith("ToolAnnotations", "io.modelcontextprotocol.kotlin.sdk.types.ToolAnnotations"), - level = DeprecationLevel.ERROR, -) -public typealias ToolAnnotations = io.modelcontextprotocol.kotlin.sdk.types.ToolAnnotations - -@Deprecated( - message = "Use `Tool` instead", - replaceWith = ReplaceWith("Tool", "io.modelcontextprotocol.kotlin.sdk.types.Tool"), - level = DeprecationLevel.ERROR, -) -public object Tool { - @Deprecated( - message = "Use `ToolSchema` instead", - replaceWith = ReplaceWith("ToolSchema", "io.modelcontextprotocol.kotlin.sdk.types.ToolSchema"), - level = DeprecationLevel.ERROR, - ) - public fun Input( - properties: JsonObject? = null, - required: List? = null, - ): io.modelcontextprotocol.kotlin.sdk.types.ToolSchema = - io.modelcontextprotocol.kotlin.sdk.types.ToolSchema(properties, required) - - @Deprecated( - message = "Use `ToolSchema` instead", - replaceWith = ReplaceWith("ToolSchema", "io.modelcontextprotocol.kotlin.sdk.types.ToolSchema"), - level = DeprecationLevel.ERROR, - ) - public fun Output( - properties: JsonObject? = null, - required: List? = null, - ): io.modelcontextprotocol.kotlin.sdk.types.ToolSchema = - io.modelcontextprotocol.kotlin.sdk.types.ToolSchema(properties, required) -} - -/** - * Definition for a tool the client can call. - */ -@Deprecated( - message = "Use `Tool` instead", - replaceWith = ReplaceWith("Tool", "io.modelcontextprotocol.kotlin.sdk.types.Tool"), - level = DeprecationLevel.ERROR, -) -@Suppress("LongParameterList", "FunctionParameterNaming") -public fun Tool( - name: String, - inputSchema: io.modelcontextprotocol.kotlin.sdk.types.ToolSchema, - description: String? = null, - outputSchema: io.modelcontextprotocol.kotlin.sdk.types.ToolSchema? = null, - title: String? = null, - annotations: io.modelcontextprotocol.kotlin.sdk.types.ToolAnnotations? = null, - icons: List? = null, - _meta: JsonObject? = null, -): io.modelcontextprotocol.kotlin.sdk.types.Tool = io.modelcontextprotocol.kotlin.sdk.types.Tool( - name, - inputSchema, - description, - outputSchema, - title, - annotations, - icons, - meta = _meta, -) - -/** - * Sent from the client to request a list of tools the server has. - */ -@Deprecated( - message = "Use `ListToolsRequest` instead", - replaceWith = ReplaceWith("ListToolsRequest", "io.modelcontextprotocol.kotlin.sdk.types.ListToolsRequest"), - level = DeprecationLevel.ERROR, -) -public typealias ListToolsRequest = io.modelcontextprotocol.kotlin.sdk.types.ListToolsRequest - -/** - * The server's response to a tools/list request from the client. - */ -@Deprecated( - message = "Use `ListToolsResult` instead", - replaceWith = ReplaceWith("ListToolsResult", "io.modelcontextprotocol.kotlin.sdk.types.ListToolsResult"), - level = DeprecationLevel.ERROR, -) -public typealias ListToolsResult = io.modelcontextprotocol.kotlin.sdk.types.ListToolsResult - -/** - * The server's response to a tool call. - */ -@Serializable -@Deprecated( - message = "This interface will be removed.", - replaceWith = ReplaceWith("CallToolResult", "io.modelcontextprotocol.kotlin.sdk.types.CallToolResult"), - level = DeprecationLevel.ERROR, -) -@Suppress("DEPRECATION_ERROR") -public sealed interface CallToolResultBase { - public val content: List - public val structuredContent: JsonObject? - public val isError: Boolean? get() = false -} - -@Deprecated( - message = "Use `CallToolResult` instead", - replaceWith = ReplaceWith("CallToolResult", "io.modelcontextprotocol.kotlin.sdk.types.CallToolResult"), - level = DeprecationLevel.ERROR, -) -public object CallToolResult - -/** - * The server's response to a tool call. - */ -@Deprecated( - message = "Use `CallToolResult` instead", - replaceWith = ReplaceWith("CallToolResult", "io.modelcontextprotocol.kotlin.sdk.types.CallToolResult"), - level = DeprecationLevel.ERROR, -) -public fun CallToolResult( - content: List, - isError: Boolean? = null, - structuredContent: JsonObject? = null, - meta: JsonObject? = null, -): io.modelcontextprotocol.kotlin.sdk.types.CallToolResult = - io.modelcontextprotocol.kotlin.sdk.types.CallToolResult(content, isError, structuredContent, meta) - -/** - * [CallToolResult] extended with backwards compatibility to protocol version 2024-11-05. - */ -@Serializable -@Deprecated( - message = "This class will be removed.", - replaceWith = ReplaceWith("ToolCallResult"), - level = DeprecationLevel.ERROR, -) -@Suppress("DEPRECATION_ERROR", "ConstructorParameterNaming") -public class CompatibilityCallToolResult( - public val content: List, - public val structuredContent: JsonObject? = null, - public val isError: Boolean? = false, - public val _meta: JsonObject = EmptyJsonObject, - public val toolResult: JsonObject = EmptyJsonObject, -) - -/** - * Used by the client to invoke a tool provided by the server. - */ -@Deprecated( - message = "Use `CallToolRequest` instead", - replaceWith = ReplaceWith("CallToolRequest", "io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest"), - level = DeprecationLevel.ERROR, -) -public typealias CallToolRequest = io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest - -@Deprecated( - message = "Use `ToolListChangedNotification` instead", - replaceWith = ReplaceWith( - "ToolListChangedNotification", - "io.modelcontextprotocol.kotlin.sdk.types.ToolListChangedNotification", - ), - level = DeprecationLevel.ERROR, -) -public object ToolListChangedNotification { - @Deprecated( - message = "Use `BaseNotificationParams` instead", - replaceWith = ReplaceWith( - "BaseNotificationParams", - "io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams", - ), - level = DeprecationLevel.ERROR, - ) - public fun Params(meta: JsonObject? = null): io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams = - io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams(meta) -} - -/** - * An optional notification from the server to the client, informing it that the list of tools it offers has changed. - * Servers may issue this without any previous subscription from the client. - */ -@Deprecated( - message = "Use `ToolListChangedNotification` instead", - replaceWith = ReplaceWith( - "ToolListChangedNotification", - "io.modelcontextprotocol.kotlin.sdk.types.ToolListChangedNotification", - ), - level = DeprecationLevel.ERROR, -) -public fun ToolListChangedNotification( - params: io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams, -): io.modelcontextprotocol.kotlin.sdk.types.ToolListChangedNotification = - io.modelcontextprotocol.kotlin.sdk.types.ToolListChangedNotification(params) - -/* Logging */ - -/** - * The severity of a log message. - */ -@Deprecated( - message = "Use `LoggingLevel` instead", - replaceWith = ReplaceWith("LoggingLevel", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), - level = DeprecationLevel.ERROR, -) -public object LoggingLevel { - @Deprecated( - message = "Use `LoggingLevel.Debug` instead", - replaceWith = ReplaceWith("LoggingLevel.Debug", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), - level = DeprecationLevel.ERROR, - ) - public val debug: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel = - io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel.Debug - - @Deprecated( - message = "Use `LoggingLevel.Info` instead", - replaceWith = ReplaceWith("LoggingLevel.Info", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), - level = DeprecationLevel.ERROR, - ) - public val info: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel = - io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel.Info - - @Deprecated( - message = "Use `LoggingLevel.Notice` instead", - replaceWith = ReplaceWith("LoggingLevel.Notice", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), - level = DeprecationLevel.ERROR, - ) - public val notice: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel = - io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel.Notice - - @Deprecated( - message = "Use `LoggingLevel.Warning` instead", - replaceWith = ReplaceWith("LoggingLevel.Warning", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), - level = DeprecationLevel.ERROR, - ) - public val warning: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel = - io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel.Warning - - @Deprecated( - message = "Use `LoggingLevel.Error` instead", - replaceWith = ReplaceWith("LoggingLevel.Error", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), - level = DeprecationLevel.ERROR, - ) - public val error: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel = - io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel.Error - - @Deprecated( - message = "Use `LoggingLevel.Critical` instead", - replaceWith = ReplaceWith("LoggingLevel.Critical", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), - level = DeprecationLevel.ERROR, - ) - public val critical: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel = - io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel.Critical - - @Deprecated( - message = "Use `LoggingLevel.Alert` instead", - replaceWith = ReplaceWith("LoggingLevel.Alert", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), - level = DeprecationLevel.ERROR, - ) - public val alert: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel = - io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel.Alert - - @Deprecated( - message = "Use `LoggingLevel.Emergency` instead", - replaceWith = ReplaceWith("LoggingLevel.Emergency", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), - level = DeprecationLevel.ERROR, - ) - public val emergency: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel = - io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel.Emergency -} - -@Deprecated( - message = "Use `LoggingMessageNotification` instead", - replaceWith = ReplaceWith( - "LoggingMessageNotification", - "io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotification", - ), - level = DeprecationLevel.ERROR, -) -public object LoggingMessageNotification { - @Deprecated( - message = "Use `LoggingMessageNotificationParams` instead", - replaceWith = ReplaceWith( - "LoggingMessageNotificationParams", - "io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotificationParams", - ), - level = DeprecationLevel.ERROR, - ) - public fun Params( - level: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel, - data: JsonElement, - logger: String? = null, - meta: JsonObject? = null, - ): io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotificationParams = - io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotificationParams(level, data, logger, meta) - - @Deprecated( - message = "Use `SetLevelRequest` instead", - replaceWith = ReplaceWith( - "SetLevelRequest", - "io.modelcontextprotocol.kotlin.sdk.types.SetLevelRequest", - ), - level = DeprecationLevel.ERROR, - ) - public fun SetLevelRequest( - params: io.modelcontextprotocol.kotlin.sdk.types.SetLevelRequestParams, - ): io.modelcontextprotocol.kotlin.sdk.types.SetLevelRequest = - io.modelcontextprotocol.kotlin.sdk.types.SetLevelRequest(params) -} - -/** - * Notification of a log message passed from server to client. - * If no logging level request has been sent from the client, - * the server MAY decide which messages to send automatically. - */ -@Deprecated( - message = "Use `LoggingMessageNotification` instead", - replaceWith = ReplaceWith( - "LoggingMessageNotification", - "io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotification", - ), - level = DeprecationLevel.ERROR, -) -public fun LoggingMessageNotification( - params: io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotificationParams, -): io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotification = - io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotification(params) - -/* Sampling */ - -/** - * Hints to use for model selection. - */ -@Deprecated( - message = "Use `ModelHint` instead", - replaceWith = ReplaceWith("ModelHint", "io.modelcontextprotocol.kotlin.sdk.types.ModelHint"), - level = DeprecationLevel.ERROR, -) -public typealias ModelHint = io.modelcontextprotocol.kotlin.sdk.types.ModelHint - -/** - * The server's preferences for model selection, requested by the client during sampling. - */ -@Deprecated( - message = "Use `ModelPreferences` instead", - replaceWith = ReplaceWith("ModelPreferences", "io.modelcontextprotocol.kotlin.sdk.types.ModelPreferences"), - level = DeprecationLevel.ERROR, -) -public typealias ModelPreferences = io.modelcontextprotocol.kotlin.sdk.types.ModelPreferences - -/** - * Describes a message issued to or received from an LLM API. - */ -@Deprecated( - message = "Use `SamplingMessage` instead", - replaceWith = ReplaceWith("SamplingMessage", "io.modelcontextprotocol.kotlin.sdk.types.SamplingMessage"), - level = DeprecationLevel.ERROR, -) -public typealias SamplingMessage = io.modelcontextprotocol.kotlin.sdk.types.SamplingMessage - -@Deprecated( - message = "Use `CreateMessageRequest` instead", - replaceWith = ReplaceWith("CreateMessageRequest", "io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest"), - level = DeprecationLevel.ERROR, -) -public object CreateMessageRequest { - public val IncludeContext: io.modelcontextprotocol.kotlin.sdk.types.IncludeContext.Companion = - io.modelcontextprotocol.kotlin.sdk.types.IncludeContext -} - -/** - * A request from the server to sample an LLM via the client. - * The client has full discretion over which model to select. - * The client should also inform the user before beginning sampling to allow them to inspect the request - * (human in the loop) and decide whether to approve it. - */ -@Deprecated( - message = "Use `CreateMessageRequest` instead", - replaceWith = ReplaceWith("CreateMessageRequest", "io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest"), - level = DeprecationLevel.ERROR, -) -public fun CreateMessageRequest( - params: io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequestParams, -): io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest = - io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest(params) - -@Deprecated( - message = "Use `StopReason` instead", - replaceWith = ReplaceWith("StopReason", "io.modelcontextprotocol.kotlin.sdk.types.StopReason"), - level = DeprecationLevel.ERROR, -) -public typealias StopReason = io.modelcontextprotocol.kotlin.sdk.types.StopReason - -/** - * The client's response to a sampling/create_message request from the server. - * The client should inform the user before returning the sampled message to allow them to inspect the response - * (human in the loop) and decide whether to allow the server to see it. - */ -@Deprecated( - message = "Use `CreateMessageResult` instead", - replaceWith = ReplaceWith("CreateMessageResult", "io.modelcontextprotocol.kotlin.sdk.types.CreateMessageResult"), - level = DeprecationLevel.ERROR, -) -public typealias CreateMessageResult = io.modelcontextprotocol.kotlin.sdk.types.CreateMessageResult - -/* Autocomplete */ -@Deprecated( - message = "Use `Reference` instead", - replaceWith = ReplaceWith("Reference", "io.modelcontextprotocol.kotlin.sdk.types.Reference"), - level = DeprecationLevel.ERROR, -) -public typealias Reference = io.modelcontextprotocol.kotlin.sdk.types.Reference - -/** - * A reference to a resource or resource template definition. - */ -@Deprecated( - message = "Use `ResourceTemplateReference` instead", - replaceWith = ReplaceWith( - "ResourceTemplateReference", - "io.modelcontextprotocol.kotlin.sdk.types.ResourceTemplateReference", - ), - level = DeprecationLevel.ERROR, -) -public typealias ResourceTemplateReference = io.modelcontextprotocol.kotlin.sdk.types.ResourceTemplateReference - -/** - * Identifies a prompt. - */ -@Deprecated( - message = "Use `PromptReference` instead", - replaceWith = ReplaceWith("PromptReference", "io.modelcontextprotocol.kotlin.sdk.types.PromptReference"), - level = DeprecationLevel.ERROR, -) -public typealias PromptReference = io.modelcontextprotocol.kotlin.sdk.types.PromptReference - -/** - * Identifies a prompt. - */ -@Serializable -@Deprecated( - message = "This class will be removed.", - level = DeprecationLevel.ERROR, -) -public data class UnknownReference(val type: String) - -@Deprecated( - message = "Use `CompleteRequest` instead", - replaceWith = ReplaceWith("CompleteRequest", "io.modelcontextprotocol.kotlin.sdk.types.CompleteRequest"), - level = DeprecationLevel.ERROR, -) -public object CompleteRequest { - @Deprecated( - message = "Use `CompleteRequestParams.Argument` instead", - replaceWith = ReplaceWith( - "CompleteRequestParams.Argument", - "io.modelcontextprotocol.kotlin.sdk.types.CompleteRequestParams.Argument", - ), - level = DeprecationLevel.ERROR, - ) - public fun Argument( - name: String, - value: String, - ): io.modelcontextprotocol.kotlin.sdk.types.CompleteRequestParams.Argument = - io.modelcontextprotocol.kotlin.sdk.types.CompleteRequestParams.Argument(name, value) -} - -/** - * A request from the client to the server to ask for completion options. - */ -@Deprecated( - message = "Use `CompleteRequest` instead", - replaceWith = ReplaceWith("CompleteRequest", "io.modelcontextprotocol.kotlin.sdk.types.CompleteRequest"), - level = DeprecationLevel.ERROR, -) -public fun CompleteRequest( - params: io.modelcontextprotocol.kotlin.sdk.types.CompleteRequestParams, -): io.modelcontextprotocol.kotlin.sdk.types.CompleteRequest = - io.modelcontextprotocol.kotlin.sdk.types.CompleteRequest(params) - -@Deprecated( - message = "Use `CompleteResult` instead", - replaceWith = ReplaceWith("CompleteResult", "io.modelcontextprotocol.kotlin.sdk.types.CompleteResult"), - level = DeprecationLevel.ERROR, -) -public object CompleteResult { - @Deprecated( - message = "Use `CompleteResult.Completion` instead", - replaceWith = ReplaceWith( - "CompleteResult.Completion", - "io.modelcontextprotocol.kotlin.sdk.types.CompleteResult.Completion", - ), - level = DeprecationLevel.ERROR, - ) - public fun Completion( - values: List, - total: Int? = null, - hasMore: Boolean? = null, - ): io.modelcontextprotocol.kotlin.sdk.types.CompleteResult.Completion = - io.modelcontextprotocol.kotlin.sdk.types.CompleteResult.Completion(values, total, hasMore) -} - -/** - * The server's response to a completion/complete request - */ -@Deprecated( - message = "Use `CompleteResult` instead", - replaceWith = ReplaceWith("CompleteResult", "io.modelcontextprotocol.kotlin.sdk.types.CompleteResult"), - level = DeprecationLevel.ERROR, -) -public fun CompleteResult( - completion: io.modelcontextprotocol.kotlin.sdk.types.CompleteResult.Completion, - meta: JsonObject? = null, -): io.modelcontextprotocol.kotlin.sdk.types.CompleteResult = - io.modelcontextprotocol.kotlin.sdk.types.CompleteResult(completion, meta) - -/* Roots */ - -/** - * Represents a root directory or file that the server can operate on. - */ -@Deprecated( - message = "Use `Root` instead", - replaceWith = ReplaceWith("Root", "io.modelcontextprotocol.kotlin.sdk.types.Root"), - level = DeprecationLevel.ERROR, -) -public typealias Root = io.modelcontextprotocol.kotlin.sdk.types.Root - -/** - * Sent from the server to request a list of root URIs from the client. - */ -@Deprecated( - message = "Use `ListRootsRequest` instead", - replaceWith = ReplaceWith("ListRootsRequest", "io.modelcontextprotocol.kotlin.sdk.types.ListRootsRequest"), - level = DeprecationLevel.ERROR, -) -public typealias ListRootsRequest = io.modelcontextprotocol.kotlin.sdk.types.ListRootsRequest - -/** - * The client's response to a roots/list request from the server. - */ -@Deprecated( - message = "Use `ListRootsResult` instead", - replaceWith = ReplaceWith("ListRootsResult", "io.modelcontextprotocol.kotlin.sdk.types.ListRootsResult"), - level = DeprecationLevel.ERROR, -) -public typealias ListRootsResult = io.modelcontextprotocol.kotlin.sdk.types.ListRootsResult - -@Deprecated( - message = "Use `RootsListChangedNotification` instead", - replaceWith = ReplaceWith( - "RootsListChangedNotification", - "io.modelcontextprotocol.kotlin.sdk.types.RootsListChangedNotification", - ), - level = DeprecationLevel.ERROR, -) -public object RootsListChangedNotification { - @Deprecated( - message = "Use `BaseNotificationParams` instead", - replaceWith = ReplaceWith( - "BaseNotificationParams", - "io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams", - ), - level = DeprecationLevel.ERROR, - ) - public fun Params(meta: JsonObject? = null): io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams = - io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams(meta) -} - -/** - * A notification from the client to the server, informing it that the list of roots has changed. - */ -@Deprecated( - message = "Use `RootsListChangedNotification` instead", - replaceWith = ReplaceWith( - "RootsListChangedNotification", - "io.modelcontextprotocol.kotlin.sdk.types.RootsListChangedNotification", - ), - level = DeprecationLevel.ERROR, -) -public fun RootsListChangedNotification( - params: io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams? = null, -): io.modelcontextprotocol.kotlin.sdk.types.RootsListChangedNotification = - io.modelcontextprotocol.kotlin.sdk.types.RootsListChangedNotification(params) - -@Deprecated( - message = "Use `ElicitRequest` instead", - replaceWith = ReplaceWith( - "ElicitRequest", - "io.modelcontextprotocol.kotlin.sdk.types.ElicitRequest", - ), - level = DeprecationLevel.ERROR, -) -public object CreateElicitationRequest { - public fun RequestedSchema( - properties: JsonObject, - required: List? = null, - ): io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestParams.RequestedSchema = - io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestParams.RequestedSchema(properties, required) -} - -/** - * Sent from the server to create an elicitation from the client. - */ -@Deprecated( - message = "Use `ElicitRequest` instead", - replaceWith = ReplaceWith( - "ElicitRequest", - "io.modelcontextprotocol.kotlin.sdk.types.ElicitRequest", - ), - level = DeprecationLevel.ERROR, -) -public fun CreateElicitationRequest( - params: io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestParams, -): io.modelcontextprotocol.kotlin.sdk.types.ElicitRequest = - io.modelcontextprotocol.kotlin.sdk.types.ElicitRequest(params) - -@Deprecated( - message = "Use `ElicitResult` instead", - replaceWith = ReplaceWith( - "ElicitResult", - "io.modelcontextprotocol.kotlin.sdk.types.ElicitResult", - ), - level = DeprecationLevel.ERROR, -) -public object CreateElicitationResult { - @Deprecated( - message = "Use `ElicitResult.Action` instead", - replaceWith = ReplaceWith( - "ElicitResult.Action", - "io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action", - ), - level = DeprecationLevel.ERROR, - ) - public object Action { - @Deprecated( - message = "Use `ElicitResult.Action.Accept` instead", - replaceWith = ReplaceWith( - "ElicitResult.Action.Accept", - "io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action", - ), - level = DeprecationLevel.ERROR, - ) - public val accept: io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action = - io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action.Accept - - @Deprecated( - message = "Use `ElicitResult.Action.Decline` instead", - replaceWith = ReplaceWith( - "ElicitResult.Action.Decline", - "io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action", - ), - level = DeprecationLevel.ERROR, - ) - public val decline: io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action = - io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action.Decline - - @Deprecated( - message = "Use `ElicitResult.Action.Cancel` instead", - replaceWith = ReplaceWith( - "ElicitResult.Action.Cancel", - "io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action", - ), - level = DeprecationLevel.ERROR, - ) - public val cancel: io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action = - io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action.Cancel - } -} - -/** - * The client's response to an elicitation/create request from the server. - */ -@Deprecated( - message = "Use `ElicitResult` instead", - replaceWith = ReplaceWith( - "ElicitResult", - "io.modelcontextprotocol.kotlin.sdk.types.ElicitResult", - ), - level = DeprecationLevel.ERROR, -) -public fun CreateElicitationResult( - action: io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action, - content: JsonObject? = null, - meta: JsonObject? = null, -): io.modelcontextprotocol.kotlin.sdk.types.ElicitResult = - io.modelcontextprotocol.kotlin.sdk.types.ElicitResult(action, content, meta) - -/** - * Represents an error specific to the MCP protocol. - */ -@Deprecated( - message = "Use `McpException` instead", - replaceWith = ReplaceWith("McpException"), - level = DeprecationLevel.ERROR, -) -public typealias McpError = io.modelcontextprotocol.kotlin.sdk.types.McpException diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.util.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.util.kt deleted file mode 100644 index 3e0abe1..0000000 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.util.kt +++ /dev/null @@ -1,42 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk - -import io.modelcontextprotocol.kotlin.sdk.types.error -import io.modelcontextprotocol.kotlin.sdk.types.success -import kotlinx.serialization.json.JsonObject - -@Deprecated( - message = "Use `io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject` instead", - replaceWith = ReplaceWith("EmptyJsonObject", "io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject"), - level = DeprecationLevel.ERROR, -) -public val EmptyJsonObject: JsonObject = io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject - -/** - * Creates a [CallToolResult] with single [TextContent] and [meta]. - */ -@Deprecated( - message = "Use `CallToolResult.success` instead", - replaceWith = ReplaceWith("CallToolResult.Companion.success"), - level = DeprecationLevel.ERROR, -) -@Suppress("DEPRECATION_ERROR") -public fun CallToolResult.ok( - content: String, - meta: JsonObject = EmptyJsonObject, -): io.modelcontextprotocol.kotlin.sdk.types.CallToolResult = - io.modelcontextprotocol.kotlin.sdk.types.CallToolResult.success(content, meta) - -/** - * Creates a [CallToolResult] with single [TextContent] and [meta], with `isError` being true. - */ -@Deprecated( - message = "Use `CallToolResult.error` instead", - replaceWith = ReplaceWith("CallToolResult.Companion.error"), - level = DeprecationLevel.ERROR, -) -@Suppress("DEPRECATION_ERROR") -public fun CallToolResult.error( - content: String, - meta: JsonObject = EmptyJsonObject, -): io.modelcontextprotocol.kotlin.sdk.types.CallToolResult = - io.modelcontextprotocol.kotlin.sdk.types.CallToolResult.error(content, meta) diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt index 8ddee0a..7ec9c07 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt @@ -30,15 +30,6 @@ public val SUPPORTED_PROTOCOL_VERSIONS: List = listOf( public sealed interface WithMeta { @SerialName("_meta") public val meta: JsonObject? - - @Deprecated( - message = "Use 'meta' instead.", - replaceWith = ReplaceWith("meta"), - level = DeprecationLevel.ERROR, - ) - @Suppress("PropertyName", "VariableNaming") - public val _meta: JsonObject - get() = meta ?: EmptyJsonObject } // ============================================================================ diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.kt index 5ca41c6..79a76ea 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.kt @@ -39,18 +39,6 @@ public data class CompleteRequest(override val params: CompleteRequestParams) : public val meta: RequestMeta? get() = params.meta - - @Deprecated( - message = "Will be removed in a future release.", - replaceWith = ReplaceWith(expression = "CompleteRequest(CompleteRequestParams(argument, ref, context, meta))"), - level = DeprecationLevel.ERROR, - ) - public constructor( - argument: Argument, - ref: Reference, - context: Context? = null, - meta: RequestMeta? = null, - ) : this(CompleteRequestParams(argument, ref, context, meta)) } /** diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.kt index 08a7a9f..5d7054b 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.kt @@ -113,23 +113,6 @@ public data class GetPromptRequest(override val params: GetPromptRequestParams) @EncodeDefault override val method: Method = Method.Defined.PromptsGet - @Deprecated( - message = "Use constructor with GetPromptRequestParams instead", - replaceWith = ReplaceWith("GetPromptRequest(GetPromptRequestParams(name, arguments, meta))"), - level = DeprecationLevel.ERROR, - ) - public constructor( - name: String, - arguments: Map? = null, - meta: RequestMeta? = null, - ) : this( - GetPromptRequestParams( - name = name, - arguments = arguments, - meta = meta, - ), - ) - /** * The name of the prompt or prompt template to retrieve. */ @@ -204,26 +187,6 @@ public data class ListPromptsRequest(override val params: PaginatedRequestParams PaginatedRequest { @EncodeDefault override val method: Method = Method.Defined.PromptsList - - /** - * Secondary constructor for creating a [ListPromptsRequest] instance - * using optional cursor and metadata parameters. - * - * This constructor simplifies the creation of the [ListPromptsRequest] by allowing a cursor - * and metadata to be provided. - * - * @param cursor Optional cursor string to specify the starting point of the paginated request. - * @param meta Optional metadata associated with the request. - */ - @Deprecated( - message = "Use constructor with ListPromptsParams instead", - replaceWith = ReplaceWith("ListPromptsRequest(ListPromptsParams(name, arguments, meta))"), - level = DeprecationLevel.ERROR, - ) - public constructor( - cursor: String?, - meta: RequestMeta? = null, - ) : this(paginatedRequestParams(cursor, meta)) } /** diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.kt index 8782db1..fa316de 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.kt @@ -191,26 +191,6 @@ public data class ListResourcesRequest(override val params: PaginatedRequestPara PaginatedRequest { @EncodeDefault override val method: Method = Method.Defined.ResourcesList - - /** - * Secondary constructor for creating a [ListResourcesRequest] instance - * using optional cursor and metadata parameters. - * - * This constructor simplifies the creation of the [ListResourcesRequest] by allowing a cursor - * and metadata to be provided. - * - * @param cursor Optional cursor string to specify the starting point of the paginated request. - * @param meta Optional metadata associated with the request. - */ - @Deprecated( - message = "Use the constructor with PaginatedRequestParams property instead", - replaceWith = ReplaceWith("ReadResourceRequest(PaginatedRequestParams(cursor, meta))"), - level = DeprecationLevel.ERROR, - ) - public constructor( - cursor: String?, - meta: RequestMeta? = null, - ) : this(paginatedRequestParams(cursor, meta)) } /** @@ -261,16 +241,6 @@ public data class ReadResourceRequest(override val params: ReadResourceRequestPa */ public val meta: RequestMeta? get() = params.meta - - @Deprecated( - message = "Use the constructor with ReadResourceRequestParams property instead", - replaceWith = ReplaceWith("ReadResourceRequest(ReadResourceRequestParams(uri, meta))"), - level = DeprecationLevel.ERROR, - ) - public constructor( - uri: String, - meta: RequestMeta? = null, - ) : this(ReadResourceRequestParams(uri, meta)) } /** @@ -337,16 +307,6 @@ public data class SubscribeRequest(override val params: SubscribeRequestParams) */ public val meta: RequestMeta? get() = params.meta - - @Deprecated( - message = "Use the constructor with SubscribeRequestParams property instead", - replaceWith = ReplaceWith("ReadResourceRequest(SubscribeRequestParams(uri, meta))"), - level = DeprecationLevel.ERROR, - ) - public constructor( - uri: String, - meta: RequestMeta? = null, - ) : this(SubscribeRequestParams(uri, meta)) } /** @@ -433,26 +393,6 @@ public data class ListResourceTemplatesRequest(override val params: PaginatedReq PaginatedRequest { @EncodeDefault override val method: Method = Method.Defined.ResourcesTemplatesList - - /** - * Secondary constructor for creating a [ListResourceTemplatesRequest] instance - * using optional cursor and metadata parameters. - * - * This constructor simplifies the creation of the [ListResourceTemplatesRequest] by allowing a cursor - * and metadata to be provided. - * - * @param cursor Optional cursor string to specify the starting point of the paginated request. - * @param meta Optional metadata associated with the request. - */ - @Deprecated( - message = "Use the constructor with PaginatedRequestParams property instead", - replaceWith = ReplaceWith("ListResourceTemplatesRequest(PaginatedRequestParams(cursor, meta))"), - level = DeprecationLevel.ERROR, - ) - public constructor( - cursor: String?, - meta: RequestMeta? = null, - ) : this(paginatedRequestParams(cursor, meta)) } /** diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.kt index 9e7dbae..af7b1e7 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.kt @@ -62,15 +62,6 @@ public data class ListRootsRequest(override val params: BaseRequestParams? = nul public val meta: RequestMeta? get() = params?.meta - - @Deprecated( - message = "Use the constructor with BaseRequestParams property instead", - replaceWith = ReplaceWith("ListRootsRequest(BaseRequestParams(meta))"), - level = DeprecationLevel.ERROR, - ) - public constructor( - meta: RequestMeta?, - ) : this(BaseRequestParams(meta)) } /** diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt index c121f9d..4f642f4 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt @@ -267,26 +267,6 @@ public data class ListToolsRequest(override val params: PaginatedRequestParams? PaginatedRequest { @EncodeDefault override val method: Method = Method.Defined.ToolsList - - /** - * Secondary constructor for creating a [ListToolsRequest] instance - * using optional cursor and metadata parameters. - * - * This constructor simplifies the creation of the [ListToolsRequest] by allowing a cursor - * and metadata to be provided. - * - * @param cursor Optional cursor string to specify the starting point of the paginated request. - * @param meta Optional metadata associated with the request. - */ - @Deprecated( - message = "Use the constructor with BaseRequestParams property instead", - replaceWith = ReplaceWith("ListToolsRequest(BaseRequestParams(meta))"), - level = DeprecationLevel.ERROR, - ) - public constructor( - cursor: String?, - meta: RequestMeta? = null, - ) : this(paginatedRequestParams(cursor, meta)) } /** diff --git a/kotlin-sdk-server/api/kotlin-sdk-server.api b/kotlin-sdk-server/api/kotlin-sdk-server.api index b1b898a..fef34b2 100644 --- a/kotlin-sdk-server/api/kotlin-sdk-server.api +++ b/kotlin-sdk-server/api/kotlin-sdk-server.api @@ -107,7 +107,6 @@ public class io/modelcontextprotocol/kotlin/sdk/server/Server { public final fun addTools (Ljava/util/List;)V public final fun clientConnection (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/server/ClientConnection; public final fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun connect (Lio/modelcontextprotocol/kotlin/sdk/shared/Transport;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun createElicitation (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun createElicitation$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun createMessage (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -125,7 +124,6 @@ public class io/modelcontextprotocol/kotlin/sdk/server/Server { public static synthetic fun listRoots$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun onClose (Lkotlin/jvm/functions/Function0;)V public final fun onConnect (Lkotlin/jvm/functions/Function0;)V - public final fun onInitialized (Lkotlin/jvm/functions/Function0;)V public final fun ping (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun removeNotificationHandler (Lio/modelcontextprotocol/kotlin/sdk/types/Method;)V public final fun removeNotificationHandler (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Method;)V diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt index e221482..afed86a 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt @@ -118,8 +118,6 @@ public open class Server( block: Server.() -> Unit = {}, ) : this(serverInfo, options, { instructions }, block) - private var _onInitialized: (() -> Unit) = {} - private var _onConnect: (() -> Unit) = {} private var _onClose: () -> Unit = {} @@ -197,20 +195,6 @@ public open class Server( _onClose() } - /** - * Starts a new server session with the given transport and initializes - * internal request handlers based on the server's capabilities. - * - * @param transport The transport layer to connect the session with. - * @return The initialized and connected server session. - */ - @Deprecated( - "Use createSession(transport) instead.", - ReplaceWith("createSession(transport)"), - DeprecationLevel.ERROR, - ) - public suspend fun connect(transport: Transport): ServerSession = createSession(transport) - /** * Starts a new server session with the given transport and initializes * internal request handlers based on the server's capabilities. @@ -294,22 +278,6 @@ public open class Server( } } - /** - * Registers a callback to be invoked when the server has completed initialization. - */ - @Deprecated( - "Initialization moved to ServerSession, use ServerSession.onInitialized instead.", - ReplaceWith("ServerSession.onInitialized"), - DeprecationLevel.ERROR, - ) - public fun onInitialized(block: () -> Unit) { - val old = _onInitialized - _onInitialized = { - old() - block() - } - } - /** * Registers a callback to be invoked when the server connection is closing. */ From 435698a611b106efcf5dfa737b4291e5036edfff Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Thu, 26 Mar 2026 13:19:10 +0100 Subject: [PATCH 188/449] refactor(integration-test): modernize ts tests, update versions, rename SSE -> HTTP (#618) - add tsconfig - fix tsignore - rename sse -> http - clean up code ## How Has This Been Tested? all tests passed ## Breaking Changes none ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- .../main/kotlin/mcp.multiplatform.gradle.kts | 1 - config/detekt/detekt.yml | 4 + integration-test/detekt-baseline-test.xml | 14 -- .../integration/AbstractAuthenticationTest.kt | 1 - ...tractKotlinClientTsServerEdgeCasesTest.kt} | 45 ++--- .../sdk/integration/typescript/TsTestBase.kt | 26 +-- .../typescript/TypeScriptClient.kt | 10 +- .../typescript/TypeScriptServer.kt | 20 +-- .../KotlinClientTsServerEdgeCasesTestHttp.kt | 14 ++ .../KotlinClientTsServerTestHttp.kt} | 9 +- .../KotlinServerForTsClientHttp.kt} | 48 ++--- .../TsClientKotlinServerTestHttp.kt} | 10 +- .../TsEdgeCasesTestHttp.kt} | 26 +-- .../KotlinClientTsServerEdgeCasesTestStdio.kt | 167 +----------------- .../client/{sse-client.ts => http-client.ts} | 39 ++-- .../jvmTest/typescript/client/stdio-client.ts | 21 +-- .../src/jvmTest/typescript/package-lock.json | 71 ++------ .../src/jvmTest/typescript/package.json | 10 +- .../server/{sse-server.ts => http-server.ts} | 55 ++---- .../src/jvmTest/typescript/tsconfig.json | 12 ++ kotlin-sdk-client/detekt-baseline-test.xml | 2 - .../kotlin/test/utils/testing.kt | 1 - 22 files changed, 169 insertions(+), 437 deletions(-) rename integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/{sse/KotlinClientTsServerEdgeCasesTestSse.kt => AbstractKotlinClientTsServerEdgeCasesTest.kt} (82%) create mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/http/KotlinClientTsServerEdgeCasesTestHttp.kt rename integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/{sse/KotlinClientTsServerTestSse.kt => http/KotlinClientTsServerTestHttp.kt} (75%) rename integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/{sse/KotlinServerForTsClientSse.kt => http/KotlinServerForTsClientHttp.kt} (92%) rename integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/{sse/TsClientKotlinServerTestSse.kt => http/TsClientKotlinServerTestHttp.kt} (85%) rename integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/{sse/TsEdgeCasesTestSse.kt => http/TsEdgeCasesTestHttp.kt} (90%) rename integration-test/src/jvmTest/typescript/client/{sse-client.ts => http-client.ts} (67%) rename integration-test/src/jvmTest/typescript/server/{sse-server.ts => http-server.ts} (80%) create mode 100644 integration-test/src/jvmTest/typescript/tsconfig.json diff --git a/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts b/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts index f3d4616..36097ff 100644 --- a/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts +++ b/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts @@ -39,7 +39,6 @@ kotlin { forkEvery = 100 testLogging { exceptionFormat = TestExceptionFormat.SHORT - showStandardStreams = true events("failed") } systemProperty("kotest.output.ansi", "true") diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml index 8c19749..2cb135a 100644 --- a/config/detekt/detekt.yml +++ b/config/detekt/detekt.yml @@ -14,6 +14,10 @@ complexity: LongMethod: excludes: *testFolders +coroutines: + InjectDispatcher: + excludes: *testFolders + empty-blocks: EmptyFunctionBlock: excludes: *testFolders diff --git a/integration-test/detekt-baseline-test.xml b/integration-test/detekt-baseline-test.xml index 710b729..2df611a 100644 --- a/integration-test/detekt-baseline-test.xml +++ b/integration-test/detekt-baseline-test.xml @@ -8,24 +8,10 @@ AbstractClassCanBeConcreteClass:BaseTransportTest.kt:BaseTransportTest$BaseTransportTest CyclomaticComplexMethod:AbstractToolIntegrationTest.kt:AbstractToolIntegrationTest$private fun setupCalculatorTool ForbiddenComment:StdioClientTransportTest.kt:StdioClientTransportTest$// TODO: fix running on windows - InjectDispatcher:AbstractKotlinClientTsServerTest.kt:AbstractKotlinClientTsServerTest$IO - InjectDispatcher:AbstractPromptIntegrationTest.kt:AbstractPromptIntegrationTest$IO - InjectDispatcher:AbstractResourceIntegrationTest.kt:AbstractResourceIntegrationTest$IO - InjectDispatcher:AbstractToolIntegrationTest.kt:AbstractToolIntegrationTest$IO - InjectDispatcher:KotlinClientTsServerEdgeCasesTestSse.kt:KotlinClientTsServerEdgeCasesTestSse$IO - InjectDispatcher:KotlinClientTsServerEdgeCasesTestStdio.kt:KotlinClientTsServerEdgeCasesTestStdio$IO - InjectDispatcher:SseIntegrationTest.kt:SseIntegrationTest$IO - InjectDispatcher:StdioClientTransportTest.kt:StdioClientTransportTest$IO - InjectDispatcher:StreamableHttpIntegrationTest.kt:StreamableHttpIntegrationTest$IO - InjectDispatcher:TsEdgeCasesTestSse.kt:TsEdgeCasesTestSse$IO - InjectDispatcher:WebSocketIntegrationTest.kt:WebSocketIntegrationTest$IO MatchingDeclarationName:PromptIntegrationTestSse.kt:SchemaPromptIntegrationTestSse : AbstractPromptIntegrationTest - SleepInsteadOfDelay:KotlinServerForTsClientSse.kt:KotlinServerForTsClient$sleep(500) ThrowsCount:AbstractPromptIntegrationTest.kt:AbstractPromptIntegrationTest$override fun configureServer - UnusedPrivateProperty:KotlinClientTsServerTestSse.kt:KotlinClientTsServerTestSse$private val host = "localhost" UseCheckOrError:ClientTest.kt:ClientTest.<no name provided>$throw IllegalStateException("Test error") UseOrEmpty:AbstractToolIntegrationTest.kt:AbstractToolIntegrationTest$(request.params.arguments?.get("tags") as? JsonArray)?.mapNotNull { (it as? JsonPrimitive)?.content } ?: emptyList() UseRequire:AbstractPromptIntegrationTest.kt:AbstractPromptIntegrationTest$throw IllegalArgumentException("Missing required argument: $argName") - VarCouldBeVal:KotlinClientTsServerEdgeCasesTestSse.kt:KotlinClientTsServerEdgeCasesTestSse$private lateinit var client: Client
diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/AbstractAuthenticationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/AbstractAuthenticationTest.kt index 1e88558..ec42041 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/AbstractAuthenticationTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/AbstractAuthenticationTest.kt @@ -42,7 +42,6 @@ import io.ktor.server.sse.SSE as ServerSSE /** * Base class for MCP authentication integration tests. */ -@Suppress("InjectDispatcher") abstract class AbstractAuthenticationTest { protected companion object { diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerEdgeCasesTestSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractKotlinClientTsServerEdgeCasesTest.kt similarity index 82% rename from integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerEdgeCasesTestSse.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractKotlinClientTsServerEdgeCasesTest.kt index 356fd30..d977bd0 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerEdgeCasesTestSse.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/AbstractKotlinClientTsServerEdgeCasesTest.kt @@ -1,53 +1,29 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse +package io.modelcontextprotocol.kotlin.sdk.integration.typescript import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TsTestBase import io.modelcontextprotocol.kotlin.sdk.types.TextContent import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withTimeout import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive -import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.Timeout import java.util.concurrent.TimeUnit import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue -import kotlin.time.Duration.Companion.seconds -class KotlinClientTsServerEdgeCasesTestSse : TsTestBase() { +abstract class AbstractKotlinClientTsServerEdgeCasesTest : TsTestBase() { - override val transportKind = TransportKind.SSE - - private val serverUrl: String by lazy { getSharedSseUrl() } - - private lateinit var client: Client - - @AfterEach - fun tearDown() { - if (::client.isInitialized) { - try { - runBlocking { - withTimeout(3.seconds) { - client.close() - } - } - } catch (e: Exception) { - println("Warning: Error during client close: ${e.message}") - } - } - } + protected abstract suspend fun useClient(block: suspend (Client) -> T): T @Test @Timeout(30, unit = TimeUnit.SECONDS) fun testNonExistentTool(): Unit = runBlocking(Dispatchers.IO) { - withClient(serverUrl) { client -> + useClient { client -> val nonExistentToolName = "non-existent-tool" val arguments = mapOf("name" to "TestUser") @@ -70,7 +46,7 @@ class KotlinClientTsServerEdgeCasesTestSse : TsTestBase() { @Test @Timeout(30, unit = TimeUnit.SECONDS) fun testSpecialCharactersInArguments(): Unit = runBlocking(Dispatchers.IO) { - withClient(serverUrl) { client -> + useClient { client -> val specialChars = "!@#$%^&*()_+{}[]|\\:;\"'<>,.?/" val arguments = mapOf("name" to specialChars) @@ -91,7 +67,7 @@ class KotlinClientTsServerEdgeCasesTestSse : TsTestBase() { @Test @Timeout(30, unit = TimeUnit.SECONDS) fun testLargePayload(): Unit = runBlocking(Dispatchers.IO) { - withClient(serverUrl) { client -> + useClient { client -> val largeName = "A".repeat(10 * 1024) val arguments = mapOf("name" to largeName) @@ -112,7 +88,7 @@ class KotlinClientTsServerEdgeCasesTestSse : TsTestBase() { @Test @Timeout(60, unit = TimeUnit.SECONDS) fun testConcurrentRequests(): Unit = runBlocking(Dispatchers.IO) { - withClient(serverUrl) { client -> + useClient { client -> val concurrentCount = 5 val responses = coroutineScope { val results = (1..concurrentCount).map { i -> @@ -147,7 +123,7 @@ class KotlinClientTsServerEdgeCasesTestSse : TsTestBase() { @Test @Timeout(30, unit = TimeUnit.SECONDS) fun testInvalidArguments(): Unit = runBlocking(Dispatchers.IO) { - withClient(serverUrl) { client -> + useClient { client -> val invalidArguments = mapOf( "name" to JsonObject(mapOf("nested" to JsonPrimitive("value"))), ) @@ -171,7 +147,7 @@ class KotlinClientTsServerEdgeCasesTestSse : TsTestBase() { @Test @Timeout(30, unit = TimeUnit.SECONDS) fun testMultipleToolCalls(): Unit = runBlocking(Dispatchers.IO) { - withClient(serverUrl) { client -> + useClient { client -> repeat(10) { i -> val name = "SequentialClient$i" val arguments = mapOf("name" to name) @@ -179,8 +155,7 @@ class KotlinClientTsServerEdgeCasesTestSse : TsTestBase() { val result = client.callTool("greet", arguments) assertNotNull(result, "Tool call result should not be null for call $i") - val callResult = result - val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent + val textContent = result.content.firstOrNull { it is TextContent } as? TextContent assertNotNull(textContent, "Text content should be present for call $i") assertEquals( diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TsTestBase.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TsTestBase.kt index aa47a70..29a6de7 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TsTestBase.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TsTestBase.kt @@ -6,7 +6,7 @@ import io.ktor.client.plugins.sse.SSE import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport import io.modelcontextprotocol.kotlin.sdk.client.mcpStreamableHttp -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse.KotlinServerForTsClient +import io.modelcontextprotocol.kotlin.sdk.integration.typescript.http.KotlinServerForTsClient import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport import io.modelcontextprotocol.kotlin.sdk.types.Implementation @@ -26,7 +26,7 @@ import java.util.concurrent.TimeUnit import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds -enum class TransportKind { SSE, STDIO } +enum class TransportKind { HTTP, STDIO } @Retry(times = 3) abstract class TsTestBase { @@ -43,10 +43,10 @@ abstract class TsTestBase { protected val typescriptDir = File(projectRoot, "src/jvmTest/typescript") @JvmStatic - private var sharedSseServer: Process? = null + private var sharedHttpServer: Process? = null @JvmStatic - private var sharedSsePort: Int = 0 + private var sharedHttpPort: Int = 0 init { installDependencies(typescriptDir) @@ -54,23 +54,23 @@ abstract class TsTestBase { @JvmStatic @Synchronized - protected fun getSharedSseUrl(): String { - if (sharedSseServer == null || !sharedSseServer!!.isAlive) { - sharedSsePort = io.modelcontextprotocol.kotlin.test.utils.findFreePort() + protected fun getSharedHttpUrl(): String { + if (sharedHttpServer == null || !sharedHttpServer!!.isAlive) { + sharedHttpPort = io.modelcontextprotocol.kotlin.test.utils.findFreePort() val server = TypeScriptServer(typescriptDir) - sharedSseServer = server.startSse(sharedSsePort) - println("Shared TypeScript SSE server started on port $sharedSsePort") + sharedHttpServer = server.startHttp(sharedHttpPort) + println("Shared TypeScript HTTP server started on port $sharedHttpPort") Runtime.getRuntime().addShutdownHook( Thread { - sharedSseServer?.let { - println("Stopping shared TypeScript SSE server") + sharedHttpServer?.let { + println("Stopping shared TypeScript HTTP server") io.modelcontextprotocol.kotlin.test.utils.stopProcess(it) } }, ) } - return "http://localhost:$sharedSsePort/mcp" + return "http://localhost:$sharedHttpPort/mcp" } @JvmStatic @@ -141,7 +141,7 @@ abstract class TsTestBase { println("$name stopped") } - // ===== SSE client helpers ===== + // ===== HTTP client helpers ===== protected suspend fun newClient(serverUrl: String): Client = HttpClient(CIO) { install(SSE) }.mcpStreamableHttp(serverUrl) diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TypeScriptClient.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TypeScriptClient.kt index 7664abf..109eab4 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TypeScriptClient.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TypeScriptClient.kt @@ -7,28 +7,28 @@ import java.io.File /** * Component for starting and managing TypeScript MCP clients for integration tests. * - * This class provides methods to start TypeScript clients using SSE or STDIO transports. + * This class provides methods to start TypeScript clients using HTTP or STDIO transports. * The clients are started using npx tsx to execute TypeScript files directly. */ class TypeScriptClient(private val typescriptDir: File) : AutoCloseable { private var process: Process? = null /** - * Starts a TypeScript MCP client using SSE transport. + * Starts a TypeScript MCP client using Streamable HTTP transport. * * @param arguments Arguments to pass to the client (typically server URL, tool name, and tool args) * @param log Whether to automatically log process output/error * @return The started Process */ - fun startSse(arguments: List, log: Boolean = true): Process { - val scriptPath = File(typescriptDir, "client/sse-client.ts").absolutePath + fun startHttp(arguments: List, log: Boolean = true): Process { + val scriptPath = File(typescriptDir, "client/http-client.ts").absolutePath val proc = TypeScriptRunner.run( typescriptDir = typescriptDir, scriptPath = scriptPath, arguments = arguments, env = emptyMap(), redirectErrorStream = false, - logPrefix = "TS-CLIENT-SSE", + logPrefix = "TS-CLIENT-HTTP", log = log, ) process = proc diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TypeScriptServer.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TypeScriptServer.kt index 579f96d..411a212 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TypeScriptServer.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TypeScriptServer.kt @@ -12,23 +12,23 @@ import java.util.concurrent.TimeUnit * Factory class for starting and managing TypeScript MCP servers for integration tests. * * This class provides methods to: - * - Start TypeScript servers using SSE or STDIO transports via npx tsx + * - Start TypeScript servers using HTTP or STDIO transports via npx tsx * - Install npm dependencies in the TypeScript test directory * - Manage server lifecycle (start/stop) * - Handle port management and process cleanup * * The servers are started using npx tsx to execute TypeScript files directly: * - `npx tsx server/stdio-server.ts` for STDIO transport - * - `npx tsx server/sse-server.ts` for SSE transport + * - `npx tsx server/http-server.ts` for HTTP transport * * Usage: * ```kotlin * val typescriptDir = File("kotlin-sdk-test/src/jvmTest/typescript") * val server = TypeScriptServer(typescriptDir) * - * // Start SSE server + * // Start HTTP server * val port = TypeScriptServer.findFreePort() - * val process = server.startSse(port) + * val process = server.startHttp(port) * * // Stop server * server.stop() @@ -38,27 +38,27 @@ class TypeScriptServer(private val typescriptDir: File) { private var process: Process? = null /** - * Starts a TypeScript MCP server using SSE transport. + * Starts a TypeScript MCP server using Streamable HTTP transport. * - * @param port The port number to bind the SSE server to + * @param port The port number to bind the HTTP server to * @return The started Process * @throws IllegalStateException if the server fails to start within timeout */ - fun startSse(port: Int): Process { + fun startHttp(port: Int): Process { killProcessOnPort(port) - val serverPath = File(typescriptDir, "server/sse-server.ts").absolutePath + val serverPath = File(typescriptDir, "server/http-server.ts").absolutePath val proc = TypeScriptRunner.run( typescriptDir = typescriptDir, scriptPath = serverPath, env = mapOf("MCP_PORT" to port.toString()), redirectErrorStream = true, - logPrefix = "TS-SERVER-SSE", + logPrefix = "TS-SERVER-HTTP", ) process = proc check(waitForPort(port = port, timeoutSeconds = 60)) { proc.destroyForcibly() - "TypeScript SSE server did not become ready on localhost:$port within timeout" + "TypeScript HTTP server did not become ready on localhost:$port within timeout" } return proc } diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/http/KotlinClientTsServerEdgeCasesTestHttp.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/http/KotlinClientTsServerEdgeCasesTestHttp.kt new file mode 100644 index 0000000..016aa1c --- /dev/null +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/http/KotlinClientTsServerEdgeCasesTestHttp.kt @@ -0,0 +1,14 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.typescript.http + +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.integration.typescript.AbstractKotlinClientTsServerEdgeCasesTest +import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind + +class KotlinClientTsServerEdgeCasesTestHttp : AbstractKotlinClientTsServerEdgeCasesTest() { + + override val transportKind = TransportKind.HTTP + + private val serverUrl: String by lazy { getSharedHttpUrl() } + + override suspend fun useClient(block: suspend (Client) -> T): T = withClient(serverUrl, block) +} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerTestSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/http/KotlinClientTsServerTestHttp.kt similarity index 75% rename from integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerTestSse.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/http/KotlinClientTsServerTestHttp.kt index 2006285..7d919af 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinClientTsServerTestSse.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/http/KotlinClientTsServerTestHttp.kt @@ -1,4 +1,4 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse +package io.modelcontextprotocol.kotlin.sdk.integration.typescript.http import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.integration.typescript.AbstractKotlinClientTsServerTest @@ -6,11 +6,10 @@ import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind import kotlinx.coroutines.withTimeout import kotlin.time.Duration.Companion.seconds -class KotlinClientTsServerTestSse : AbstractKotlinClientTsServerTest() { - override val transportKind = TransportKind.SSE +class KotlinClientTsServerTestHttp : AbstractKotlinClientTsServerTest() { + override val transportKind = TransportKind.HTTP - private val host = "localhost" - private val serverUrl: String by lazy { getSharedSseUrl() } + private val serverUrl: String by lazy { getSharedHttpUrl() } override suspend fun useClient(block: suspend (Client) -> T): T = withClient(serverUrl) { client -> try { diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/http/KotlinServerForTsClientHttp.kt similarity index 92% rename from integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/http/KotlinServerForTsClientHttp.kt index 97acc29..ebd0499 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/http/KotlinServerForTsClientHttp.kt @@ -1,4 +1,4 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse +package io.modelcontextprotocol.kotlin.sdk.integration.typescript.http import io.github.oshai.kotlinlogging.KotlinLogging import io.ktor.http.ContentType @@ -42,8 +42,9 @@ import io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.launch import kotlinx.coroutines.withTimeoutOrNull import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonElement @@ -132,14 +133,16 @@ class KotlinServerForTsClient { call.response.header("mcp-session-id", newSessionId) - val serverThread = Thread { - runBlocking { + launch(Dispatchers.IO) { + try { mcpServer.createSession(transport) + } catch (e: Exception) { + logger.error(e) { "Failed to create MCP session" } + transport.ready.completeExceptionally(e) } } - serverThread.start() - Thread.sleep(500) + transport.ready.await() transport.handleRequest(call, jsonElement) } else { @@ -173,9 +176,7 @@ class KotlinServerForTsClient { logger.info { "Terminating session: $sessionId" } val transport = serverTransports[sessionId]!! serverTransports.remove(sessionId) - runBlocking { - transport.close() - } + transport.close() call.respond(HttpStatusCode.OK) } else { logger.warn { "Invalid session termination request: $sessionId" } @@ -313,9 +314,10 @@ class HttpServerTransport(private val sessionId: String) : AbstractTransport() { private val logger = KotlinLogging.logger {} private val pendingResponses = ConcurrentHashMap>() private val messageQueue = Channel(Channel.UNLIMITED) + internal val ready = CompletableDeferred() suspend fun stream(call: ApplicationCall) { - logger.debug { "Starting SSE stream for session: $sessionId" } + logger.debug { "Starting Event stream for session: $sessionId" } call.response.header("Cache-Control", "no-cache") call.response.header("Connection", "keep-alive") call.respondTextWriter(ContentType.Text.EventStream) { @@ -331,29 +333,28 @@ class HttpServerTransport(private val sessionId: String) : AbstractTransport() { flush() } } catch (e: Exception) { - logger.warn(e) { "SSE stream terminated for session: $sessionId" } + logger.warn(e) { "Event stream terminated for session: $sessionId" } } finally { - logger.debug { "SSE stream closed for session: $sessionId" } + logger.debug { "Event stream closed for session: $sessionId" } } } } suspend fun handleRequest(call: ApplicationCall, requestBody: JsonElement) { try { - logger.info { "Handling request body: $requestBody" } + logger.trace { "Handling request body: $requestBody" } val message = McpJson.decodeFromJsonElement(requestBody) - logger.info { "Decoded message: $message" } + logger.debug { "Decoded message: $message" } if (message is JSONRPCRequest) { val id = message.id.toString() - logger.info { "Received request with ID: $id, method: ${message.method}" } + logger.debug { "Received request with ID: $id, method: ${message.method}" } val responseDeferred = CompletableDeferred() pendingResponses[id] = responseDeferred - logger.info { "Created deferred response for ID: $id" } - logger.info { "Invoking onMessage handler" } + logger.trace { "Invoking onMessage handler for ID: $id" } _onMessage.invoke(message) - logger.info { "onMessage handler completed" } + logger.trace { "onMessage handler completed for ID: $id" } try { val response = withTimeoutOrNull(10000) { @@ -426,29 +427,28 @@ class HttpServerTransport(private val sessionId: String) : AbstractTransport() { override suspend fun start() { logger.debug { "Starting HTTP server transport for session: $sessionId" } + ready.complete(Unit) } override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { - logger.info { "Sending message: $message" } + logger.trace { "Sending message: $message" } if (message is JSONRPCResponse) { val id = message.id.toString() - logger.info { "Sending response for request ID: $id" } + logger.debug { "Completing response for request ID: $id" } val deferred = pendingResponses.remove(id) if (deferred != null) { - logger.info { "Found pending response for ID: $id, completing deferred" } deferred.complete(message) return } else { logger.warn { "No pending response found for ID: $id" } } } else if (message is JSONRPCRequest) { - logger.info { "Sending request with ID: ${message.id}" } + logger.debug { "Sending request with ID: ${message.id}" } } else if (message is JSONRPCNotification) { - logger.info { "Sending notification: ${message.method}" } + logger.debug { "Sending notification: ${message.method}" } } - logger.info { "Queueing message for next client request" } messageQueue.send(message) } diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsClientKotlinServerTestSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/http/TsClientKotlinServerTestHttp.kt similarity index 85% rename from integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsClientKotlinServerTestSse.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/http/TsClientKotlinServerTestHttp.kt index dd43b70..eff91e5 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsClientKotlinServerTestSse.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/http/TsClientKotlinServerTestHttp.kt @@ -1,4 +1,4 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse +package io.modelcontextprotocol.kotlin.sdk.integration.typescript.http import io.modelcontextprotocol.kotlin.sdk.integration.typescript.AbstractTsClientKotlinServerTest import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind @@ -7,9 +7,9 @@ import io.modelcontextprotocol.kotlin.test.utils.killProcessOnPort import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach -class TsClientKotlinServerTestSse : AbstractTsClientKotlinServerTest() { +class TsClientKotlinServerTestHttp : AbstractTsClientKotlinServerTest() { - override val transportKind = TransportKind.SSE + override val transportKind = TransportKind.HTTP private var port: Int = 0 private lateinit var serverUrl: String @@ -36,11 +36,11 @@ class TsClientKotlinServerTestSse : AbstractTsClientKotlinServerTest() { } override fun runClient(vararg args: String): String = tsClient.use { client -> - val process = client.startSse(listOf(serverUrl) + args.toList(), log = true) + val process = client.startHttp(listOf(serverUrl) + args.toList(), log = true) val output = StringBuilder() process.inputStream.bufferedReader().useLines { lines -> lines.forEach { line -> - println("[TS-CLIENT-SSE] $line") + println("[TS-CLIENT-HTTP] $line") output.append(line).append("\n") } } diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsEdgeCasesTestSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/http/TsEdgeCasesTestHttp.kt similarity index 90% rename from integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsEdgeCasesTestSse.kt rename to integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/http/TsEdgeCasesTestHttp.kt index ee81ce2..9326cc6 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/TsEdgeCasesTestSse.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/http/TsEdgeCasesTestHttp.kt @@ -1,4 +1,4 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse +package io.modelcontextprotocol.kotlin.sdk.integration.typescript.http import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TsTestBase @@ -21,9 +21,9 @@ import kotlin.test.assertEquals import kotlin.test.assertTrue import kotlin.test.fail -class TsEdgeCasesTestSse : TsTestBase() { +class TsEdgeCasesTestHttp : TsTestBase() { - override val transportKind = TransportKind.SSE + override val transportKind = TransportKind.HTTP private var port: Int = 0 private lateinit var serverUrl: String @@ -55,7 +55,7 @@ class TsEdgeCasesTestSse : TsTestBase() { @Test @Timeout(30, unit = TimeUnit.SECONDS) fun testInvalidURL() = runTest { - val nonExistentToolCommand = "npx tsx client/sse-client.ts $serverUrl non-existent-tool" + val nonExistentToolCommand = "npx tsx client/http-client.ts $serverUrl non-existent-tool" val nonExistentToolOutput = executeCommandAllowingFailure(nonExistentToolCommand, typescriptDir) assertTrue( @@ -63,7 +63,7 @@ class TsEdgeCasesTestSse : TsTestBase() { "Client should handle non-existent tool gracefully", ) - val invalidUrlCommand = "npx tsx client/sse-client.ts http://localhost:${port + 1000}/mcp greet TestUser" + val invalidUrlCommand = "npx tsx client/http-client.ts http://localhost:${port + 1000}/mcp greet TestUser" val invalidUrlOutput = executeCommandAllowingFailure(invalidUrlCommand, typescriptDir) assertTrue( @@ -84,7 +84,7 @@ class TsEdgeCasesTestSse : TsTestBase() { tempFile.deleteOnExit() val specialCharsContent = tempFile.readText() - val specialCharsCommand = "npx tsx client/sse-client.ts $serverUrl greet \"$specialCharsContent\"" + val specialCharsCommand = "npx tsx client/http-client.ts $serverUrl greet \"$specialCharsContent\"" val specialCharsOutput = executeCommand(specialCharsCommand, typescriptDir) assertTrue( @@ -109,7 +109,7 @@ class TsEdgeCasesTestSse : TsTestBase() { tempFile.deleteOnExit() val largeNameContent = tempFile.readText() - val largePayloadCommand = "npx tsx client/sse-client.ts $serverUrl greet \"$largeNameContent\"" + val largePayloadCommand = "npx tsx client/http-client.ts $serverUrl greet \"$largeNameContent\"" val largePayloadOutput = executeCommand(largePayloadCommand, typescriptDir) tempFile.delete() @@ -157,11 +157,11 @@ class TsEdgeCasesTestSse : TsTestBase() { } val commands = listOf( - "npx tsx client/sse-client.ts $serverUrl greet \"Client1\"", - "npx tsx client/sse-client.ts $serverUrl multi-greet \"Client2\"", - "npx tsx client/sse-client.ts $serverUrl greet \"Client3\"", - "npx tsx client/sse-client.ts $serverUrl", - "npx tsx client/sse-client.ts $serverUrl multi-greet \"Client5\"", + "npx tsx client/http-client.ts $serverUrl greet \"Client1\"", + "npx tsx client/http-client.ts $serverUrl multi-greet \"Client2\"", + "npx tsx client/http-client.ts $serverUrl greet \"Client3\"", + "npx tsx client/http-client.ts $serverUrl", + "npx tsx client/http-client.ts $serverUrl multi-greet \"Client5\"", ) coroutineScope { @@ -242,7 +242,7 @@ class TsEdgeCasesTestSse : TsTestBase() { @Timeout(120, unit = TimeUnit.SECONDS) fun testRapidSequentialRequests() = runBlocking { val outputs = (1..10).map { i -> - val command = "npx tsx client/sse-client.ts $serverUrl greet \"RapidClient$i\"" + val command = "npx tsx client/http-client.ts $serverUrl greet \"RapidClient$i\"" val output = executeCommand(command, typescriptDir) assertTrue( diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt index 2bdd361..b8c8e93 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/KotlinClientTsServerEdgeCasesTestStdio.kt @@ -1,173 +1,12 @@ package io.modelcontextprotocol.kotlin.sdk.integration.typescript.stdio import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.integration.typescript.AbstractKotlinClientTsServerEdgeCasesTest import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TsTestBase -import io.modelcontextprotocol.kotlin.sdk.types.TextContent -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.runBlocking -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.Timeout -import java.util.concurrent.TimeUnit -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertTrue -class KotlinClientTsServerEdgeCasesTestStdio : TsTestBase() { +class KotlinClientTsServerEdgeCasesTestStdio : AbstractKotlinClientTsServerEdgeCasesTest() { override val transportKind = TransportKind.STDIO - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun testNonExistentToolOverStdio(): Unit = runBlocking(Dispatchers.IO) { - withClientStdio { client: Client, _ -> - val nonExistentToolName = "non-existent-tool" - val arguments = mapOf("name" to "TestUser") - - val result = client.callTool(nonExistentToolName, arguments) - assertNotNull(result, "Tool call result should not be null") - - assertTrue(result.isError ?: false, "isError should be true for non-existent tool") - - val textContent = result.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Error content should be present in the result") - - val errorText = textContent.text - assertTrue( - errorText.contains("non-existent-tool") && errorText.contains("not found"), - "Error message should indicate the tool was not found", - ) - } - } - - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun testSpecialCharactersInArgumentsOverStdio(): Unit = runBlocking(Dispatchers.IO) { - withClientStdio { client: Client, _ -> - val specialChars = "!@#$%^&*()_+{}[]|\\:;\"'<>.,?/" - val arguments = mapOf("name" to specialChars) - - val result = client.callTool("greet", arguments) - assertNotNull(result, "Tool call result should not be null") - - val textContent = result.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Text content should be present in the result") - - val text = textContent.text - assertTrue( - text.contains(specialChars), - "Tool response should contain the special characters", - ) - } - } - - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun testLargePayloadOverStdio(): Unit = runBlocking(Dispatchers.IO) { - withClientStdio { client: Client, _ -> - val largeName = "A".repeat(10 * 1024) - val arguments = mapOf("name" to largeName) - - val result = client.callTool("greet", arguments) - assertNotNull(result, "Tool call result should not be null") - - val callResult = result - val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Text content should be present in the result") - - val text = textContent.text - assertTrue( - text.contains("Hello,") && text.contains("A"), - "Tool response should contain the greeting with the large name", - ) - } - } - - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun testConcurrentRequestsOverStdio(): Unit = runBlocking(Dispatchers.IO) { - withClientStdio { client: Client, _ -> - val concurrentCount = 5 - val responses = coroutineScope { - val results = (1..concurrentCount).map { i -> - async { - val name = "ConcurrentClient$i" - val arguments = mapOf("name" to name) - - val result = client.callTool("greet", arguments) - assertNotNull(result, "Tool call result should not be null for client $i") - - val textContent = result.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Text content should be present for client $i") - - textContent.text - } - } - results.awaitAll() - } - - for (i in 1..concurrentCount) { - val expectedName = "ConcurrentClient$i" - val matchingResponses = responses.filter { it.contains("Hello, $expectedName!") } - assertEquals( - 1, - matchingResponses.size, - "Should have exactly one response for $expectedName", - ) - } - } - } - - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun testInvalidArgumentsOverStdio(): Unit = runBlocking(Dispatchers.IO) { - withClientStdio { client: Client, _ -> - val invalidArguments = mapOf( - "name" to JsonObject(mapOf("nested" to JsonPrimitive("value"))), - ) - - val result = client.callTool("greet", invalidArguments) - assertNotNull(result, "Tool call result should not be null") - - val callResult = result - assertTrue(callResult.isError ?: false, "isError should be true for invalid arguments") - - val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Error content should be present in the result") - - val errorText = textContent.text - assertTrue( - errorText.contains("Invalid arguments") && errorText.contains("greet"), - "Error message should indicate invalid arguments for tool greet", - ) - } - } - - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun testMultipleToolCallsOverStdio(): Unit = runBlocking(Dispatchers.IO) { - withClientStdio { client: Client, _ -> - repeat(10) { i -> - val name = "SequentialClient$i" - val arguments = mapOf("name" to name) - - val result = client.callTool("greet", arguments) - assertNotNull(result, "Tool call result should not be null for call $i") - - val textContent = result.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Text content should be present for call $i") - - assertEquals( - "Hello, $name!", - textContent.text, - "Tool response should contain the greeting with the provided name", - ) - } - } - } + override suspend fun useClient(block: suspend (Client) -> T): T = withClientStdio { c, _ -> block(c) } } diff --git a/integration-test/src/jvmTest/typescript/client/sse-client.ts b/integration-test/src/jvmTest/typescript/client/http-client.ts similarity index 67% rename from integration-test/src/jvmTest/typescript/client/sse-client.ts rename to integration-test/src/jvmTest/typescript/client/http-client.ts index 0acc256..5956a37 100644 --- a/integration-test/src/jvmTest/typescript/client/sse-client.ts +++ b/integration-test/src/jvmTest/typescript/client/http-client.ts @@ -2,14 +2,14 @@ import {Client} from '@modelcontextprotocol/sdk/client/index'; import {StreamableHTTPClientTransport} from '@modelcontextprotocol/sdk/client/streamableHttp'; const args = process.argv.slice(2); -const serverUrl = args[0] || 'http://localhost:3001/mcp'; +const serverUrl = args[0] || 'http://localhost:3000/mcp'; const toolName = args[1]; const toolArgs = args.slice(2); -const PROTOCOL_VERSION = "2024-11-05"; +const PROTOCOL_VERSION = "2025-11-25"; async function main() { if (!toolName) { - console.log('Usage: npx tsx client/sse-client.ts [server-url] [tool-args...]'); + console.log('Usage: npx tsx client/http-client.ts [server-url] [tool-args...]'); console.log('Using default server URL:', serverUrl); console.log('Available utils will be listed after connection'); } @@ -22,6 +22,10 @@ async function main() { const client = new Client({ name: 'test-client', version: '1.0.0' + }, { + fallbackNotificationHandler: async (notification) => { + console.log('Notification:', notification.method, JSON.stringify(notification)); + } }); const transport = new StreamableHTTPClientTransport(new URL(serverUrl)); @@ -30,37 +34,20 @@ async function main() { await client.connect(transport, {protocolVersion: PROTOCOL_VERSION}); console.log('Connected to server'); - try { - if (typeof (client as any).on === 'function') { - (client as any).on('notification', (n: any) => { - try { - const method = (n && (n.method || (n.params && n.params.method))) || 'unknown'; - console.log('Notification:', method, JSON.stringify(n)); - } catch { - console.log('Notification: '); - } - }); - } - } catch { - } - const toolsResult = await client.listTools(); const tools = toolsResult.tools; - console.log('Available utils:', tools.map((t: { name: any; }) => t.name).join(', ')); + console.log('Available utils:', tools.map((t) => t.name).join(', ')); if (!toolName) { - await client.close(); return; } - const tool = tools.find((t: { name: string; }) => t.name === toolName); + const tool = tools.find((t) => t.name === toolName); if (!tool) { - console.error(`Tool "${toolName}" not found`); - // @ts-ignore - process.exit(1); + throw new Error(`Tool "${toolName}" not found`); } - const toolArguments = {}; + const toolArguments: Record = {}; if (toolName === "greet" && toolArgs.length > 0) { toolArguments["name"] = toolArgs[0]; @@ -93,8 +80,7 @@ async function main() { } catch (error) { console.error('Error:', error); - // @ts-ignore - process.exit(1); + process.exitCode = 1; } finally { await client.close(); console.log('Disconnected from server'); @@ -103,6 +89,5 @@ async function main() { main().catch(error => { console.error('Unhandled error:', error); - // @ts-ignore process.exit(1); }); diff --git a/integration-test/src/jvmTest/typescript/client/stdio-client.ts b/integration-test/src/jvmTest/typescript/client/stdio-client.ts index 82c25de..a9c765c 100644 --- a/integration-test/src/jvmTest/typescript/client/stdio-client.ts +++ b/integration-test/src/jvmTest/typescript/client/stdio-client.ts @@ -3,7 +3,7 @@ import {Client} from '@modelcontextprotocol/sdk/client/index'; const args = process.argv.slice(2); const toolName = args[0]; const toolArgs = args.slice(1); -const PROTOCOL_VERSION = "2024-11-05"; +const PROTOCOL_VERSION = "2025-11-25"; async function main() { if (!toolName) { @@ -65,6 +65,10 @@ async function main() { const client = new Client({ name: 'test-client', version: '1.0.0' + }, { + fallbackNotificationHandler: async (notification) => { + console.error('Notification:', notification.method, JSON.stringify(notification)); + } }); const transport = new MinimalStdioClientTransport(); @@ -73,21 +77,6 @@ async function main() { await client.connect(transport, {protocolVersion: PROTOCOL_VERSION}); console.error('Connected to server over stdio'); - try { - if (typeof (client as any).on === 'function') { - (client as any).on('notification', (n: any) => { - try { - const method = (n && (n.method || (n.params && n.params.method))) || 'unknown'; - console.error('Notification:', method, JSON.stringify(n)); - } catch { - console.error('Notification: '); - } - }); - } - } catch { - // ignore - } - const toolsResult = await client.listTools(); const tools = toolsResult.tools; console.error('Available utils:', tools.map((t: { name: any; }) => t.name).join(', ')); diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index fddbba4..1f26245 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -8,18 +8,14 @@ "name": "mcp-typescript-test-sdk", "version": "1.0.0", "dependencies": { - "@modelcontextprotocol/sdk": "^1.26.0", - "body-parser": "^2.2.2", + "@modelcontextprotocol/sdk": "^1.28.0", "express": "^5.2.1", - "uuid": "^13.0.0", "zod": "^4.3.5" }, "devDependencies": { - "@eslint/js": "^9.39.2", "@types/cors": "^2.8.19", "@types/express": "^5.0.6", "@types/node": "^24.10.6", - "@types/uuid": "^10.0.0", "cors": "^2.8.5", "tsx": "^4.21.0", "typescript": "^5.9.3" @@ -467,23 +463,10 @@ "node": ">=18" } }, - "node_modules/@eslint/js": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", - "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, "node_modules/@hono/node-server": { - "version": "1.19.9", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", - "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", + "version": "1.19.11", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz", + "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==", "license": "MIT", "engines": { "node": ">=18.14.1" @@ -493,9 +476,9 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz", - "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==", + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.28.0.tgz", + "integrity": "sha512-gmloF+i+flI8ouQK7MWW4mOwuMh4RePBuPFAEPC6+pdqyWOUMDOixb6qZ69owLJpz6XmyllCouc4t8YWO+E2Nw==", "license": "MIT", "dependencies": { "@hono/node-server": "^1.19.9", @@ -640,13 +623,6 @@ "@types/node": "*" } }, - "node_modules/@types/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", - "dev": true, - "license": "MIT" - }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -1029,12 +1005,12 @@ } }, "node_modules/express-rate-limit": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", - "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz", + "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==", "license": "MIT", "dependencies": { - "ip-address": "10.0.1" + "ip-address": "10.1.0" }, "engines": { "node": ">= 16" @@ -1217,9 +1193,9 @@ } }, "node_modules/hono": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.9.tgz", - "integrity": "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==", + "version": "4.12.9", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.9.tgz", + "integrity": "sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA==", "license": "MIT", "engines": { "node": ">=16.9.0" @@ -1268,9 +1244,9 @@ "license": "ISC" }, "node_modules/ip-address": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", - "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", "license": "MIT", "engines": { "node": ">= 12" @@ -1786,19 +1762,6 @@ "node": ">= 0.8" } }, - "node_modules/uuid": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", - "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist-node/bin/uuid" - } - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/integration-test/src/jvmTest/typescript/package.json b/integration-test/src/jvmTest/typescript/package.json index 6972151..3ce560c 100644 --- a/integration-test/src/jvmTest/typescript/package.json +++ b/integration-test/src/jvmTest/typescript/package.json @@ -4,23 +4,19 @@ "private": true, "scripts": { "client:stdio": "tsx client/stdio-client.ts", - "client:sse": "tsx client/sse-client.ts http://localhost:3000/mcp greet Hello", + "client:http": "tsx client/http-client.ts http://localhost:3000/mcp greet Hello", "server:stdio": "tsx server/stdio-server.ts", - "server:sse": "tsx server/sse-server.ts" + "server:http": "tsx server/http-server.ts" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.26.0", - "body-parser": "^2.2.2", + "@modelcontextprotocol/sdk": "^1.28.0", "express": "^5.2.1", - "uuid": "^13.0.0", "zod": "^4.3.5" }, "devDependencies": { - "@eslint/js": "^9.39.2", "@types/cors": "^2.8.19", "@types/express": "^5.0.6", "@types/node": "^24.10.6", - "@types/uuid": "^10.0.0", "cors": "^2.8.5", "tsx": "^4.21.0", "typescript": "^5.9.3" diff --git a/integration-test/src/jvmTest/typescript/server/sse-server.ts b/integration-test/src/jvmTest/typescript/server/http-server.ts similarity index 80% rename from integration-test/src/jvmTest/typescript/server/sse-server.ts rename to integration-test/src/jvmTest/typescript/server/http-server.ts index 48ea453..c42e115 100644 --- a/integration-test/src/jvmTest/typescript/server/sse-server.ts +++ b/integration-test/src/jvmTest/typescript/server/http-server.ts @@ -1,30 +1,24 @@ -// @ts-ignore import express, {Request, Response} from 'express'; import {randomUUID} from 'node:crypto'; -// @ts-ignore import cors from 'cors'; import {McpServer} from '@modelcontextprotocol/sdk/server/mcp'; -import {StreamableHTTPServerTransport,} from '@modelcontextprotocol/sdk/server/streamableHttp'; +import {StreamableHTTPServerTransport} from '@modelcontextprotocol/sdk/server/streamableHttp'; import {registerTestUtils} from './server-common'; import { getOAuthProtectedResourceMetadataUrl, mcpAuthMetadataRouter } from '@modelcontextprotocol/sdk/server/auth/router'; import {requireBearerAuth} from '@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth'; -import {isInitializeRequest,} from '@modelcontextprotocol/sdk/types'; +import {isInitializeRequest} from '@modelcontextprotocol/sdk/types'; import {InMemoryEventStore} from '@modelcontextprotocol/sdk/examples/shared/inMemoryEventStore'; import {setupAuthServer} from '@modelcontextprotocol/sdk/examples/server/demoInMemoryOAuthProvider'; import {checkResourceAllowed} from '@modelcontextprotocol/sdk/shared/auth-utils'; import {OAuthMetadata} from "@modelcontextprotocol/sdk/shared/auth"; -// import {type OAuthMetadata} from '@modelcontextprotocol/sdk/auth.js'; - async function main() { - // Check for OAuth flag const useOAuth = process.argv.includes('--oauth'); const strictOAuth = process.argv.includes('--oauth-strict'); -// Create an MCP server with implementation details const getServer = () => { const server = new McpServer({ name: 'simple-streamable-http-server', @@ -42,16 +36,15 @@ async function main() { const app = express(); app.use(express.json()); -// Allow CORS all domains, expose the Mcp-Session-Id header + // Allow CORS all domains, expose the Mcp-Session-Id header app.use(cors({ - origin: '*', // Allow all origins + origin: '*', exposedHeaders: ["Mcp-Session-Id"] })); -// Set up OAuth if enabled + // Set up OAuth if enabled let authMiddleware = null; if (useOAuth) { - // Create auth middleware for MCP endpoints const mcpServerUrl = new URL(`http://localhost:${MCP_PORT}/mcp`); const authServerUrl = new URL(`http://localhost:${AUTH_PORT}`); @@ -79,7 +72,6 @@ async function main() { }).toString() }); - if (!response.ok) { throw new Error(`Invalid or expired token: ${await response.text()}`); } @@ -95,7 +87,6 @@ async function main() { } } - // Convert the response to AuthInfo format return { token, clientId: data.client_id, @@ -104,7 +95,7 @@ async function main() { }; } } - // Add metadata routes to the main MCP server + app.use(mcpAuthMetadataRouter({ oauthMetadata, resourceServerUrl: mcpServerUrl, @@ -119,10 +110,10 @@ async function main() { }); } -// Map to store transports by session ID + // Map to store transports by session ID const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {}; -// MCP POST endpoint with optional auth + // MCP POST endpoint with optional auth const mcpPostHandler = async (req: Request, res: Response) => { const sessionId = req.headers['mcp-session-id'] as string | undefined; if (sessionId) { @@ -137,23 +128,18 @@ async function main() { try { let transport: StreamableHTTPServerTransport; if (sessionId && transports[sessionId]) { - // Reuse existing transport transport = transports[sessionId]; } else if (!sessionId && isInitializeRequest(req.body)) { - // New initialization request const eventStore = new InMemoryEventStore(); transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), - eventStore, // Enable resumability + eventStore, onsessioninitialized: (sessionId) => { - // Store the transport by session ID when session is initialized - // This avoids race conditions where requests might come in before the session is stored console.log(`Session initialized with ID: ${sessionId}`); transports[sessionId] = transport; } }); - // Set up onclose handler to clean up transport when closed transport.onclose = () => { const sid = transport.sessionId; if (sid && transports[sid]) { @@ -162,15 +148,12 @@ async function main() { } }; - // Connect the transport to the MCP server BEFORE handling the request - // so responses can flow back through the same transport const server = getServer(); await server.connect(transport); await transport.handleRequest(req, res, req.body); - return; // Already handled + return; } else { - // Invalid request - no session ID or not initialization request res.status(400).json({ jsonrpc: '2.0', error: { @@ -182,8 +165,6 @@ async function main() { return; } - // Handle the request with existing transport - no need to reconnect - // The existing transport is already connected to the server await transport.handleRequest(req, res, req.body); } catch (error) { console.error('Error handling MCP request:', error); @@ -200,14 +181,13 @@ async function main() { } }; -// Set up routes with conditional auth middleware if (useOAuth && authMiddleware) { app.post('/mcp', authMiddleware, mcpPostHandler); } else { app.post('/mcp', mcpPostHandler); } -// Handle GET requests for SSE streams (using built-in support from StreamableHTTP) + // Handle GET requests for event streams const mcpGetHandler = async (req: Request, res: Response) => { const sessionId = req.headers['mcp-session-id'] as string | undefined; if (!sessionId || !transports[sessionId]) { @@ -216,29 +196,27 @@ async function main() { } if (useOAuth && req.auth) { - console.log('Authenticated SSE connection from user:', req.auth); + console.log('Authenticated event stream connection from user:', req.auth); } - // Check for Last-Event-ID header for resumability const lastEventId = req.headers['last-event-id'] as string | undefined; if (lastEventId) { console.log(`Client reconnecting with Last-Event-ID: ${lastEventId}`); } else { - console.log(`Establishing new SSE stream for session ${sessionId}`); + console.log(`Establishing new event stream for session ${sessionId}`); } const transport = transports[sessionId]; await transport.handleRequest(req, res); }; -// Set up GET route with conditional auth middleware if (useOAuth && authMiddleware) { app.get('/mcp', authMiddleware, mcpGetHandler); } else { app.get('/mcp', mcpGetHandler); } -// Handle DELETE requests for session termination (according to MCP spec) + // Handle DELETE requests for session termination (according to MCP spec) const mcpDeleteHandler = async (req: Request, res: Response) => { const sessionId = req.headers['mcp-session-id'] as string | undefined; if (!sessionId || !transports[sessionId]) { @@ -259,7 +237,6 @@ async function main() { } }; -// Set up DELETE route with conditional auth middleware if (useOAuth && authMiddleware) { app.delete('/mcp', authMiddleware, mcpDeleteHandler); } else { @@ -274,11 +251,10 @@ async function main() { console.log(`MCP Streamable HTTP Server listening on port ${MCP_PORT}`); }); -// Handle server shutdown + // Handle server shutdown process.on('SIGINT', async () => { console.log('Shutting down server...'); - // Close all active transports to properly clean up resources for (const sessionId in transports) { try { console.log(`Closing transport for session ${sessionId}`); @@ -291,7 +267,6 @@ async function main() { console.log('Server shutdown complete'); process.exit(0); }); - } main().catch((err) => { diff --git a/integration-test/src/jvmTest/typescript/tsconfig.json b/integration-test/src/jvmTest/typescript/tsconfig.json new file mode 100644 index 0000000..58e3c67 --- /dev/null +++ b/integration-test/src/jvmTest/typescript/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "strict": true, + "target": "ES2022", + "module": "Node16", + "moduleResolution": "Node16", + "esModuleInterop": true, + "skipLibCheck": true, + "noEmit": true + }, + "include": ["**/*.ts"] +} diff --git a/kotlin-sdk-client/detekt-baseline-test.xml b/kotlin-sdk-client/detekt-baseline-test.xml index b16e8eb..dd6adfa 100644 --- a/kotlin-sdk-client/detekt-baseline-test.xml +++ b/kotlin-sdk-client/detekt-baseline-test.xml @@ -4,8 +4,6 @@ AbstractClassCanBeConcreteClass:AbstractStreamableHttpClientTest.kt:AbstractStreamableHttpClientTest$AbstractStreamableHttpClientTest ForbiddenComment:StreamableHttpClientTest.kt:StreamableHttpClientTest$// TODO: how to get notifications via Client API? - InjectDispatcher:StdioClientTransportErrorHandlingTest.kt:StdioClientTransportErrorHandlingTest$IO - InjectDispatcher:StreamableHttpClientTransportTest.kt:StreamableHttpClientTransportTest$Default LongParameterList:MockMcp.kt:MockMcp$fun handleJSONRPCRequest LongParameterList:MockMcp.kt:MockMcp$fun handleWithResult LongParameterList:MockMcp.kt:MockMcp$fun onInitialize diff --git a/test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/testing.kt b/test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/testing.kt index ee73162..32f55c9 100644 --- a/test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/testing.kt +++ b/test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/testing.kt @@ -8,7 +8,6 @@ import kotlin.coroutines.CoroutineContext import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds -@Suppress("InjectDispatcher") // this is constant private val defaultTestDispatcher: CoroutineContext = Dispatchers.IO.limitedParallelism(100) private val defaultTestTimeout: Duration = 5.seconds From cd91eed35de68efe2e426ed0758a8f4097c39ff2 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Thu, 26 Mar 2026 13:45:58 +0100 Subject: [PATCH 189/449] chore: simplify and clean up CI workflows (#617) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove redundant Gradle flags, env vars, and unused settings across all workflows - Minimize GitHub Actions permissions to least-privilege - Drop Windows build matrix (JVM tests covered by Linux) - Simplify CodeQL workflow (remove matrix, unused permissions, stale triggers) - Clean up gradle.properties (remove unused KMP settings) ## How Has This Been Tested? ## Breaking Changes — ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] 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 --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/apidocs.yaml | 5 +--- .github/workflows/build.yml | 44 +++++++++++++--------------- .github/workflows/codeql.yml | 38 +++++------------------- .github/workflows/gradle-publish.yml | 27 ++--------------- .github/workflows/samples.yml | 7 ++--- gradle.properties | 6 ---- 6 files changed, 34 insertions(+), 93 deletions(-) diff --git a/.github/workflows/apidocs.yaml b/.github/workflows/apidocs.yaml index 41e15f3..5c7ed4c 100644 --- a/.github/workflows/apidocs.yaml +++ b/.github/workflows/apidocs.yaml @@ -25,9 +25,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - with: - submodules: true # Fetch Hugo themes (true OR recursive) - fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod - name: Set up JDK ${{ env.JAVA_VERSION }} uses: actions/setup-java@v5 @@ -41,7 +38,7 @@ jobs: - name: Generate Dokka Site run: |- - ./gradlew clean knit docs:classes docs:knitCheck dokkaGenerate + ./gradlew knit docs:classes docs:knitCheck dokkaGenerate - name: Upload artifact uses: actions/upload-pages-artifact@v4 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f97721b..4fd2522 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,14 +13,12 @@ concurrency: cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} permissions: - checks: write - pull-requests: write # only required if `comment: true` was enabled + contents: read env: JAVA_VERSION: 21 JAVA_DISTRIBUTION: temurin - JAVA_OPTS: "-Dfile.encoding=UTF-8 -Djava.awt.headless=true" - GRADLE_OPTS: "-Dorg.gradle.daemon=false -Dkotlin.compiler.execution.strategy=in-process" + GRADLE_OPTS: "-Dorg.gradle.daemon=false" jobs: build: @@ -36,17 +34,12 @@ jobs: include: - os: ubuntu-latest job-name: "Linux Tests" - gradle-tasks: "build ktlintCheck koverLog koverHtmlReport koverXmlReport publishToMavenLocal -Pversion=1-SNAPSHOT" - max-workers: 3 - - - os: windows-latest - job-name: "Windows Tests" - gradle-tasks: "jvmTest -x :integration-test:jvmTest" + gradle-tasks: "build" max-workers: 3 - os: macos-latest job-name: "iOS Simulator tests" - gradle-tasks: "linkIosSimulatorArm64 iosSimulatorArm64Test" + gradle-tasks: "iosSimulatorArm64Test" max-workers: 2 steps: @@ -75,19 +68,25 @@ jobs: validate-wrappers: true add-job-summary: 'always' cache-read-only: ${{ github.ref != 'refs/heads/main' }} - cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} gradle-home-cache-includes: | caches notifications - sdks ../.konan/** - name: ${{ matrix.job-name }} run: |- - ./gradlew ${{ matrix.gradle-tasks }} -x :conformance-test:test --max-workers ${{ matrix.max-workers }} --continue --rerun-tasks --configure-on-demand --parallel + ./gradlew ${{ matrix.gradle-tasks }} --max-workers ${{ matrix.max-workers }} --continue --rerun-tasks + + - name: Coverage Report + if: ${{ success() && matrix.os == 'ubuntu-latest' }} + run: ./gradlew koverLog koverHtmlReport koverXmlReport + + - name: Publish to Maven Local + if: ${{ success() && matrix.os == 'ubuntu-latest' }} + run: ./gradlew publishToMavenLocal -Pversion=1-SNAPSHOT - name: Upload Reports - if: always() + if: failure() uses: actions/upload-artifact@v7 with: name: reports-build-${{ matrix.os }} @@ -100,10 +99,8 @@ jobs: with: annotate_only: true detailed_summary: true - flaky_summary: true group_suite: true include_empty_in_summary: false - include_time_in_summary: true report_paths: '**/test-results/**/TEST-*.xml' truncate_stack_traces: false @@ -127,7 +124,7 @@ jobs: - name: Archive Maven Local Repository if: ${{ success() && matrix.os == 'ubuntu-latest' }} run: | - cd ~/.m2/repository && zip -r $GITHUB_WORKSPACE/local-maven-repository.zip . + cd ~/.m2/repository/io/modelcontextprotocol && zip -r $GITHUB_WORKSPACE/local-maven-repository.zip . - name: Upload Maven Local Repository if: ${{ success() && matrix.os == 'ubuntu-latest' }} @@ -159,7 +156,6 @@ jobs: validate-wrappers: true add-job-summary: 'always' cache-read-only: ${{ github.ref != 'refs/heads/main' }} - cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} gradle-home-cache-includes: | caches notifications @@ -168,12 +164,14 @@ jobs: - name: Run Knit run: |- - ./gradlew knit docs:classes docs:knitCheck -x :conformance-test:test --max-workers 3 --continue --rerun-tasks --configure-on-demand --parallel + ./gradlew knit docs:classes docs:knitCheck --max-workers 3 --continue --rerun-tasks samples: needs: [ build ] runs-on: ubuntu-latest + permissions: + contents: read strategy: fail-fast: false matrix: @@ -185,8 +183,6 @@ jobs: name: "Build Sample: ${{ matrix.sample }}" timeout-minutes: 10 - env: - JAVA_OPTS: "-Xmx8g -Dfile.encoding=UTF-8 -Djava.awt.headless=true -Dkotlin.daemon.jvm.options=-Xmx6g" steps: - uses: actions/checkout@v6 @@ -215,8 +211,8 @@ jobs: - name: Extract Maven Local Repository run: | - mkdir -p ~/.m2/repository - unzip -o local-maven-repository.zip -d ~/.m2/repository + mkdir -p ~/.m2/repository/io/modelcontextprotocol + unzip -o local-maven-repository.zip -d ~/.m2/repository/io/modelcontextprotocol - name: "Build Sample: ${{ matrix.sample }}" working-directory: ./samples/${{ matrix.sample }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 4657da9..41d1fde 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,16 +1,14 @@ name: "CodeQL Advanced" on: - push: - branches: [ "main" ] pull_request: branches: [ "main" ] schedule: - - cron: '0 4 * * 0' + - cron: '0 7 * * 1' # Monday 8:00 CET (7:00 UTC) concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + cancel-in-progress: true env: JAVA_VERSION: 21 @@ -18,30 +16,16 @@ env: jobs: analyze: - name: Analyze (${{ matrix.language }}) + name: Analyze runs-on: ubuntu-latest timeout-minutes: 30 permissions: - # required for all workflows security-events: write - - # required to fetch internal or private CodeQL packs - packages: read - - # only required for workflows in private repositories - actions: read contents: read - strategy: - fail-fast: false - matrix: - language: [ java-kotlin ] - steps: - name: Checkout repository uses: actions/checkout@v6 - with: - fetch-depth: 0 - name: Set up JDK ${{ env.JAVA_VERSION }} uses: actions/setup-java@v5 @@ -49,11 +33,10 @@ jobs: distribution: ${{ env.JAVA_DISTRIBUTION }} java-version: ${{ env.JAVA_VERSION }} - # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v4 with: - languages: ${{ matrix.language }} + languages: java-kotlin build-mode: manual - name: Setup Gradle @@ -64,18 +47,11 @@ jobs: - name: Build Kotlin sources run: | - ./gradlew \ - :kotlin-sdk-core:compileKotlinJvm \ - :kotlin-sdk-client:compileKotlinJvm \ - :kotlin-sdk-server:compileKotlinJvm \ - :kotlin-sdk:compileKotlinJvm \ - :integration-test:compileKotlinJvm \ - :conformance-test:testClasses \ - detekt \ + ./gradlew compileKotlinJvm :conformance-test:compileKotlin \ -Pkotlin.incremental=false \ - --no-daemon --stacktrace --rerun-tasks + --no-daemon --rerun-tasks --stacktrace - name: Analyze uses: github/codeql-action/analyze@v4 with: - category: '/language:${{ matrix.language }}' + category: '/language:java-kotlin' diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml index 5acfce8..ed9a63a 100644 --- a/.github/workflows/gradle-publish.yml +++ b/.github/workflows/gradle-publish.yml @@ -1,10 +1,3 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. -# This workflow will build a package using Gradle and then publish it to GitHub packages when a release is created -# For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#Publishing-using-gradle - name: Release on: @@ -17,8 +10,7 @@ jobs: runs-on: macos-latest environment: release env: - JAVA_OPTS: "-Xmx4g -Dfile.encoding=UTF-8 -XX:MaxMetaspaceSize=768m -Djava.awt.headless=true" - GRADLE_OPTS: "-Dorg.gradle.daemon=false -Dkotlin.compiler.execution.strategy=in-process" + GRADLE_OPTS: "-Dorg.gradle.daemon=false" permissions: contents: write @@ -35,21 +27,8 @@ jobs: - name: Setup Gradle uses: gradle/actions/setup-gradle@v5 - - name: Clean Build with Gradle - run: ./gradlew clean build -x :conformance-test:test --rerun-tasks --max-workers 2 --continue --configure-on-demand --parallel - - - name: Publish Test Report - uses: mikepenz/action-junit-report@v6 - if: ${{ !cancelled() }} - with: - annotate_only: true - detailed_summary: true - flaky_summary: true - group_suite: true - include_empty_in_summary: false - include_time_in_summary: true - report_paths: '**/test-results/**/TEST-*.xml' - truncate_stack_traces: false + - name: Build + run: ./gradlew build --rerun-tasks --max-workers 2 --continue - name: Publish to Maven Central Portal id: publish diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index 373f00a..632b710 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -19,6 +19,7 @@ concurrency: env: JAVA_VERSION: 21 JAVA_DISTRIBUTION: temurin + GRADLE_OPTS: "-Dorg.gradle.daemon=false" jobs: build: @@ -34,8 +35,6 @@ jobs: name: Build Sample (${{ matrix.sample }}) timeout-minutes: 10 - env: - JAVA_OPTS: "-Xmx8g -Dfile.encoding=UTF-8 -Djava.awt.headless=true -Dkotlin.daemon.jvm.options=-Xmx6g" steps: - uses: actions/checkout@v6 @@ -54,10 +53,10 @@ jobs: - name: "Build Sample: ${{ matrix.sample }}" working-directory: ./samples/${{ matrix.sample }} - run: ./gradlew --no-daemon build + run: ./gradlew build - name: Upload Reports - if: ${{ !cancelled() }} + if: failure() uses: actions/upload-artifact@v7 with: name: reports-samples-${{ matrix.sample }} diff --git a/gradle.properties b/gradle.properties index 45b067c..43b4acb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,16 +1,10 @@ org.gradle.jvmargs=-Xmx4g -Dfile.encoding=UTF-8 org.gradle.parallel=true org.gradle.caching=true -# Dokka -org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled -org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true # Kotlin kotlin.code.style=official kotlin.daemon.jvmargs=-Xmx4G -# Build JS targets using npm package manager https://kotlinlang.org/docs/js-project-setup.html#npm-dependencies -kotlin.js.yarn=false # MPP -kotlin.mpp.enableCInteropCommonization=true kotlin.native.ignoreDisabledTargets=true group=io.modelcontextprotocol From c77ebe0c00ac8be633f530b7355141c61ddbc5cd Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Thu, 26 Mar 2026 17:36:01 +0200 Subject: [PATCH 190/449] refactor(core): Fix Kotlin language and api version to 2.1 in build config (#620) ## Fix Kotlin language and api version to 2.1 - Remove language constructs not supported in Kotlin 2.1 ## Motivation and Context To ensure binary compatibility ## How Has This Been Tested? Regression tests ## Breaking Changes ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [x] Build 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 ## Additional context --- .../main/kotlin/mcp.multiplatform.gradle.kts | 3 +++ .../http/StreamableHttpClientTransportTest.kt | 21 +++++++++---------- .../kotlin/sdk/types/request.kt | 6 +++--- .../kotlin/sdk/types/serializers.kt | 6 +++--- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts b/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts index 36097ff..f8d89c0 100644 --- a/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts +++ b/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts @@ -5,6 +5,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode import org.jetbrains.kotlin.gradle.dsl.JvmDefaultMode import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.dsl.KotlinVersion plugins { kotlin("multiplatform") @@ -15,6 +16,8 @@ plugins { kotlin { compilerOptions { + languageVersion = KotlinVersion.KOTLIN_2_1 + apiVersion = KotlinVersion.KOTLIN_2_1 freeCompilerArgs = listOf( "-Wextra", diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt index 55df3e0..67b2f07 100644 --- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt @@ -148,18 +148,17 @@ class StreamableHttpClientTransportTest { ) val transport = createTransport { request -> - when ( - val msg = McpJson.decodeFromString( - (request.body as TextContent).text, - ) - ) { - is JSONRPCRequest if msg.method == "initialize" -> respond( + val msg = McpJson.decodeFromString( + (request.body as TextContent).text, + ) + when { + msg is JSONRPCRequest && msg.method == "initialize" -> respond( content = "", status = HttpStatusCode.OK, headers = headersOf("mcp-session-id", "test-session-id"), ) - is JSONRPCNotification if msg.method == "test" -> { + msg is JSONRPCNotification && msg.method == "test" -> { assertEquals("test-session-id", request.headers["mcp-session-id"]) respond( content = "", @@ -246,8 +245,8 @@ class StreamableHttpClientTransportTest { var sseStarted = false val transport = createTransport { request -> - when (request.method) { - HttpMethod.Post if request.body.toString().contains("notifications/initialized") -> { + when { + request.method == HttpMethod.Post && request.body.toString().contains("notifications/initialized") -> { respond( content = "", status = HttpStatusCode.Accepted, @@ -256,7 +255,7 @@ class StreamableHttpClientTransportTest { } // Handle SSE connection - HttpMethod.Get -> { + request.method == HttpMethod.Get -> { sseStarted = true val sseContent = buildString { // Server sends various notifications @@ -288,7 +287,7 @@ class StreamableHttpClientTransportTest { } // Handle regular notifications - HttpMethod.Post -> { + request.method == HttpMethod.Post -> { respond( content = "", status = HttpStatusCode.Accepted, diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.kt index dc7bd3b..683186c 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.kt @@ -16,9 +16,9 @@ import kotlin.jvm.JvmInline public value class RequestMeta(public val json: JsonObject) { public val progressToken: ProgressToken? get() = json["progressToken"]?.let { element -> - when (element) { - is JsonPrimitive if (element.isString) -> ProgressToken(element.content) - is JsonPrimitive if (element.longOrNull != null) -> ProgressToken(element.long) + when { + element is JsonPrimitive && element.isString -> ProgressToken(element.content) + element is JsonPrimitive && element.longOrNull != null -> ProgressToken(element.long) else -> null } } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt index 2d20d42..792c636 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt @@ -414,9 +414,9 @@ internal object JSONRPCMessagePolymorphicSerializer : * Supports both string and number IDs. */ internal object RequestIdPolymorphicSerializer : JsonContentPolymorphicSerializer(RequestId::class) { - override fun selectDeserializer(element: JsonElement): DeserializationStrategy = when (element) { - is JsonPrimitive if (element.isString) -> RequestId.StringId.serializer() - is JsonPrimitive if (element.longOrNull != null) -> RequestId.NumberId.serializer() + override fun selectDeserializer(element: JsonElement): DeserializationStrategy = when { + element is JsonPrimitive && element.isString -> RequestId.StringId.serializer() + element is JsonPrimitive && element.longOrNull != null -> RequestId.NumberId.serializer() else -> throw SerializationException("Invalid RequestId type: $element") } } From bd4cf0c948203380cdb96503e332b8e3b2ae3e2e Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Thu, 26 Mar 2026 18:25:13 +0200 Subject: [PATCH 191/449] docs: add missing KDoc comments for public functions, properties, and classes (#621) --- .../modelcontextprotocol/kotlin/sdk/shared/ReadBuffer.kt | 3 +++ .../kotlin/sdk/types/capabilities.dsl.kt | 1 + .../io/modelcontextprotocol/kotlin/sdk/types/common.kt | 3 +++ .../modelcontextprotocol/kotlin/sdk/types/content.dsl.kt | 1 + .../io/modelcontextprotocol/kotlin/sdk/types/content.kt | 1 + .../io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt | 2 ++ .../io/modelcontextprotocol/kotlin/sdk/types/sampling.kt | 1 + .../io/modelcontextprotocol/kotlin/sdk/server/Server.kt | 7 +++++++ .../kotlin/sdk/server/StreamableHttpServerTransport.kt | 2 ++ 9 files changed, 21 insertions(+) diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBuffer.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBuffer.kt index d3fc61f..d4c8b43 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBuffer.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBuffer.kt @@ -16,6 +16,7 @@ public class ReadBuffer { private val buffer: Buffer = Buffer() + /** Appends raw bytes to the internal buffer for subsequent message parsing. */ public fun append(chunk: ByteArray) { buffer.write(chunk) } @@ -86,6 +87,7 @@ public class ReadBuffer { } } + /** Clears all buffered data, discarding any partially received messages. */ public fun clear() { buffer.clear() } @@ -93,4 +95,5 @@ public class ReadBuffer { internal fun deserializeMessage(line: String): JSONRPCMessage = McpJson.decodeFromString(line) +/** Serializes a [JSONRPCMessage] to its JSON string representation with a trailing newline. */ public fun serializeMessage(message: JSONRPCMessage): String = McpJson.encodeToString(message) + "\n" diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.dsl.kt index b132a5e..146eabf 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.dsl.kt @@ -171,6 +171,7 @@ public class ClientCapabilitiesBuilder @PublishedApi internal constructor() { */ public fun experimental(block: JsonObjectBuilder.() -> Unit): Unit = experimental(buildJsonObject(block)) + /** Constructs the [ClientCapabilities] instance from the current builder state. */ @PublishedApi internal fun build(): ClientCapabilities = ClientCapabilities( sampling = sampling, diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt index 7ec9c07..3ae8997 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt @@ -41,8 +41,10 @@ public sealed interface WithMeta { */ public typealias ProgressToken = RequestId +/** Creates a [ProgressToken] from a string value. */ public fun ProgressToken(value: String): ProgressToken = RequestId(value) +/** Creates a [ProgressToken] from a numeric value. */ public fun ProgressToken(value: Long): ProgressToken = RequestId(value) // ============================================================================ @@ -127,6 +129,7 @@ public sealed interface Reference { public val type: ReferenceType } +/** Discriminator for [Reference] subtypes used in completion and other operations. */ @Serializable public enum class ReferenceType(public val value: String) { @SerialName("ref/prompt") diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/content.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/content.dsl.kt index e538af5..8718dec 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/content.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/content.dsl.kt @@ -88,6 +88,7 @@ public abstract class MediaContentBuilder { meta = buildJsonObject(block) } + /** Constructs the [MediaContent] instance from the current builder state. */ @PublishedApi internal abstract fun build(): MediaContent } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/content.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/content.kt index 0e6377f..45cfd0d 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/content.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/content.kt @@ -8,6 +8,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonObject +/** Discriminator values for the polymorphic [ContentBlock] hierarchy. */ @Serializable public enum class ContentTypes(public val value: String) { @SerialName("text") diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt index 6a53d35..f42139f 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt @@ -40,10 +40,12 @@ public fun RequestId(value: Long): RequestId = RequestId.NumberId(value) @Serializable(with = RequestIdPolymorphicSerializer::class) public sealed interface RequestId { + /** A string-based request identifier. */ @JvmInline @Serializable public value class StringId(public val value: String) : RequestId + /** A numeric request identifier. */ @JvmInline @Serializable public value class NumberId(public val value: Long) : RequestId diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.kt index 41dc864..1ade1bf 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.kt @@ -243,6 +243,7 @@ public data class CreateMessageResult( override val meta: JsonObject? = null, ) : ClientResult +/** The reason why the LLM stopped generating tokens. */ @JvmInline @Serializable public value class StopReason(public val value: String) { diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt index afed86a..e566d09 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt @@ -185,6 +185,7 @@ public open class Server( block(this) } + /** Closes this server, shutting down the notification service and all active sessions. */ public suspend fun close() { logger.debug { "Closing MCP server" } notificationService.close() @@ -718,6 +719,7 @@ public open class Server( // Start the ServerSession / ClientConnection redirection section + /** Returns the [ClientConnection] for the session identified by [sessionId]. */ public fun clientConnection(sessionId: String): ClientConnection = sessionRegistry.getSession(sessionId).clientConnection @@ -827,18 +829,22 @@ public open class Server( // End the ServerSession / ClientConnection redirection section // Start the notification handling section + + /** Registers a notification [handler] for the given [method] on all active sessions. */ public fun setNotificationHandler(method: Method, handler: (notification: T) -> Deferred) { sessions.forEach { (_, session) -> session.setNotificationHandler(method, handler) } } + /** Removes the notification handler for the given [method] from all active sessions. */ public fun removeNotificationHandler(method: Method) { sessions.forEach { (_, session) -> session.removeNotificationHandler(method) } } + /** Registers a notification [handler] for the given [method] on a specific session. */ public fun setNotificationHandler( sessionId: String, method: Method, @@ -847,6 +853,7 @@ public open class Server( sessionRegistry.getSessionOrNull(sessionId)?.setNotificationHandler(method, handler) } + /** Removes the notification handler for the given [method] from a specific session. */ public fun removeNotificationHandler(sessionId: String, method: Method) { sessionRegistry.getSessionOrNull(sessionId)?.removeNotificationHandler(method) } diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt index d0bbcfb..821c525 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt @@ -418,6 +418,7 @@ public class StreamableHttpServerTransport(private val configuration: Configurat } } + /** Handles an HTTP GET request by establishing a standalone SSE stream for server-initiated notifications. */ @Suppress("ReturnCount") public suspend fun handleGetRequest(session: ServerSSESession?, call: ApplicationCall) { // NOTE: enableJsonResponse only controls how POST responses are delivered (JSON vs. SSE). @@ -465,6 +466,7 @@ public class StreamableHttpServerTransport(private val configuration: Configurat awaitCancellation() } + /** Handles an HTTP DELETE request by closing the session and the transport. */ public suspend fun handleDeleteRequest(call: ApplicationCall) { if (!validateSession(call) || !validateProtocolVersion(call)) return sessionId?.let { onSessionClosed?.invoke(it) } From 961b29bbe6e0def47337a2c54597e0a5750caa81 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Thu, 26 Mar 2026 21:00:09 +0200 Subject: [PATCH 192/449] chore: bump project version to 0.10.0 in gradle.properties (#623) --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 43b4acb..1d5620a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,4 +8,4 @@ kotlin.daemon.jvmargs=-Xmx4G kotlin.native.ignoreDisabledTargets=true group=io.modelcontextprotocol -version=0.9.0 +version=0.10.0 From 01689cc2481823617357af2706ce543a49d2929d Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Thu, 26 Mar 2026 20:35:04 +0100 Subject: [PATCH 193/449] chore: improve dependabot configuration (#619) - Add grouped dependency updates for root Gradle (ktor, kotlinx, kotlin, kotest, testing, infrastructure, gradle-plugins) - Track all sample projects via `directories: /samples/*` with separate PRs for ktor, kotlin, and mcp-kotlin-sdk - Track npm dependencies in integration tests - Group github actions updates into a single PR ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] 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 --- .github/dependabot.yml | 93 ++++++++++++++++++++- .github/workflows/dependabot-auto-merge.yml | 23 +++++ 2 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/dependabot-auto-merge.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8d02aac..b951eaa 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,20 +1,109 @@ -# Configuration options: -# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#about-the-dependabotyml-file +# yaml-language-server: $schema=https://json.schemastore.org/dependabot-2.0.json +# Configuration: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file version: 2 + updates: + # Root Gradle project (includes buildSrc) - package-ecosystem: "gradle" directory: "/" schedule: interval: "weekly" + day: "monday" + commit-message: + prefix: "chore(deps):" labels: - "dependencies" - "kotlin" + open-pull-requests-limit: 10 + groups: + ktor: + patterns: + - "io.ktor:*" + kotlinx: + patterns: + - "org.jetbrains.kotlinx:*" + kotlin: + patterns: + - "org.jetbrains.kotlin:*" + kotest: + patterns: + - "io.kotest:*" + testing: + patterns: + - "io.mockk:*" + - "dev.mokksy:*" + - "org.awaitility:*" + - "org.junit.jupiter:*" + infrastructure: + patterns: + - "io.netty:*" + - "io.github.oshai:*" + - "org.slf4j:*" + gradle-plugins: + patterns: + - "dev.detekt" + - "org.jetbrains.kotlinx.kover" + - "org.jlleitschuh.gradle.ktlint" + - "org.jetbrains.kotlinx.knit" + - "org.jetbrains.kotlinx.binary-compatibility-validator" + - "org.openapi.generator" + - "org.jetbrains.dokka:*" + - "com.vanniktech:*" + # Samples (independent Gradle builds) + - package-ecosystem: "gradle" + directories: + - "/samples/*" + schedule: + interval: "weekly" + day: "monday" + commit-message: + prefix: "chore(deps):" + labels: + - "dependencies" + - "kotlin" + - "samples" + open-pull-requests-limit: 10 + groups: + other-dependencies: + patterns: + - "*" + exclude-patterns: + - "io.ktor*" + - "org.jetbrains.kotlin*" + - "io.modelcontextprotocol*" + + # npm (integration tests) + - package-ecosystem: "npm" + directory: "/integration-test/src/jvmTest/typescript" + schedule: + interval: "weekly" + day: "monday" + commit-message: + prefix: "chore(deps):" + labels: + - "dependencies" + - "javascript" + open-pull-requests-limit: 10 + groups: + all-dependencies: + patterns: + - "*" + + # GitHub Actions - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" + day: "monday" + commit-message: + prefix: "chore(deps):" labels: - "dependencies" - "github-actions" + open-pull-requests-limit: 10 + groups: + all-actions: + patterns: + - "*" diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml new file mode 100644 index 0000000..6073684 --- /dev/null +++ b/.github/workflows/dependabot-auto-merge.yml @@ -0,0 +1,23 @@ +name: Auto-merge Dependabot + +on: pull_request + +permissions: + contents: write + pull-requests: write + +jobs: + auto-merge: + if: github.actor == 'dependabot[bot]' + runs-on: ubuntu-latest + steps: + - uses: dependabot/fetch-metadata@v2 + id: metadata + + - if: >- + steps.metadata.outputs.package-ecosystem == 'github_actions' || + steps.metadata.outputs.package-ecosystem == 'npm_and_yarn' + run: gh pr merge --auto --squash "$PR_URL" + env: + PR_URL: ${{ github.event.pull_request.html_url }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 681e7e813d82b09a06543ab96b01efc1d4118bea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 22:01:17 +0200 Subject: [PATCH 194/449] chore(deps): bump the gradle-plugins group with 4 updates (#633) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the gradle-plugins group with 4 updates: [org.jetbrains.dokka:dokka-gradle-plugin](https://github.com/Kotlin/dokka), [org.jetbrains.kotlinx.kover](https://github.com/Kotlin/kotlinx-kover), org.jlleitschuh.gradle.ktlint and org.openapi.generator. Updates `org.jetbrains.dokka:dokka-gradle-plugin` from 2.1.0 to 2.2.0
Release notes

Sourced from org.jetbrains.dokka:dokka-gradle-plugin's releases.

2.2.0

Dokka Gradle Plugin

Starting from Dokka 2.1.0, the new Dokka Gradle Plugin is enabled by default. The documentation on kotlinlang.org has been updated accordingly:

Dokka 2.2.0 introduces multiple improvements and fixes:

Note: most of the following changes affect only the new Dokka Gradle Plugin, enabled by default since Dokka 2.1.0

Analysis improvements

Starting from Dokka 2.1.0, the K2 analysis is enabled by default. K2 analysis is now stable, enabled by default, and fully migrated to the new shared Analysis API. This includes the migration to the new KDoc resolution API within the Analysis API.

Dokka 2.2.0 introduces multiple improvements and fixes:

Note: most of the following changes affect only Dokka's K2 analysis, enabled by default since Dokka 2.1.0

  • Allow actual declarations to automatically inherit their documentation from expect counterparts in multiplatform projects (#2493, #4245, #4351)
  • Link resolution improvements:
    • Support references to declarations with quoted names (#3356)
    • Support resolution of links to extensions with type parameters according to KEEP#385 (#3555)
    • Improve handling of ambiguous KDoc links according to KEEP#389 (#3451, #3179, #3632, #4327, #3604)
      • Note: those changes are currently available only under experimental org.jetbrains.dokka.analysis.enableExperimentalKDocResolution system property
  • K2/K1 compatibility improvements:
    • Fix Multiple pages associated with key (#4300)
    • Fix inconsistent constructor rendering for expect/actual annotation (#4055)
    • Fix missing abstract modifier for abstract interface method with redundant open modifier (#4144)
    • Fix working with intersected and overridden fake functions/properties (#3857)
    • Fix rendering of links in @see block (#3680)
    • Fix redundant ? on properties with a type of typealias to nullable type (#4337)
    • Fix the missing default parameter value for inherited (not overridden) members (#4320)
    • Fix duplicate source links for function overloads and properties (#4049, #4338)
    • Fix resolution of links in the second line of KDoc tags (#4332, KT-75215, KT-79783)
  • Improve DRI handling for varargs and properties (#3558, #4347)
  • Context parameters improvements:
    • Fix KDoc links to context parameters (#4389)

... (truncated)

Commits
  • 656ca46 Update Dokka version references to 2.2.0 (#4485)
  • 534e242 Update com.gradle.publish:plugin-publish-plugin and declare Gradle CC compa...
  • abc048d Update the version for the 2.2.0 release
  • 60062bb Detect and handle intersected source roots in Android multi-variant projects ...
  • 0bd8995 Fix DGP reading all Gradle properties (#4468)
  • 06dbbfd Update the version for the 2.2.0-Beta release
  • a81ace7 Update AGP version in tests to stable 9.0.0 (#4420)
  • fd97482 Fix minor oversight in the implemenation: missed + in enum entries (#4411)
  • 8148aa2 Fix unnecesary logging for unresolved links in module documentation (#4413)
  • bf3b5ee Fix suppressGeneratedFiles was unused (#4348)
  • Additional commits viewable in compare view

Updates `org.jetbrains.kotlinx.kover` from 0.9.7 to 0.9.8
Release notes

Sourced from org.jetbrains.kotlinx.kover's releases.

0.9.8

Kover Gradle Plugin

Bugfixes

Internal features

Changelog

Sourced from org.jetbrains.kotlinx.kover's changelog.

0.9.8 / 2026-03-25

Kover Gradle Plugin

Internal features

Bugfixes

Commits
  • e329fea Release 0.9.8
  • d006ccd Filled Implementation fields Vendor/Title/Version in MANIFEST.MF
  • 9c300e1 Fixed Android multiplatform library support in case of the violation of the p...
  • e302173 Update publish plugin and declare configuration cache compatibility
  • b95e2bf Fix aggregated plugin docs
  • See full diff in compare view

Updates `org.jlleitschuh.gradle.ktlint` from 14.0.1 to 14.2.0 Updates `org.openapi.generator` from 7.18.0 to 7.21.0 Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 313c8f6..b5a22e3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,14 +3,14 @@ atomicfu = "0.31.0" binaryCompatibilityValidatorPlugin = "0.18.1" detekt = "2.0.0-alpha.2" -dokka = "2.1.0" +dokka = "2.2.0" kotlin = "2.2.21" -kover = "0.9.7" -ktlint = "14.0.1" +kover = "0.9.8" +ktlint = "14.2.0" knit = "0.5.1" mavenPublish = "0.36.0" netty = "4.2.10.Final" -openapi-generator = "7.18.0" +openapi-generator = "7.21.0" # libraries version awaitility = "4.3.0" From aed2439ecd5c6e864c7afc9b4b07f6e169acb404 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 23:11:06 +0200 Subject: [PATCH 195/449] chore(deps): bump the infrastructure group with 2 updates (#632) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the infrastructure group with 2 updates: [io.github.oshai:kotlin-logging](https://github.com/oshai/kotlin-logging) and [io.netty:netty-bom](https://github.com/netty/netty). Updates `io.github.oshai:kotlin-logging` from 7.0.14 to 8.0.01
Release notes

Sourced from io.github.oshai:kotlin-logging's releases.

8.0.01

What's Changed

Full Changelog: https://github.com/oshai/kotlin-logging/compare/7.0.14...8.0.01

8.0.00-beta-04

What's Changed

Full Changelog: https://github.com/oshai/kotlin-logging/compare/8.0.00-beta-03...8.0.00-beta-04

8.0.00-beta-03

What's Changed

Full Changelog: https://github.com/oshai/kotlin-logging/compare/8.0.00-beta-02...8.0.00-beta-03

8.0.00-beta-02

What's Changed

Full Changelog: https://github.com/oshai/kotlin-logging/compare/8.0.00-beta-01...8.0.00-beta-02

8.0.00-beta-01

What's Changed

Full Changelog: https://github.com/oshai/kotlin-logging/compare/7.0.14...8.0.00-beta-01

Commits
  • b1eb9fc bump version to 8.0.01
  • ff436aa bump version to 8.0.00-beta-05
  • 36e56c3 Fix Darwin OSLog composition failure in historical logs (#594)
  • 4ff0b7d bump version to 8.0.00-beta-04
  • 283f30c Feature: Add JPMS Support (Issue #365) (#592)
  • e5366c2 bump version to 8.0.00-beta-03
  • eb267ab feat: add configuration to suppress startup message
  • 6a67a6f Fix Darwin historical logs redaction using public format string
  • aa51a0d feat: add timestamp to KLoggingEvent
  • 733deaa fix action
  • Additional commits viewable in compare view

Updates `io.netty:netty-bom` from 4.2.10.Final to 4.2.12.Final
Release notes

Sourced from io.netty:netty-bom's releases.

netty-4.2.12.Final

What's Changed

Full Changelog: https://github.com/netty/netty/compare/netty-4.2.11.Final...netty-4.2.12.Final

netty-4.2.11.Final

Security

What's Changed

... (truncated)

Commits
  • 67ce541 [maven-release-plugin] prepare release netty-4.2.12.Final
  • 7074624 Revert "Eliminate redundant bounds checks in CompositeByteBuf accessors" (#16...
  • c3b0a43 [maven-release-plugin] prepare for next development iteration
  • c94a818 [maven-release-plugin] prepare release netty-4.2.11.Final
  • 3b76df1 Merge commit from fork
  • aae944a Auto-port 4.2: Limit the number of Continuation frames per HTTP2 Headers (#16...
  • 6001499 Eliminate redundant bounds checks in CompositeByteBuf accessors (#16525)
  • a7fbb6f JdkZlibDecoder: accumulate decompressed output before firing channelRead (#16...
  • 7937553 Enforce io.netty.maxDirectMemory accounting on all Java versions (#16489)
  • 893ea2e Allocate less in QueryStringDecoder.addParam for typical use case (#16527)
  • Additional commits viewable in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b5a22e3..a3cb759 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,7 +9,7 @@ kover = "0.9.8" ktlint = "14.2.0" knit = "0.5.1" mavenPublish = "0.36.0" -netty = "4.2.10.Final" +netty = "4.2.12.Final" openapi-generator = "7.21.0" # libraries version @@ -19,7 +19,7 @@ coroutines = "1.10.2" kotest = "6.1.3" kotlinx-io = "0.8.2" ktor = "3.2.3" -logging = "7.0.14" +logging = "8.0.01" mockk = "1.14.9" mokksy = "0.8.0" serialization = "1.9.0" From 1cda704694fa920fe986aa634dfbcf7ec25e90c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 21:28:37 +0000 Subject: [PATCH 196/449] chore(deps): bump the all-actions group with 3 updates (#631) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the all-actions group with 3 updates: [gradle/actions](https://github.com/gradle/actions), [actions/deploy-pages](https://github.com/actions/deploy-pages) and [codecov/codecov-action](https://github.com/codecov/codecov-action). Updates `gradle/actions` from 5 to 6
Release notes

Sourced from gradle/actions's releases.

v6.0.0

[!IMPORTANT] The release of gradle/actions@v6 contains important changes to the license terms. More details in this blog post. TL;DR: By upgrading to v6, you accept the Terms of Use for the gradle-actions-caching component.

Summary

  • Caching functionality of 'gradle-actions' has been extracted into a separate gradle-actions-caching library, and is no longer open-source. See this blog post for more context.
  • Existing, rudimentary, configuration-cache support has been removed, pending a fully functional implementation in gradle-actions-caching.
  • Dependencies updated to address security vulnerabilities

[!IMPORTANT]

Licensing notice

The caching functionality in `gradle-actions` has been extracted into `gradle-actions-caching`, a proprietary commercial component that is not covered by the MIT License. The bundled `gradle-actions-caching` component is licensed and governed by a separate license, available at https://gradle.com/legal/terms-of-use/.

The `gradle-actions-caching` component is used only when caching is enabled and is not loaded or used when caching is disabled.

Use of the `gradle-actions-caching` component is subject to a separate license, available at https://gradle.com/legal/terms-of-use/. If you do not agree to these license terms, do not use the `gradle-actions-caching` component.

What's Changed

Full Changelog: https://github.com/gradle/actions/compare/v5.0.2...v6.0.0

v5.0.2

Summary

This release contains no functional changes. It updates dependencies and known Gradle wrapper checksums.

What's Changed

... (truncated)

Commits
  • 39e147c [bot] Update dist directory
  • 14ac3d6 Bump com.fasterxml.jackson.dataformat:jackson-dataformat-smile from 2.21.1 to...
  • 81fec7a Mention explicit license acceptance in notice (#912)
  • 4ac5b01 [bot] Update dist directory
  • f64284c Mute license warning when terms are accepted (#911)
  • c2457a7 Update tagging instructions for release
  • 8205114 Update Gradle version compatibility information
  • 6710000 Add typing information for use by typesafegithub (#910)
  • 3d0e2a8 Pin version for github actions
  • f663ed9 Ignore internal action files for type validation
  • Additional commits viewable in compare view

Updates `actions/deploy-pages` from 4 to 5
Release notes

Sourced from actions/deploy-pages's releases.

v5.0.0

Changelog


See details of all code changes since previous release.

:warning: For use with products other than GitHub.com, such as GitHub Enterprise Server, please consult the compatibility table.

v4.0.5

Changelog


See details of all code changes since previous release.

:warning: For use with products other than GitHub.com, such as GitHub Enterprise Server, please consult the compatibility table.

v4.0.4

Changelog


See details of all code changes since previous release.

:warning: For use with products other than GitHub.com, such as GitHub Enterprise Server, please consult the compatibility table.

v4.0.3

Changelog

... (truncated)

Commits
  • cd2ce8f Merge pull request #404 from salmanmkc/node24
  • bbe2a95 Update Node.js version to 24.x
  • 854d7aa Merge pull request #374 from actions/Jcambass-patch-1
  • 306bb81 Add workflow file for publishing releases to immutable action package
  • b742728 Merge pull request #360 from actions/dependabot/npm_and_yarn/npm_and_yarn-513...
  • 7273294 Bump braces in the npm_and_yarn group across 1 directory
  • 963791f Merge pull request #361 from actions/dependabot-friendly
  • 51bb29d Make the rebuild dist workflow safer for Dependabot
  • 89f3d10 Merge pull request #358 from actions/dependabot/npm_and_yarn/non-breaking-cha...
  • bce7355 Merge branch 'main' into dependabot/npm_and_yarn/non-breaking-changes-99c12deb21
  • Additional commits viewable in compare view

Updates `codecov/codecov-action` from 5 to 6
Release notes

Sourced from codecov/codecov-action's releases.

v6.0.0

⚠️ This version introduces support for node24 which make cause breaking changes for systems that do not currently support node24. ⚠️

What's Changed

Full Changelog: https://github.com/codecov/codecov-action/compare/v5.5.4...v6.0.0

v5.5.4

This is a mirror of v5.5.2. v6 will be released which requires node24

What's Changed

Full Changelog: https://github.com/codecov/codecov-action/compare/v5.5.3...v5.5.4

v5.5.3

What's Changed

Full Changelog: https://github.com/codecov/codecov-action/compare/v5.5.2...v5.5.3

v5.5.2

What's Changed

New Contributors

Full Changelog: https://github.com/codecov/codecov-action/compare/v5.5.1...v5.5.2

v5.5.1

What's Changed

... (truncated)

Changelog

Sourced from codecov/codecov-action's changelog.

v5.5.2

What's Changed

Full Changelog: https://github.com/codecov/codecov-action/compare/v5.5.1..v5.5.2

v5.5.1

What's Changed

Full Changelog: https://github.com/codecov/codecov-action/compare/v5.5.0..v5.5.1

v5.5.0

What's Changed

Full Changelog: https://github.com/codecov/codecov-action/compare/v5.4.3..v5.5.0

v5.4.3

What's Changed

Full Changelog: https://github.com/codecov/codecov-action/compare/v5.4.2..v5.4.3

v5.4.2

... (truncated)

Commits

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/apidocs.yaml | 4 ++-- .github/workflows/build.yml | 10 +++++----- .github/workflows/codeql.yml | 2 +- .github/workflows/conformance.yml | 8 ++++---- .github/workflows/gradle-publish.yml | 2 +- .github/workflows/samples.yml | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/apidocs.yaml b/.github/workflows/apidocs.yaml index 5c7ed4c..76f4434 100644 --- a/.github/workflows/apidocs.yaml +++ b/.github/workflows/apidocs.yaml @@ -34,7 +34,7 @@ jobs: cache: gradle - name: Setup Gradle - uses: gradle/actions/setup-gradle@v5 + uses: gradle/actions/setup-gradle@v6 - name: Generate Dokka Site run: |- @@ -47,4 +47,4 @@ jobs: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v5 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4fd2522..3467db2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -63,7 +63,7 @@ jobs: run: npm ci - name: Setup Gradle - uses: gradle/actions/setup-gradle@v5 + uses: gradle/actions/setup-gradle@v6 with: validate-wrappers: true add-job-summary: 'always' @@ -106,7 +106,7 @@ jobs: - name: Upload coverage reports to Codecov if: ${{ !cancelled() && matrix.os == 'ubuntu-latest' }} - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} report_type: coverage @@ -115,7 +115,7 @@ jobs: - name: Upload test results to Codecov if: ${{ !cancelled() && matrix.os == 'ubuntu-latest' }} - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} report_type: test_results @@ -151,7 +151,7 @@ jobs: distribution: ${{ env.JAVA_DISTRIBUTION }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v5 + uses: gradle/actions/setup-gradle@v6 with: validate-wrappers: true add-job-summary: 'always' @@ -193,7 +193,7 @@ jobs: distribution: ${{ env.JAVA_DISTRIBUTION }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v5 + uses: gradle/actions/setup-gradle@v6 with: add-job-summary: 'always' cache-read-only: true diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 41d1fde..5697b61 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -40,7 +40,7 @@ jobs: build-mode: manual - name: Setup Gradle - uses: gradle/actions/setup-gradle@v5 + uses: gradle/actions/setup-gradle@v6 with: validate-wrappers: true cache-read-only: true diff --git a/.github/workflows/conformance.yml b/.github/workflows/conformance.yml index b218b4c..5b5e405 100644 --- a/.github/workflows/conformance.yml +++ b/.github/workflows/conformance.yml @@ -34,7 +34,7 @@ jobs: distribution: ${{ env.JAVA_DISTRIBUTION }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v5 + uses: gradle/actions/setup-gradle@v6 with: cache-read-only: ${{ github.ref != 'refs/heads/main' }} @@ -85,7 +85,7 @@ jobs: distribution: ${{ env.JAVA_DISTRIBUTION }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v5 + uses: gradle/actions/setup-gradle@v6 with: cache-read-only: ${{ github.ref != 'refs/heads/main' }} @@ -116,7 +116,7 @@ jobs: distribution: ${{ env.JAVA_DISTRIBUTION }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v5 + uses: gradle/actions/setup-gradle@v6 with: cache-read-only: ${{ github.ref != 'refs/heads/main' }} @@ -155,7 +155,7 @@ jobs: distribution: ${{ env.JAVA_DISTRIBUTION }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v5 + uses: gradle/actions/setup-gradle@v6 with: cache-read-only: ${{ github.ref != 'refs/heads/main' }} diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml index ed9a63a..6b60e87 100644 --- a/.github/workflows/gradle-publish.yml +++ b/.github/workflows/gradle-publish.yml @@ -25,7 +25,7 @@ jobs: distribution: 'temurin' - name: Setup Gradle - uses: gradle/actions/setup-gradle@v5 + uses: gradle/actions/setup-gradle@v6 - name: Build run: ./gradlew build --rerun-tasks --max-workers 2 --continue diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index 632b710..2c910fd 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -45,7 +45,7 @@ jobs: distribution: ${{ env.JAVA_DISTRIBUTION }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v5 + uses: gradle/actions/setup-gradle@v6 with: validate-wrappers: true add-job-summary: 'always' From 0e1eef32809d14c7c55337f0357138d05c0a55a2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 00:38:30 +0000 Subject: [PATCH 197/449] chore(deps): bump the all-dependencies group across 1 directory with 4 updates (#627) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the all-dependencies group with 4 updates in the /integration-test/src/jvmTest/typescript directory: [zod](https://github.com/colinhacks/zod), [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node), [cors](https://github.com/expressjs/cors) and [typescript](https://github.com/microsoft/TypeScript). Updates `zod` from 4.3.5 to 4.3.6
Release notes

Sourced from zod's releases.

v4.3.6

Commits:

  • 9977fb0868432461de265a773319e80a90ba3e37 Add brand.dev to sponsors
  • f4b7bae3468f6188b8f004e007d722148fc91d77 Update pullfrog.yml (#5634)
  • 251d7163a0ac7740fee741428d913e3c55702ace Clean up workflow_call
  • edd4132466da0f5065a8e051b599d01fdd1081d8 fix: add missing User-agent to robots.txt and allow all (#5646)
  • 85db85e9091d0706910d60c7eb2e9c181edd87bd fix: typo in codec.test.ts file (#5628)
  • cbf77bb12bdfda2e054818e79001f5cb3798ce76 Avoid non null assertion (#5638)
  • dfbbf1c1ae0c224b8131d80ddf0a264262144086 Avoid re-exported star modules (#5656)
  • 762e911e5773f949452fd6dd4e360f2362110e8e Generalize numeric key handling
  • ca3c8629c0c2715571f70b44c2433cad3db7fe4e v4.3.6
Commits

Updates `@types/node` from 24.10.6 to 25.5.0
Commits

Updates `cors` from 2.8.5 to 2.8.6
Release notes

Sourced from cors's releases.

v2.8.6

What's Changed

New Contributors

... (truncated)

Changelog

Sourced from cors's changelog.

2.8.6 / 2026-01-22

  • Improve documentation (API, context, examples...)
  • Remove additional markdown files from tarball
Commits
Maintainer changes

This version was pushed to npm by ulisesgascon, a new releaser for cors since your current version.


Updates `typescript` from 5.9.3 to 6.0.2
Release notes

Sourced from typescript's releases.

TypeScript 6.0

For release notes, check out the release announcement blog post.

Downloads are available on:

TypeScript 6.0 Beta

For release notes, check out the release announcement.

Downloads are available on:

Commits

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../src/jvmTest/typescript/package-lock.json | 44 ++++++++++--------- .../src/jvmTest/typescript/package.json | 8 ++-- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index 1f26245..2021be5 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -10,15 +10,15 @@ "dependencies": { "@modelcontextprotocol/sdk": "^1.28.0", "express": "^5.2.1", - "zod": "^4.3.5" + "zod": "^4.3.6" }, "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^24.10.6", - "cors": "^2.8.5", + "@types/node": "^25.5.0", + "cors": "^2.8.6", "tsx": "^4.21.0", - "typescript": "^5.9.3" + "typescript": "^6.0.2" } }, "node_modules/@esbuild/aix-ppc64": { @@ -579,13 +579,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.10.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.6.tgz", - "integrity": "sha512-B8h60xgJMR/xmgyX9fncRzEW9gCxoJjdenUhke2v1JGOd/V66KopmWrLPXi5oUI4VuiGK+d+HlXJjDRZMj21EQ==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.18.0" } }, "node_modules/@types/qs": { @@ -772,9 +772,9 @@ } }, "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", "license": "MIT", "dependencies": { "object-assign": "^4", @@ -782,6 +782,10 @@ }, "engines": { "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/cross-spawn": { @@ -1733,9 +1737,9 @@ } }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", + "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -1747,9 +1751,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "dev": true, "license": "MIT" }, @@ -1793,9 +1797,9 @@ "license": "ISC" }, "node_modules/zod": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", - "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/integration-test/src/jvmTest/typescript/package.json b/integration-test/src/jvmTest/typescript/package.json index 3ce560c..0a43cd2 100644 --- a/integration-test/src/jvmTest/typescript/package.json +++ b/integration-test/src/jvmTest/typescript/package.json @@ -11,14 +11,14 @@ "dependencies": { "@modelcontextprotocol/sdk": "^1.28.0", "express": "^5.2.1", - "zod": "^4.3.5" + "zod": "^4.3.6" }, "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^24.10.6", - "cors": "^2.8.5", + "@types/node": "^25.5.0", + "cors": "^2.8.6", "tsx": "^4.21.0", - "typescript": "^5.9.3" + "typescript": "^6.0.2" } } From 4a82d20192f7191497ecffcf1ee8e4ceaec0f278 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 00:58:15 +0000 Subject: [PATCH 198/449] chore(deps): bump the other-dependencies group across 4 directories with 4 updates (#634) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the other-dependencies group with 3 updates in the /samples/kotlin-mcp-client directory: [gradle-wrapper](https://github.com/gradle/gradle), [com.anthropic:anthropic-java](https://github.com/anthropics/anthropic-sdk-java) and [com.gradleup.shadow](https://github.com/GradleUp/shadow). Bumps the other-dependencies group with 2 updates in the /samples/kotlin-mcp-server directory: [gradle-wrapper](https://github.com/gradle/gradle) and [com.gradleup.shadow](https://github.com/GradleUp/shadow). Bumps the other-dependencies group with 2 updates in the /samples/simple-streamable-server directory: [gradle-wrapper](https://github.com/gradle/gradle) and [com.gradleup.shadow](https://github.com/GradleUp/shadow). Bumps the other-dependencies group with 3 updates in the /samples/weather-stdio-server directory: [gradle-wrapper](https://github.com/gradle/gradle), [com.gradleup.shadow](https://github.com/GradleUp/shadow) and [io.github.oshai:kotlin-logging-jvm](https://github.com/oshai/kotlin-logging). Updates `gradle-wrapper` from 9.3.1 to 9.4.1
Release notes

Sourced from gradle-wrapper's releases.

9.4.1

The Gradle team is excited to announce Gradle 9.4.1.

Here are the highlights of this release:

  • Java 26 support
  • Non-class-based JVM tests
  • Enhanced console progress bar

Read the Release Notes

We would like to thank the following community members for their contributions to this release of Gradle: akankshaa-00, Attila Kelemen, Björn Kautler, dblood, Dennis Rieks, duvvuvenkataramana, John Burns, Julian, kevinstembridge, Niels Doucet, Philip Wedemann, ploober, Richard Hernandez, Roberto Perez Alcolea, Sebastian Lövdahl, stephan2405, Stephane Landelle, Ujwal Suresh Vanjare, Victor Merkulov, Vincent Potuček, Vladimir Sitnikov.

Upgrade instructions

Switch your build to use Gradle 9.4.1 by updating your wrapper:

./gradlew wrapper --gradle-version=9.4.1 && ./gradlew
wrapper

See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

Reporting problems

If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

... (truncated)

Commits

Updates `com.anthropic:anthropic-java` from 2.15.0 to 2.18.0
Release notes

Sourced from com.anthropic:anthropic-java's releases.

v2.18.0

2.18.0 (2026-03-18)

Full Changelog: v2.17.0...v2.18.0

Features

Bug Fixes

  • client: allow updating header/query affecting fields in toBuilder() (81c22ee)

Chores

  • internal: tweak CI branches (34b9875)
  • internal: update retry delay tests (704bc83)

v2.17.0

2.17.0 (2026-03-16)

Full Changelog: v2.16.1...v2.17.0

Features

  • api: GA thinking-display-setting (422b461)

v2.16.1

2.16.1 (2026-03-11)

Full Changelog: v2.16.0...v2.16.1

Bug Fixes

  • client: correct "exactly one" check in JSON schema validator (#309) (6e58ba9)
  • tests: declare baseUrl field in RetryingHttpClientTest (#1003) (4546f83)

Chores

  • internal: codegen related update (7a3a942)
  • internal: codegen related update (a293049)

v2.16.0

2.16.0 (2026-03-06)

Full Changelog: v2.15.0...v2.16.0

... (truncated)

Changelog

Sourced from com.anthropic:anthropic-java's changelog.

2.18.0 (2026-03-18)

Full Changelog: v2.17.0...v2.18.0

Features

Bug Fixes

  • client: allow updating header/query affecting fields in toBuilder() (81c22ee)

Chores

  • internal: tweak CI branches (34b9875)
  • internal: update retry delay tests (704bc83)

2.17.0 (2026-03-16)

Full Changelog: v2.16.1...v2.17.0

Features

  • api: GA thinking-display-setting (422b461)

2.16.1 (2026-03-11)

Full Changelog: v2.16.0...v2.16.1

Bug Fixes

  • client: correct "exactly one" check in JSON schema validator (#309) (6e58ba9)
  • tests: declare baseUrl field in RetryingHttpClientTest (#1003) (4546f83)

Chores

  • internal: codegen related update (7a3a942)
  • internal: codegen related update (a293049)

2.16.0 (2026-03-06)

Full Changelog: v2.15.0...v2.16.0

Features

  • api: change array_format to brackets (f9eef6e)

... (truncated)

Commits
  • 54cac60 release: 2.18.0
  • 26250c8 feat(api): manual updates
  • be47fc3 feat(api): manual updates
  • 39bf190 fix(client): allow updating header/query affecting fields in toBuilder()
  • 06d97c6 chore(internal): update retry delay tests
  • be96a0c chore(internal): tweak CI branches
  • ce72582 release: 2.17.0
  • 97cadfc feat(api): GA thinking-display-setting
  • cb2f646 release: 2.16.1
  • aa13ec7 fix(tests): declare baseUrl field in RetryingHttpClientTest (#1003)
  • Additional commits viewable in compare view

Updates `com.gradleup.shadow` from 9.2.2 to 9.4.0
Release notes

Sourced from com.gradleup.shadow's releases.

9.4.0

Added

  • Support Isolated Projects. (#1139)

Changed

  • Allow opting out of adding shadowJar into assemble lifecycle. (#1939)
    shadow {
    // Disable making `assemble` task depend on `shadowJar`. This is enabled
    by default.
      addShadowJarToAssembleLifecycle = false
    }
    
  • Stop catching ZipException when writing entries. (#1970)

Fixed

  • Fix interaction with Gradle artifact transforms. (#1345)
  • Fix skipStringConstants per-relocator behavior in mapName. (#1968)
  • Fix failing for non-existent class directories. (#1976)

9.3.2

Changed

  • Stop moving gradleApi dependency from api to compileOnly for Gradle 9.4+. (#1919)
  • Log warnings for duplicates in the final JAR. (#1931)

Fixed

  • Fix relocation patterns not included in task fingerprint. (#1933)

9.3.1

Fixed

  • Use ASM from jdependency embedded. (#1898)
    This fixes potential classpath conflicts when using Shadow with other plugins that also use ASM.

9.3.0

Added

  • Add PatternFilterableResourceTransformer to simplify pattern based ResourceTransformers. (#1849)
  • Expose patternSet of ServiceFileTransformer as public. (#1849)
  • Expose patternSet of ApacheLicenseResourceTransformer as public. (#1850)
  • Expose patternSet of ApacheNoticeResourceTransformer as public. (#1850)
  • Expose patternSet of PreserveFirstFoundResourceTransformer as public. (#1855)
  • Support overriding output path of ApacheNoticeResourceTransformer. (#1851)
  • Add new merge strategy Fail to PropertiesFileTransformer. (#1856)
  • Add FindResourceInClasspath task to help with debugging issues with merged duplicate resources. (#1860)
  • Add MergeLicenseResourceTransformer. (#1858)

... (truncated)

Commits
  • e51f3e7 Prepare version 9.4.0
  • cf531d4 Prefer parameterized logging (#1977)
  • 85ba389 Fix failing for non-existent class directories (#1976)
  • 3a24369 Update dependency org.apache.maven:maven-model to v3.9.14 (#1973)
  • 6e3dfb5 Use removePrefix instead of replace for multi-release prefix removal (#1972)
  • 49f8c31 Reuse most logic for writing entries (#1971)
  • ed47d0c Update README.md for clarity and formatting
  • d73d94a Stop catching ZipException when writing entries (#1970)
  • 0564cae Fix skipStringConstants per-relocator behavior: use continue instead of retur...
  • 8d995b2 Split relocators remapping (#1967)
  • Additional commits viewable in compare view

Updates `gradle-wrapper` from 9.3.1 to 9.4.1
Release notes

Sourced from gradle-wrapper's releases.

9.4.1

The Gradle team is excited to announce Gradle 9.4.1.

Here are the highlights of this release:

  • Java 26 support
  • Non-class-based JVM tests
  • Enhanced console progress bar

Read the Release Notes

We would like to thank the following community members for their contributions to this release of Gradle: akankshaa-00, Attila Kelemen, Björn Kautler, dblood, Dennis Rieks, duvvuvenkataramana, John Burns, Julian, kevinstembridge, Niels Doucet, Philip Wedemann, ploober, Richard Hernandez, Roberto Perez Alcolea, Sebastian Lövdahl, stephan2405, Stephane Landelle, Ujwal Suresh Vanjare, Victor Merkulov, Vincent Potuček, Vladimir Sitnikov.

Upgrade instructions

Switch your build to use Gradle 9.4.1 by updating your wrapper:

./gradlew wrapper --gradle-version=9.4.1 && ./gradlew
wrapper

See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

Reporting problems

If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

... (truncated)

Commits

Updates `com.gradleup.shadow` from 9.2.2 to 9.4.0
Release notes

Sourced from com.gradleup.shadow's releases.

9.4.0

Added

  • Support Isolated Projects. (#1139)

Changed

  • Allow opting out of adding shadowJar into assemble lifecycle. (#1939)
    shadow {
    // Disable making `assemble` task depend on `shadowJar`. This is enabled
    by default.
      addShadowJarToAssembleLifecycle = false
    }
    
  • Stop catching ZipException when writing entries. (#1970)

Fixed

  • Fix interaction with Gradle artifact transforms. (#1345)
  • Fix skipStringConstants per-relocator behavior in mapName. (#1968)
  • Fix failing for non-existent class directories. (#1976)

9.3.2

Changed

  • Stop moving gradleApi dependency from api to compileOnly for Gradle 9.4+. (#1919)
  • Log warnings for duplicates in the final JAR. (#1931)

Fixed

  • Fix relocation patterns not included in task fingerprint. (#1933)

9.3.1

Fixed

  • Use ASM from jdependency embedded. (#1898)
    This fixes potential classpath conflicts when using Shadow with other plugins that also use ASM.

9.3.0

Added

  • Add PatternFilterableResourceTransformer to simplify pattern based ResourceTransformers. (#1849)
  • Expose patternSet of ServiceFileTransformer as public. (#1849)
  • Expose patternSet of ApacheLicenseResourceTransformer as public. (#1850)
  • Expose patternSet of ApacheNoticeResourceTransformer as public. (#1850)
  • Expose patternSet of PreserveFirstFoundResourceTransformer as public. (#1855)
  • Support overriding output path of ApacheNoticeResourceTransformer. (#1851)
  • Add new merge strategy Fail to PropertiesFileTransformer. (#1856)
  • Add FindResourceInClasspath task to help with debugging issues with merged duplicate resources. (#1860)
  • Add MergeLicenseResourceTransformer. (#1858)

... (truncated)

Commits
  • e51f3e7 Prepare version 9.4.0
  • cf531d4 Prefer parameterized logging (#1977)
  • 85ba389 Fix failing for non-existent class directories (#1976)
  • 3a24369 Update dependency org.apache.maven:maven-model to v3.9.14 (#1973)
  • 6e3dfb5 Use removePrefix instead of replace for multi-release prefix removal (#1972)
  • 49f8c31 Reuse most logic for writing entries (#1971)
  • ed47d0c Update README.md for clarity and formatting
  • d73d94a Stop catching ZipException when writing entries (#1970)
  • 0564cae Fix skipStringConstants per-relocator behavior: use continue instead of retur...
  • 8d995b2 Split relocators remapping (#1967)
  • Additional commits viewable in compare view

Updates `gradle-wrapper` from 9.3.1 to 9.4.1
Release notes

Sourced from gradle-wrapper's releases.

9.4.1

The Gradle team is excited to announce Gradle 9.4.1.

Here are the highlights of this release:

  • Java 26 support
  • Non-class-based JVM tests
  • Enhanced console progress bar

Read the Release Notes

We would like to thank the following community members for their contributions to this release of Gradle: akankshaa-00, Attila Kelemen, Björn Kautler, dblood, Dennis Rieks, duvvuvenkataramana, John Burns, Julian, kevinstembridge, Niels Doucet, Philip Wedemann, ploober, Richard Hernandez, Roberto Perez Alcolea, Sebastian Lövdahl, stephan2405, Stephane Landelle, Ujwal Suresh Vanjare, Victor Merkulov, Vincent Potuček, Vladimir Sitnikov.

Upgrade instructions

Switch your build to use Gradle 9.4.1 by updating your wrapper:

./gradlew wrapper --gradle-version=9.4.1 && ./gradlew
wrapper

See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

Reporting problems

If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

... (truncated)

Commits

Updates `com.gradleup.shadow` from 9.2.2 to 9.4.0
Release notes

Sourced from com.gradleup.shadow's releases.

9.4.0

Added

  • Support Isolated Projects. (#1139)

Changed

  • Allow opting out of adding shadowJar into assemble lifecycle. (#1939)
    shadow {
    // Disable making `assemble` task depend on `shadowJar`. This is enabled
    by default.
      addShadowJarToAssembleLifecycle = false
    }
    
  • Stop catching ZipException when writing entries. (#1970)

Fixed

  • Fix interaction with Gradle artifact transforms. (#1345)
  • Fix skipStringConstants per-relocator behavior in mapName. (#1968)
  • Fix failing for non-existent class directories. (#1976)

9.3.2

Changed

  • Stop moving gradleApi dependency from api to compileOnly for Gradle 9.4+. (#1919)
  • Log warnings for duplicates in the final JAR. (#1931)

Fixed

  • Fix relocation patterns not included in task fingerprint. (#1933)

9.3.1

Fixed

  • Use ASM from jdependency embedded. (#1898)
    This fixes potential classpath conflicts when using Shadow with other plugins that also use ASM.

9.3.0

Added

  • Add PatternFilterableResourceTransformer to simplify pattern based ResourceTransformers. (#1849)
  • Expose patternSet of ServiceFileTransformer as public. (#1849)
  • Expose patternSet of ApacheLicenseResourceTransformer as public. (#1850)
  • Expose patternSet of ApacheNoticeResourceTransformer as public. (#1850)
  • Expose patternSet of PreserveFirstFoundResourceTransformer as public. (#1855)
  • Support overriding output path of ApacheNoticeResourceTransformer. (#1851)
  • Add new merge strategy Fail to PropertiesFileTransformer. (#1856)
  • Add FindResourceInClasspath task to help with debugging issues with merged duplicate resources. (#1860)
  • Add MergeLicenseResourceTransformer. (#1858)

... (truncated)

Commits
  • e51f3e7 Prepare version 9.4.0
  • cf531d4 Prefer parameterized logging (#1977)
  • 85ba389 Fix failing for non-existent class directories (#1976)
  • 3a24369 Update dependency org.apache.maven:maven-model to v3.9.14 (#1973)
  • 6e3dfb5 Use removePrefix instead of replace for multi-release prefix removal (#1972)
  • 49f8c31 Reuse most logic for writing entries (#1971)
  • ed47d0c Update README.md for clarity and formatting
  • d73d94a Stop catching ZipException when writing entries (#1970)
  • 0564cae Fix skipStringConstants per-relocator behavior: use continue instead of retur...
  • 8d995b2 Split relocators remapping (#1967)
  • Additional commits viewable in compare view

Updates `gradle-wrapper` from 9.3.1 to 9.4.1
Release notes

Sourced from gradle-wrapper's releases.

9.4.1

The Gradle team is excited to announce Gradle 9.4.1.

Here are the highlights of this release:

  • Java 26 support
  • Non-class-based JVM tests
  • Enhanced console progress bar

Read the Release Notes

We would like to thank the following community members for their contributions to this release of Gradle: akankshaa-00, Attila Kelemen, Björn Kautler, dblood, Dennis Rieks, duvvuvenkataramana, John Burns, Julian, kevinstembridge, Niels Doucet, Philip Wedemann, ploober, Richard Hernandez, Roberto Perez Alcolea, Sebastian Lövdahl, stephan2405, Stephane Landelle, Ujwal Suresh Vanjare, Victor Merkulov, Vincent Potuček, Vladimir Sitnikov.

Upgrade instructions

Switch your build to use Gradle 9.4.1 by updating your wrapper:

./gradlew wrapper --gradle-version=9.4.1 && ./gradlew
wrapper

See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

Reporting problems

If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

... (truncated)

Commits

Updates `com.gradleup.shadow` from 9.2.2 to 9.4.0
Release notes

Sourced from com.gradleup.shadow's releases.

9.4.0

Added

  • Support Isolated Projects. (#1139)

Changed

  • Allow opting out of adding shadowJar into assemble lifecycle. (#1939)
    shadow {
    // Disable making `assemble` task depend on `shadowJar`. This is enabled
    by default.
      addShadowJarToAssembleLifecycle = false
    }
    
  • Stop catching ZipException when writing entries. (#1970)

Fixed

  • Fix interaction with Gradle artifact transforms. (#1345)
  • Fix skipStringConstants per-relocator behavior in mapName. (#1968)
  • Fix failing for non-existent class directories. (#1976)

9.3.2

Changed

  • Stop moving gradleApi dependency from api to compileOnly for Gradle 9.4+. (#1919)
  • Log warnings for duplicates in the final JAR. (#1931)

Fixed

  • Fix relocation patterns not included in task fingerprint. (#1933)

9.3.1

Fixed

  • Use ASM from jdependency embedded. (#1898)
    This fixes potential classpath conflicts when using Shadow with other plugins that also use ASM.

9.3.0

Added

  • Add PatternFilterableResourceTransformer to simplify pattern based ResourceTransformers. (#1849)
  • Expose patternSet of ServiceFileTransformer as public. (#1849)
  • Expose patternSet of ApacheLicenseResourceTransformer as public. (#1850)
  • Expose patternSet of ApacheNoticeResourceTransformer as public. (#1850)
  • Expose patternSet of PreserveFirstFoundResourceTransformer as public. (#1855)
  • Support overriding output path of ApacheNoticeResourceTransformer. (#1851)
  • Add new merge strategy Fail to PropertiesFileTransformer. (#1856)
  • Add FindResourceInClasspath task to help with debugging issues with merged duplicate resources. (#1860)
  • Add MergeLicenseResourceTransformer. (#1858)

... (truncated)

Commits
  • e51f3e7 Prepare version 9.4.0
  • cf531d4 Prefer parameterized logging (#1977)
  • 85ba389 Fix failing for non-existent class directories (#1976)
  • 3a24369 Update dependency org.apache.maven:maven-model to v3.9.14 (#1973)
  • 6e3dfb5 Use removePrefix instead of replace for multi-release prefix removal (#1972)
  • 49f8c31 Reuse most logic for writing entries (#1971)
  • ed47d0c Update README.md for clarity and formatting
  • d73d94a Stop catching ZipException when writing entries (#1970)
  • 0564cae Fix skipStringConstants per-relocator behavior: use continue instead of retur...
  • 8d995b2 Split relocators remapping (#1967)
  • Additional commits viewable in compare view

Updates `io.github.oshai:kotlin-logging-jvm` from 7.0.13 to 8.0.01
Release notes

Sourced from io.github.oshai:kotlin-logging-jvm's releases.

8.0.01

What's Changed

Full Changelog: https://github.com/oshai/kotlin-logging/compare/7.0.14...8.0.01

8.0.00-beta-04

What's Changed

Full Changelog: https://github.com/oshai/kotlin-logging/compare/8.0.00-beta-03...8.0.00-beta-04

8.0.00-beta-03

What's Changed

Full Changelog: https://github.com/oshai/kotlin-logging/compare/8.0.00-beta-02...8.0.00-beta-03

8.0.00-beta-02

What's Changed

Full Changelog: https://github.com/oshai/kotlin-logging/compare/8.0.00-beta-01...8.0.00-beta-02

8.0.00-beta-01

What's Changed

Full Changelog: https://github.com/oshai/kotlin-logging/compare/7.0.14...8.0.00-beta-01

7.0.14

What's Changed

... (truncated)

Commits
  • b1eb9fc bump version to 8.0.01
  • ff436aa bump version to 8.0.00-beta-05
  • 36e56c3 Fix Darwin OSLog composition failure in historical logs (#594)
  • 4ff0b7d bump version to 8.0.00-beta-04
  • 283f30c Feature: Add JPMS Support (Issue #365) (#592)
  • e5366c2 bump version to 8.0.00-beta-03
  • eb267ab feat: add configuration to suppress startup message
  • 6a67a6f Fix Darwin historical logs redaction using public format string
  • aa51a0d feat: add timestamp to KLoggingEvent
  • 733deaa fix action
  • Additional commits viewable in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../gradle/libs.versions.toml | 4 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 46175 -> 48966 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- samples/kotlin-mcp-client/gradlew | 2 +- samples/kotlin-mcp-client/gradlew.bat | 186 +++++++++--------- .../gradle/libs.versions.toml | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 46175 -> 48966 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- samples/kotlin-mcp-server/gradlew | 2 +- samples/kotlin-mcp-server/gradlew.bat | 186 +++++++++--------- .../gradle/libs.versions.toml | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 46175 -> 48966 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- samples/simple-streamable-server/gradlew | 2 +- samples/simple-streamable-server/gradlew.bat | 186 +++++++++--------- .../gradle/libs.versions.toml | 4 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 46175 -> 48966 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- samples/weather-stdio-server/gradlew | 2 +- samples/weather-stdio-server/gradlew.bat | 186 +++++++++--------- 20 files changed, 386 insertions(+), 386 deletions(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index ab34b7e..2a819cf 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -1,9 +1,9 @@ [versions] -anthropic = "2.15.0" +anthropic = "2.18.0" kotlin = "2.2.21" ktor = "3.2.3" mcp-kotlin = "0.9.0" -shadow = "9.2.2" +shadow = "9.4.0" slf4j = "2.0.17" [libraries] diff --git a/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.jar b/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.jar index 61285a659d17295f1de7c53e24fdf13ad755c379..d997cfc60f4cff0e7451d19d49a82fa986695d07 100644 GIT binary patch delta 39855 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

SdO`o?U^bCPz6Ehh!k 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


Xdyd*p!l1Y@nCA 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 diff --git a/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties b/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties index 37f78a6..c61a118 100644 --- a/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties +++ b/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/samples/kotlin-mcp-client/gradlew b/samples/kotlin-mcp-client/gradlew index adff685..739907d 100755 --- a/samples/kotlin-mcp-client/gradlew +++ b/samples/kotlin-mcp-client/gradlew @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. diff --git a/samples/kotlin-mcp-client/gradlew.bat b/samples/kotlin-mcp-client/gradlew.bat index c4bdd3a..e509b2d 100644 --- a/samples/kotlin-mcp-client/gradlew.bat +++ b/samples/kotlin-mcp-client/gradlew.bat @@ -1,93 +1,93 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/samples/kotlin-mcp-server/gradle/libs.versions.toml b/samples/kotlin-mcp-server/gradle/libs.versions.toml index 0ce6d58..c96f6c1 100644 --- a/samples/kotlin-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-server/gradle/libs.versions.toml @@ -3,7 +3,7 @@ kotlin = "2.2.21" ktor = "3.2.3" mcp-kotlin = "0.8.1" slf4j = "2.0.17" -shadow = "9.2.2" +shadow = "9.4.0" [libraries] ktor-bom = { group = "io.ktor", name = "ktor-bom", version.ref = "ktor" } diff --git a/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.jar b/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.jar index 61285a659d17295f1de7c53e24fdf13ad755c379..d997cfc60f4cff0e7451d19d49a82fa986695d07 100644 GIT binary patch delta 39855 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

SdO`o?U^bCPz6Ehh!k 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


Xdyd*p!l1Y@nCA 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 diff --git a/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties b/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties index 37f78a6..c61a118 100644 --- a/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties +++ b/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/samples/kotlin-mcp-server/gradlew b/samples/kotlin-mcp-server/gradlew index adff685..739907d 100755 --- a/samples/kotlin-mcp-server/gradlew +++ b/samples/kotlin-mcp-server/gradlew @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. diff --git a/samples/kotlin-mcp-server/gradlew.bat b/samples/kotlin-mcp-server/gradlew.bat index c4bdd3a..e509b2d 100644 --- a/samples/kotlin-mcp-server/gradlew.bat +++ b/samples/kotlin-mcp-server/gradlew.bat @@ -1,93 +1,93 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/samples/simple-streamable-server/gradle/libs.versions.toml b/samples/simple-streamable-server/gradle/libs.versions.toml index 7240ff2..ebe741c 100644 --- a/samples/simple-streamable-server/gradle/libs.versions.toml +++ b/samples/simple-streamable-server/gradle/libs.versions.toml @@ -3,7 +3,7 @@ kotlin = "2.2.21" ktor = "3.2.3" mcp-kotlin = "0.9.0" slf4j = "2.0.17" -shadow = "9.2.2" +shadow = "9.4.0" [libraries] ktor-bom = { group = "io.ktor", name = "ktor-bom", version.ref = "ktor" } diff --git a/samples/simple-streamable-server/gradle/wrapper/gradle-wrapper.jar b/samples/simple-streamable-server/gradle/wrapper/gradle-wrapper.jar index 61285a659d17295f1de7c53e24fdf13ad755c379..d997cfc60f4cff0e7451d19d49a82fa986695d07 100644 GIT binary patch delta 39855 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

SdO`o?U^bCPz6Ehh!k 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


Xdyd*p!l1Y@nCA zkqV|5mav3F`$}CIvn~v_k?Q7B*`oQ<_j@sn0<>6eya4v)opW!qeva_Tn=Y*^yeQ!|_JrAs%LLir z?%?aYiC@Ay&q`uFSn>Nsh0`LaUceI8*kRXw(57^PV3DnDa9Ov|!nN)&*SY~JNgJJZ z8|#BV)HpfCmB)t&ak$M!KHAbRCi8?aKo!pYb?chG0q! zeI*6=Wpt(CrW}L5OZWM0nzBvaZ4WqS&V~mC z&=wc@kA-IR5CWaaKgaTdx3D?PRH=rsvX|+_kEn{wuPlbxF4W2b)0B~97a&J3)jlGp_>KOi ze5R&4FXWG}EUBa-u!je%Ay~@#wW!PKUf})*A)OwZ!<6q#7Qk6~D0Z}Q+O;MYwxrAq0{D2vYgnl{Xj|T%O1Kf_Iv% zp5Fc*$N?HAcHaPBzJ@)15maZ@@VPe3mfUL0vkposm2hq6T8Yvgu_z%ij4dIzP##!b zIbP-5Yn%)OZD5}A(OAzR;xzp5?Bpt{gHb-g!UlQ2y&qFpUvbs_t{e2)T;zNAvxymq>6wOY5+ycnxMdvK8Y+Ms@D=G9h*QAY;O2HtgUYw_C$jqbqAP+PeVg-j}!dT4E)76--?}`E~R!5-?K08Cy-0Q zaBYPh82SI0eT0IF>>#7-Z?=Q_JnD24UR=3OGvnW-g%@$ zyKy~4ArAL6qz`j1lUNKa60erJcem@)0GS!Q0si$bl>CL&s76ltEzF2EX@ z%eo%30f5@xzeRY3S%^J^qXo+~3!YJ@3k$5BAAUN$L!Srj%k%n8kh%Zm^rqn}czuVJ z;CSKcFDfF%<&>qYArI{{E@dkf8xH?TxVR9y`;*Y(%t!JmEMj`9>W{eeN)SuG6!8%va) zm?M@bqh6-ohJ|rdRCAk6e~B$Fr?(^603cywC`*a^m$FR>`ubqKx_c-({lS0$F>{hE zfr8m0d>4KAE0cL^$|W1UV?fGl{9u*@02h_rp2+;2aACw~=y>fq=tr!h&VvG@4-96V zVOyF4cD(Dg1EX;GrPF!+^7&-{nSUvtbOJUHFCkvw8krRGc91{xp%>I4`}aXdd0AD# z75*k0rv>1&u-Tj_6Vsa^YCz*dqidGU%eeN24>s zU{F=Y*`7BFQZeT2baaDDv|0W9c077gr+0m~w2|8KH;rG)MT`4O%5HBSwBYkkaJ_`@8?-vAfC*U%zjU%M19KKxa_$;-BFIUS#4tKvd zZuvU9b_^QS0H-MbjQ%d2z|_S!{p0(U6FTld(GC2eKY%*_2`VrY?4$}VqACM>*Ku;gtm$b! zhKwsBDaZ|j7(Hy^OaQnn_wahak*lD%YOy5<7Z5J@0u^n?XUKVl-ZbKB*{u;I4d{|D zT{w)+DbFky=lduaxmQV9P{6kp+;+bb%+}Z)Yz1Su<&BP;F;sd`Nrww+65~kRdONnw z)P?x!VuC0xWI0Y;DCFiW2GT4W<4-Qh5O8@DnkYN2V4pDR*?=SM@q}w$Y6pH}466)7 zt{@z2a1*DiQ< zh^w_!(HzHXM!aO-oFRY-!d=%|CPYDxUPsy0m~lIm@b!n7I!lE9kvCd%6=s&~Lu_}V zE=T4)u2V=@K;?B8ci-3|;eMwS=^Rf5S1&p3QKsWG`9dKrk37#**T*_1`u=)9Gs^BB zS8JviR<+h$imDFb>J6&Zs*&9x-yB1{Nq4w{a5qA!2ihi&Ra`5ESh?*IEOuUjxw#un zG?wMlOlPVi+-?F?W`)cm}FJVa~!;_h{7)k)c>-iatF z+7LKfK?r0IfLJI1z#KKV;}c&9IXs%R@}rtNrGhIW7fukSQ<%4@I3< z^Q5v>x$7fWzml-bu#SV0X+DJJLL4KII5e_T34xEuLy%f8dz(hl1?1E7?Ys0!&$(X7 z27(I;P%>pyOVXsIjPFM@x=tL3hk2>$(PP`7!o@>E?tSf;aYqD32x~6?Z5&f}QT{b^!n2%}MzmcU5J&hqV zoLoVert|Bc0dbD(uZ*P!vSc_+k`c)bY(3G7z&-Du9{xYa+pkha4|LZVw~af^cc#@|$kVyU*8Z-3+VvN+{Cpj3J}lQ)h*1i1*- zKL%ylEKZQJ>-nYnRDEeL_~P_aT2NnSYOlNV#SoR zq!@>RE_MPo@R`~y>CISLr%kc-qc;rcv#Zpc1v&t3v3sfxeC2C8e|bfnSVPA^hX@-Y z1X?`hY#6op-dC#Q$XY-JCiZY~$$1m@=&mwDxE^RXWYo!-?^5egyBW(TENk@fghVG{ z*6+8)xH?X)A-l?M>6ycwK_k<#oOpBiX%s(jb|IG#U?a0BNfJ1)Pkkr!t=cy~A7L2WuYWM;|W3!qx5v%k_Airm*OMGvSd9& z0@aL~QaD8#8mV1fq1JNS3}FX7!5c~FOHA?k9gIY;g+1)BB&8fqo8y7*m>!2$-aEX9 zMhgR3@y|}+1x`$Mz5B(;AC7dX)lVQVP8yHXIhP=*r-js0BPGV&J_@@QJ(ev!o1~0> za|=y9b&)-WY_yOA!0~5j_XzoTt%{LHfx}==M?|V?=j|v(x}`bE?4YHSr46KnH`RiW ziI&kL8e$$CDdt_R-7)sX#52zT>4(2UJi71*CH~{jQdcb^fQQp9_%D0><^hnR8eL+m zv6PusB!YskX%x-z4982P;_TU9Hz*g(Ev`}UR#OEzEb+^AfGzL)R5SEf;zu&Zi5=KL zt3}7@{RLaB3|k$}r&6Lf!9s2ilRHR}3y|brT<2ulox~$dq%wCu2im%rXqaog+~QM~ z-d?f>Wr2Q_h^6yc%3PKr);u8J(8it587nv>ku_Opfeba>Rc=Boxq-->3Oy*C9pu8U zG6X&BpqR#%r%VFgk(fyy)Nip@{ba_f)0>&^KwSXt67!D?jXgfxvcf}!D$k2`Ol3;I zfs<{k_En&%6jOA|%V>j)LISy8_f+{=1X7AH(wA#wI*#-G!?ysFvOwhJ*HKwvs{{O_ zMBp>pC81{RUkUQC(GrHPll%-IGVwvlhXqpx3=XLwM!Fu1B0f|aFT!Kmi|B&No*j$5 zuk|^{j^`NN;1^h9bBkvPoikY?)5Q3rC{nUA-gU#GRKeVf*wXj&GlhU3CiF8AD))NK z3y3fmg&tgz>yl)g4SeU)IzVX~+kWVL?-;h4de^A}q@=&--a!JeJ5bu7f*u4*DSDPl zsQUi@?T15R?$E;i6&LmYD=rg);y{PxwYSREx)>(OQM?w!vS>0GUK|EQ@r>mo9^yPI zD;oO9vxrw*meRs~xL36Ur@_3O>2I^0oR1%m_b~f-gpduath{x!K4hVA^5X5+uo6Cd z$VN139w-NeEZ<73RP4eVyB5qyBFm zs{OwlU;!p-%5^(?N{=uuAzgwx3E~&7Y#geu$eLhp_Y^?hOjwqjg46(S%8f8SFr_UO z1Gm%W=H)f8|4;A3WB=X!U{HW(im`sr<@GpnM6&emu`d^-1K?0ebP)XF>ip+vm!p_@J<|F?;?^MXg#HN_NY z=PX+9B`sa*VT>X6S`4}I@I!SbVDby?pX86I5WECKok6@1{_c~rgD^8hP}p^Gm#Hb-i=JITDHQQY^N3h<3thj3M!@ae-vxeLaiS4}3}Bdd8u%g$)y()zm4up+S=kw_M|A;Y0F=&m^)@z)Zi!ca=M;uiPxV@x7K5$PYn zCR8ZE^`c_R%plpXeKhRBLgQq7-LK=Q8ChwVc^P*SmLo?ijNo*!k(wn`~0HN-z^3k?Oy*YE$G1B7GJ8?6$Xd85^Q57)q z@9O0TvL8;9rP0l)E*I1UD`=pyJ|q|QA~}h=hNbO4Mp#3#%?7*XKcAolEwYP8W^>1d z-QKHNs@)msIwl%{-t4m_+`~*0gNK02Iem+CVKciQT$=$2$hHhmWYQlb`>PBvHfK?7 zXSI54etziG=W6NWs%ROdE=TwwGP&w?6ihB(WKzgG18cgCI1Oii2*)|N&v4(ISy>p` zT3#vIx0PsBxpBo1fH=(28;Y+r`O=(#F+6<>6xVeZTBF#&ECt%g=%X5vmDvq&p|_CC zj(N8nzWV6MvV-N)v!v7)<^*N?LydSN?08=MA#P9Ddz9V0=3j(t4wr^=_sG>xdYe|@ zD|2GCZ>YCE`!phClJj$$m_u^QG{7InIQs1Y(=xBReaD#Q|5AQ1{zF>#^xQt#+Jekz z_Q-*bi8}MZJGIWT3>&*<)K!L(p?m6<@QklTqC}u)_b*F2gn43~$wwX-dt>U*vTr`M z@z@%#ha%d$VstphM&obv?>I;#(Cw;m(9WNys$Y6Vv{WN!C` zYtPP=cSh@UUm{u0X2&a%s!Lc4@&@zYO>ZR@rt-&t;0V5{#Ij39fKQ`{vPlEydt@N0 zTXIqS_FeDTp)aw`iIewSmgh0t@ae^bidlc@#w#aDA6Y}(-bX@vMdNSd!}Xu*oE@nJ zgSLIA<{fOvY7uJV$9A#kFYHk%LfuJJ z7o_%_Jt2`DpKcN3>A7|ueP(ty7uQxGfo3kDKL``$XlDi3NWYjYxlnE_KP~S%mk{F( z*tx$)%ReEdf!WVBSJ-x$khy-4L)Rjj1b)AKxi=$jA1Y8Y>KmPMf#|RNQsBS;zvSuM z0)H(93J^q_}}%C%#& zFzI>z#@E<)w7=hNl%-LKgkMWmvJu8Y11oR*ZdYsMpXW_{ULa8JH20@xXaC$+m{P3f z{@~+7T;ckOteKCqIiY^4mwCd@?mU_3IdY`fr8+A+Yn0ZtZ_5CTE7>WO9n!=psuw^MBW*dRe7{WnXgh1hQhZIq!1P0%#nO_B zaZi~=E{!A|Cfx*hrkFtsS$CZmBci=RXf+YqDFpzvvOEBMjsm3{m*R$&;NphjFcM`ojsfXMvgFXmssM!3gJN zOH>Q8N<<^l(%B)wS^}z3)G4yWJ)9iV6FsV(+|`_P1bdu*oJxG08bqnkI4{Slu=+G^ zcU{dYMP6)_jTp+ZHl>iLH3k;J@}b3kK#$Az9;=wXoEl6za>A?YK6}It;26pjB&Uj2 z@fByV{4?oWJB#5=273fd@FV*M&V1{@Y z2W6aB6UU+@Q zEPrvy`kFJXQ2x*@Pz>`ce*DjElf18B+e(R7v;lIDCGwFY7~-OMA3Zbh$!dqV!(&vC zQ8BrFH56#Re&*|LuE}cRCwm|dkYMTjJ`#+&UxMZY2Q6z@zPhTl%FVe44ETWEM?=LH zF*5EW35uMTGijVXDA7$gG_I}r!2<)Mu~AyffwS#4c%%oye2B_#?7M4T8kezP5PCTf zPyxzUV>aJS{1_fgsel4^fn7d)wXq;y5vbvoe$2*Md5@ih%x!$zpnh!>Jwr{2J-r`? zO%?ahoXtJKEjJB!K7QcxNyX0X^U++tT3W6~6p?*QjwOb158gQpg|9)((a6@&Pn=!W zIn`JrAL<&q!Qj^Fx@*y7>5;-LDr)?k(FI~EV`&TQ@G^6`RYbuv<0q~e!iCG^HTOS{ z+W@^|hYongciIvCdC5~kthMu}s6Re@KIl7PdHzOuQARbEp`~GkU0{0){N;24i+E@M z9IGF?@c5?D(nJHsa-KHXO=hq=4j?l!s7~%``wQdK5KQffO4vWPC2o>L@Z7dp(1h*N zN>xcs6rCuO6xs&8o|z0$rzDYx7* zsV*U+buabY3O&yBkj~F6z8$?9 zcU~HV+#O>mq@x)(2huLrxpQ2+>)fp`ci& z%%tcqpfBs~PUvikJi8c$Nla^au*~+svy}2jLtBxg*$Bj5mAH|8hvVRWA~7jHbf_8) zkyGONC=m-jZC@4fQErfCQAfE2o*puTv=@LPB{+ngnBbQJYlXyk;u8w{QLWBmHhRK= zYn4PG5Dh0(UDueU9@)^5{5KwGujQN|L4YA%y^^J$x$4=ksh%<+fs1H3b%a>u-;(ll z{O|ICHPx|}TZq`T2QLnzlYFr%E6?YA)j3^ZB^Wam52e4^8k=*4ILbHmSJhCyM`dOq zz4Pc0r<9Y+cLju;hVGD%nd0K2SPiVw#wU_BApT^w1)c(4yh&9{a$b3s*W6$I6~RCP9RZpPzgM2oUyOM<I_mOa>~1vSVD{OM>O3k)sg4~tV>J@T;v0~RnM~) zKq9`*IJdUA&?+ZIA$cm&G`SuM(P4;>0iWM9p``at=eR@xA==v0`Wi2fFCT7-&OrrT zok$kA%X+iD8+OcesA|-^lFGjk($tm7fkG8m2S+H%HWj$3qH1&Wf|>ndUw~r^t9z-}7!AK*$!a_Lr9jIbmvbK9v7py&?p+?@#A>4{fBR97aIV4p!$v){N^uFp;Vp&^FI3<(t&D!m zNrtUdC`;v(x}7M=IHN}sgClUK9Nu$Rzujt!|_w_!FM1|UEjHr1e-n|TgrY!?j zi(O=K8I01IDPK13AmHW0OGRI+tV#)FOeyj^Vtr3clV2L&dUW~6UDVyXbQF!LCfpj= zicV-xJ(vt7JQs!Y=|d$f#2HkcwQ)Yq5Ayg6G&rL3(|3g)x1U(bVwp*9Ghelw6o19! z!%r3%E!6VI$~Ch^hNI~n;1rGkFBmrt?*#a(7F&fUolf&R{Il%EHAhjJv=a z`j&S(9Zv?t?EE6l*ag*MU)UP0?I!{(HNw3nJgcK|H?Y0^`~9auSU;56iO~!r7lpV> zR%PKOaeV)nJcZ8SIpX=fuz7*m(OX6vS_9ceR=EsJh6u&-_XfNKg#s&2G*J4oT03wTRd}3D^4&1_fdq&HEwl<^a|Oi=<@RpXhf~*#Ei@ zq({{X+&^B3{U0xWYOw-!5qu4`us>ZmQ(gp!|Lt(m7+pIHf7mB}u_lcNB(17Z)G&u~xQ{-EaS~ zUXtTj3)*De+xD63J>B-0|2^M%`s+U928cGm01MiBx!PEA_xb>S&)=M#^$c_fv~TR| z6tOyfkk$=V-nS!u#N%)&KEr*kF3Yo_}h^=CAQ6+1zT|M4a^xWm>0Q7>-(i)Ea0hX zL-Gy?>& z94pp!il8m3JuNA*j9f=baF3If;|-q?=+IpQKG#I4u;=i{bAT$Vmv)X zT;{bgHNF859{qo>NOtl61@>8iu0|FaD zfgFuzu2XOTI%D)s^q9qdCnAABx(oI%78LQ(?M~pGg%O%qE?M6iXUhkvs!n4P_{k#c zu*lH^B4+_z65?^BK2L0BJnEn(2h1h@UYJDxGq)unzK*#=B8-ocy5^rv0U!CcsKDm> zB)029=q~e3UcS)xZ+iTX{wXSn#$@e+Sc`^#}2b-C@?^h4OBC*KVJ z9%JD2uLWtI+W~Czle9AY9b&`-tzG*aiGea0XUs5l63$t+giJyw;gph4X!f&v1eM=o z8?FK*%Hw}&PJZvA<=hri=$@yHWS(D?5K3zZv2NFrO}k#G!5AQ71rs%7^3q4~TxiBE zXFJ$^+wty@_R~DVrx+K}-b-|fJA=|Q=9mDY3_xPH{FbQCNjR2T_(SYmL#J3n{=+g( z6^o?uRc3M%}@zj=gfzJw*y4(&D1gvU05mFP}H@;SK{q|>gxote&D6K zQBEW%@r3Ld$G^>dj3!kG*|wZ6AMP@e1KEM%LKE&M(5vhdyYggcsw)EZlBTKCZIul? zg4CD2n6SM~Y+520hTHx+Rq^QWHD07ZS9fsj`FjR%*cFnb6vG9yf#8jHDAtD0g1)y+ z%baIPr~ZaGt>oLT>c)BOZ}F!Iw@-$tN9(=zm%7}~T{9GYv7U8>vKRJOD_5;;F`j!y zq?H&prfLv+7pq95Ae8NJ1YZ4Co3{c`MP{C+Zm&qGbv7`tH~XoDXJ<8A%BQhBC)-Rw zNUBUuLFt>$zNnFSthD$h&ABSG689nxETXxR;$@mq3YrH%AX7VYlMP+t9vyTCtj$6c zk*=)RwHk|J`0;kw!T7!RRg(I(TWoEi)Po`jl7Zn=Q_EvmPN`m+!9PgGdABE?jzecsS}jfmWp(QP zvvAEv105B?Jbus#(H_EMu2VBW59XZBUkXP|EKz;xmxTvzApWg&`d53oAK5`LCKZu* zCylK++1uP&3*8@lF}u8Xvk>_M?R2bfe|V$~G=+|hlrF~%7X?}BPu8zjU<2Uxu&eGp zJEfA57^Xg7@VVIk#dT(_ETBMH@pbD)JH*qE-VJKlD6d~yLEqFb{POjHHkn<*57BIk;Vk{p zqi5$bw{#D?pM@>1%XN9|JN`!YQgan#Z%_vvp93eA;l=goPo< zXe$4w>kZvw`wFA3^2`d*!*KK#p;S^)>2on><$5JCS~R9!Jl|S!)Z`q&wJo}TQEBo2 z0ii%%zu?3h9IdgzX_J4;D?U~HgHSVQ**V>vfto59uY#JXKK7qDB7*+{_0jDFUMd&~ zm)?VP!}W>lTD)24t=3b>4RBj>P)D7ZLQhB^eNit-Uv;9VlOuJMa-`0V#(!FxT|gAW zzlgdCH6#NhA`@7c>>S6*MJzptGZ?y>4q`dOZCFDeQHF;RPbRw$Vg;j`a&FH-tYJ6= zm38mM3C)rsc6TJ&T*QUj_D(($*+*&_UZUR^e3J-aj)H{>wf(elL_u6Z+a%fI^SDIO zA8?ph)ZgQxl7VNF!NS00k$>cl9phNrbO7zm2e4rRo06SPT}5o~E@I~eMGUn1ir}sOB8FOPBTdaq!@jUTTsw~4 z`#L9JB|}$6#^F9BmCU2}MUK2!C&v&L$#F5gvBbCpr^`{p8FFmE3V%6zE(n565=kCW zh*u|?=aPvDiU6arDRM71goY2|)pN+Nb&}d6smD+^foqe3Gmh8ahwH^T=Sa1+m~+|@ z3hyL+2Z*Z`5g)!CLDJP8`e+fK41Ky& z$ajVIkK?l;^6SB5veg%wDB|;>FV;MO14SHa^@qMJ=&$&QPS%9JmLO)>&uCgH;z{Bv z$!N{F{?NCJ_}(J_PMUs_ETrvMZVUTDKNPahR?4!H$OTejX@6N@@8lEBk*26;d=by> z_d@z}FQjvEHLj<7ZP4*5`Q}kA0uIk@-MKe1fyFKkRZK5pj-s@SLMKV3hFmys!LG6D^uNq`a_xO z5!9cK0z!~~nIioQRN-Y(zoWIbCiHy57y5g`A5GMTeF-J(PpFZ^g4(9U0;M?-IvlRO z4=?VM`A993#B0sJ0Z>Z^2oy06QP~Lq0Ok?^08mQ<1d|7gEt8jFEq~p9pjc5r{9F}E z!giy@q(NeWQsAKm(^?asn#=BVyL7*DcejQZ`62!bV}e8ze}F&AI9oJE@xhmSXU@!- zIWzZu`~LYWfORYjygxo}H{R+8(i&1=>l?b&*Vl9_^dr}ki5munAKJvYB9CND9305l zum)rea~Vp(@1}(K?oE(VX7?JaXk`P36*0yO4=ToZaYB9`mjy}=B`;LS^CU+C%hmHrR?kCaT*1{M<}lBVvt z#P@ynRxrU9u=E8puRme7QaQ!K39eUe@^J$FBkp|w#p{2W&*F9bzrF78+asTIB$+m1cq`#M+ z;of`B_kHKv^v;bd3cj*c6Q zNJb?mlRbfbrWsWSf+PFw8NtMcrF)sCjI1`s!|Ak2I+Lf%$m~p+84v-BO{PVovTCVC zBW*;osaU4BZY<0O7rAJXPUSS2Y5t{QRhoawGzkYaLRpr?OmoK_F|rHdZu00fjixir zng~jz8BFCM8#E)*m{3fCXwt~k?b#Isp;_eBX(r8Pa*f_mX)co^WA542G7hZ;X!B`- zPV>lDjMk!3B~uyBY=@5|Ajb3p>S%4dXb~;eX(3$+t8~J+8dVip&4N>D8I#kvF$;em zW2&eMjy3CsrTbk}Lw=pAsTQ`fIEk5cf@a;$aHbnZT+UdGddg5AA6 zaK>rDG5HH5d+5e8GARY-Z`6MX26Nn)jTsq@j$x%qqZ2T3x;LFM5`JN5jb6<(S(3?S zV)43QEREFpS_su{WPBE&FYgh(KC{!8={9`Z_O|+}jM}bRpT8;5D|R;~dXI(USz~Ff zMmOPvsF9AOVtM_zOF6^Mbc^8g)8BTJXZOxJZAyg)9&(W*G$E zL~qvVB;7V%m(mHMqcp10TcNxW3R}bJZiuVW+fWiLtEL-zEmq+u!D7hPa1V}qJH10V z$vejp!nR8P1p%Z&;8L@yMswR}#^Y8c0FgWC-8!A3_b_>@O2b$_`#zoSpwps|1;=rn z2YJ6vx6|EBYhEcB7Bznuoo31k=k{zzeqW^zGHt24gwtBs8^%J6Q*NH059{mkxGLi04`VOkLdITdK5DH{Rgh!c&J*V z$MBH|XHc2bF8Y$-rkcKt(vZ$}r1S1wQPom1TYr_#3+Ts@dCg>zwEHi!1iYfC7Qs=L z!?9nZuM3s^H`9O0{~TYXZy=lH*%elPN@DbM*&x&1Lc zE4ck16bQ+!U{><_Q)I72s0*T;!=0L9X%T->7yaBSald~+s?KBh4+(@{6`D)QPkjM1 z-=+LUr{9XwSspQy8FaDf?MAPQekZ!IQ}lmKGslY3kd4KoqW=CK#RmcK2c4c5t%*}K z?@1I`e@XEtAOlJNM1K|}{(}6GF|AD(y(k))=jm@S7J3Av#e#ZW^bfjUXy%_%>ri7) z+{mDJc*%b<@5|sMj=?0;Ewcd(IRrqeX3QbwX0px9_XRGt2@T)NV$hIu3g&1|MqTU_ zJ;lAO7WcEVbgEpI?_7qPs<8!OWM_km%h{!~&Xa^fq3EkG$2-PlgOT=vr=lwGG^Q&r z4@YGW5<+lHLCzQ0JGr8ar}KUM}L`4qhShL%KQ9lj(KwD)=9J8Iy%Q9ecIm zV$6RMVqxvLygOWIR`PlQfpKENsM3jsper1g0pENgV&tub(PECpst;w&m&nF5F}S$T zYCUQ--lX$J5pWCgP*KxJ`;uk`;KvMKIN57~0HoJeg8Lb^R@#f*ixmGmJwX$*Mt=52=_l#b+ z=4GV-D194m7qJlp*|BG8+y)zitdTtC;++;CW|wLC^GA&|+|IPHs(6N*VD#WU7%+G* zQ&kDYjJUQSu@zwyN225Ftos8i`bP)-f-z?<9pi^C-p>bg4)H-WgC))jnq6Jufa`xn z(b;eDcSPsI92Nuf2}B@VFe1`jJtMJJmLQS8Gig3yM6#kC;!b$INHa@H>SJtnvd)a@ z+{HKGOgMgL4Ar$LAB{PxQNm*)Y09sgkg%L!7VP%aJG!ojJbbjCU`vtDaKo+x@rPhOZEJGf_rtokufo?tSTk7 zWupxxa9b?py;h*Vj%juY<6!c{H|TsT zW1Kp4Nro?BjFOv0yyQ=Mlg>Buo6&+qW1_X}$XdZ57}NX-ZgYl7-^uS53dT4!DPz{RH@39oTLgZe zyg*@$P`1{lt2BN;Jh1o@t<^}U!(B#GtjiF^>;qPsl1532%efU3r>W93z|V*H!#aPE zF$FpH?B48Or?D7(K(?VbBfNiaMk$&H8eIHw{)A8him5Z(6GhGkg{lJ$qE>y1?-evZ zU8u9@?z`(6VqGoCj3E=mXMhxy9EeOI$vwcI6*!;6PF0H}1ABd5=ll7L=$_7tx14C9 zkPD`cHeW+Hjhgk4$meN(7`E8CYsa?c#@!l!VGN|ar{YH~$a8>vb*z8K!v3PQ_9bi0 zg8PcK_EkiJaUv4WrenwCjcRzS8BE2VRw^X&)JpD^%!ZZ~zM=C4e$w&^d4+@eQ8a+& z?{)Z_{4JeSei}xtjYofuYWy8oGjTMEG2X@Bv+_RXkMbD0{1iF~Glll!ht@iVj@cs= zcV&|qQB=`i`_2 z&t?qEvOkrViu^O3pA~(FmJBCNk(FhGz0JkHF@jxou6k69~=H3eys9KXk_G_ zLu1@b8?O@AdGUYVk?euf<%SsTWIKD2hje~fp`v+YcQ?!$RTTxPBpo-59+4fk0bH>w z4qdS+&O%>b05^}z%Nj)kWCX75QgnI{?y8hS3;AD%T*@R2Km39+83vBWIy7Y}I)V}* z&|sPwWQ%Z*_{Bz!=a^zwsES)xJRAViFk>X9bJ}$gaa(+^8LKPd6?& ztu63!g;J?2K4qbcTCBIlLY4!?Kfg?XEwh2LL|0}jRVZI5C?fhSqm8|jvQ}~6GNoEr zt_Fgn#ZP}p@T?P=B6eq2O?;kGtJDef<#1(KtTx{($HUoVq#OOZ)%pv2Y064rAz13P^z*KNivo^W*$WXT3=$2ocL}v^etJOUQ(+mn3tT$u_)+ccrBry61*1XBxS48 zg62WlhGLNKhsC|UrUb<=j3q9oM%}I`ZRi4&9ZYpTxET13`i_TV834)bKU}MQVVR+P z8B>22g8-;w+H#75FWxa?mHT38U)K6@MN{?^<(84kqwE7uBkIEp+YKdQR`*%Alh8_t zY1yUk8;4U>K8P?yJ*!}fs>zpK-^lc5l`f(0kx5w2PB;jI)uu)yaV$kKNTw38q~VJQ zKkPwelk(@2nQvP-mQ8dRDY=3a?;ur{Qp6W&_>Yw?qDe>aR!*cUr?zH+$^#EP<5FvhoedOLZNcExC>Krxo)7F~cvg*S3cKmn}J+*M|-sZ0o16{VW-dN2od!vbnq3?e186juP(bvy?8ZX0du) ztnMqU^kU^TVkP8$9RS_0KTB^IptlUt?V*5uknRZi&(OPa^xl5DtDinFNFNFX9Dc98 zpYC~xKFJhtdYuo^XPHj(d9OpfpJ9J`45R~Ujs{Ni$GxiiVId|>8>BA)SD>Ej8@hn? zFXregr^yR670P+Ss~*nLg&aK{aP$q`hyCx!{aUdRrv02zPKAhlP^ z(Q~J1x}YWA3%pJB=V=GZ1XP)XdV|+7NY977Wry7_^wS@6^w%8yUF=rdYCw}@wIZ?>GZzB@@oE7O=o>l* zI~m2yUKFQ9Cgdv*&>%2!>=1wNYrJ+a#o7Q*ZX2Xi;JlxwxO;Q#KEpF}JbT32)KX+? z56{o>6`?iS-84h8$%wx zrk}4pXT3Iv*9UpaJ`cAHa4XI_PZc7xAd&+(UMJ)yzlV1W@U97Vr^potsEE+?hs0;K zhj;h$z5zZ28N`CuQMAH`Lv4`JokcViq{GX~e(uPzaoTo%kh?;mnn9i$>gVo$K6-}D z)ob-;Mac~?&q5Z`Q}h7B5#my1xZJBKcDpX^KF0+wVmO&3HsCohCTfD z9KS2HM!j1&_GGWK!qU00org~q_H@Xk_R%D-(^jEM%lJbeGr;f7@m&GU!*>txJ)uCE z7q1`7@h5Y9-yq))KeDgUa{OS02A0T;62jE=dbozhmVav?|s<5ASh6h0i zs+D;__c{V)eQ*=3JR(+&6O{ad&>4Pgm=>H<5`#_!wX!q(f^L0zdFNl=iRh*ke>_5`1(@~IQVmp|0W&jU!k_gX+9#|KAQQTvBUud%Ic?IQ=b)|{vIL1lL6U=R>< za?1Qx`y(_jWUFZ(P!{EsEBlqD1BxFfuka|Va>`olmWP5i_r`XQvJT5vV?o8jvUbK- z!@iu-{5gN2Ho3grRt>N%%LbI~LSy52=eBbN6~i_jrB&MIcR6LJN7*HeTvnvXXWK_5u5O`MhBNk$8VPr#t63PY^kmIakQ%T4z8$H#s-U z=VoV%vm4K#bBBEHc3v-^9nNm~yv2D^t;h4E^PLj@l=D5}sn)AO`P`xIlF!|0r+miL zTf~zT1=u!&Ru6$aMWu}@EhJXynjxB;{|4D1`Ut7khy1%X5gB>2(P`*SnZ1ZTQ z%}29ri^*$SO0#WiXpXIs=Gu1BJX?P^&9^0Kf$fdtv)x8l*uG7bwijuk-A0S-DlN88 zp)2ifT4JxFDtiqrwXddS_O(=PucsROb>z1nqFQ@|>g*?Jx&33b!rn(K?GMl@`;Ta~ z{YARU{x4eNU|Q=~MC%-WTJKm+0Y?jMaO|L~9SPd#I7XWsy>yM^eRQqk0jfH8PNxRv zT55E@hnk#sQM2=hv{~IuThzDFR`n@rQJYt%27H$Y#+5QbsO9u#{)Cb{z761TU zEt3I79Fl%9e+hV8RTcj4Op^C9nQjSbJEgQCZ6QrFNf#Q*0ELpa5DfvFmN2vsUuRyD zS7zpgnKx~5K}9WYD2rPW7u<@93YbnJks@MSK?QLKQE@>*#RX9jk@%lGGtDGTBKf|_ zdFS49&wkH2_o0{XIRxM|)vj>MHP>ue_xk#sR_sbUe-*Ef)W>@3o9bh3a==Mgp5vy% zNjGkDJ#8m!D`RuB-^zqz{dVliOg5RRkMvrJjNMc}&=*cx17Sya#N(%}S-o}*Y18Y9 z=X1Q#;>R(KUrJJsi;Y&-3w`nbB=PG=~K>+71=G_MQC?cMcnG@%p%U2ZlVvo|{l zTVbJ_f9`APOIz`T-LfZb4Gh@nmiAP}vl5A=s|=JW%-&_~wptQas;}juoxALqXP`pi zB)yvToJ32^O~tb5w4L%=+IY;`nXnC*JhILmzY87QxR{s1lmE zlkqk>X@#01mUeb##Z%kTiDQRSw%4+4OFIwEe-ScD?REOHY3)&kze;d!hz;axx2} zS(vrZ)8d0vTp`?WJmK+Y3!=zk6;_M1H8j52z0$;51=Dl$R6(3B0#;z1!jefNI8KUo zT|^X;ymT_mNIJ?*U#%T`SrBJqz3iSte|4RVa0y~Ve(5}gSu}RT&WxMLdiKSZ*B`{j zymgxt7EGNI2F~Y&v|=$k!;D%NStFSK7$|@9GYoU@VHB(3G-9N4y?y2;g;iBS{ln5%F}|oQCDw zC)SKN;msoNExaTX_6)qW7)s50Lpp6~nFih-z&KGX^d*aPBx0+6(Jc?!998;|{8H4zOw31!8gAKrQ zH*~eNw-@W@m!yQb_%i*+al+}ndZW81m2j}O})+; z=#V*Ks)Rmf1`i%YP7V&Std0eY3|cs3Y}y;M2l99BtNH$uFU2Eye>=X$wdRbzd?pSN zN!u*gyIF1Or*1pN%M`@daldf+2E9?#>bz`kubsBzTWm|WzHc&W#l7~_K(#5*`de+Ka*aoJ(~m||lIH^Y^m%3N_6j}>pM7E|K!pN-qt+Mjm!gxry%|;?)e4&Le<<% zbBa@riNA4dkd#Zi)Zb$bJ>?Y*GnD*yJRe{m{713o=gXMf2)gfI3chV!$2wxk9#8%o zFIM6O{D-1Fx5M4T-oqEgnCMdKNk#t`F9&cHMrp_%Clz=1WK6|3g30mPvz!!5`iZ4h zwDnu*F8ivif1Qfys-pa=jOSH3{j<|a6@q9gLt*~dDY`@koZ^J2DkZD>`HC@B6${zv zYuB1;291~IYo*+jLw)tlRkQRErDjV7-#$fptLlIXs2cL*q>}ceTa=nw5PoJ*)vCEd zIgc0ZxNSp)#08e)ZI*t)iLX7VPE-p6YJuWtJ(H@Hf7~?Qq)Vw#OS}H%j36KDW-vP@g)!ES-2A+lk(5 zHq}N3s*TTWD$(WfMSr0+uvIkWFe8PsGn?FLf2Z{dA8h5E3~4jUXU~yG8$cK=Kt9+s z02!d1fwu3!*t}9!5v>!a=+y+Ia*O2mG^E+>LHB*`9-yL%h2&8r?x^Qq1oh z#KK4!k44G{u_zj;Xv(3#dl1Qp;cqo7S}VhvyIE`QN1!PjD$5}oD$il>EvOpCH4*aw z+6BKh8ZnPj*66b#a|HXMk-!kHJJed`e{T)e25YN6iNztaHn=((nW2@g3I#&^dUyBR zg6hENlc7Mw44GfWjSBgX4=U`(8u{9<*tVCEANBvJI3yJ4ss8v7ZljrbU*z!FVSK*( z!03b2uVN5i%;C;($QZ_;C^k$p4&XQ4wUrgO;gOJW6c06Ns%XT}>m4)=<8fA1@D zd>~?uXsIDH6bKhW5zbStETLo^=#UW{j_!~XN24QnkQxr*JJk;l;n5-dFo&N+%p4vM znGxdvI>lj?Az8SuDO$A1=&62^77gQfIXqMS$75y{_syQ_XSKzDJ+`GHMp>&_Tj_gk zw6*eM>dad6mY2JWDZt-C&Fs#Se?(AKvK@_-Nr0=L8^%BH#!ERSukz(o#eT*PKhidr zhijBc!&K*p3PdaJ#Z}R0sJtiYuTjCSvKlqBtGu-$r{>gF^mGlW6LM-k(%l8Zpc6g%OQZfBHj47u{W% zQ!5zECpr&cHh&9*(Mo>I4G*iNhL7OnP+8GU2fA9M$k4Jgnj49Eb$Ue+VP+X$~0zUu0V*WWx<;ID>smpmZ96_38`_&sJMBOsWC( zB%V-Lsds4jE_Ji(jc&i%L@N4Q(4IfoMR8Ilw$LcYSKc)UC(09G>1OAz+MZ7pLgNAjzs>gPGN|Od&uulVHIvORLe{7lS-U9M#HTF z6(q2w8!clSWu+V9^8CgNIC+#Ex{Q6gK**6&zB}`&bZkRWV{g8_7l@DXYK{Zlq}$H@ zE9l2-ISlM0-Az>NvuyD%A)q#(N^L|?^aw(oMx@x@T>>qCw28l2$o zLaqM_%=O1H&+lNqKY@^cK+Ey#vBUpAP)i30lY;XFllzKjf7e6n;l{gF@YHqDRwydo z2%?|}3WAq$ce;&c4B_ zEHh6P9%0yQe{60wm^H1R`F2-p7Hmg)8{AS7sf5U=Bx1Ek#`0OLx7Hi$Eia^=dp`mp zP&rS#CZGeQNnj~8kslcuYVvQ5%rY|mQDSqc_2PHlFD_Qbpup6%>`7nCB=S$Mt|`dN z7-qk(@xwG`zlq~Mqf)={-(jIGmF^lkA!}vCMD6(3Zsj~LZp+m0u1ZwCC$O;m*Wf?A zav@M!Ub%4KV4{LDCLN4mbQD9VI;dc*sHO!5_xY7j<)+L(Gr$#7TvZE(v*2(r&g(39 z^C)ouldG4PFPK_;My>vgnJ1u+miiW@Pf$w-2x_|O^J)PA0OtXd0Yw~>>x?jecpTMr zKG*x0)oT5aWZ7P9@L002w5yf;z?PADNwNW1>j#n_tnFY%yCZ4v?#{9^YgxPsjp+m0 zrX;k9odzf=6>Vr5w`OJHfT2x+(2_KLr=_GVNgoMmQrfgNEo}dDXI9#kB}iL;{`Stf z_uO;OJ?B4tU|_W>K@V3mfqf!8;xbOT+Cn@snk`QHg4Vo-u%|` z{*gjDjR|W^i){d@XGe{!uIG*HC}xlAc?)M@erw03j;*nje!S`400}{V!6CDdPwF=s zXmbFXEYVwq8 zD>oZiThC{;bms^dJJV)=@)$1Mxnth#5bnRm$Qt%_f4yhAjs3&b|6HHXi1P1suQ&B|Dm@+4MAE;bs-AT!W#0?vJeHRhQC&XC`h&Zbs5~L z$z5yLuU{`{bj}O94&4@)&NR$UKFp=0Ylmz`&9=4=*u2&q`xvHw?AuY@?n`TyC8(jb ztwNTZ+!mrMXf<0w6%?vGR-q<1L_c9zwj~XAC`44I746A^1>ss3mS6d@Q>uCdP zu~E?CS!)Ucn;K?+MEB(LnmkjXEkWvHPuCjOb|VkX%=|=%u68cejSFfipue#-K0A)K z@x`y9Yk5DAxu{xkg>Dd}7}gHHU5I+ArIvcAPtff*N$;pBFy)Qm0$V~|*J7JdI zS<_aNX4ck>tg2-vz~<;==vIfi<3tXGo>Fa79Wk;gRX?GBCGGTtx?!4cq9Z^%;GYpQ zpV45_t6MKc$>BNfaw%7cZlarm)JFY+*8PaEQfNR>bL)q~RL0n@AjN67Ag^WIrAs9B zhiEU|!iE||sLyLC*FF}^V5*t_tCjZQNQ40Uw!iICi-hO^9b{E*1z*}24$vV+1oUm2 z!x+7$X+uqaEw>Ab4cS^AsbcL0g+3Cb+ZbJK)i%j$8O|3rXPr4F@aNne$WvD5}$V53O_PGU1(B?T%^5ISdz=v+`iEZ4xB|xJnC6dL`lZCut zPjv1=PD2{pZj9<24hBLD=9Xy5CgJZ5bDZh=VQv|JFwHSa2k8!i#>*?U>(Ay2Hbm%J zMj?}vL$&e_-tG)ij!=vi9PU-fF6RUARBb;FK;jEA?`u8W%aA-l6G0lMyAV}{TuQT{ zyMm?ueinNV-OC!?R~9F4vu`YKj%&l5EANM#WZJa!5dAn;m2vtgKUuz3g-Ln~Mmoi{hNB+^N!FR4fo*N`X8nY-=MqRyNA%Cp$Aa{; z^z+;Rpxdy=LiBOEg@gPPm|`qtaq(5HeV6Wb6@idnpkHKNJ}D?RzYFKtd5U+QM)9%D zvaU;8=T!BV=rhdw7}uIR3+Sgp^aLl{Hu`0MHXu4L8#eu{lc#?LDIehK8Me%H!PdF1 zhv-*XLNiT@1^xq!dm|~EH`N@OD?-!}4Nys~Y00)^6X>tzX>$1SBG^ytJ+!y zv5!PEZrEcTE!jRZJ7VNBsy(LJ_|esMm79mgG(^f!A+t`+xyCdi5JYdWJraHpBrRx`jD1 z%^?JJS~fF{(;Z4Ruz!nwn_+nt8Doxhg^D5i0-Xt>H#~>jQOMq92}*zUsY*q*MeuhLhT{WVmiOSIkrH76AM189th-i<;T zqOWo!zfNC6#+kPt=a}D@*Z9?cq&dw9XU4Cid9}0=nGsl)peui*oCPKSnEoV4e?))E zC!-JaXO5wJz+L~sNjcv@o-8||w=gooiC|B`uBaq`C1^#Zo2pm;I!JG_U&1qX9 z_cuX$gZ>uXr7WG(tAaXP<8zy?e3|OHhWorl-(uH(8(x{~K!yGRa2rQ|*@eOXiL2T_ z(s%ghqr3}6D=4AJDIy)BFVcBN==UqD=$?u|`WL(e`pg2tl$#W}Qw`9+az;l4c{%z6 z^zVWMg7QCMgn1uz3cbtympK}u|KJzfjO_4|ED;T}3hunEdqu$&jWD@b zCTQ)Do=0q`dEGALvq59OA1J!4n`v>C{CUF+y zP;HgCJSbL*E2_7}6@gddA`~&MiCO2lhtxZ3|I8XBHHqe+SR>XVC*Z}^t64^}r-0Ic z6zvqHnI^hynfZhvbi|cn9or0V&w2nhSxBRA+i&Ulo>52)i3nhVoOyKei9$$1EUGivEz; zEVk4@r!G_#oZ}un&Eak3ep6g6x>*L^?~9}|TFT`JiEEvu>&i8b?{G6}@vM8?;Pgs^ zuIu~Y`H<*E7bto}A2)w}q`S$8RF8yx>DPkBky4(Tc#c3C;zA;=>moJx{I~gn~p$A1$ zj3B{I_ju!)r5ZE0?g)r6s6z;R3W#IKt$F!6-DieGhTD*4fyk??%x|*23I`Dfju8bs(Oi}%LTACP` zqQ=Oxv^@GOh1;K{m1m^yYiJc+?rajTVv8SRZ8TD(H3y5d?lc9@QRl!UT^}vdro_N2 z(2LZJ z`Q}6-9;rV(MMt3QDQb<%^VdYr(`~HaQP9JGiTKO3IQoM3395;DHcpaPyi$2Y>XIWC zN+KdaM85zNEf9C%_ikEPf~h@h={BMgEakyxvrE;IN1@H-wT0vZrBD}W6lw~W;16c+ zav21(D-L_hl_lz7y4j&`z}H1uU4m~GU?vWa+zkaHaJU~E_hNPo!j8jRAA{J(Fgpo< zzPA93cd(}fz8cbL#Puq#GjzV%{t9`|)Q_E`?C$fFOLTjqQ)JaGp)UoxePJ)V?C!)C z|6^1i3;R5c{v!R@B-~A(X!I|5oc;c0EbJ}P$s+v}_CJLEQ}nQBi?7iad*Mmyh&B2) z)luobbM#1}8=D`6!E3|bCF_gyse=%IkEu@|Jm~`>zTVDq9#8Bp(vzp4QZ!Mdr+~Jn z;|hBvairVpi41w8L%#MQe{87!*TY`NMb9MQpx?Y8wYUHaG}2`-IRU}Va%{uz=4pq0 zoPxghX_-QID3nvEP@)wi^BqVM3O!I_^TOhe6Q}v$u8R~XLAt+Uv7n$95IQq|CS3=+ zixBt_`*aa`D>g_scUGS${kRC4`{2h%aQtiduHi?J8@Br;Oo+BbB#>hmo@M;5^<29u z3M;Q-$VZ~9HUjbI=(*G6^E`8M0c`p$a6a`6b_#j-h2(jU>J^$2D=tE04R^6F9G-SF z$&=^l`9xwDEc!x`eurc96^_w=llb_3f$(}gv6~MAN@7L&!*ld!GRXe?6fI`^|K-8S z($^;GaC_`Ly}_JsCKyCh^v$quivF%hf8Xt`^Ui|Sr)hB+THl>4eJ7T1@$@$SPnPZ< zh~T8RFSHlwduRCP09SEfv2a7l~zbxJQJo|K$lLMZg#*lA%S)tcC ziow*x;F+F%L!og%i0EBfQNpdfQUKV#MLoa4QZN=J~m*d9s9tUC}biW=v5WPzdxLSTakIbtPJo;v8B z+Ky8f;nZ_tX;CaME3|SqdmBYY)G(SFM7Y~4x_zSCFZos{x)nx$R(F7*1(1G|Q6*Xu zfEh5v{}b)Nm1rx9_6E^$v?#7RE4CKJHS+iRmqgDg+8Oq}D0+%wd*a$Udi4oTW?kp$ zodj#O>L{ZXrnsp=^h52i;wU#Ic3v0=1Gba&eRnK|eTkyj)9tTo1)z5o#o!iiO;=4# zS8dqeE|Kj+f=r!%6So${;nTEtS?#i#M&E-+x@xp8d}{buDvo4o9{mi3men?TAAIyQ zEsrhZNxiG)tk5vEthOjd!+~~BBVy&dETOBmt7fwF1nb)%3|1=|4ut)&v*L~hk%kS+ zp@X^?h_byS@QHcw4660!f$}xsoCa}c`F;(;!e>mH2=^|3IPQu}i4zwpCBIAoPS+>H zKK_D2Z$~cB8bpIBCPiG1p9N6zbg!g&WcpsZpS}m0$8Uo^NuQH6k4%4_ijwA$>6hqL zN%P3`SMbX;k4*m%Phh5bWcod^K+-&d79QbeT8>OF5=$k`BhxFz8cFlWbeGsBX&#v# z6#FI3Bh$BkiV;ck$n>4!9!c}a^wZ)S@}4p`2!ocCpgLMHp@=0;85mc@8jfltfM(gG zVTD6|oXW9Y!dK;jy8$BNa!F>4X1T10^;HsAP_47d#RzTn%yzGr*Slw}i>md85;e?q zvePb996PNpR#wvjcSZI)io4xOXzqPHXgeyWA=kY>4%%#tv)3SLJ(t}Fs$>zX=a;io zKD|bs;C4UtNPo8=SKSKpKSCaqF)ue!><;q$4^T@72uXpH=MoVB0Mj6o0Yw~>Po6b@ zp};~Zlu|$tP+S$;!m`{n4K*f)#Dt_?Vhu*VO?QXw!rs^m#u)h_{0cRSi68s{{v!2* z@eD0Ou$7(cX7-))yyr~L%=h14zX4do62sBq;q&rawa$$_;hE~XYV4>Bs^PnV?eN(4 zJZyvq-`?r_i2pVoJU5i96r7(G)T65*M=?g#~a3_bgQi7jFV zw$0Fc-}dbI0Yi6TyST-WDipUe$Y3Z91=$SJ80be2ad#d@UOd9@fNuB0NJ>iq&?1o3AkFmm&WYISWKC%mY1&Jal%>P%mcu=C(*W{Khe7EuJ#&o0 z1&<#@oq42AJc^yGm^#M7f2*JiL@shU^#@Q(2M8yw0*RB}pjUs-O2a@9#%E3c8LQYQ zQ1;YH)1a*ost6)@5)_5rx0`9Q?Pe2p(|8d3Aijks!GjOrLx~g7gR?Ln-*3N}Wk0{( zKLB6?dkkJSoBQaA&xKr}iTRYv1s`&mXNA(DRJjSVJVxRcH42AxnF<%k6y?gTGsmY3 zp&br+kp!720#$$Sh~vrl;#AsR)kAqDhoNw8|tzE3}T@A|8##qbP{6 z;?Esm4E%?DZ6#hSjSLQRn}mrKvBvPxilRUp-ib23bPlt*M%#u4gZ-tbM5u*H!rS>0 zW!Z)ngVwn+s=Q!u(7*W!s64E)a~FCS!UjmW=6k)iF#>7`BzF+C@%smz!MkI4LWd zm(nX-e_!|fsu!CqX{N`MF{hlWYEH_K7{%hm_~k3(Wb0=3{7b%RlEABIsY`U^R@tyP zcMYpd(hcr<6pQ4UvGK7?s>nBDKZL*-)ST_RI=^k0oFQ(z<#gHAiY8BQx|-u~H+|Q& z=_L&ANt;>CBBiUKgQ0s(+tAXcW|h;6g*C1Ve+8WkXUbgUwmiYBO;3gk@oZpi*l7tf zHL`Q`g<+=WHD`(;(yCXWGISc=PFn5pk^2!u(4``blMH=L-)Y-4DKgdODd=Vh@v0-X z2$A7*{BV#6dT>U?Y4ly4c{~*F1IL#T!a8=Hn`7NJV#$4-FFlcefe$s{l4r%bNEoLvxBMFVnwc z#6?y9fXl~{LI05-7@W?+=gjZHg>5R#(a;vYg41G>K6g-WcqJtLGV3f{hPm|@eq?l`Z zzu1B!vN@~L7E^OFbkTpF!iOfKGmveTPDO8rwn9?m^(f_`y7s1OQ%4|}qiht8CaVnu z*T%r372-v&2BaYwWp9VG2Y>R{GpNJe)QX7&b979pmUL-0+z!8^v_Pv0R}9g-6;tmN zN4q5u20y$PaE>^ch z;P-}nlh4s6N2hM>|yYssH!>Zj;G&PyE~>Du-o6#Z)EB; zDtRzt+-z|E1=&zVKNVmKQNW!%Q>gUy3QY7 zJnf>qOU^>~y@zct94{q=UY_OD3d_S}tBjm`uj2jdVgA<*ANubksr0Yif;@--YT~SSaO>`~2N;~~yc&wyzYt94z#l3zWdf*xwb2v zA0BFm4i?rWQ55o1KY0weXm&yJ>D7ki=;`&x2M>wQbU#bcCM3a6+>MYoohS`RlnA1| z2w`80-R^mVrpdzy-t*>jj0d11%~KZYslsU#Z?KUO_wN_gdx0wg`X*}yIDhhnQQ3Pq zInSI@3+L&TA8#HpWf-4x^Its5o_sX+&(1-&G3advRfI7c+s}!U%h9X@nL>t!rd0xc z=XF}GP-dQ@P3dJT$j+ds(z{u7QBY5GaRSsrS$fqRWhG|v(M8&{Jg02f$^ft6qLBU2 z{%!8)+s4q+iZXde3lC3**b4{*r!yu$?PlF8Iu=~V&xz}9vKbGqXzjCuGzdkSYk8asfJ0j4(e1Ckj06slMs(&|KCRCiCw~Q!rlUD%>s&^SX z{$!XLniozCS#~q{J##OoBTvuf{6`9FV?zw<({^TkMNKRi#aoWf-ERXNoYqQVmS^QX22+BLQlSsuq=*|=|S z#XjC^NE`^7ot04i8t-l!`ig6yX)j+`6+e^QvPHu-5Htfw9Dd?@;=a-V3Vj^>MT!kgbzaQwl$~7lmJ|a&A^k*2c_j zQ{#{+J1Ks$%!!3Np&BNxhC}GuK)V5-EV+hWS72nO@_N@ur?QF@>vuPoI~Ep3+=&q1 ztrnX&M2D_WjoVIH?K6Gc(wW&B9rGfZTbB2xHQ+ekgs#Rs4~4AL^BDa$kG2};+j{QG zoqGIwcO2*r3+-fvL$mXJF>&5=%nDllPnD(I-o}v2F@u|CN28Q&v3_W+$PC9RvLLgI zPpi`n*Vq+bj-*EmAT*+H_t&;_*{lP3|6fy zdZe&B{V?PD0+bAlfzqQOUvzYm4q0*V(9&TCW?RTap7{8V$`u;~UHi2Saxq zKx7k=r;-|^Ks;{oFJjPSds5b+;%?Mt-F%Lsls#avVpqkAlP4Nz_$@}@G0Tv^Z4r2~`^S~@B6KZW5QXHycQ<)LVW^#oKZyTz!u=Iz%- znKIsjyK5_Xnaq~UdM7s=GOvB(Or(1?YUO28|Iw1atTLVXy_smh5C`F75$0#36~c)x zyqUtN&vHQ0dTHZV9VAKu|+Yn-!FIM zsk>mTeukp5&*%%fZFy>9ha0zYVy^zPr>~`5t-oz`CSfeGBOWpWJR{p~CPa%*?6iw; zAudKg;#4l_Z``e&O@gJ zZyX6s&1?H_X#s%&inBAN+8Vokiftii=WuhvbC??3GAsK|FS$uwW>W_OwlHtCB#H%Xhw0FkI|^(oSet-(A7 zzp(VKSlYy+d$LBqT3C0CeE3w|l#)@tpY3M<^}}lZp++#(!2V700V(aHkkxf=*=?zy zxc!9jm&W@yVI}OWWg-u3m zioLs+hTFpMyukPQp7t~sXz3fww76a6It|U}Q<6ua(A7PD0xf!zXHnMPJgN>2t#;s2 z^rkALD)e<_qdhpe*E1!y8*)uvxdW_VPM1yj*rJ+tAR1ck-5Q_c+p5L{@nT&I(+x&eB5F4LqeO$(&g*y*MqW7DVt4~d~7R4+`j z^8x*;QVN!N-lxEBl?&yeZNWkkU|($sBm1fj=Jekpb9_V*J**7H@8Dhl zjb$Y-5FpO`B0z|OZDxf1$%iErRh~qAUHCtc3Xl}xB*MRwK;ZTO1Nq~_gs_|!t;K@2M7%@?h0CW?MkQxb;DM5sM>f~U@xmzHR5D641MS$SId>s$h zaemIbU^d1`RHvZ#x0%CP1nr5E<~QfgiZgC+!S1b93wt3j)cIsL~kyfwP*Bu>Us^<0An>F8v3x5fzW^r$8Wa67abd z5zKJpCxXX6`hY-UB;c|Q5o~N`0(4x#MELh0xz}_AQ!9252u=d`+#`Ws*zx-l2qZzWmT@K#(r=T29k*crK0FIKM5v-o8b+)ls6ZfXdJss2 dL`goE2uYNj1UTAx82CVZAVvbDQ1ZK;_#Zl>@t6Pr diff --git a/samples/simple-streamable-server/gradle/wrapper/gradle-wrapper.properties b/samples/simple-streamable-server/gradle/wrapper/gradle-wrapper.properties index 37f78a6..c61a118 100644 --- a/samples/simple-streamable-server/gradle/wrapper/gradle-wrapper.properties +++ b/samples/simple-streamable-server/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/samples/simple-streamable-server/gradlew b/samples/simple-streamable-server/gradlew index adff685..739907d 100755 --- a/samples/simple-streamable-server/gradlew +++ b/samples/simple-streamable-server/gradlew @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. diff --git a/samples/simple-streamable-server/gradlew.bat b/samples/simple-streamable-server/gradlew.bat index c4bdd3a..e509b2d 100644 --- a/samples/simple-streamable-server/gradlew.bat +++ b/samples/simple-streamable-server/gradlew.bat @@ -1,93 +1,93 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/samples/weather-stdio-server/gradle/libs.versions.toml b/samples/weather-stdio-server/gradle/libs.versions.toml index dab7f7d..a580416 100644 --- a/samples/weather-stdio-server/gradle/libs.versions.toml +++ b/samples/weather-stdio-server/gradle/libs.versions.toml @@ -2,9 +2,9 @@ coroutines = "1.10.2" kotlin = "2.2.21" ktor = "3.2.3" -logging = "7.0.13" +logging = "8.0.01" mcp-kotlin = "0.9.0" -shadow = "9.2.2" +shadow = "9.4.0" slf4j = "2.0.17" [libraries] diff --git a/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.jar b/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.jar index 61285a659d17295f1de7c53e24fdf13ad755c379..d997cfc60f4cff0e7451d19d49a82fa986695d07 100644 GIT binary patch delta 39855 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

SdO`o?U^bCPz6Ehh!k z$iWoy5;(rkuX8fgr;aa6Ctk;PqW79jwVr`$<8zxzba{NypJ@$l z5=}YGNTKY^CVPMFv|izZt(=n~ZASUrdGcrj2!jR42b+d`u4%~U9RMHcYj6;slDq%v#!)Pb zb~FxQVGheju_D^oGmIvUuFT<>>Q?^C;kaR(FoZ=poVf?pDinENSf?1hjyip!#r zz%VYqx3z!D-x{n9)>eHUhlb4B;Hqe3mR7nd6bSL_Bi)w<)$XyULxG4HGVjDS3i*#u zD(u41^0iB`Z7(A~>VLC1BoyeW{_HSrp_zGKW7E%=rA73;mL@Z!!JT+#Mq5aaad(Y7Vc|`7A-P* zs-LDsBltrOf2w}|fLXteeaC6R(?huUu)j*dUr7e_*<-* z-Clo^2&zi9qmeQRaP>$O? zd!qgt73?ajQM0?sTPt#EUTsBB*RVP$rxr48a%#ygWW*7j;)aM3;!=I}!#(ubqalNi z7*$J2H>{S?ollZr9~wdxHR{NSS#}SMXrzDAA2Pb=?#i56!C*esxf^r&TO^ED@?(B@ zM78D=jen7t85S7chr>c;MK_iA)TrYpWkyruikw>8tuIiV;O(8^+eg*OQMnDnYTbSE zosVseYSU-`RHIHU1eg0*g=_d;cn9vn&78ai-o|lS;1EYtf#1b`4Ije88vcRLx#Xt*_H{}a0437VjmMIokn22I!?nA)kY1IYEV6mr__b&3JtGRS7~^)x>3WM z)QE<6t4B3_R6VAi1=JJj=Nf-jJulFAmG650Y}KM+K!trb`97y{fr8)S`;x{53Vy3^ zkH!TGKH?kIxIn@0_1&*=fr3Ba+oykVfr3Bi`<2E83jVb3IgJYx`~}}j8W$+|%f44M zE>Q6Q`YSXpkhs6vzd&#eiNmK(W7)kNb^pUT29_DaaM8!Sicc`!mw;8JCHTX#-&K#%VR)Go<*wT%b@r|<54RLvX;}sk> z#tvP^K3yQ>NGac)`BXZvp{Qdw-`rkcEBdGc@OC>Sew-$4Jn=sdR9_IOCsP^@v#&<5=K#u+X1C$Ums% z`1RP~|36Sm2MA9re$SKMfM|bLYdzg~w+f!RF5;=Ecq52{A}9!6rn}Q^Gl=U#%m_R_Je)V~+@=g}C<)yiH)y$aH%Q}5 zX_>1u@!~Wj)(vTrmUy!*trxT@xUrqsx;rhYE!EvD@?x2Js_@usZpnXeYnxfq_?d5Y zv}VD!rMJc{C6P*qj7lO_yJRe%#d>3PeYN3*)OGKNAOtEGX~zU~s5A*Iq$ctsBSTI8 zt&v$q#y?JMF14Qj&IiTC%IFsuzm{F;Ynep;S@W8Lyo^Ei`x-w=WA+<6=`kwx3;$gf zT2kqbp;NL}Modhc{JLmdO9u#)3d59>tb$U1d|TCd|4#I{lB_&z$4Nv2xv^tnOO~C4#tsTE#|hwA zd0^*(NJ_YtuI)=CU7>pw$Giq>*gDwO(XzEkS73C^Y-L@ufgGAbVC#Ug(XM-UV{{ws z9xYuvwr+zBy#IIZl`T6mbX|V=>D=#}?|kPw-}nC>$FIEi#pj6VL*h<(z&Lfl{(TZX%}Om`1>i(4!EM@rc&Caf_nz6qqBA2ss2UNrKfm_4o+Eu4k< zt(}*3ZjER3VhsZi=$nmMJ7qspFp|?VfAzDuL zVG7gYAo*xTm;w~!uT^0RQ5}C>1b1q3*ZPecHwqf9c|q5q+mh0mhS|l3xs-J6kj<#s z*8V=5*SljM!<2o0JF44#S|?*}rM%vD^WYXm8VwUcibrtQ>PN4?Z1 z=$7lGchn4+ipFq>Eun5`wKk|3Q@7N-X{%{7Z)-+g)$$Wyb96Fvt5e;1q5wkAsJ5w& z82OB^?1o}PwpK){Siec34~OVxMpye> zo8+||=L?&&P7N5}!Y65hc6~5b_;{_zSDitPT4NXPn-;VJHN_a2sN}>xw_pj{QUfI) z>_h;3==$FH<}KX;8bv9QES8=w6%Bi$Yd3Nl(%=qbROfIo5MnU5L`yyme{ZUBrt62= zGGLm2W0Vcitptr%R%_RvFO+PE(6yXGCMSov$~$m znwB1>pX91CP9K4sjJyy|LKfQ|ru*opSjbO*SFTlMlI8 z>&(x>wzhe_e!|&v0i0d?42e^8NEi+rPb*}4S`Zbo&LX%>V{~+VuNXzC;HAiYih&rMHDw%by z`PO_2{Z&n#oHn73X~%VSSl9Eat>qB=NHpVyJ=WQp?=$lwMlq+_W15X0UENTS zqzsjE8`MJ4#728UMYvAzSxz>IyV<0F(_Ke4Q@Phr4GYm-H_x7f)S+fjX)1I27YZM87#%2AW1V%pV{&u4vXl>1!I-B2r=CoMY(RGtiaGJF*h3Hw%dWxR6xjqVt%;~ar=1V!f zDBTX_&eQYE|H2%3RV)hq9zqSTo!w?p-YW^gh0w;_6s{tn%xES)o}g1Xw0wM|#K%-p($`@BKl zV%L5fUa57ULjMT3jic;;!r=eRRqdbXJN)wz-i4wSl2GInkqy%q=^P{U`_)x+Z&e`u zD_#P9W(i@+O^V#92I${7qa%X69Q^_M4?zM!`Cl-?f{!|d-r@es91YX|a0LE0y^HEG zh-|`XDnQdv47PC_g)m;nfXcmM5vI`s9K|4C$HOG2L}6pW&A9LlzqsmdE0qc zFKcU`*O&>vP~dqHVD|%yzRm*rynv_!#&%St;(%C;X6Sw1qKa4wbTcdu6wwx4(l$?< zxnx+>i-wR`CK~4z+yz_rs)8$;U~;iS(E1_0h}ckzx?L*fk<_o>zkeSntANys{A*@( zm{Y8(Joenv6@dqTs?RnL3?{2g;w&bi+8S|jNURo@%-xn$1YV6xP{g<<=AGvrQt!O| zvulvlELuWhomh{YiWgUJ2~`2v*{Mgf{d2`A3&}y$h)cx=HW!|q4Zv!;lts&Sz|xDo zqmURDQ6L1%F(8Cz<8nG6;+14{flx(sL6oK2gJ?w1w(WC&t2drS3%1Sks)yJlHiyJU zaT%-v`Qv8s*nU(VvxFQe`om(2=ng`s9$X&hxJS=$c-y$u6qkzx%fQopiBv|*xEx_| zrL%NZrM&SSu16W3caLkFHfhlHdLNt~7TeJPieAyjeP4~Pu^LP}8BEv0a4KR;g>Z%p z-iU8;P_LbTIeExL@~x;pn-m1zhAZ0^OiyArUjgr{WuwvrHr$eQnpClmb=)X!nDh4q zblExw(-49e?*$HBXKH@kab|(B1L9yv>=%cy!LYb{E*47#bU0y=LQ==dO+Mm(%ZP9i z`i4;ih{ex&J%7QUvF1nh`W^a+R?6BHdf&Y5IR9pUag^PB%iO;!{a*zsVi@JQ(){7E zX_u_NFo}ASl5CCoT{bOV1iR2VIaU3WT1D=kdhHIl|Y1hCxN~V$`Iz@XY>673B zw7rj1vmLmAtq}D*L#ajdJhfoHC6!7>8xBv=5h#0#+G6tjb+L1FGb?x$^l&QqA}x)7 zJ?DLtf-%qLN%D%9s*lKAaKvIsLrNGf8;l4k!?X z!~NK}53^$sb{yXN7{oq|*-7xd0bsm;g+0^Y3zAMFu2*@Tq4U*_m&kjjVeBmB_nf0b zD&dVykyXEpz7$CKB3^dc9jR{r!_*Lu_&iPiGX2CP+)bZo@-KRX{r-A9;w{t3GJO>L z@5lZrdcf1|Yx2dPdyG2cO}@+OY5MN7^k6E1%@4ugbrJ8fjb-}OA&AG+rw^Tf^Z^lH z?_fEPr1o8%1yGw?u*ZWHcXxLS?nQ&U6)jfWic5h&(L&J_mtw`;rO-lfw*sZO6ewP_ zSYP12x%crhlgZ4^Z+Fi*^L?3on{)R6VYgOClQo5HdPC|5zEXHcl4#Pgf4=PUd_uL= z05!G4RczFp+a!clc&B+xg>wuFujYxXsxI|9hT_LbyLF!hb#cOxgi+o^B^n_Co7yy6 z(jP1a)bPV>o73*~IDFfy)v9JzAm;9US#Q!?BPqL~G*8NXF0jn%-TpM=X5|9S(xSkn z+-^VP#3Hif^QaB8E}w)INtb!51R(9NIAxlC9`VsD)1y{*H4Oci(1iHDS*K^~<5PUa zgWG<;H|gfjj8_A0mG%jKAI1!xatW~TGwOF>CQ7@Qn+l7s*(CLkP1cvrxEP$Z^4@Xv z@2F4|FrSt!_>y7*fz3z;^{EQC^?c$|@Wbmmy% zs7(sdcd}mJ@ZQOG8y{sOS87a8p*3`hiQHxT4)soFUP+1sj!O|RcldP{sIG`XCASkt zWm<;3?Hjh-O_a(gGE4R1oM$-uhj-aT4hv1)Ri|ExV1cJ_MX2%$fWnCpHLGs#RYg*E z;6#2Od81}%3{Hk+{8J<7p;#-_lNmO(1cS6|J_kA;HU zAzNPL#$HVj?8mx6`TM9Om>$uOGJhovF9Lk^NhBvp&0OFE7Y(_pwwa&>0Q1J>ceMG%3duGQp|?bP6GzT*7V@B+NZ@8A$6 z18q;%T}|j%IvSeLf~$k1&vNojaaT_Bn>oA-t1609skdIudc-X&*XldQ@fuk~?~#Ed zXX04m+;uGH{)C{Iiz%)6^t<<@vc+_uf&<9`9qk;?+B>>(%xj9d){hbfE@-7~#-hoG z*N?&W3(fg1pqfFSmBgIf*-#Bk>fzo*ljFQ`O<#~H}qrsL~6W4p~8Efb}BsKLsM3oLat7L1;nm+(AIJ_OHsu(PEb zmw7c?nX;x?T*?=#wG6J_tKhFKGxnQKvburS0~$ApS-J$8`*+#Ch-FJ2>pA((loN(qT60_48pE z`-PoIDMMkRoBmdHIB}QJvbE6fm4BlFGYmY${lPFwKOMRr46|L!^U%R;;7+wgR@i5d zOn~gN&d+BS6Lt0_ar&w(mgzdr`Uq>|3dm4%L4x)MYns%>$`u+L<^Eku09>V320r;1 z6aAh(5xf^#+4V!cD9-2m-qopd_@}eIS?7KB4i#Q?(_FfgVg+3Kvr}|FpbN3+_9Z2| z!$5I6v9e;?xelxar}w9#b2Rq!sY`FR2k7+2xot~fJD_q_y(KXf!9p}ImQYS56{$Y( za0_YeWBtD6ekh#8F?v>F;sO9;G>`ww3w;m(!wt!>esIU)X6usxH(cVEAX^R%^z_H>BN=8YFBaPC?%iQcR2e$Xfg| z@Lu~OR&Ua;%nWGYXcCo=Bj_dfa>0DA=g?7KlbX!@>H^yx+FXMPE&Q;6k_3UYVyhxI z=ZYbhy_Z`_Cndm2QNKeN*i!@(pCsi5dhxA#8ShlXAKuVSu;>tb43bpPf8TYJeKU=z~RPxWQ@Sp>{~%uSFSJFGHCQl zEQ-YmJrgu|0~65)=@^jKp>$V)q#G8MLlewza~2E~NRS?1o~?8z+LU6+PghYaUD@tv zsX&P+))C-)9~8|5Ys~=Gc^5Q~G(}6I7bUNP>L??UPaB>1eKiS@YhPn(jlB>BJ9m!c znuXo!Y>MlqUy4Exs`h8~=fcW~Whu3}20k>GoV3PPjZK4RMM($>yXd^ULe*)ZuLbf$ z@1t(U8AlSTCTIgF!~}4&SOzrX34s_>nTcw}Z<2ET(4c4Ec3jaU_=8`qZP~uJk+j&C z*o1TmGcE9-AEbG%(f7qA-Ubg_#i*GihhPkI4pWoR+EC3cj2LAOHl*bdd2E2zDBnpG z&uA3pO*edGVO5FDbdIFEwgYT%Mq2Cw#pdJ=x#N%Ivi;6-d^g))BsyE}e$ksiq9bly zydi(M*mxPxH;iF|P)3kk21=L!)IVo`|E9n?9w{S8+k)W)4fFNbKsy!3QLyNlUGS^P?J7uIvJS{#VAD#5H35gxAmN=f7ZX7Y-5eBk_-W6%C`q zy}8T$7(908u=gD-l!jJvjy%oPkT)Gd1av|zF*_m7MaFE>Lw)W%zu7rj)o*0wOz~Oz z@?1zW1hXXY@!T|hbm=HPtW%}K<8fg7G&OKvE{Tjxg|mIwOQ%FMK`BN9)sEG{O$O4m zk@p@Ubu(2LUDT7$cdX2A2o~z}Z&ovp?w^4}x%&bmRN#9)89MTMTx|VFJw3R)S>ZN= zYl-rTV8*86unCGHY-wT|v2>y$T!oX`G%n{X4ut@RJK~WGIW-uj= zQ>j(VA#DeyC=vGh?-%2cgl5zSDBw4H$^v^hi?g`IKHEi|ML?a6g?Gi`K-?O{RSoHD zOstehlo(6p0olcvE-BOK;d*&~Xrf?J_2lr>AD$9g&c3`^F}4VfOUf!~gNfM%N4xU= zaX%m!i8dYZ$$2_HK2r|y@ryAuZ@CC9C~S8euhb1Aq!UX8Uq}ndD(X7BLU2gc`>>JU zWeGU14Ex2c>LGz$2kj!! zFickTd7?ZpC?k4fFl@=>)$T(M#G(d)vKamRPn?rdM9z0wP!d_9EP4_QZU%)bL4^?Zgec^OeyZ&&*o9)fcz8LTS-yu%j4v%$ZC71%Xh87Kr{R%3Ra|*L)Nz?Dun$D3CC!i zR>D6tIj)O}Uw|N17Oo~4{(jSQvH6RX{HU_iaaJOevC+T+cR@wU#>;J>Q9f=Pnar-) zAuz2n8VxSn1$4*?0qTJNPX0wO)I4znGRW!O<3jN`>8y8l3zH^Wk4hg&1;<2o|#XZ2ukL>ycB@tCZxXmb8>%nIkpS*M;zxxy5 zjqd7V1(X!Z7-4S0sa#tkTVCl?yuTnC@ZCq^;vHc$TcwZaPs;_zmt*|-L*{a(`VDx~ za&H^m*$x#L*EwIhewT z&T_W{rVZxo4pG+JhgaN*7YY^TfXP+LUK}dzU$P`vW7sDi$1b4?Q{+1sbM{D$>?B$f z)e|Ed+$Plp2&#ENkUE&%t3f3&O_YxX$;9D@qLk>{<6RMTrdO)83&&BN_I8R35f|Xc zW&%K)@t=_8te4T9OD(v2qMl)?Vg_1H=g`a-^9{$~_tcPr> zAAkej_4%bx^nPnTFFemu!gQUq3Y*GD@&Mi(_os2Pc@~z>tB%FRFeqM=d&+5k`dY z&;y-6XxU~%n|pfj_m|3}V?7ea-ASW3`NP-;-101=_m$56G!p_s+%*~5#$Ae106pWp6B-@GARIlSOK?cJWMt%Vac zq}=#D2;#G?VgGi3>`B-Q9%MD{lh?Opf^M$`8ciq1C@%KxA}?Hx5qFx$+k@#&$#7pv zZpJTOAdOyxP}Zip(OpRO4D7{SSdQ0p<@*V&#~dBY#?pGeHoZygLkHU2E3C&*pYV68 z1vt~Jx87cLpCFy2=Lw^0z+^99&Q(|p_nQrTuAK88X4Bh5q365n>@~kaGy=U@x*d%zjSyQ()Bw9ra2AD*8TRFvnATpY>wlWhho?NqJWhTUeiHz)-o3 z9?fPPT?Otv^^chT+@;5rnMD+7&6h*Vj%3#L7@~yV4fIVleAQAc;_zbMgO=jO| zs3jsRC*=Mvi`G^*hlFoaCWQQ*>0yh#qy{nPQUZ9@I;~lQDjC15VhhhW^5Ud{G4Gu! zAx1VoXLu%t(1oZdNJR@D0dl%W@>93Lq}anm4WxmR&Yv;WmZIm5;oQO3KYg%6fEg(Z zXH;z$?ZpkPn>BiKzG3%caMj-V2kBRekyBZjgoL?WweA4P3|tJFU~-#0T%l*HP>z#E z8UR?*CZ-yM5%NozVNq53_g%DodfZ+Yz@@7)h(kI}Skp^H2bDV*<>&R_&;f=JiOHC_ z6i{}>q6A~K(z%218j@0S+y+QlSBEo@52k2-_A1mdW%%ebZ|;a9z&Q%7{{X{OPehD@ zHKP|(O@BCDEGIcHUoq1Oe75KkM5Cuf$9|OrE1?2}W zC1N{;6uM|i4qS_s%Ur)03D$D8U&o}lTagvo*E?ME5dZZhTxN7VVp!gi^FEP<`1*)$ zte473PaSU0rnx-mvYK!kjo}`kf@vZxU=}z_gHv?!IDK8zFV867>5Xj{qN(&?NH-G4rC>uj@ct zBQbrbyD8sBD@|`tfy8pjUu!f>U6U2w+ik-l+v z{mgt_TViVEb*7Ea(ac`{y|h2p_>CI|H&E^`c+Z(y@QlYV>N@(z*Z3PZ0&bo~-6aqI z2AN4Zj?`1Um!)S3?keti)z@zD)mkqqeN+^ELkEa@CZ1P)E$0x^U+(@9^!c67kXMOb z;xU!1mC?7}lshRC!J`dmMiN-9h<=U!S5W$b`^_;bH2d6N@hK|{vqAyfz>X~V)%-?KHR z4KBN@Ilp8@3y!T^!gFx5hw?W?52dLOQYatR}#@;3ZxZ`;r5fh}kig)nN#ouF6Ewf?AaE_U{Ddv~f zuK(`fH?t9JH)y(a0#>~ow={O(Bzj2a+x5WJ_h;U@TJX3rt!H3Tb6k$MsWyKd;+qt# zCTE0YQYW&M&*KZW;9bCN!QsR;^L@_L>%+eMIT`o#CQk4^^L7XIxCP_Mf}*mf3>7g4 zjcy-f4=0$CsMwT@Wda#6doKK)7@YSpB$U?=r`B^O@EJa-XwUXNC-)=QSg3J&|6SOV zOz6_Id-B62Z=vp&VhK`zrspBVRvW&5hD4M(-^N}MMNY<6jtK{Y`?KA!<+HSUrESH- zHpZ^-?qU+9#XlNPypHX8h8l|}p`R88{c8?aOTu~zisfm{Skxy3%~s!H6!9r=hOC|ShnFP4mPD34EOxBot;zk7m}{G%C&hD@~g zbI-V8B0DF?@)QdmSpD2zrZ{QYj`z%3%vvI@x*Dgh%WVVBF$Cw1U-~(Smw!qpZU{gWOQ(G{0WvGw)GRsrOl1{nXT-?l zd0l;@Rn(XCp)ZRI`{nZgCK!zQqk@Jg^vPELJjx5404-bdAiTw;h^yDCa*&ncS4b8W z;RkUL#S#O`SruC4hezTjn7jlZ0QEsu=YL<}_y9;Az62zp1P2Kh2$9ofXt{_Cn=K9BKSRq9DuX-v> zN}B13Zv@W+MGN@~D=a+B=RYaL|4)uVP%34R9+mtc8kL0bRbmj-N;=4!5=O)a5i3Y- zB@u$91OO5s@won!|4Adk`b0m;c_{Nhk-}3jo=^xN0E7xei~fJKlprA` z)Rg}zPXGYVpLobCKX@oU%!A@V03jZ>T2!#r;(kIUt3tYJ2q6O10*H@2-Ce4Q;G@+a zZ9z3C5U>*WV}So!Tmu07PXefE{|l4X@KXHWetfh~z)rpY1(_)xnzwz24W|w^9L^_@ zjRg!+r1-aK84P;54h2>)fE+?&ijDy5_6DITp{MxoU=RSn_9PmD^&>1*%ZB*4m(Hb@ z2>xf_<1jL7StuU1d^x}}Y{TA9hk+3D2oZAD*;$VUcWLcPH1AXg2weU~n44Bl!5M w7PgL>&;I`;?us74{(3&7f4)He))T^OmOUET88lJokpk8iEZ0%Y};;}6WeLhG)ceS&-?w@`}e-CJ+s!V zSu^$txpxNH=!yz#X{~D|!Rqmyk#lN~X&X|PN-VC(*S{l4u_MT_%(!w!9}$Uk0n6R( zL%pgVFwqG>LG8`I2L|;5AqL>R@p~M3nvbIPkUGU1UNfg*ZauQPW^~muS7cbUWCOm& zP{?3pP$V2-95b*Q&5a|Od0agz$|zeVRaw(<6XfTiP7(r>_dccUn9+Z$OIAQHIvk>h z-e>>h2IYFA7XUz^RO%fk^G>D!fyWjAhD)3jY%kZDE?g1Q*iyD{vH)#Q_EPC(p+ZDo z$G6l>EuZ$n!Tme2S}Diy_50eVaJN?#7YR``jmssIO%c}!MG@dX0H^-IbZ zDWa4@c9N7UL2RIv#+LfBDwa`1TUZ--NgTb$yk{XDga}iYdLMp2q=-Jw!N%7|lq^9Y zo1&b|ArSu_@%g>sA`&2Q_>u}xtYqFt#F9;%Ym}2ZQt90lzAoLHBd92%Vhg~=HI%8;6Z;I*t=r~oAQ^(Ce z$tFjU=&C6`fx|6I?f?taoq*2Q@%?a>ww_4Q%-Uj_?EQkM+vm_GPu8pe6lveXxY+Y^ zlaZ3m!a!*IQDy5e_qY)(ho2=cabN$zhJa)pbf5?==6-{4l`i3tma zC?Z6zT;g(>&7Vg8BP}62RGt^}eAThhTWwBoT}c6txFgn+IJoQ(LR26eSprJFghhedF~s`k_<91{q-vf($kZLm;kO5CyrANi2N;X+hK}}o z@as!OVxzbXf$(^yWI|Y`sN^Ir zB?!|V2r1K0hJ}7-BqX-ERHDXKwUS8|6(t8X#!w!>zSVv0=Gwk~WmGdVfqKvTDu$UV zi3$8JI>i@APR3=|qu_0AlW$|~?fvVefV3Z?mSX(w{O-=`K2+^+tuL{y$y(Qo(nU9T z&sCVDGnngR0LM~i2vZ2_X#2Rx?i$fS)bXtd*ra`GO!pu?%pSPQw&M-hGB)~l=NjHv z?K{-KE1UoTv+&+7Ys-$OiPPx_SUMqKtF!#T&Cp4YDQDIn8)s*O?Zx0qqt5TlH)Vr5 z#v&SZQo&-HXLf|{n=dmemu2lh49_-ckP&y{-VBc*0O3DC(Hp5n`BHJka!}Q3z=x^< zMQ{tD+ULXQ*<(e#%Ls+dbSD5Hn|6E<=X~>)+?njf0$ctF-ho@JX$blCV`z4vyXJ~8 z+^}bP&$vO)zS}t#gIc#u*%ivLFWKM0>!-nIvwYTjbA_%i^IV|0S6i~n*6oZz!EeE} zX4zs-HBP1tuo-@B6V3`YUNifMI~HU>UZ`(NITas%uRt*V2`qLk7*;~QCjrYuM|l~S z1M&Q`t12hy5_>J}0M3dxR$gv<=$g;jJl?Dt^=s%L+F@IuGen!c|4_6oL~`bMNPKsP z3{U~F7nSYof-?>~AW8A4y2-+_G`)}f z1N+(~`BOnyHQJJp3+vDJqY~JCzFii6h-!+s#~}p#b!7&& zX&rh-Hq%+v(=yR%uD;Pl9zN;=|I9wt1j2yp*=&rKOkiZ1qaSKXdzZP6@c+|V3SWUFj zbA~b-Z08Y|k=kf$IU;60v1;)3x`0jkyh||%oLyWCw;JbSB zl72>_X$Ofz_Y(ZmoReF7cxu265&|)RIB4e%)WBA$;N~1X(r5M)1WW<%>I!|3QHIs{ z`;Ojg!Q`D?4B4n+P4H1m4B3I4$IIc3M5A-eoE*>T_m22ewptA*QPmBEVq?caEAk`x zM2R%lVcLr<7;pIMv@tf^3!c)zF$h@vWVbC@zVU^_F#m6lqEgCuuoUA;du$#rojSk) zLMa&ByKlI2he)74iD`^JOWOv70y9sSLdLWT@fUif2r`(Aq*ONqiSaS~W3eF}ENosS zo6DwNGeHCIFn@rf(jdHa=!80evnguFroVx69AEjI^tVjQDcD&(QLGIZBOq&mCZk2S zCJ_L2?UYy9$B%Ef@Ov^~(|fLto7wD-Ptb}KV|WUn7jBx&EZVFxEyYry*E%`Was_*G zB{C!XY~)GgaHo%H;l32z%&q#*4EjUAXdkvd6HGJRROTQuXppi;ve;#6;xH#AHh)wF z%Qqe@^$ytZd5ULL8TkV&knbV0AZf?PK;pWy6Ijgd6N9@(Z-8#@re!mB*2e~e;NNWX z<(>%4djki3OMp(M|L#9VegX;JZ;d=8l zCvX*q zDD9}o*=x1@x#$CdQ;{`YhT`ABub?5TqiTH1Y088SNJa_&E0*V1NMXQgA0%kuFWR$J zWH(EvJQL+X)`C2=Tq^I8_&FiABP94Q zg?%_$KWR%~Tg|-+V#zK>Vf)WN|dg1*vZR zruO6PApbS-j0Ley-b$GrdREN}OQG*@p$s49m0a_tqzxv>$8%;KAe zFp)wNc!nz{AG0}th**_<$wcuFInbJ*0*;33gL*Qq`HD}Dj0hJ36reY-d}tZpN0}a^ znx#cdZDGLKH3sCiTKbVD{vw6ERQDQJLnM4fq9hjf=h142pDhXN&?>cv25>F4~^fGjosOWWFOpM zRyS`dHg!A$EoHM-cj%=Km6z6p?b~JQ2Q>iQ>!4228bX40>HwO^il&G$=O{7>iOwEj zBrVTX=(OPM$pxoLH0G^eK5WtqT&`v=$*>w(_DNrNv=8-1Ypi8FHr_hF+ZBzFJ?3Oj zVkojNPXG~+vYwZ%ciz)XIUOGbALw%jjy(Z6P9`aoU=K|*p8m{LCzAFVK4A&V05RLYLVdC_ z_&I`_Hhma2z5bMj1HU=?D7ODFJgbqDarCj+G6Q{+%%-d1n-qNYQX|Ws@l#96PcXuv z(#_Bq+&-d6-f8-@B3$;jxKfBe_*o9I*)0k0ck~Sh`wpIdQGLW*!U1SOKR`9hnnrf$ zGFitw{g>>&D683bj@vHuQ}>KU2_9>685SlFb?z;`9CO=)o=-7?m_0&3EB)4#^;ngKmEq&zYmHag;poGfezAyQxDr<@NE} z`Q(oN~pfBiw+6ZiBaIRuEhXf!WycIyB{pQ(^9C0i}&J3ac0rR(u^OFqE$B5RZ~b=p{LF3&V(FlYCKdBV+}3 zvxgn25^#6N({f%h0x>1biUXOmhZWlI@(^SF@%q+thF{LxUp5A`TCjm%dEW1oG);y- zIXZJ#Pv(5Uv`uRUuQ05dHF(PAzt+Wmul^1!COkn~g+ zeN6(URji*!bk}krVu$QbR(R%`!5xQNpZE^HO7Cw1tr3H;1A%926u~={s}VTO2vT!i z5oyX*D@;LI*3jnsI{EpcslSl_wP6-*2{KbS2nYdG2nbaLs1#T!+?026bs$t%hO(&u z`rZQKW|Nl&sO$S8-Qo!JVC3LLd-ty;t<9~nYuVT&(gT;fP#OVD(O0NmJq~)-v-aty;i#BD*gp9785RSy#rV(L^^ES_fXwtaNF)u!=8C# z)wkaa^&Lu|a^Z^LPxATAWRnSZ_?{zzd-3mO>F}&7i6@2G=q+;1wt*^|c=Tf+VIV7X!8c|X05xc89 z)|(5dg|H-$1V+EcGg}&#(h>=&fpgEb`VBj!09{n$DP4VmNleQ;QJp#O+~UNdSf|7* zmr0?e3Xk3wgvDtYgJi$rsU~zr zmqIjT#^pbVtsIjbDgGO;GX8J8>ZURTiWzK{8I~D_X@-EH6`&+TfD@iRx;WnLmfkUF zl&A-suM)@^l9;3e5ghqWVx81GO4dGok9oI-Co{LAqCsEq#)XDY4-Z#oWLgKFg~0?D zE!8eH^jfR}f6`|IYtHPI7tz8L%#dynIBr~3mVLtdPSc1~@^(+!Xw@(Js`vwdCe4t@ z!+4~Gd3codGlp+28ICy+E)fotE!g#To#L|7+z7&GOC`EtHlQ&O$3LrQMFrUukko1} zcX7~ag#?-`=2|X40x>UjIhEl?#}6A>WTieB`icK)xPs%i5q14HMgQK$MYP9P*UD@7 zw!+DE@s}QO@qir6vC~1pIjjo2z121Tdq;d_0EYZkd#wLSG@Rq><@bCS<)bFKfG16S z!@e?>0ZA5}4v#fbZ2Ogut(Con@4b?&QuSPiU~B>1WcL_O$jM_}vElb%=M2>@C*A!w z)*@tTLf3+KXLT%3%u%q&Y$)z1l&ADUsIk4#;w<)#!o7}9luq62#Wz)8^IP?5Ltz3q zpYMr!UcUJVe*LAgQT{C1W#hay_1$*k;XR8^QwaGG;SGQDhK$a44DA5qR>H{`ZdCMV zC5!GrR`QN0w4JkCD=GvlTyL_0weG0ReWN|b;P=(r+kt(2(I0s~^~{6Bi-$mRBY8LY zb2cu3i9-N26if*Kx%>`@>v*G<=5#-j#xzZa5NkmZ!ro)LP|YLQt)#{XcS1kG2H4J? z?51UjK8G*=N~_uZRVS~ApY2#)S!}|~xDjTjS0MXqA#9T=d~muhTSZvdz(jx4T1LyI z6f?Oc$@aElelfpi{MrirWO1C7&;Z8tVChzP*nzY1auPO^YLy?C&~tySz|tc zVK#lPBx)_|n>#LQ_0Ih(s2=oDY?L>^GBhnFtYRDn8t>T61(T8Xc7rV^(V?an8xp)w zd(m01$h~k$*zT(MP~Asab2NE$&t)nw!$s1vNldkZ!lCmksw^%XB{xb))>*vCeoYB-`MJ{F;9c7_&Rr;o7KQOPy^=MNbH>&9i< zU%QTnT2RE<~unJT#U+vWgSJVjEk2Ly+F&^<5opYJQ6I@uuPC&6qmTUWmE4 z*9~JRrYk9<=kzBx!E~J#-U0smu!1y~Uq$~6@W5m#;*


Xdyd*p!l1Y@nCA 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 diff --git a/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties b/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties index 37f78a6..c61a118 100644 --- a/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties +++ b/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/samples/weather-stdio-server/gradlew b/samples/weather-stdio-server/gradlew index adff685..739907d 100755 --- a/samples/weather-stdio-server/gradlew +++ b/samples/weather-stdio-server/gradlew @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. diff --git a/samples/weather-stdio-server/gradlew.bat b/samples/weather-stdio-server/gradlew.bat index c4bdd3a..e509b2d 100644 --- a/samples/weather-stdio-server/gradlew.bat +++ b/samples/weather-stdio-server/gradlew.bat @@ -1,93 +1,93 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega From 2b7e754bf38170280b4ed25aa7001d59640e22ff Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Fri, 27 Mar 2026 02:25:21 +0100 Subject: [PATCH 199/449] chore: remove deprecated macosX64 target (#625) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove `macosX64()` target from multiplatform configuration ## Motivation and Context `macosX64` is [deprecated in Kotlin 2.2](https://kotlinlang.org/docs/whatsnew2220.html#deprecation-of-x86-64-apple-targets) and scheduled for removal in [2.3](https://kotlinlang.org/docs/native-target-support.html#deprecated-targets) ## How Has This Been Tested? — ## Breaking Changes for users of macosX64, no longer get a native `macosX64` artifact ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] 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 --- buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts b/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts index f8d89c0..a331139 100644 --- a/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts +++ b/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts @@ -52,7 +52,6 @@ kotlin { } } } - macosX64() macosArm64() linuxX64() linuxArm64() From b946e4eaf995324035524e434ac44897c86a9494 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 05:59:26 +0000 Subject: [PATCH 200/449] chore(deps): bump mcp-kotlin from 0.8.1 to 0.9.0 in /samples/kotlin-mcp-server (#638) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `mcp-kotlin` from 0.8.1 to 0.9.0. Updates `io.modelcontextprotocol:kotlin-sdk-client` from 0.8.1 to 0.9.0
Release notes

Sourced from io.modelcontextprotocol:kotlin-sdk-client's releases.

0.9.0

What's Changed

Breaking Changes

  • Server call handlers now receive a RequestContext — handler lambdas for tools, prompts, and resources have a new context parameter providing access to request metadata (#515) by @​rnett
  • Ktor extension functions moved from Routing to Route — update call sites: routing { mcpSse() } → route("/mcp") { mcpSse() } or use directly in any Route block (#531) by @​kpavlov
  • StdioServerTransport rethrows CancellationException — cancellation is no longer silently swallowed; ReadBuffer parsing was also fixed (#571) by @​kpavlov

New Features

  • kotlin-sdk-testing module — new artifact providing an in-process, channel-based TestClientTransport / TestServerTransport for unit testing MCP servers and clients without network I/O (#505) by @​kpavlov
  • StreamableHttpServerTransport.Configuration — configurable builder for the Streamable HTTP server transport (timeouts, session handling, etc.) (#560) by @​kpavlov
  • Ktor extensions for Streamable HTTP — mcpStreamableHttp() Ktor plugin and routing DSL for mounting a Streamable HTTP MCP server (#504 by @​devcrocod, #568 by @​kpavlov)
  • $defs support in ToolSchema — JSON Schema $defs / definitions can now be expressed in tool input schemas (#526) by @​i1bro

Bug Fixes

  • Fixed SSE transport endpoint resolution for non-root mount paths (#565) by @​Amaneusz
  • Fixed StdioClientTransport hanging indefinitely by adding proper read timeouts (#528) by @​kpavlov

Improvements

  • Introduced AbstractClientTransport base class, simplifying client transport lifecycle management (#516) by @​kpavlov
  • Deprecation cycle started — annotated APIs scheduled for removal in a future release (#498) by @​devcrocod
  • Detekt static analysis integrated into CI (#493, #507) by @​kpavlov
  • Code coverage reporting added via Codecov (#579, #580) by @​kpavlov
  • Flaky StdioClientTransport tests replaced with real stdin pipe–based tests (#508) by @​kpavlov

Dependency Updates

  • Kotest 6.1.2, MockK 1.14.9, AtomicFU 0.31.0, Gradle 9.3.1, Kover 0.9.7

New Contributors

Full Changelog: https://github.com/modelcontextprotocol/kotlin-sdk/compare/0.8.4...0.9.0

0.8.4

Changes

... (truncated)

Commits
  • 5b58814 feat: bump version to 0.9.0 (#576)
  • 1454229 test: add serialization tests and guidelines for handling null and omitted ...
  • 3e7ea39 chore: relax Detekt rules for tests (#582)
  • a388180 fix!(server): #575 Rethrow CancellationException in StdioServerTransport, fix...
  • 9eabb54 refactor: refactor Streamable HTTP ktor extensions (#562) (#568)
  • 66d31d3 ci: update Codecov configuration for test and coverage upload (#580)
  • eb94f14 chore(deps): bump dev.mokksy:mokksy from 0.6.2 to 0.8.0 (#557)
  • c368812 ci: add Codecov coverage upload and XML report generation (#579)
  • 45fcc17 doc: update API reference URL in README.md (#578)
  • e124262 chore(deps): bump actions/download-artifact from 7 to 8
  • Additional commits viewable in compare view

Updates `io.modelcontextprotocol:kotlin-sdk-server` from 0.8.1 to 0.9.0
Release notes

Sourced from io.modelcontextprotocol:kotlin-sdk-server's releases.

0.9.0

What's Changed

Breaking Changes

  • Server call handlers now receive a RequestContext — handler lambdas for tools, prompts, and resources have a new context parameter providing access to request metadata (#515) by @​rnett
  • Ktor extension functions moved from Routing to Route — update call sites: routing { mcpSse() } → route("/mcp") { mcpSse() } or use directly in any Route block (#531) by @​kpavlov
  • StdioServerTransport rethrows CancellationException — cancellation is no longer silently swallowed; ReadBuffer parsing was also fixed (#571) by @​kpavlov

New Features

  • kotlin-sdk-testing module — new artifact providing an in-process, channel-based TestClientTransport / TestServerTransport for unit testing MCP servers and clients without network I/O (#505) by @​kpavlov
  • StreamableHttpServerTransport.Configuration — configurable builder for the Streamable HTTP server transport (timeouts, session handling, etc.) (#560) by @​kpavlov
  • Ktor extensions for Streamable HTTP — mcpStreamableHttp() Ktor plugin and routing DSL for mounting a Streamable HTTP MCP server (#504 by @​devcrocod, #568 by @​kpavlov)
  • $defs support in ToolSchema — JSON Schema $defs / definitions can now be expressed in tool input schemas (#526) by @​i1bro

Bug Fixes

  • Fixed SSE transport endpoint resolution for non-root mount paths (#565) by @​Amaneusz
  • Fixed StdioClientTransport hanging indefinitely by adding proper read timeouts (#528) by @​kpavlov

Improvements

  • Introduced AbstractClientTransport base class, simplifying client transport lifecycle management (#516) by @​kpavlov
  • Deprecation cycle started — annotated APIs scheduled for removal in a future release (#498) by @​devcrocod
  • Detekt static analysis integrated into CI (#493, #507) by @​kpavlov
  • Code coverage reporting added via Codecov (#579, #580) by @​kpavlov
  • Flaky StdioClientTransport tests replaced with real stdin pipe–based tests (#508) by @​kpavlov

Dependency Updates

  • Kotest 6.1.2, MockK 1.14.9, AtomicFU 0.31.0, Gradle 9.3.1, Kover 0.9.7

New Contributors

Full Changelog: https://github.com/modelcontextprotocol/kotlin-sdk/compare/0.8.4...0.9.0

0.8.4

Changes

... (truncated)

Commits
  • 5b58814 feat: bump version to 0.9.0 (#576)
  • 1454229 test: add serialization tests and guidelines for handling null and omitted ...
  • 3e7ea39 chore: relax Detekt rules for tests (#582)
  • a388180 fix!(server): #575 Rethrow CancellationException in StdioServerTransport, fix...
  • 9eabb54 refactor: refactor Streamable HTTP ktor extensions (#562) (#568)
  • 66d31d3 ci: update Codecov configuration for test and coverage upload (#580)
  • eb94f14 chore(deps): bump dev.mokksy:mokksy from 0.6.2 to 0.8.0 (#557)
  • c368812 ci: add Codecov coverage upload and XML report generation (#579)
  • 45fcc17 doc: update API reference URL in README.md (#578)
  • e124262 chore(deps): bump actions/download-artifact from 7 to 8
  • Additional commits viewable in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-server/gradle/libs.versions.toml b/samples/kotlin-mcp-server/gradle/libs.versions.toml index c96f6c1..72b8a16 100644 --- a/samples/kotlin-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-server/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] kotlin = "2.2.21" ktor = "3.2.3" -mcp-kotlin = "0.8.1" +mcp-kotlin = "0.9.0" slf4j = "2.0.17" shadow = "9.4.0" From 0d97b8296de16968215f1c722ef5a6ab87325004 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 07:38:17 +0000 Subject: [PATCH 201/449] chore(deps): bump the kotest group with 3 updates (#629) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the kotest group with 3 updates: [io.kotest:kotest-assertions-core](https://github.com/kotest/kotest), [io.kotest:kotest-assertions-json](https://github.com/kotest/kotest) and [io.kotest:kotest-assertions-ktor](https://github.com/kotest/kotest). Updates `io.kotest:kotest-assertions-core` from 6.1.3 to 6.1.9
Release notes

Sourced from io.kotest:kotest-assertions-core's releases.

v6.1.9

Full Changelog: https://github.com/kotest/kotest/compare/v6.1.8...v6.1.9

The Gradle 9.4 --tests filter change was missed from the 6.1.8 release. This 6.1.9 release includes it.

If you are using Gradle 9.4+ then you need this build to run a single test from the IDE.

v6.1.8

What's Changed

New Contributors

Full Changelog: https://github.com/kotest/kotest/compare/v6.1.7...v6.1.8

v6.1.7

... (truncated)

Commits
  • b368573 Revert the Gradle 9.4 test filter handling removal
  • 666ae51 Simplify discovery log message formatting in Discovery.kt.
  • f641e00 Add primitive array support for containAll matcher (#4354) (#5776)
  • 2d21515 Add primitive array support for inorder (shouldContainInOrder) matcher (#4354...
  • 8f7a507 Increase forNone performance threshold to 10x for CI variance
  • 92ee8ff Normalize line breaks in test names for Gradle test filters (#5764)
  • 359f65e Add tests and fix for afterSpec/beforeSpec in TestFactory (#4133) (#5786)
  • 4ee4a97 Add finalizeSpec DSL function to TestConfiguration and Spec (#5750)
  • e0a45fa Add primitive array support for beIn (shouldBeIn) matcher (#4354) (#5780)
  • 282a929 Add LazyJvmAssertion to avoid stacktraces in inspectors (#5759)
  • Additional commits viewable in compare view

Updates `io.kotest:kotest-assertions-json` from 6.1.3 to 6.1.9
Release notes

Sourced from io.kotest:kotest-assertions-json's releases.

v6.1.9

Full Changelog: https://github.com/kotest/kotest/compare/v6.1.8...v6.1.9

The Gradle 9.4 --tests filter change was missed from the 6.1.8 release. This 6.1.9 release includes it.

If you are using Gradle 9.4+ then you need this build to run a single test from the IDE.

v6.1.8

What's Changed

New Contributors

Full Changelog: https://github.com/kotest/kotest/compare/v6.1.7...v6.1.8

v6.1.7

... (truncated)

Commits
  • b368573 Revert the Gradle 9.4 test filter handling removal
  • 666ae51 Simplify discovery log message formatting in Discovery.kt.
  • f641e00 Add primitive array support for containAll matcher (#4354) (#5776)
  • 2d21515 Add primitive array support for inorder (shouldContainInOrder) matcher (#4354...
  • 8f7a507 Increase forNone performance threshold to 10x for CI variance
  • 92ee8ff Normalize line breaks in test names for Gradle test filters (#5764)
  • 359f65e Add tests and fix for afterSpec/beforeSpec in TestFactory (#4133) (#5786)
  • 4ee4a97 Add finalizeSpec DSL function to TestConfiguration and Spec (#5750)
  • e0a45fa Add primitive array support for beIn (shouldBeIn) matcher (#4354) (#5780)
  • 282a929 Add LazyJvmAssertion to avoid stacktraces in inspectors (#5759)
  • Additional commits viewable in compare view

Updates `io.kotest:kotest-assertions-ktor` from 6.1.3 to 6.1.9
Release notes

Sourced from io.kotest:kotest-assertions-ktor's releases.

v6.1.9

Full Changelog: https://github.com/kotest/kotest/compare/v6.1.8...v6.1.9

The Gradle 9.4 --tests filter change was missed from the 6.1.8 release. This 6.1.9 release includes it.

If you are using Gradle 9.4+ then you need this build to run a single test from the IDE.

v6.1.8

What's Changed

New Contributors

Full Changelog: https://github.com/kotest/kotest/compare/v6.1.7...v6.1.8

v6.1.7

... (truncated)

Commits
  • b368573 Revert the Gradle 9.4 test filter handling removal
  • 666ae51 Simplify discovery log message formatting in Discovery.kt.
  • f641e00 Add primitive array support for containAll matcher (#4354) (#5776)
  • 2d21515 Add primitive array support for inorder (shouldContainInOrder) matcher (#4354...
  • 8f7a507 Increase forNone performance threshold to 10x for CI variance
  • 92ee8ff Normalize line breaks in test names for Gradle test filters (#5764)
  • 359f65e Add tests and fix for afterSpec/beforeSpec in TestFactory (#4133) (#5786)
  • 4ee4a97 Add finalizeSpec DSL function to TestConfiguration and Spec (#5750)
  • e0a45fa Add primitive array support for beIn (shouldBeIn) matcher (#4354) (#5780)
  • 282a929 Add LazyJvmAssertion to avoid stacktraces in inspectors (#5759)
  • Additional commits viewable in compare view

Updates `io.kotest:kotest-assertions-json` from 6.1.3 to 6.1.9
Release notes

Sourced from io.kotest:kotest-assertions-json's releases.

v6.1.9

Full Changelog: https://github.com/kotest/kotest/compare/v6.1.8...v6.1.9

The Gradle 9.4 --tests filter change was missed from the 6.1.8 release. This 6.1.9 release includes it.

If you are using Gradle 9.4+ then you need this build to run a single test from the IDE.

v6.1.8

What's Changed

New Contributors

Full Changelog: https://github.com/kotest/kotest/compare/v6.1.7...v6.1.8

v6.1.7

... (truncated)

Commits
  • b368573 Revert the Gradle 9.4 test filter handling removal
  • 666ae51 Simplify discovery log message formatting in Discovery.kt.
  • f641e00 Add primitive array support for containAll matcher (#4354) (#5776)
  • 2d21515 Add primitive array support for inorder (shouldContainInOrder) matcher (#4354...
  • 8f7a507 Increase forNone performance threshold to 10x for CI variance
  • 92ee8ff Normalize line breaks in test names for Gradle test filters (#5764)
  • 359f65e Add tests and fix for afterSpec/beforeSpec in TestFactory (#4133) (#5786)
  • 4ee4a97 Add finalizeSpec DSL function to TestConfiguration and Spec (#5750)
  • e0a45fa Add primitive array support for beIn (shouldBeIn) matcher (#4354) (#5780)
  • 282a929 Add LazyJvmAssertion to avoid stacktraces in inspectors (#5759)
  • Additional commits viewable in compare view

Updates `io.kotest:kotest-assertions-ktor` from 6.1.3 to 6.1.9
Release notes

Sourced from io.kotest:kotest-assertions-ktor's releases.

v6.1.9

Full Changelog: https://github.com/kotest/kotest/compare/v6.1.8...v6.1.9

The Gradle 9.4 --tests filter change was missed from the 6.1.8 release. This 6.1.9 release includes it.

If you are using Gradle 9.4+ then you need this build to run a single test from the IDE.

v6.1.8

What's Changed

New Contributors

Full Changelog: https://github.com/kotest/kotest/compare/v6.1.7...v6.1.8

v6.1.7

... (truncated)

Commits
  • b368573 Revert the Gradle 9.4 test filter handling removal
  • 666ae51 Simplify discovery log message formatting in Discovery.kt.
  • f641e00 Add primitive array support for containAll matcher (#4354) (#5776)
  • 2d21515 Add primitive array support for inorder (shouldContainInOrder) matcher (#4354...
  • 8f7a507 Increase forNone performance threshold to 10x for CI variance
  • 92ee8ff Normalize line breaks in test names for Gradle test filters (#5764)
  • 359f65e Add tests and fix for afterSpec/beforeSpec in TestFactory (#4133) (#5786)
  • 4ee4a97 Add finalizeSpec DSL function to TestConfiguration and Spec (#5750)
  • e0a45fa Add primitive array support for beIn (shouldBeIn) matcher (#4354) (#5780)
  • 282a929 Add LazyJvmAssertion to avoid stacktraces in inspectors (#5759)
  • Additional commits viewable in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Pavel Gorgulov --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a3cb759..bf56c87 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,7 +16,7 @@ openapi-generator = "7.21.0" awaitility = "4.3.0" collections-immutable = "0.4.0" coroutines = "1.10.2" -kotest = "6.1.3" +kotest = "6.1.9" kotlinx-io = "0.8.2" ktor = "3.2.3" logging = "8.0.01" From e7687c79393a6e9174bda3bfb610b3ed1a5f5d53 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 11:58:25 +0000 Subject: [PATCH 202/449] chore(deps): bump the testing group across 1 directory with 2 updates (#630) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the testing group with 2 updates in the / directory: [dev.mokksy:mokksy](https://github.com/mokksy/mokksy) and [org.junit.jupiter:junit-jupiter-params](https://github.com/junit-team/junit-framework). Updates `dev.mokksy:mokksy` from 0.8.0 to 0.8.1
Release notes

Sourced from dev.mokksy:mokksy's releases.

0.8.1

What's Changed

Bug Fixes

  • Header chaining fixed — multiple headers { } blocks now accumulate correctly instead of the last one silently overwriting earlier ones by @​kpavlov in mokksy/mokksy#29
  • Improved exception handling in streaming responses — errors during stream building are now logged with context and rethrown cleanly.

Improvements

  • Unified header API — headers and headerList direct properties replaced by a single HeadersConfigurer DSL for response header configuration by @​kpavlov in mokksy/mokksy#29
  • Priority API cleaned up — priority is now non-nullable with a sensible default; redundant priority() method removed by @​kpavlov in mokksy/mokksy#29
  • Faster stub lookup — StubRegistry now uses binary search for stub matching by @​kpavlov in mokksy/mokksy#29

Dependencies

Full Changelog: https://github.com/mokksy/mokksy/compare/v0.8.0...v0.8.1

Commits
  • 1be47c4 release: finalize version 0.8.1
  • 3e8ebea docs: create Module.md and update Dokka includes
  • 5a48038 tests: simplify StringOrListSerializerTest assertions
  • e7bc227 docs: add explanation and example for stub priority in README
  • 861ec28 fix: ensure headersLambda chaining in ResponseDefinitionBuilders
  • f00d03c tests: improve StringOrListSerializerTest with shouldEqualJson assertions
  • af59bb9 tests: add @BeforeEach to reset mocks in MokksyServerJavaTest
  • f988b68 refactor: use binarySearch in StubRegistry for optimized stub lookup
  • a5f45e1 tests: enhance header validation and error handling in response builder tests
  • ff8de25 Update mokksy/src/jvmTest/kotlin/dev/mokksy/mokksy/serializers/StringOrListSe...
  • Additional commits viewable in compare view

Updates `org.junit.jupiter:junit-jupiter-params` from 5.14.2 to 5.14.3
Release notes

Sourced from org.junit.jupiter:junit-jupiter-params's releases.

JUnit 5.14.3 = Platform 1.14.3 + Jupiter 5.14.3 + Vintage 5.14.3

See Release Notes.

Full Changelog: https://github.com/junit-team/junit-framework/compare/r5.14.2...r5.14.3

Commits
  • 1c14c1b Release 5.14.3
  • 698391f Finalize 5.14.3 release notes
  • 5655c22 Fix link to milestone
  • b4d1f56 Reliably support JRE.OTHER with @⁠EnabledOnJre and @⁠DisabledOnJre
  • 5b6dfef Fix references
  • 718ee15 Fail build if xref fragment is invalid
  • a809887 Install poppler-utils for pdfinfo
  • b568f5a Update API baseline
  • 1ebb6bc Add missing checkout step
  • 4ca615b Back to snapshots for further development
  • See full diff in compare view

Most Recent Ignore Conditions Applied to This Pull Request | Dependency Name | Ignore Conditions | | --- | --- | | org.junit.jupiter:junit-jupiter-params | [>= 6.a, < 7] |
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bf56c87..f9160e5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,10 +21,10 @@ kotlinx-io = "0.8.2" ktor = "3.2.3" logging = "8.0.01" mockk = "1.14.9" -mokksy = "0.8.0" +mokksy = "0.8.1" serialization = "1.9.0" slf4j = "2.0.17" -junit = "5.14.2" +junit = "5.14.3" [libraries] # Plugins From 9c0aa45e2aaf130a9eba2ff20f12db8bdc8f4725 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Fri, 27 Mar 2026 16:16:10 +0100 Subject: [PATCH 203/449] fix: reject duplicate initialize requests in ServerSession (#624) `ServerSession.handleInitialize()` now rejects duplicate `initialize` requests with `INVALID_REQUEST` (-32600) error. `clientCapabilities` and `clientVersion` changed from `var` to `AtomicRef`-backed `val`, with atomic `compareAndSet(null, ...)` serving as both the guard and the write ## How Has This Been Tested? second `initialize` returns error, original capabilities preserved ## Breaking Changes none ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed ## Additional context --- .../kotlin/sdk/client/ClientTest.kt | 9 +- .../sdk/server/ServerSessionInitializeTest.kt | 152 ++++++++++++++++++ .../kotlin/sdk/server/ServerSession.kt | 21 ++- 3 files changed, 172 insertions(+), 10 deletions(-) create mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSessionInitializeTest.kt diff --git a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt index b117dbf..9d51236 100644 --- a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt +++ b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt @@ -417,13 +417,14 @@ class ClientTest { clientInfo = Implementation(name = "test client without capability", version = "1.0"), options = ClientOptions( capabilities = ClientCapabilities(), - // enforceStrictCapabilities = true // TODO() ), ) - clientWithoutCapability.connect(clientTransport) - // Using the same transport pair might not be realistic - in a real scenario you'd create another pair. - // Adjust if necessary. + val (clientTransport2, serverTransport2) = InMemoryTransport.createLinkedPair() + listOf( + launch { clientWithoutCapability.connect(clientTransport2) }, + launch { server.createSession(serverTransport2) }, + ).joinAll() // This should fail val ex = assertFailsWith { diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSessionInitializeTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSessionInitializeTest.kt new file mode 100644 index 0000000..5031a9c --- /dev/null +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSessionInitializeTest.kt @@ -0,0 +1,152 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport +import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest +import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCError +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse +import io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION +import io.modelcontextprotocol.kotlin.sdk.types.RPCError +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.toJSON +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.withContext +import org.junit.jupiter.api.Test +import java.util.concurrent.CopyOnWriteArrayList +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue + +class ServerSessionInitializeTest { + + private fun createSession(): ServerSession = ServerSession( + serverInfo = Implementation(name = "test-server", version = "1.0"), + options = ServerOptions(capabilities = ServerCapabilities()), + instructions = null, + ) + + private fun createInitializeRequest(clientName: String = "test-client"): InitializeRequest = InitializeRequest( + InitializeRequestParams( + protocolVersion = LATEST_PROTOCOL_VERSION, + capabilities = ClientCapabilities(), + clientInfo = Implementation(name = clientName, version = "1.0"), + ), + ) + + @Test + fun `should handle first initialize request successfully`() = runTest { + val session = createSession() + val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() + + assertNull(session.clientCapabilities) + assertNull(session.clientVersion) + + val responseDone = CompletableDeferred() + clientTransport.onMessage { message -> + if (message is JSONRPCResponse) { + responseDone.complete(message) + } + } + + session.connect(serverTransport) + clientTransport.send(createInitializeRequest().toJSON()) + + val response = responseDone.await() + assertNotNull(response.result) + assertNotNull(session.clientCapabilities) + assertEquals("test-client", session.clientVersion?.name) + } + + @Test + fun `should reject duplicate initialize request`() = runTest { + val session = createSession() + val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() + + val responses = CopyOnWriteArrayList() + val secondResponseDone = CompletableDeferred() + + clientTransport.onMessage { message -> + when (message) { + is JSONRPCResponse, is JSONRPCError -> { + responses.add(message) + if (responses.size == 2) secondResponseDone.complete(Unit) + } + + else -> {} + } + } + + session.connect(serverTransport) + + // First initialize should succeed + clientTransport.send(createInitializeRequest(clientName = "first-client").toJSON()) + + // Second initialize should be rejected + clientTransport.send(createInitializeRequest(clientName = "second-client").toJSON()) + + secondResponseDone.await() + + assertEquals(2, responses.size) + assertTrue(responses[0] is JSONRPCResponse, "First response should be success") + assertTrue(responses[1] is JSONRPCError, "Second response should be error") + assertEquals(RPCError.ErrorCode.INVALID_REQUEST, (responses[1] as JSONRPCError).error.code) + + // Capabilities still reflect the first client, not overwritten + assertEquals("first-client", session.clientVersion?.name) + } + + @Test + fun `should reject concurrent initialize requests - only first succeeds`() = runTest { + val session = createSession() + val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() + + val n = 10 + val allResponsesDone = CompletableDeferred() + val successes = CopyOnWriteArrayList() + val errors = CopyOnWriteArrayList() + + clientTransport.onMessage { message -> + when (message) { + is JSONRPCResponse -> successes.add(message) + is JSONRPCError -> errors.add(message) + else -> {} + } + if (successes.size + errors.size == n) { + allResponsesDone.complete(Unit) + } + } + + session.connect(serverTransport) + + // Use Dispatchers.Default for true parallelism on JVM + withContext(Dispatchers.Default) { + val barrier = CompletableDeferred() + val jobs = (1..n).map { i -> + launch { + barrier.await() + clientTransport.send( + createInitializeRequest(clientName = "client-$i").toJSON(), + ) + } + } + barrier.complete(Unit) + jobs.joinAll() + } + + allResponsesDone.await() + + assertEquals(1, successes.size, "Exactly one initialize should succeed") + assertEquals(n - 1, errors.size, "All other initializes should be rejected") + errors.forEach { error -> + assertEquals(RPCError.ErrorCode.INVALID_REQUEST, error.error.code) + } + } +} diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt index 69d20d1..a8a34af 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt @@ -20,8 +20,10 @@ import io.modelcontextprotocol.kotlin.sdk.types.ListRootsRequest import io.modelcontextprotocol.kotlin.sdk.types.ListRootsResult import io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel import io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotification +import io.modelcontextprotocol.kotlin.sdk.types.McpException import io.modelcontextprotocol.kotlin.sdk.types.Method import io.modelcontextprotocol.kotlin.sdk.types.Method.Defined +import io.modelcontextprotocol.kotlin.sdk.types.RPCError import io.modelcontextprotocol.kotlin.sdk.types.RequestMeta import io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotification import io.modelcontextprotocol.kotlin.sdk.types.SUPPORTED_PROTOCOL_VERSIONS @@ -52,17 +54,18 @@ public open class ServerSession( private var _onClose: () -> Unit = {} + private val _clientCapabilities: AtomicRef = atomic(null) + private val _clientVersion: AtomicRef = atomic(null) + /** * The client's reported capabilities after initialization. */ - public var clientCapabilities: ClientCapabilities? = null - private set + public val clientCapabilities: ClientCapabilities? get() = _clientCapabilities.value /** * The client's version information after initialization. */ - public var clientVersion: Implementation? = null - private set + public val clientVersion: Implementation? get() = _clientVersion.value /** * The capabilities supported by the server, related to the session. @@ -286,9 +289,15 @@ public open class ServerSession( } private fun handleInitialize(request: InitializeRequest): InitializeResult { + if (!_clientCapabilities.compareAndSet(null, request.params.capabilities)) { + throw McpException( + code = RPCError.ErrorCode.INVALID_REQUEST, + message = "Server already initialized", + ) + } + logger.debug { "Handling initialization request from client" } - clientCapabilities = request.params.capabilities - clientVersion = request.params.clientInfo + _clientVersion.value = request.params.clientInfo val requestedVersion = request.params.protocolVersion val protocolVersion = if (SUPPORTED_PROTOCOL_VERSIONS.contains(requestedVersion)) { From d60b0a29c1b9a55fcbbdab9f81b2d6d33c30472d Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Fri, 27 Mar 2026 18:37:26 +0100 Subject: [PATCH 204/449] chore: #608 update kotlin and kotlinx versions (#611) bumps: - kotlin 2.2.21 -> 2.3.10 - ktor 3.2.3 -> 3.3.3 - kotlinx-io 0.8.2 -> 0.9.0 - kotlinx-serialization 1.9.0 -> 1.10.0 ## Breaking Changes ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] 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 ## Additional context Unfortunately, CodeQL does not yet support the latest version of Kotlin Colses #608 --- gradle/libs.versions.toml | 8 +- gradlew.bat | 186 +++++++++++++++++++------------------- 2 files changed, 97 insertions(+), 97 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f9160e5..0ee9432 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ atomicfu = "0.31.0" binaryCompatibilityValidatorPlugin = "0.18.1" detekt = "2.0.0-alpha.2" dokka = "2.2.0" -kotlin = "2.2.21" +kotlin = "2.3.10" kover = "0.9.8" ktlint = "14.2.0" knit = "0.5.1" @@ -17,12 +17,12 @@ awaitility = "4.3.0" collections-immutable = "0.4.0" coroutines = "1.10.2" kotest = "6.1.9" -kotlinx-io = "0.8.2" -ktor = "3.2.3" +kotlinx-io = "0.9.0" +ktor = "3.3.3" logging = "8.0.01" mockk = "1.14.9" mokksy = "0.8.1" -serialization = "1.9.0" +serialization = "1.10.0" slf4j = "2.0.17" junit = "5.14.3" diff --git a/gradlew.bat b/gradlew.bat index e509b2d..c4bdd3a 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,93 +1,93 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega From 0042c745af87d40736fe77bde00accdf0a709026 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 18:23:29 +0000 Subject: [PATCH 205/449] chore(deps): bump org.jetbrains.kotlinx:atomicfu-gradle-plugin from 0.31.0 to 0.32.1 in the kotlinx group across 1 directory (#644) Bumps the kotlinx group with 1 update in the / directory: [org.jetbrains.kotlinx:atomicfu-gradle-plugin](https://github.com/Kotlin/kotlinx.atomicfu). Updates `org.jetbrains.kotlinx:atomicfu-gradle-plugin` from 0.31.0 to 0.32.1
Release notes

Sourced from org.jetbrains.kotlinx:atomicfu-gradle-plugin's releases.

0.32.1

0.32.1 is identical to 0.32.0 and it only includes infra updates required to publish the Gradle plugin to Gradle Plugin Portal.

0.32.0

  • SynchronizedObject implementation was reverted to version 0.29.0 to resolve a performance regression (See #584 for details)

[!NOTE] AtomicFU Gradle plugin with version 0.32.0 was not published in the Gradle Plugin Portal.

Changelog

Sourced from org.jetbrains.kotlinx:atomicfu-gradle-plugin's changelog.

Version 0.32.1

  • Fixed issues with the Gradle plugin publication to the Gradle plugin portal (#589)

Version 0.32.0

  • SynchronizedObject implementation was reverted to version 0.29.0 to resolve a performance regression (See #584 for details)
Commits

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0ee9432..f511d0d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] # plugins version -atomicfu = "0.31.0" +atomicfu = "0.32.1" binaryCompatibilityValidatorPlugin = "0.18.1" detekt = "2.0.0-alpha.2" dokka = "2.2.0" From d5337d4234c6ae891d8a34709222a1554c12443d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 11:43:53 +0300 Subject: [PATCH 206/449] chore(deps): bump modelcontextprotocol/conformance from 0.1.15 to 0.1.16 in the all-actions group (#657) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the all-actions group with 1 update: [modelcontextprotocol/conformance](https://github.com/modelcontextprotocol/conformance). Updates `modelcontextprotocol/conformance` from 0.1.15 to 0.1.16
Release notes

Sourced from modelcontextprotocol/conformance's releases.

v0.1.16

What's Changed

New Contributors

Full Changelog: https://github.com/modelcontextprotocol/conformance/compare/v0.1.15...v0.1.16

Commits
  • 21a9a2f chore: bump version to 0.1.16 (#198)
  • 1c48a30 feat: add --spec-version option to tier-check (#176)
  • fe16e38 fix: don't leak escalated scopes in PRM scopes_supported for step-up test (#154)
  • a2ec2d4 Fix Windows compatibility in tier-check client conformance (#168)
  • e702a25 fix: resolve CLI path for tier-check conformance subprocess (#175)
  • 7b5865a chore(deps-dev): bump the all-dependencies group with 2 updates (#196)
  • 687d5b4 chore(deps): ignore major version bumps in dependabot (#195)
  • 4859a81 docs: add AGENTS.md and CONTRIBUTING.md (#194)
  • 8f3994c chore(deps): consolidate dependabot updates (#192)
  • 02a5f1c feat (auth): add sep-2207 client checks (#166)
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=modelcontextprotocol/conformance&package-manager=github_actions&previous-version=0.1.15&new-version=0.1.16)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/conformance.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/conformance.yml b/.github/workflows/conformance.yml index 5b5e405..4058d02 100644 --- a/.github/workflows/conformance.yml +++ b/.github/workflows/conformance.yml @@ -53,7 +53,7 @@ jobs: done - name: Run conformance tests - uses: modelcontextprotocol/conformance@v0.1.15 + uses: modelcontextprotocol/conformance@v0.1.16 with: mode: server url: http://localhost:3001/mcp @@ -93,7 +93,7 @@ jobs: run: ./gradlew :conformance-test:installDist - name: Run conformance tests - uses: modelcontextprotocol/conformance@v0.1.15 + uses: modelcontextprotocol/conformance@v0.1.16 with: mode: client command: conformance-test/build/install/conformance-test/bin/conformance-client @@ -124,7 +124,7 @@ jobs: run: ./gradlew :conformance-test:installDist - name: Run conformance tests - uses: modelcontextprotocol/conformance@v0.1.15 + uses: modelcontextprotocol/conformance@v0.1.16 with: mode: client command: conformance-test/build/install/conformance-test/bin/conformance-client @@ -163,7 +163,7 @@ jobs: run: ./gradlew :conformance-test:installDist - name: Run conformance tests - uses: modelcontextprotocol/conformance@v0.1.15 + uses: modelcontextprotocol/conformance@v0.1.16 with: mode: client command: conformance-test/build/install/conformance-test/bin/conformance-client From 7ed57f9fc7f1cdfcaff3f68e7a7cad2ad9112146 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 13:18:06 +0300 Subject: [PATCH 207/449] chore(deps): bump mcp-kotlin from 0.9.0 to 0.10.0 in /samples/weather-stdio-server (#654) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `mcp-kotlin` from 0.9.0 to 0.10.0. Updates `io.modelcontextprotocol:kotlin-sdk-client` from 0.9.0 to 0.10.0
Release notes

Sourced from io.modelcontextprotocol:kotlin-sdk-client's releases.

0.10.0

Highlights

This release adds MCP conformance testing infrastructure, SSE reconnection support, Tasks protocol types, resource template matching, and
tool execution properties. It also completes a deprecation cycle — removing all previously deprecated (error-level) symbols from io.modelcontextprotocol.kotlin.sdk in favor of their replacements in io.modelcontextprotocol.kotlin.sdk.types.

Breaking Changes

  • Deprecation cycle completed — All symbols deprecated at ERROR level in 0.9.0 have been removed by @​devcrocod (#615). This includes
    the entire io.modelcontextprotocol.kotlin.sdk.types.kt compatibility file (type aliases such as CallToolResult, CancelledNotification, ClientCapabilities, CompleteRequest, etc.) and the legacy McpJson in io.modelcontextprotocol.kotlin.sdk. Migrate to the equivalents in io.modelcontextprotocol.kotlin.sdk.types.
  • SSE/Streamable HTTP transport changes — StreamableHttpClientTransport now accepts ReconnectionOptions for configurable retry behavior by @​devcrocod (#585, #596). The previous constructor signatures remain available.

Features

  • MCP conformance test infrastructure — Comprehensive conformance test suite covering core protocol operations, tool calls, elicitation, resources, prompts, and 20 OAuth/auth scenarios. Includes CI workflow, baseline tracking, and shell runner by @​devcrocod (#585)
  • SSE reconnection with retry support — Client transports now support configurable reconnection with exponential backoff via
    ReconnectionOptions (initial delay, max delay, multiplier, max retries) by @​devcrocod (#596)
  • Tasks protocol types — Added Task, TaskStatus, and related models per the MCP tasks specification by @​devcrocod (#566)
  • Resource templates with basic template matcher — Server-side support for URI template-based resource matching by @​kpavlov (#502, #606)
  • Tool execution properties — Tool now exposes execution properties for richer tool metadata by @​devcrocod (#567)

Bug Fixes

  • Notifications not delivered over Streamable HTTP — Fixed server-side notification delivery for Streamable HTTP transport sessions by @​kpavlov (#587, #599)
  • Conformance: SEP-1330 enum schemas — Corrected elicitation enum schemas in conformance tests by @​kpavlov (#600)

Documentation

Maintenance

Full Changelog: https://github.com/modelcontextprotocol/kotlin-sdk/compare/0.9.0...0.10.0

Commits
  • 5ad0492 chore: bump project version to 0.10.0 in gradle.properties
  • bd4cf0c docs: add missing KDoc comments for public functions, properties, and classes...
  • c77ebe0 refactor(core): Fix Kotlin language and api version to 2.1 in build config (#...
  • cd91eed chore: simplify and clean up CI workflows (#617)
  • 435698a refactor(integration-test): modernize ts tests, update versions, rename SSE -...
  • 5583990 chore: deprecation cycle (#615)
  • 55afb63 chore(deps): bump gradle-wrapper from 9.3.1 to 9.4.1 (#614)
  • fd3a858 feat: add tasks models (#566)
  • 62e4e50 docs: update AGENTS.md (#612)
  • ee6561c feat: #502 resource templates with basic template matcher (#606)
  • Additional commits viewable in compare view

Updates `io.modelcontextprotocol:kotlin-sdk-server` from 0.9.0 to 0.10.0
Release notes

Sourced from io.modelcontextprotocol:kotlin-sdk-server's releases.

0.10.0

Highlights

This release adds MCP conformance testing infrastructure, SSE reconnection support, Tasks protocol types, resource template matching, and
tool execution properties. It also completes a deprecation cycle — removing all previously deprecated (error-level) symbols from io.modelcontextprotocol.kotlin.sdk in favor of their replacements in io.modelcontextprotocol.kotlin.sdk.types.

Breaking Changes

  • Deprecation cycle completed — All symbols deprecated at ERROR level in 0.9.0 have been removed by @​devcrocod (#615). This includes
    the entire io.modelcontextprotocol.kotlin.sdk.types.kt compatibility file (type aliases such as CallToolResult, CancelledNotification, ClientCapabilities, CompleteRequest, etc.) and the legacy McpJson in io.modelcontextprotocol.kotlin.sdk. Migrate to the equivalents in io.modelcontextprotocol.kotlin.sdk.types.
  • SSE/Streamable HTTP transport changes — StreamableHttpClientTransport now accepts ReconnectionOptions for configurable retry behavior by @​devcrocod (#585, #596). The previous constructor signatures remain available.

Features

  • MCP conformance test infrastructure — Comprehensive conformance test suite covering core protocol operations, tool calls, elicitation, resources, prompts, and 20 OAuth/auth scenarios. Includes CI workflow, baseline tracking, and shell runner by @​devcrocod (#585)
  • SSE reconnection with retry support — Client transports now support configurable reconnection with exponential backoff via
    ReconnectionOptions (initial delay, max delay, multiplier, max retries) by @​devcrocod (#596)
  • Tasks protocol types — Added Task, TaskStatus, and related models per the MCP tasks specification by @​devcrocod (#566)
  • Resource templates with basic template matcher — Server-side support for URI template-based resource matching by @​kpavlov (#502, #606)
  • Tool execution properties — Tool now exposes execution properties for richer tool metadata by @​devcrocod (#567)

Bug Fixes

  • Notifications not delivered over Streamable HTTP — Fixed server-side notification delivery for Streamable HTTP transport sessions by @​kpavlov (#587, #599)
  • Conformance: SEP-1330 enum schemas — Corrected elicitation enum schemas in conformance tests by @​kpavlov (#600)

Documentation

Maintenance

Full Changelog: https://github.com/modelcontextprotocol/kotlin-sdk/compare/0.9.0...0.10.0

Commits
  • 5ad0492 chore: bump project version to 0.10.0 in gradle.properties
  • bd4cf0c docs: add missing KDoc comments for public functions, properties, and classes...
  • c77ebe0 refactor(core): Fix Kotlin language and api version to 2.1 in build config (#...
  • cd91eed chore: simplify and clean up CI workflows (#617)
  • 435698a refactor(integration-test): modernize ts tests, update versions, rename SSE -...
  • 5583990 chore: deprecation cycle (#615)
  • 55afb63 chore(deps): bump gradle-wrapper from 9.3.1 to 9.4.1 (#614)
  • fd3a858 feat: add tasks models (#566)
  • 62e4e50 docs: update AGENTS.md (#612)
  • ee6561c feat: #502 resource templates with basic template matcher (#606)
  • Additional commits viewable in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/weather-stdio-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/weather-stdio-server/gradle/libs.versions.toml b/samples/weather-stdio-server/gradle/libs.versions.toml index a580416..d1a647a 100644 --- a/samples/weather-stdio-server/gradle/libs.versions.toml +++ b/samples/weather-stdio-server/gradle/libs.versions.toml @@ -3,7 +3,7 @@ coroutines = "1.10.2" kotlin = "2.2.21" ktor = "3.2.3" logging = "8.0.01" -mcp-kotlin = "0.9.0" +mcp-kotlin = "0.10.0" shadow = "9.4.0" slf4j = "2.0.17" From 384064ed43ebffffda66673d9dcd8eddd9a7cb55 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 13:50:53 +0300 Subject: [PATCH 208/449] chore(deps): bump the other-dependencies group across 4 directories with 1 update (#648) Bumps the other-dependencies group with 1 update in the /samples/kotlin-mcp-client directory: [com.gradleup.shadow](https://github.com/GradleUp/shadow). Bumps the other-dependencies group with 1 update in the /samples/kotlin-mcp-server directory: [com.gradleup.shadow](https://github.com/GradleUp/shadow). Bumps the other-dependencies group with 1 update in the /samples/simple-streamable-server directory: [com.gradleup.shadow](https://github.com/GradleUp/shadow). Bumps the other-dependencies group with 1 update in the /samples/weather-stdio-server directory: [com.gradleup.shadow](https://github.com/GradleUp/shadow). Updates `com.gradleup.shadow` from 9.4.0 to 9.4.1
Release notes

Sourced from com.gradleup.shadow's releases.

9.4.1

Changed

  • Update Kotlin to 2.3.20. (#1978)
Commits

Updates `com.gradleup.shadow` from 9.4.0 to 9.4.1
Release notes

Sourced from com.gradleup.shadow's releases.

9.4.1

Changed

  • Update Kotlin to 2.3.20. (#1978)
Commits

Updates `com.gradleup.shadow` from 9.4.0 to 9.4.1
Release notes

Sourced from com.gradleup.shadow's releases.

9.4.1

Changed

  • Update Kotlin to 2.3.20. (#1978)
Commits

Updates `com.gradleup.shadow` from 9.4.0 to 9.4.1
Release notes

Sourced from com.gradleup.shadow's releases.

9.4.1

Changed

  • Update Kotlin to 2.3.20. (#1978)
Commits

Updates `com.gradleup.shadow` from 9.4.0 to 9.4.1
Release notes

Sourced from com.gradleup.shadow's releases.

9.4.1

Changed

  • Update Kotlin to 2.3.20. (#1978)
Commits

Updates `com.gradleup.shadow` from 9.4.0 to 9.4.1
Release notes

Sourced from com.gradleup.shadow's releases.

9.4.1

Changed

  • Update Kotlin to 2.3.20. (#1978)
Commits

Updates `com.gradleup.shadow` from 9.4.0 to 9.4.1
Release notes

Sourced from com.gradleup.shadow's releases.

9.4.1

Changed

  • Update Kotlin to 2.3.20. (#1978)
Commits

Updates `com.gradleup.shadow` from 9.4.0 to 9.4.1
Release notes

Sourced from com.gradleup.shadow's releases.

9.4.1

Changed

  • Update Kotlin to 2.3.20. (#1978)
Commits

Updates `com.gradleup.shadow` from 9.4.0 to 9.4.1
Release notes

Sourced from com.gradleup.shadow's releases.

9.4.1

Changed

  • Update Kotlin to 2.3.20. (#1978)
Commits

Updates `com.gradleup.shadow` from 9.4.0 to 9.4.1
Release notes

Sourced from com.gradleup.shadow's releases.

9.4.1

Changed

  • Update Kotlin to 2.3.20. (#1978)
Commits

Updates `com.gradleup.shadow` from 9.4.0 to 9.4.1
Release notes

Sourced from com.gradleup.shadow's releases.

9.4.1

Changed

  • Update Kotlin to 2.3.20. (#1978)
Commits

Updates `com.gradleup.shadow` from 9.4.0 to 9.4.1
Release notes

Sourced from com.gradleup.shadow's releases.

9.4.1

Changed

  • Update Kotlin to 2.3.20. (#1978)
Commits

Updates `com.gradleup.shadow` from 9.4.0 to 9.4.1
Release notes

Sourced from com.gradleup.shadow's releases.

9.4.1

Changed

  • Update Kotlin to 2.3.20. (#1978)
Commits

Updates `com.gradleup.shadow` from 9.4.0 to 9.4.1
Release notes

Sourced from com.gradleup.shadow's releases.

9.4.1

Changed

  • Update Kotlin to 2.3.20. (#1978)
Commits

Updates `com.gradleup.shadow` from 9.4.0 to 9.4.1
Release notes

Sourced from com.gradleup.shadow's releases.

9.4.1

Changed

  • Update Kotlin to 2.3.20. (#1978)
Commits

Updates `com.gradleup.shadow` from 9.4.0 to 9.4.1
Release notes

Sourced from com.gradleup.shadow's releases.

9.4.1

Changed

  • Update Kotlin to 2.3.20. (#1978)
Commits

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-client/gradle/libs.versions.toml | 2 +- samples/kotlin-mcp-server/gradle/libs.versions.toml | 2 +- samples/simple-streamable-server/gradle/libs.versions.toml | 2 +- samples/weather-stdio-server/gradle/libs.versions.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index 2a819cf..227422b 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -3,7 +3,7 @@ anthropic = "2.18.0" kotlin = "2.2.21" ktor = "3.2.3" mcp-kotlin = "0.9.0" -shadow = "9.4.0" +shadow = "9.4.1" slf4j = "2.0.17" [libraries] diff --git a/samples/kotlin-mcp-server/gradle/libs.versions.toml b/samples/kotlin-mcp-server/gradle/libs.versions.toml index 72b8a16..fa0cd81 100644 --- a/samples/kotlin-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-server/gradle/libs.versions.toml @@ -3,7 +3,7 @@ kotlin = "2.2.21" ktor = "3.2.3" mcp-kotlin = "0.9.0" slf4j = "2.0.17" -shadow = "9.4.0" +shadow = "9.4.1" [libraries] ktor-bom = { group = "io.ktor", name = "ktor-bom", version.ref = "ktor" } diff --git a/samples/simple-streamable-server/gradle/libs.versions.toml b/samples/simple-streamable-server/gradle/libs.versions.toml index ebe741c..1f893bd 100644 --- a/samples/simple-streamable-server/gradle/libs.versions.toml +++ b/samples/simple-streamable-server/gradle/libs.versions.toml @@ -3,7 +3,7 @@ kotlin = "2.2.21" ktor = "3.2.3" mcp-kotlin = "0.9.0" slf4j = "2.0.17" -shadow = "9.4.0" +shadow = "9.4.1" [libraries] ktor-bom = { group = "io.ktor", name = "ktor-bom", version.ref = "ktor" } diff --git a/samples/weather-stdio-server/gradle/libs.versions.toml b/samples/weather-stdio-server/gradle/libs.versions.toml index d1a647a..0b75f09 100644 --- a/samples/weather-stdio-server/gradle/libs.versions.toml +++ b/samples/weather-stdio-server/gradle/libs.versions.toml @@ -4,7 +4,7 @@ kotlin = "2.2.21" ktor = "3.2.3" logging = "8.0.01" mcp-kotlin = "0.10.0" -shadow = "9.4.0" +shadow = "9.4.1" slf4j = "2.0.17" [libraries] From c46f53a7bf49f6b9edb66af21227c46cfcffffbe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 11:08:35 +0000 Subject: [PATCH 209/449] chore(deps): bump path-to-regexp from 8.3.0 to 8.4.0 in /integration-test/src/jvmTest/typescript (#647) Bumps [path-to-regexp](https://github.com/pillarjs/path-to-regexp) from 8.3.0 to 8.4.0.
Release notes

Sourced from path-to-regexp's releases.

8.4.0

Important

Fixed

Changed

  • Dedupes regex prefixes (pillarjs/path-to-regexp#422)
    • This will result in shorter regular expressions for some cases using optional groups
  • Rejects large optional route combinations (pillarjs/path-to-regexp#424)
    • When using groups such as /users{/delete} it will restrict the number of generated combinations to < 256, equivalent to 8 top-level optional groups and unlikely to occur in a real world application, but avoids exploding the regex size for applications that accept user created routes
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=path-to-regexp&package-manager=npm_and_yarn&previous-version=8.3.0&new-version=8.4.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/modelcontextprotocol/kotlin-sdk/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- integration-test/src/jvmTest/typescript/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index 2021be5..ca30f65 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -1429,9 +1429,9 @@ } }, "node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.0.tgz", + "integrity": "sha512-PuseHIvAnz3bjrM2rGJtSgo1zjgxapTLZ7x2pjhzWwlp4SJQgK3f3iZIQwkpEnBaKz6seKBADpM4B4ySkuYypg==", "license": "MIT", "funding": { "type": "opencollective", From 7a95cb7e19eb90216c040635156653034f407916 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 14:22:15 +0000 Subject: [PATCH 210/449] chore(deps): bump io.ktor:ktor-bom from 3.2.3 to 3.3.3 in /samples/kotlin-mcp-server (#652) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [io.ktor:ktor-bom](https://github.com/ktorio/ktor) from 3.2.3 to 3.3.3.
Release notes

Sourced from io.ktor:ktor-bom's releases.

3.3.3

Published 26 November 2025

Improvements

  • KTOR-6837 Discrepancies when parsing URL host with CIO and Darwin engines compared to the rest engines
  • KTOR-9050 Logging: SimpleLogger should be an object, not a class
  • KTOR-9094 Jetty Client: Support HTTP/2 over cleartext (h2c)
  • KTOR-9120 OpenAPI gen: missing operationId for KDoc fields
  • KTOR-3019 Improve logging for CORS plugin

Bugfixes

  • KTOR-8671 Netty: RejectedExecutionException during shutdown on MacOS when dev mode is enabled
  • KTOR-9096 Darwin: New SSE handlers stop responding after canceling few SSE sessions
  • KTOR-9125 Double ResponseSent invocation when exception is thrown after respond
  • KTOR-8878 OpenAPI: StackOverflowError when a response object has property with @Contextual serializer
  • KTOR-8947 Java, ContentEncoding: IllegalHeaderNameException is thrown for ":status" pseudo header with HTTP/2
  • KTOR-9092 NettyHttp2Handler throws IllegalArgumentException: 'ktor.ApplicationCall' is already in use
  • KTOR-8924 Curl: Client sends both Transfer-Encoding and Content-Length headers for DELETE requests with body
  • KTOR-8838 Exception handling issue in client cache

3.3.2

Published 5 November 2025

Improvements

  • WebRTC Client. Remove redundant targets (KTOR-9062)
  • Add Socks proxy support to Darwin engine (KTOR-8968)
  • Java: Improve error message when SOCKS proxy is used (KTOR-2908)

Bugfixes

  • HttpRequestRetry: SendCountExceedException when max retries is more than maxSendCount of HttpSend (KTOR-5850)
  • Darwin: The maxFrameSize option has no effect (KTOR-6963)
  • OpenAPI: StackOverflowError when a response object has property with @Contextual serializer (KTOR-8878)
  • OpenAPI gen: missing KDoc fields (KTOR-9021)
  • Server call.request.path() returns routing selectors in path (KTOR-7639)
  • StaticContent doesn't allow siblings (KTOR-9012)
  • HttpCache: FileStorage doesn't use given dispatcher for all file operations (KTOR-8832)
  • Curl: SOCKS proxy doesn't work (KTOR-9008)
  • Netty: java.lang.VerifyError is thrown on Android since 3.3.0 (KTOR-8916)
  • Response body channel is canceled while the body is being saved when having HttpRequestRetry and onDownload (KTOR-8975)
  • HttpCache: InvalidCacheStateException when varyKeys stored in files contain uppercase letters since 3.3.0 (KTOR-8970)

3.3.1

Published 8 October 2025

Improvements

  • Add a note about SSE session lifetime in KDoc (KTOR-8440)
  • Update Kotlin to 2.2.20 (KTOR-8896)

Bugfixes

  • NumberFormatException when Content-Length header value contains null bytes (KTOR-4828)

... (truncated)

Changelog

Sourced from io.ktor:ktor-bom's changelog.

3.3.3

Published 26 November 2025

Improvements

  • KTOR-6837 Discrepancies when parsing URL host with CIO and Darwin engines compared to the rest engines
  • KTOR-9050 Logging: SimpleLogger should be an object, not a class
  • KTOR-9094 Jetty Client: Support HTTP/2 over cleartext (h2c)
  • KTOR-9120 OpenAPI gen: missing operationId for KDoc fields
  • KTOR-3019 Improve logging for CORS plugin

Bugfixes

  • KTOR-8671 Netty: RejectedExecutionException during shutdown on MacOS when dev mode is enabled
  • KTOR-9096 Darwin: New SSE handlers stop responding after canceling few SSE sessions
  • KTOR-9125 Double ResponseSent invocation when exception is thrown after respond
  • KTOR-8878 OpenAPI: StackOverflowError when a response object has property with @​Contextual serializer
  • KTOR-8947 Java, ContentEncoding: IllegalHeaderNameException is thrown for ":status" pseudo header with HTTP/2
  • KTOR-9092 NettyHttp2Handler throws IllegalArgumentException: 'ktor.ApplicationCall' is already in use
  • KTOR-8924 Curl: Client sends both Transfer-Encoding and Content-Length headers for DELETE requests with body
  • KTOR-8838 Exception handling issue in client cache

3.3.2

Published 5 November 2025

Improvements

  • WebRTC Client. Remove redundant targets (KTOR-9062)
  • Add Socks proxy support to Darwin engine (KTOR-8968)
  • Java: Improve error message when SOCKS proxy is used (KTOR-2908)

Bugfixes

  • HttpRequestRetry: SendCountExceedException when max retries is more than maxSendCount of HttpSend (KTOR-5850)
  • Darwin: The maxFrameSize option has no effect (KTOR-6963)
  • OpenAPI: StackOverflowError when a response object has property with @​Contextual serializer (KTOR-8878)
  • OpenAPI gen: missing KDoc fields (KTOR-9021)
  • Server call.request.path() returns routing selectors in path (KTOR-7639)
  • StaticContent doesn't allow siblings (KTOR-9012)
  • HttpCache: FileStorage doesn't use given dispatcher for all file operations (KTOR-8832)
  • Curl: SOCKS proxy doesn't work (KTOR-9008)
  • Netty: java.lang.VerifyError is thrown on Android since 3.3.0 (KTOR-8916)
  • Response body channel is canceled while the body is being saved when having HttpRequestRetry and onDownload (KTOR-8975)
  • HttpCache: InvalidCacheStateException when varyKeys stored in files contain uppercase letters since 3.3.0 (KTOR-8970)

3.3.1

Published 8 October 2025

Improvements

  • Add a note about SSE session lifetime in KDoc (KTOR-8440)
  • Update Kotlin to 2.2.20 (KTOR-8896)

... (truncated)

Commits
  • 629d22c Release 3.3.3 (#5219)
  • 77bbf10 Server Netty. Fix rejected execution during engine stop (#8671)
  • 75439b0 KTOR-9096 Fix SSE cancellation leak on Darwin (#5208)
  • 62ea8e4 KTOR-6837 Fix client URL handling for invalid DefaultRequest.host
  • ef09e84 Add stream.demand calls to JettyResponseListener
  • 47e6049 KTOR-9094 Jetty Client: Allow h2c traffic
  • e70922e KTOR-9125 Fix double ResponseSent invocation when exception is thrown after r...
  • 589f02d KTOR-3019 Improve CORS plugin logging to ease troubleshooting (#5194)
  • 5a66b47 Update Gradle and Develocity (#5201)
  • 2ba2f02 KTOR-8947 Filter pseudo-headers in Java client (#5198)
  • Additional commits viewable in compare view

Most Recent Ignore Conditions Applied to This Pull Request | Dependency Name | Ignore Conditions | | --- | --- | | io.ktor:ktor-bom | [>= 3.4.a, < 3.5] |
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-server/gradle/libs.versions.toml b/samples/kotlin-mcp-server/gradle/libs.versions.toml index fa0cd81..36f5244 100644 --- a/samples/kotlin-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-server/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] kotlin = "2.2.21" -ktor = "3.2.3" +ktor = "3.3.3" mcp-kotlin = "0.9.0" slf4j = "2.0.17" shadow = "9.4.1" From 6e247f902d77b0cf431cfac3bad3b6dede54749b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 23:32:24 +0300 Subject: [PATCH 211/449] chore(deps): bump mcp-kotlin from 0.9.0 to 0.10.0 in /samples/kotlin-mcp-server (#651) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `mcp-kotlin` from 0.9.0 to 0.10.0. Updates `io.modelcontextprotocol:kotlin-sdk-client` from 0.9.0 to 0.10.0
Release notes

Sourced from io.modelcontextprotocol:kotlin-sdk-client's releases.

0.10.0

Highlights

This release adds MCP conformance testing infrastructure, SSE reconnection support, Tasks protocol types, resource template matching, and
tool execution properties. It also completes a deprecation cycle — removing all previously deprecated (error-level) symbols from io.modelcontextprotocol.kotlin.sdk in favor of their replacements in io.modelcontextprotocol.kotlin.sdk.types.

Breaking Changes

  • Deprecation cycle completed — All symbols deprecated at ERROR level in 0.9.0 have been removed by @​devcrocod (#615). This includes
    the entire io.modelcontextprotocol.kotlin.sdk.types.kt compatibility file (type aliases such as CallToolResult, CancelledNotification, ClientCapabilities, CompleteRequest, etc.) and the legacy McpJson in io.modelcontextprotocol.kotlin.sdk. Migrate to the equivalents in io.modelcontextprotocol.kotlin.sdk.types.
  • SSE/Streamable HTTP transport changes — StreamableHttpClientTransport now accepts ReconnectionOptions for configurable retry behavior by @​devcrocod (#585, #596). The previous constructor signatures remain available.

Features

  • MCP conformance test infrastructure — Comprehensive conformance test suite covering core protocol operations, tool calls, elicitation, resources, prompts, and 20 OAuth/auth scenarios. Includes CI workflow, baseline tracking, and shell runner by @​devcrocod (#585)
  • SSE reconnection with retry support — Client transports now support configurable reconnection with exponential backoff via
    ReconnectionOptions (initial delay, max delay, multiplier, max retries) by @​devcrocod (#596)
  • Tasks protocol types — Added Task, TaskStatus, and related models per the MCP tasks specification by @​devcrocod (#566)
  • Resource templates with basic template matcher — Server-side support for URI template-based resource matching by @​kpavlov (#502, #606)
  • Tool execution properties — Tool now exposes execution properties for richer tool metadata by @​devcrocod (#567)

Bug Fixes

  • Notifications not delivered over Streamable HTTP — Fixed server-side notification delivery for Streamable HTTP transport sessions by @​kpavlov (#587, #599)
  • Conformance: SEP-1330 enum schemas — Corrected elicitation enum schemas in conformance tests by @​kpavlov (#600)

Documentation

Maintenance

Full Changelog: https://github.com/modelcontextprotocol/kotlin-sdk/compare/0.9.0...0.10.0

Commits
  • 5ad0492 chore: bump project version to 0.10.0 in gradle.properties
  • bd4cf0c docs: add missing KDoc comments for public functions, properties, and classes...
  • c77ebe0 refactor(core): Fix Kotlin language and api version to 2.1 in build config (#...
  • cd91eed chore: simplify and clean up CI workflows (#617)
  • 435698a refactor(integration-test): modernize ts tests, update versions, rename SSE -...
  • 5583990 chore: deprecation cycle (#615)
  • 55afb63 chore(deps): bump gradle-wrapper from 9.3.1 to 9.4.1 (#614)
  • fd3a858 feat: add tasks models (#566)
  • 62e4e50 docs: update AGENTS.md (#612)
  • ee6561c feat: #502 resource templates with basic template matcher (#606)
  • Additional commits viewable in compare view

Updates `io.modelcontextprotocol:kotlin-sdk-server` from 0.9.0 to 0.10.0
Release notes

Sourced from io.modelcontextprotocol:kotlin-sdk-server's releases.

0.10.0

Highlights

This release adds MCP conformance testing infrastructure, SSE reconnection support, Tasks protocol types, resource template matching, and
tool execution properties. It also completes a deprecation cycle — removing all previously deprecated (error-level) symbols from io.modelcontextprotocol.kotlin.sdk in favor of their replacements in io.modelcontextprotocol.kotlin.sdk.types.

Breaking Changes

  • Deprecation cycle completed — All symbols deprecated at ERROR level in 0.9.0 have been removed by @​devcrocod (#615). This includes
    the entire io.modelcontextprotocol.kotlin.sdk.types.kt compatibility file (type aliases such as CallToolResult, CancelledNotification, ClientCapabilities, CompleteRequest, etc.) and the legacy McpJson in io.modelcontextprotocol.kotlin.sdk. Migrate to the equivalents in io.modelcontextprotocol.kotlin.sdk.types.
  • SSE/Streamable HTTP transport changes — StreamableHttpClientTransport now accepts ReconnectionOptions for configurable retry behavior by @​devcrocod (#585, #596). The previous constructor signatures remain available.

Features

  • MCP conformance test infrastructure — Comprehensive conformance test suite covering core protocol operations, tool calls, elicitation, resources, prompts, and 20 OAuth/auth scenarios. Includes CI workflow, baseline tracking, and shell runner by @​devcrocod (#585)
  • SSE reconnection with retry support — Client transports now support configurable reconnection with exponential backoff via
    ReconnectionOptions (initial delay, max delay, multiplier, max retries) by @​devcrocod (#596)
  • Tasks protocol types — Added Task, TaskStatus, and related models per the MCP tasks specification by @​devcrocod (#566)
  • Resource templates with basic template matcher — Server-side support for URI template-based resource matching by @​kpavlov (#502, #606)
  • Tool execution properties — Tool now exposes execution properties for richer tool metadata by @​devcrocod (#567)

Bug Fixes

  • Notifications not delivered over Streamable HTTP — Fixed server-side notification delivery for Streamable HTTP transport sessions by @​kpavlov (#587, #599)
  • Conformance: SEP-1330 enum schemas — Corrected elicitation enum schemas in conformance tests by @​kpavlov (#600)

Documentation

Maintenance

Full Changelog: https://github.com/modelcontextprotocol/kotlin-sdk/compare/0.9.0...0.10.0

Commits
  • 5ad0492 chore: bump project version to 0.10.0 in gradle.properties
  • bd4cf0c docs: add missing KDoc comments for public functions, properties, and classes...
  • c77ebe0 refactor(core): Fix Kotlin language and api version to 2.1 in build config (#...
  • cd91eed chore: simplify and clean up CI workflows (#617)
  • 435698a refactor(integration-test): modernize ts tests, update versions, rename SSE -...
  • 5583990 chore: deprecation cycle (#615)
  • 55afb63 chore(deps): bump gradle-wrapper from 9.3.1 to 9.4.1 (#614)
  • fd3a858 feat: add tasks models (#566)
  • 62e4e50 docs: update AGENTS.md (#612)
  • ee6561c feat: #502 resource templates with basic template matcher (#606)
  • Additional commits viewable in compare view

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-server/gradle/libs.versions.toml b/samples/kotlin-mcp-server/gradle/libs.versions.toml index 36f5244..1c59a88 100644 --- a/samples/kotlin-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-server/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] kotlin = "2.2.21" ktor = "3.3.3" -mcp-kotlin = "0.9.0" +mcp-kotlin = "0.10.0" slf4j = "2.0.17" shadow = "9.4.1" From ad072325c9f4e1feeb8bb2e532d9ec061c31959f Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Mon, 30 Mar 2026 23:47:31 +0300 Subject: [PATCH 212/449] samples: update mcp-kotlin to 0.10.0 across sample projects (#645) ## Update mcp-kotlin to 0.10.0 across sample projects ## How Has This Been Tested? Locally, added ServerIntegrationTest to simple-streamable-server ## Breaking Changes No ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] Documentation update - [x] Test 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 --- .../gradle/libs.versions.toml | 2 +- samples/notebooks/McpClient.ipynb | 665 ++++++++++-------- .../simple-streamable-server/build.gradle.kts | 9 + .../gradle/libs.versions.toml | 6 +- .../sample/server/ServerIntegrationTest.kt | 146 ++++ .../test/resources/simplelogger.properties | 6 + 6 files changed, 527 insertions(+), 307 deletions(-) create mode 100644 samples/simple-streamable-server/src/test/kotlin/io/modelcontextprotocol/sample/server/ServerIntegrationTest.kt create mode 100644 samples/simple-streamable-server/src/test/resources/simplelogger.properties diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index 227422b..38cb588 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -2,7 +2,7 @@ anthropic = "2.18.0" kotlin = "2.2.21" ktor = "3.2.3" -mcp-kotlin = "0.9.0" +mcp-kotlin = "0.10.0" shadow = "9.4.1" slf4j = "2.0.17" diff --git a/samples/notebooks/McpClient.ipynb b/samples/notebooks/McpClient.ipynb index 583039d..54a0953 100644 --- a/samples/notebooks/McpClient.ipynb +++ b/samples/notebooks/McpClient.ipynb @@ -18,52 +18,65 @@ }, { "metadata": { + "executionRelatedData": { + "compiledClasses": [ + "Line_3_jupyter", + "Line_7_jupyter", + "Line_10_jupyter", + "Line_5_jupyter", + "Line_8_jupyter", + "Line_6_jupyter", + "Line_11_jupyter", + "Line_4_jupyter", + "Line_9_jupyter", + "Line_18_jupyter", + "Line_19_jupyter" + ] + }, "ExecuteTime": { - "end_time": "2025-12-04T16:08:57.281786Z", - "start_time": "2025-12-04T16:08:57.126434Z" + "end_time": "2026-03-30T10:42:19.114283Z", + "start_time": "2026-03-30T10:42:19.068020Z" } }, "cell_type": "code", "source": [ "%use coroutines (1.10.2)\n", - "%use ktor-client (0.3.0-5)\n", + "%use ktor-client (0.3.0-98)\n", "\n", "USE {\n", " dependencies(\n", - " \"io.modelcontextprotocol:kotlin-sdk-client-jvm:0.8.1\",\n", + " \"io.modelcontextprotocol:kotlin-sdk-client-jvm:0.10.0\",\n", " )\n", "}" ], "outputs": [], - "execution_count": 9 + "execution_count": 8 }, { "metadata": { + "executionRelatedData": { + "compiledClasses": [ + "Line_12_jupyter", + "Line_20_jupyter" + ] + }, "ExecuteTime": { - "end_time": "2025-12-04T16:08:57.298752Z", - "start_time": "2025-12-04T16:08:57.285099Z" + "end_time": "2026-03-30T10:42:19.131015Z", + "start_time": "2026-03-30T10:42:19.115475Z" } }, "cell_type": "code", "source": [ - "import kotlinx.coroutines.runBlocking\n", - "import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject\n", - "import io.modelcontextprotocol.kotlin.sdk.types.Implementation\n", - "import io.modelcontextprotocol.kotlin.sdk.types.TextContent\n", - "import io.modelcontextprotocol.kotlin.sdk.client.Client\n", - "import io.modelcontextprotocol.kotlin.sdk.client.ClientOptions\n", - "import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport\n", - "import io.modelcontextprotocol.kotlin.sdk.client.StreamableHttpClientTransport\n", "import io.ktor.client.HttpClient\n", - "import io.ktor.client.call.body\n", - "import io.ktor.client.request.get\n", - "import kotlinx.serialization.Serializable\n", - "import kotlinx.serialization.json.JsonObject\n", "import io.ktor.client.plugins.logging.*\n", - "import io.ktor.client.plugins.sse.SSE" + "import io.ktor.client.plugins.sse.SSE\n", + "import io.modelcontextprotocol.kotlin.sdk.client.Client\n", + "import io.modelcontextprotocol.kotlin.sdk.client.StreamableHttpClientTransport\n", + "import io.modelcontextprotocol.kotlin.sdk.types.Implementation\n", + "import io.modelcontextprotocol.kotlin.sdk.types.TextContent" ], "outputs": [], - "execution_count": 10 + "execution_count": 9 }, { "metadata": {}, @@ -75,9 +88,15 @@ }, { "metadata": { + "executionRelatedData": { + "compiledClasses": [ + "Line_13_jupyter", + "Line_21_jupyter" + ] + }, "ExecuteTime": { - "end_time": "2025-12-04T16:08:57.346926Z", - "start_time": "2025-12-04T16:08:57.301809Z" + "end_time": "2026-03-30T10:42:19.189250Z", + "start_time": "2026-03-30T10:42:19.131969Z" } }, "cell_type": "code", @@ -88,14 +107,15 @@ " level = LogLevel.ALL\n", " logger = object : Logger {\n", " override fun log(message: String) {\n", - " DISPLAY(HTML(\"
HTTP Log
$message
\"))\n", + " val messageType = if (message.contains(\"REQUEST\")) \"Request\" else \"Response\"\n", + " DISPLAY(HTML(\"
HTTP $messageType
$message
\"))\n", " }\n", " }\n", " }\n", "}\n" ], "outputs": [], - "execution_count": 11 + "execution_count": 10 }, { "metadata": {}, @@ -104,9 +124,15 @@ }, { "metadata": { + "executionRelatedData": { + "compiledClasses": [ + "Line_14_jupyter", + "Line_22_jupyter" + ] + }, "ExecuteTime": { - "end_time": "2025-12-04T16:08:57.375128Z", - "start_time": "2025-12-04T16:08:57.350611Z" + "end_time": "2026-03-30T10:42:19.216018Z", + "start_time": "2026-03-30T10:42:19.190636Z" } }, "cell_type": "code", @@ -115,12 +141,11 @@ " clientInfo = Implementation(\n", " name = \"my-client\",\n", " version = \"1.0.0\"\n", - " ),\n", - " options = ClientOptions(),\n", + " )\n", ")" ], "outputs": [], - "execution_count": 12 + "execution_count": 11 }, { "metadata": {}, @@ -129,9 +154,15 @@ }, { "metadata": { + "executionRelatedData": { + "compiledClasses": [ + "Line_15_jupyter", + "Line_23_jupyter" + ] + }, "ExecuteTime": { - "end_time": "2025-12-04T16:08:58.199710Z", - "start_time": "2025-12-04T16:08:57.379661Z" + "end_time": "2026-03-30T10:42:19.748081Z", + "start_time": "2026-03-30T10:42:19.216824Z" } }, "cell_type": "code", @@ -152,7 +183,7 @@ { "data": { "text/html": [ - "
HTTP Log
REQUEST: https://learn.microsoft.com/api/mcp\n",
+       "
HTTP Request
REQUEST: https://learn.microsoft.com/api/mcp\n",
        "METHOD: POST\n",
        "COMMON HEADERS\n",
        "-> Accept: application/json, text/event-stream\n",
@@ -162,7 +193,7 @@
        "-> Content-Type: application/json\n",
        "BODY Content-Type: application/json\n",
        "BODY START\n",
-       "{\"id\":\"26884bad1c9b4ea0b9c4470f893b60d3\",\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2025-06-18\",\"capabilities\":{},\"clientInfo\":{\"name\":\"my-client\",\"version\":\"1.0.0\"}},\"jsonrpc\":\"2.0\"}\n",
+       "{\"id\":\"8461d63b3d784df183832d109b688608\",\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2025-11-25\",\"capabilities\":{},\"clientInfo\":{\"name\":\"my-client\",\"version\":\"1.0.0\"}},\"jsonrpc\":\"2.0\"}\n",
        "BODY END
" ] }, @@ -175,31 +206,31 @@ { "data": { "text/html": [ - "
HTTP Log
RESPONSE: 200 OK\n",
+       "
HTTP Response
RESPONSE: 200 OK\n",
        "METHOD: POST\n",
        "FROM: https://learn.microsoft.com/api/mcp\n",
        "COMMON HEADERS\n",
-       "-> Akamai-Cache-Status: Miss from child, Miss from parent\n",
+       "-> Akamai-Cache-Status: Miss from child, NotCacheable from parent\n",
        "-> Cache-Control: no-cache, no-store\n",
        "-> Connection: keep-alive\n",
        "-> Content-Encoding: identity\n",
-       "-> Content-Length: 1947\n",
+       "-> Content-Length: 1983\n",
        "-> Content-Type: text/event-stream\n",
-       "-> Date: Thu, 04 Dec 2025 16:08:57 GMT\n",
-       "-> Expires: Thu, 04 Dec 2025 16:08:57 GMT\n",
-       "-> Mcp-Session-Id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n",
+       "-> Date: Mon, 30 Mar 2026 10:42:19 GMT\n",
+       "-> Expires: Mon, 30 Mar 2026 10:42:19 GMT\n",
        "-> Request-Context: appId=cid-v1:ccd19013-8f20-4877-8d3f-84a0107e7119\n",
        "-> Set-Cookie: ASLBSA=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; Path=/; Secure; HttpOnly;; ASLBSACORS=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; SameSite=none; Path=/; Secure; HttpOnly;\n",
        "-> Strict-Transport-Security: max-age=31536000; includeSubDomains; preload\n",
        "-> X-Content-Type-Options: nosniff\n",
        "-> X-Powered-By: ASP.NET\n",
+       "-> mcp-session-id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIiwiaWNvbnMiOm51bGwsIndlYnNpdGVVcmwiOm51bGx9LCJ1c2VySWRDbGFpbSI6IjQ4NjFlZWMxLWRlNGUtNGZhMi1hMjA4LWJkNzNhNDdkYmE3OCJ9\n",
        "-> nel: {\"report_to\":\"network-errors\",\"max_age\":604800,\"success_fraction\":0.01,\"failure_fraction\":1.0}\n",
        "-> report-to: {\"group\":\"network-errors\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://mdec.nelreports.net/api/report?cat=mdocs\"}]}\n",
-       "-> x-azure-ref: 20251204T160857Z-r1cfc47d796grb7bhC1STOs2rc0000002u20000000002xwb\n",
+       "-> x-azure-ref: 20260330T104219Z-157fb874dd7qc6ndhC1SVG8pe00000000740000000000xwt\n",
        "BODY Content-Type: text/event-stream\n",
        "BODY START\n",
        "event: message\n",
-       "data: {\"result\":{\"protocolVersion\":\"2025-06-18\",\"capabilities\":{\"logging\":{},\"prompts\":{},\"resources\":{},\"tools\":{\"listChanged\":true}},\"serverInfo\":{\"name\":\"Microsoft Learn MCP Server\",\"version\":\"1.0.0\"},\"instructions\":\"# Microsoft Learn MCP Server\\r\\n\\r\\nThis server gives structured access to official Microsoft and Azure documentation via three tools:\\r\\n\\r\\n## Tools\\r\\n\\r\\n### microsoft_docs_search\\r\\nSearch official documentation and return up to 10 concise, high-quality content chunks (max 500 tokens each), including title, URL, and excerpt.\\r\\n\\r\\n- Use first to get a quick, reliable overview\\r\\n- Ideal for grounding answers in Microsoft knowledge\\r\\n\\r\\n### microsoft_code_sample_search\\r\\nSearch for code snippets and examples in official Microsoft Learn documentation and return up to 20 relevant, high-quality code samples.\\r\\n\\r\\n- Use when you need to provide sample Microsoft/Azure related code in your answers.  \\r\\n- Ideal for generating code snippets or practical implementation examples.  \\r\\n- Optional parameter \\u0060language\\u0060 can filter results. \\r\\n\\r\\n### microsoft_docs_fetch\\r\\nFetch and convert full Microsoft documentation pages to markdown.\\r\\n\\r\\n- Use after search when you need full content from a specific URL\\r\\n- Required for detailed tutorials, troubleshooting, prerequisites, code samples, or when search results are incomplete or outdated\\r\\n\\r\\n## Workflow\\r\\n\\r\\n1. Use \\u0060microsoft_docs_search\\u0060 to find relevant documents.\\r\\n2. If you need code examples or practical snippets, use \\u0060microsoft_code_sample_search\\u0060.\\r\\n3. If deeper or complete information is needed, use \\u0060microsoft_docs_fetch\\u0060.\\r\\n\\r\\n**Search gives breadth. Code Sample Search gives practical examples. Fetch gives depth.**\\r\\n\\r\\nAll content comes from Microsoft Learn or official sources, returned in clean markdown format.\"},\"id\":\"26884bad1c9b4ea0b9c4470f893b60d3\",\"jsonrpc\":\"2.0\"}\n",
+       "data: {\"result\":{\"protocolVersion\":\"2025-06-18\",\"capabilities\":{\"logging\":{},\"prompts\":{\"listChanged\":true},\"resources\":{\"listChanged\":true},\"tools\":{\"listChanged\":true}},\"serverInfo\":{\"name\":\"Microsoft Learn MCP Server\",\"version\":\"1.0.0\"},\"instructions\":\"# Microsoft Learn MCP Server\\r\\n\\r\\nThis server gives structured access to official Microsoft and Azure documentation via three tools:\\r\\n\\r\\n## Tools\\r\\n\\r\\n### microsoft_docs_search\\r\\nSearch official documentation and return up to 10 concise, high-quality content chunks (max 500 tokens each), including title, URL, and excerpt.\\r\\n\\r\\n- Use first to get a quick, reliable overview\\r\\n- Ideal for grounding answers in Microsoft knowledge\\r\\n\\r\\n### microsoft_code_sample_search\\r\\nSearch for code snippets and examples in official Microsoft Learn documentation and return up to 20 relevant, high-quality code samples.\\r\\n\\r\\n- Use when you need to provide sample Microsoft/Azure related code in your answers.  \\r\\n- Ideal for generating code snippets or practical implementation examples.  \\r\\n- Optional parameter \\u0060language\\u0060 can filter results. \\r\\n\\r\\n### microsoft_docs_fetch\\r\\nFetch and convert full Microsoft documentation pages to markdown.\\r\\n\\r\\n- Use after search when you need full content from a specific URL\\r\\n- Required for detailed tutorials, troubleshooting, prerequisites, code samples, or when search results are incomplete or outdated\\r\\n\\r\\n## Workflow\\r\\n\\r\\n1. Use \\u0060microsoft_docs_search\\u0060 to find relevant documents.\\r\\n2. If you need code examples or practical snippets, use \\u0060microsoft_code_sample_search\\u0060.\\r\\n3. If deeper or complete information is needed, use \\u0060microsoft_docs_fetch\\u0060.\\r\\n\\r\\n**Search gives breadth. Code Sample Search gives practical examples. Fetch gives depth.**\\r\\n\\r\\nAll content comes from Microsoft Learn or official sources, returned in clean markdown format.\"},\"id\":\"8461d63b3d784df183832d109b688608\",\"jsonrpc\":\"2.0\"}\n",
        "\n",
        "\n",
        "BODY END
" @@ -214,12 +245,12 @@ { "data": { "text/html": [ - "
HTTP Log
REQUEST: https://learn.microsoft.com/api/mcp\n",
+       "
HTTP Request
REQUEST: https://learn.microsoft.com/api/mcp\n",
        "METHOD: POST\n",
        "COMMON HEADERS\n",
        "-> Accept: application/json, text/event-stream\n",
        "-> Accept-Charset: UTF-8\n",
-       "-> mcp-session-id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n",
+       "-> mcp-session-id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIiwiaWNvbnMiOm51bGwsIndlYnNpdGVVcmwiOm51bGx9LCJ1c2VySWRDbGFpbSI6IjQ4NjFlZWMxLWRlNGUtNGZhMi1hMjA4LWJkNzNhNDdkYmE3OCJ9\n",
        "CONTENT HEADERS\n",
        "-> Content-Length: 54\n",
        "-> Content-Type: application/json\n",
@@ -238,26 +269,26 @@
     {
      "data": {
       "text/html": [
-       "
HTTP Log
RESPONSE: 202 Accepted\n",
+       "
HTTP Response
RESPONSE: 202 Accepted\n",
        "METHOD: POST\n",
        "FROM: https://learn.microsoft.com/api/mcp\n",
        "COMMON HEADERS\n",
-       "-> Akamai-Cache-Status: Miss from child, Miss from parent\n",
+       "-> Akamai-Cache-Status: Miss from child, NotCacheable from parent\n",
        "-> Cache-Control: no-cache, no-store\n",
        "-> Connection: keep-alive\n",
        "-> Content-Encoding: identity\n",
        "-> Content-Length: 0\n",
-       "-> Date: Thu, 04 Dec 2025 16:08:57 GMT\n",
-       "-> Expires: Thu, 04 Dec 2025 16:08:57 GMT\n",
-       "-> Mcp-Session-Id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n",
+       "-> Date: Mon, 30 Mar 2026 10:42:19 GMT\n",
+       "-> Expires: Mon, 30 Mar 2026 10:42:19 GMT\n",
        "-> Request-Context: appId=cid-v1:ccd19013-8f20-4877-8d3f-84a0107e7119\n",
        "-> Set-Cookie: ASLBSA=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; Path=/; Secure; HttpOnly;; ASLBSACORS=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; SameSite=none; Path=/; Secure; HttpOnly;\n",
        "-> Strict-Transport-Security: max-age=31536000; includeSubDomains; preload\n",
        "-> X-Content-Type-Options: nosniff\n",
        "-> X-Powered-By: ASP.NET\n",
+       "-> mcp-session-id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIiwiaWNvbnMiOm51bGwsIndlYnNpdGVVcmwiOm51bGx9LCJ1c2VySWRDbGFpbSI6IjQ4NjFlZWMxLWRlNGUtNGZhMi1hMjA4LWJkNzNhNDdkYmE3OCJ9\n",
        "-> nel: {\"report_to\":\"network-errors\",\"max_age\":604800,\"success_fraction\":0.01,\"failure_fraction\":1.0}\n",
        "-> report-to: {\"group\":\"network-errors\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://mdec.nelreports.net/api/report?cat=mdocs\"}]}\n",
-       "-> x-azure-ref: 20251204T160857Z-r1cfc47d796r8qqwhC1STO91g00000001p6g000000002rdg\n",
+       "-> x-azure-ref: 20260330T104219Z-157fb874dd7dr7drhC1SVGcxmc0000000pkg000000005v30\n",
        "BODY Content-Type: null\n",
        "BODY START\n",
        "\n",
@@ -270,15 +301,22 @@
       "display_id": null
      }
     },
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "✅ Server version: Implementation(name=Microsoft Learn MCP Server, version=1.0.0, title=null, websiteUrl=null, icons=null)\n"
+     ]
+    },
     {
      "data": {
       "text/html": [
-       "
HTTP Log
REQUEST: https://learn.microsoft.com/api/mcp\n",
+       "
HTTP Request
REQUEST: https://learn.microsoft.com/api/mcp\n",
        "METHOD: GET\n",
        "COMMON HEADERS\n",
        "-> Accept: application/json\n",
        "-> Accept-Charset: UTF-8\n",
-       "-> mcp-session-id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n",
+       "-> mcp-session-id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIiwiaWNvbnMiOm51bGwsIndlYnNpdGVVcmwiOm51bGx9LCJ1c2VySWRDbGFpbSI6IjQ4NjFlZWMxLWRlNGUtNGZhMi1hMjA4LWJkNzNhNDdkYmE3OCJ9\n",
        "CONTENT HEADERS\n",
        "-> Content-Length: 0\n",
        "-> Accept: text/event-stream\n",
@@ -299,107 +337,11 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "✅ Server version: Implementation(name=Microsoft Learn MCP Server, version=1.0.0, title=null, websiteUrl=null, icons=null)\n",
-      "✅ Server capabilities: ServerCapabilities(tools=Tools(listChanged=true), resources=Resources(listChanged=null, subscribe=null), prompts=Prompts(listChanged=null), logging={}, completions=null, experimental=null)\n"
+      "✅ Server capabilities: ServerCapabilities(tools=Tools(listChanged=true), resources=Resources(listChanged=true, subscribe=null), prompts=Prompts(listChanged=true), logging={}, completions=null, experimental=null)\n"
      ]
     }
    ],
-   "execution_count": 13
-  },
-  {
-   "metadata": {},
-   "cell_type": "markdown",
-   "source": "Now we can send ping request to MCP server:"
-  },
-  {
-   "metadata": {
-    "ExecuteTime": {
-     "end_time": "2025-12-04T16:08:58.507109Z",
-     "start_time": "2025-12-04T16:08:58.206412Z"
-    }
-   },
-   "cell_type": "code",
-   "source": [
-    "runBlocking {\n",
-    "    mcpClient.ping()\n",
-    "}"
-   ],
-   "outputs": [
-    {
-     "data": {
-      "text/html": [
-       "
HTTP Log
REQUEST: https://learn.microsoft.com/api/mcp\n",
-       "METHOD: POST\n",
-       "COMMON HEADERS\n",
-       "-> Accept: application/json, text/event-stream\n",
-       "-> Accept-Charset: UTF-8\n",
-       "-> mcp-session-id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n",
-       "CONTENT HEADERS\n",
-       "-> Content-Length: 73\n",
-       "-> Content-Type: application/json\n",
-       "BODY Content-Type: application/json\n",
-       "BODY START\n",
-       "{\"id\":\"caf63f9816a44766a926944329e987d5\",\"method\":\"ping\",\"jsonrpc\":\"2.0\"}\n",
-       "BODY END
" - ] - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - }, - { - "data": { - "text/html": [ - "
HTTP Log
RESPONSE: 200 OK\n",
-       "METHOD: POST\n",
-       "FROM: https://learn.microsoft.com/api/mcp\n",
-       "COMMON HEADERS\n",
-       "-> Akamai-Cache-Status: Miss from child, Miss from parent\n",
-       "-> Cache-Control: no-cache, no-store\n",
-       "-> Connection: keep-alive\n",
-       "-> Content-Encoding: identity\n",
-       "-> Content-Length: 92\n",
-       "-> Content-Type: text/event-stream\n",
-       "-> Date: Thu, 04 Dec 2025 16:08:58 GMT\n",
-       "-> Expires: Thu, 04 Dec 2025 16:08:58 GMT\n",
-       "-> Mcp-Session-Id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n",
-       "-> Request-Context: appId=cid-v1:ccd19013-8f20-4877-8d3f-84a0107e7119\n",
-       "-> Set-Cookie: ASLBSA=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; Path=/; Secure; HttpOnly;; ASLBSACORS=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; SameSite=none; Path=/; Secure; HttpOnly;\n",
-       "-> Strict-Transport-Security: max-age=31536000; includeSubDomains; preload\n",
-       "-> X-Content-Type-Options: nosniff\n",
-       "-> X-Powered-By: ASP.NET\n",
-       "-> nel: {\"report_to\":\"network-errors\",\"max_age\":604800,\"success_fraction\":0.01,\"failure_fraction\":1.0}\n",
-       "-> report-to: {\"group\":\"network-errors\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://mdec.nelreports.net/api/report?cat=mdocs\"}]}\n",
-       "-> x-azure-ref: 20251204T160858Z-r195dfd6579k9c28hC1STOn4n80000000nk0000000006rvx\n",
-       "BODY Content-Type: text/event-stream\n",
-       "BODY START\n",
-       "event: message\n",
-       "data: {\"result\":{},\"id\":\"caf63f9816a44766a926944329e987d5\",\"jsonrpc\":\"2.0\"}\n",
-       "\n",
-       "\n",
-       "BODY END
" - ] - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - }, - { - "data": { - "text/plain": [ - "EmptyResult(meta=null)" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "execution_count": 14 + "execution_count": 12 }, { "metadata": {}, @@ -408,9 +350,15 @@ }, { "metadata": { + "executionRelatedData": { + "compiledClasses": [ + "Line_16_jupyter", + "Line_24_jupyter" + ] + }, "ExecuteTime": { - "end_time": "2025-12-04T16:08:58.818668Z", - "start_time": "2025-12-04T16:08:58.530569Z" + "end_time": "2026-03-30T10:42:20.410862Z", + "start_time": "2026-03-30T10:42:19.750112Z" } }, "cell_type": "code", @@ -419,7 +367,20 @@ " val listToolsResult = mcpClient.listTools()\n", "\n", " for (tool in listToolsResult.tools) {\n", - " DISPLAY(tool)\n", + " println(\n", + " \"\"\"\n", + " |🔨Title: ${tool.title} (${tool.name})\n", + " |\n", + " |Description: ${tool.description}\n", + " |\n", + " |Input schema: ${tool.inputSchema}\n", + " |Output schema: ${tool.outputSchema}\n", + " |Icons: ${tool.icons?.joinToString(\", \")}\n", + " |Annotations: ${tool.annotations}\n", + " |Execution: ${tool.execution}\n", + " |\n", + " \"\"\".trimMargin()\n", + " )\n", " }\n", "}\n" ], @@ -427,18 +388,18 @@ { "data": { "text/html": [ - "
HTTP Log
REQUEST: https://learn.microsoft.com/api/mcp\n",
+       "
HTTP Request
REQUEST: https://learn.microsoft.com/api/mcp\n",
        "METHOD: POST\n",
        "COMMON HEADERS\n",
        "-> Accept: application/json, text/event-stream\n",
        "-> Accept-Charset: UTF-8\n",
-       "-> mcp-session-id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n",
+       "-> mcp-session-id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIiwiaWNvbnMiOm51bGwsIndlYnNpdGVVcmwiOm51bGx9LCJ1c2VySWRDbGFpbSI6IjQ4NjFlZWMxLWRlNGUtNGZhMi1hMjA4LWJkNzNhNDdkYmE3OCJ9\n",
        "CONTENT HEADERS\n",
        "-> Content-Length: 79\n",
        "-> Content-Type: application/json\n",
        "BODY Content-Type: application/json\n",
        "BODY START\n",
-       "{\"id\":\"1c46ee9521954378958971e267d543fc\",\"method\":\"tools/list\",\"jsonrpc\":\"2.0\"}\n",
+       "{\"id\":\"b9c6596da2514e04b08cbaa1defb300b\",\"method\":\"tools/list\",\"jsonrpc\":\"2.0\"}\n",
        "BODY END
" ] }, @@ -451,154 +412,35 @@ { "data": { "text/html": [ - "
HTTP Log
RESPONSE: 200 OK\n",
-       "METHOD: POST\n",
+       "
HTTP Response
RESPONSE https://learn.microsoft.com/api/mcp failed with exception: io.ktor.client.plugins.sse.SSEClientException: Expected status code 200 but was 405
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "data": { + "text/html": [ + "
HTTP Response
RESPONSE: 405 Method Not Allowed\n",
+       "METHOD: GET\n",
        "FROM: https://learn.microsoft.com/api/mcp\n",
        "COMMON HEADERS\n",
-       "-> Akamai-Cache-Status: Miss from child, Miss from parent\n",
-       "-> Cache-Control: no-cache, no-store\n",
+       "-> Akamai-Cache-Status: Miss from child, NotCacheable from parent\n",
+       "-> Cache-Control: max-age=0\n",
        "-> Connection: keep-alive\n",
-       "-> Content-Encoding: identity\n",
-       "-> Content-Length: 4495\n",
-       "-> Content-Type: text/event-stream\n",
-       "-> Date: Thu, 04 Dec 2025 16:08:58 GMT\n",
-       "-> Expires: Thu, 04 Dec 2025 16:08:58 GMT\n",
-       "-> Mcp-Session-Id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n",
+       "-> Content-Length: 215\n",
+       "-> Date: Mon, 30 Mar 2026 10:42:19 GMT\n",
+       "-> Expires: Mon, 30 Mar 2026 10:42:19 GMT\n",
        "-> Request-Context: appId=cid-v1:ccd19013-8f20-4877-8d3f-84a0107e7119\n",
-       "-> Set-Cookie: ASLBSA=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; Path=/; Secure; HttpOnly;; ASLBSACORS=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; SameSite=none; Path=/; Secure; HttpOnly;\n",
        "-> Strict-Transport-Security: max-age=31536000; includeSubDomains; preload\n",
        "-> X-Content-Type-Options: nosniff\n",
        "-> X-Powered-By: ASP.NET\n",
        "-> nel: {\"report_to\":\"network-errors\",\"max_age\":604800,\"success_fraction\":0.01,\"failure_fraction\":1.0}\n",
        "-> report-to: {\"group\":\"network-errors\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://mdec.nelreports.net/api/report?cat=mdocs\"}]}\n",
-       "-> x-azure-ref: 20251204T160858Z-r195dfd6579k9c28hC1STOn4n80000000nk0000000006rw8\n",
-       "BODY Content-Type: text/event-stream\n",
-       "BODY START\n",
-       "event: message\n",
-       "data: {\"result\":{\"tools\":[{\"name\":\"microsoft_docs_search\",\"title\":\"Microsoft Docs Search\",\"description\":\"Search official Microsoft/Azure documentation to find the most relevant and trustworthy content for a user\\u0027s query. This tool returns up to 10 high-quality content chunks (each max 500 tokens), extracted from Microsoft Learn and other official sources. Each result includes the article title, URL, and a self-contained content excerpt optimized for fast retrieval and reasoning. Always use this tool to quickly ground your answers in accurate, first-party Microsoft/Azure knowledge.\\n\\nThe \\u0060question\\u0060 parameter is no longer used, use \\u0060query\\u0060 instead.\\n\\n## Follow-up Pattern\\nTo ensure completeness, use microsoft_docs_fetch when high-value pages are identified by search. The fetch tool complements search by providing the full detail. This is a required step for comprehensive results.\",\"inputSchema\":{\"type\":\"object\",\"properties\":{\"query\":{\"description\":\"a query or topic about Microsoft/Azure products, services, platforms, developer tools, frameworks, or APIs\",\"type\":\"string\",\"default\":null},\"question\":{\"description\":\"this parameter is no longer used, use query instead.\",\"type\":\"string\",\"default\":null}}},\"annotations\":{\"title\":\"Microsoft Docs Search\",\"destructiveHint\":false,\"idempotentHint\":true,\"readOnlyHint\":true}},{\"name\":\"microsoft_code_sample_search\",\"title\":\"Microsoft Code Sample Search\",\"description\":\"Search for code snippets and examples in official Microsoft Learn documentation. This tool retrieves relevant code samples from Microsoft documentation pages providing developers with practical implementation examples and best practices for Microsoft/Azure products and services related coding tasks. This tool will help you use the **LATEST OFFICIAL** code snippets to empower coding capabilities.\\n\\n## When to Use This Tool\\n- When you are going to provide sample Microsoft/Azure related code snippets in your answers.\\n- When you are **generating any Microsoft/Azure related code**.\\n\\n## Usage Pattern\\nInput a descriptive query, or SDK/class/method name to retrieve related code samples. The optional parameter \\u0060language\\u0060 can help to filter results.\\n\\nEligible values for \\u0060language\\u0060 parameter include: csharp javascript typescript python powershell azurecli al sql java kusto cpp go rust ruby php\",\"inputSchema\":{\"type\":\"object\",\"properties\":{\"query\":{\"description\":\"a descriptive query, SDK name, method name or code snippet related to Microsoft/Azure products, services, platforms, developer tools, frameworks, APIs or SDKs\",\"type\":\"string\"},\"language\":{\"description\":\"Optional parameter specifying the programming language of code snippets to retrieve. Can significantly improve search quality if provided. Eligible values: csharp javascript typescript python powershell azurecli al sql java kusto cpp go rust ruby php\",\"type\":\"string\",\"default\":null}},\"required\":[\"query\"]},\"annotations\":{\"title\":\"Microsoft Code Sample Search\",\"destructiveHint\":false,\"idempotentHint\":true,\"readOnlyHint\":true}},{\"name\":\"microsoft_docs_fetch\",\"title\":\"Microsoft Docs Fetch\",\"description\":\"Fetch and convert a Microsoft Learn documentation page to markdown format. This tool retrieves the latest complete content of Microsoft documentation pages including Azure, .NET, Microsoft 365, and other Microsoft technologies.\\n\\n## When to Use This Tool\\n- When search results provide incomplete information or truncated content\\n- When you need complete step-by-step procedures or tutorials\\n- When you need troubleshooting sections, prerequisites, or detailed explanations\\n- When search results reference a specific page that seems highly relevant\\n- For comprehensive guides that require full context\\n\\n## Usage Pattern\\nUse this tool AFTER microsoft_docs_search when you identify specific high-value pages that need complete content. The search tool gives you an overview; this tool gives you the complete picture.\\n\\n## URL Requirements\\n- The URL must be a valid link from the microsoft.com domain.\\n\\n## Output Format\\nmarkdown with headings, code blocks, tables, and links preserved.\",\"inputSchema\":{\"type\":\"object\",\"properties\":{\"url\":{\"description\":\"URL of the Microsoft documentation page to read\",\"type\":\"string\"}},\"required\":[\"url\"]},\"annotations\":{\"title\":\"Microsoft Docs Fetch\",\"destructiveHint\":false,\"idempotentHint\":true,\"readOnlyHint\":true}}]},\"id\":\"1c46ee9521954378958971e267d543fc\",\"jsonrpc\":\"2.0\"}\n",
-       "\n",
-       "\n",
-       "BODY END
" - ] - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - }, - { - "data": { - "text/plain": [ - "Tool(name=microsoft_docs_search, inputSchema=ToolSchema(properties={\"query\":{\"description\":\"a query or topic about Microsoft/Azure products, services, platforms, developer tools, frameworks, or APIs\",\"type\":\"string\",\"default\":null},\"question\":{\"description\":\"this parameter is no longer used, use query instead.\",\"type\":\"string\",\"default\":null}}, required=null), description=Search official Microsoft/Azure documentation to find the most relevant and trustworthy content for a user's query. This tool returns up to 10 high-quality content chunks (each max 500 tokens), extracted from Microsoft Learn and other official sources. Each result includes the article title, URL, and a self-contained content excerpt optimized for fast retrieval and reasoning. Always use this tool to quickly ground your answers in accurate, first-party Microsoft/Azure knowledge.\n", - "\n", - "The `question` parameter is no longer used, use `query` instead.\n", - "\n", - "## Follow-up Pattern\n", - "To ensure completeness, use microsoft_docs_fetch when high-value pages are identified by search. The fetch tool complements search by providing the full detail. This is a required step for comprehensive results., outputSchema=null, title=Microsoft Docs Search, annotations=ToolAnnotations(title=Microsoft Docs Search, readOnlyHint=true, destructiveHint=false, idempotentHint=true, openWorldHint=null), icons=null, meta=null)" - ] - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - }, - { - "data": { - "text/plain": [ - "Tool(name=microsoft_code_sample_search, inputSchema=ToolSchema(properties={\"query\":{\"description\":\"a descriptive query, SDK name, method name or code snippet related to Microsoft/Azure products, services, platforms, developer tools, frameworks, APIs or SDKs\",\"type\":\"string\"},\"language\":{\"description\":\"Optional parameter specifying the programming language of code snippets to retrieve. Can significantly improve search quality if provided. Eligible values: csharp javascript typescript python powershell azurecli al sql java kusto cpp go rust ruby php\",\"type\":\"string\",\"default\":null}}, required=[query]), description=Search for code snippets and examples in official Microsoft Learn documentation. This tool retrieves relevant code samples from Microsoft documentation pages providing developers with practical implementation examples and best practices for Microsoft/Azure products and services related coding tasks. This tool will help you use the **LATEST OFFICIAL** code snippets to empower coding capabilities.\n", - "\n", - "## When to Use This Tool\n", - "- When you are going to provide sample Microsoft/Azure related code snippets in your answers.\n", - "- When you are **generating any Microsoft/Azure related code**.\n", - "\n", - "## Usage Pattern\n", - "Input a descriptive query, or SDK/class/method name to retrieve related code samples. The optional parameter `language` can help to filter results.\n", - "\n", - "Eligible values for `language` parameter include: csharp javascript typescript python powershell azurecli al sql java kusto cpp go rust ruby php, outputSchema=null, title=Microsoft Code Sample Search, annotations=ToolAnnotations(title=Microsoft Code Sample Search, readOnlyHint=true, destructiveHint=false, idempotentHint=true, openWorldHint=null), icons=null, meta=null)" - ] - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - }, - { - "data": { - "text/plain": [ - "Tool(name=microsoft_docs_fetch, inputSchema=ToolSchema(properties={\"url\":{\"description\":\"URL of the Microsoft documentation page to read\",\"type\":\"string\"}}, required=[url]), description=Fetch and convert a Microsoft Learn documentation page to markdown format. This tool retrieves the latest complete content of Microsoft documentation pages including Azure, .NET, Microsoft 365, and other Microsoft technologies.\n", - "\n", - "## When to Use This Tool\n", - "- When search results provide incomplete information or truncated content\n", - "- When you need complete step-by-step procedures or tutorials\n", - "- When you need troubleshooting sections, prerequisites, or detailed explanations\n", - "- When search results reference a specific page that seems highly relevant\n", - "- For comprehensive guides that require full context\n", - "\n", - "## Usage Pattern\n", - "Use this tool AFTER microsoft_docs_search when you identify specific high-value pages that need complete content. The search tool gives you an overview; this tool gives you the complete picture.\n", - "\n", - "## URL Requirements\n", - "- The URL must be a valid link from the microsoft.com domain.\n", - "\n", - "## Output Format\n", - "markdown with headings, code blocks, tables, and links preserved., outputSchema=null, title=Microsoft Docs Fetch, annotations=ToolAnnotations(title=Microsoft Docs Fetch, readOnlyHint=true, destructiveHint=false, idempotentHint=true, openWorldHint=null), icons=null, meta=null)" - ] - }, - "metadata": {}, - "output_type": "display_data", - "jetTransient": { - "display_id": null - } - } - ], - "execution_count": 15 - }, - { - "metadata": {}, - "cell_type": "markdown", - "source": "Let's call a tool:" - }, - { - "metadata": { - "ExecuteTime": { - "end_time": "2025-12-04T16:08:59.912312Z", - "start_time": "2025-12-04T16:08:58.839853Z" - } - }, - "cell_type": "code", - "source": [ - "runBlocking {\n", - " val toolCallResult = mcpClient.callTool(\n", - " name = \"microsoft_docs_search\",\n", - " arguments = mapOf(\"query\" to \"Linux\")\n", - " )\n", - " toolCallResult?.let {\n", - " for (content in it.content) {\n", - " println(content)\n", - " }\n", - " }\n", - "}\n" - ], - "outputs": [ - { - "data": { - "text/html": [ - "
HTTP Log
REQUEST: https://learn.microsoft.com/api/mcp\n",
-       "METHOD: POST\n",
-       "COMMON HEADERS\n",
-       "-> Accept: application/json, text/event-stream\n",
-       "-> Accept-Charset: UTF-8\n",
-       "-> mcp-session-id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n",
-       "CONTENT HEADERS\n",
-       "-> Content-Length: 162\n",
-       "-> Content-Type: application/json\n",
-       "BODY Content-Type: application/json\n",
-       "BODY START\n",
-       "{\"id\":\"c4d77709f5314c9e9f097f08dd756438\",\"method\":\"tools/call\",\"params\":{\"name\":\"microsoft_docs_search\",\"arguments\":{\"query\":\"Linux\"},\"_meta\":{}},\"jsonrpc\":\"2.0\"}\n",
-       "BODY END
" + "-> x-azure-ref: 20260330T104219Z-157fb874dd7qc6ndhC1SVG8pe00000000740000000000xxc
" ] }, "metadata": {}, @@ -610,31 +452,31 @@ { "data": { "text/html": [ - "
HTTP Log
RESPONSE: 200 OK\n",
+       "
HTTP Response
RESPONSE: 200 OK\n",
        "METHOD: POST\n",
        "FROM: https://learn.microsoft.com/api/mcp\n",
        "COMMON HEADERS\n",
-       "-> Akamai-Cache-Status: Miss from child, Miss from parent\n",
+       "-> Akamai-Cache-Status: Miss from child, NotCacheable from parent\n",
        "-> Cache-Control: no-cache, no-store\n",
        "-> Connection: keep-alive\n",
        "-> Content-Encoding: identity\n",
-       "-> Content-Length: 24086\n",
+       "-> Content-Length: 4983\n",
        "-> Content-Type: text/event-stream\n",
-       "-> Date: Thu, 04 Dec 2025 16:08:59 GMT\n",
-       "-> Expires: Thu, 04 Dec 2025 16:08:59 GMT\n",
-       "-> Mcp-Session-Id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIn0sInVzZXJJZENsYWltIjpudWxsfQ\n",
+       "-> Date: Mon, 30 Mar 2026 10:42:20 GMT\n",
+       "-> Expires: Mon, 30 Mar 2026 10:42:20 GMT\n",
        "-> Request-Context: appId=cid-v1:ccd19013-8f20-4877-8d3f-84a0107e7119\n",
        "-> Set-Cookie: ASLBSA=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; Path=/; Secure; HttpOnly;; ASLBSACORS=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; SameSite=none; Path=/; Secure; HttpOnly;\n",
        "-> Strict-Transport-Security: max-age=31536000; includeSubDomains; preload\n",
        "-> X-Content-Type-Options: nosniff\n",
        "-> X-Powered-By: ASP.NET\n",
+       "-> mcp-session-id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIiwiaWNvbnMiOm51bGwsIndlYnNpdGVVcmwiOm51bGx9LCJ1c2VySWRDbGFpbSI6IjQ4NjFlZWMxLWRlNGUtNGZhMi1hMjA4LWJkNzNhNDdkYmE3OCJ9\n",
        "-> nel: {\"report_to\":\"network-errors\",\"max_age\":604800,\"success_fraction\":0.01,\"failure_fraction\":1.0}\n",
        "-> report-to: {\"group\":\"network-errors\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://mdec.nelreports.net/api/report?cat=mdocs\"}]}\n",
-       "-> x-azure-ref: 20251204T160859Z-r195dfd6579k9c28hC1STOn4n80000000nk0000000006rws\n",
+       "-> x-azure-ref: 20260330T104219Z-157fb874dd7dr7drhC1SVGcxmc0000000pkg000000005v38\n",
        "BODY Content-Type: text/event-stream\n",
        "BODY START\n",
        "event: message\n",
-       "data: {\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"[{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\n## Step 2 - Choose a Linux distribution\\\\nThe different versions of Linux are called \\\\u0022distributions\\\\u0022 (sometimes shortened to \\\\u201Cdistros\\\\u201D). So there isn\\\\u2019t actually a way to just \\\\u201Cinstall Linux\\\\u201D, you first need to choose which distribution that uses the Linux kernel you want to download and install. There are over 600 active Linux distributions, such as Ubuntu, Debian, Kali Linux, openSUSE, and more. There are a number of factors to consider in choosing a distribution, such as:\\\\n### Required Linux experience\\\\nIf you\\\\u2019re new to Linux, you may want to start with Ubuntu. This is one of the most common starting places for Linux users due to the level of support and ease of use. Ubuntu offers a desktop-version and a server-version depending on how you plan to use it. The specific tasks that the operating system will be responsible for may help you determine which distribution to use. Arch Linux is a popular choice for those who want a highly customizable, do-it-yourself approach that is still stable and well-supported by a large user base. It is a much more complicated place to start, but can help you to get a better understanding of how Linux works due to the amount of custom configuration.\\\\n### System requirements\\\\nA minimal, or \\\\u0022lightweight\\\\u0022, distribution means a small footprint, with lower memory and processing speed requirements. Alpine Linux is a popular option where users will find most features disabled or not installed by default, but still has everything you need for a containerized app. Security needs: Some Linux users may have specific security concerns to consider. Kali Linux is an example of a distro designed for security tasks, such as Penetration Testing, Security Research, Computer Forensics and Reverse Engineering.\\\\n### Business and Enterprise requirements\\\\nBusiness, corporate, or academic environments may have unique concerns from consumer environments. For example Red Hat Enterprise Linux (RHEL), SUSE Linux Enterprise Server (SLES) and Oracle Linux offer 24/7 global support and subscription-based service. CentOS is another popular enterprise distro that is community-supported but still compatible with RHEL.\\\\n### Community support\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/linux/install#step-2---choose-a-linux-distribution\\u0022},{\\u0022title\\u0022:\\u0022New to Linux resources for SQL users\\u0022,\\u0022content\\u0022:\\u0022# New to Linux resources for SQL users\\\\n**Applies to:**[SQL Server](https://learn.microsoft.com/en-us/sql/sql-server/sql-docs-navigation-guide?view=sql-server-ver17#applies-to) - Linux\\\\nThis article is intended to provide a learning path through an introduction of Linux concepts. Use the sections in the article as a guided learning path to familiarize yourself with the Linux environment.\\\\nThis isn\\\\u0027t intended to be a comprehensive list but instead provides the bare minimum you need to help you administer and navigate your SQL Server on Linux environment. See the [full list of tutorials](https://www.linux.org/forums/linux-beginner-tutorials.123/) to learn more.\\\\n## What is Linux?\\\\nThe [What is Linux](https://www.linux.org/threads/what-is-linux.4106/) module introduces the history of Linux as an operating system. The module explains *the Kernel* and where Linux is today. This tutorial serves as an introduction to Linux, helping you get started.\\\\n## Select a distribution\\\\nAfter you know the history of Linux, decide which [distribution of Linux](https://www.linux.org/threads/selecting-a-linux-distribution.4117/) best suits your business needs. [SQL Server is supported](https://learn.microsoft.com/en-us/sql/linux/sql-server-linux-release-notes-2019?view=sql-server-ver17#supported-platforms) on various distributions such as Red Hat Enterprise Linux (RHEL), SUSE Linux Enterprise Server (SLES), and Ubuntu.\\\\n## Get around directories\\\\nAfter you have chosen the appropriate distribution of Linux, familiarize yourself with [getting around Linux directories](https://www.linux.org/threads/getting-around-in-linux-directories.4120/).\\\\nThis module will help you answer the following questions:\\\\n1. How to navigate between different files\\\\n2. How to identify a file from a directory\\\\n3. Identify different directories\\\\n## Install new software\\\\nNext, learn how to [install new software](https://www.linux.org/threads/installing-new-software-debian-red-hat-slackware.4119/) on your new Linux operating system. This module details installing new software to the Debian and Red Hat Linux operating systems.\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/sql/linux/new-to-linux-resources?view=sql-server-ver17\\u0022},{\\u0022title\\u0022:\\u0022Part 1.2 - Basic operations on Linux\\u0022,\\u0022content\\u0022:\\u0022# Part 1.2 - Basic operations on Linux\\\\n## Run commands by using the \\\\u0022sudo\\\\u0022 prefix\\\\nInstead of becoming the root account user in your session, you can run commands as root by using the \\\\u0060sudo \\\\u003Ccommand\\\\u003E\\\\u0060 format. This approach is safer because it will run the given command only as a superuser.\\\\nFor example, if you run \\\\u0060restart\\\\u0060 while you\\\\u0027re not elevated to superuser status, the result is as follows.\\\\n![Screenshot of restart command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/restart-command.png)\\\\nTo run this command as a superuser instead, you can either change the session context to root (by running \\\\u0060sudo su\\\\u0060) or add the \\\\u0060sudo\\\\u0060 prefix, as follows:\\\\n\\\\u0060\\\\u0060\\\\u0060bash\\\\nsudo reboot\\\\n\\\\u0060\\\\u0060\\\\u0060\\\\nNow, the restart operation runs as expected by the virtual machine.\\\\n![Screenshot of reboot command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/reboot-command.png)\\\\n## Package managers\\\\nPackage managers are used to install, upgrade, and remove applications in Linux. There are several different package managers, such as DPKG (Debian Package Management System), RPM (Red Hat Package Manager), and Zypper for SUSE Package managers themselves are low-level. Therefore, it might be difficult to manage software directly by using the package manager. Fortunately, there are wrappers for these tools that make it easier to install and uninstall software. For example, [yum](https://wikipedia.org/wiki/Yum_%28software%29) is a wrapper around RPM (Red Hat Package Manager) and [APT](https://wikipedia.org/wiki/APT_%28software%29) is a wrapper around DPKG (Debian Package Management System).\\\\nThis tutorial assumes that you\\\\u0027re running the [Ubuntu package management system](https://ubuntu.com/server/docs/package-management). This system is derived from the same system that\\\\u0027s used by the Debian GNU/Linux distribution. Therefore, we can use APT to install the software.\\\\nDepending on your Linux choice, you might have to use other tools. For example, you can use yum to install or uninstall software if you\\\\u0027re running Red Hat.\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-by-using-the-sudo-prefix\\u0022},{\\u0022title\\u0022:\\u0022Part 1.2 - Basic operations on Linux\\u0022,\\u0022content\\u0022:\\u0022# Part 1.2 - Basic operations on Linux\\\\n## Run commands as a superuser\\\\nThe superuser is the most privileged user account. It has root (unrestricted) access to all files and folders, and complete control over the operation of the computer. The system administrator uses this account for system maintenance.\\\\nOn UNIX-like systems, the conventional name of the superuser is \\\\u0022root\\\\u0022. The root user can do tasks that are otherwise restricted for standard users, including the following ones:\\\\n1. Changing system directories\\\\n2. Changing user privileges\\\\n3. Changing file ownership\\\\n4. System-related tasks such as restarts\\\\n5. Installation of certain applications\\\\nThere are several ways to run commands as the root account user. One is to use the \\\\u0060sudo su\\\\u0060 command. In this command, \\\\u0060sudo\\\\u0060 is short for \\\\u0060superuser do\\\\u0060, and \\\\u0060su\\\\u0060 is an abbreviation for \\\\u0022switch user.\\\\u0022 You can also use the \\\\u0060su\\\\u0060 command to switch between standard users.\\\\nImportant\\\\nAfter you become the root account user, everything you do runs in root context. Therefore, you should act carefully because the root account has access to the entire system.\\\\nTo demonstrate a permissions issue, create a text file in a special folder that\\\\u0027s named */etc*.\\\\nThe */etc/* folder is where system configuration files are located. This folder protects its contents from being changed by a standard user.\\\\nRun the following command:\\\\n\\\\u0060\\\\u0060\\\\u0060bash\\\\necho hello world \\\\u003E /etc/helloworld.txt\\\\n\\\\u0060\\\\u0060\\\\u0060\\\\nThe \\\\u0060echo\\\\u0060 command writes all the text that follows it to the output. The angle bracket (\\\\u0060\\\\u003E\\\\u0060) tells the system to send the output to the */etc/helloworld.txt* file instead of the console. This behavior is similar to how Windows works.\\\\nNote\\\\nLinux is case-sensitive: *Helloworld.txt*, *helloworld.txt* and *helloworld.Txt* are different files.\\\\nYou don\\\\u0027t have to provide a file name extension. The name \\\\u0022helloworld\\\\u0022 by itself is perfectly valid. (The \\\\u0022txt\\\\u0022 extension is used here only as an example.)\\\\nWhen you run this command, you receive a **permission denied** error message.\\\\n![Screenshot of permission denied message.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/permission-denied-message.png)\\\\nThe operation fails because the */etc* folder is special folder that a standard user can\\\\u0027t change. Verify the user by running the \\\\u0060id\\\\u0060 command.\\\\n![Screenshot of I D command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/id-command.png)\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-as-a-superuser\\u0022},{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\n## Step 1 - Choose a method to install Linux\\\\nThe choice of which method to use for installing Linux depends on your needs and preferences.\\\\n1. **New to using Linux?** We recommend starting with Windows Subsystem for Linux (WSL) as it\\\\u0027s the easiest way to get up and running.\\\\n2. **Work in a business environment with more complex needs related to scale or security?** We recommend running Linux as a Virtual Machine (VM) in the cloud and checking out the support that Azure has to offer. This also applies if you want to run Linux as a server.\\\\n3. **Only want to run Linux as your primary operating system?** If you are good with a slightly more complicated install process and don\\\\u0027t need access to Windows tools (like Outlook, Teams, Word, PowerPoint, etc), you can run Linux on bare metal to access the full potential of your hardware without any overhead from virtualization or emulation.\\\\nLearn more about these options below.\\\\n### Windows Subsystem for Linux (WSL)\\\\nAvailable to those with a Windows machine, this is the most simple way to install Linux. Just run the Linux install command: \\\\u0060wsl --install\\\\u0060 to install the Ubuntu distribution.\\\\nThis method uses virtualization to integrate the Windows operating system (OS) with the Linux OS (running on an actual Linux kernel). You can add as many different Linux distributions as you please and run your favorite Linux tools, including GUI apps, alongside your favorite Windows tools. You can also mix and match Bash and PowerShell commands in the same command line. A Linux file system hosts your project files, while a separate Windows file systems hosts any developer tools, such as VS Code, that you\\\\u0027d like to work on the files with. This method of running Linux is highly efficient and performant. Once you choose a Linux distribution or opt to use the default Ubuntu distro, follow the instructions to Use the install Linux command with Windows Subsystem for Linux or learn more in the [WSL documentation](https://learn.microsoft.com/en-us/windows/wsl).\\\\n![WSL install command screenshot](https://learn.microsoft.com/en-us/linux/images/wsl-install-command-screenshot.png)\\\\n### Create a Virtual Machine (VM)\\\\nAvailable to everyone regardless if you\\\\u0027re using a Windows or MacOS-based machine, but will require either an Azure account, if creating a VM in the cloud, or virtualization software, if creating a VM directly on your machine (locally).\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/linux/install#step-1---choose-a-method-to-install-linux\\u0022},{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\nLinux is an operating system, similar to Windows, but with many different versions due to the nature of being open source and fully customizable. To install Linux, you must choose an install method and choose a Linux distribution.\\\\nTo install Linux:\\\\n1. Choose an install method: Windows Subsystem for Linux (WSL), Bare metal Linux; or create a Virtual Machine (VM) to run Linux locally or in the cloud.\\\\n2. Choose a Linux distribution: Ubuntu, Debian, Kali Linux, openSUSE, etc.\\\\n3. Follow the steps for your preferred install method:\\\\n3.1. Use the install Linux command with Windows Subsystem for Linux (WSL)\\\\n3.2. Create a Linux Virtual Machine (VM) in the cloud\\\\n3.3. Create a Linux Virtual Machine (VM) on your local machine\\\\n3.4. Create a bootable USB to install bare-metal Linux\\\\n4. After installing Linux: Get familiar with your distribution\\\\u0027s package manager, update and upgrade the packages available, and get familiar with the other [Linux resources at Microsoft](https://learn.microsoft.com/en-us/linux/), such as training courses, Linux-versions of popular tools, news, and Open Source events.\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/linux/install\\u0022},{\\u0022title\\u0022:\\u0022Guide for changing your dev environment from Mac to Windows\\u0022,\\u0022content\\u0022:\\u0022# Guide for changing your dev environment from Mac to Windows\\\\n## Command-line shells and terminals\\\\nYou can now install Windows Subsystem for Linux (WSL) to support running a Linux shell within Windows. This means that you can run **bash**, with whichever specific Linux distribution you choose, integrated right inside Windows. Using WSL provides the kind of environment most familiar to Mac users. For example, you use **ls** to list the files in a current directory, not **dir** as you would with the traditional Windows Cmd Shell. To learn about installing and using WSL, see the [Windows Subsystem for Linux Installation Guide](https://learn.microsoft.com/en-us/windows/wsl/install). Linux distributions that you can install on Windows with WSL include:\\\\n1. [Ubuntu 20.04 LTS](https://www.microsoft.com/store/apps/9n6svws3rx71)\\\\n2. [Kali Linux](https://www.microsoft.com/store/apps/9PKR34TNCV07)\\\\n3. [Debian GNU/Linux](https://www.microsoft.com/store/apps/9MSVKQC78PK6)\\\\n4. [openSUSE Leap 15.1](https://www.microsoft.com/store/apps/9NJFZK00FGKV)\\\\n5. [SUSE Linux Enterprise Server 15 SP1](https://www.microsoft.com/store/apps/9PN498VPMF3Z)\\\\nJust to name a few. Find more in the [WSL install docs](https://learn.microsoft.com/en-us/windows/wsl/install-win10#install-your-linux-distribution-of-choice) and install them directly from the [Microsoft Store](https://aka.ms/wslstore).\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/windows/dev-environment/mac-to-windows#command-line-shells-and-terminals\\u0022},{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\n## Step 3 - Follow install method instructions\\\\n### Install Linux with Windows Subsystem for Linux\\\\nTo install Linux on a Windows PC, use the install Linux command.\\\\n1. Open PowerShell or Windows Command Prompt in *administrator mode* by right-clicking and selecting \\\\u0022Run as administrator\\\\u0022.\\\\n2. Enter the Linux install command: \\\\u0060wsl --install\\\\u0060.\\\\n3. Restart your machine.\\\\n![WSL Linux Install List Command screenshot](https://learn.microsoft.com/en-us/linux/images/wsl-install-list-screenshot.png)\\\\n#### Install Linux command options\\\\nBy default, the install command will use the Ubuntu distribution of Linux. You can see the other distributions that are available in the Microsoft Store by entering the command: \\\\u0060wsl -l -o\\\\u0060. If you\\\\u0027d like to install a distribution other than Ubuntu, just add \\\\u0060-d\\\\u0060 to the command with the name of the distribution you\\\\u0027d like to install:\\\\n\\\\u0060\\\\u0060\\\\u0060powershell\\\\nwsl --install -d \\\\u003CDistro Name\\\\u003E\\\\n\\\\u0060\\\\u0060\\\\u0060\\\\nIf the distribution you want to install isn\\\\u0027t available in the Microsoft Store, see the Windows Subsystem for Linux (WSL) documentation on how to [Import any Linux distribution to use with WSL](https://learn.microsoft.com/en-us/windows/wsl/use-custom-distro).\\\\nFor more information on how to use VS Code, Git, databases, Docker, GUI apps, GPU acceleration, NodesJS, USB devices, or mount a disk, build a custom distribution, manage disk space, or set up WSL for your enterprise business, see the [WSL documentation](https://learn.microsoft.com/en-us/windows/wsl/).\\\\n### Create a Linux Virtual Machine in the cloud\\\\nTo create a Linux Virtual Machine (VM) that is hosted in the cloud on Azure, log-in to the Azure Portal and select the \\\\u201CCreate a Virtual Machine\\\\u201D service. Then follow the instructions for creating a user name, project, instance, SSH key, ports, and copying the IP address.\\\\nAzure supports a variety of options for setting up Linux VMs in the cloud. For a quickstart guide on how to set up a Linux VM, choose your preferred method:\\\\n1. [Azure Portal](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-portal?tabs=ubuntu\\\\u0026source=docs)\\\\n2. [Azure CLI](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-cli)\\\\n3. [Azure PowerShell](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-powershell)\\\\n4. [Terraform](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-terraform)\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/linux/install#step-3---follow-install-method-instructions\\u0022},{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\n## Step 2 - Choose a Linux distribution\\\\nA large user community that consistently contributes to the distribution, fixing bugs, adding features, helping with questions, etc. is another good consideration. Ubuntu is one of the most popular distos. openSUSE has been around longer than most other distributions and offers in addition to the stable version (Leap) a rolling version (Tumbleweed). Many other distros rapidly grow popular as the needs and preferences of the user community change. The Linux.org site has resources to help you assess what\\\\u0027s available, well-supported, or growing in popularity.\\\\n![Screenshot of the list of distributions on Linux.org](https://learn.microsoft.com/en-us/linux/images/linux-distributions-list-screenshot.png)\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/linux/install#step-2---choose-a-linux-distribution\\u0022},{\\u0022title\\u0022:\\u0022Part 1.2 - Basic operations on Linux\\u0022,\\u0022content\\u0022:\\u0022# Part 1.2 - Basic operations on Linux\\\\n## Run commands as a superuser\\\\nTo become the root account user, run the \\\\u0060sudo su\\\\u0060 command.\\\\n![Screenshot of sudo su command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/sudo-su-command.png)\\\\nYou should notice two things: When you become root, the dollar sign character (\\\\u0060$\\\\u0060) becomes a pound sign character (\\\\u0060#\\\\u0060). Also, the username is changed to **root**. If you run the same \\\\u0060id\\\\u0060 command again, you see that the user and group ID of root are **0**.\\\\n![Screenshot of root command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/root-command.png)\\\\nImportant\\\\nNow you have elevated yourself to the \\\\u0022superuser\\\\u0022 role in your session, you have complete access to the system. Keep in mind that this is a potentially dangerous situation, and you should exercise caution as you continue.\\\\nNow, run the same command one more time:\\\\n\\\\u0060\\\\u0060\\\\u0060bash\\\\necho hello world \\\\u003E /etc/helloworld.txt\\\\n\\\\u0060\\\\u0060\\\\u0060\\\\nThis time, you don\\\\u0027t receive any error message. To verify if the file is created, run the following command:\\\\n\\\\u0060\\\\u0060\\\\u0060bash\\\\nll /etc/hello*\\\\n\\\\u0060\\\\u0060\\\\u0060\\\\nThe \\\\u0060ll\\\\u0060 command lists the files and subfolders that are in a specified folder. In this example, the \\\\u0060/etc/hello*\\\\u0060 parameter value limits the command output to the files or folders whose name begins in \\\\u0060hello\\\\u0060.\\\\n![Screenshot of hello command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/hello-command.png)\\\\nThe output shows that the file was created. What if we want to examine the content of the file? The \\\\u0060cat\\\\u0060 command helps here. To see the content, run \\\\u0060cat /etc/helloworld.txt\\\\u0060.\\\\n![Screenshot of cat command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/cat-command.png)\\\\nThe \\\\u0060cat\\\\u0060 command reads files sequentially, and writes their content to the standard output. Therefore, it writes \\\\u0022hello world\\\\u0022 in the console.\\\\nTo prevent mistakes that could damage the system while you\\\\u0027re elevated to root status, it\\\\u0027s a good practice to exit the root session and return to your standard user session to avoid some dangerous operations. To do this, run \\\\u0060exit\\\\u0060. You can see that the pound sign reverts to a dollar sign, and the username is displayed as your standard user.\\\\n![Screenshot of exit command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/exit-command.png)\\\\nFor additional practice, run the same \\\\u0060ll\\\\u0060 and \\\\u0060cat\\\\u0060 commands within your standard user context. You\\\\u0027ll see that you can\\\\u0027t create the file in the */etc/* folder, but can you list the file and read the contents.\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-as-a-superuser\\u0022}]\"}]},\"id\":\"c4d77709f5314c9e9f097f08dd756438\",\"jsonrpc\":\"2.0\"}\n",
+       "data: {\"result\":{\"tools\":[{\"name\":\"microsoft_docs_search\",\"title\":\"Microsoft Docs Search\",\"description\":\"Search official Microsoft/Azure documentation to find the most relevant and trustworthy content for a user\\u0027s query. This tool returns up to 10 high-quality content chunks (each max 500 tokens), extracted from Microsoft Learn and other official sources. Each result includes the article title, URL, and a self-contained content excerpt optimized for fast retrieval and reasoning. Always use this tool to quickly ground your answers in accurate, first-party Microsoft/Azure knowledge.\\n\\n## Follow-up Pattern\\nTo ensure completeness, use microsoft_docs_fetch when high-value pages are identified by search. The fetch tool complements search by providing the full detail. This is a required step for comprehensive results.\",\"inputSchema\":{\"type\":\"object\",\"properties\":{\"query\":{\"description\":\"a query or topic about Microsoft/Azure products, services, platforms, developer tools, frameworks, or APIs\",\"type\":\"string\",\"default\":null}}},\"outputSchema\":{\"type\":\"object\",\"properties\":{\"results\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":[\"string\",\"null\"]},\"title\":{\"type\":\"string\"},\"content\":{\"type\":[\"string\",\"null\"]},\"contentUrl\":{\"type\":\"string\"},\"content_omitted\":{\"type\":\"boolean\"},\"extensionData\":{\"type\":[\"object\",\"null\"]}}}}}},\"annotations\":{\"title\":\"Microsoft Docs Search\",\"destructiveHint\":false,\"idempotentHint\":true,\"readOnlyHint\":true}},{\"name\":\"microsoft_code_sample_search\",\"title\":\"Microsoft Code Sample Search\",\"description\":\"Search for code snippets and examples in official Microsoft Learn documentation. This tool retrieves relevant code samples from Microsoft documentation pages providing developers with practical implementation examples and best practices for Microsoft/Azure products and services related coding tasks. This tool will help you use the **LATEST OFFICIAL** code snippets to empower coding capabilities.\\n\\n## When to Use This Tool\\n- When you are going to provide sample Microsoft/Azure related code snippets in your answers.\\n- When you are **generating any Microsoft/Azure related code**.\\n\\n## Usage Pattern\\nInput a descriptive query, or SDK/class/method name to retrieve related code samples. The optional parameter \\u0060language\\u0060 can help to filter results.\\n\\nEligible values for \\u0060language\\u0060 parameter include: csharp javascript typescript python powershell azurecli al sql java kusto cpp go rust ruby php\",\"inputSchema\":{\"type\":\"object\",\"properties\":{\"query\":{\"description\":\"a descriptive query, SDK name, method name or code snippet related to Microsoft/Azure products, services, platforms, developer tools, frameworks, APIs or SDKs\",\"type\":\"string\"},\"language\":{\"description\":\"Optional parameter specifying the programming language of code snippets to retrieve. Can significantly improve search quality if provided. Eligible values: csharp javascript typescript python powershell azurecli al sql java kusto cpp go rust ruby php\",\"type\":\"string\",\"default\":null}},\"required\":[\"query\"]},\"outputSchema\":{\"type\":\"object\",\"properties\":{\"results\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"description\":{\"type\":\"string\"},\"codeSnippet\":{\"type\":[\"string\",\"null\"]},\"link\":{\"type\":\"string\"},\"language\":{\"type\":\"string\"},\"content_omitted\":{\"type\":\"boolean\"}}}}}},\"annotations\":{\"title\":\"Microsoft Code Sample Search\",\"destructiveHint\":false,\"idempotentHint\":true,\"readOnlyHint\":true}},{\"name\":\"microsoft_docs_fetch\",\"title\":\"Microsoft Docs Fetch\",\"description\":\"Fetch and convert a Microsoft Learn documentation webpage to markdown format. This tool retrieves the latest complete content of Microsoft documentation webpages including Azure, .NET, Microsoft 365, and other Microsoft technologies.\\n\\n## When to Use This Tool\\n- When search results provide incomplete information or truncated content\\n- When you need complete step-by-step procedures or tutorials\\n- When you need troubleshooting sections, prerequisites, or detailed explanations\\n- When search results reference a specific page that seems highly relevant\\n- For comprehensive guides that require full context\\n\\n## Usage Pattern\\nUse this tool AFTER microsoft_docs_search when you identify specific high-value pages that need complete content. The search tool gives you an overview; this tool gives you the complete picture.\\n\\n## URL Requirements\\n- The URL must be a valid HTML documentation webpage from the microsoft.com domain\\n- Binary files (PDF, DOCX, images, etc.) are not supported\\n\\n## Output Format\\nmarkdown with headings, code blocks, tables, and links preserved.\",\"inputSchema\":{\"type\":\"object\",\"properties\":{\"url\":{\"description\":\"URL of the Microsoft documentation page to read\",\"type\":\"string\"}},\"required\":[\"url\"]},\"annotations\":{\"title\":\"Microsoft Docs Fetch\",\"destructiveHint\":false,\"idempotentHint\":true,\"readOnlyHint\":true}}]},\"id\":\"b9c6596da2514e04b08cbaa1defb300b\",\"jsonrpc\":\"2.0\"}\n",
        "\n",
        "\n",
        "BODY END
" @@ -650,11 +492,224 @@ "name": "stdout", "output_type": "stream", "text": [ - "TextContent(text=[{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\n## Step 2 - Choose a Linux distribution\\nThe different versions of Linux are called \\u0022distributions\\u0022 (sometimes shortened to \\u201Cdistros\\u201D). So there isn\\u2019t actually a way to just \\u201Cinstall Linux\\u201D, you first need to choose which distribution that uses the Linux kernel you want to download and install. There are over 600 active Linux distributions, such as Ubuntu, Debian, Kali Linux, openSUSE, and more. There are a number of factors to consider in choosing a distribution, such as:\\n### Required Linux experience\\nIf you\\u2019re new to Linux, you may want to start with Ubuntu. This is one of the most common starting places for Linux users due to the level of support and ease of use. Ubuntu offers a desktop-version and a server-version depending on how you plan to use it. The specific tasks that the operating system will be responsible for may help you determine which distribution to use. Arch Linux is a popular choice for those who want a highly customizable, do-it-yourself approach that is still stable and well-supported by a large user base. It is a much more complicated place to start, but can help you to get a better understanding of how Linux works due to the amount of custom configuration.\\n### System requirements\\nA minimal, or \\u0022lightweight\\u0022, distribution means a small footprint, with lower memory and processing speed requirements. Alpine Linux is a popular option where users will find most features disabled or not installed by default, but still has everything you need for a containerized app. Security needs: Some Linux users may have specific security concerns to consider. Kali Linux is an example of a distro designed for security tasks, such as Penetration Testing, Security Research, Computer Forensics and Reverse Engineering.\\n### Business and Enterprise requirements\\nBusiness, corporate, or academic environments may have unique concerns from consumer environments. For example Red Hat Enterprise Linux (RHEL), SUSE Linux Enterprise Server (SLES) and Oracle Linux offer 24/7 global support and subscription-based service. CentOS is another popular enterprise distro that is community-supported but still compatible with RHEL.\\n### Community support\",\"contentUrl\":\"https://learn.microsoft.com/en-us/linux/install#step-2---choose-a-linux-distribution\"},{\"title\":\"New to Linux resources for SQL users\",\"content\":\"# New to Linux resources for SQL users\\n**Applies to:**[SQL Server](https://learn.microsoft.com/en-us/sql/sql-server/sql-docs-navigation-guide?view=sql-server-ver17#applies-to) - Linux\\nThis article is intended to provide a learning path through an introduction of Linux concepts. Use the sections in the article as a guided learning path to familiarize yourself with the Linux environment.\\nThis isn\\u0027t intended to be a comprehensive list but instead provides the bare minimum you need to help you administer and navigate your SQL Server on Linux environment. See the [full list of tutorials](https://www.linux.org/forums/linux-beginner-tutorials.123/) to learn more.\\n## What is Linux?\\nThe [What is Linux](https://www.linux.org/threads/what-is-linux.4106/) module introduces the history of Linux as an operating system. The module explains *the Kernel* and where Linux is today. This tutorial serves as an introduction to Linux, helping you get started.\\n## Select a distribution\\nAfter you know the history of Linux, decide which [distribution of Linux](https://www.linux.org/threads/selecting-a-linux-distribution.4117/) best suits your business needs. [SQL Server is supported](https://learn.microsoft.com/en-us/sql/linux/sql-server-linux-release-notes-2019?view=sql-server-ver17#supported-platforms) on various distributions such as Red Hat Enterprise Linux (RHEL), SUSE Linux Enterprise Server (SLES), and Ubuntu.\\n## Get around directories\\nAfter you have chosen the appropriate distribution of Linux, familiarize yourself with [getting around Linux directories](https://www.linux.org/threads/getting-around-in-linux-directories.4120/).\\nThis module will help you answer the following questions:\\n1. How to navigate between different files\\n2. How to identify a file from a directory\\n3. Identify different directories\\n## Install new software\\nNext, learn how to [install new software](https://www.linux.org/threads/installing-new-software-debian-red-hat-slackware.4119/) on your new Linux operating system. This module details installing new software to the Debian and Red Hat Linux operating systems.\",\"contentUrl\":\"https://learn.microsoft.com/en-us/sql/linux/new-to-linux-resources?view=sql-server-ver17\"},{\"title\":\"Part 1.2 - Basic operations on Linux\",\"content\":\"# Part 1.2 - Basic operations on Linux\\n## Run commands by using the \\u0022sudo\\u0022 prefix\\nInstead of becoming the root account user in your session, you can run commands as root by using the \\u0060sudo \\u003Ccommand\\u003E\\u0060 format. This approach is safer because it will run the given command only as a superuser.\\nFor example, if you run \\u0060restart\\u0060 while you\\u0027re not elevated to superuser status, the result is as follows.\\n![Screenshot of restart command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/restart-command.png)\\nTo run this command as a superuser instead, you can either change the session context to root (by running \\u0060sudo su\\u0060) or add the \\u0060sudo\\u0060 prefix, as follows:\\n\\u0060\\u0060\\u0060bash\\nsudo reboot\\n\\u0060\\u0060\\u0060\\nNow, the restart operation runs as expected by the virtual machine.\\n![Screenshot of reboot command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/reboot-command.png)\\n## Package managers\\nPackage managers are used to install, upgrade, and remove applications in Linux. There are several different package managers, such as DPKG (Debian Package Management System), RPM (Red Hat Package Manager), and Zypper for SUSE Package managers themselves are low-level. Therefore, it might be difficult to manage software directly by using the package manager. Fortunately, there are wrappers for these tools that make it easier to install and uninstall software. For example, [yum](https://wikipedia.org/wiki/Yum_%28software%29) is a wrapper around RPM (Red Hat Package Manager) and [APT](https://wikipedia.org/wiki/APT_%28software%29) is a wrapper around DPKG (Debian Package Management System).\\nThis tutorial assumes that you\\u0027re running the [Ubuntu package management system](https://ubuntu.com/server/docs/package-management). This system is derived from the same system that\\u0027s used by the Debian GNU/Linux distribution. Therefore, we can use APT to install the software.\\nDepending on your Linux choice, you might have to use other tools. For example, you can use yum to install or uninstall software if you\\u0027re running Red Hat.\",\"contentUrl\":\"https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-by-using-the-sudo-prefix\"},{\"title\":\"Part 1.2 - Basic operations on Linux\",\"content\":\"# Part 1.2 - Basic operations on Linux\\n## Run commands as a superuser\\nThe superuser is the most privileged user account. It has root (unrestricted) access to all files and folders, and complete control over the operation of the computer. The system administrator uses this account for system maintenance.\\nOn UNIX-like systems, the conventional name of the superuser is \\u0022root\\u0022. The root user can do tasks that are otherwise restricted for standard users, including the following ones:\\n1. Changing system directories\\n2. Changing user privileges\\n3. Changing file ownership\\n4. System-related tasks such as restarts\\n5. Installation of certain applications\\nThere are several ways to run commands as the root account user. One is to use the \\u0060sudo su\\u0060 command. In this command, \\u0060sudo\\u0060 is short for \\u0060superuser do\\u0060, and \\u0060su\\u0060 is an abbreviation for \\u0022switch user.\\u0022 You can also use the \\u0060su\\u0060 command to switch between standard users.\\nImportant\\nAfter you become the root account user, everything you do runs in root context. Therefore, you should act carefully because the root account has access to the entire system.\\nTo demonstrate a permissions issue, create a text file in a special folder that\\u0027s named */etc*.\\nThe */etc/* folder is where system configuration files are located. This folder protects its contents from being changed by a standard user.\\nRun the following command:\\n\\u0060\\u0060\\u0060bash\\necho hello world \\u003E /etc/helloworld.txt\\n\\u0060\\u0060\\u0060\\nThe \\u0060echo\\u0060 command writes all the text that follows it to the output. The angle bracket (\\u0060\\u003E\\u0060) tells the system to send the output to the */etc/helloworld.txt* file instead of the console. This behavior is similar to how Windows works.\\nNote\\nLinux is case-sensitive: *Helloworld.txt*, *helloworld.txt* and *helloworld.Txt* are different files.\\nYou don\\u0027t have to provide a file name extension. The name \\u0022helloworld\\u0022 by itself is perfectly valid. (The \\u0022txt\\u0022 extension is used here only as an example.)\\nWhen you run this command, you receive a **permission denied** error message.\\n![Screenshot of permission denied message.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/permission-denied-message.png)\\nThe operation fails because the */etc* folder is special folder that a standard user can\\u0027t change. Verify the user by running the \\u0060id\\u0060 command.\\n![Screenshot of I D command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/id-command.png)\",\"contentUrl\":\"https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-as-a-superuser\"},{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\n## Step 1 - Choose a method to install Linux\\nThe choice of which method to use for installing Linux depends on your needs and preferences.\\n1. **New to using Linux?** We recommend starting with Windows Subsystem for Linux (WSL) as it\\u0027s the easiest way to get up and running.\\n2. **Work in a business environment with more complex needs related to scale or security?** We recommend running Linux as a Virtual Machine (VM) in the cloud and checking out the support that Azure has to offer. This also applies if you want to run Linux as a server.\\n3. **Only want to run Linux as your primary operating system?** If you are good with a slightly more complicated install process and don\\u0027t need access to Windows tools (like Outlook, Teams, Word, PowerPoint, etc), you can run Linux on bare metal to access the full potential of your hardware without any overhead from virtualization or emulation.\\nLearn more about these options below.\\n### Windows Subsystem for Linux (WSL)\\nAvailable to those with a Windows machine, this is the most simple way to install Linux. Just run the Linux install command: \\u0060wsl --install\\u0060 to install the Ubuntu distribution.\\nThis method uses virtualization to integrate the Windows operating system (OS) with the Linux OS (running on an actual Linux kernel). You can add as many different Linux distributions as you please and run your favorite Linux tools, including GUI apps, alongside your favorite Windows tools. You can also mix and match Bash and PowerShell commands in the same command line. A Linux file system hosts your project files, while a separate Windows file systems hosts any developer tools, such as VS Code, that you\\u0027d like to work on the files with. This method of running Linux is highly efficient and performant. Once you choose a Linux distribution or opt to use the default Ubuntu distro, follow the instructions to Use the install Linux command with Windows Subsystem for Linux or learn more in the [WSL documentation](https://learn.microsoft.com/en-us/windows/wsl).\\n![WSL install command screenshot](https://learn.microsoft.com/en-us/linux/images/wsl-install-command-screenshot.png)\\n### Create a Virtual Machine (VM)\\nAvailable to everyone regardless if you\\u0027re using a Windows or MacOS-based machine, but will require either an Azure account, if creating a VM in the cloud, or virtualization software, if creating a VM directly on your machine (locally).\",\"contentUrl\":\"https://learn.microsoft.com/en-us/linux/install#step-1---choose-a-method-to-install-linux\"},{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\nLinux is an operating system, similar to Windows, but with many different versions due to the nature of being open source and fully customizable. To install Linux, you must choose an install method and choose a Linux distribution.\\nTo install Linux:\\n1. Choose an install method: Windows Subsystem for Linux (WSL), Bare metal Linux; or create a Virtual Machine (VM) to run Linux locally or in the cloud.\\n2. Choose a Linux distribution: Ubuntu, Debian, Kali Linux, openSUSE, etc.\\n3. Follow the steps for your preferred install method:\\n3.1. Use the install Linux command with Windows Subsystem for Linux (WSL)\\n3.2. Create a Linux Virtual Machine (VM) in the cloud\\n3.3. Create a Linux Virtual Machine (VM) on your local machine\\n3.4. Create a bootable USB to install bare-metal Linux\\n4. After installing Linux: Get familiar with your distribution\\u0027s package manager, update and upgrade the packages available, and get familiar with the other [Linux resources at Microsoft](https://learn.microsoft.com/en-us/linux/), such as training courses, Linux-versions of popular tools, news, and Open Source events.\",\"contentUrl\":\"https://learn.microsoft.com/en-us/linux/install\"},{\"title\":\"Guide for changing your dev environment from Mac to Windows\",\"content\":\"# Guide for changing your dev environment from Mac to Windows\\n## Command-line shells and terminals\\nYou can now install Windows Subsystem for Linux (WSL) to support running a Linux shell within Windows. This means that you can run **bash**, with whichever specific Linux distribution you choose, integrated right inside Windows. Using WSL provides the kind of environment most familiar to Mac users. For example, you use **ls** to list the files in a current directory, not **dir** as you would with the traditional Windows Cmd Shell. To learn about installing and using WSL, see the [Windows Subsystem for Linux Installation Guide](https://learn.microsoft.com/en-us/windows/wsl/install). Linux distributions that you can install on Windows with WSL include:\\n1. [Ubuntu 20.04 LTS](https://www.microsoft.com/store/apps/9n6svws3rx71)\\n2. [Kali Linux](https://www.microsoft.com/store/apps/9PKR34TNCV07)\\n3. [Debian GNU/Linux](https://www.microsoft.com/store/apps/9MSVKQC78PK6)\\n4. [openSUSE Leap 15.1](https://www.microsoft.com/store/apps/9NJFZK00FGKV)\\n5. [SUSE Linux Enterprise Server 15 SP1](https://www.microsoft.com/store/apps/9PN498VPMF3Z)\\nJust to name a few. Find more in the [WSL install docs](https://learn.microsoft.com/en-us/windows/wsl/install-win10#install-your-linux-distribution-of-choice) and install them directly from the [Microsoft Store](https://aka.ms/wslstore).\",\"contentUrl\":\"https://learn.microsoft.com/en-us/windows/dev-environment/mac-to-windows#command-line-shells-and-terminals\"},{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\n## Step 3 - Follow install method instructions\\n### Install Linux with Windows Subsystem for Linux\\nTo install Linux on a Windows PC, use the install Linux command.\\n1. Open PowerShell or Windows Command Prompt in *administrator mode* by right-clicking and selecting \\u0022Run as administrator\\u0022.\\n2. Enter the Linux install command: \\u0060wsl --install\\u0060.\\n3. Restart your machine.\\n![WSL Linux Install List Command screenshot](https://learn.microsoft.com/en-us/linux/images/wsl-install-list-screenshot.png)\\n#### Install Linux command options\\nBy default, the install command will use the Ubuntu distribution of Linux. You can see the other distributions that are available in the Microsoft Store by entering the command: \\u0060wsl -l -o\\u0060. If you\\u0027d like to install a distribution other than Ubuntu, just add \\u0060-d\\u0060 to the command with the name of the distribution you\\u0027d like to install:\\n\\u0060\\u0060\\u0060powershell\\nwsl --install -d \\u003CDistro Name\\u003E\\n\\u0060\\u0060\\u0060\\nIf the distribution you want to install isn\\u0027t available in the Microsoft Store, see the Windows Subsystem for Linux (WSL) documentation on how to [Import any Linux distribution to use with WSL](https://learn.microsoft.com/en-us/windows/wsl/use-custom-distro).\\nFor more information on how to use VS Code, Git, databases, Docker, GUI apps, GPU acceleration, NodesJS, USB devices, or mount a disk, build a custom distribution, manage disk space, or set up WSL for your enterprise business, see the [WSL documentation](https://learn.microsoft.com/en-us/windows/wsl/).\\n### Create a Linux Virtual Machine in the cloud\\nTo create a Linux Virtual Machine (VM) that is hosted in the cloud on Azure, log-in to the Azure Portal and select the \\u201CCreate a Virtual Machine\\u201D service. Then follow the instructions for creating a user name, project, instance, SSH key, ports, and copying the IP address.\\nAzure supports a variety of options for setting up Linux VMs in the cloud. For a quickstart guide on how to set up a Linux VM, choose your preferred method:\\n1. [Azure Portal](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-portal?tabs=ubuntu\\u0026source=docs)\\n2. [Azure CLI](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-cli)\\n3. [Azure PowerShell](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-powershell)\\n4. [Terraform](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-terraform)\",\"contentUrl\":\"https://learn.microsoft.com/en-us/linux/install#step-3---follow-install-method-instructions\"},{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\n## Step 2 - Choose a Linux distribution\\nA large user community that consistently contributes to the distribution, fixing bugs, adding features, helping with questions, etc. is another good consideration. Ubuntu is one of the most popular distos. openSUSE has been around longer than most other distributions and offers in addition to the stable version (Leap) a rolling version (Tumbleweed). Many other distros rapidly grow popular as the needs and preferences of the user community change. The Linux.org site has resources to help you assess what\\u0027s available, well-supported, or growing in popularity.\\n![Screenshot of the list of distributions on Linux.org](https://learn.microsoft.com/en-us/linux/images/linux-distributions-list-screenshot.png)\",\"contentUrl\":\"https://learn.microsoft.com/en-us/linux/install#step-2---choose-a-linux-distribution\"},{\"title\":\"Part 1.2 - Basic operations on Linux\",\"content\":\"# Part 1.2 - Basic operations on Linux\\n## Run commands as a superuser\\nTo become the root account user, run the \\u0060sudo su\\u0060 command.\\n![Screenshot of sudo su command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/sudo-su-command.png)\\nYou should notice two things: When you become root, the dollar sign character (\\u0060$\\u0060) becomes a pound sign character (\\u0060#\\u0060). Also, the username is changed to **root**. If you run the same \\u0060id\\u0060 command again, you see that the user and group ID of root are **0**.\\n![Screenshot of root command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/root-command.png)\\nImportant\\nNow you have elevated yourself to the \\u0022superuser\\u0022 role in your session, you have complete access to the system. Keep in mind that this is a potentially dangerous situation, and you should exercise caution as you continue.\\nNow, run the same command one more time:\\n\\u0060\\u0060\\u0060bash\\necho hello world \\u003E /etc/helloworld.txt\\n\\u0060\\u0060\\u0060\\nThis time, you don\\u0027t receive any error message. To verify if the file is created, run the following command:\\n\\u0060\\u0060\\u0060bash\\nll /etc/hello*\\n\\u0060\\u0060\\u0060\\nThe \\u0060ll\\u0060 command lists the files and subfolders that are in a specified folder. In this example, the \\u0060/etc/hello*\\u0060 parameter value limits the command output to the files or folders whose name begins in \\u0060hello\\u0060.\\n![Screenshot of hello command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/hello-command.png)\\nThe output shows that the file was created. What if we want to examine the content of the file? The \\u0060cat\\u0060 command helps here. To see the content, run \\u0060cat /etc/helloworld.txt\\u0060.\\n![Screenshot of cat command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/cat-command.png)\\nThe \\u0060cat\\u0060 command reads files sequentially, and writes their content to the standard output. Therefore, it writes \\u0022hello world\\u0022 in the console.\\nTo prevent mistakes that could damage the system while you\\u0027re elevated to root status, it\\u0027s a good practice to exit the root session and return to your standard user session to avoid some dangerous operations. To do this, run \\u0060exit\\u0060. You can see that the pound sign reverts to a dollar sign, and the username is displayed as your standard user.\\n![Screenshot of exit command.](https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/exit-command.png)\\nFor additional practice, run the same \\u0060ll\\u0060 and \\u0060cat\\u0060 commands within your standard user context. You\\u0027ll see that you can\\u0027t create the file in the */etc/* folder, but can you list the file and read the contents.\",\"contentUrl\":\"https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-as-a-superuser\"}], annotations=null, meta=null)\n" + "🔨Title: Microsoft Docs Search (microsoft_docs_search)\n", + "\n", + "Description: Search official Microsoft/Azure documentation to find the most relevant and trustworthy content for a user's query. This tool returns up to 10 high-quality content chunks (each max 500 tokens), extracted from Microsoft Learn and other official sources. Each result includes the article title, URL, and a self-contained content excerpt optimized for fast retrieval and reasoning. Always use this tool to quickly ground your answers in accurate, first-party Microsoft/Azure knowledge.\n", + "\n", + "## Follow-up Pattern\n", + "To ensure completeness, use microsoft_docs_fetch when high-value pages are identified by search. The fetch tool complements search by providing the full detail. This is a required step for comprehensive results.\n", + "\n", + "Input schema: ToolSchema(properties={\"query\":{\"description\":\"a query or topic about Microsoft/Azure products, services, platforms, developer tools, frameworks, or APIs\",\"type\":\"string\",\"default\":null}}, required=null, defs=null)\n", + "Output schema: ToolSchema(properties={\"results\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":[\"string\",\"null\"]},\"title\":{\"type\":\"string\"},\"content\":{\"type\":[\"string\",\"null\"]},\"contentUrl\":{\"type\":\"string\"},\"content_omitted\":{\"type\":\"boolean\"},\"extensionData\":{\"type\":[\"object\",\"null\"]}}}}}, required=null, defs=null)\n", + "Icons: null\n", + "Annotations: ToolAnnotations(title=Microsoft Docs Search, readOnlyHint=true, destructiveHint=false, idempotentHint=true, openWorldHint=null)\n", + "Execution: null\n", + "\n", + "🔨Title: Microsoft Code Sample Search (microsoft_code_sample_search)\n", + "\n", + "Description: Search for code snippets and examples in official Microsoft Learn documentation. This tool retrieves relevant code samples from Microsoft documentation pages providing developers with practical implementation examples and best practices for Microsoft/Azure products and services related coding tasks. This tool will help you use the **LATEST OFFICIAL** code snippets to empower coding capabilities.\n", + "\n", + "## When to Use This Tool\n", + "- When you are going to provide sample Microsoft/Azure related code snippets in your answers.\n", + "- When you are **generating any Microsoft/Azure related code**.\n", + "\n", + "## Usage Pattern\n", + "Input a descriptive query, or SDK/class/method name to retrieve related code samples. The optional parameter `language` can help to filter results.\n", + "\n", + "Eligible values for `language` parameter include: csharp javascript typescript python powershell azurecli al sql java kusto cpp go rust ruby php\n", + "\n", + "Input schema: ToolSchema(properties={\"query\":{\"description\":\"a descriptive query, SDK name, method name or code snippet related to Microsoft/Azure products, services, platforms, developer tools, frameworks, APIs or SDKs\",\"type\":\"string\"},\"language\":{\"description\":\"Optional parameter specifying the programming language of code snippets to retrieve. Can significantly improve search quality if provided. Eligible values: csharp javascript typescript python powershell azurecli al sql java kusto cpp go rust ruby php\",\"type\":\"string\",\"default\":null}}, required=[query], defs=null)\n", + "Output schema: ToolSchema(properties={\"results\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"description\":{\"type\":\"string\"},\"codeSnippet\":{\"type\":[\"string\",\"null\"]},\"link\":{\"type\":\"string\"},\"language\":{\"type\":\"string\"},\"content_omitted\":{\"type\":\"boolean\"}}}}}, required=null, defs=null)\n", + "Icons: null\n", + "Annotations: ToolAnnotations(title=Microsoft Code Sample Search, readOnlyHint=true, destructiveHint=false, idempotentHint=true, openWorldHint=null)\n", + "Execution: null\n", + "\n", + "🔨Title: Microsoft Docs Fetch (microsoft_docs_fetch)\n", + "\n", + "Description: Fetch and convert a Microsoft Learn documentation webpage to markdown format. This tool retrieves the latest complete content of Microsoft documentation webpages including Azure, .NET, Microsoft 365, and other Microsoft technologies.\n", + "\n", + "## When to Use This Tool\n", + "- When search results provide incomplete information or truncated content\n", + "- When you need complete step-by-step procedures or tutorials\n", + "- When you need troubleshooting sections, prerequisites, or detailed explanations\n", + "- When search results reference a specific page that seems highly relevant\n", + "- For comprehensive guides that require full context\n", + "\n", + "## Usage Pattern\n", + "Use this tool AFTER microsoft_docs_search when you identify specific high-value pages that need complete content. The search tool gives you an overview; this tool gives you the complete picture.\n", + "\n", + "## URL Requirements\n", + "- The URL must be a valid HTML documentation webpage from the microsoft.com domain\n", + "- Binary files (PDF, DOCX, images, etc.) are not supported\n", + "\n", + "## Output Format\n", + "markdown with headings, code blocks, tables, and links preserved.\n", + "\n", + "Input schema: ToolSchema(properties={\"url\":{\"description\":\"URL of the Microsoft documentation page to read\",\"type\":\"string\"}}, required=[url], defs=null)\n", + "Output schema: null\n", + "Icons: null\n", + "Annotations: ToolAnnotations(title=Microsoft Docs Fetch, readOnlyHint=true, destructiveHint=false, idempotentHint=true, openWorldHint=null)\n", + "Execution: null\n", + "\n" ] } ], - "execution_count": 16 + "execution_count": 13 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Let's call a tool:" + }, + { + "metadata": { + "executionRelatedData": { + "compiledClasses": [ + "Line_17_jupyter", + "Line_25_jupyter" + ] + }, + "ExecuteTime": { + "end_time": "2026-03-30T10:42:21.227094Z", + "start_time": "2026-03-30T10:42:20.419561Z" + } + }, + "cell_type": "code", + "source": [ + "runBlocking {\n", + " val toolCallResult = mcpClient.callTool(\n", + " name = \"microsoft_docs_search\",\n", + " arguments = mapOf(\"query\" to \"Linux\")\n", + " )\n", + " for (content in toolCallResult.content) {\n", + " (content as? TextContent).let {\n", + " Json.parseToJsonElement(it?.text.orEmpty()).jsonObject.get(\"results\")?.jsonArray?.forEach { result ->\n", + " val item = result as JsonObject\n", + " println(\n", + " \"\"\"\n", + " |Title: ${item[\"title\"]}\n", + " |\n", + " |Content: ${item[\"content\"]}\n", + " |Content URL: ${item[\"contentUrl\"]}\n", + " \"\"\".trimMargin()\n", + " )\n", + " }\n", + " }\n", + " }\n", + "}\n" + ], + "outputs": [ + { + "data": { + "text/html": [ + "
HTTP Request
REQUEST: https://learn.microsoft.com/api/mcp\n",
+       "METHOD: POST\n",
+       "COMMON HEADERS\n",
+       "-> Accept: application/json, text/event-stream\n",
+       "-> Accept-Charset: UTF-8\n",
+       "-> mcp-session-id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIiwiaWNvbnMiOm51bGwsIndlYnNpdGVVcmwiOm51bGx9LCJ1c2VySWRDbGFpbSI6IjQ4NjFlZWMxLWRlNGUtNGZhMi1hMjA4LWJkNzNhNDdkYmE3OCJ9\n",
+       "CONTENT HEADERS\n",
+       "-> Content-Length: 162\n",
+       "-> Content-Type: application/json\n",
+       "BODY Content-Type: application/json\n",
+       "BODY START\n",
+       "{\"id\":\"07e26d19ee6248d98f6dc039bf4fc589\",\"method\":\"tools/call\",\"params\":{\"name\":\"microsoft_docs_search\",\"arguments\":{\"query\":\"Linux\"},\"_meta\":{}},\"jsonrpc\":\"2.0\"}\n",
+       "BODY END
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "data": { + "text/html": [ + "
HTTP Response
RESPONSE: 200 OK\n",
+       "METHOD: POST\n",
+       "FROM: https://learn.microsoft.com/api/mcp\n",
+       "COMMON HEADERS\n",
+       "-> Akamai-Cache-Status: Miss from child, NotCacheable from parent\n",
+       "-> Cache-Control: no-cache, no-store\n",
+       "-> Connection: keep-alive\n",
+       "-> Content-Encoding: identity\n",
+       "-> Content-Length: 49801\n",
+       "-> Content-Type: text/event-stream\n",
+       "-> Date: Mon, 30 Mar 2026 10:42:21 GMT\n",
+       "-> Expires: Mon, 30 Mar 2026 10:42:21 GMT\n",
+       "-> Request-Context: appId=cid-v1:ccd19013-8f20-4877-8d3f-84a0107e7119\n",
+       "-> Set-Cookie: ASLBSA=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; Path=/; Secure; HttpOnly;; ASLBSACORS=0003562b174eb06eed9ae0ec705e26d2c9aec6332388d120314730a5c2da183d42c8; SameSite=none; Path=/; Secure; HttpOnly;\n",
+       "-> Strict-Transport-Security: max-age=31536000; includeSubDomains; preload\n",
+       "-> X-Content-Type-Options: nosniff\n",
+       "-> X-Powered-By: ASP.NET\n",
+       "-> mcp-session-id: eyJjbGllbnRJbmZvIjp7Im5hbWUiOiJteS1jbGllbnQiLCJ0aXRsZSI6bnVsbCwidmVyc2lvbiI6IjEuMC4wIiwiaWNvbnMiOm51bGwsIndlYnNpdGVVcmwiOm51bGx9LCJ1c2VySWRDbGFpbSI6IjQ4NjFlZWMxLWRlNGUtNGZhMi1hMjA4LWJkNzNhNDdkYmE3OCJ9\n",
+       "-> nel: {\"report_to\":\"network-errors\",\"max_age\":604800,\"success_fraction\":0.01,\"failure_fraction\":1.0}\n",
+       "-> report-to: {\"group\":\"network-errors\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://mdec.nelreports.net/api/report?cat=mdocs\"}]}\n",
+       "-> x-azure-ref: 20260330T104220Z-157fb874dd7dr7drhC1SVGcxmc0000000pkg000000005v3x\n",
+       "BODY Content-Type: text/event-stream\n",
+       "BODY START\n",
+       "event: message\n",
+       "data: {\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"{\\u0022results\\u0022:[{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\n## Step 2 - Choose a Linux distribution\\\\nThe different versions of Linux are called \\\\u0022distributions\\\\u0022 (sometimes shortened to \\\\u201Cdistros\\\\u201D). So there isn\\\\u2019t actually a way to just \\\\u201Cinstall Linux\\\\u201D, you first need to choose which distribution that uses the Linux kernel you want to download and install. There are over 600 active Linux distributions, such as Ubuntu, Debian, Kali Linux, openSUSE, and more. There are a number of factors to consider in choosing a distribution, such as:\\\\n### Required Linux experience\\\\nIf you\\\\u2019re new to Linux, you may want to start with Ubuntu. This is one of the most common starting places for Linux users due to the level of support and ease of use. Ubuntu offers a desktop-version and a server-version depending on how you plan to use it. The specific tasks that the operating system will be responsible for may help you determine which distribution to use. Arch Linux is a popular choice for those who want a highly customizable, do-it-yourself approach that is still stable and well-supported by a large user base. It is a much more complicated place to start, but can help you to get a better understanding of how Linux works due to the amount of custom configuration.\\\\n### System requirements\\\\nA minimal, or \\\\u0022lightweight\\\\u0022, distribution means a small footprint, with lower memory and processing speed requirements. Alpine Linux is a popular option where users will find most features disabled or not installed by default, but still has everything you need for a containerized app. Security needs: Some Linux users may have specific security concerns to consider. Kali Linux is an example of a distro designed for security tasks, such as Penetration Testing, Security Research, Computer Forensics and Reverse Engineering.\\\\n### Business and Enterprise requirements\\\\nBusiness, corporate, or academic environments may have unique concerns from consumer environments. For example Red Hat Enterprise Linux (RHEL), SUSE Linux Enterprise Server (SLES) and Oracle Linux offer 24/7 global support and subscription-based service. CentOS is another popular enterprise distro that is community-supported but still compatible with RHEL.\\\\n### Community support\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/linux/install#step-2---choose-a-linux-distribution\\u0022},{\\u0022title\\u0022:\\u0022Part 1.2 - Basic operations on Linux\\u0022,\\u0022content\\u0022:\\u0022# Part 1.2 - Basic operations on Linux\\\\n## Run commands by using the \\\\u0022sudo\\\\u0022 prefix\\\\nInstead of becoming the root account user in your session, you can run commands as root by using the \\\\u0060sudo \\\\u003Ccommand\\\\u003E\\\\u0060 format. This approach is safer because it will run the given command only as a superuser.\\\\nFor example, if you run \\\\u0060restart\\\\u0060 while you\\\\u0027re not elevated to superuser status, the result is as follows.\\\\n![Screenshot of restart command.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/restart-command.png)\\\\nTo run this command as a superuser instead, you can either change the session context to root (by running \\\\u0060sudo su\\\\u0060) or add the \\\\u0060sudo\\\\u0060 prefix, as follows:\\\\n\\\\u0060\\\\u0060\\\\u0060bash\\\\nsudo reboot\\\\n\\\\u0060\\\\u0060\\\\u0060\\\\nNow, the restart operation runs as expected by the virtual machine.\\\\n![Screenshot of reboot command.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/reboot-command.png)\\\\n## Package managers\\\\nPackage managers are used to install, upgrade, and remove applications in Linux. There are several different package managers, such as DPKG (Debian Package Management System), RPM (Red Hat Package Manager), and Zypper for SUSE Package managers themselves are low-level. Therefore, it might be difficult to manage software directly by using the package manager. Fortunately, there are wrappers for these tools that make it easier to install and uninstall software. For example, [yum](https://wikipedia.org/wiki/Yum_%28software%29) is a wrapper around RPM (Red Hat Package Manager) and [APT](https://wikipedia.org/wiki/APT_%28software%29) is a wrapper around DPKG (Debian Package Management System).\\\\nThis tutorial assumes that you\\\\u0027re running the [Ubuntu package management system](https://ubuntu.com/server/docs/package-management). This system is derived from the same system that\\\\u0027s used by the Debian GNU/Linux distribution. Therefore, we can use APT to install the software.\\\\nDepending on your Linux choice, you might have to use other tools. For example, you can use yum to install or uninstall software if you\\\\u0027re running Red Hat.\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-by-using-the-sudo-prefix\\u0022},{\\u0022title\\u0022:\\u0022Part 1.2 - Basic operations on Linux\\u0022,\\u0022content\\u0022:\\u0022# Part 1.2 - Basic operations on Linux\\\\n## Run commands as a superuser\\\\nThe superuser is the most privileged user account. It has root (unrestricted) access to all files and folders, and complete control over the operation of the computer. The system administrator uses this account for system maintenance.\\\\nOn UNIX-like systems, the conventional name of the superuser is \\\\u0022root\\\\u0022. The root user can do tasks that are otherwise restricted for standard users, including the following ones:\\\\n1. Changing system directories\\\\n2. Changing user privileges\\\\n3. Changing file ownership\\\\n4. System-related tasks such as restarts\\\\n5. Installation of certain applications\\\\nThere are several ways to run commands as the root account user. One is to use the \\\\u0060sudo su\\\\u0060 command. In this command, \\\\u0060sudo\\\\u0060 is short for \\\\u0060superuser do\\\\u0060, and \\\\u0060su\\\\u0060 is an abbreviation for \\\\u0022switch user.\\\\u0022 You can also use the \\\\u0060su\\\\u0060 command to switch between standard users.\\\\nImportant\\\\nAfter you become the root account user, everything you do runs in root context. Therefore, you should act carefully because the root account has access to the entire system.\\\\nTo demonstrate a permissions issue, create a text file in a special folder that\\\\u0027s named */etc*.\\\\nThe */etc/* folder is where system configuration files are located. This folder protects its contents from being changed by a standard user.\\\\nRun the following command:\\\\n\\\\u0060\\\\u0060\\\\u0060bash\\\\necho hello world \\\\u003E /etc/helloworld.txt\\\\n\\\\u0060\\\\u0060\\\\u0060\\\\nThe \\\\u0060echo\\\\u0060 command writes all the text that follows it to the output. The angle bracket (\\\\u0060\\\\u003E\\\\u0060) tells the system to send the output to the */etc/helloworld.txt* file instead of the console. This behavior is similar to how Windows works.\\\\nNote\\\\nLinux is case-sensitive: *Helloworld.txt*, *helloworld.txt* and *helloworld.Txt* are different files.\\\\nYou don\\\\u0027t have to provide a file name extension. The name \\\\u0022helloworld\\\\u0022 by itself is perfectly valid. (The \\\\u0022txt\\\\u0022 extension is used here only as an example.)\\\\nWhen you run this command, you receive a **permission denied** error message.\\\\n![Screenshot of permission denied message.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/permission-denied-message.png)\\\\nThe operation fails because the */etc* folder is special folder that a standard user can\\\\u0027t change. Verify the user by running the \\\\u0060id\\\\u0060 command.\\\\n![Screenshot of I D command.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/id-command.png)\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-as-a-superuser\\u0022},{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\n## Step 1 - Choose a method to install Linux\\\\nThe choice of which method to use for installing Linux depends on your needs and preferences.\\\\n1. **New to using Linux?** We recommend starting with Windows Subsystem for Linux (WSL) as it\\\\u0027s the easiest way to get up and running.\\\\n2. **Work in a business environment with more complex needs related to scale or security?** We recommend running Linux as a Virtual Machine (VM) in the cloud and checking out the support that Azure has to offer. This also applies if you want to run Linux as a server.\\\\n3. **Only want to run Linux as your primary operating system?** If you are good with a slightly more complicated install process and don\\\\u0027t need access to Windows tools (like Outlook, Teams, Word, PowerPoint, etc), you can run Linux on bare metal to access the full potential of your hardware without any overhead from virtualization or emulation.\\\\nLearn more about these options below.\\\\n### Windows Subsystem for Linux (WSL)\\\\nAvailable to those with a Windows machine, this is the most simple way to install Linux. Just run the Linux install command: \\\\u0060wsl --install\\\\u0060 to install the Ubuntu distribution.\\\\nThis method uses virtualization to integrate the Windows operating system (OS) with the Linux OS (running on an actual Linux kernel). You can add as many different Linux distributions as you please and run your favorite Linux tools, including GUI apps, alongside your favorite Windows tools. You can also mix and match Bash and PowerShell commands in the same command line. A Linux file system hosts your project files, while a separate Windows file systems hosts any developer tools, such as VS Code, that you\\\\u0027d like to work on the files with. This method of running Linux is highly efficient and performant. Once you choose a Linux distribution or opt to use the default Ubuntu distro, follow the instructions to Use the install Linux command with Windows Subsystem for Linux or learn more in the [WSL documentation](https://learn.microsoft.com/en-us/windows/wsl).\\\\n![WSL install command screenshot](https://learn.microsoft.com/linux/images/wsl-install-command-screenshot.png)\\\\n### Create a Virtual Machine (VM)\\\\nAvailable to everyone regardless if you\\\\u0027re using a Windows or MacOS-based machine, but will require either an Azure account, if creating a VM in the cloud, or virtualization software, if creating a VM directly on your machine (locally).\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/linux/install#step-1---choose-a-method-to-install-linux\\u0022},{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\nLinux is an operating system, similar to Windows, but with many different versions due to the nature of being open source and fully customizable. To install Linux, you must choose an install method and choose a Linux distribution.\\\\nTo install Linux:\\\\n1. Choose an install method: Windows Subsystem for Linux (WSL), Bare metal Linux; or create a Virtual Machine (VM) to run Linux locally or in the cloud.\\\\n2. Choose a Linux distribution: Ubuntu, Debian, Kali Linux, openSUSE, etc.\\\\n3. Follow the steps for your preferred install method:\\\\n3.1. Use the install Linux command with Windows Subsystem for Linux (WSL)\\\\n3.2. Create a Linux Virtual Machine (VM) in the cloud\\\\n3.3. Create a Linux Virtual Machine (VM) on your local machine\\\\n3.4. Create a bootable USB to install bare-metal Linux\\\\n4. After installing Linux: Get familiar with your distribution\\\\u0027s package manager, update and upgrade the packages available, and get familiar with the other [Linux resources at Microsoft](https://learn.microsoft.com/linux/), such as training courses, Linux-versions of popular tools, news, and Open Source events.\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/linux/install\\u0022},{\\u0022title\\u0022:\\u0022New to Linux resources for SQL users\\u0022,\\u0022content\\u0022:\\u0022# New to Linux resources for SQL users\\\\n**Applies to:**![](https://learn.microsoft.com/sql/includes/media/yes-icon.svg?view=sql-server-ver17)[SQL Server](https://learn.microsoft.com/sql/sql-server/sql-docs-navigation-guide?view=sql-server-ver17#applies-to) on Linux\\\\nThis article provides a learning path through an introduction to Linux concepts. Use the sections in the article as a guided learning path to familiarize yourself with the Linux environment.\\\\nThis list isn\\\\u0027t comprehensive but instead provides the bare minimum you need to help you administer and navigate your SQL Server on Linux environment. See the [full list of tutorials](https://www.linux.org/forums/linux-beginner-tutorials.123/) to learn more.\\\\n## What is Linux?\\\\nThe [What is Linux](https://www.linux.org/threads/what-is-linux.4106/) module introduces the history of Linux as an operating system. The module explains *the Kernel* and where Linux is today. This tutorial serves as an introduction to Linux, helping you get started.\\\\n## Select a distribution\\\\nAfter you know the history of Linux, decide which [distribution of Linux](https://www.linux.org/threads/selecting-a-linux-distribution.4117/) best suits your business needs.\\\\nSQL Server 2025 (17.x) is [supported](https://learn.microsoft.com/sql/linux/sql-server-linux-release-notes-2025?view=sql-server-ver17#supported-platforms) on Red Hat Enterprise Linux (RHEL) and Ubuntu.\\\\nSQL Server 2022 (16.x) and earlier versions are [also supported](https://learn.microsoft.com/sql/linux/sql-server-linux-release-notes-2022?view=sql-server-ver17#supported-platforms) on SUSE Linux Enterprise Server (SLES).\\\\n## Get around directories\\\\nAfter you choose the appropriate distribution of Linux, familiarize yourself with [getting around Linux directories](https://www.linux.org/threads/getting-around-in-linux-directories.4120/).\\\\nThis module helps you answer the following questions:\\\\n1. How to navigate between different files\\\\n2. How to identify a file from a directory\\\\n3. Identify different directories\\\\n## Install new software\\\\nNext, learn how to [install new software](https://www.linux.org/threads/installing-new-software-debian-red-hat-slackware.4119/) on your new Linux operating system. This module covers installing new software on the Debian and Red Hat Linux operating systems.\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/sql/linux/new-to-linux-resources?view=sql-server-ver17\\u0022},{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\n## Step 3 - Follow install method instructions\\\\n### Install Linux with Windows Subsystem for Linux\\\\nTo install Linux on a Windows PC, use the install Linux command.\\\\n1. Open PowerShell or Windows Command Prompt in *administrator mode* by right-clicking and selecting \\\\u0022Run as administrator\\\\u0022.\\\\n2. Enter the Linux install command: \\\\u0060wsl --install\\\\u0060.\\\\n3. Restart your machine.\\\\n![WSL Linux Install List Command screenshot](https://learn.microsoft.com/linux/images/wsl-install-list-screenshot.png)\\\\n#### Install Linux command options\\\\nBy default, the install command will use the Ubuntu distribution of Linux. You can see the other distributions that are available in the Microsoft Store by entering the command: \\\\u0060wsl -l -o\\\\u0060. If you\\\\u0027d like to install a distribution other than Ubuntu, just add \\\\u0060-d\\\\u0060 to the command with the name of the distribution you\\\\u0027d like to install:\\\\n\\\\u0060\\\\u0060\\\\u0060powershell\\\\nwsl --install -d \\\\u003CDistro Name\\\\u003E\\\\n\\\\u0060\\\\u0060\\\\u0060\\\\nIf the distribution you want to install isn\\\\u0027t available in the Microsoft Store, see the Windows Subsystem for Linux (WSL) documentation on how to [Import any Linux distribution to use with WSL](https://learn.microsoft.com/en-us/windows/wsl/use-custom-distro).\\\\nFor more information on how to use VS Code, Git, databases, Docker, GUI apps, GPU acceleration, NodesJS, USB devices, or mount a disk, build a custom distribution, manage disk space, or set up WSL for your enterprise business, see the [WSL documentation](https://learn.microsoft.com/en-us/windows/wsl/).\\\\n### Create a Linux Virtual Machine in the cloud\\\\nTo create a Linux Virtual Machine (VM) that is hosted in the cloud on Azure, log-in to the Azure Portal and select the \\\\u201CCreate a Virtual Machine\\\\u201D service. Then follow the instructions for creating a user name, project, instance, SSH key, ports, and copying the IP address.\\\\nAzure supports a variety of options for setting up Linux VMs in the cloud. For a quickstart guide on how to set up a Linux VM, choose your preferred method:\\\\n1. [Azure Portal](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-portal?tabs=ubuntu\\\\u0026source=docs)\\\\n2. [Azure CLI](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-cli)\\\\n3. [Azure PowerShell](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-powershell)\\\\n4. [Terraform](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-terraform)\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/linux/install#step-3---follow-install-method-instructions\\u0022},{\\u0022title\\u0022:\\u0022How to download and install Linux\\u0022,\\u0022content\\u0022:\\u0022# How to download and install Linux\\\\n## Step 2 - Choose a Linux distribution\\\\nA large user community that consistently contributes to the distribution, fixing bugs, adding features, helping with questions, etc. is another good consideration. Ubuntu is one of the most popular distos. openSUSE has been around longer than most other distributions and offers in addition to the stable version (Leap) a rolling version (Tumbleweed). Many other distros rapidly grow popular as the needs and preferences of the user community change. The Linux.org site has resources to help you assess what\\\\u0027s available, well-supported, or growing in popularity.\\\\n![Screenshot of the list of distributions on Linux.org](https://learn.microsoft.com/linux/images/linux-distributions-list-screenshot.png)\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/linux/install#step-2---choose-a-linux-distribution\\u0022},{\\u0022title\\u0022:\\u0022Part 1.2 - Basic operations on Linux\\u0022,\\u0022content\\u0022:\\u0022# Part 1.2 - Basic operations on Linux\\\\n## Run commands as a superuser\\\\nTo become the root account user, run the \\\\u0060sudo su\\\\u0060 command.\\\\n![Screenshot of sudo su command.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/sudo-su-command.png)\\\\nYou should notice two things: When you become root, the dollar sign character (\\\\u0060$\\\\u0060) becomes a pound sign character (\\\\u0060#\\\\u0060). Also, the username is changed to **root**. If you run the same \\\\u0060id\\\\u0060 command again, you see that the user and group ID of root are **0**.\\\\n![Screenshot of root command.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/root-command.png)\\\\nImportant\\\\nNow you have elevated yourself to the \\\\u0022superuser\\\\u0022 role in your session, you have complete access to the system. Keep in mind that this is a potentially dangerous situation, and you should exercise caution as you continue.\\\\nNow, run the same command one more time:\\\\n\\\\u0060\\\\u0060\\\\u0060bash\\\\necho hello world \\\\u003E /etc/helloworld.txt\\\\n\\\\u0060\\\\u0060\\\\u0060\\\\nThis time, you don\\\\u0027t receive any error message. To verify if the file is created, run the following command:\\\\n\\\\u0060\\\\u0060\\\\u0060bash\\\\nll /etc/hello*\\\\n\\\\u0060\\\\u0060\\\\u0060\\\\nThe \\\\u0060ll\\\\u0060 command lists the files and subfolders that are in a specified folder. In this example, the \\\\u0060/etc/hello*\\\\u0060 parameter value limits the command output to the files or folders whose name begins in \\\\u0060hello\\\\u0060.\\\\n![Screenshot of hello command.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/hello-command.png)\\\\nThe output shows that the file was created. What if we want to examine the content of the file? The \\\\u0060cat\\\\u0060 command helps here. To see the content, run \\\\u0060cat /etc/helloworld.txt\\\\u0060.\\\\n![Screenshot of cat command.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/cat-command.png)\\\\nThe \\\\u0060cat\\\\u0060 command reads files sequentially, and writes their content to the standard output. Therefore, it writes \\\\u0022hello world\\\\u0022 in the console.\\\\nTo prevent mistakes that could damage the system while you\\\\u0027re elevated to root status, it\\\\u0027s a good practice to exit the root session and return to your standard user session to avoid some dangerous operations. To do this, run \\\\u0060exit\\\\u0060. You can see that the pound sign reverts to a dollar sign, and the username is displayed as your standard user.\\\\n![Screenshot of exit command.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/exit-command.png)\\\\nFor additional practice, run the same \\\\u0060ll\\\\u0060 and \\\\u0060cat\\\\u0060 commands within your standard user context. You\\\\u0027ll see that you can\\\\u0027t create the file in the */etc/* folder, but can you list the file and read the contents.\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-as-a-superuser\\u0022},{\\u0022title\\u0022:\\u0022Part 1.2 - Basic operations on Linux\\u0022,\\u0022content\\u0022:\\u0022# Part 1.2 - Basic operations on Linux\\\\n## Remove packages\\\\nFinding the file doesn\\\\u0027t involve package managers. Instead, you\\\\u0027ll run a standard search for files in Linux. There are several ways to do so. One of the most common search commands is \\\\u0060find\\\\u0060. However, this command is also confusing to use, so we won\\\\u0027t discuss it here. Instead, we\\\\u0027ll use \\\\u0060whereis\\\\u0060.\\\\nNote\\\\nLinux is very well documented. Nearly every command has a useful help page available. Use the [man](https://www.geeksforgeeks.org/man-command-in-linux-with-examples/) and [info](https://www.geeksforgeeks.org/info-command-in-linux-with-examples/) commands to show the Help pages in Linux. For example, to learn more on your own about the \\\\u0060find\\\\u0060 command, run \\\\u0060man find\\\\u0060 or \\\\u0060info find\\\\u0060.\\\\nAccording to the Help page for \\\\u0060whereis\\\\u0060, the definition is as follows:\\\\n\\\\u0060whereis\\\\u0060 locates the binary, source, and manual files for the specified command names.\\\\nIf you run \\\\u0060whereis apache2\\\\u0060, you should find the */etc/apache2* installation.\\\\n![Screenshot of whereis apache2 command.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/whereis-apache2-command.png)\\\\nRemember that the */etc/* directory is where the system configuration files are located. To learn whether \\\\u0022apache2\\\\u0022is a file or folder, run \\\\u0060ll /etc/apache2\\\\u0060. As you can see, this is a folder, and it contains the apache2 configuration files.\\\\n![Screenshot of ll etc apache2 command.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/ll-etc-apache2-command.png)\\\\nYou expect \\\\u0060apache2\\\\u0060 to contain these files and folders because you used the \\\\u0060apt remove\\\\u0060 command that doesn\\\\u0027t delete the configuration files.\\\\nAs final step, run the \\\\u0060apt purge\\\\u0060 command to see its effect. You can run a purge to clean up the installation folder even after you remove the package. The following screenshot shows that \\\\u0060whereis\\\\u0060 didn\\\\u0027t find anything. This is because the \\\\u0060purge\\\\u0060 command removed the configuration files together with the binaries.\\\\n![Screenshot of apt purge command.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/apt-purge-command.png)\\u0022,\\u0022contentUrl\\u0022:\\u0022https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#remove-packages\\u0022}]}\"}],\"structuredContent\":{\"results\":[{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\n## Step 2 - Choose a Linux distribution\\nThe different versions of Linux are called \\u0022distributions\\u0022 (sometimes shortened to \\u201Cdistros\\u201D). So there isn\\u2019t actually a way to just \\u201Cinstall Linux\\u201D, you first need to choose which distribution that uses the Linux kernel you want to download and install. There are over 600 active Linux distributions, such as Ubuntu, Debian, Kali Linux, openSUSE, and more. There are a number of factors to consider in choosing a distribution, such as:\\n### Required Linux experience\\nIf you\\u2019re new to Linux, you may want to start with Ubuntu. This is one of the most common starting places for Linux users due to the level of support and ease of use. Ubuntu offers a desktop-version and a server-version depending on how you plan to use it. The specific tasks that the operating system will be responsible for may help you determine which distribution to use. Arch Linux is a popular choice for those who want a highly customizable, do-it-yourself approach that is still stable and well-supported by a large user base. It is a much more complicated place to start, but can help you to get a better understanding of how Linux works due to the amount of custom configuration.\\n### System requirements\\nA minimal, or \\u0022lightweight\\u0022, distribution means a small footprint, with lower memory and processing speed requirements. Alpine Linux is a popular option where users will find most features disabled or not installed by default, but still has everything you need for a containerized app. Security needs: Some Linux users may have specific security concerns to consider. Kali Linux is an example of a distro designed for security tasks, such as Penetration Testing, Security Research, Computer Forensics and Reverse Engineering.\\n### Business and Enterprise requirements\\nBusiness, corporate, or academic environments may have unique concerns from consumer environments. For example Red Hat Enterprise Linux (RHEL), SUSE Linux Enterprise Server (SLES) and Oracle Linux offer 24/7 global support and subscription-based service. CentOS is another popular enterprise distro that is community-supported but still compatible with RHEL.\\n### Community support\",\"contentUrl\":\"https://learn.microsoft.com/linux/install#step-2---choose-a-linux-distribution\"},{\"title\":\"Part 1.2 - Basic operations on Linux\",\"content\":\"# Part 1.2 - Basic operations on Linux\\n## Run commands by using the \\u0022sudo\\u0022 prefix\\nInstead of becoming the root account user in your session, you can run commands as root by using the \\u0060sudo \\u003Ccommand\\u003E\\u0060 format. This approach is safer because it will run the given command only as a superuser.\\nFor example, if you run \\u0060restart\\u0060 while you\\u0027re not elevated to superuser status, the result is as follows.\\n![Screenshot of restart command.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/restart-command.png)\\nTo run this command as a superuser instead, you can either change the session context to root (by running \\u0060sudo su\\u0060) or add the \\u0060sudo\\u0060 prefix, as follows:\\n\\u0060\\u0060\\u0060bash\\nsudo reboot\\n\\u0060\\u0060\\u0060\\nNow, the restart operation runs as expected by the virtual machine.\\n![Screenshot of reboot command.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/reboot-command.png)\\n## Package managers\\nPackage managers are used to install, upgrade, and remove applications in Linux. There are several different package managers, such as DPKG (Debian Package Management System), RPM (Red Hat Package Manager), and Zypper for SUSE Package managers themselves are low-level. Therefore, it might be difficult to manage software directly by using the package manager. Fortunately, there are wrappers for these tools that make it easier to install and uninstall software. For example, [yum](https://wikipedia.org/wiki/Yum_%28software%29) is a wrapper around RPM (Red Hat Package Manager) and [APT](https://wikipedia.org/wiki/APT_%28software%29) is a wrapper around DPKG (Debian Package Management System).\\nThis tutorial assumes that you\\u0027re running the [Ubuntu package management system](https://ubuntu.com/server/docs/package-management). This system is derived from the same system that\\u0027s used by the Debian GNU/Linux distribution. Therefore, we can use APT to install the software.\\nDepending on your Linux choice, you might have to use other tools. For example, you can use yum to install or uninstall software if you\\u0027re running Red Hat.\",\"contentUrl\":\"https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-by-using-the-sudo-prefix\"},{\"title\":\"Part 1.2 - Basic operations on Linux\",\"content\":\"# Part 1.2 - Basic operations on Linux\\n## Run commands as a superuser\\nThe superuser is the most privileged user account. It has root (unrestricted) access to all files and folders, and complete control over the operation of the computer. The system administrator uses this account for system maintenance.\\nOn UNIX-like systems, the conventional name of the superuser is \\u0022root\\u0022. The root user can do tasks that are otherwise restricted for standard users, including the following ones:\\n1. Changing system directories\\n2. Changing user privileges\\n3. Changing file ownership\\n4. System-related tasks such as restarts\\n5. Installation of certain applications\\nThere are several ways to run commands as the root account user. One is to use the \\u0060sudo su\\u0060 command. In this command, \\u0060sudo\\u0060 is short for \\u0060superuser do\\u0060, and \\u0060su\\u0060 is an abbreviation for \\u0022switch user.\\u0022 You can also use the \\u0060su\\u0060 command to switch between standard users.\\nImportant\\nAfter you become the root account user, everything you do runs in root context. Therefore, you should act carefully because the root account has access to the entire system.\\nTo demonstrate a permissions issue, create a text file in a special folder that\\u0027s named */etc*.\\nThe */etc/* folder is where system configuration files are located. This folder protects its contents from being changed by a standard user.\\nRun the following command:\\n\\u0060\\u0060\\u0060bash\\necho hello world \\u003E /etc/helloworld.txt\\n\\u0060\\u0060\\u0060\\nThe \\u0060echo\\u0060 command writes all the text that follows it to the output. The angle bracket (\\u0060\\u003E\\u0060) tells the system to send the output to the */etc/helloworld.txt* file instead of the console. This behavior is similar to how Windows works.\\nNote\\nLinux is case-sensitive: *Helloworld.txt*, *helloworld.txt* and *helloworld.Txt* are different files.\\nYou don\\u0027t have to provide a file name extension. The name \\u0022helloworld\\u0022 by itself is perfectly valid. (The \\u0022txt\\u0022 extension is used here only as an example.)\\nWhen you run this command, you receive a **permission denied** error message.\\n![Screenshot of permission denied message.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/permission-denied-message.png)\\nThe operation fails because the */etc* folder is special folder that a standard user can\\u0027t change. Verify the user by running the \\u0060id\\u0060 command.\\n![Screenshot of I D command.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/id-command.png)\",\"contentUrl\":\"https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-as-a-superuser\"},{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\n## Step 1 - Choose a method to install Linux\\nThe choice of which method to use for installing Linux depends on your needs and preferences.\\n1. **New to using Linux?** We recommend starting with Windows Subsystem for Linux (WSL) as it\\u0027s the easiest way to get up and running.\\n2. **Work in a business environment with more complex needs related to scale or security?** We recommend running Linux as a Virtual Machine (VM) in the cloud and checking out the support that Azure has to offer. This also applies if you want to run Linux as a server.\\n3. **Only want to run Linux as your primary operating system?** If you are good with a slightly more complicated install process and don\\u0027t need access to Windows tools (like Outlook, Teams, Word, PowerPoint, etc), you can run Linux on bare metal to access the full potential of your hardware without any overhead from virtualization or emulation.\\nLearn more about these options below.\\n### Windows Subsystem for Linux (WSL)\\nAvailable to those with a Windows machine, this is the most simple way to install Linux. Just run the Linux install command: \\u0060wsl --install\\u0060 to install the Ubuntu distribution.\\nThis method uses virtualization to integrate the Windows operating system (OS) with the Linux OS (running on an actual Linux kernel). You can add as many different Linux distributions as you please and run your favorite Linux tools, including GUI apps, alongside your favorite Windows tools. You can also mix and match Bash and PowerShell commands in the same command line. A Linux file system hosts your project files, while a separate Windows file systems hosts any developer tools, such as VS Code, that you\\u0027d like to work on the files with. This method of running Linux is highly efficient and performant. Once you choose a Linux distribution or opt to use the default Ubuntu distro, follow the instructions to Use the install Linux command with Windows Subsystem for Linux or learn more in the [WSL documentation](https://learn.microsoft.com/en-us/windows/wsl).\\n![WSL install command screenshot](https://learn.microsoft.com/linux/images/wsl-install-command-screenshot.png)\\n### Create a Virtual Machine (VM)\\nAvailable to everyone regardless if you\\u0027re using a Windows or MacOS-based machine, but will require either an Azure account, if creating a VM in the cloud, or virtualization software, if creating a VM directly on your machine (locally).\",\"contentUrl\":\"https://learn.microsoft.com/linux/install#step-1---choose-a-method-to-install-linux\"},{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\nLinux is an operating system, similar to Windows, but with many different versions due to the nature of being open source and fully customizable. To install Linux, you must choose an install method and choose a Linux distribution.\\nTo install Linux:\\n1. Choose an install method: Windows Subsystem for Linux (WSL), Bare metal Linux; or create a Virtual Machine (VM) to run Linux locally or in the cloud.\\n2. Choose a Linux distribution: Ubuntu, Debian, Kali Linux, openSUSE, etc.\\n3. Follow the steps for your preferred install method:\\n3.1. Use the install Linux command with Windows Subsystem for Linux (WSL)\\n3.2. Create a Linux Virtual Machine (VM) in the cloud\\n3.3. Create a Linux Virtual Machine (VM) on your local machine\\n3.4. Create a bootable USB to install bare-metal Linux\\n4. After installing Linux: Get familiar with your distribution\\u0027s package manager, update and upgrade the packages available, and get familiar with the other [Linux resources at Microsoft](https://learn.microsoft.com/linux/), such as training courses, Linux-versions of popular tools, news, and Open Source events.\",\"contentUrl\":\"https://learn.microsoft.com/linux/install\"},{\"title\":\"New to Linux resources for SQL users\",\"content\":\"# New to Linux resources for SQL users\\n**Applies to:**![](https://learn.microsoft.com/sql/includes/media/yes-icon.svg?view=sql-server-ver17)[SQL Server](https://learn.microsoft.com/sql/sql-server/sql-docs-navigation-guide?view=sql-server-ver17#applies-to) on Linux\\nThis article provides a learning path through an introduction to Linux concepts. Use the sections in the article as a guided learning path to familiarize yourself with the Linux environment.\\nThis list isn\\u0027t comprehensive but instead provides the bare minimum you need to help you administer and navigate your SQL Server on Linux environment. See the [full list of tutorials](https://www.linux.org/forums/linux-beginner-tutorials.123/) to learn more.\\n## What is Linux?\\nThe [What is Linux](https://www.linux.org/threads/what-is-linux.4106/) module introduces the history of Linux as an operating system. The module explains *the Kernel* and where Linux is today. This tutorial serves as an introduction to Linux, helping you get started.\\n## Select a distribution\\nAfter you know the history of Linux, decide which [distribution of Linux](https://www.linux.org/threads/selecting-a-linux-distribution.4117/) best suits your business needs.\\nSQL Server 2025 (17.x) is [supported](https://learn.microsoft.com/sql/linux/sql-server-linux-release-notes-2025?view=sql-server-ver17#supported-platforms) on Red Hat Enterprise Linux (RHEL) and Ubuntu.\\nSQL Server 2022 (16.x) and earlier versions are [also supported](https://learn.microsoft.com/sql/linux/sql-server-linux-release-notes-2022?view=sql-server-ver17#supported-platforms) on SUSE Linux Enterprise Server (SLES).\\n## Get around directories\\nAfter you choose the appropriate distribution of Linux, familiarize yourself with [getting around Linux directories](https://www.linux.org/threads/getting-around-in-linux-directories.4120/).\\nThis module helps you answer the following questions:\\n1. How to navigate between different files\\n2. How to identify a file from a directory\\n3. Identify different directories\\n## Install new software\\nNext, learn how to [install new software](https://www.linux.org/threads/installing-new-software-debian-red-hat-slackware.4119/) on your new Linux operating system. This module covers installing new software on the Debian and Red Hat Linux operating systems.\",\"contentUrl\":\"https://learn.microsoft.com/sql/linux/new-to-linux-resources?view=sql-server-ver17\"},{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\n## Step 3 - Follow install method instructions\\n### Install Linux with Windows Subsystem for Linux\\nTo install Linux on a Windows PC, use the install Linux command.\\n1. Open PowerShell or Windows Command Prompt in *administrator mode* by right-clicking and selecting \\u0022Run as administrator\\u0022.\\n2. Enter the Linux install command: \\u0060wsl --install\\u0060.\\n3. Restart your machine.\\n![WSL Linux Install List Command screenshot](https://learn.microsoft.com/linux/images/wsl-install-list-screenshot.png)\\n#### Install Linux command options\\nBy default, the install command will use the Ubuntu distribution of Linux. You can see the other distributions that are available in the Microsoft Store by entering the command: \\u0060wsl -l -o\\u0060. If you\\u0027d like to install a distribution other than Ubuntu, just add \\u0060-d\\u0060 to the command with the name of the distribution you\\u0027d like to install:\\n\\u0060\\u0060\\u0060powershell\\nwsl --install -d \\u003CDistro Name\\u003E\\n\\u0060\\u0060\\u0060\\nIf the distribution you want to install isn\\u0027t available in the Microsoft Store, see the Windows Subsystem for Linux (WSL) documentation on how to [Import any Linux distribution to use with WSL](https://learn.microsoft.com/en-us/windows/wsl/use-custom-distro).\\nFor more information on how to use VS Code, Git, databases, Docker, GUI apps, GPU acceleration, NodesJS, USB devices, or mount a disk, build a custom distribution, manage disk space, or set up WSL for your enterprise business, see the [WSL documentation](https://learn.microsoft.com/en-us/windows/wsl/).\\n### Create a Linux Virtual Machine in the cloud\\nTo create a Linux Virtual Machine (VM) that is hosted in the cloud on Azure, log-in to the Azure Portal and select the \\u201CCreate a Virtual Machine\\u201D service. Then follow the instructions for creating a user name, project, instance, SSH key, ports, and copying the IP address.\\nAzure supports a variety of options for setting up Linux VMs in the cloud. For a quickstart guide on how to set up a Linux VM, choose your preferred method:\\n1. [Azure Portal](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-portal?tabs=ubuntu\\u0026source=docs)\\n2. [Azure CLI](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-cli)\\n3. [Azure PowerShell](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-powershell)\\n4. [Terraform](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-terraform)\",\"contentUrl\":\"https://learn.microsoft.com/linux/install#step-3---follow-install-method-instructions\"},{\"title\":\"How to download and install Linux\",\"content\":\"# How to download and install Linux\\n## Step 2 - Choose a Linux distribution\\nA large user community that consistently contributes to the distribution, fixing bugs, adding features, helping with questions, etc. is another good consideration. Ubuntu is one of the most popular distos. openSUSE has been around longer than most other distributions and offers in addition to the stable version (Leap) a rolling version (Tumbleweed). Many other distros rapidly grow popular as the needs and preferences of the user community change. The Linux.org site has resources to help you assess what\\u0027s available, well-supported, or growing in popularity.\\n![Screenshot of the list of distributions on Linux.org](https://learn.microsoft.com/linux/images/linux-distributions-list-screenshot.png)\",\"contentUrl\":\"https://learn.microsoft.com/linux/install#step-2---choose-a-linux-distribution\"},{\"title\":\"Part 1.2 - Basic operations on Linux\",\"content\":\"# Part 1.2 - Basic operations on Linux\\n## Run commands as a superuser\\nTo become the root account user, run the \\u0060sudo su\\u0060 command.\\n![Screenshot of sudo su command.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/sudo-su-command.png)\\nYou should notice two things: When you become root, the dollar sign character (\\u0060$\\u0060) becomes a pound sign character (\\u0060#\\u0060). Also, the username is changed to **root**. If you run the same \\u0060id\\u0060 command again, you see that the user and group ID of root are **0**.\\n![Screenshot of root command.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/root-command.png)\\nImportant\\nNow you have elevated yourself to the \\u0022superuser\\u0022 role in your session, you have complete access to the system. Keep in mind that this is a potentially dangerous situation, and you should exercise caution as you continue.\\nNow, run the same command one more time:\\n\\u0060\\u0060\\u0060bash\\necho hello world \\u003E /etc/helloworld.txt\\n\\u0060\\u0060\\u0060\\nThis time, you don\\u0027t receive any error message. To verify if the file is created, run the following command:\\n\\u0060\\u0060\\u0060bash\\nll /etc/hello*\\n\\u0060\\u0060\\u0060\\nThe \\u0060ll\\u0060 command lists the files and subfolders that are in a specified folder. In this example, the \\u0060/etc/hello*\\u0060 parameter value limits the command output to the files or folders whose name begins in \\u0060hello\\u0060.\\n![Screenshot of hello command.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/hello-command.png)\\nThe output shows that the file was created. What if we want to examine the content of the file? The \\u0060cat\\u0060 command helps here. To see the content, run \\u0060cat /etc/helloworld.txt\\u0060.\\n![Screenshot of cat command.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/cat-command.png)\\nThe \\u0060cat\\u0060 command reads files sequentially, and writes their content to the standard output. Therefore, it writes \\u0022hello world\\u0022 in the console.\\nTo prevent mistakes that could damage the system while you\\u0027re elevated to root status, it\\u0027s a good practice to exit the root session and return to your standard user session to avoid some dangerous operations. To do this, run \\u0060exit\\u0060. You can see that the pound sign reverts to a dollar sign, and the username is displayed as your standard user.\\n![Screenshot of exit command.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/exit-command.png)\\nFor additional practice, run the same \\u0060ll\\u0060 and \\u0060cat\\u0060 commands within your standard user context. You\\u0027ll see that you can\\u0027t create the file in the */etc/* folder, but can you list the file and read the contents.\",\"contentUrl\":\"https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-as-a-superuser\"},{\"title\":\"Part 1.2 - Basic operations on Linux\",\"content\":\"# Part 1.2 - Basic operations on Linux\\n## Remove packages\\nFinding the file doesn\\u0027t involve package managers. Instead, you\\u0027ll run a standard search for files in Linux. There are several ways to do so. One of the most common search commands is \\u0060find\\u0060. However, this command is also confusing to use, so we won\\u0027t discuss it here. Instead, we\\u0027ll use \\u0060whereis\\u0060.\\nNote\\nLinux is very well documented. Nearly every command has a useful help page available. Use the [man](https://www.geeksforgeeks.org/man-command-in-linux-with-examples/) and [info](https://www.geeksforgeeks.org/info-command-in-linux-with-examples/) commands to show the Help pages in Linux. For example, to learn more on your own about the \\u0060find\\u0060 command, run \\u0060man find\\u0060 or \\u0060info find\\u0060.\\nAccording to the Help page for \\u0060whereis\\u0060, the definition is as follows:\\n\\u0060whereis\\u0060 locates the binary, source, and manual files for the specified command names.\\nIf you run \\u0060whereis apache2\\u0060, you should find the */etc/apache2* installation.\\n![Screenshot of whereis apache2 command.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/whereis-apache2-command.png)\\nRemember that the */etc/* directory is where the system configuration files are located. To learn whether \\u0022apache2\\u0022is a file or folder, run \\u0060ll /etc/apache2\\u0060. As you can see, this is a folder, and it contains the apache2 configuration files.\\n![Screenshot of ll etc apache2 command.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/ll-etc-apache2-command.png)\\nYou expect \\u0060apache2\\u0060 to contain these files and folders because you used the \\u0060apt remove\\u0060 command that doesn\\u0027t delete the configuration files.\\nAs final step, run the \\u0060apt purge\\u0060 command to see its effect. You can run a purge to clean up the installation folder even after you remove the package. The following screenshot shows that \\u0060whereis\\u0060 didn\\u0027t find anything. This is because the \\u0060purge\\u0060 command removed the configuration files together with the binaries.\\n![Screenshot of apt purge command.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/apt-purge-command.png)\",\"contentUrl\":\"https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#remove-packages\"}]}},\"id\":\"07e26d19ee6248d98f6dc039bf4fc589\",\"jsonrpc\":\"2.0\"}\n",
+       "\n",
+       "\n",
+       "BODY END
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Title: \"How to download and install Linux\"\n", + "\n", + "Content: \"# How to download and install Linux\\n## Step 2 - Choose a Linux distribution\\nThe different versions of Linux are called \\\"distributions\\\" (sometimes shortened to “distros”). So there isn’t actually a way to just “install Linux”, you first need to choose which distribution that uses the Linux kernel you want to download and install. There are over 600 active Linux distributions, such as Ubuntu, Debian, Kali Linux, openSUSE, and more. There are a number of factors to consider in choosing a distribution, such as:\\n### Required Linux experience\\nIf you’re new to Linux, you may want to start with Ubuntu. This is one of the most common starting places for Linux users due to the level of support and ease of use. Ubuntu offers a desktop-version and a server-version depending on how you plan to use it. The specific tasks that the operating system will be responsible for may help you determine which distribution to use. Arch Linux is a popular choice for those who want a highly customizable, do-it-yourself approach that is still stable and well-supported by a large user base. It is a much more complicated place to start, but can help you to get a better understanding of how Linux works due to the amount of custom configuration.\\n### System requirements\\nA minimal, or \\\"lightweight\\\", distribution means a small footprint, with lower memory and processing speed requirements. Alpine Linux is a popular option where users will find most features disabled or not installed by default, but still has everything you need for a containerized app. Security needs: Some Linux users may have specific security concerns to consider. Kali Linux is an example of a distro designed for security tasks, such as Penetration Testing, Security Research, Computer Forensics and Reverse Engineering.\\n### Business and Enterprise requirements\\nBusiness, corporate, or academic environments may have unique concerns from consumer environments. For example Red Hat Enterprise Linux (RHEL), SUSE Linux Enterprise Server (SLES) and Oracle Linux offer 24/7 global support and subscription-based service. CentOS is another popular enterprise distro that is community-supported but still compatible with RHEL.\\n### Community support\"\n", + "Content URL: \"https://learn.microsoft.com/linux/install#step-2---choose-a-linux-distribution\"\n", + "Title: \"Part 1.2 - Basic operations on Linux\"\n", + "\n", + "Content: \"# Part 1.2 - Basic operations on Linux\\n## Run commands by using the \\\"sudo\\\" prefix\\nInstead of becoming the root account user in your session, you can run commands as root by using the `sudo ` format. This approach is safer because it will run the given command only as a superuser.\\nFor example, if you run `restart` while you're not elevated to superuser status, the result is as follows.\\n![Screenshot of restart command.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/restart-command.png)\\nTo run this command as a superuser instead, you can either change the session context to root (by running `sudo su`) or add the `sudo` prefix, as follows:\\n```bash\\nsudo reboot\\n```\\nNow, the restart operation runs as expected by the virtual machine.\\n![Screenshot of reboot command.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/reboot-command.png)\\n## Package managers\\nPackage managers are used to install, upgrade, and remove applications in Linux. There are several different package managers, such as DPKG (Debian Package Management System), RPM (Red Hat Package Manager), and Zypper for SUSE Package managers themselves are low-level. Therefore, it might be difficult to manage software directly by using the package manager. Fortunately, there are wrappers for these tools that make it easier to install and uninstall software. For example, [yum](https://wikipedia.org/wiki/Yum_%28software%29) is a wrapper around RPM (Red Hat Package Manager) and [APT](https://wikipedia.org/wiki/APT_%28software%29) is a wrapper around DPKG (Debian Package Management System).\\nThis tutorial assumes that you're running the [Ubuntu package management system](https://ubuntu.com/server/docs/package-management). This system is derived from the same system that's used by the Debian GNU/Linux distribution. Therefore, we can use APT to install the software.\\nDepending on your Linux choice, you might have to use other tools. For example, you can use yum to install or uninstall software if you're running Red Hat.\"\n", + "Content URL: \"https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-by-using-the-sudo-prefix\"\n", + "Title: \"Part 1.2 - Basic operations on Linux\"\n", + "\n", + "Content: \"# Part 1.2 - Basic operations on Linux\\n## Run commands as a superuser\\nThe superuser is the most privileged user account. It has root (unrestricted) access to all files and folders, and complete control over the operation of the computer. The system administrator uses this account for system maintenance.\\nOn UNIX-like systems, the conventional name of the superuser is \\\"root\\\". The root user can do tasks that are otherwise restricted for standard users, including the following ones:\\n1. Changing system directories\\n2. Changing user privileges\\n3. Changing file ownership\\n4. System-related tasks such as restarts\\n5. Installation of certain applications\\nThere are several ways to run commands as the root account user. One is to use the `sudo su` command. In this command, `sudo` is short for `superuser do`, and `su` is an abbreviation for \\\"switch user.\\\" You can also use the `su` command to switch between standard users.\\nImportant\\nAfter you become the root account user, everything you do runs in root context. Therefore, you should act carefully because the root account has access to the entire system.\\nTo demonstrate a permissions issue, create a text file in a special folder that's named */etc*.\\nThe */etc/* folder is where system configuration files are located. This folder protects its contents from being changed by a standard user.\\nRun the following command:\\n```bash\\necho hello world > /etc/helloworld.txt\\n```\\nThe `echo` command writes all the text that follows it to the output. The angle bracket (`>`) tells the system to send the output to the */etc/helloworld.txt* file instead of the console. This behavior is similar to how Windows works.\\nNote\\nLinux is case-sensitive: *Helloworld.txt*, *helloworld.txt* and *helloworld.Txt* are different files.\\nYou don't have to provide a file name extension. The name \\\"helloworld\\\" by itself is perfectly valid. (The \\\"txt\\\" extension is used here only as an example.)\\nWhen you run this command, you receive a **permission denied** error message.\\n![Screenshot of permission denied message.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/permission-denied-message.png)\\nThe operation fails because the */etc* folder is special folder that a standard user can't change. Verify the user by running the `id` command.\\n![Screenshot of I D command.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/id-command.png)\"\n", + "Content URL: \"https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-as-a-superuser\"\n", + "Title: \"How to download and install Linux\"\n", + "\n", + "Content: \"# How to download and install Linux\\n## Step 1 - Choose a method to install Linux\\nThe choice of which method to use for installing Linux depends on your needs and preferences.\\n1. **New to using Linux?** We recommend starting with Windows Subsystem for Linux (WSL) as it's the easiest way to get up and running.\\n2. **Work in a business environment with more complex needs related to scale or security?** We recommend running Linux as a Virtual Machine (VM) in the cloud and checking out the support that Azure has to offer. This also applies if you want to run Linux as a server.\\n3. **Only want to run Linux as your primary operating system?** If you are good with a slightly more complicated install process and don't need access to Windows tools (like Outlook, Teams, Word, PowerPoint, etc), you can run Linux on bare metal to access the full potential of your hardware without any overhead from virtualization or emulation.\\nLearn more about these options below.\\n### Windows Subsystem for Linux (WSL)\\nAvailable to those with a Windows machine, this is the most simple way to install Linux. Just run the Linux install command: `wsl --install` to install the Ubuntu distribution.\\nThis method uses virtualization to integrate the Windows operating system (OS) with the Linux OS (running on an actual Linux kernel). You can add as many different Linux distributions as you please and run your favorite Linux tools, including GUI apps, alongside your favorite Windows tools. You can also mix and match Bash and PowerShell commands in the same command line. A Linux file system hosts your project files, while a separate Windows file systems hosts any developer tools, such as VS Code, that you'd like to work on the files with. This method of running Linux is highly efficient and performant. Once you choose a Linux distribution or opt to use the default Ubuntu distro, follow the instructions to Use the install Linux command with Windows Subsystem for Linux or learn more in the [WSL documentation](https://learn.microsoft.com/en-us/windows/wsl).\\n![WSL install command screenshot](https://learn.microsoft.com/linux/images/wsl-install-command-screenshot.png)\\n### Create a Virtual Machine (VM)\\nAvailable to everyone regardless if you're using a Windows or MacOS-based machine, but will require either an Azure account, if creating a VM in the cloud, or virtualization software, if creating a VM directly on your machine (locally).\"\n", + "Content URL: \"https://learn.microsoft.com/linux/install#step-1---choose-a-method-to-install-linux\"\n", + "Title: \"How to download and install Linux\"\n", + "\n", + "Content: \"# How to download and install Linux\\nLinux is an operating system, similar to Windows, but with many different versions due to the nature of being open source and fully customizable. To install Linux, you must choose an install method and choose a Linux distribution.\\nTo install Linux:\\n1. Choose an install method: Windows Subsystem for Linux (WSL), Bare metal Linux; or create a Virtual Machine (VM) to run Linux locally or in the cloud.\\n2. Choose a Linux distribution: Ubuntu, Debian, Kali Linux, openSUSE, etc.\\n3. Follow the steps for your preferred install method:\\n3.1. Use the install Linux command with Windows Subsystem for Linux (WSL)\\n3.2. Create a Linux Virtual Machine (VM) in the cloud\\n3.3. Create a Linux Virtual Machine (VM) on your local machine\\n3.4. Create a bootable USB to install bare-metal Linux\\n4. After installing Linux: Get familiar with your distribution's package manager, update and upgrade the packages available, and get familiar with the other [Linux resources at Microsoft](https://learn.microsoft.com/linux/), such as training courses, Linux-versions of popular tools, news, and Open Source events.\"\n", + "Content URL: \"https://learn.microsoft.com/linux/install\"\n", + "Title: \"New to Linux resources for SQL users\"\n", + "\n", + "Content: \"# New to Linux resources for SQL users\\n**Applies to:**![](https://learn.microsoft.com/sql/includes/media/yes-icon.svg?view=sql-server-ver17)[SQL Server](https://learn.microsoft.com/sql/sql-server/sql-docs-navigation-guide?view=sql-server-ver17#applies-to) on Linux\\nThis article provides a learning path through an introduction to Linux concepts. Use the sections in the article as a guided learning path to familiarize yourself with the Linux environment.\\nThis list isn't comprehensive but instead provides the bare minimum you need to help you administer and navigate your SQL Server on Linux environment. See the [full list of tutorials](https://www.linux.org/forums/linux-beginner-tutorials.123/) to learn more.\\n## What is Linux?\\nThe [What is Linux](https://www.linux.org/threads/what-is-linux.4106/) module introduces the history of Linux as an operating system. The module explains *the Kernel* and where Linux is today. This tutorial serves as an introduction to Linux, helping you get started.\\n## Select a distribution\\nAfter you know the history of Linux, decide which [distribution of Linux](https://www.linux.org/threads/selecting-a-linux-distribution.4117/) best suits your business needs.\\nSQL Server 2025 (17.x) is [supported](https://learn.microsoft.com/sql/linux/sql-server-linux-release-notes-2025?view=sql-server-ver17#supported-platforms) on Red Hat Enterprise Linux (RHEL) and Ubuntu.\\nSQL Server 2022 (16.x) and earlier versions are [also supported](https://learn.microsoft.com/sql/linux/sql-server-linux-release-notes-2022?view=sql-server-ver17#supported-platforms) on SUSE Linux Enterprise Server (SLES).\\n## Get around directories\\nAfter you choose the appropriate distribution of Linux, familiarize yourself with [getting around Linux directories](https://www.linux.org/threads/getting-around-in-linux-directories.4120/).\\nThis module helps you answer the following questions:\\n1. How to navigate between different files\\n2. How to identify a file from a directory\\n3. Identify different directories\\n## Install new software\\nNext, learn how to [install new software](https://www.linux.org/threads/installing-new-software-debian-red-hat-slackware.4119/) on your new Linux operating system. This module covers installing new software on the Debian and Red Hat Linux operating systems.\"\n", + "Content URL: \"https://learn.microsoft.com/sql/linux/new-to-linux-resources?view=sql-server-ver17\"\n", + "Title: \"How to download and install Linux\"\n", + "\n", + "Content: \"# How to download and install Linux\\n## Step 3 - Follow install method instructions\\n### Install Linux with Windows Subsystem for Linux\\nTo install Linux on a Windows PC, use the install Linux command.\\n1. Open PowerShell or Windows Command Prompt in *administrator mode* by right-clicking and selecting \\\"Run as administrator\\\".\\n2. Enter the Linux install command: `wsl --install`.\\n3. Restart your machine.\\n![WSL Linux Install List Command screenshot](https://learn.microsoft.com/linux/images/wsl-install-list-screenshot.png)\\n#### Install Linux command options\\nBy default, the install command will use the Ubuntu distribution of Linux. You can see the other distributions that are available in the Microsoft Store by entering the command: `wsl -l -o`. If you'd like to install a distribution other than Ubuntu, just add `-d` to the command with the name of the distribution you'd like to install:\\n```powershell\\nwsl --install -d \\n```\\nIf the distribution you want to install isn't available in the Microsoft Store, see the Windows Subsystem for Linux (WSL) documentation on how to [Import any Linux distribution to use with WSL](https://learn.microsoft.com/en-us/windows/wsl/use-custom-distro).\\nFor more information on how to use VS Code, Git, databases, Docker, GUI apps, GPU acceleration, NodesJS, USB devices, or mount a disk, build a custom distribution, manage disk space, or set up WSL for your enterprise business, see the [WSL documentation](https://learn.microsoft.com/en-us/windows/wsl/).\\n### Create a Linux Virtual Machine in the cloud\\nTo create a Linux Virtual Machine (VM) that is hosted in the cloud on Azure, log-in to the Azure Portal and select the “Create a Virtual Machine” service. Then follow the instructions for creating a user name, project, instance, SSH key, ports, and copying the IP address.\\nAzure supports a variety of options for setting up Linux VMs in the cloud. For a quickstart guide on how to set up a Linux VM, choose your preferred method:\\n1. [Azure Portal](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-portal?tabs=ubuntu&source=docs)\\n2. [Azure CLI](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-cli)\\n3. [Azure PowerShell](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-powershell)\\n4. [Terraform](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/quick-create-terraform)\"\n", + "Content URL: \"https://learn.microsoft.com/linux/install#step-3---follow-install-method-instructions\"\n", + "Title: \"How to download and install Linux\"\n", + "\n", + "Content: \"# How to download and install Linux\\n## Step 2 - Choose a Linux distribution\\nA large user community that consistently contributes to the distribution, fixing bugs, adding features, helping with questions, etc. is another good consideration. Ubuntu is one of the most popular distos. openSUSE has been around longer than most other distributions and offers in addition to the stable version (Leap) a rolling version (Tumbleweed). Many other distros rapidly grow popular as the needs and preferences of the user community change. The Linux.org site has resources to help you assess what's available, well-supported, or growing in popularity.\\n![Screenshot of the list of distributions on Linux.org](https://learn.microsoft.com/linux/images/linux-distributions-list-screenshot.png)\"\n", + "Content URL: \"https://learn.microsoft.com/linux/install#step-2---choose-a-linux-distribution\"\n", + "Title: \"Part 1.2 - Basic operations on Linux\"\n", + "\n", + "Content: \"# Part 1.2 - Basic operations on Linux\\n## Run commands as a superuser\\nTo become the root account user, run the `sudo su` command.\\n![Screenshot of sudo su command.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/sudo-su-command.png)\\nYou should notice two things: When you become root, the dollar sign character (`$`) becomes a pound sign character (`#`). Also, the username is changed to **root**. If you run the same `id` command again, you see that the user and group ID of root are **0**.\\n![Screenshot of root command.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/root-command.png)\\nImportant\\nNow you have elevated yourself to the \\\"superuser\\\" role in your session, you have complete access to the system. Keep in mind that this is a potentially dangerous situation, and you should exercise caution as you continue.\\nNow, run the same command one more time:\\n```bash\\necho hello world > /etc/helloworld.txt\\n```\\nThis time, you don't receive any error message. To verify if the file is created, run the following command:\\n```bash\\nll /etc/hello*\\n```\\nThe `ll` command lists the files and subfolders that are in a specified folder. In this example, the `/etc/hello*` parameter value limits the command output to the files or folders whose name begins in `hello`.\\n![Screenshot of hello command.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/hello-command.png)\\nThe output shows that the file was created. What if we want to examine the content of the file? The `cat` command helps here. To see the content, run `cat /etc/helloworld.txt`.\\n![Screenshot of cat command.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/cat-command.png)\\nThe `cat` command reads files sequentially, and writes their content to the standard output. Therefore, it writes \\\"hello world\\\" in the console.\\nTo prevent mistakes that could damage the system while you're elevated to root status, it's a good practice to exit the root session and return to your standard user session to avoid some dangerous operations. To do this, run `exit`. You can see that the pound sign reverts to a dollar sign, and the username is displayed as your standard user.\\n![Screenshot of exit command.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/exit-command.png)\\nFor additional practice, run the same `ll` and `cat` commands within your standard user context. You'll see that you can't create the file in the */etc/* folder, but can you list the file and read the contents.\"\n", + "Content URL: \"https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#run-commands-as-a-superuser\"\n", + "Title: \"Part 1.2 - Basic operations on Linux\"\n", + "\n", + "Content: \"# Part 1.2 - Basic operations on Linux\\n## Remove packages\\nFinding the file doesn't involve package managers. Instead, you'll run a standard search for files in Linux. There are several ways to do so. One of the most common search commands is `find`. However, this command is also confusing to use, so we won't discuss it here. Instead, we'll use `whereis`.\\nNote\\nLinux is very well documented. Nearly every command has a useful help page available. Use the [man](https://www.geeksforgeeks.org/man-command-in-linux-with-examples/) and [info](https://www.geeksforgeeks.org/info-command-in-linux-with-examples/) commands to show the Help pages in Linux. For example, to learn more on your own about the `find` command, run `man find` or `info find`.\\nAccording to the Help page for `whereis`, the definition is as follows:\\n`whereis` locates the binary, source, and manual files for the specified command names.\\nIf you run `whereis apache2`, you should find the */etc/apache2* installation.\\n![Screenshot of whereis apache2 command.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/whereis-apache2-command.png)\\nRemember that the */etc/* directory is where the system configuration files are located. To learn whether \\\"apache2\\\"is a file or folder, run `ll /etc/apache2`. As you can see, this is a folder, and it contains the apache2 configuration files.\\n![Screenshot of ll etc apache2 command.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/ll-etc-apache2-command.png)\\nYou expect `apache2` to contain these files and folders because you used the `apt remove` command that doesn't delete the configuration files.\\nAs final step, run the `apt purge` command to see its effect. You can run a purge to clean up the installation folder even after you remove the package. The following screenshot shows that `whereis` didn't find anything. This is because the `purge` command removed the configuration files together with the binaries.\\n![Screenshot of apt purge command.](https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/media/1-2-linux-special-directories-users-package-managers/apt-purge-command.png)\"\n", + "Content URL: \"https://learn.microsoft.com/troubleshoot/developer/webapps/aspnetcore/practice-troubleshoot-linux/1-2-linux-special-directories-users-package-managers#remove-packages\"\n" + ] + } + ], + "execution_count": 14 } ], "metadata": { diff --git a/samples/simple-streamable-server/build.gradle.kts b/samples/simple-streamable-server/build.gradle.kts index 8cb908e..0296429 100644 --- a/samples/simple-streamable-server/build.gradle.kts +++ b/samples/simple-streamable-server/build.gradle.kts @@ -22,6 +22,15 @@ dependencies { implementation(libs.ktor.server.sse) implementation(libs.ktor.serialization.kotlinx.json) implementation(libs.slf4j.simple) + + testImplementation(libs.mcp.kotlin.client) + testImplementation(libs.ktor.client.cio) + testImplementation(libs.junit.jupiter) + testImplementation(kotlin("test")) +} + +tasks.test { + useJUnitPlatform() } kotlin { diff --git a/samples/simple-streamable-server/gradle/libs.versions.toml b/samples/simple-streamable-server/gradle/libs.versions.toml index 1f893bd..a9fced7 100644 --- a/samples/simple-streamable-server/gradle/libs.versions.toml +++ b/samples/simple-streamable-server/gradle/libs.versions.toml @@ -1,7 +1,8 @@ [versions] kotlin = "2.2.21" ktor = "3.2.3" -mcp-kotlin = "0.9.0" +junit = "6.0.3" +mcp-kotlin = "0.10.0" slf4j = "2.0.17" shadow = "9.4.1" @@ -13,7 +14,10 @@ ktor-server-content-negotiation = { group = "io.ktor", name = "ktor-server-conte ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json" } ktor-server-auth = { group = "io.ktor", name = "ktor-server-auth" } ktor-server-sse = { group = "io.ktor", name = "ktor-server-sse" } +mcp-kotlin-client = { group = "io.modelcontextprotocol", name = "kotlin-sdk-client", version.ref = "mcp-kotlin" } mcp-kotlin-server = { group = "io.modelcontextprotocol", name = "kotlin-sdk-server", version.ref = "mcp-kotlin" } +ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio" } +junit-jupiter = { group = "org.junit.jupiter", name = "junit-jupiter", version.ref = "junit" } slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" } [plugins] diff --git a/samples/simple-streamable-server/src/test/kotlin/io/modelcontextprotocol/sample/server/ServerIntegrationTest.kt b/samples/simple-streamable-server/src/test/kotlin/io/modelcontextprotocol/sample/server/ServerIntegrationTest.kt new file mode 100644 index 0000000..c4dae46 --- /dev/null +++ b/samples/simple-streamable-server/src/test/kotlin/io/modelcontextprotocol/sample/server/ServerIntegrationTest.kt @@ -0,0 +1,146 @@ +package io.modelcontextprotocol.sample.server + +import io.ktor.client.HttpClient +import io.ktor.client.engine.cio.CIO +import io.ktor.client.plugins.sse.SSE +import io.ktor.server.engine.embeddedServer +import io.ktor.server.netty.Netty +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.client.mcpStreamableHttp +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.Role +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import kotlin.test.assertEquals +import kotlin.test.assertIs +import kotlin.test.assertNotNull + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class ServerIntegrationTest { + + private val server = embeddedServer(Netty, host = "127.0.0.1", port = 0) { + configureServer() + } + + private val httpClient = HttpClient(CIO) { + install(SSE) + } + + private lateinit var mcpClient: Client + + @BeforeAll + suspend fun beforeAll() { + server.start(wait = false) + val port = server.engine.resolvedConnectors().single().port + + mcpClient = httpClient.mcpStreamableHttp( + url = "http://127.0.0.1:$port/mcp", + ) + } + + @AfterAll + suspend fun afterAll() { + mcpClient.close() + httpClient.close() + server.stop(500, 1000) + } + + @Test + fun `should negotiate server capabilities`() { + val capabilities = mcpClient.serverCapabilities + assertNotNull(capabilities) + assertNotNull(capabilities.tools) + assertNotNull(capabilities.prompts) + assertNotNull(capabilities.resources) + assertNotNull(capabilities.logging) + } + + @Test + fun `should report server version`() { + val version = mcpClient.serverVersion + assertNotNull(version) + assertEquals("simple-streamable-http-server", version.name) + assertEquals("1.0.0", version.version) + } + + @Test + suspend fun `should list tools`() { + val result = mcpClient.listTools() + val toolNames = result.tools.map { it.name }.sorted() + assertEquals(listOf("greet", "multi-greet"), toolNames) + } + + @Test + suspend fun `should call greet tool`() { + val result = mcpClient.callTool(name = "greet", arguments = mapOf("name" to "Alice")) + val content = result.content.single() + assertIs(content) + assertEquals("Hello, Alice!", content.text) + } + + @Test + suspend fun `should call greet tool with default name`() { + val result = mcpClient.callTool(name = "greet", arguments = emptyMap()) + val content = result.content.single() + assertIs(content) + assertEquals("Hello, World!", content.text) + } + + @Test + suspend fun `should call multi-greet tool with logging notifications`() { + val result = mcpClient.callTool(name = "multi-greet", arguments = mapOf("name" to "Bob")) + val content = result.content.single() + assertIs(content) + assertEquals("Good morning, Bob!", content.text) + } + + @Test + suspend fun `should list prompts`() { + val result = mcpClient.listPrompts() + assertEquals(listOf("greeting-template"), result.prompts.map { it.name }) + } + + @Test + suspend fun `should get greeting template prompt`() { + val result = mcpClient.getPrompt( + GetPromptRequest( + GetPromptRequestParams( + name = "greeting-template", + arguments = mapOf("name" to "Charlie"), + ), + ), + ) + val message = result.messages.single() + assertEquals(Role.User, message.role) + val content = message.content + assertIs(content) + assertEquals("Please greet Charlie in a friendly manner.", content.text) + } + + @Test + suspend fun `should list resources`() { + val result = mcpClient.listResources() + assertEquals(listOf("Default Greeting"), result.resources.map { it.name }) + } + + @Test + suspend fun `should read default greeting resource`() { + val result = mcpClient.readResource( + ReadResourceRequest( + ReadResourceRequestParams(uri = "https://example.com/greetings/default"), + ), + ) + val content = result.contents.single() + assertIs(content) + assertEquals("Hello, world!", content.text) + assertEquals("text/plain", content.mimeType) + } +} + diff --git a/samples/simple-streamable-server/src/test/resources/simplelogger.properties b/samples/simple-streamable-server/src/test/resources/simplelogger.properties new file mode 100644 index 0000000..ad2d3de --- /dev/null +++ b/samples/simple-streamable-server/src/test/resources/simplelogger.properties @@ -0,0 +1,6 @@ +org.slf4j.simpleLogger.defaultLogLevel=INFO +org.slf4j.simpleLogger.showThreadName=true +org.slf4j.simpleLogger.showDateTime=false + +# Log level for specific packages or classes +org.slf4j.simpleLogger.log.io.modelcontextprotocol.sample.server=DEBUG From fdecf6bdf55119438ae15fcfc1e33a6ea634ec85 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Wed, 1 Apr 2026 12:20:12 +0200 Subject: [PATCH 213/449] feat: add URL mode elicitation and typed schema definitions per MCP (#660) Add URL mode elicitation support and typed `PrimitiveSchemaDefinition` hierarchy to align with MCP specification 2025-11-25 closes #540 ## How Has This Been Tested? unit/integration/conformance ## Breaking Changes - `ElicitRequestParams` changed from `data class` to `sealed interface`. A deprecated factory function preserves source compatibility for `ElicitRequestParams(message, requestedSchema)`. - `RequestedSchema.properties` changed from `JsonObject` to `Map`. - `ElicitRequest.requestedSchema` is deprecated, use `(params as ElicitRequestFormParams).requestedSchema`. ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [x] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- conformance-test/run-conformance.sh | 2 +- .../sdk/conformance/ConformanceTools.kt | 296 +++------ .../kotlin/sdk/client/ClientTest.kt | 18 +- kotlin-sdk-core/api/kotlin-sdk-core.api | 570 +++++++++++++++++- .../kotlin/sdk/types/elicitation.dsl.kt | 6 +- .../kotlin/sdk/types/elicitation.kt | 137 ++++- .../kotlin/sdk/types/schema.kt | 292 +++++++++ .../kotlin/sdk/types/serializers.kt | 78 +++ .../kotlin/sdk/types/ElicitationTest.kt | 232 ++++--- .../kotlin/sdk/types/PrimitiveSchemaTest.kt | 481 +++++++++++++++ .../sdk/types/dsl/ElicitationDslTest.kt | 19 +- kotlin-sdk-server/api/kotlin-sdk-server.api | 7 + .../kotlin/sdk/server/ClientConnection.kt | 48 +- .../kotlin/sdk/server/Server.kt | 24 + .../kotlin/sdk/server/ServerSession.kt | 18 + 15 files changed, 1841 insertions(+), 387 deletions(-) create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/schema.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PrimitiveSchemaTest.kt diff --git a/conformance-test/run-conformance.sh b/conformance-test/run-conformance.sh index 8f15ac0..279254e 100755 --- a/conformance-test/run-conformance.sh +++ b/conformance-test/run-conformance.sh @@ -9,7 +9,7 @@ set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" || exit 1; pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." || exit 1; pwd)" -CONFORMANCE_VERSION="0.1.15" +CONFORMANCE_VERSION="0.1.16" PORT="${MCP_PORT:-3001}" SERVER_URL="http://localhost:${PORT}/mcp" RESULTS_DIR="$SCRIPT_DIR/results" diff --git a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTools.kt b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTools.kt index 579b222..54d5c8c 100644 --- a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTools.kt +++ b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTools.kt @@ -3,12 +3,17 @@ package io.modelcontextprotocol.kotlin.sdk.conformance import io.github.oshai.kotlinlogging.KotlinLogging import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.types.AudioContent +import io.modelcontextprotocol.kotlin.sdk.types.BooleanSchema import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.DoubleSchema import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestParams import io.modelcontextprotocol.kotlin.sdk.types.EmbeddedResource +import io.modelcontextprotocol.kotlin.sdk.types.EnumOption import io.modelcontextprotocol.kotlin.sdk.types.ImageContent +import io.modelcontextprotocol.kotlin.sdk.types.IntegerSchema +import io.modelcontextprotocol.kotlin.sdk.types.LegacyTitledEnumSchema import io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel import io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotification import io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotificationParams @@ -16,11 +21,15 @@ import io.modelcontextprotocol.kotlin.sdk.types.ProgressNotification import io.modelcontextprotocol.kotlin.sdk.types.ProgressNotificationParams import io.modelcontextprotocol.kotlin.sdk.types.Role import io.modelcontextprotocol.kotlin.sdk.types.SamplingMessage +import io.modelcontextprotocol.kotlin.sdk.types.StringSchema import io.modelcontextprotocol.kotlin.sdk.types.TextContent import io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents +import io.modelcontextprotocol.kotlin.sdk.types.TitledMultiSelectEnumSchema +import io.modelcontextprotocol.kotlin.sdk.types.TitledSingleSelectEnumSchema import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema +import io.modelcontextprotocol.kotlin.sdk.types.UntitledMultiSelectEnumSchema +import io.modelcontextprotocol.kotlin.sdk.types.UntitledSingleSelectEnumSchema import kotlinx.coroutines.delay -import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.double @@ -187,22 +196,10 @@ fun Server.registerConformanceTools() { ) { request -> val message = request.arguments?.get("message")?.jsonPrimitive?.content ?: "Please provide input" val schema = ElicitRequestParams.RequestedSchema( - properties = buildJsonObject { - put( - "username", - buildJsonObject { - put("type", JsonPrimitive("string")) - put("description", JsonPrimitive("User's response")) - }, - ) - put( - "email", - buildJsonObject { - put("type", JsonPrimitive("string")) - put("description", JsonPrimitive("User's email address")) - }, - ) - }, + properties = mapOf( + "username" to StringSchema(description = "User's response"), + "email" to StringSchema(description = "User's email address"), + ), required = listOf("username", "email"), ) val result = createElicitation(message, schema) @@ -215,54 +212,17 @@ fun Server.registerConformanceTools() { description = "test_elicitation_sep1034_defaults", ) { val schema = ElicitRequestParams.RequestedSchema( - properties = buildJsonObject { - put( - "name", - buildJsonObject { - put("type", JsonPrimitive("string")) - put("description", JsonPrimitive("User name")) - put("default", JsonPrimitive("John Doe")) - }, - ) - put( - "age", - buildJsonObject { - put("type", JsonPrimitive("integer")) - put("description", JsonPrimitive("User age")) - put("default", JsonPrimitive(30)) - }, - ) - put( - "score", - buildJsonObject { - put("type", JsonPrimitive("number")) - put("description", JsonPrimitive("User score")) - put("default", JsonPrimitive(95.5)) - }, - ) - put( - "status", - buildJsonObject { - put("type", JsonPrimitive("string")) - put("description", JsonPrimitive("User status")) - put("default", JsonPrimitive("active")) - put( - "enum", - JsonArray( - listOf(JsonPrimitive("active"), JsonPrimitive("inactive"), JsonPrimitive("pending")), - ), - ) - }, - ) - put( - "verified", - buildJsonObject { - put("type", JsonPrimitive("boolean")) - put("description", JsonPrimitive("Verification status")) - put("default", JsonPrimitive(true)) - }, - ) - }, + properties = mapOf( + "name" to StringSchema(description = "User name", default = "John Doe"), + "age" to IntegerSchema(description = "User age", default = 30), + "score" to DoubleSchema(description = "User score", default = 95.5), + "status" to UntitledSingleSelectEnumSchema( + description = "User status", + enumValues = listOf("active", "inactive", "pending"), + default = "active", + ), + "verified" to BooleanSchema(description = "Verification status", default = true), + ), required = listOf("name", "age", "score", "status", "verified"), ) val result = createElicitation( @@ -278,122 +238,43 @@ fun Server.registerConformanceTools() { description = "test_elicitation_sep1330_enums", ) { val schema = ElicitRequestParams.RequestedSchema( - properties = buildJsonObject { + properties = mapOf( // Untitled single-select - put( - "untitledSingle", - buildJsonObject { - put("type", JsonPrimitive("string")) - put( - "enum", - JsonArray( - listOf(JsonPrimitive("option1"), JsonPrimitive("option2"), JsonPrimitive("option3")), - ), - ) - }, - ) + "untitledSingle" to UntitledSingleSelectEnumSchema( + enumValues = listOf("option1", "option2", "option3"), + ), // Titled single-select - put( - "titledSingle", - buildJsonObject { - put("type", JsonPrimitive("string")) - put( - "oneOf", - JsonArray( - listOf( - buildJsonObject { - put("const", JsonPrimitive("value1")) - put("title", JsonPrimitive("First Option")) - }, - buildJsonObject { - put("const", JsonPrimitive("value2")) - put("title", JsonPrimitive("Second Option")) - }, - buildJsonObject { - put("const", JsonPrimitive("value3")) - put("title", JsonPrimitive("Third Option")) - }, - ), - ), - ) - }, - ) - // Legacy titled (deprecated) - uses enum + enumNames arrays - put( - "legacyEnum", - buildJsonObject { - put("type", JsonPrimitive("string")) - put( - "enum", - JsonArray(listOf(JsonPrimitive("opt1"), JsonPrimitive("opt2"), JsonPrimitive("opt3"))), - ) - put( - "enumNames", - JsonArray( - listOf( - JsonPrimitive("Option One"), - JsonPrimitive("Option Two"), - JsonPrimitive("Option Three"), - ), - ), - ) - }, - ) + "titledSingle" to TitledSingleSelectEnumSchema( + oneOf = listOf( + EnumOption(const = "value1", title = "First Option"), + EnumOption(const = "value2", title = "Second Option"), + EnumOption(const = "value3", title = "Third Option"), + ), + ), + // Legacy titled (deprecated) — uses enum + enumNames + @Suppress("DEPRECATION") + "legacyEnum" + to LegacyTitledEnumSchema( + enumValues = listOf("opt1", "opt2", "opt3"), + enumNames = listOf("Option One", "Option Two", "Option Three"), + ), // Untitled multi-select - put( - "untitledMulti", - buildJsonObject { - put("type", JsonPrimitive("array")) - put( - "items", - buildJsonObject { - put("type", JsonPrimitive("string")) - put( - "enum", - JsonArray( - listOf( - JsonPrimitive("option1"), - JsonPrimitive("option2"), - JsonPrimitive("option3"), - ), - ), - ) - }, - ) - }, - ) - // Titled multi-select - uses items.anyOf with const/title pairs - put( - "titledMulti", - buildJsonObject { - put("type", JsonPrimitive("array")) - put( - "items", - buildJsonObject { - put( - "anyOf", - JsonArray( - listOf( - buildJsonObject { - put("const", JsonPrimitive("value1")) - put("title", JsonPrimitive("First Choice")) - }, - buildJsonObject { - put("const", JsonPrimitive("value2")) - put("title", JsonPrimitive("Second Choice")) - }, - buildJsonObject { - put("const", JsonPrimitive("value3")) - put("title", JsonPrimitive("Third Choice")) - }, - ), - ), - ) - }, - ) - }, - ) - }, + "untitledMulti" to UntitledMultiSelectEnumSchema( + items = UntitledMultiSelectEnumSchema.Items( + enumValues = listOf("option1", "option2", "option3"), + ), + ), + // Titled multi-select + "titledMulti" to TitledMultiSelectEnumSchema( + items = TitledMultiSelectEnumSchema.Items( + anyOf = listOf( + EnumOption(const = "value1", title = "First Choice"), + EnumOption(const = "value2", title = "Second Choice"), + EnumOption(const = "value3", title = "Third Choice"), + ), + ), + ), + ), ) val result = createElicitation( "Please review and update the form fields with defaults", @@ -538,54 +419,17 @@ fun Server.registerConformanceTools() { description = "test_client_elicitation_defaults", ) { val schema = ElicitRequestParams.RequestedSchema( - properties = buildJsonObject { - put( - "name", - buildJsonObject { - put("type", JsonPrimitive("string")) - put("description", JsonPrimitive("User name")) - put("default", JsonPrimitive("John Doe")) - }, - ) - put( - "age", - buildJsonObject { - put("type", JsonPrimitive("integer")) - put("description", JsonPrimitive("User age")) - put("default", JsonPrimitive(30)) - }, - ) - put( - "score", - buildJsonObject { - put("type", JsonPrimitive("number")) - put("description", JsonPrimitive("User score")) - put("default", JsonPrimitive(95.5)) - }, - ) - put( - "status", - buildJsonObject { - put("type", JsonPrimitive("string")) - put("description", JsonPrimitive("User status")) - put("default", JsonPrimitive("active")) - put( - "enum", - JsonArray( - listOf(JsonPrimitive("active"), JsonPrimitive("inactive"), JsonPrimitive("pending")), - ), - ) - }, - ) - put( - "verified", - buildJsonObject { - put("type", JsonPrimitive("boolean")) - put("description", JsonPrimitive("Verification status")) - put("default", JsonPrimitive(true)) - }, - ) - }, + properties = mapOf( + "name" to StringSchema(description = "User name", default = "John Doe"), + "age" to IntegerSchema(description = "User age", default = 30), + "score" to DoubleSchema(description = "User score", default = 95.5), + "status" to UntitledSingleSelectEnumSchema( + description = "User status", + enumValues = listOf("active", "inactive", "pending"), + default = "active", + ), + "verified" to BooleanSchema(description = "Verification status", default = true), + ), required = emptyList(), ) val result = createElicitation( diff --git a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt index 9d51236..1d84732 100644 --- a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt +++ b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt @@ -10,6 +10,7 @@ import io.modelcontextprotocol.kotlin.sdk.shared.TransportSendOptions import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageResult +import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestFormParams import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestParams import io.modelcontextprotocol.kotlin.sdk.types.ElicitResult import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject @@ -35,6 +36,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.Root import io.modelcontextprotocol.kotlin.sdk.types.RootsListChangedNotification import io.modelcontextprotocol.kotlin.sdk.types.SUPPORTED_PROTOCOL_VERSIONS import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.StringSchema import io.modelcontextprotocol.kotlin.sdk.types.TextContent import io.modelcontextprotocol.kotlin.sdk.types.Tool import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema @@ -48,7 +50,6 @@ import kotlinx.coroutines.test.runTest import kotlinx.coroutines.withTimeout import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.put -import kotlinx.serialization.json.putJsonObject import kotlin.coroutines.cancellation.CancellationException import kotlin.test.Test import kotlin.test.assertEquals @@ -953,11 +954,7 @@ class ClientTest { serverSession.createElicitation( message = "Please provide your GitHub username", requestedSchema = ElicitRequestParams.RequestedSchema( - properties = buildJsonObject { - putJsonObject("name") { - put("type", "string") - } - }, + properties = mapOf("name" to StringSchema()), required = listOf("name"), ), ) @@ -1061,11 +1058,7 @@ class ClientTest { val elicitationMessage = "Please provide your GitHub username" val requestedSchema = ElicitRequestParams.RequestedSchema( - properties = buildJsonObject { - putJsonObject("name") { - put("type", "string") - } - }, + properties = mapOf("name" to StringSchema()), required = listOf("name"), ) @@ -1076,7 +1069,8 @@ class ClientTest { client.setElicitationHandler { request -> assertEquals(elicitationMessage, request.params.message) - assertEquals(requestedSchema, request.params.requestedSchema) + val formParams = request.params as ElicitRequestFormParams + assertEquals(requestedSchema, formParams.requestedSchema) ElicitResult( action = elicitationResultAction, diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index e7cd0b8..0b81586 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -336,6 +336,40 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/BlobResourceContents public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/BooleanSchema : io/modelcontextprotocol/kotlin/sdk/types/PrimitiveSchemaDefinition { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/BooleanSchema$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/Boolean; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)Lio/modelcontextprotocol/kotlin/sdk/types/BooleanSchema; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/BooleanSchema;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/BooleanSchema; + public fun equals (Ljava/lang/Object;)Z + public final fun getDefault ()Ljava/lang/Boolean; + public final fun getDescription ()Ljava/lang/String; + public final fun getTitle ()Ljava/lang/String; + public final fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/BooleanSchema$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/BooleanSchema$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/BooleanSchema; + 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/BooleanSchema;)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/BooleanSchema$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/CallToolRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequest$Companion; public fun (Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams;)V @@ -1134,6 +1168,44 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CustomRequest$Compan public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/DoubleSchema : io/modelcontextprotocol/kotlin/sdk/types/NumberSchemaDefinition { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/DoubleSchema$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Double;Ljava/lang/Double;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Double;Ljava/lang/Double;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/Double; + public final fun component4 ()Ljava/lang/Double; + public final fun component5 ()Ljava/lang/Double; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Double;Ljava/lang/Double;)Lio/modelcontextprotocol/kotlin/sdk/types/DoubleSchema; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/DoubleSchema;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Double;Ljava/lang/Double;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/DoubleSchema; + public fun equals (Ljava/lang/Object;)Z + public final fun getDefault ()Ljava/lang/Double; + public final fun getDescription ()Ljava/lang/String; + public final fun getMaximum ()Ljava/lang/Double; + public final fun getMinimum ()Ljava/lang/Double; + public final fun getTitle ()Ljava/lang/String; + public final fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/DoubleSchema$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/DoubleSchema$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/DoubleSchema; + 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/DoubleSchema;)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/DoubleSchema$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequest : io/modelcontextprotocol/kotlin/sdk/types/ServerRequest { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequest$Companion; public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams;)V @@ -1176,49 +1248,63 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestBuilder public final fun setMessage (Ljava/lang/String;)V } -public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams { - public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$Companion; - public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V +public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestFormParams : io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestFormParams$Companion; + public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema; - public final fun component3-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; - public final fun copy-EDEVuBg (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams; - public static synthetic fun copy-EDEVuBg$default (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata; + public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema; + public final fun component4-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun copy-6R815Ik (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestFormParams; + public static synthetic fun copy-6R815Ik$default (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestFormParams;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestFormParams; public fun equals (Ljava/lang/Object;)Z - public final fun getMessage ()Ljava/lang/String; + public fun getMessage ()Ljava/lang/String; public fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun getMode ()Ljava/lang/String; public final fun getRequestedSchema ()Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema; + public final fun getTask ()Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata; public fun hashCode ()I public fun toString ()Ljava/lang/String; } -public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$$serializer; +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestFormParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestFormParams$$serializer; public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestFormParams; 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/ElicitRequestParams;)V + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestFormParams;)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/ElicitRequestFormParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$Companion; + public abstract fun getMessage ()Ljava/lang/String; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema$Companion; - public fun (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;)V - public synthetic fun (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lkotlinx/serialization/json/JsonObject; - public final fun component2 ()Ljava/util/List; - public final fun copy (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema; + public fun (Ljava/lang/String;Ljava/util/Map;Ljava/util/List;)V + public synthetic fun (Ljava/lang/String;Ljava/util/Map;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/Map; + public final fun component3 ()Ljava/util/List; + public final fun copy (Ljava/lang/String;Ljava/util/Map;Ljava/util/List;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Ljava/lang/String;Ljava/util/Map;Ljava/util/List;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema; public fun equals (Ljava/lang/Object;)Z - public final fun getProperties ()Lkotlinx/serialization/json/JsonObject; + public final fun getProperties ()Ljava/util/Map; public final fun getRequired ()Ljava/util/List; + public final fun getSchema ()Ljava/lang/String; public final fun getType ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; @@ -1239,6 +1325,43 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$ public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestURLParams : io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestURLParams$Companion; + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata; + public final fun component5-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun copy-4asOmiE (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestURLParams; + public static synthetic fun copy-4asOmiE$default (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestURLParams;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestURLParams; + public fun equals (Ljava/lang/Object;)Z + public final fun getElicitationId ()Ljava/lang/String; + public fun getMessage ()Ljava/lang/String; + public fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun getMode ()Ljava/lang/String; + public final fun getTask ()Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata; + public final fun getUrl ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestURLParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestURLParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestURLParams; + 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/ElicitRequestURLParams;)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/ElicitRequestURLParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestedSchemaBuilder { public fun ()V public final fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema; @@ -1294,6 +1417,11 @@ 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/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; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/Elicitation_dslKt { public static final fun buildElicitRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequest; } @@ -1360,6 +1488,43 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/EmptyResult$Companio public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/EnumOption { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/EnumOption$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/EnumOption; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/EnumOption;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/EnumOption; + public fun equals (Ljava/lang/Object;)Z + public final fun getConst ()Ljava/lang/String; + public final fun getTitle ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/EnumOption$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/EnumOption$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/EnumOption; + 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/EnumOption;)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/EnumOption$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/EnumSchemaDefinition : io/modelcontextprotocol/kotlin/sdk/types/PrimitiveSchemaDefinition { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/EnumSchemaDefinition$Companion; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/EnumSchemaDefinition$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest$Companion; public fun (Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequestParams;)V @@ -1955,6 +2120,44 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/InitializedNotificat public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/IntegerSchema : io/modelcontextprotocol/kotlin/sdk/types/NumberSchemaDefinition { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/IntegerSchema$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/Integer; + public final fun component4 ()Ljava/lang/Integer; + public final fun component5 ()Ljava/lang/Integer; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;)Lio/modelcontextprotocol/kotlin/sdk/types/IntegerSchema; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/IntegerSchema;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/IntegerSchema; + public fun equals (Ljava/lang/Object;)Z + public final fun getDefault ()Ljava/lang/Integer; + public final fun getDescription ()Ljava/lang/String; + public final fun getMaximum ()Ljava/lang/Integer; + public final fun getMinimum ()Ljava/lang/Integer; + public final fun getTitle ()Ljava/lang/String; + public final fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/IntegerSchema$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/IntegerSchema$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/IntegerSchema; + 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/IntegerSchema;)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/IntegerSchema$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/JSONRPCEmptyMessage : io/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage { public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCEmptyMessage; public fun equals (Ljava/lang/Object;)Z @@ -2119,6 +2322,43 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/JsonUtilsKt { public static final fun toJson (Ljava/util/Map;)Ljava/util/Map; } +public final class io/modelcontextprotocol/kotlin/sdk/types/LegacyTitledEnumSchema : io/modelcontextprotocol/kotlin/sdk/types/EnumSchemaDefinition { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/LegacyTitledEnumSchema$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/util/List; + public final fun component4 ()Ljava/util/List; + public final fun component5 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/LegacyTitledEnumSchema; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/LegacyTitledEnumSchema;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/LegacyTitledEnumSchema; + public fun equals (Ljava/lang/Object;)Z + public final fun getDefault ()Ljava/lang/String; + public final fun getDescription ()Ljava/lang/String; + public final fun getEnumNames ()Ljava/util/List; + public final fun getEnumValues ()Ljava/util/List; + public final fun getTitle ()Ljava/lang/String; + public final fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/LegacyTitledEnumSchema$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/LegacyTitledEnumSchema$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/LegacyTitledEnumSchema; + 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/LegacyTitledEnumSchema;)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/LegacyTitledEnumSchema$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest, io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequest { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest$Companion; public fun ()V @@ -2796,6 +3036,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ModelPreferences$Com public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/MultiSelectEnumSchema : io/modelcontextprotocol/kotlin/sdk/types/EnumSchemaDefinition { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/MultiSelectEnumSchema$Companion; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/MultiSelectEnumSchema$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/Notification { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Notification$Companion; public abstract fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; @@ -2814,6 +3062,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/NotificationParams$C public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/NumberSchemaDefinition : io/modelcontextprotocol/kotlin/sdk/types/PrimitiveSchemaDefinition { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/NumberSchemaDefinition$Companion; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/NumberSchemaDefinition$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequest : io/modelcontextprotocol/kotlin/sdk/types/Request { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedRequest$Companion; public fun getCursor ()Ljava/lang/String; @@ -2917,6 +3173,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/PingRequest_dslKt { public static final fun buildPingRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/PingRequest; } +public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/PrimitiveSchemaDefinition { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/PrimitiveSchemaDefinition$Companion; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/PrimitiveSchemaDefinition$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/Progress { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Progress$Companion; public fun (DLjava/lang/Double;Ljava/lang/String;)V @@ -4141,6 +4405,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/SetLevelRequestParam public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/SingleSelectEnumSchema : io/modelcontextprotocol/kotlin/sdk/types/EnumSchemaDefinition { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/SingleSelectEnumSchema$Companion; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/SingleSelectEnumSchema$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/StopReason { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/StopReason$Companion; public static final synthetic fun box-impl (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/StopReason; @@ -4174,6 +4446,61 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/StopReason$Companion public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/StringSchema : io/modelcontextprotocol/kotlin/sdk/types/PrimitiveSchemaDefinition { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/StringSchema$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Lio/modelcontextprotocol/kotlin/sdk/types/StringSchemaFormat;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Lio/modelcontextprotocol/kotlin/sdk/types/StringSchemaFormat;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/Integer; + public final fun component4 ()Ljava/lang/Integer; + public final fun component5 ()Lio/modelcontextprotocol/kotlin/sdk/types/StringSchemaFormat; + public final fun component6 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Lio/modelcontextprotocol/kotlin/sdk/types/StringSchemaFormat;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/StringSchema; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/StringSchema;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Lio/modelcontextprotocol/kotlin/sdk/types/StringSchemaFormat;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/StringSchema; + public fun equals (Ljava/lang/Object;)Z + public final fun getDefault ()Ljava/lang/String; + public final fun getDescription ()Ljava/lang/String; + public final fun getFormat ()Lio/modelcontextprotocol/kotlin/sdk/types/StringSchemaFormat; + public final fun getMaxLength ()Ljava/lang/Integer; + public final fun getMinLength ()Ljava/lang/Integer; + public final fun getTitle ()Ljava/lang/String; + public final fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/StringSchema$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/StringSchema$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/StringSchema; + 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/StringSchema;)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/StringSchema$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/StringSchemaFormat : java/lang/Enum { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/StringSchemaFormat$Companion; + public static final field Date Lio/modelcontextprotocol/kotlin/sdk/types/StringSchemaFormat; + public static final field DateTime Lio/modelcontextprotocol/kotlin/sdk/types/StringSchemaFormat; + public static final field Email Lio/modelcontextprotocol/kotlin/sdk/types/StringSchemaFormat; + public static final field Uri Lio/modelcontextprotocol/kotlin/sdk/types/StringSchemaFormat; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/StringSchemaFormat; + public static fun values ()[Lio/modelcontextprotocol/kotlin/sdk/types/StringSchemaFormat; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/StringSchemaFormat$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest$Companion; public fun (Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequestParams;)V @@ -4503,6 +4830,107 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/TextResourceContents public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/TitledMultiSelectEnumSchema : io/modelcontextprotocol/kotlin/sdk/types/MultiSelectEnumSchema { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/TitledMultiSelectEnumSchema$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Lio/modelcontextprotocol/kotlin/sdk/types/TitledMultiSelectEnumSchema$Items;Ljava/util/List;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Lio/modelcontextprotocol/kotlin/sdk/types/TitledMultiSelectEnumSchema$Items;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/Integer; + public final fun component4 ()Ljava/lang/Integer; + public final fun component5 ()Lio/modelcontextprotocol/kotlin/sdk/types/TitledMultiSelectEnumSchema$Items; + public final fun component6 ()Ljava/util/List; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Lio/modelcontextprotocol/kotlin/sdk/types/TitledMultiSelectEnumSchema$Items;Ljava/util/List;)Lio/modelcontextprotocol/kotlin/sdk/types/TitledMultiSelectEnumSchema; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/TitledMultiSelectEnumSchema;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Lio/modelcontextprotocol/kotlin/sdk/types/TitledMultiSelectEnumSchema$Items;Ljava/util/List;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/TitledMultiSelectEnumSchema; + public fun equals (Ljava/lang/Object;)Z + public final fun getDefault ()Ljava/util/List; + public final fun getDescription ()Ljava/lang/String; + public final fun getItems ()Lio/modelcontextprotocol/kotlin/sdk/types/TitledMultiSelectEnumSchema$Items; + public final fun getMaxItems ()Ljava/lang/Integer; + public final fun getMinItems ()Ljava/lang/Integer; + public final fun getTitle ()Ljava/lang/String; + public final fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/TitledMultiSelectEnumSchema$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/TitledMultiSelectEnumSchema$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/TitledMultiSelectEnumSchema; + 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/TitledMultiSelectEnumSchema;)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/TitledMultiSelectEnumSchema$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/TitledMultiSelectEnumSchema$Items { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/TitledMultiSelectEnumSchema$Items$Companion; + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lio/modelcontextprotocol/kotlin/sdk/types/TitledMultiSelectEnumSchema$Items; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/TitledMultiSelectEnumSchema$Items;Ljava/util/List;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/TitledMultiSelectEnumSchema$Items; + public fun equals (Ljava/lang/Object;)Z + public final fun getAnyOf ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/TitledMultiSelectEnumSchema$Items$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/TitledMultiSelectEnumSchema$Items$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/TitledMultiSelectEnumSchema$Items; + 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/TitledMultiSelectEnumSchema$Items;)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/TitledMultiSelectEnumSchema$Items$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/TitledSingleSelectEnumSchema : io/modelcontextprotocol/kotlin/sdk/types/SingleSelectEnumSchema { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/TitledSingleSelectEnumSchema$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/util/List; + public final fun component4 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/TitledSingleSelectEnumSchema; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/TitledSingleSelectEnumSchema;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/TitledSingleSelectEnumSchema; + public fun equals (Ljava/lang/Object;)Z + public final fun getDefault ()Ljava/lang/String; + public final fun getDescription ()Ljava/lang/String; + public final fun getOneOf ()Ljava/util/List; + public final fun getTitle ()Ljava/lang/String; + public final fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/TitledSingleSelectEnumSchema$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/TitledSingleSelectEnumSchema$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/TitledSingleSelectEnumSchema; + 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/TitledSingleSelectEnumSchema;)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/TitledSingleSelectEnumSchema$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/Tool : io/modelcontextprotocol/kotlin/sdk/types/WithMeta { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Tool$Companion; public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ToolExecution;Lkotlinx/serialization/json/JsonObject;)V @@ -4793,6 +5221,108 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequestPa public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/UntitledMultiSelectEnumSchema : io/modelcontextprotocol/kotlin/sdk/types/MultiSelectEnumSchema { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/UntitledMultiSelectEnumSchema$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Lio/modelcontextprotocol/kotlin/sdk/types/UntitledMultiSelectEnumSchema$Items;Ljava/util/List;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Lio/modelcontextprotocol/kotlin/sdk/types/UntitledMultiSelectEnumSchema$Items;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/Integer; + public final fun component4 ()Ljava/lang/Integer; + public final fun component5 ()Lio/modelcontextprotocol/kotlin/sdk/types/UntitledMultiSelectEnumSchema$Items; + public final fun component6 ()Ljava/util/List; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Lio/modelcontextprotocol/kotlin/sdk/types/UntitledMultiSelectEnumSchema$Items;Ljava/util/List;)Lio/modelcontextprotocol/kotlin/sdk/types/UntitledMultiSelectEnumSchema; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/UntitledMultiSelectEnumSchema;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Lio/modelcontextprotocol/kotlin/sdk/types/UntitledMultiSelectEnumSchema$Items;Ljava/util/List;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/UntitledMultiSelectEnumSchema; + public fun equals (Ljava/lang/Object;)Z + public final fun getDefault ()Ljava/util/List; + public final fun getDescription ()Ljava/lang/String; + public final fun getItems ()Lio/modelcontextprotocol/kotlin/sdk/types/UntitledMultiSelectEnumSchema$Items; + public final fun getMaxItems ()Ljava/lang/Integer; + public final fun getMinItems ()Ljava/lang/Integer; + public final fun getTitle ()Ljava/lang/String; + public final fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/UntitledMultiSelectEnumSchema$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/UntitledMultiSelectEnumSchema$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/UntitledMultiSelectEnumSchema; + 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/UntitledMultiSelectEnumSchema;)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/UntitledMultiSelectEnumSchema$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/UntitledMultiSelectEnumSchema$Items { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/UntitledMultiSelectEnumSchema$Items$Companion; + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lio/modelcontextprotocol/kotlin/sdk/types/UntitledMultiSelectEnumSchema$Items; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/UntitledMultiSelectEnumSchema$Items;Ljava/util/List;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/UntitledMultiSelectEnumSchema$Items; + public fun equals (Ljava/lang/Object;)Z + public final fun getEnumValues ()Ljava/util/List; + public final fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/UntitledMultiSelectEnumSchema$Items$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/UntitledMultiSelectEnumSchema$Items$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/UntitledMultiSelectEnumSchema$Items; + 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/UntitledMultiSelectEnumSchema$Items;)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/UntitledMultiSelectEnumSchema$Items$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/UntitledSingleSelectEnumSchema : io/modelcontextprotocol/kotlin/sdk/types/SingleSelectEnumSchema { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/UntitledSingleSelectEnumSchema$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/util/List; + public final fun component4 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/UntitledSingleSelectEnumSchema; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/UntitledSingleSelectEnumSchema;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/UntitledSingleSelectEnumSchema; + public fun equals (Ljava/lang/Object;)Z + public final fun getDefault ()Ljava/lang/String; + public final fun getDescription ()Ljava/lang/String; + public final fun getEnumValues ()Ljava/util/List; + public final fun getTitle ()Ljava/lang/String; + public final fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/UntitledSingleSelectEnumSchema$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/UntitledSingleSelectEnumSchema$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/UntitledSingleSelectEnumSchema; + 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/UntitledSingleSelectEnumSchema;)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/UntitledSingleSelectEnumSchema$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/WithMeta { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/WithMeta$Companion; public abstract fun getMeta ()Lkotlinx/serialization/json/JsonObject; diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.dsl.kt index e398d98..12309fa 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.dsl.kt @@ -4,6 +4,7 @@ import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObjectBuilder import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.decodeFromJsonElement import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract @@ -223,6 +224,9 @@ public class ElicitRequestedSchemaBuilder @PublishedApi internal constructor() { val properties = requireNotNull(properties) { "Missing required field 'properties'. Use properties { put(\"fieldName\", schema) }" } - return ElicitRequestParams.RequestedSchema(properties, required) + val typedProperties: Map = properties.mapValues { (_, value) -> + McpJson.decodeFromJsonElement(value) + } + return ElicitRequestParams.RequestedSchema(properties = typedProperties, required = required) } } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.kt index dfda9bf..fc7787b 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.kt @@ -1,23 +1,20 @@ package io.modelcontextprotocol.kotlin.sdk.types import kotlinx.serialization.EncodeDefault -import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonObject /** - * A request from the server to elicit additional information from the user via the client. + * Represents an `elicitation/create` request from the server to the client. * - * This request type allows servers to prompt users for structured input through forms - * or dialogs presented by the client. The server defines a schema for the requested data, - * and the client presents an appropriate UI to collect this information. + * Supports two modes: form mode ([ElicitRequestFormParams]) for collecting structured data + * in-band, and URL mode ([ElicitRequestURLParams]) for directing the user to an external URL. * - * @property params The elicitation parameters including the message and requested schema. + * @property params The elicitation parameters — either form or URL mode. */ @Serializable public data class ElicitRequest(override val params: ElicitRequestParams) : ServerRequest { - @OptIn(ExperimentalSerializationApi::class) @EncodeDefault public override val method: Method = Method.Defined.ElicitationCreate @@ -30,8 +27,13 @@ public data class ElicitRequest(override val params: ElicitRequestParams) : Serv /** * A restricted subset of JSON Schema defining the structure of the requested data. */ - public val requestedSchema: ElicitRequestParams.RequestedSchema - get() = params.requestedSchema + @Deprecated( + "Use (params as ElicitRequestFormParams).requestedSchema", + ReplaceWith("(params as ElicitRequestFormParams).requestedSchema"), + DeprecationLevel.WARNING, + ) + public val requestedSchema: ElicitRequestParams.RequestedSchema? + get() = (params as? ElicitRequestFormParams)?.requestedSchema /** * Metadata for this request. May include a progressToken for out-of-band progress notifications. @@ -41,23 +43,13 @@ public data class ElicitRequest(override val params: ElicitRequestParams) : Serv } /** - * Parameters for an elicitation/create request. + * Represents the parameters for an `elicitation/create` request. * - * @property message The message to present to the user. This should clearly explain - * what information is being requested and why. - * @property requestedSchema A restricted subset of JSON Schema defining the structure - * of the requested data. Only top-level properties are allowed, - * without nesting. - * @property meta Optional metadata for this request. May include a progressToken for - * out-of-band progress notifications. + * Implementations: [ElicitRequestFormParams], [ElicitRequestURLParams]. */ -@Serializable -public data class ElicitRequestParams( - val message: String, - val requestedSchema: RequestedSchema, - @SerialName("_meta") - override val meta: RequestMeta? = null, -) : RequestParams { +@Serializable(with = ElicitRequestParamsSerializer::class) +public sealed interface ElicitRequestParams : RequestParams { + public val message: String /** * A restricted JSON Schema for elicitation requests. @@ -65,6 +57,7 @@ public data class ElicitRequestParams( * Only supports top-level primitive properties without nesting. Each property * represents a field in the form or dialog presented to the user. * + * @property schema Optional URI to a JSON Schema definition. * @property properties A map of property names to their schema definitions. * Each property must be a primitive type (string, number, boolean). * @property required Optional list of property names that must be provided by the user. @@ -72,24 +65,102 @@ public data class ElicitRequestParams( * @property type Always "object" for elicitation schemas. */ @Serializable - public data class RequestedSchema(val properties: JsonObject, val required: List? = null) { - @OptIn(ExperimentalSerializationApi::class) + public data class RequestedSchema( + @SerialName($$"$schema") + val schema: String? = null, + val properties: Map, + val required: List? = null, + ) { @EncodeDefault val type: String = "object" } } /** - * The client's response to an [ElicitRequest]. + * Creates an [ElicitRequestFormParams] for backwards compatibility. * - * Represents the user's action and, if accepted, the submitted form data. + * @param message The message to present to the user. + * @param requestedSchema The JSON Schema for the requested data. + * @param meta Optional request metadata. + * @return A configured [ElicitRequestFormParams] instance. + */ +@Deprecated( + "Use ElicitRequestFormParams instead", + ReplaceWith("ElicitRequestFormParams(message, requestedSchema = requestedSchema, meta = meta)"), + DeprecationLevel.WARNING, +) +@Suppress("FunctionNaming", "FunctionName") +public fun ElicitRequestParams( + message: String, + requestedSchema: ElicitRequestParams.RequestedSchema, + meta: RequestMeta? = null, +): ElicitRequestFormParams = ElicitRequestFormParams( + message = message, + requestedSchema = requestedSchema, + meta = meta, +) + +/** + * Represents form mode parameters for an `elicitation/create` request. * - * @property action The user action in response to the elicitation prompt. - * @property content The submitted form data, only present when [action] is [Action.Accept]. - * Contains values matching the requested schema, where keys correspond - * to property names and values are primitives (string, number, or boolean). + * Collects non-sensitive structured data from the user via a form presented by the client. + * + * @property message The message to present to the user describing what information is being requested. + * @property task If specified, the caller is requesting task-augmented execution. The request + * will return a [CreateTaskResult] immediately, and the actual result can be retrieved + * later via `tasks/result`. + * @property requestedSchema A restricted subset of JSON Schema. Only top-level properties + * are allowed, without nesting. + * @property meta Optional metadata. May include a progressToken for out-of-band progress notifications. + */ +@Serializable +public data class ElicitRequestFormParams( + override val message: String, + val task: TaskMetadata? = null, + val requestedSchema: ElicitRequestParams.RequestedSchema, + @SerialName("_meta") + override val meta: RequestMeta? = null, +) : ElicitRequestParams { + @EncodeDefault + public val mode: String = "form" +} + +/** + * Represents URL mode parameters for an `elicitation/create` request. + * + * Directs the user to an external URL for out-of-band interactions (e.g., OAuth flows, + * payment processing, or entering sensitive credentials) that must not pass through the MCP client. + * + * @property message The message explaining why the interaction is needed. + * @property elicitationId A unique identifier for this elicitation. The client MUST treat + * this ID as an opaque value. + * @property url The URL that the user should navigate to. + * @property task If specified, the caller is requesting task-augmented execution. The request + * will return a [CreateTaskResult] immediately, and the actual result can be retrieved + * later via `tasks/result`. + * @property meta Optional metadata. May include a progressToken for out-of-band progress notifications. + */ +@Serializable +public data class ElicitRequestURLParams( + override val message: String, + val elicitationId: String, + val url: String, + val task: TaskMetadata? = null, + @SerialName("_meta") + override val meta: RequestMeta? = null, +) : ElicitRequestParams { + @EncodeDefault + public val mode: String = "url" +} + +/** + * Represents the client's response to an [ElicitRequest]. + * + * @property action The user action in response to the elicitation. + * @property content The submitted form data, only present when [action] is [Action.Accept] + * and mode was form. Contains values matching the requested schema. Omitted for + * URL mode responses. * @property meta Optional metadata for this response. - * @throws IllegalArgumentException if content is provided with a non-accept action. */ @Serializable public data class ElicitResult( diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/schema.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/schema.kt new file mode 100644 index 0000000..dda2ac6 --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/schema.kt @@ -0,0 +1,292 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import kotlinx.serialization.EncodeDefault +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Restricted schema definition for elicitation request properties. + * + * Supports primitive types (string, number, integer, boolean) and enum schemas + * (single-select and multi-select). Only flat, top-level properties are allowed — + * arbitrary nested objects or arrays are not supported. + * + * Implementations: [StringSchema], [NumberSchemaDefinition], [BooleanSchema], [EnumSchemaDefinition]. + */ +@Serializable(with = PrimitiveSchemaDefinitionSerializer::class) +public sealed interface PrimitiveSchemaDefinition + +/** + * Defines a string-typed property in an elicitation schema. + * + * @property title Optional display title for the field. + * @property description Optional description for the field. + * @property minLength Minimum string length. + * @property maxLength Maximum string length. + * @property format Optional format constraint (e.g., email, URI, date). + * @property default Optional default value. + */ +@Serializable +public data class StringSchema( + val title: String? = null, + val description: String? = null, + val minLength: Int? = null, + val maxLength: Int? = null, + val format: StringSchemaFormat? = null, + val default: String? = null, +) : PrimitiveSchemaDefinition { + @EncodeDefault + public val type: String = "string" +} + +/** + * Supported format constraints for [StringSchema]. + */ +@Serializable +public enum class StringSchemaFormat { + @SerialName("email") + Email, + + @SerialName("uri") + Uri, + + @SerialName("date") + Date, + + @SerialName("date-time") + DateTime, +} + +/** + * Defines a numeric property in an elicitation schema. + * + * Implementations: [IntegerSchema] (`"integer"`), [DoubleSchema] (`"number"`). + */ +@Serializable +public sealed interface NumberSchemaDefinition : PrimitiveSchemaDefinition + +/** + * Defines an integer-typed property in an elicitation schema. + * + * @property title Optional display title for the field. + * @property description Optional description for the field. + * @property minimum Minimum allowed value. + * @property maximum Maximum allowed value. + * @property default Optional default value. + */ +@Serializable +public data class IntegerSchema( + val title: String? = null, + val description: String? = null, + val minimum: Int? = null, + val maximum: Int? = null, + val default: Int? = null, +) : NumberSchemaDefinition { + @EncodeDefault + val type: String = "integer" +} + +/** + * Defines a floating-point number property in an elicitation schema. + * + * @property title Optional display title for the field. + * @property description Optional description for the field. + * @property minimum Minimum allowed value. + * @property maximum Maximum allowed value. + * @property default Optional default value. + */ +@Serializable +public data class DoubleSchema( + val title: String? = null, + val description: String? = null, + val minimum: Double? = null, + val maximum: Double? = null, + val default: Double? = null, +) : NumberSchemaDefinition { + @EncodeDefault + val type: String = "number" +} + +/** + * Defines a boolean-typed property in an elicitation schema. + * + * @property title Optional display title for the field. + * @property description Optional description for the field. + * @property default Optional default value. + */ +@Serializable +public data class BooleanSchema( + val title: String? = null, + val description: String? = null, + val default: Boolean? = null, +) : PrimitiveSchemaDefinition { + @EncodeDefault + val type: String = "boolean" +} + +/** + * Defines an enumeration property in an elicitation schema. + * + * Implementations: [SingleSelectEnumSchema], [MultiSelectEnumSchema], [LegacyTitledEnumSchema]. + */ +@Serializable +public sealed interface EnumSchemaDefinition : PrimitiveSchemaDefinition + +/** + * Represents an enum option with a value and a display label. + * + * Used in [TitledSingleSelectEnumSchema] and [TitledMultiSelectEnumSchema]. + * + * @property const The enum value. + * @property title Display label for this option. + */ +@Serializable +public data class EnumOption(val const: String, val title: String) + +/** + * Defines a single-selection enumeration property. + * + * Implementations: [UntitledSingleSelectEnumSchema], [TitledSingleSelectEnumSchema]. + */ +@Serializable +public sealed interface SingleSelectEnumSchema : EnumSchemaDefinition + +/** + * Defines a single-selection enumeration without display titles for options. + * + * @property title Optional display title for the field. + * @property description Optional description for the field. + * @property enumValues Array of enum values to choose from. + * @property default Optional default value. + */ +@Serializable +public data class UntitledSingleSelectEnumSchema( + val title: String? = null, + val description: String? = null, + @SerialName("enum") + val enumValues: List, + val default: String? = null, +) : SingleSelectEnumSchema { + @EncodeDefault + val type: String = "string" +} + +/** + * Defines a single-selection enumeration with display titles for each option. + * + * @property title Optional display title for the field. + * @property description Optional description for the field. + * @property oneOf Array of enum options with values and display labels. + * @property default Optional default value. + */ +@Serializable +public data class TitledSingleSelectEnumSchema( + val title: String? = null, + val description: String? = null, + val oneOf: List, + val default: String? = null, +) : SingleSelectEnumSchema { + @EncodeDefault + val type: String = "string" +} + +/** + * Defines a single-selection enumeration with display names via the deprecated `enumNames` array. + * + * Use [TitledSingleSelectEnumSchema] instead. This class will be removed in a future version. + * + * @property title Optional display title for the field. + * @property description Optional description for the field. + * @property enumValues Array of enum values to choose from. + * @property enumNames Display names for enum values. Non-standard according to JSON Schema 2020-12. + * @property default Optional default value. + */ +@Deprecated("Use TitledSingleSelectEnumSchema instead") +@Serializable +public data class LegacyTitledEnumSchema( + val title: String? = null, + val description: String? = null, + @SerialName("enum") + val enumValues: List, + val enumNames: List? = null, + val default: String? = null, +) : EnumSchemaDefinition { + @EncodeDefault + val type: String = "string" +} + +/** + * Defines a multiple-selection enumeration property. + * + * Implementations: [UntitledMultiSelectEnumSchema], [TitledMultiSelectEnumSchema]. + */ +@Serializable +public sealed interface MultiSelectEnumSchema : EnumSchemaDefinition + +/** + * Defines a multiple-selection enumeration without display titles for options. + * + * @property title Optional display title for the field. + * @property description Optional description for the field. + * @property minItems Minimum number of items to select. + * @property maxItems Maximum number of items to select. + * @property items Schema for the array items. + * @property default Optional default value. + */ +@Serializable +public data class UntitledMultiSelectEnumSchema( + val title: String? = null, + val description: String? = null, + val minItems: Int? = null, + val maxItems: Int? = null, + val items: Items, + val default: List? = null, +) : MultiSelectEnumSchema { + @EncodeDefault + val type: String = "array" + + /** + * Schema for the array items with plain enum values. + * + * @property enumValues Array of enum values to choose from. + */ + @Serializable + public data class Items( + @SerialName("enum") + val enumValues: List, + ) { + @EncodeDefault + val type: String = "string" + } +} + +/** + * Defines a multiple-selection enumeration with display titles for each option. + * + * @property title Optional display title for the field. + * @property description Optional description for the field. + * @property minItems Minimum number of items to select. + * @property maxItems Maximum number of items to select. + * @property items Schema for array items with enum options and display labels. + * @property default Optional default value. + */ +@Serializable +public data class TitledMultiSelectEnumSchema( + val title: String? = null, + val description: String? = null, + val minItems: Int? = null, + val maxItems: Int? = null, + val items: Items, + val default: List? = null, +) : MultiSelectEnumSchema { + @EncodeDefault + val type: String = "array" + + /** + * Schema for array items with enum options and display labels. + * + * @property anyOf Array of enum options with values and display labels. + */ + @Serializable + public data class Items(val anyOf: List) +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt index 792c636..7f1c81d 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt @@ -12,6 +12,7 @@ import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.json.JsonContentPolymorphicSerializer import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.contentOrNull import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.longOrNull @@ -420,3 +421,80 @@ internal object RequestIdPolymorphicSerializer : JsonContentPolymorphicSerialize else -> throw SerializationException("Invalid RequestId type: $element") } } + +// ============================================================================ +// ElicitationParams Serializers +// ============================================================================ + +internal object ElicitRequestParamsSerializer : JsonContentPolymorphicSerializer( + ElicitRequestParams::class, +) { + @Suppress("ThrowsCount") + override fun selectDeserializer(element: JsonElement): DeserializationStrategy { + val mode = when (val modeElement = element.jsonObject["mode"]) { + null -> null + + is JsonPrimitive -> { + if (!modeElement.isString) { + throw SerializationException("Invalid 'mode' type: expected string but was: $modeElement") + } + modeElement.contentOrNull + } + + else -> throw SerializationException("Invalid 'mode' type: expected string but was: $modeElement") + } + return when (mode) { + "url" -> ElicitRequestURLParams.serializer() + "form", null -> ElicitRequestFormParams.serializer() + else -> throw SerializationException("Unsupported elicitation mode: '$mode'") + } + } +} + +// ============================================================================ +// PrimitiveSchemaDefinition Serializers +// ============================================================================ + +internal object PrimitiveSchemaDefinitionSerializer : JsonContentPolymorphicSerializer( + PrimitiveSchemaDefinition::class, +) { + @Suppress("ThrowsCount") + override fun selectDeserializer(element: JsonElement): DeserializationStrategy { + val obj = element.jsonObject + val typeElement = obj["type"] + ?: throw SerializationException("Missing 'type' in PrimitiveSchemaDefinition: $element") + val type = if (typeElement is JsonPrimitive && typeElement.isString) { + typeElement.content + } else { + throw SerializationException("Expected 'type' to be a string but was: $typeElement") + } + return when (type) { + "boolean" -> BooleanSchema.serializer() + "integer" -> IntegerSchema.serializer() + "number" -> DoubleSchema.serializer() + "string" -> selectStringTypeDeserializer(obj) + "array" -> selectArrayTypeDeserializer(obj) + else -> throw SerializationException("Unknown PrimitiveSchemaDefinition type: '$type'") + } + } + + private fun selectArrayTypeDeserializer( + obj: Map, + ): DeserializationStrategy { + val items = obj["items"]?.jsonObject + return when { + items != null && "anyOf" in items -> TitledMultiSelectEnumSchema.serializer() + else -> UntitledMultiSelectEnumSchema.serializer() + } + } + + @Suppress("DEPRECATION") + private fun selectStringTypeDeserializer( + obj: Map, + ): DeserializationStrategy = when { + "enumNames" in obj -> LegacyTitledEnumSchema.serializer() + "enum" in obj -> UntitledSingleSelectEnumSchema.serializer() + "oneOf" in obj -> TitledSingleSelectEnumSchema.serializer() + else -> StringSchema.serializer() + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ElicitationTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ElicitationTest.kt index 0026ea0..5392890 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ElicitationTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ElicitationTest.kt @@ -1,58 +1,39 @@ package io.modelcontextprotocol.kotlin.sdk.types import io.kotest.assertions.json.shouldEqualJson +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.nulls.shouldBeNull +import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.shouldBe +import io.kotest.matchers.types.shouldBeInstanceOf import io.modelcontextprotocol.kotlin.test.utils.verifyDeserialization import io.modelcontextprotocol.kotlin.test.utils.verifySerialization import kotlinx.serialization.json.boolean import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.int -import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.put import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertNotNull -import kotlin.test.assertNull class ElicitationTest { + // ── Form mode ─────────────────────────────────────────────────────── + @Test - @Suppress("LongMethod") - fun `should serialize ElicitRequest with requested schema`() { + fun `should serialize form mode ElicitRequest`() { val request = ElicitRequest( - ElicitRequestParams( + ElicitRequestFormParams( message = "Provide repository details", requestedSchema = ElicitRequestParams.RequestedSchema( - properties = buildJsonObject { - put( - "owner", - buildJsonObject { - put("type", "string") - put("description", "GitHub organization") - }, - ) - put( - "repository", - buildJsonObject { - put("type", "string") - put("title", "Repository name") - }, - ) - put( - "private", - buildJsonObject { - put("type", "boolean") - put("description", "Is the repository private?") - }, - ) - }, + properties = mapOf( + "owner" to StringSchema(description = "GitHub organization"), + "repository" to StringSchema(title = "Repository name"), + "private" to BooleanSchema(description = "Is the repository private?"), + ), required = listOf("owner", "repository"), ), meta = RequestMeta( - buildJsonObject { - put("progressToken", "token-42") - }, + buildJsonObject { put("progressToken", "token-42") }, ), ), ) @@ -68,16 +49,16 @@ class ElicitationTest { "requestedSchema": { "properties": { "owner": { - "type": "string", - "description": "GitHub organization" + "description": "GitHub organization", + "type": "string" }, "repository": { - "type": "string", - "title": "Repository name" + "title": "Repository name", + "type": "string" }, "private": { - "type": "boolean", - "description": "Is the repository private?" + "description": "Is the repository private?", + "type": "boolean" } }, "required": ["owner", "repository"], @@ -85,7 +66,8 @@ class ElicitationTest { }, "_meta": { "progressToken": "token-42" - } + }, + "mode": "form" } } """.trimIndent(), @@ -93,22 +75,16 @@ class ElicitationTest { } @Test - fun `should deserialize ElicitRequest from JSON`() { + fun `should deserialize form mode ElicitRequest with explicit mode`() { val json = """ { "method": "elicitation/create", "params": { - "message": "Tell us about the repository", + "mode": "form", + "message": "Enter your name", "requestedSchema": { "properties": { - "name": { - "type": "string", - "title": "Repository name" - }, - "stars": { - "type": "number", - "description": "GitHub stars" - } + "name": {"type": "string", "title": "Full name"} }, "type": "object" } @@ -117,27 +93,121 @@ class ElicitationTest { """.trimIndent() val request = verifyDeserialization(McpJson, json) - assertEquals(Method.Defined.ElicitationCreate, request.method) + request.method shouldBe Method.Defined.ElicitationCreate + request.message shouldBe "Enter your name" - val params = request.params - assertEquals("Tell us about the repository", params.message) - assertNull(params.meta) - - val schema = params.requestedSchema - assertEquals("object", schema.type) - assertNull(schema.required) - - val nameDefinition = schema.properties["name"]?.jsonObject - assertNotNull(nameDefinition) - assertEquals("string", nameDefinition["type"]?.jsonPrimitive?.content) - assertEquals("Repository name", nameDefinition["title"]?.jsonPrimitive?.content) - - val starsDefinition = schema.properties["stars"]?.jsonObject - assertNotNull(starsDefinition) - assertEquals("number", starsDefinition["type"]?.jsonPrimitive?.content) - assertEquals("GitHub stars", starsDefinition["description"]?.jsonPrimitive?.content) + val params = request.params.shouldBeInstanceOf() + val nameSchema = params.requestedSchema.properties["name"].shouldBeInstanceOf() + nameSchema.title shouldBe "Full name" } + @Test + fun `should deserialize form mode ElicitRequest without mode field`() { + val json = """ + { + "method": "elicitation/create", + "params": { + "message": "Enter your name", + "requestedSchema": { + "properties": { + "name": {"type": "string"} + }, + "type": "object" + } + } + } + """.trimIndent() + + val request = McpJson.decodeFromString(json) + request.params.shouldBeInstanceOf() + request.params.message shouldBe "Enter your name" + } + + // ── URL mode ──────────────────────────────────────────────────────── + + @Test + fun `should serialize URL mode ElicitRequest`() { + val request = ElicitRequest( + ElicitRequestURLParams( + message = "Please provide your API key", + elicitationId = "550e8400-e29b-41d4-a716-446655440000", + url = "https://mcp.example.com/ui/set_api_key", + ), + ) + + verifySerialization( + request, + McpJson, + """ + { + "method": "elicitation/create", + "params": { + "message": "Please provide your API key", + "elicitationId": "550e8400-e29b-41d4-a716-446655440000", + "url": "https://mcp.example.com/ui/set_api_key", + "mode": "url" + } + } + """.trimIndent(), + ) + } + + @Test + fun `should deserialize URL mode ElicitRequest`() { + val json = """ + { + "method": "elicitation/create", + "params": { + "mode": "url", + "message": "Authorize access", + "elicitationId": "id-123", + "url": "https://example.com/auth" + } + } + """.trimIndent() + + val request = verifyDeserialization(McpJson, json) + val params = request.params.shouldBeInstanceOf() + params.message shouldBe "Authorize access" + params.elicitationId shouldBe "id-123" + params.url shouldBe "https://example.com/auth" + } + + // ── Deprecated compat ─────────────────────────────────────────────── + + @Test + @Suppress("DEPRECATION") + fun `deprecated factory function should create ElicitRequestFormParams`() { + val schema = ElicitRequestParams.RequestedSchema( + properties = mapOf("name" to StringSchema()), + ) + val params = ElicitRequestParams(message = "Test", requestedSchema = schema) + params.shouldBeInstanceOf() + params.message shouldBe "Test" + params.requestedSchema shouldBe schema + } + + @Test + @Suppress("DEPRECATION") + fun `deprecated requestedSchema should return schema for form mode`() { + val schema = ElicitRequestParams.RequestedSchema( + properties = mapOf("x" to BooleanSchema()), + ) + val request = ElicitRequest(ElicitRequestFormParams(message = "m", requestedSchema = schema)) + request.requestedSchema shouldBe schema + } + + @Test + @Suppress("DEPRECATION") + fun `deprecated requestedSchema should return null for URL mode`() { + val request = ElicitRequest( + ElicitRequestURLParams(message = "m", elicitationId = "id", url = "https://example.com"), + ) + request.requestedSchema.shouldBeNull() + } + + // ── ElicitResult ──────────────────────────────────────────────────── + @Test fun `should serialize and deserialize accept result with content`() { val result = ElicitResult( @@ -169,17 +239,15 @@ class ElicitationTest { """.trimIndent() val decoded = verifyDeserialization(McpJson, json) - assertEquals(ElicitResult.Action.Accept, decoded.action) + decoded.action shouldBe ElicitResult.Action.Accept - val content = decoded.content - assertNotNull(content) - assertEquals("kotlin-sdk", content["repository"]?.jsonPrimitive?.content) - assertEquals(128, content["stars"]?.jsonPrimitive?.int) - assertEquals(false, content["private"]?.jsonPrimitive?.boolean) + val content = decoded.content.shouldNotBeNull() + content["repository"]?.jsonPrimitive?.content shouldBe "kotlin-sdk" + content["stars"]?.jsonPrimitive?.int shouldBe 128 + content["private"]?.jsonPrimitive?.boolean shouldBe false - val meta = decoded.meta - assertNotNull(meta) - assertEquals("2025-01-12T15:00:58Z", meta["submittedAt"]?.jsonPrimitive?.content) + val meta = decoded.meta.shouldNotBeNull() + meta["submittedAt"]?.jsonPrimitive?.content shouldBe "2025-01-12T15:00:58Z" } @Test @@ -194,14 +262,14 @@ class ElicitationTest { """.trimIndent() val result = verifyDeserialization(McpJson, json) - assertEquals(ElicitResult.Action.Decline, result.action) - assertNull(result.content) - assertEquals("User skipped", result.meta?.get("reason")?.jsonPrimitive?.content) + result.action shouldBe ElicitResult.Action.Decline + result.content.shouldBeNull() + result.meta?.get("reason")?.jsonPrimitive?.content shouldBe "User skipped" } @Test fun `should require content only for accept action`() { - assertFailsWith { + shouldThrow { ElicitResult( action = ElicitResult.Action.Cancel, content = buildJsonObject { put("value", "ignored") }, diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PrimitiveSchemaTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PrimitiveSchemaTest.kt new file mode 100644 index 0000000..954fb1e --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PrimitiveSchemaTest.kt @@ -0,0 +1,481 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.collections.shouldHaveSize +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain +import io.kotest.matchers.types.shouldBeInstanceOf +import io.modelcontextprotocol.kotlin.test.utils.verifyDeserialization +import io.modelcontextprotocol.kotlin.test.utils.verifySerialization +import kotlinx.serialization.SerializationException +import kotlin.test.Test + +class PrimitiveSchemaTest { + + // ── StringSchema ──────────────────────────────────────────────────── + + @Test + fun `should serialize StringSchema with minimal fields`() { + verifySerialization( + StringSchema() as PrimitiveSchemaDefinition, + McpJson, + """{"type": "string"}""", + ) + } + + @Test + fun `should serialize StringSchema with all fields`() { + val schema = StringSchema( + title = "Email address", + description = "User email", + minLength = 5, + maxLength = 100, + format = StringSchemaFormat.Email, + default = "user@example.com", + ) + + verifySerialization( + schema as PrimitiveSchemaDefinition, + McpJson, + """ + { + "title": "Email address", + "description": "User email", + "minLength": 5, + "maxLength": 100, + "format": "email", + "default": "user@example.com", + "type": "string" + } + """.trimIndent(), + ) + } + + @Test + fun `should deserialize StringSchema from JSON`() { + val json = """ + { + "type": "string", + "title": "Name", + "format": "uri" + } + """.trimIndent() + + val schema = verifyDeserialization(McpJson, json) + schema.shouldBeInstanceOf() + schema.title shouldBe "Name" + schema.format shouldBe StringSchemaFormat.Uri + } + + // ── StringSchemaFormat ────────────────────────────────────────────── + + @Test + fun `should serialize all StringSchemaFormat values`() { + val cases = mapOf( + StringSchemaFormat.Email to "email", + StringSchemaFormat.Uri to "uri", + StringSchemaFormat.Date to "date", + StringSchemaFormat.DateTime to "date-time", + ) + for ((format, expectedValue) in cases) { + val schema = StringSchema(format = format) + val json = McpJson.encodeToString(schema) + json shouldContain "\"format\":\"$expectedValue\"" + } + } + + // ── IntegerSchema ─────────────────────────────────────────────────── + + @Test + fun `should serialize IntegerSchema with minimal fields`() { + verifySerialization( + IntegerSchema() as PrimitiveSchemaDefinition, + McpJson, + """{"type": "integer"}""", + ) + } + + @Test + fun `should serialize IntegerSchema with all fields`() { + verifySerialization( + IntegerSchema( + title = "Age", + description = "User age", + minimum = 0, + maximum = 150, + default = 25, + ) as PrimitiveSchemaDefinition, + McpJson, + """ + { + "title": "Age", + "description": "User age", + "minimum": 0, + "maximum": 150, + "default": 25, + "type": "integer" + } + """.trimIndent(), + ) + } + + @Test + fun `should deserialize IntegerSchema from JSON`() { + val json = """{"type": "integer", "minimum": 1, "maximum": 10}""" + val schema = verifyDeserialization(McpJson, json) + schema.shouldBeInstanceOf() + schema.minimum shouldBe 1 + schema.maximum shouldBe 10 + } + + // ── DoubleSchema ──────────────────────────────────────────────────── + + @Test + fun `should serialize DoubleSchema with minimal fields`() { + verifySerialization( + DoubleSchema() as PrimitiveSchemaDefinition, + McpJson, + """{"type": "number"}""", + ) + } + + @Test + fun `should serialize DoubleSchema with all fields`() { + verifySerialization( + DoubleSchema( + title = "Score", + description = "Test score", + minimum = 0.0, + maximum = 100.0, + default = 50.0, + ) as PrimitiveSchemaDefinition, + McpJson, + """ + { + "title": "Score", + "description": "Test score", + "minimum": 0.0, + "maximum": 100.0, + "default": 50.0, + "type": "number" + } + """.trimIndent(), + ) + } + + @Test + fun `should deserialize DoubleSchema from JSON`() { + val json = """{"type": "number", "minimum": 0.5}""" + val schema = verifyDeserialization(McpJson, json) + schema.shouldBeInstanceOf() + schema.minimum shouldBe 0.5 + } + + // ── BooleanSchema ─────────────────────────────────────────────────── + + @Test + fun `should serialize BooleanSchema with minimal fields`() { + verifySerialization( + BooleanSchema() as PrimitiveSchemaDefinition, + McpJson, + """{"type": "boolean"}""", + ) + } + + @Test + fun `should serialize BooleanSchema with all fields`() { + verifySerialization( + BooleanSchema( + title = "Agree", + description = "Terms acceptance", + default = false, + ) as PrimitiveSchemaDefinition, + McpJson, + """ + { + "title": "Agree", + "description": "Terms acceptance", + "default": false, + "type": "boolean" + } + """.trimIndent(), + ) + } + + @Test + fun `should deserialize BooleanSchema from JSON`() { + val json = """{"type": "boolean", "default": true}""" + val schema = verifyDeserialization(McpJson, json) + schema.shouldBeInstanceOf() + schema.default shouldBe true + } + + // ── UntitledSingleSelectEnumSchema ────────────────────────────────── + + @Test + fun `should serialize UntitledSingleSelectEnumSchema`() { + verifySerialization( + UntitledSingleSelectEnumSchema( + title = "Color", + enumValues = listOf("Red", "Green", "Blue"), + default = "Red", + ) as PrimitiveSchemaDefinition, + McpJson, + """ + { + "title": "Color", + "enum": ["Red", "Green", "Blue"], + "default": "Red", + "type": "string" + } + """.trimIndent(), + ) + } + + @Test + fun `should deserialize UntitledSingleSelectEnumSchema from JSON`() { + val json = """ + { + "type": "string", + "enum": ["A", "B", "C"] + } + """.trimIndent() + + val schema = verifyDeserialization(McpJson, json) + schema.shouldBeInstanceOf() + schema.enumValues shouldBe listOf("A", "B", "C") + } + + // ── TitledSingleSelectEnumSchema ──────────────────────────────────── + + @Test + fun `should serialize TitledSingleSelectEnumSchema`() { + verifySerialization( + TitledSingleSelectEnumSchema( + title = "Color", + oneOf = listOf( + EnumOption(const = "#FF0000", title = "Red"), + EnumOption(const = "#00FF00", title = "Green"), + ), + default = "#FF0000", + ) as PrimitiveSchemaDefinition, + McpJson, + """ + { + "title": "Color", + "oneOf": [ + {"const": "#FF0000", "title": "Red"}, + {"const": "#00FF00", "title": "Green"} + ], + "default": "#FF0000", + "type": "string" + } + """.trimIndent(), + ) + } + + @Test + fun `should deserialize TitledSingleSelectEnumSchema from JSON`() { + val json = """ + { + "type": "string", + "oneOf": [ + {"const": "a", "title": "Alpha"}, + {"const": "b", "title": "Beta"} + ] + } + """.trimIndent() + + val schema = verifyDeserialization(McpJson, json) + schema.shouldBeInstanceOf() + schema.oneOf shouldHaveSize 2 + schema.oneOf[0].const shouldBe "a" + schema.oneOf[0].title shouldBe "Alpha" + } + + // ── UntitledMultiSelectEnumSchema ──────────────────────────────────── + + @Test + fun `should serialize UntitledMultiSelectEnumSchema`() { + verifySerialization( + UntitledMultiSelectEnumSchema( + title = "Colors", + minItems = 1, + maxItems = 3, + items = UntitledMultiSelectEnumSchema.Items(enumValues = listOf("Red", "Green", "Blue")), + default = listOf("Red"), + ) as PrimitiveSchemaDefinition, + McpJson, + """ + { + "title": "Colors", + "minItems": 1, + "maxItems": 3, + "items": { + "enum": ["Red", "Green", "Blue"], + "type": "string" + }, + "default": ["Red"], + "type": "array" + } + """.trimIndent(), + ) + } + + @Test + fun `should deserialize UntitledMultiSelectEnumSchema from JSON`() { + val json = """ + { + "type": "array", + "items": { + "type": "string", + "enum": ["X", "Y"] + } + } + """.trimIndent() + + val schema = verifyDeserialization(McpJson, json) + schema.shouldBeInstanceOf() + schema.items.enumValues shouldBe listOf("X", "Y") + } + + // ── TitledMultiSelectEnumSchema ────────────────────────────────────── + + @Test + fun `should serialize TitledMultiSelectEnumSchema`() { + verifySerialization( + TitledMultiSelectEnumSchema( + title = "Colors", + minItems = 1, + maxItems = 2, + items = TitledMultiSelectEnumSchema.Items( + anyOf = listOf( + EnumOption(const = "#FF0000", title = "Red"), + EnumOption(const = "#00FF00", title = "Green"), + ), + ), + default = listOf("#FF0000"), + ) as PrimitiveSchemaDefinition, + McpJson, + """ + { + "title": "Colors", + "minItems": 1, + "maxItems": 2, + "items": { + "anyOf": [ + {"const": "#FF0000", "title": "Red"}, + {"const": "#00FF00", "title": "Green"} + ] + }, + "default": ["#FF0000"], + "type": "array" + } + """.trimIndent(), + ) + } + + @Test + fun `should deserialize TitledMultiSelectEnumSchema from JSON`() { + val json = """ + { + "type": "array", + "items": { + "anyOf": [ + {"const": "x", "title": "X"}, + {"const": "y", "title": "Y"} + ] + } + } + """.trimIndent() + + val schema = verifyDeserialization(McpJson, json) + schema.shouldBeInstanceOf() + schema.items.anyOf shouldHaveSize 2 + schema.items.anyOf[0].const shouldBe "x" + } + + // ── LegacyTitledEnumSchema ──────────────────────────────────────────── + + @Suppress("DEPRECATION") + @Test + fun `should serialize LegacyTitledEnumSchema`() { + verifySerialization( + LegacyTitledEnumSchema( + title = "Status", + enumValues = listOf("opt1", "opt2", "opt3"), + enumNames = listOf("Option One", "Option Two", "Option Three"), + default = "opt1", + ) as PrimitiveSchemaDefinition, + McpJson, + """ + { + "title": "Status", + "enum": ["opt1", "opt2", "opt3"], + "enumNames": ["Option One", "Option Two", "Option Three"], + "default": "opt1", + "type": "string" + } + """.trimIndent(), + ) + } + + @Suppress("DEPRECATION") + @Test + fun `should deserialize LegacyTitledEnumSchema from JSON`() { + val json = """ + { + "type": "string", + "enum": ["a", "b"], + "enumNames": ["Alpha", "Beta"] + } + """.trimIndent() + + val schema = verifyDeserialization(McpJson, json) + schema.shouldBeInstanceOf() + schema.enumValues shouldBe listOf("a", "b") + schema.enumNames shouldBe listOf("Alpha", "Beta") + } + + // ── Error cases ───────────────────────────────────────────────────── + + @Test + fun `should throw on unknown type`() { + val json = """{"type": "unknown"}""" + shouldThrow { + McpJson.decodeFromString(json) + } + } + + // ── RequestedSchema with typed properties ─────────────────────────── + + @Test + fun `should round-trip RequestedSchema with mixed property types`() { + val schema = ElicitRequestParams.RequestedSchema( + properties = mapOf( + "name" to StringSchema(title = "Name"), + "age" to IntegerSchema(minimum = 0), + "confirmed" to BooleanSchema(default = false), + "color" to UntitledSingleSelectEnumSchema(enumValues = listOf("Red", "Blue")), + ), + required = listOf("name"), + ) + + verifySerialization( + schema, + McpJson, + """ + { + "properties": { + "name": {"title": "Name", "type": "string"}, + "age": {"minimum": 0, "type": "integer"}, + "confirmed": {"default": false, "type": "boolean"}, + "color": {"enum": ["Red", "Blue"], "type": "string"} + }, + "required": ["name"], + "type": "object" + } + """.trimIndent(), + ) + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ElicitationDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ElicitationDslTest.kt index e49e4d2..4a34bab 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ElicitationDslTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ElicitationDslTest.kt @@ -2,8 +2,11 @@ package io.modelcontextprotocol.kotlin.sdk.types.dsl import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.shouldBe +import io.kotest.matchers.types.shouldBeInstanceOf import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestFormParams import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.StringSchema import io.modelcontextprotocol.kotlin.sdk.types.buildElicitRequest import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.put @@ -24,14 +27,15 @@ class ElicitationDslTest { } request.params.message shouldBe "Provide info" - request.params.requestedSchema.properties["email"] shouldBe buildJsonObject { put("type", "string") } - request.params.requestedSchema.required shouldBe listOf("email") + val formParams = request.params.shouldBeInstanceOf() + formParams.requestedSchema.properties["email"].shouldBeInstanceOf() + formParams.requestedSchema.required shouldBe listOf("email") } @Test fun `buildElicitRequest should support direct requestedSchema`() { val schema = ElicitRequestParams.RequestedSchema( - properties = buildJsonObject { put("name", buildJsonObject { put("type", "string") }) }, + properties = mapOf("name" to StringSchema()), required = listOf("name"), ) val request = buildElicitRequest { @@ -39,19 +43,20 @@ class ElicitationDslTest { requestedSchema(schema) } - request.params.requestedSchema shouldBe schema + val formParams = request.params.shouldBeInstanceOf() + formParams.requestedSchema shouldBe schema } @Test fun `ElicitRequestedSchemaBuilder should support direct properties assignment`() { - val props = buildJsonObject { put("key", "value") } val request = buildElicitRequest { message = "Test" requestedSchema { - properties(props) + properties(buildJsonObject { put("key", buildJsonObject { put("type", "string") }) }) } } - request.params.requestedSchema.properties shouldBe props + val formParams = request.params.shouldBeInstanceOf() + formParams.requestedSchema.properties["key"].shouldBeInstanceOf() } @Test diff --git a/kotlin-sdk-server/api/kotlin-sdk-server.api b/kotlin-sdk-server/api/kotlin-sdk-server.api index fef34b2..027af1c 100644 --- a/kotlin-sdk-server/api/kotlin-sdk-server.api +++ b/kotlin-sdk-server/api/kotlin-sdk-server.api @@ -1,8 +1,10 @@ public abstract interface class io/modelcontextprotocol/kotlin/sdk/server/ClientConnection { public abstract fun createElicitation (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun createElicitation (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun createElicitation (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun createElicitation$default (Lio/modelcontextprotocol/kotlin/sdk/server/ClientConnection;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun createElicitation$default (Lio/modelcontextprotocol/kotlin/sdk/server/ClientConnection;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun createElicitation$default (Lio/modelcontextprotocol/kotlin/sdk/server/ClientConnection;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public abstract fun createMessage (Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun createMessage$default (Lio/modelcontextprotocol/kotlin/sdk/server/ClientConnection;Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public abstract fun getSessionId ()Ljava/lang/String; @@ -22,6 +24,7 @@ public abstract interface class io/modelcontextprotocol/kotlin/sdk/server/Client public final class io/modelcontextprotocol/kotlin/sdk/server/ClientConnection$DefaultImpls { public static synthetic fun createElicitation$default (Lio/modelcontextprotocol/kotlin/sdk/server/ClientConnection;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun createElicitation$default (Lio/modelcontextprotocol/kotlin/sdk/server/ClientConnection;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun createElicitation$default (Lio/modelcontextprotocol/kotlin/sdk/server/ClientConnection;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun createMessage$default (Lio/modelcontextprotocol/kotlin/sdk/server/ClientConnection;Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun listRoots$default (Lio/modelcontextprotocol/kotlin/sdk/server/ClientConnection;Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; 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; @@ -108,7 +111,9 @@ public class io/modelcontextprotocol/kotlin/sdk/server/Server { public final fun clientConnection (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/server/ClientConnection; public final fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun createElicitation (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun createElicitation (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun createElicitation$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun createElicitation$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun createMessage (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun createMessage$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun createSession (Lio/modelcontextprotocol/kotlin/sdk/shared/Transport;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -159,7 +164,9 @@ public class io/modelcontextprotocol/kotlin/sdk/server/ServerSession : io/modelc protected fun assertNotificationCapability (Lio/modelcontextprotocol/kotlin/sdk/types/Method;)V public fun assertRequestHandlerCapability (Lio/modelcontextprotocol/kotlin/sdk/types/Method;)V public final fun createElicitation (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun createElicitation (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun createElicitation$default (Lio/modelcontextprotocol/kotlin/sdk/server/ServerSession;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static synthetic fun createElicitation$default (Lio/modelcontextprotocol/kotlin/sdk/server/ServerSession;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun createMessage (Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun createMessage$default (Lio/modelcontextprotocol/kotlin/sdk/server/ServerSession;Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public fun equals (Ljava/lang/Object;)Z diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnection.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnection.kt index 998d62c..96177ee 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnection.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnection.kt @@ -5,7 +5,9 @@ import io.modelcontextprotocol.kotlin.sdk.shared.RequestOptions import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageResult import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequest +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.EmptyResult import io.modelcontextprotocol.kotlin.sdk.types.ListRootsRequest @@ -104,6 +106,24 @@ public interface ClientConnection { options: RequestOptions? = null, ): ElicitResult + /** + * Sends a URL mode elicitation request to the client, directing the user + * to an external URL for out-of-band interactions. + * + * @param message The message explaining why the interaction is needed. + * @param elicitationId A unique identifier for the elicitation. + * @param url The URL that the user should navigate to. + * @param options Optional request options. + * @return The result of the elicitation request. + * @throws IllegalStateException If the server or client does not support elicitation. + */ + public suspend fun createElicitation( + message: String, + elicitationId: String, + url: String, + options: RequestOptions? = null, + ): ElicitResult + /** * Sends a message to the client requesting elicitation. * This typically results in a form being displayed to the user. @@ -189,10 +209,16 @@ internal class ClientConnectionImpl(private val session: ServerSession) : Client } override suspend fun createElicitation(request: ElicitRequest, options: RequestOptions?): ElicitResult { - with(request.params) { - logger.debug { - "Creating elicitation with message length=${message.length}, " + - "schema properties count=${requestedSchema.properties.size}" + val params = request.params + logger.debug { + when (params) { + is ElicitRequestFormParams -> + "Creating form elicitation with message length=${params.message.length}, " + + "schema properties count=${params.requestedSchema.properties.size}" + + is ElicitRequestURLParams -> + "Creating URL elicitation with message length=${params.message.length}, " + + "elicitationId=${params.elicitationId}" } } logger.trace { "ElicitRequest: $request" } @@ -204,7 +230,19 @@ internal class ClientConnectionImpl(private val session: ServerSession) : Client requestedSchema: ElicitRequestParams.RequestedSchema, options: RequestOptions?, ): ElicitResult = createElicitation( - request = ElicitRequest(params = ElicitRequestParams(message, requestedSchema)), + request = ElicitRequest(params = ElicitRequestFormParams(message = message, requestedSchema = requestedSchema)), + options = options, + ) + + override suspend fun createElicitation( + message: String, + elicitationId: String, + url: String, + options: RequestOptions?, + ): ElicitResult = createElicitation( + request = ElicitRequest( + params = ElicitRequestURLParams(message = message, elicitationId = elicitationId, url = url), + ), options = options, ) diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt index e566d09..a3b2256 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt @@ -780,6 +780,30 @@ public open class Server( options = options, ) + /** + * Triggers URL mode [ClientConnection.createElicitation] request for session by provided [sessionId]. + * + * @param sessionId The session ID to create elicitation for. + * @param message The message explaining why the interaction is needed. + * @param elicitationId A unique identifier for the elicitation. + * @param url The URL that the user should navigate to. + * @param options Optional request options. + * @return The created elicitation result. + * @throws IllegalStateException If the server does not support elicitation or if the request fails. + */ + public suspend fun createElicitation( + sessionId: String, + message: String, + elicitationId: String, + url: String, + options: RequestOptions? = null, + ): ElicitResult = clientConnection(sessionId).createElicitation( + message = message, + elicitationId = elicitationId, + url = url, + options = options, + ) + /** * Triggers [ClientConnection.sendLoggingMessage] for session by provided [sessionId]. * diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt index a8a34af..9e6553a 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt @@ -382,6 +382,24 @@ public open class ServerSession( options: RequestOptions? = null, ): ElicitResult = clientConnection.createElicitation(message, requestedSchema, options) + /** + * Sends a URL mode elicitation request to the client, directing the user + * to an external URL for out-of-band interactions. + * + * @param message The message explaining why the interaction is needed. + * @param elicitationId A unique identifier for the elicitation. + * @param url The URL that the user should navigate to. + * @param options Optional request options. + * @return The result of the elicitation request. + * @throws IllegalStateException If the server or client does not support elicitation. + */ + public suspend fun createElicitation( + message: String, + elicitationId: String, + url: String, + options: RequestOptions? = null, + ): ElicitResult = clientConnection.createElicitation(message, elicitationId, url, options) + /** * Sends a logging message notification to the client. * Messages are filtered based on the current logging level set by the client. From 6ce9ff81e7cc5fe9e27e654f32f98dce7ca965b0 Mon Sep 17 00:00:00 2001 From: johny <69754601+jskjw157@users.noreply.github.com> Date: Wed, 1 Apr 2026 19:57:17 +0900 Subject: [PATCH 214/449] feat(server): support configurable max request payload size (#646) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Add `maxRequestBodySize` parameter to `StreamableHttpServerTransport.Configuration`, allowing consumers to set a custom request body size limit lower than the default 4 MB. - Add `maxRequestBodySize: Long` property to `Configuration` with a default of 4 MB (`4,194,304` bytes) - Replace hardcoded `MAXIMUM_MESSAGE_SIZE` constant with the configurable value in `parseBody()` - Use `Long` for content-length comparison to avoid `Int` overflow on large values ## Motivation and Context Closes #521 The `StreamableHttpServerTransport` has a hardcoded 4 MB request body limit. Consumers who need a lower limit currently have to manually count the request payload size before passing it to the SDK. This change makes the limit configurable via `Configuration`. ## How Has This Been Tested? - Existing unit tests continue to pass (default 4 MB behavior unchanged) - Added `POST with custom max request body size rejects oversized payload` — verifies `PayloadTooLarge` is returned for payloads exceeding the custom limit - Added `POST at exactly custom max request body size is not rejected as payload too large` — verifies payloads at exactly the limit are not rejected by the size check ```bash ./gradlew :kotlin-sdk-server:jvmTest --tests "*.StreamableHttpServerTransportTest" ``` ## Breaking Changes None. The new parameter has a default value matching the previous hardcoded limit. ## 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- kotlin-sdk-server/api/kotlin-sdk-server.api | 5 +- kotlin-sdk-server/detekt-baseline-main.xml | 1 + .../server/StreamableHttpServerTransport.kt | 27 +++++++--- .../StreamableHttpServerTransportTest.kt | 50 +++++++++++++++++++ 4 files changed, 73 insertions(+), 10 deletions(-) diff --git a/kotlin-sdk-server/api/kotlin-sdk-server.api b/kotlin-sdk-server/api/kotlin-sdk-server.api index 027af1c..70e8e14 100644 --- a/kotlin-sdk-server/api/kotlin-sdk-server.api +++ b/kotlin-sdk-server/api/kotlin-sdk-server.api @@ -227,13 +227,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServe } public final class io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport$Configuration { - public synthetic fun (ZZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/time/Duration;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun (ZZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/time/Duration;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (ZZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/time/Duration;JILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (ZZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/time/Duration;JLkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getAllowedHosts ()Ljava/util/List; public final fun getAllowedOrigins ()Ljava/util/List; public final fun getEnableDnsRebindingProtection ()Z public final fun getEnableJsonResponse ()Z public final fun getEventStore ()Lio/modelcontextprotocol/kotlin/sdk/server/EventStore; + public final fun getMaxRequestBodySize ()J public final fun getRetryInterval-FghU774 ()Lkotlin/time/Duration; } diff --git a/kotlin-sdk-server/detekt-baseline-main.xml b/kotlin-sdk-server/detekt-baseline-main.xml index 141c7d0..a27f38a 100644 --- a/kotlin-sdk-server/detekt-baseline-main.xml +++ b/kotlin-sdk-server/detekt-baseline-main.xml @@ -4,6 +4,7 @@ InjectDispatcher:FeatureNotificationService.kt:FeatureNotificationService$Default LongParameterList:KtorServer.kt:private suspend fun RoutingContext.streamableTransport: StreamableHttpServerTransport? + LongParameterList:StreamableHttpServerTransport.kt:StreamableHttpServerTransport.Configuration MagicNumber:StdioServerTransport.kt:StdioServerTransport$8192 MaxLineLength:SSEServerTransport.kt:SseServerTransport$"SSEServerTransport already started! If using Server class, note that connect() calls start() automatically." MaxLineLength:SSEServerTransport.kt:SseServerTransport$* diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt index 821c525..1ea8d83 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt @@ -45,7 +45,7 @@ import kotlin.uuid.Uuid internal const val MCP_SESSION_ID_HEADER = "mcp-session-id" private const val MCP_PROTOCOL_VERSION_HEADER = "mcp-protocol-version" private const val MCP_RESUMPTION_TOKEN_HEADER = "Last-Event-ID" -private const val MAXIMUM_MESSAGE_SIZE = 4 * 1024 * 1024 // 4 MB +private const val DEFAULT_MAX_REQUEST_BODY_SIZE: Long = 4L * 1024 * 1024 // 4 MB private const val MIN_PRIMING_EVENT_PROTOCOL_VERSION = "2025-11-25" /** @@ -141,6 +141,9 @@ public class StreamableHttpServerTransport(private val configuration: Configurat * * @property retryInterval Retry interval for event handling or reconnection attempts. * Defaults to `null`. + * + * @property maxRequestBodySize Maximum allowed size (in bytes) for incoming request bodies. + * Defaults to 4 MB (4,194,304 bytes). */ public class Configuration( public val enableJsonResponse: Boolean = false, @@ -149,7 +152,14 @@ public class StreamableHttpServerTransport(private val configuration: Configurat public val allowedOrigins: List? = null, public val eventStore: EventStore? = null, public val retryInterval: Duration? = null, - ) + public val maxRequestBodySize: Long = DEFAULT_MAX_REQUEST_BODY_SIZE, + ) { + init { + require(maxRequestBodySize > 0) { + "maxRequestBodySize must be greater than 0" + } + } + } public var sessionId: String? = null private set @@ -661,24 +671,25 @@ public class StreamableHttpServerTransport(private val configuration: Configurat } } - @Suppress("ReturnCount", "MagicNumber") + @Suppress("ReturnCount") private suspend fun parseBody(call: ApplicationCall): List? { - val contentLength = call.request.header(HttpHeaders.ContentLength)?.toIntOrNull() ?: 0 - if (contentLength > MAXIMUM_MESSAGE_SIZE) { + val maxSize = configuration.maxRequestBodySize + val contentLength = call.request.header(HttpHeaders.ContentLength)?.toLongOrNull() ?: 0L + if (contentLength > maxSize) { call.reject( HttpStatusCode.PayloadTooLarge, RPCError.ErrorCode.INVALID_REQUEST, - "Invalid Request: message size exceeds maximum of ${MAXIMUM_MESSAGE_SIZE / (1024 * 1024)} MB", + "Invalid Request: message size exceeds maximum of $maxSize bytes", ) return null } val body = call.receiveText() - if (body.length > MAXIMUM_MESSAGE_SIZE) { + if (body.length.toLong() > maxSize) { call.reject( HttpStatusCode.PayloadTooLarge, RPCError.ErrorCode.INVALID_REQUEST, - "Invalid Request: message size exceeds maximum of ${MAXIMUM_MESSAGE_SIZE / (1024 * 1024)} MB", + "Invalid Request: message size exceeds maximum of $maxSize bytes", ) return null } diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt index c6e539a..9cf916a 100644 --- a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt @@ -43,10 +43,12 @@ import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.put import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource import java.util.concurrent.atomic.AtomicBoolean import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFailsWith import kotlin.test.assertFalse import kotlin.test.assertNotNull import io.ktor.client.plugins.contentnegotiation.ContentNegotiation as ClientContentNegotiation @@ -63,6 +65,15 @@ class StreamableHttpServerTransportTest { null, "lolol", ) + + private val sizeTestPayload = "x".repeat(64) + + @JvmStatic + fun maxBodySizeTestCases(): List = listOf( + Arguments.of(sizeTestPayload.length.toLong() - 1, HttpStatusCode.PayloadTooLarge), + Arguments.of(sizeTestPayload.length.toLong(), HttpStatusCode.BadRequest), + Arguments.of(sizeTestPayload.length.toLong() + 1, HttpStatusCode.BadRequest), + ) } private val path = "/transport" @@ -383,6 +394,45 @@ class StreamableHttpServerTransportTest { response.status shouldBe HttpStatusCode.PayloadTooLarge } + @ParameterizedTest + @MethodSource("maxBodySizeTestCases") + fun `POST with custom max request body size validates payload size`( + maxSize: Long, + expectedStatus: HttpStatusCode, + ) = testApplication { + configTestServer() + + val client = createTestClient() + + val transport = StreamableHttpServerTransport( + StreamableHttpServerTransport.Configuration( + enableJsonResponse = true, + maxRequestBodySize = maxSize, + ), + ) + transport.onMessage { message -> + if (message is JSONRPCRequest) { + transport.send(JSONRPCResponse(message.id, EmptyResult())) + } + } + + configureTransportEndpoint(transport) + + val response = client.post(path) { + addStreamableHeaders() + setBody(sizeTestPayload) + } + + response.status shouldBe expectedStatus + } + + @Test + fun `Configuration with negative maxRequestBodySize throws IllegalArgumentException`() { + assertFailsWith { + StreamableHttpServerTransport.Configuration(maxRequestBodySize = -1) + } + } + private fun ApplicationTestBuilder.configureTransportEndpoint(transport: StreamableHttpServerTransport) { application { routing { From 9c4a2394b40e89f3d8e560d7a8ae0c4a5a3a6d5b Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Wed, 1 Apr 2026 15:22:35 +0200 Subject: [PATCH 215/449] test: remove `Ignore` for SseTransportTest and refactor (#662) SseTransportTest passes on wasm after updating ktor ## How Has This Been Tested? jvm/js/wasm/macos ## Breaking Changes none ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] 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 --- .../kotlin/sdk/client/SseTransportTest.kt | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt index d82ff47..8abf75d 100644 --- a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt +++ b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt @@ -12,9 +12,10 @@ import io.modelcontextprotocol.kotlin.sdk.server.mcp import io.modelcontextprotocol.kotlin.sdk.shared.BaseTransportTest import io.modelcontextprotocol.kotlin.sdk.types.Implementation import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.withContext import kotlin.test.BeforeTest -import kotlin.test.Ignore import kotlin.test.Test import io.ktor.client.plugins.sse.SSE as ClientSSE import io.ktor.server.sse.SSE as ServerSSE @@ -37,7 +38,6 @@ class SseTransportTest : BaseTransportTest() { } @Test - @Ignore // Ignored because it doesn’t work with wasm/js in Ktor 3.2.3 fun `should start then close cleanly`() = runTest { val server = embeddedServer(CIO, port = 0) { install(ServerSSE) @@ -58,13 +58,12 @@ class SseTransportTest : BaseTransportTest() { } try { - testTransportRead(transport) + testTransportOpenClose(transport) } finally { server.stopSuspend() } } - @Ignore @Test fun `should read messages`() = runTest { val server = embeddedServer(CIO, port = 0) { @@ -85,20 +84,28 @@ class SseTransportTest : BaseTransportTest() { } } + val client = Client( + clientInfo = Implementation(name = "test-client", version = "1.0"), + options = ClientOptions(), + ) + try { - testTransportRead(transport) + withContext(Dispatchers.Default) { + client.connect(transport) + client.ping() + } } finally { + client.close() server.stopSuspend() } } - @Ignore @Test fun `test sse path not root path`() = runTest { val server = embeddedServer(CIO, port = 0) { install(ServerSSE) routing { - mcp { mcpServer } + mcp("/sse") { mcpServer } } }.startSuspend(wait = false) @@ -114,9 +121,18 @@ class SseTransportTest : BaseTransportTest() { } } + val client = Client( + clientInfo = Implementation(name = "test-client", version = "1.0"), + options = ClientOptions(), + ) + try { - testTransportRead(transport) + withContext(Dispatchers.Default) { + client.connect(transport) + client.ping() + } } finally { + client.close() server.stopSuspend() } } From e21d2298a1a60c735c1532d46e926fc1309bdcd5 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Wed, 1 Apr 2026 16:31:34 +0200 Subject: [PATCH 216/449] fix: apply elicitation schema defaults for missing fields in client response (#661) Apply default values from the elicitation schema to the client's response when the handler returns `Accept` with missing fields closes #414 ## How Has This Been Tested? integration and conformance tests ## Breaking Changes none ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- conformance-test/conformance-baseline.yml | 3 +- .../kotlin/sdk/client/ClientTest.kt | 180 ++++++++++++++++++ .../kotlin/sdk/client/Client.kt | 68 ++++++- 3 files changed, 248 insertions(+), 3 deletions(-) diff --git a/conformance-test/conformance-baseline.yml b/conformance-test/conformance-baseline.yml index 337d8ce..f349b8c 100644 --- a/conformance-test/conformance-baseline.yml +++ b/conformance-test/conformance-baseline.yml @@ -2,5 +2,4 @@ # Add entries here as tests are identified as known SDK limitations server: [] -client: - - elicitation-sep1034-client-defaults +client: [] diff --git a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt index 1d84732..d0acda5 100644 --- a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt +++ b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt @@ -7,9 +7,11 @@ import io.modelcontextprotocol.kotlin.sdk.server.ServerSession import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport import io.modelcontextprotocol.kotlin.sdk.shared.TransportSendOptions +import io.modelcontextprotocol.kotlin.sdk.types.BooleanSchema import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageResult +import io.modelcontextprotocol.kotlin.sdk.types.DoubleSchema import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestFormParams import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestParams import io.modelcontextprotocol.kotlin.sdk.types.ElicitResult @@ -17,6 +19,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject import io.modelcontextprotocol.kotlin.sdk.types.Implementation import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest import io.modelcontextprotocol.kotlin.sdk.types.InitializeResult +import io.modelcontextprotocol.kotlin.sdk.types.IntegerSchema import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse @@ -38,8 +41,11 @@ import io.modelcontextprotocol.kotlin.sdk.types.SUPPORTED_PROTOCOL_VERSIONS import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.types.StringSchema import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.sdk.types.TitledMultiSelectEnumSchema import io.modelcontextprotocol.kotlin.sdk.types.Tool import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema +import io.modelcontextprotocol.kotlin.sdk.types.UntitledMultiSelectEnumSchema +import io.modelcontextprotocol.kotlin.sdk.types.UntitledSingleSelectEnumSchema import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.cancel @@ -48,7 +54,10 @@ import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest import kotlinx.coroutines.withTimeout +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.put import kotlin.coroutines.cancellation.CancellationException import kotlin.test.Test @@ -1110,4 +1119,175 @@ class ClientTest { assertEquals(elicitationResultAction, result.action) assertEquals(elicitationResultContent, result.content) } + + @Test + fun `should apply elicitation defaults for missing fields in empty content`() = runTest { + val schema = defaultsTestSchema() + val (client, serverSession) = setupElicitationPair { + ElicitResult(action = ElicitResult.Action.Accept, content = JsonObject(emptyMap())) + } + + val result = serverSession.createElicitation(message = "fill defaults", requestedSchema = schema) + + assertEquals(ElicitResult.Action.Accept, result.action) + val content = result.content!! + assertEquals(JsonPrimitive("John Doe"), content["name"]) + assertEquals(JsonPrimitive(30), content["age"]) + assertEquals(JsonPrimitive(95.5), content["score"]) + assertEquals(JsonPrimitive("active"), content["status"]) + assertEquals(JsonPrimitive(true), content["verified"]) + + client.close() + } + + @Test + fun `should apply elicitation defaults only for missing fields preserving user values`() = runTest { + val schema = defaultsTestSchema() + val userContent = buildJsonObject { put("name", "Custom Name") } + val (client, serverSession) = setupElicitationPair { + ElicitResult(action = ElicitResult.Action.Accept, content = userContent) + } + + val result = serverSession.createElicitation(message = "partial", requestedSchema = schema) + + val content = result.content!! + assertEquals(JsonPrimitive("Custom Name"), content["name"]) + assertEquals(JsonPrimitive(30), content["age"]) + assertEquals(JsonPrimitive(95.5), content["score"]) + assertEquals(JsonPrimitive("active"), content["status"]) + assertEquals(JsonPrimitive(true), content["verified"]) + + client.close() + } + + @Test + fun `should not apply elicitation defaults when action is decline`() = runTest { + val schema = defaultsTestSchema() + val (client, serverSession) = setupElicitationPair { + ElicitResult(action = ElicitResult.Action.Decline) + } + + val result = serverSession.createElicitation(message = "decline", requestedSchema = schema) + + assertEquals(ElicitResult.Action.Decline, result.action) + assertNull(result.content) + + client.close() + } + + @Test + fun `should not apply elicitation defaults when action is cancel`() = runTest { + val schema = defaultsTestSchema() + val (client, serverSession) = setupElicitationPair { + ElicitResult(action = ElicitResult.Action.Cancel) + } + + val result = serverSession.createElicitation(message = "cancel", requestedSchema = schema) + + assertEquals(ElicitResult.Action.Cancel, result.action) + assertNull(result.content) + + client.close() + } + + @Test + fun `should not modify content when schema has no defaults`() = runTest { + val schema = ElicitRequestParams.RequestedSchema( + properties = mapOf( + "name" to StringSchema(description = "name"), + "age" to IntegerSchema(description = "age"), + ), + ) + val emptyContent = JsonObject(emptyMap()) + val (client, serverSession) = setupElicitationPair { + ElicitResult(action = ElicitResult.Action.Accept, content = emptyContent) + } + + val result = serverSession.createElicitation(message = "no defaults", requestedSchema = schema) + + assertEquals(ElicitResult.Action.Accept, result.action) + assertTrue(result.content!!.isEmpty()) + + client.close() + } + + @Test + fun `should apply elicitation defaults for multi-select enum schemas`() = runTest { + val schema = ElicitRequestParams.RequestedSchema( + properties = mapOf( + "tags" to UntitledMultiSelectEnumSchema( + description = "tags", + items = UntitledMultiSelectEnumSchema.Items(enumValues = listOf("a", "b", "c")), + default = listOf("a", "b"), + ), + "options" to TitledMultiSelectEnumSchema( + description = "options", + items = TitledMultiSelectEnumSchema.Items( + anyOf = listOf( + io.modelcontextprotocol.kotlin.sdk.types.EnumOption("x", "X"), + io.modelcontextprotocol.kotlin.sdk.types.EnumOption("y", "Y"), + ), + ), + default = listOf("x"), + ), + ), + ) + val (client, serverSession) = setupElicitationPair { + ElicitResult(action = ElicitResult.Action.Accept, content = JsonObject(emptyMap())) + } + + val result = serverSession.createElicitation(message = "multi-select", requestedSchema = schema) + + val content = result.content!! + val tags = content["tags"]!! + assertIs(tags) + assertEquals(2, tags.size) + assertEquals("a", tags[0].jsonPrimitive.content) + assertEquals("b", tags[1].jsonPrimitive.content) + + val options = content["options"]!! + assertIs(options) + assertEquals(1, options.size) + assertEquals("x", options[0].jsonPrimitive.content) + + client.close() + } + + private fun defaultsTestSchema(): ElicitRequestParams.RequestedSchema = ElicitRequestParams.RequestedSchema( + properties = mapOf( + "name" to StringSchema(description = "User name", default = "John Doe"), + "age" to IntegerSchema(description = "User age", default = 30), + "score" to DoubleSchema(description = "User score", default = 95.5), + "status" to UntitledSingleSelectEnumSchema( + description = "User status", + enumValues = listOf("active", "inactive", "pending"), + default = "active", + ), + "verified" to BooleanSchema(description = "Verification status", default = true), + ), + ) + + private suspend fun setupElicitationPair( + handler: (io.modelcontextprotocol.kotlin.sdk.types.ElicitRequest) -> ElicitResult, + ): Pair = kotlinx.coroutines.coroutineScope { + val client = Client( + Implementation(name = "test client", version = "1.0"), + ClientOptions(capabilities = ClientCapabilities(elicitation = EmptyJsonObject)), + ) + client.setElicitationHandler(handler) + + val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() + val server = Server( + serverInfo = Implementation(name = "test server", version = "1.0"), + options = ServerOptions(capabilities = ServerCapabilities()), + ) + + val serverSessionResult = CompletableDeferred() + listOf( + launch { client.connect(clientTransport) }, + launch { serverSessionResult.complete(server.createSession(serverTransport)) }, + ).joinAll() + + client to serverSessionResult.await() + } } diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt index e3added..34b9a5b 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt @@ -6,13 +6,16 @@ import io.modelcontextprotocol.kotlin.sdk.shared.Protocol import io.modelcontextprotocol.kotlin.sdk.shared.ProtocolOptions import io.modelcontextprotocol.kotlin.sdk.shared.RequestOptions import io.modelcontextprotocol.kotlin.sdk.shared.Transport +import io.modelcontextprotocol.kotlin.sdk.types.BooleanSchema import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequestParams import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities import io.modelcontextprotocol.kotlin.sdk.types.CompleteRequest import io.modelcontextprotocol.kotlin.sdk.types.CompleteResult +import io.modelcontextprotocol.kotlin.sdk.types.DoubleSchema import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequest +import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestFormParams import io.modelcontextprotocol.kotlin.sdk.types.ElicitResult import io.modelcontextprotocol.kotlin.sdk.types.EmptyResult import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest @@ -22,7 +25,9 @@ import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequestParams import io.modelcontextprotocol.kotlin.sdk.types.InitializeResult import io.modelcontextprotocol.kotlin.sdk.types.InitializedNotification +import io.modelcontextprotocol.kotlin.sdk.types.IntegerSchema import io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION +import io.modelcontextprotocol.kotlin.sdk.types.LegacyTitledEnumSchema import io.modelcontextprotocol.kotlin.sdk.types.ListPromptsRequest import io.modelcontextprotocol.kotlin.sdk.types.ListPromptsResult import io.modelcontextprotocol.kotlin.sdk.types.ListResourceTemplatesRequest @@ -37,6 +42,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel import io.modelcontextprotocol.kotlin.sdk.types.McpException import io.modelcontextprotocol.kotlin.sdk.types.Method import io.modelcontextprotocol.kotlin.sdk.types.PingRequest +import io.modelcontextprotocol.kotlin.sdk.types.PrimitiveSchemaDefinition import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult import io.modelcontextprotocol.kotlin.sdk.types.RequestMeta @@ -46,8 +52,13 @@ import io.modelcontextprotocol.kotlin.sdk.types.SUPPORTED_PROTOCOL_VERSIONS import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.types.SetLevelRequest import io.modelcontextprotocol.kotlin.sdk.types.SetLevelRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.StringSchema import io.modelcontextprotocol.kotlin.sdk.types.SubscribeRequest +import io.modelcontextprotocol.kotlin.sdk.types.TitledMultiSelectEnumSchema +import io.modelcontextprotocol.kotlin.sdk.types.TitledSingleSelectEnumSchema import io.modelcontextprotocol.kotlin.sdk.types.UnsubscribeRequest +import io.modelcontextprotocol.kotlin.sdk.types.UntitledMultiSelectEnumSchema +import io.modelcontextprotocol.kotlin.sdk.types.UntitledSingleSelectEnumSchema import io.modelcontextprotocol.kotlin.sdk.types.toJson import kotlinx.atomicfu.atomic import kotlinx.atomicfu.getAndUpdate @@ -56,7 +67,10 @@ import kotlinx.collections.immutable.minus import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.toPersistentSet import kotlinx.serialization.SerializationException +import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.buildJsonArray import kotlin.coroutines.cancellation.CancellationException private val logger = KotlinLogging.logger {} @@ -576,6 +590,10 @@ public open class Client(private val clientInfo: Implementation, options: Client /** * Sets the elicitation handler. * + * When the handler returns [ElicitResult.Action.Accept], any properties missing from + * [ElicitResult.content] are automatically populated with default values defined in the + * elicitation schema. + * * @param handler The elicitation handler. * @throws IllegalStateException if the client does not support elicitation. */ @@ -587,12 +605,60 @@ public open class Client(private val clientInfo: Implementation, options: Client logger.info { "Setting the elicitation handler" } setRequestHandler(Method.Defined.ElicitationCreate) { request, _ -> - handler(request) + val result = handler(request) + applyElicitationDefaults(request, result) } } // --- Internal Handlers --- + @Suppress("ReturnCount") + private fun applyElicitationDefaults(request: ElicitRequest, result: ElicitResult): ElicitResult { + if (result.action != ElicitResult.Action.Accept) return result + val formParams = request.params as? ElicitRequestFormParams ?: return result + val content = result.content ?: return result + + val merged = buildMap { + putAll(content) + for ((key, schemaDef) in formParams.requestedSchema.properties) { + if (key !in content) { + schemaDef.defaultJsonValue()?.let { put(key, it) } + } + } + } + + return if (merged.size == content.size) { + result + } else { + result.copy(content = JsonObject(merged)) + } + } + + @Suppress("DEPRECATION", "CyclomaticComplexMethod") + private fun PrimitiveSchemaDefinition.defaultJsonValue(): JsonElement? = when (this) { + is StringSchema -> default?.let { JsonPrimitive(it) } + + is IntegerSchema -> default?.let { JsonPrimitive(it) } + + is DoubleSchema -> default?.let { JsonPrimitive(it) } + + is BooleanSchema -> default?.let { JsonPrimitive(it) } + + is UntitledSingleSelectEnumSchema -> default?.let { JsonPrimitive(it) } + + is TitledSingleSelectEnumSchema -> default?.let { JsonPrimitive(it) } + + is LegacyTitledEnumSchema -> default?.let { JsonPrimitive(it) } + + is UntitledMultiSelectEnumSchema -> default?.let { list -> + buildJsonArray { list.forEach { add(JsonPrimitive(it)) } } + } + + is TitledMultiSelectEnumSchema -> default?.let { list -> + buildJsonArray { list.forEach { add(JsonPrimitive(it)) } } + } + } + private fun handleListRoots(): ListRootsResult { val rootList = roots.value.values.toList() return ListRootsResult(rootList) From 3617ce4e158fa61c1f24733036bab0f624f6c3f1 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Thu, 2 Apr 2026 11:01:13 +0300 Subject: [PATCH 217/449] feat(server): auto-install ContentNegotiation with McpJson (#664) (#665) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Users currently must manually `install(ContentNegotiation) { json(McpJson) }` before calling `mcp()`, `mcpStreamableHttp()`, or `mcpStatelessStreamableHttp()`. Forgetting this or using the default `json()` instead of `json(McpJson)` causes silent serialization failures at runtime (e.g., explicit null fields in JSON-RPC responses). This PR makes the SDK install it automatically. Fixes #664 ## Changes - **Core feature** — `mcp()`, `mcpStreamableHttp()`, and `mcpStatelessStreamableHttp()` now auto-install `ContentNegotiation` configured with `McpJson`. If the user already installed `ContentNegotiation`, a warning is logged instead of crashing with `DuplicatePluginException`. - **`KtorServerHelpers.kt`** (new) — `installMcpContentNegotiation()` internal helper with `pluginOrNull` guard and warning - **`KtorServer.kt`** — calls the helper from all three entry points - **`build.gradle.kts`** — adds `ktor-server-content-negotiation` and `ktor-serialization` as `implementation` deps to `kotlin-sdk-server` - **Removed manual installs** from `ConformanceServer.kt` and `AbstractStreamableHttpIntegrationTest.kt`. - **Test infrastructure** — replaced `slf4j-simple` with Logback and use `ListAppender` (`LogCapture` utility) for deterministic log assertion. Added `logback-test.xml` matching previous log levels. - **`StreamableHttpServerConfigurationTest.kt`** (new) — verifies the warning for all three entry points: `mcpStreamableHttp`, `mcpStatelessStreamableHttp`, and `mcp` (SSE). - **README.md** — removed manual `ContentNegotiation` boilerplate from examples, documented automatic installation, added CORS configuration snippet for browser-based clients. - **CI** — added `ktlintCheck` to the Linux build matrix job. ## How Has This Been Tested? Unit/integration tests. Conformance tests updated ## Breaking Changes No ## Types of changes - [x] 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) - [x] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- .github/workflows/build.yml | 2 +- README.md | 87 ++++++++------- .../sdk/conformance/ConformanceServer.kt | 7 -- gradle/libs.versions.toml | 2 + .../AbstractStreamableHttpIntegrationTest.kt | 7 -- kotlin-sdk-server/build.gradle.kts | 4 +- .../kotlin/sdk/server/KtorServer.kt | 12 ++ .../kotlin/sdk/server/KtorServerHelpers.kt | 46 ++++++++ .../kotlin/sdk/server/LogCapture.kt | 35 ++++++ .../StreamableHttpServerConfigurationTest.kt | 103 ++++++++++++++++++ .../src/jvmTest/resources/logback-test.xml | 15 +++ .../jvmTest/resources/simplelogger.properties | 8 -- 12 files changed, 267 insertions(+), 61 deletions(-) create mode 100644 kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServerHelpers.kt create mode 100644 kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/LogCapture.kt create mode 100644 kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerConfigurationTest.kt create mode 100644 kotlin-sdk-server/src/jvmTest/resources/logback-test.xml delete mode 100644 kotlin-sdk-server/src/jvmTest/resources/simplelogger.properties diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3467db2..08242f7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,7 +34,7 @@ jobs: include: - os: ubuntu-latest job-name: "Linux Tests" - gradle-tasks: "build" + gradle-tasks: "ktlintCheck build" max-workers: 3 - os: macos-latest diff --git a/README.md b/README.md index 3e65b26..58a2cad 100644 --- a/README.md +++ b/README.md @@ -19,34 +19,34 @@ standardized protocol interface. * [Overview](#overview) * [Installation](#installation) - * [Artifacts](#artifacts) - * [Gradle setup (JVM)](#gradle-setup-jvm) - * [Multiplatform](#multiplatform) - * [Ktor dependencies](#ktor-dependencies) + * [Artifacts](#artifacts) + * [Gradle setup (JVM)](#gradle-setup-jvm) + * [Multiplatform](#multiplatform) + * [Ktor dependencies](#ktor-dependencies) * [Quickstart](#quickstart) - * [Creating a Client](#creating-a-client) - * [Creating a Server](#creating-a-server) + * [Creating a Client](#creating-a-client) + * [Creating a Server](#creating-a-server) * [Core Concepts](#core-concepts) - * [MCP Primitives](#mcp-primitives) - * [Capabilities](#capabilities) - * [Server Capabilities](#server-capabilities) - * [Client Capabilities](#client-capabilities) - * [Server Features](#server-features) - * [Prompts](#prompts) - * [Resources](#resources) - * [Tools](#tools) - * [Completion](#completion) - * [Logging](#logging) - * [Pagination](#pagination) - * [Client Features](#client-features) - * [Roots](#roots) - * [Sampling](#sampling) + * [MCP Primitives](#mcp-primitives) + * [Capabilities](#capabilities) + * [Server Capabilities](#server-capabilities) + * [Client Capabilities](#client-capabilities) + * [Server Features](#server-features) + * [Prompts](#prompts) + * [Resources](#resources) + * [Tools](#tools) + * [Completion](#completion) + * [Logging](#logging) + * [Pagination](#pagination) + * [Client Features](#client-features) + * [Roots](#roots) + * [Sampling](#sampling) * [Transports](#transports) - * [STDIO Transport](#stdio-transport) - * [Streamable HTTP Transport](#streamable-http-transport) - * [SSE Transport](#sse-transport) - * [WebSocket Transport](#websocket-transport) - * [ChannelTransport (testing)](#channeltransport-testing) + * [STDIO Transport](#stdio-transport) + * [Streamable HTTP Transport](#streamable-http-transport) + * [SSE Transport](#sse-transport) + * [WebSocket Transport](#websocket-transport) + * [ChannelTransport (testing)](#channeltransport-testing) * [Connecting your server](#connecting-your-server) * [Examples](#examples) * [Documentation](#documentation) @@ -188,24 +188,19 @@ the [simple-streamable-server](samples/simple-streamable-server) sample. ```kotlin -import io.ktor.serialization.kotlinx.json.json -import io.ktor.server.application.install import io.ktor.server.cio.CIO import io.ktor.server.engine.embeddedServer -import io.ktor.server.plugins.contentnegotiation.ContentNegotiation import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions import io.modelcontextprotocol.kotlin.sdk.server.mcpStreamableHttp import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult import io.modelcontextprotocol.kotlin.sdk.types.Implementation -import io.modelcontextprotocol.kotlin.sdk.types.McpJson import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.types.TextContent import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.put - fun main(args: Array) { val port = args.firstOrNull()?.toIntOrNull() ?: 3000 val mcpServer = Server( @@ -231,11 +226,8 @@ fun main(args: Array) { ) { request -> CallToolResult(content = listOf(TextContent("Hello, world!"))) } - + embeddedServer(CIO, host = "127.0.0.1", port = port) { - install(ContentNegotiation) { - json(McpJson) - } mcpStreamableHttp { mcpServer } @@ -774,8 +766,10 @@ CLI tooling that spawns a helper process. No networking setup is required. `StreamableHttpClientTransport` and the Ktor `mcpStreamableHttp()` / `mcpStatelessStreamableHttp()` helpers expose MCP over a single HTTP endpoint with optional JSON-only or SSE streaming responses. This is the recommended choice for -remote deployments and integrates nicely with proxies or service meshes. Both accept a `path` parameter (default: -`"/mcp"`) to mount the endpoint at any URL: +remote deployments and integrates nicely with proxies or service meshes. + +These helpers automatically install `ContentNegotiation` with `McpJson` — do not install it yourself, or a warning will +be logged. Both accept a `path` parameter (default: `"/mcp"`) to mount the endpoint at any URL: +**CORS for browser-based clients (e.g. MCP Inspector):** if you connect from a browser-based +client you need to install the Ktor CORS plugin so that MCP-specific headers are allowed and exposed: + +```kotlin +install(CORS) { + anyHost() // restrict to specific origins in production + allowMethod(HttpMethod.Options) + allowMethod(HttpMethod.Get) + allowMethod(HttpMethod.Post) + allowMethod(HttpMethod.Delete) + allowNonSimpleContentTypes = true + allowHeader("Mcp-Session-Id") + allowHeader("Mcp-Protocol-Version") + exposeHeader("Mcp-Session-Id") + exposeHeader("Mcp-Protocol-Version") +} +``` + ### SSE Transport Server-Sent Events remain available for backwards compatibility with older MCP clients. Two Ktor helpers are provided: -- **`Application.mcp { }`** — installs the SSE plugin automatically and registers MCP endpoints at `/`. +- **`Application.mcp { }`** — installs SSE and `ContentNegotiation` with `McpJson` automatically, then registers MCP + endpoints at `/`. Do not install `ContentNegotiation` yourself — the SDK handles it. - **`Route.mcp { }`** — registers MCP endpoints at the current route path; requires `install(SSE)` in the application first. Use this to host MCP alongside other routes or under a path prefix: diff --git a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceServer.kt b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceServer.kt index b420775..03d5ee3 100644 --- a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceServer.kt +++ b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceServer.kt @@ -1,23 +1,16 @@ package io.modelcontextprotocol.kotlin.sdk.conformance -import io.ktor.serialization.kotlinx.json.json -import io.ktor.server.application.install import io.ktor.server.cio.CIO import io.ktor.server.engine.embeddedServer -import io.ktor.server.plugins.contentnegotiation.ContentNegotiation import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions import io.modelcontextprotocol.kotlin.sdk.server.mcpStreamableHttp import io.modelcontextprotocol.kotlin.sdk.types.Implementation -import io.modelcontextprotocol.kotlin.sdk.types.McpJson import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities fun main() { val port = System.getenv("MCP_PORT")?.toIntOrNull() ?: 3001 embeddedServer(CIO, port = port) { - install(ContentNegotiation) { - json(McpJson) - } mcpStreamableHttp( enableDnsRebindingProtection = true, allowedHosts = listOf("localhost", "127.0.0.1", "localhost:$port", "127.0.0.1:$port"), diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f511d0d..78f55fe 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -24,6 +24,7 @@ mockk = "1.14.9" mokksy = "0.8.1" serialization = "1.10.0" slf4j = "2.0.17" +logback = "1.5.32" junit = "5.14.3" [libraries] @@ -67,6 +68,7 @@ mockk = { module = "io.mockk:mockk", version.ref = "mockk" } mokksy = { group = "dev.mokksy", name = "mokksy", version.ref = "mokksy" } netty-bom = { group = "io.netty", name = "netty-bom", version.ref = "netty" } slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" } +logback-classic = { group = "ch.qos.logback", name = "logback-classic", version.ref = "logback" } junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit" } # Samples diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/AbstractStreamableHttpIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/AbstractStreamableHttpIntegrationTest.kt index 0f6646e..9bc0c17 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/AbstractStreamableHttpIntegrationTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/AbstractStreamableHttpIntegrationTest.kt @@ -1,17 +1,13 @@ package io.modelcontextprotocol.kotlin.sdk.integration.streamablehttp -import io.ktor.serialization.kotlinx.json.json -import io.ktor.server.application.install import io.ktor.server.cio.CIOApplicationEngine import io.ktor.server.engine.EmbeddedServer import io.ktor.server.engine.embeddedServer -import io.ktor.server.plugins.contentnegotiation.ContentNegotiation import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions import io.modelcontextprotocol.kotlin.sdk.server.mcpStreamableHttp import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult import io.modelcontextprotocol.kotlin.sdk.types.Implementation -import io.modelcontextprotocol.kotlin.sdk.types.McpJson import io.modelcontextprotocol.kotlin.sdk.types.PromptArgument import io.modelcontextprotocol.kotlin.sdk.types.PromptMessage import io.modelcontextprotocol.kotlin.sdk.types.Role @@ -56,9 +52,6 @@ open class AbstractStreamableHttpIntegrationTest { host = URL, port = PORT, ) { - install(ContentNegotiation) { - json(McpJson) - } mcpStreamableHttp { mcpServer } } diff --git a/kotlin-sdk-server/build.gradle.kts b/kotlin-sdk-server/build.gradle.kts index 3a0a398..126c516 100644 --- a/kotlin-sdk-server/build.gradle.kts +++ b/kotlin-sdk-server/build.gradle.kts @@ -12,7 +12,9 @@ kotlin { api(project(":kotlin-sdk-core")) api(libs.ktor.server.core) api(libs.ktor.server.sse) + implementation(libs.ktor.server.content.negotiation) implementation(libs.ktor.server.websockets) + implementation(libs.ktor.serialization) implementation(libs.kotlin.logging) } } @@ -37,7 +39,7 @@ kotlin { implementation(libs.ktor.server.content.negotiation) implementation(libs.ktor.server.test.host) - runtimeOnly(libs.slf4j.simple) + implementation(libs.logback.classic) } } } diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt index 739f8d3..35dcc7e 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt @@ -86,11 +86,15 @@ public fun Route.mcp(block: ServerSSESession.() -> Server) { * over [Server-Sent Events (SSE) Transport](https://modelcontextprotocol.io/specification/2024-11-05/basic/transports#http-with-sse) * and sets up routing with the provided configuration block. * + * Automatically installs [ContentNegotiation][io.ktor.server.plugins.contentnegotiation.ContentNegotiation] + * with [McpJson][io.modelcontextprotocol.kotlin.sdk.types.McpJson] and [SSE]. + * * @param block factory block with access to the [ServerSSESession] * that creates and returns the [Server] to handle the connection. */ @KtorDsl public fun Application.mcp(block: ServerSSESession.() -> Server) { + installMcpContentNegotiation() install(SSE) routing { @@ -103,6 +107,7 @@ private fun Application.mcpStreamableHttp( configuration: StreamableHttpServerTransport.Configuration, block: RoutingContext.() -> Server, ) { + installMcpContentNegotiation() install(SSE) val transportManager = TransportManager() @@ -139,6 +144,9 @@ private fun Application.mcpStreamableHttp( * Sets up SSE, HTTP POST, and DELETE endpoints at the specified [path]. * Simple request/response pairs are returned as JSON (not SSE streams). * + * Automatically installs [ContentNegotiation][io.ktor.server.plugins.contentnegotiation.ContentNegotiation] + * with [McpJson][io.modelcontextprotocol.kotlin.sdk.types.McpJson] and [SSE]. + * * @param path The base path for the MCP Streamable HTTP endpoint. Defaults to "/mcp". * @param enableDnsRebindingProtection Enables DNS rebinding attack protection for the endpoint. Defaults to false. * @param allowedHosts A list of hostnames allowed to access the endpoint. If `null`, no restrictions are applied. @@ -177,6 +185,7 @@ private fun Application.mcpStatelessStreamableHttp( configuration: StreamableHttpServerTransport.Configuration, block: RoutingContext.() -> Server, ) { + installMcpContentNegotiation() install(SSE) routing { @@ -212,6 +221,9 @@ private fun Application.mcpStatelessStreamableHttp( * Sets up an HTTP POST endpoint at [path]. GET and DELETE requests return 405 Method Not Allowed. * Simple request/response pairs are returned as JSON (not SSE streams). * + * Automatically installs [ContentNegotiation][io.ktor.server.plugins.contentnegotiation.ContentNegotiation] + * with [McpJson][io.modelcontextprotocol.kotlin.sdk.types.McpJson] and [SSE]. + * * @param path The URL path where the server listens for incoming JSON-RPC requests. Defaults to "/mcp". * @param enableDnsRebindingProtection Determines whether DNS rebinding protection is enabled. Defaults to `false`. * @param allowedHosts A list of allowed hostnames. If null, host filtering is disabled. diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServerHelpers.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServerHelpers.kt new file mode 100644 index 0000000..42093c8 --- /dev/null +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServerHelpers.kt @@ -0,0 +1,46 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.github.oshai.kotlinlogging.KotlinLogging +import io.ktor.serialization.kotlinx.json.json +import io.ktor.server.application.Application +import io.ktor.server.application.install +import io.ktor.server.application.pluginOrNull +import io.ktor.server.plugins.contentnegotiation.ContentNegotiation +import io.ktor.util.AttributeKey +import io.modelcontextprotocol.kotlin.sdk.types.McpJson + +private val logger = KotlinLogging.logger {} + +private val McpContentNegotiationInstalled = AttributeKey("McpContentNegotiationInstalled") + +/** + * Installs [ContentNegotiation] configured with [McpJson]. + * + * MCP requires specific JSON settings (`explicitNulls = false`, `encodeDefaults = true`, + * `classDiscriminatorMode = NONE`). Using the default `json()` instead of `json(McpJson)` + * causes serialization failures at runtime (e.g., explicit null fields in JSON-RPC responses). + * + * If [ContentNegotiation] was already installed by user code, a warning is logged because + * Ktor's public API does not expose the Json configuration for verification. + * + * This function is idempotent: subsequent calls on the same [Application] return immediately + * without re-installing the plugin or logging additional warnings. + */ +internal fun Application.installMcpContentNegotiation() { + if (attributes.getOrNull(McpContentNegotiationInstalled) != null) return + attributes.put(McpContentNegotiationInstalled, Unit) + + if (pluginOrNull(ContentNegotiation) != null) { + // ContentNegotiation was already installed by user code. + logger.warn { + "ContentNegotiation is already installed. " + + "MCP requires json(McpJson) for correct JSON-RPC serialization " + + "(explicitNulls = false, encodeDefaults = true). " + + "Ensure your ContentNegotiation configuration includes json(McpJson)." + } + } else { + install(ContentNegotiation) { + json(McpJson) + } + } +} diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/LogCapture.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/LogCapture.kt new file mode 100644 index 0000000..dcf3ad2 --- /dev/null +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/LogCapture.kt @@ -0,0 +1,35 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import ch.qos.logback.classic.Logger +import ch.qos.logback.classic.spi.ILoggingEvent +import ch.qos.logback.core.read.ListAppender +import org.slf4j.LoggerFactory + +/** + * Captures log events from the given logger (and its children) using a Logback [ListAppender]. + * + * Usage: + * ``` + * LogCapture("io.modelcontextprotocol.kotlin.sdk.server").use { logs -> + * // … code that emits log messages … + * logs.messages.shouldExist { "expected text" in it } + * } + * ``` + */ +internal class LogCapture(loggerName: String) : AutoCloseable { + private val logger: Logger = LoggerFactory.getLogger(loggerName) as Logger + private val appender: ListAppender = ListAppender() + + init { + appender.start() + logger.addAppender(appender) + } + + val messages: List + get() = appender.list.map { it.formattedMessage } + + override fun close() { + logger.detachAppender(appender) + appender.stop() + } +} diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerConfigurationTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerConfigurationTest.kt new file mode 100644 index 0000000..1efe2ab --- /dev/null +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerConfigurationTest.kt @@ -0,0 +1,103 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.kotest.matchers.collections.shouldExist +import io.kotest.matchers.collections.shouldHaveSize +import io.ktor.client.request.get +import io.ktor.serialization.kotlinx.json.json +import io.ktor.server.application.install +import io.ktor.server.testing.testApplication +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import kotlin.test.Test +import io.ktor.server.plugins.contentnegotiation.ContentNegotiation as ServerContentNegotiation + +private const val MCP_SERVER_PACKAGE = "io.modelcontextprotocol.kotlin.sdk.server" + +class StreamableHttpServerConfigurationTest { + + private fun testServer(): Server = Server( + Implementation("test-server", "1.0"), + ServerOptions(capabilities = ServerCapabilities()), + ) + + @Test + fun `mcpStreamableHttp warns when ContentNegotiation is pre-installed`() { + LogCapture(MCP_SERVER_PACKAGE).use { logs -> + testApplication { + application { + install(ServerContentNegotiation) { + json() // Pre-install with default (wrong) config + } + mcpStreamableHttp { testServer() } + } + client.get("/mcp") + } + logs.messages.shouldExist { "ContentNegotiation is already installed" in it } + } + } + + @Test + fun `mcpStatelessStreamableHttp warns when ContentNegotiation is pre-installed`() { + LogCapture(MCP_SERVER_PACKAGE).use { logs -> + testApplication { + application { + install(ServerContentNegotiation) { + json() // Pre-install with default (wrong) config + } + mcpStatelessStreamableHttp { testServer() } + } + client.get("/mcp") + } + logs.messages.shouldExist { "ContentNegotiation is already installed" in it } + } + } + + @Test + fun `mcp warns when ContentNegotiation is pre-installed`() { + LogCapture(MCP_SERVER_PACKAGE).use { logs -> + testApplication { + application { + install(ServerContentNegotiation) { + json() // Pre-install with default (wrong) config + } + mcp { testServer() } + } + client.get("/sse") + } + logs.messages.shouldExist { "ContentNegotiation is already installed" in it } + } + } + + @Test + fun `installMcpContentNegotiation is idempotent`() { + LogCapture(MCP_SERVER_PACKAGE).use { logs -> + testApplication { + application { + installMcpContentNegotiation() + installMcpContentNegotiation() + } + client.get("/") + } + logs.messages.filter { "ContentNegotiation is already installed" in it } + .shouldHaveSize(0) + } + } + + @Test + fun `installMcpContentNegotiation warns exactly once when ContentNegotiation is pre-installed`() { + LogCapture(MCP_SERVER_PACKAGE).use { logs -> + testApplication { + application { + install(ServerContentNegotiation) { + json() + } + installMcpContentNegotiation() + installMcpContentNegotiation() + } + client.get("/") + } + logs.messages.filter { "ContentNegotiation is already installed" in it } + .shouldHaveSize(1) + } + } +} diff --git a/kotlin-sdk-server/src/jvmTest/resources/logback-test.xml b/kotlin-sdk-server/src/jvmTest/resources/logback-test.xml new file mode 100644 index 0000000..aa33478 --- /dev/null +++ b/kotlin-sdk-server/src/jvmTest/resources/logback-test.xml @@ -0,0 +1,15 @@ + + + System.err + + [%thread] %level %logger - %msg%n + + + + + + + + + + diff --git a/kotlin-sdk-server/src/jvmTest/resources/simplelogger.properties b/kotlin-sdk-server/src/jvmTest/resources/simplelogger.properties deleted file mode 100644 index 95fb646..0000000 --- a/kotlin-sdk-server/src/jvmTest/resources/simplelogger.properties +++ /dev/null @@ -1,8 +0,0 @@ -# Level of logging for the ROOT logger: ERROR, WARN, INFO, DEBUG, TRACE (default is INFO) -org.slf4j.simpleLogger.defaultLogLevel=INFO -org.slf4j.simpleLogger.showThreadName=true -org.slf4j.simpleLogger.showDateTime=false - -# Log level for specific packages or classes -org.slf4j.simpleLogger.log.io.ktor.server=DEBUG -org.slf4j.simpleLogger.log.io.modelcontextprotocol=DEBUG From e5e5ec206fec96eee12013f1f886553914b8002d Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Thu, 2 Apr 2026 12:37:33 +0200 Subject: [PATCH 218/449] 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 --- .../kotlin/sdk/server/ClientConnectionTest.kt | 13 ++++ kotlin-sdk-core/api/kotlin-sdk-core.api | 60 +++++++++++++++++++ .../kotlin/sdk/types/methods.kt | 1 + .../kotlin/sdk/types/notification.kt | 30 ++++++++++ .../kotlin/sdk/types/serializers.kt | 1 + .../kotlin/sdk/types/NotificationTest.kt | 45 ++++++++++++++ kotlin-sdk-server/api/kotlin-sdk-server.api | 3 + .../kotlin/sdk/server/ClientConnection.kt | 13 ++++ .../kotlin/sdk/server/Server.kt | 11 ++++ .../kotlin/sdk/server/ServerSession.kt | 9 +++ 10 files changed, 186 insertions(+) diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnectionTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnectionTest.kt index 71f8129..ee8953a 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnectionTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnectionTest.kt @@ -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(Method.Defined.NotificationsMessage) val resourceUpdated = onClientNotification(Method.Defined.NotificationsResourcesUpdated) + val elicitationComplete = + onClientNotification(Method.Defined.NotificationsElicitationComplete) init { onClientRequest(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) } diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index 0b81586..d4f88ba 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -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 (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 (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (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; diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/methods.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/methods.kt index 1d96fea..5697df8 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/methods.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/methods.kt @@ -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"), diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/notification.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/notification.kt index cb1aa99..08b1f12 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/notification.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/notification.kt @@ -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 // ============================================================================ diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt index 7f1c81d..3e25957 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt @@ -241,6 +241,7 @@ private val serverNotificationDeserializers: Map(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( diff --git a/kotlin-sdk-server/api/kotlin-sdk-server.api b/kotlin-sdk-server/api/kotlin-sdk-server.api index 70e8e14..80da5b5 100644 --- a/kotlin-sdk-server/api/kotlin-sdk-server.api +++ b/kotlin-sdk-server/api/kotlin-sdk-server.api @@ -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; diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnection.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnection.kt index 96177ee..23dbe89 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnection.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnection.kt @@ -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. diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt index a3b2256..de219d4 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt @@ -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 diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt index 9e6553a..e548e03 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt @@ -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 } From 33923bc7c9f74e640f30385c4af0366fae88da8c Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Thu, 2 Apr 2026 14:41:48 +0200 Subject: [PATCH 219/449] feat(docs): public api kdocs (#658) - enable rule for checking undocumented public api - add kdoc skill ## How Has This Been Tested? ./gradlew detekt ## Breaking Changes none ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] 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 - [x] I have added or updated documentation as needed --------- Co-authored-by: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> --- .claude/skills/kdoc/SKILL.md | 74 +++++++++++++++++++ config/detekt/detekt.yml | 19 +++++ .../client/StreamableHttpClientTransport.kt | 7 +- .../kotlin/sdk/internal/utils.kt | 1 + .../kotlin/sdk/shared/Protocol.kt | 13 ++++ .../kotlin/sdk/shared/TransportSendOptions.kt | 6 ++ .../sdk/shared/WebSocketMcpTransport.kt | 1 + .../kotlin/sdk/types/PingRequest.kt | 5 ++ .../kotlin/sdk/types/capabilities.kt | 8 ++ .../kotlin/sdk/types/common.kt | 13 +++- .../kotlin/sdk/types/completion.kt | 1 + .../kotlin/sdk/types/content.kt | 14 +++- .../kotlin/sdk/types/elicitation.kt | 4 + .../kotlin/sdk/types/jsonRpc.kt | 18 ++++- .../kotlin/sdk/types/jsonUtils.kt | 2 + .../kotlin/sdk/types/methods.kt | 2 + .../kotlin/sdk/types/notification.kt | 4 + .../kotlin/sdk/types/request.kt | 25 ++++++- .../kotlin/sdk/types/resources.kt | 3 + .../kotlin/sdk/types/roots.kt | 1 + .../kotlin/sdk/types/sampling.kt | 11 ++- .../kotlin/sdk/types/schema.kt | 10 +++ .../kotlin/sdk/types/tasks.kt | 9 +++ .../sdk/utils/ResourceTemplateMatcher.kt | 2 + .../kotlin/sdk/internal/utils.jvm.kt | 1 + .../kotlin/sdk/server/SSEServerTransport.kt | 2 + .../kotlin/sdk/server/ServerSession.kt | 2 + .../server/StreamableHttpServerTransport.kt | 29 +++----- 28 files changed, 260 insertions(+), 27 deletions(-) create mode 100644 .claude/skills/kdoc/SKILL.md diff --git a/.claude/skills/kdoc/SKILL.md b/.claude/skills/kdoc/SKILL.md new file mode 100644 index 0000000..4760293 --- /dev/null +++ b/.claude/skills/kdoc/SKILL.md @@ -0,0 +1,74 @@ +--- +name: kdoc +description: "Add KDoc documentation to Kotlin public API. Use whenever the user asks to document Kotlin code, add KDoc, generate API docs, mentions undocumented public declarations, or wants to improve existing documentation. Also trigger when the user says 'add docs', 'document this class/file/module', 'write KDoc', or asks about missing documentation in Kotlin code." +--- + +# KDoc Generator + +Add KDoc comments to public Kotlin API declarations. + +## What to document + +All public declarations (no explicit `private`/`internal`/`protected`): +- Classes, interfaces, objects, sealed classes/interfaces +- Functions, extension functions +- Properties +- Enum classes and entries +- Type aliases, annotation classes + +Include `@Deprecated` elements. + +## Context + +Read the implementation, not just the signature, to write accurate descriptions. Understanding what the code actually does prevents superficial or misleading documentation. + +## KDoc format + +**Class/interface example:** + +````kotlin +/** + * Manages active client sessions and their lifecycle. + * + * Sessions are created on first connection and cleaned up + * when the transport closes. + * + * @property maxSessions upper limit on concurrent sessions + * @property timeout idle timeout before a session is evicted + */ +```` + +**Function example:** + +````kotlin +/** + * Registers a new tool with the given handler. + * + * Example: + * ```kotlin + * server.addTool( + * name = "echo", + * description = "Echoes input back", + * ) { request -> + * CallToolResult(content = listOf(TextContent("Echo: ${request.arguments}"))) + * } + * ``` + * + * @param name unique tool identifier + * @param description human-readable tool description + * @param handler suspend function invoked when the tool is called + * @return the registered tool definition + */ +```` + +## Rules + +- Summary: concise first sentence starting with a third-person verb ("Creates", "Returns", "Represents"). Expand to 2-3 sentences only when genuinely complex +- **@property** in class-level KDoc for all public properties (never as individual KDoc on the property); **@param** for function parameters +- **@return** for non-Unit return types +- **Example block**: add for DSL builders, complex functions, extension functions with non-obvious usage. Skip for trivial one-liners and simple getters +- **DSL builders**: always include an Example showing full usage with the receiver scope — this is critical for discoverability +- KDoc links (`[ClassName]`, `[methodName]`): only where it adds clear navigational value +- No **@throws** — don't document exceptions +- No **suspend** notes — coroutine nature is visible from the signature +- **Existing KDoc**: rewrite if incomplete (missing @param/@return/@property) or low quality \ No newline at end of file diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml index 2cb135a..4d44734 100644 --- a/config/detekt/detekt.yml +++ b/config/detekt/detekt.yml @@ -22,3 +22,22 @@ empty-blocks: EmptyFunctionBlock: excludes: *testFolders +comments: + UndocumentedPublicClass: + active: true + excludes: &testAndGeneratedFolders + - '**/test/**' + - '**/commonTest/**' + - '**/jvmTest/**' + - '**/jsTest/**' + - '**/iosTest/**' + - '**/generated-sources/**' + ignoreDefaultCompanionObject: true + UndocumentedPublicFunction: + active: true + excludes: *testAndGeneratedFolders + UndocumentedPublicProperty: + active: true + excludes: *testAndGeneratedFolders + searchProtectedProperty: false + ignoreEnumEntries: true diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt index 80f37ae..999e25a 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt @@ -51,7 +51,9 @@ private const val MCP_PROTOCOL_VERSION_HEADER = "mcp-protocol-version" private const val MCP_RESUMPTION_TOKEN_HEADER = "Last-Event-ID" /** - * Error class for Streamable HTTP transport errors. + * Represents an error from the Streamable HTTP transport. + * + * @property code HTTP status code associated with the error, or `null` if unavailable */ public class StreamableHttpError(public val code: Int? = null, message: String? = null) : Exception("Streamable HTTP error: $message") @@ -66,6 +68,9 @@ private sealed interface ConnectResult { * Client transport for Streamable HTTP: this implements the MCP Streamable HTTP transport specification. * It will connect to a server using HTTP POST for sending messages and HTTP GET with Server-Sent Events * for receiving messages. + * + * @property sessionId session identifier assigned by the server after initialization, or `null` before connection + * @property protocolVersion MCP protocol version negotiated with the server, or `null` before connection */ @Suppress("TooManyFunctions") public class StreamableHttpClientTransport( diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/internal/utils.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/internal/utils.kt index d1cbe78..37a57b7 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/internal/utils.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/internal/utils.kt @@ -2,4 +2,5 @@ package io.modelcontextprotocol.kotlin.sdk.internal import kotlinx.coroutines.CoroutineDispatcher +/** Platform-specific [CoroutineDispatcher] for I/O-bound operations. */ public expect val IODispatcher: CoroutineDispatcher diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt index 20739e0..43c0ae5 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt @@ -42,6 +42,7 @@ import kotlin.time.Duration.Companion.seconds private val logger = KotlinLogging.logger { } +/** Default implementation name used in MCP handshake. */ public const val IMPLEMENTATION_NAME: String = "mcp-ktor" /** @@ -51,6 +52,8 @@ public typealias ProgressCallback = (Progress) -> Unit /** * Additional initialization options. + * + * @property timeout default timeout for outgoing requests */ public open class ProtocolOptions( /** @@ -97,9 +100,13 @@ public class RequestOptions( public val onProgress: ProgressCallback? = null, public val timeout: Duration = DEFAULT_REQUEST_TIMEOUT, ) : TransportSendOptions(relatedRequestId, resumptionToken, onResumptionToken) { + /** Destructuring component for [onProgress]. */ public operator fun component4(): ProgressCallback? = onProgress + + /** Destructuring component for [timeout]. */ public operator fun component5(): Duration = timeout + /** Creates a copy of this [RequestOptions] with the specified fields replaced. */ public fun copy( relatedRequestId: RequestId? = this.relatedRequestId, resumptionToken: String? = this.resumptionToken, @@ -139,6 +146,12 @@ internal val COMPLETED = CompletableDeferred(Unit).also { it.complete(Unit) } /** * Implements MCP protocol framing on top of a pluggable transport, including * features like request/response linking, notifications, and progress. + * + * @property transport the active transport, or `null` if not connected + * @property requestHandlers registered request handlers keyed by method name + * @property notificationHandlers registered notification handlers keyed by method name + * @property responseHandlers pending response handlers keyed by request ID + * @property progressHandlers registered progress callbacks keyed by progress token */ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptions?) { public var transport: Transport? = null diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/TransportSendOptions.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/TransportSendOptions.kt index c56df04..5490288 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/TransportSendOptions.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/TransportSendOptions.kt @@ -17,10 +17,16 @@ public open class TransportSendOptions( public val resumptionToken: String? = null, public val onResumptionToken: ((String) -> Unit)? = null, ) { + /** Destructuring component for [relatedRequestId]. */ public operator fun component1(): RequestId? = relatedRequestId + + /** Destructuring component for [resumptionToken]. */ public operator fun component2(): String? = resumptionToken + + /** Destructuring component for [onResumptionToken]. */ public operator fun component3(): ((String) -> Unit)? = onResumptionToken + /** Creates a copy of this [TransportSendOptions] with the specified fields replaced. */ public open fun copy( relatedRequestId: RequestId? = this.relatedRequestId, resumptionToken: String? = this.resumptionToken, diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt index de75cc4..5f8bf9d 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt @@ -17,6 +17,7 @@ import kotlinx.coroutines.launch import kotlin.concurrent.atomics.AtomicBoolean import kotlin.concurrent.atomics.ExperimentalAtomicApi +/** WebSocket subprotocol identifier for MCP connections. */ public const val MCP_SUBPROTOCOL: String = "mcp" private val logger = KotlinLogging.logger {} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PingRequest.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PingRequest.kt index 2f1e3ae..c45ed30 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PingRequest.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PingRequest.kt @@ -4,6 +4,11 @@ import kotlinx.serialization.EncodeDefault import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.Serializable +/** + * Represents a ping request used to check if the connection is alive. + * + * @property meta optional request metadata + */ @Serializable public data class PingRequest(override val params: BaseRequestParams? = null) : ClientRequest, diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.kt index cff3b89..fae24c3 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.kt @@ -54,6 +54,10 @@ public data class ClientCapabilities( public val experimental: JsonObject? = null, ) { + /** + * @property sampling convenience value to enable the sampling capability + * @property elicitation convenience value to enable the elicitation capability + */ public companion object { public val sampling: JsonObject = EmptyJsonObject public val elicitation: JsonObject = EmptyJsonObject @@ -100,6 +104,10 @@ public data class ServerCapabilities( val experimental: JsonObject? = null, ) { + /** + * @property Logging convenience value to enable the logging capability + * @property Completions convenience value to enable the completions capability + */ public companion object { public val Logging: JsonObject = EmptyJsonObject public val Completions: JsonObject = EmptyJsonObject diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt index 3ae8997..ac867af 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt @@ -8,10 +8,13 @@ import kotlinx.serialization.json.JsonObject // Protocol Version Constants // ============================================================================ +/** The latest supported MCP protocol version string. */ public const val LATEST_PROTOCOL_VERSION: String = "2025-11-25" +/** The default protocol version used when negotiation is not performed. */ public const val DEFAULT_NEGOTIATED_PROTOCOL_VERSION: String = "2025-06-18" +/** All MCP protocol versions supported by this SDK. */ public val SUPPORTED_PROTOCOL_VERSIONS: List = listOf( LATEST_PROTOCOL_VERSION, "2025-06-18", @@ -25,6 +28,8 @@ public val SUPPORTED_PROTOCOL_VERSIONS: List = listOf( /** * Represents an entity that includes additional metadata in its responses. + * + * @property meta optional metadata attached to this entity */ @Serializable public sealed interface WithMeta { @@ -123,13 +128,19 @@ public enum class Role { * * References are used to point to other entities (prompts, resources, etc.) * without including their full definitions. + * + * @property type discriminator identifying the reference subtype */ @Serializable(with = ReferencePolymorphicSerializer::class) public sealed interface Reference { public val type: ReferenceType } -/** Discriminator for [Reference] subtypes used in completion and other operations. */ +/** + * Discriminator for [Reference] subtypes used in completion and other operations. + * + * @property value serialized string representation of this reference type + */ @Serializable public enum class ReferenceType(public val value: String) { @SerialName("ref/prompt") diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.kt index 79a76ea..c782e3e 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.kt @@ -12,6 +12,7 @@ import kotlinx.serialization.json.JsonObject * A request from the client to the server to ask for completion options. * * @property params The request parameters containing the argument to complete and its context. + * @property meta optional request metadata */ @Serializable public data class CompleteRequest(override val params: CompleteRequestParams) : ClientRequest { diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/content.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/content.kt index 45cfd0d..a1c9e4f 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/content.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/content.kt @@ -8,7 +8,11 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonObject -/** Discriminator values for the polymorphic [ContentBlock] hierarchy. */ +/** + * Discriminator values for the polymorphic [ContentBlock] hierarchy. + * + * @property value serialized string representation of this content type + */ @Serializable public enum class ContentTypes(public val value: String) { @SerialName("text") @@ -27,11 +31,19 @@ public enum class ContentTypes(public val value: String) { EMBEDDED_RESOURCE("resource"), } +/** + * Base interface for all content blocks in the protocol. + * + * @property type discriminator identifying the content block subtype + */ @Serializable(with = ContentBlockPolymorphicSerializer::class) public sealed interface ContentBlock : WithMeta { public val type: ContentTypes } +/** + * Content block that carries media data such as text, images, or audio. + */ @Serializable(with = MediaContentPolymorphicSerializer::class) public sealed interface MediaContent : ContentBlock diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.kt index fc7787b..bb303f0 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.kt @@ -46,6 +46,8 @@ public data class ElicitRequest(override val params: ElicitRequestParams) : Serv * Represents the parameters for an `elicitation/create` request. * * Implementations: [ElicitRequestFormParams], [ElicitRequestURLParams]. + * + * @property message The message to present to the user describing what information is being requested. */ @Serializable(with = ElicitRequestParamsSerializer::class) public sealed interface ElicitRequestParams : RequestParams { @@ -111,6 +113,7 @@ public fun ElicitRequestParams( * later via `tasks/result`. * @property requestedSchema A restricted subset of JSON Schema. Only top-level properties * are allowed, without nesting. + * @property mode The elicitation mode discriminator, always `"form"`. * @property meta Optional metadata. May include a progressToken for out-of-band progress notifications. */ @Serializable @@ -138,6 +141,7 @@ public data class ElicitRequestFormParams( * @property task If specified, the caller is requesting task-augmented execution. The request * will return a [CreateTaskResult] immediately, and the actual result can be retrieved * later via `tasks/result`. + * @property mode The elicitation mode discriminator, always `"url"`. * @property meta Optional metadata. May include a progressToken for out-of-band progress notifications. */ @Serializable diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt index f42139f..1a9ea2e 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt @@ -14,6 +14,7 @@ import kotlin.jvm.JvmOverloads import kotlin.uuid.ExperimentalUuidApi import kotlin.uuid.Uuid +/** JSON-RPC protocol version used by MCP (`"2.0"`). */ public const val JSONRPC_VERSION: String = "2.0" /** @@ -40,12 +41,20 @@ public fun RequestId(value: Long): RequestId = RequestId.NumberId(value) @Serializable(with = RequestIdPolymorphicSerializer::class) public sealed interface RequestId { - /** A string-based request identifier. */ + /** + * A string-based request identifier. + * + * @property value the string representation of this request ID + */ @JvmInline @Serializable public value class StringId(public val value: String) : RequestId - /** A numeric request identifier. */ + /** + * A numeric request identifier. + * + * @property value the numeric representation of this request ID + */ @JvmInline @Serializable public value class NumberId(public val value: Long) : RequestId @@ -95,12 +104,17 @@ internal fun JSONRPCNotification.fromJSON(): Notification = * Base interface for all JSON-RPC 2.0 messages. * * All messages in the MCP protocol follow the JSON-RPC 2.0 specification. + * + * @property jsonrpc the JSON-RPC protocol version, always `"2.0"` */ @Serializable(with = JSONRPCMessagePolymorphicSerializer::class) public sealed interface JSONRPCMessage { public val jsonrpc: String } +/** + * Represents an empty JSON-RPC message used as a placeholder or no-op response. + */ @Serializable public data object JSONRPCEmptyMessage : JSONRPCMessage { override val jsonrpc: String = JSONRPC_VERSION diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonUtils.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonUtils.kt index 566905f..d83e012 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonUtils.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonUtils.kt @@ -11,8 +11,10 @@ import kotlinx.serialization.json.add import kotlinx.serialization.json.buildJsonArray import kotlinx.serialization.json.buildJsonObject +/** Reusable empty [JsonObject] instance. */ public val EmptyJsonObject: JsonObject = JsonObject(emptyMap()) +/** Pre-configured [Json] instance for MCP serialization. */ @OptIn(ExperimentalSerializationApi::class) public val McpJson: Json by lazy { Json { diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/methods.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/methods.kt index 5697df8..dae2fc7 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/methods.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/methods.kt @@ -4,6 +4,8 @@ import kotlinx.serialization.Serializable /** * Represents a method in the protocol, which can be predefined or custom. + * + * @property value the string representation of this method name */ @Serializable(with = MethodSerializer::class) public sealed interface Method { diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/notification.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/notification.kt index 08b1f12..24be040 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/notification.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/notification.kt @@ -11,6 +11,9 @@ import kotlinx.serialization.json.JsonObject /** * Represents a notification in the protocol. + * + * @property method the notification method identifier + * @property params optional notification parameters */ @Serializable(with = NotificationPolymorphicSerializer::class) public sealed interface Notification { @@ -73,6 +76,7 @@ public class Progress( * @property method The custom method name. By convention, custom methods often contain * organization-specific prefixes (e.g., "mycompany/custom_event"). * @property params Raw JSON parameters for the custom notification, if present. + * @property meta optional metadata for this notification */ @Serializable public data class CustomNotification(override val method: Method, override val params: BaseNotificationParams? = null) : diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.kt index 683186c..9f52ec9 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.kt @@ -11,6 +11,12 @@ import kotlinx.serialization.json.long import kotlinx.serialization.json.longOrNull import kotlin.jvm.JvmInline +/** + * Metadata attached to a request's `_meta` field. + * + * @property json the raw JSON object containing the metadata + * @property progressToken optional progress token for tracking request progress + */ @JvmInline @Serializable public value class RequestMeta(public val json: JsonObject) { @@ -43,6 +49,9 @@ public value class RequestMeta(public val json: JsonObject) { public operator fun get(key: String): JsonElement? = json[key] } +/** + * Base interface for parameters attached to a [Request]. + */ @Serializable public sealed interface RequestParams { /** @@ -55,11 +64,17 @@ public sealed interface RequestParams { public val meta: RequestMeta? } +/** + * Default [RequestParams] implementation carrying only optional metadata. + */ @Serializable public data class BaseRequestParams(@SerialName("_meta") override val meta: RequestMeta? = null) : RequestParams /** * Represents a request in the protocol. + * + * @property method the request method identifier + * @property params optional request parameters */ @OptIn(ExperimentalSerializationApi::class) @Serializable(with = RequestPolymorphicSerializer::class) @@ -112,9 +127,15 @@ public sealed interface PaginatedRequest : Request { @Serializable(with = RequestResultPolymorphicSerializer::class) public sealed interface RequestResult : WithMeta +/** + * Represents a result returned by the server in response to a [ClientRequest]. + */ @Serializable(with = ClientResultPolymorphicSerializer::class) public sealed interface ClientResult : RequestResult +/** + * Represents a result returned by the client in response to a [ServerRequest]. + */ @Serializable(with = ServerResultPolymorphicSerializer::class) public sealed interface ServerResult : RequestResult @@ -129,7 +150,9 @@ public data class EmptyResult(@SerialName("_meta") override val meta: JsonObject ServerResult /** - * Represents a request supporting pagination. + * Represents a paginated result. + * + * @property nextCursor opaque token for retrieving the next page of results, or `null` if no more results */ @Serializable public sealed interface PaginatedResult : RequestResult { diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.kt index fa316de..86ce3ce 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.kt @@ -8,6 +8,9 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonObject +/** + * Common interface for resource-like types that share URI, name, and description fields. + */ @Serializable public sealed interface ResourceLike : WithMeta diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.kt index af7b1e7..194ea4f 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.kt @@ -53,6 +53,7 @@ public data class Root( * `listChanged = true` to receive these requests. * * @property params Optional request parameters containing metadata. + * @property meta optional request metadata */ @Serializable public data class ListRootsRequest(override val params: BaseRequestParams? = null) : ServerRequest { diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.kt index 1ade1bf..97fc0c0 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.kt @@ -243,10 +243,19 @@ public data class CreateMessageResult( override val meta: JsonObject? = null, ) : ClientResult -/** The reason why the LLM stopped generating tokens. */ +/** + * The reason why the LLM stopped generating tokens. + * + * @property value the string representation of this stop reason + */ @JvmInline @Serializable public value class StopReason(public val value: String) { + /** + * @property EndTurn generation ended naturally + * @property StopSequence a stop sequence was encountered + * @property MaxTokens the maximum token limit was reached + */ public companion object { public val EndTurn: StopReason = StopReason("endTurn") public val StopSequence: StopReason = StopReason("stopSequence") diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/schema.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/schema.kt index dda2ac6..479cc66 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/schema.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/schema.kt @@ -24,6 +24,7 @@ public sealed interface PrimitiveSchemaDefinition * @property minLength Minimum string length. * @property maxLength Maximum string length. * @property format Optional format constraint (e.g., email, URI, date). + * @property type JSON Schema type discriminator, always `"string"`. * @property default Optional default value. */ @Serializable @@ -72,6 +73,7 @@ public sealed interface NumberSchemaDefinition : PrimitiveSchemaDefinition * @property description Optional description for the field. * @property minimum Minimum allowed value. * @property maximum Maximum allowed value. + * @property type JSON Schema type discriminator, always `"integer"`. * @property default Optional default value. */ @Serializable @@ -93,6 +95,7 @@ public data class IntegerSchema( * @property description Optional description for the field. * @property minimum Minimum allowed value. * @property maximum Maximum allowed value. + * @property type JSON Schema type discriminator, always `"number"`. * @property default Optional default value. */ @Serializable @@ -112,6 +115,7 @@ public data class DoubleSchema( * * @property title Optional display title for the field. * @property description Optional description for the field. + * @property type JSON Schema type discriminator, always `"boolean"`. * @property default Optional default value. */ @Serializable @@ -157,6 +161,7 @@ public sealed interface SingleSelectEnumSchema : EnumSchemaDefinition * @property title Optional display title for the field. * @property description Optional description for the field. * @property enumValues Array of enum values to choose from. + * @property type JSON Schema type discriminator, always `"string"`. * @property default Optional default value. */ @Serializable @@ -177,6 +182,7 @@ public data class UntitledSingleSelectEnumSchema( * @property title Optional display title for the field. * @property description Optional description for the field. * @property oneOf Array of enum options with values and display labels. + * @property type JSON Schema type discriminator, always `"string"`. * @property default Optional default value. */ @Serializable @@ -199,6 +205,7 @@ public data class TitledSingleSelectEnumSchema( * @property description Optional description for the field. * @property enumValues Array of enum values to choose from. * @property enumNames Display names for enum values. Non-standard according to JSON Schema 2020-12. + * @property type JSON Schema type discriminator, always `"string"`. * @property default Optional default value. */ @Deprecated("Use TitledSingleSelectEnumSchema instead") @@ -231,6 +238,7 @@ public sealed interface MultiSelectEnumSchema : EnumSchemaDefinition * @property minItems Minimum number of items to select. * @property maxItems Maximum number of items to select. * @property items Schema for the array items. + * @property type JSON Schema type discriminator, always `"array"`. * @property default Optional default value. */ @Serializable @@ -248,6 +256,7 @@ public data class UntitledMultiSelectEnumSchema( /** * Schema for the array items with plain enum values. * + * @property type JSON Schema type discriminator, always `"string"`. * @property enumValues Array of enum values to choose from. */ @Serializable @@ -268,6 +277,7 @@ public data class UntitledMultiSelectEnumSchema( * @property minItems Minimum number of items to select. * @property maxItems Maximum number of items to select. * @property items Schema for array items with enum options and display labels. + * @property type JSON Schema type discriminator, always `"array"`. * @property default Optional default value. */ @Serializable diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tasks.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tasks.kt index 42b5752..0f154e5 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tasks.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tasks.kt @@ -17,6 +17,14 @@ public const val RELATED_TASK_META_KEY: String = "io.modelcontextprotocol/relate /** * Common fields shared by all types representing the task state. + * + * @property taskId the task identifier + * @property status current task state + * @property statusMessage optional human-readable message describing the current task state + * @property createdAt ISO 8601 timestamp when the task was created + * @property lastUpdatedAt ISO 8601 timestamp when the task was last updated + * @property ttl actual retention duration from creation in milliseconds, or `null` for unlimited + * @property pollInterval suggested polling interval in milliseconds */ public sealed interface TaskFields { public val taskId: String @@ -228,6 +236,7 @@ public data class GetTaskPayloadRequestParams( * The structure matches the result type of the original request. * For example, a tools/call task would return the [CallToolResult] structure. * + * @property json the raw JSON object containing the result payload * @property meta Optional metadata for this response. */ @JvmInline diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/ResourceTemplateMatcher.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/ResourceTemplateMatcher.kt index 5ba956d..867cc9f 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/ResourceTemplateMatcher.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/ResourceTemplateMatcher.kt @@ -32,6 +32,8 @@ public class MatchResult(public val variables: Map, public val s * candidate URIs against that template via [match]. The returned [MatchResult.score] * must reflect match specificity so that a selection algorithm can prefer the most * specific template when multiple templates match the same URI. + * + * @property resourceTemplate the template to match URIs against */ public interface ResourceTemplateMatcher { diff --git a/kotlin-sdk-core/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/sdk/internal/utils.jvm.kt b/kotlin-sdk-core/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/sdk/internal/utils.jvm.kt index d00c1ab..35a1e98 100644 --- a/kotlin-sdk-core/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/sdk/internal/utils.jvm.kt +++ b/kotlin-sdk-core/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/sdk/internal/utils.jvm.kt @@ -3,5 +3,6 @@ package io.modelcontextprotocol.kotlin.sdk.internal import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers +/** Platform-specific [CoroutineDispatcher] for I/O-bound operations. */ public actual val IODispatcher: CoroutineDispatcher get() = Dispatchers.IO diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt index 619266a..f8c6581 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt @@ -26,6 +26,8 @@ internal const val SESSION_ID_PARAM = "sessionId" * Server transport for SSE: this will send messages over an SSE connection and receive messages from HTTP POST requests. * * Creates a new SSE server transport, which will direct the client to POST messages to the relative or absolute URL identified by `_endpoint`. + * + * @property sessionId unique identifier for this transport session, generated randomly on creation */ @OptIn(ExperimentalAtomicApi::class) public class SseServerTransport(private val endpoint: String, private val session: ServerSSESession) : diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt index e548e03..2f55c76 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt @@ -40,6 +40,8 @@ private val logger = KotlinLogging.logger {} /** * Represents a server session. + * + * @property sessionId unique identifier for this session, generated randomly on creation */ @Suppress("TooManyFunctions") public open class ServerSession( diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt index 1ea8d83..70948d9 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt @@ -71,6 +71,7 @@ private data class SessionContext(val session: ServerSSESession?, val call: Appl * - No session validation is performed * * @param configuration Transport configuration. See [Configuration] for available options. + * @property sessionId session identifier assigned after initialization, or `null` in stateless mode */ @OptIn(ExperimentalUuidApi::class, ExperimentalAtomicApi::class) @Suppress("TooManyFunctions") @@ -122,28 +123,16 @@ public class StreamableHttpServerTransport(private val configuration: Configurat ) /** - * Configuration for managing various aspects of the StreamableHttpServerTransport. - * - * @property enableJsonResponse Determines whether the server should return JSON responses. - * Defaults to `false`. - * - * @property enableDnsRebindingProtection Enables DNS rebinding protection. - * Defaults to `false`. - * - * @property allowedHosts A list of hosts allowed for server communication. - * Defaults to `null`, allowing all hosts. - * - * @property allowedOrigins A list of allowed origins for CORS (Cross-Origin Resource Sharing). - * Defaults to `null`, allowing all origins. - * - * @property eventStore The `EventStore` instance for handling resumable events. - * Defaults to `null`, disabling resumability. - * - * @property retryInterval Retry interval for event handling or reconnection attempts. - * Defaults to `null`. + * Configuration options for [StreamableHttpServerTransport]. * + * @property enableJsonResponse when `true`, returns direct JSON responses instead of SSE streams + * @property enableDnsRebindingProtection enables DNS rebinding protection + * @property allowedHosts list of hosts allowed for server communication, or `null` to allow all + * @property allowedOrigins list of allowed CORS origins, or `null` to allow all + * @property eventStore store for resumable events, or `null` to disable resumability + * @property retryInterval retry interval for SSE reconnection attempts * @property maxRequestBodySize Maximum allowed size (in bytes) for incoming request bodies. - * Defaults to 4 MB (4,194,304 bytes). + * Defaults to 4 MB (4,194,304 bytes). */ public class Configuration( public val enableJsonResponse: Boolean = false, From c470c5df535b9f679f11c380998cf07ca733efe6 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Thu, 2 Apr 2026 16:07:38 +0300 Subject: [PATCH 220/449] chore: bump project version to 0.11.0 (#669) bump project version to 0.11.0 ## Motivation and Context Release 0.11 ## How Has This Been Tested? Regression ## Breaking Changes No ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] 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 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 1d5620a..09a658b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,4 +8,4 @@ kotlin.daemon.jvmargs=-Xmx4G kotlin.native.ignoreDisabledTargets=true group=io.modelcontextprotocol -version=0.10.0 +version=0.11.0 From 0ab31cfa4be8858d95f6266010df576319c650f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 05:13:07 +0000 Subject: [PATCH 221/449] chore(deps): bump io.ktor:ktor-bom from 3.2.3 to 3.3.3 in /samples/weather-stdio-server (#656) --- samples/weather-stdio-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/weather-stdio-server/gradle/libs.versions.toml b/samples/weather-stdio-server/gradle/libs.versions.toml index 0b75f09..c1a7997 100644 --- a/samples/weather-stdio-server/gradle/libs.versions.toml +++ b/samples/weather-stdio-server/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] coroutines = "1.10.2" kotlin = "2.2.21" -ktor = "3.2.3" +ktor = "3.3.3" logging = "8.0.01" mcp-kotlin = "0.10.0" shadow = "9.4.1" From f8f04d623ccdfa949fbbd0c80bd53ecfc53e2942 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 05:28:39 +0000 Subject: [PATCH 222/449] chore(deps): bump io.ktor:ktor-bom from 3.2.3 to 3.3.3 in /samples/simple-streamable-server (#653) --- samples/simple-streamable-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/simple-streamable-server/gradle/libs.versions.toml b/samples/simple-streamable-server/gradle/libs.versions.toml index a9fced7..77707e0 100644 --- a/samples/simple-streamable-server/gradle/libs.versions.toml +++ b/samples/simple-streamable-server/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] kotlin = "2.2.21" -ktor = "3.2.3" +ktor = "3.3.3" junit = "6.0.3" mcp-kotlin = "0.10.0" slf4j = "2.0.17" From 7cf65c6e6e2a877f2cbf2c323cf6959ac934882a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 15:56:55 +0300 Subject: [PATCH 223/449] chore(deps): bump the all-dependencies group in /integration-test/src/jvmTest/typescript with 2 updates (#677) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the all-dependencies group in /integration-test/src/jvmTest/typescript with 2 updates: [@modelcontextprotocol/sdk](https://github.com/modelcontextprotocol/typescript-sdk) and [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node). Updates `@modelcontextprotocol/sdk` from 1.28.0 to 1.29.0
Release notes

Sourced from @​modelcontextprotocol/sdk's releases.

v1.29.0

What's Changed

New Contributors

Full Changelog: https://github.com/modelcontextprotocol/typescript-sdk/compare/v1.28.0...v1.29.0

Commits

Updates `@types/node` from 25.5.0 to 25.5.2
Commits

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../src/jvmTest/typescript/package-lock.json | 16 ++++++++-------- .../src/jvmTest/typescript/package.json | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index ca30f65..7d96bae 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -8,14 +8,14 @@ "name": "mcp-typescript-test-sdk", "version": "1.0.0", "dependencies": { - "@modelcontextprotocol/sdk": "^1.28.0", + "@modelcontextprotocol/sdk": "^1.29.0", "express": "^5.2.1", "zod": "^4.3.6" }, "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^25.5.0", + "@types/node": "^25.5.2", "cors": "^2.8.6", "tsx": "^4.21.0", "typescript": "^6.0.2" @@ -476,9 +476,9 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.28.0.tgz", - "integrity": "sha512-gmloF+i+flI8ouQK7MWW4mOwuMh4RePBuPFAEPC6+pdqyWOUMDOixb6qZ69owLJpz6XmyllCouc4t8YWO+E2Nw==", + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "license": "MIT", "dependencies": { "@hono/node-server": "^1.19.9", @@ -579,9 +579,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", - "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", + "version": "25.5.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.2.tgz", + "integrity": "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/integration-test/src/jvmTest/typescript/package.json b/integration-test/src/jvmTest/typescript/package.json index 0a43cd2..20249d0 100644 --- a/integration-test/src/jvmTest/typescript/package.json +++ b/integration-test/src/jvmTest/typescript/package.json @@ -9,14 +9,14 @@ "server:http": "tsx server/http-server.ts" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.28.0", + "@modelcontextprotocol/sdk": "^1.29.0", "express": "^5.2.1", "zod": "^4.3.6" }, "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^25.5.0", + "@types/node": "^25.5.2", "cors": "^2.8.6", "tsx": "^4.21.0", "typescript": "^6.0.2" From f0f8b30729fed2a19408d44032a52f5931f9cb2e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 10:30:09 +0300 Subject: [PATCH 224/449] chore(deps): bump mcp-kotlin from 0.10.0 to 0.11.0 in /samples/weather-stdio-server (#676) --- samples/weather-stdio-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/weather-stdio-server/gradle/libs.versions.toml b/samples/weather-stdio-server/gradle/libs.versions.toml index c1a7997..cc2b53a 100644 --- a/samples/weather-stdio-server/gradle/libs.versions.toml +++ b/samples/weather-stdio-server/gradle/libs.versions.toml @@ -3,7 +3,7 @@ coroutines = "1.10.2" kotlin = "2.2.21" ktor = "3.3.3" logging = "8.0.01" -mcp-kotlin = "0.10.0" +mcp-kotlin = "0.11.0" shadow = "9.4.1" slf4j = "2.0.17" From 7c87ed1ba5de505b0af11df6cb5b349515826974 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 07:53:14 +0000 Subject: [PATCH 225/449] chore(deps): bump the kotest group with 3 updates (#674) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 78f55fe..f2836ef 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,7 +16,7 @@ openapi-generator = "7.21.0" awaitility = "4.3.0" collections-immutable = "0.4.0" coroutines = "1.10.2" -kotest = "6.1.9" +kotest = "6.1.11" kotlinx-io = "0.9.0" ktor = "3.3.3" logging = "8.0.01" From 3f7f499dbe5dddf456b01b35a08bacfe23b1b548 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 08:18:58 +0000 Subject: [PATCH 226/449] chore(deps): bump mcp-kotlin from 0.10.0 to 0.11.0 in /samples/kotlin-mcp-server (#673) --- samples/kotlin-mcp-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-server/gradle/libs.versions.toml b/samples/kotlin-mcp-server/gradle/libs.versions.toml index 1c59a88..05007d0 100644 --- a/samples/kotlin-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-server/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] kotlin = "2.2.21" ktor = "3.3.3" -mcp-kotlin = "0.10.0" +mcp-kotlin = "0.11.0" slf4j = "2.0.17" shadow = "9.4.1" From 4144db67365f8c8b9c20647c42facfcf4513dee8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 08:33:49 +0000 Subject: [PATCH 227/449] chore(deps): bump io.modelcontextprotocol:kotlin-sdk-client from 0.10.0 to 0.11.0 in /samples/kotlin-mcp-client (#672) --- samples/kotlin-mcp-client/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index 38cb588..8c93448 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -2,7 +2,7 @@ anthropic = "2.18.0" kotlin = "2.2.21" ktor = "3.2.3" -mcp-kotlin = "0.10.0" +mcp-kotlin = "0.11.0" shadow = "9.4.1" slf4j = "2.0.17" From 20ac69f9af424e6f29d74a5f01dce7c4e5fd0c88 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 09:37:28 +0000 Subject: [PATCH 228/449] chore(deps): bump mcp-kotlin from 0.10.0 to 0.11.0 in /samples/simple-streamable-server (#675) --- samples/simple-streamable-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/simple-streamable-server/gradle/libs.versions.toml b/samples/simple-streamable-server/gradle/libs.versions.toml index 77707e0..e74e9df 100644 --- a/samples/simple-streamable-server/gradle/libs.versions.toml +++ b/samples/simple-streamable-server/gradle/libs.versions.toml @@ -2,7 +2,7 @@ kotlin = "2.2.21" ktor = "3.3.3" junit = "6.0.3" -mcp-kotlin = "0.10.0" +mcp-kotlin = "0.11.0" slf4j = "2.0.17" shadow = "9.4.1" From 8442ab852b39a4c5493397976edd6232a10449ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 12:07:04 +0000 Subject: [PATCH 229/449] chore(deps): bump com.anthropic:anthropic-java from 2.18.0 to 2.19.0 in /samples/kotlin-mcp-client in the other-dependencies group across 1 directory (#671) --- samples/kotlin-mcp-client/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index 8c93448..130a75b 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -anthropic = "2.18.0" +anthropic = "2.19.0" kotlin = "2.2.21" ktor = "3.2.3" mcp-kotlin = "0.11.0" From 66d17956c1390b5eda195c2d3a54054ce84e1c76 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 22:29:00 +0200 Subject: [PATCH 230/449] chore(deps): bump @hono/node-server from 1.19.11 to 1.19.13 in /integration-test/src/jvmTest/typescript (#679) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [@hono/node-server](https://github.com/honojs/node-server) from 1.19.11 to 1.19.13.
Release notes

Sourced from @​hono/node-server's releases.

v1.19.13

Security Fix

Fixed an issue in Serve Static Middleware where inconsistent handling of repeated slashes (//) between the router and static file resolution could allow middleware to be bypassed. Users of Serve Static Middleware are encouraged to upgrade to this version.

See GHSA-92pp-h63x-v22m for details.

v1.19.12

What's Changed

Full Changelog: https://github.com/honojs/node-server/compare/v1.19.11...v1.19.12

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@hono/node-server&package-manager=npm_and_yarn&previous-version=1.19.11&new-version=1.19.13)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/modelcontextprotocol/kotlin-sdk/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- integration-test/src/jvmTest/typescript/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index 7d96bae..24d5227 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -464,9 +464,9 @@ } }, "node_modules/@hono/node-server": { - "version": "1.19.11", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz", - "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==", + "version": "1.19.13", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.13.tgz", + "integrity": "sha512-TsQLe4i2gvoTtrHje625ngThGBySOgSK3Xo2XRYOdqGN1teR8+I7vchQC46uLJi8OF62YTYA3AhSpumtkhsaKQ==", "license": "MIT", "engines": { "node": ">=18.14.1" From 12171dc2ec9157611d1a2a07ecd6f12a949e5828 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 20:45:43 +0000 Subject: [PATCH 231/449] chore(deps): bump hono from 4.12.9 to 4.12.12 in /integration-test/src/jvmTest/typescript (#680) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [hono](https://github.com/honojs/hono) from 4.12.9 to 4.12.12.
Release notes

Sourced from hono's releases.

v4.12.12

Security fixes

This release includes fixes for the following security issues:

Middleware bypass via repeated slashes in serveStatic

Affects: Serve Static middleware. Fixes a path normalization inconsistency where repeated slashes (//) could bypass route-based middleware protections and allow access to protected static files. GHSA-wmmm-f939-6g9c

Path traversal in toSSG() allows writing files outside the output directory

Affects: toSSG() for Static Site Generation. Fixes a path traversal issue where crafted ssgParams values could write files outside the configured output directory. GHSA-xf4j-xp2r-rqqx

Incorrect IP matching in ipRestriction() for IPv4-mapped IPv6 addresses

Affects: IP Restriction Middleware. Fixes improper handling of IPv4-mapped IPv6 addresses (e.g. ::ffff:127.0.0.1) that could cause allow/deny rules to be bypassed. GHSA-xpcf-pg52-r92g

Missing validation of cookie name on write path in setCookie()

Affects: setCookie(), serialize(), and serializeSigned() from hono/cookie. Fixes missing validation of cookie names on the write path, preventing inconsistent handling between parsing and serialization. GHSA-26pp-8wgv-hjvm

Non-breaking space prefix bypass in cookie name handling in getCookie()

Affects: getCookie() from hono/cookie. Fixes a discrepancy in cookie name handling that could allow attacker-controlled cookies to override legitimate ones and bypass prefix protections. GHSA-r5rp-j6wh-rvv4


Users who use Serve Static, Static Site Generation, Cookie utilities, or IP restriction middleware are strongly encouraged to upgrade to this version.

v4.12.11

What's Changed

New Contributors

Full Changelog: https://github.com/honojs/hono/compare/v4.12.10...v4.12.11

v4.12.10

What's Changed

New Contributors

... (truncated)

Commits

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- integration-test/src/jvmTest/typescript/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index 24d5227..dd0c9b4 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -1197,9 +1197,9 @@ } }, "node_modules/hono": { - "version": "4.12.9", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.9.tgz", - "integrity": "sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA==", + "version": "4.12.12", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.12.tgz", + "integrity": "sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q==", "license": "MIT", "engines": { "node": ">=16.9.0" From cceca4df58f128b75d9766133b7ecbf986397ccf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 21:13:53 +0000 Subject: [PATCH 232/449] chore(deps): bump io.ktor:ktor-bom from 3.2.3 to 3.3.3 in /samples/kotlin-mcp-client (#650) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [io.ktor:ktor-bom](https://github.com/ktorio/ktor) from 3.2.3 to 3.3.3.
Release notes

Sourced from io.ktor:ktor-bom's releases.

3.3.3

Published 26 November 2025

Improvements

  • KTOR-6837 Discrepancies when parsing URL host with CIO and Darwin engines compared to the rest engines
  • KTOR-9050 Logging: SimpleLogger should be an object, not a class
  • KTOR-9094 Jetty Client: Support HTTP/2 over cleartext (h2c)
  • KTOR-9120 OpenAPI gen: missing operationId for KDoc fields
  • KTOR-3019 Improve logging for CORS plugin

Bugfixes

  • KTOR-8671 Netty: RejectedExecutionException during shutdown on MacOS when dev mode is enabled
  • KTOR-9096 Darwin: New SSE handlers stop responding after canceling few SSE sessions
  • KTOR-9125 Double ResponseSent invocation when exception is thrown after respond
  • KTOR-8878 OpenAPI: StackOverflowError when a response object has property with @Contextual serializer
  • KTOR-8947 Java, ContentEncoding: IllegalHeaderNameException is thrown for ":status" pseudo header with HTTP/2
  • KTOR-9092 NettyHttp2Handler throws IllegalArgumentException: 'ktor.ApplicationCall' is already in use
  • KTOR-8924 Curl: Client sends both Transfer-Encoding and Content-Length headers for DELETE requests with body
  • KTOR-8838 Exception handling issue in client cache

3.3.2

Published 5 November 2025

Improvements

  • WebRTC Client. Remove redundant targets (KTOR-9062)
  • Add Socks proxy support to Darwin engine (KTOR-8968)
  • Java: Improve error message when SOCKS proxy is used (KTOR-2908)

Bugfixes

  • HttpRequestRetry: SendCountExceedException when max retries is more than maxSendCount of HttpSend (KTOR-5850)
  • Darwin: The maxFrameSize option has no effect (KTOR-6963)
  • OpenAPI: StackOverflowError when a response object has property with @Contextual serializer (KTOR-8878)
  • OpenAPI gen: missing KDoc fields (KTOR-9021)
  • Server call.request.path() returns routing selectors in path (KTOR-7639)
  • StaticContent doesn't allow siblings (KTOR-9012)
  • HttpCache: FileStorage doesn't use given dispatcher for all file operations (KTOR-8832)
  • Curl: SOCKS proxy doesn't work (KTOR-9008)
  • Netty: java.lang.VerifyError is thrown on Android since 3.3.0 (KTOR-8916)
  • Response body channel is canceled while the body is being saved when having HttpRequestRetry and onDownload (KTOR-8975)
  • HttpCache: InvalidCacheStateException when varyKeys stored in files contain uppercase letters since 3.3.0 (KTOR-8970)

3.3.1

Published 8 October 2025

Improvements

  • Add a note about SSE session lifetime in KDoc (KTOR-8440)
  • Update Kotlin to 2.2.20 (KTOR-8896)

Bugfixes

  • NumberFormatException when Content-Length header value contains null bytes (KTOR-4828)

... (truncated)

Changelog

Sourced from io.ktor:ktor-bom's changelog.

3.3.3

Published 26 November 2025

Improvements

  • KTOR-6837 Discrepancies when parsing URL host with CIO and Darwin engines compared to the rest engines
  • KTOR-9050 Logging: SimpleLogger should be an object, not a class
  • KTOR-9094 Jetty Client: Support HTTP/2 over cleartext (h2c)
  • KTOR-9120 OpenAPI gen: missing operationId for KDoc fields
  • KTOR-3019 Improve logging for CORS plugin

Bugfixes

  • KTOR-8671 Netty: RejectedExecutionException during shutdown on MacOS when dev mode is enabled
  • KTOR-9096 Darwin: New SSE handlers stop responding after canceling few SSE sessions
  • KTOR-9125 Double ResponseSent invocation when exception is thrown after respond
  • KTOR-8878 OpenAPI: StackOverflowError when a response object has property with @​Contextual serializer
  • KTOR-8947 Java, ContentEncoding: IllegalHeaderNameException is thrown for ":status" pseudo header with HTTP/2
  • KTOR-9092 NettyHttp2Handler throws IllegalArgumentException: 'ktor.ApplicationCall' is already in use
  • KTOR-8924 Curl: Client sends both Transfer-Encoding and Content-Length headers for DELETE requests with body
  • KTOR-8838 Exception handling issue in client cache

3.3.2

Published 5 November 2025

Improvements

  • WebRTC Client. Remove redundant targets (KTOR-9062)
  • Add Socks proxy support to Darwin engine (KTOR-8968)
  • Java: Improve error message when SOCKS proxy is used (KTOR-2908)

Bugfixes

  • HttpRequestRetry: SendCountExceedException when max retries is more than maxSendCount of HttpSend (KTOR-5850)
  • Darwin: The maxFrameSize option has no effect (KTOR-6963)
  • OpenAPI: StackOverflowError when a response object has property with @​Contextual serializer (KTOR-8878)
  • OpenAPI gen: missing KDoc fields (KTOR-9021)
  • Server call.request.path() returns routing selectors in path (KTOR-7639)
  • StaticContent doesn't allow siblings (KTOR-9012)
  • HttpCache: FileStorage doesn't use given dispatcher for all file operations (KTOR-8832)
  • Curl: SOCKS proxy doesn't work (KTOR-9008)
  • Netty: java.lang.VerifyError is thrown on Android since 3.3.0 (KTOR-8916)
  • Response body channel is canceled while the body is being saved when having HttpRequestRetry and onDownload (KTOR-8975)
  • HttpCache: InvalidCacheStateException when varyKeys stored in files contain uppercase letters since 3.3.0 (KTOR-8970)

3.3.1

Published 8 October 2025

Improvements

  • Add a note about SSE session lifetime in KDoc (KTOR-8440)
  • Update Kotlin to 2.2.20 (KTOR-8896)

... (truncated)

Commits
  • 629d22c Release 3.3.3 (#5219)
  • 77bbf10 Server Netty. Fix rejected execution during engine stop (#8671)
  • 75439b0 KTOR-9096 Fix SSE cancellation leak on Darwin (#5208)
  • 62ea8e4 KTOR-6837 Fix client URL handling for invalid DefaultRequest.host
  • ef09e84 Add stream.demand calls to JettyResponseListener
  • 47e6049 KTOR-9094 Jetty Client: Allow h2c traffic
  • e70922e KTOR-9125 Fix double ResponseSent invocation when exception is thrown after r...
  • 589f02d KTOR-3019 Improve CORS plugin logging to ease troubleshooting (#5194)
  • 5a66b47 Update Gradle and Develocity (#5201)
  • 2ba2f02 KTOR-8947 Filter pseudo-headers in Java client (#5198)
  • Additional commits viewable in compare view

Most Recent Ignore Conditions Applied to This Pull Request | Dependency Name | Ignore Conditions | | --- | --- | | io.ktor:ktor-bom | [>= 3.4.a, < 3.5] |
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-client/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index 130a75b..f56c437 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] anthropic = "2.19.0" kotlin = "2.2.21" -ktor = "3.2.3" +ktor = "3.3.3" mcp-kotlin = "0.11.0" shadow = "9.4.1" slf4j = "2.0.17" From 8a7f9f8d31e4bb222a0a39858b1d5d598b69a788 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Fri, 10 Apr 2026 13:51:03 +0200 Subject: [PATCH 233/449] fix(server): GET SSE stream crash on Netty in Streamable HTTP (#681) `appendSseHeaders()` was called inside Ktor's `sse {}` block where response headers are already committed. On Netty this throws `UnsupportedOperationException`, killing the SSE stream and causing the client to retry in a loop ## How Has This Been Tested? unit, simple-streamable-server sample ## Breaking Changes none ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 - [x ] I have added appropriate error handling - [ x] I have added or updated documentation as needed --- .../kotlin/sdk/server/KtorServer.kt | 14 ++++++ .../server/StreamableHttpServerTransport.kt | 8 ++-- .../StreamableHttpServerTransportTest.kt | 46 +++++++++++++++++++ 3 files changed, 64 insertions(+), 4 deletions(-) diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt index 35dcc7e..806a479 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt @@ -3,13 +3,17 @@ package io.modelcontextprotocol.kotlin.sdk.server import io.github.oshai.kotlinlogging.KotlinLogging +import io.ktor.http.HttpMethod import io.ktor.http.HttpStatusCode import io.ktor.server.application.Application import io.ktor.server.application.ApplicationCall +import io.ktor.server.application.ApplicationCallPipeline import io.ktor.server.application.MissingApplicationPluginException import io.ktor.server.application.install import io.ktor.server.request.ApplicationRequest import io.ktor.server.request.header +import io.ktor.server.request.httpMethod +import io.ktor.server.response.header import io.ktor.server.response.respond import io.ktor.server.routing.Route import io.ktor.server.routing.RoutingContext @@ -114,6 +118,16 @@ private fun Application.mcpStreamableHttp( routing { route(path) { + // Set Mcp-Session-Id on GET responses before Ktor's sse {} commits headers. + intercept(ApplicationCallPipeline.Plugins) { + if (context.request.httpMethod == HttpMethod.Get) { + val sessionId = context.request.header(MCP_SESSION_ID_HEADER) + if (sessionId != null && transportManager.getTransport(sessionId) != null) { + context.response.header(MCP_SESSION_ID_HEADER, sessionId) + } + } + } + sse { val transport = existingStreamableTransport(call, transportManager) ?: return@sse transport.handleRequest(this, call) diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt index 70948d9..17e6a28 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt @@ -453,8 +453,8 @@ public class StreamableHttpServerTransport(private val configuration: Configurat return } - call.appendSseHeaders() - flushSse(sseSession) // flush headers immediately + // SSE headers (Content-Type, Cache-Control, Connection) are already set by the framework's SSE handler + flushSse(sseSession) streamsMapping[STANDALONE_SSE_STREAM_ID] = SessionContext(sseSession, call) maybeSendPrimingEvent(STANDALONE_SSE_STREAM_ID, sseSession, call.request.header(MCP_PROTOCOL_VERSION_HEADER)) sseSession.coroutineContext.job.invokeOnCompletion { @@ -529,8 +529,8 @@ public class StreamableHttpServerTransport(private val configuration: Configurat } } - call.appendSseHeaders() - flushSse(session) // flush headers immediately + // SSE headers are already set by the framework's SSE handler. + flushSse(session) val streamId = store.replayEventsAfter(lastEventId) { eventId, message -> try { diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt index 9cf916a..8b4f6fa 100644 --- a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt @@ -2,6 +2,7 @@ package io.modelcontextprotocol.kotlin.sdk.server import io.kotest.matchers.collections.shouldContainAll import io.kotest.matchers.equals.shouldBeEqual +import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe import io.ktor.client.HttpClient import io.ktor.client.call.body @@ -10,7 +11,9 @@ import io.ktor.client.plugins.logging.Logging import io.ktor.client.request.HttpRequestBuilder import io.ktor.client.request.header import io.ktor.client.request.post +import io.ktor.client.request.prepareGet import io.ktor.client.request.setBody +import io.ktor.client.statement.bodyAsChannel import io.ktor.http.ContentType import io.ktor.http.HttpHeaders import io.ktor.http.HttpStatusCode @@ -21,6 +24,7 @@ import io.ktor.server.routing.post import io.ktor.server.routing.routing import io.ktor.server.testing.ApplicationTestBuilder import io.ktor.server.testing.testApplication +import io.ktor.utils.io.readUTF8Line import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities import io.modelcontextprotocol.kotlin.sdk.types.EmptyResult import io.modelcontextprotocol.kotlin.sdk.types.Implementation @@ -36,6 +40,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.ListToolsResult import io.modelcontextprotocol.kotlin.sdk.types.McpJson import io.modelcontextprotocol.kotlin.sdk.types.Method import io.modelcontextprotocol.kotlin.sdk.types.RequestId +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.types.Tool import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema import io.modelcontextprotocol.kotlin.sdk.types.toJSON @@ -433,6 +438,47 @@ class StreamableHttpServerTransportTest { } } + @Test + fun `GET SSE stream includes Mcp-Session-Id header and stays open`() = testApplication { + val mcpPath = "/mcp" + + application { + mcpStreamableHttp(mcpPath) { + Server( + Implementation("test-server", "1.0.0"), + ServerOptions(capabilities = ServerCapabilities()), + ) + } + } + + val client = createTestClient() + + // Step 1: Initialize session via POST + val initResponse = client.post(mcpPath) { + addStreamableHeaders() + setBody(buildInitializeRequestPayload()) + } + initResponse.status shouldBe HttpStatusCode.OK + val sessionId = assertNotNull(initResponse.headers[MCP_SESSION_ID_HEADER]) + + // Step 2: Open GET SSE stream with session ID + client.prepareGet(mcpPath) { + header(HttpHeaders.Accept, ContentType.Text.EventStream.toString()) + header(MCP_SESSION_ID_HEADER, sessionId) + header("mcp-protocol-version", LATEST_PROTOCOL_VERSION) + }.execute { response -> + // Verify Mcp-Session-Id is present on the SSE response + response.status shouldBe HttpStatusCode.OK + response.headers[MCP_SESSION_ID_HEADER] shouldBe sessionId + + // Verify the stream is alive by reading at least one line (flush event) + val channel = response.bodyAsChannel() + val firstLine = channel.readUTF8Line() + firstLine.shouldNotBeNull() + channel.isClosedForRead shouldBe false + } + } + private fun ApplicationTestBuilder.configureTransportEndpoint(transport: StreamableHttpServerTransport) { application { routing { From f02f5d3d10694c8fffd6a25b60f384a16b0441b9 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Fri, 10 Apr 2026 15:30:20 +0200 Subject: [PATCH 234/449] release: bump mcp kotlin-sdk version to 0.11.1 (#682) mcp kotlin-sdk 0.11.0 -> 0.11.1 ## Breaking Changes none ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] 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 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 09a658b..ca4a217 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,4 +8,4 @@ kotlin.daemon.jvmargs=-Xmx4G kotlin.native.ignoreDisabledTargets=true group=io.modelcontextprotocol -version=0.11.0 +version=0.11.1 From 36c0f4f8966911c59c43a009b01f58c15f885c02 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 12:24:54 +0200 Subject: [PATCH 235/449] chore(deps): bump @types/node from 25.5.2 to 25.6.0 in /integration-test/src/jvmTest/typescript in the all-dependencies group (#690) Bumps the all-dependencies group in /integration-test/src/jvmTest/typescript with 1 update: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node). Updates `@types/node` from 25.5.2 to 25.6.0
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@types/node&package-manager=npm_and_yarn&previous-version=25.5.2&new-version=25.6.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../src/jvmTest/typescript/package-lock.json | 16 ++++++++-------- .../src/jvmTest/typescript/package.json | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index dd0c9b4..a246efa 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -15,7 +15,7 @@ "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^25.5.2", + "@types/node": "^25.6.0", "cors": "^2.8.6", "tsx": "^4.21.0", "typescript": "^6.0.2" @@ -579,13 +579,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.5.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.2.tgz", - "integrity": "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==", + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.18.0" + "undici-types": "~7.19.0" } }, "node_modules/@types/qs": { @@ -1751,9 +1751,9 @@ } }, "node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", "dev": true, "license": "MIT" }, diff --git a/integration-test/src/jvmTest/typescript/package.json b/integration-test/src/jvmTest/typescript/package.json index 20249d0..e38cca6 100644 --- a/integration-test/src/jvmTest/typescript/package.json +++ b/integration-test/src/jvmTest/typescript/package.json @@ -16,7 +16,7 @@ "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^25.5.2", + "@types/node": "^25.6.0", "cors": "^2.8.6", "tsx": "^4.21.0", "typescript": "^6.0.2" From 1734a647c47c765a9962cf591dd9f6b3a281267d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 12:46:48 +0200 Subject: [PATCH 236/449] chore(deps): bump dependabot/fetch-metadata from 2 to 3 in the all-actions group across 1 directory (#689) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the all-actions group with 1 update in the / directory: [dependabot/fetch-metadata](https://github.com/dependabot/fetch-metadata). Updates `dependabot/fetch-metadata` from 2 to 3
Release notes

Sourced from dependabot/fetch-metadata's releases.

v3.0.0

The breaking change is requiring Node.js version v24 as the Actions runtime.

What's Changed

New Contributors

Full Changelog: https://github.com/dependabot/fetch-metadata/compare/v2...v3.0.0

v2.5.0

What's Changed

... (truncated)

Commits
  • ffa630c v3.0.0 (#686)
  • ec8fff2 Merge pull request #674 from dependabot/dependabot/npm_and_yarn/picomatch-2.3.2
  • caf48bd build(deps-dev): bump picomatch from 2.3.1 to 2.3.2
  • 13d8274 Upgrade @​actions/github to ^9.0.0 and @​octokit/request-error to ^7.1.0 (#678)
  • b603099 Upgrade @​actions/core from ^1.11.1 to ^3.0.0 (#677)
  • c5dc5b1 Enable noImplicitAny in tsconfig.json (#684)
  • a183f3c Add typecheck step to CI (#685)
  • 5e17564 Remove skipLibCheck from tsconfig.json (#683)
  • bb56eeb Switch tsconfig module resolution to bundler (#682)
  • 3632e3d Remove vestigial outDir from tsconfig.json (#681)
  • Additional commits viewable in compare view

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/dependabot-auto-merge.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index 6073684..e7e94c3 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -11,7 +11,7 @@ jobs: if: github.actor == 'dependabot[bot]' runs-on: ubuntu-latest steps: - - uses: dependabot/fetch-metadata@v2 + - uses: dependabot/fetch-metadata@v3 id: metadata - if: >- From c18a4887856f305cb34eb86ca23828a1fefb1d9c Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Mon, 13 Apr 2026 13:43:48 +0200 Subject: [PATCH 237/449] fix(ci): add node setup in release workflow (#684) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added `setup-node` with Node.js 22 to the release workflow — without it, TypeScript integration tests fail because the macOS runner has an incompatible pre-installed Node.js version --- .github/workflows/gradle-publish.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml index 6b60e87..6ed0756 100644 --- a/.github/workflows/gradle-publish.yml +++ b/.github/workflows/gradle-publish.yml @@ -24,11 +24,16 @@ jobs: java-version: '21' distribution: 'temurin' + - name: Set up Node.js + uses: actions/setup-node@v6 + with: + node-version: '22' # increase only after https://github.com/nodejs/node/issues/56645 will be fixed + - name: Setup Gradle uses: gradle/actions/setup-gradle@v6 - - name: Build - run: ./gradlew build --rerun-tasks --max-workers 2 --continue + - name: Check + run: ./gradlew check --no-configuration-cache - name: Publish to Maven Central Portal id: publish From 1d02ae8a7ff28ffa23e7b8b54c6dc235bef43d94 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Mon, 13 Apr 2026 14:15:00 +0200 Subject: [PATCH 238/449] chore(build): remove openapi generator (#683) Remove unused OpenAPI Generator plugin, its configuration, and the `.openapi-generator-ignore` file. ## Breaking Changes none ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] 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 --- gradle/libs.versions.toml | 2 - kotlin-sdk-core/.openapi-generator-ignore | 29 ------------ kotlin-sdk-core/build.gradle.kts | 56 ----------------------- 3 files changed, 87 deletions(-) delete mode 100644 kotlin-sdk-core/.openapi-generator-ignore diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f2836ef..0fb3135 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,7 +10,6 @@ ktlint = "14.2.0" knit = "0.5.1" mavenPublish = "0.36.0" netty = "4.2.12.Final" -openapi-generator = "7.21.0" # libraries version awaitility = "4.3.0" @@ -81,4 +80,3 @@ kotlinx-binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-co kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" } knit = { id = "org.jetbrains.kotlinx.knit", version.ref = "knit" } -openapi-generator = { id = "org.openapi.generator", version.ref = "openapi-generator" } diff --git a/kotlin-sdk-core/.openapi-generator-ignore b/kotlin-sdk-core/.openapi-generator-ignore deleted file mode 100644 index dbdf930..0000000 --- a/kotlin-sdk-core/.openapi-generator-ignore +++ /dev/null @@ -1,29 +0,0 @@ -# OpenAPI Generator Ignore -# Generated by openapi-generator https://github.com/openapitools/openapi-generator - -# Use this file to prevent files from being overwritten by the generator. -# The patterns follow closely to .gitignore or .dockerignore. - -# As an example, the C# client generator defines ApiClient.cs. -# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: -**/auth/** -**/infrastructure/** -!**/Base64ByteArray.kt -!**/Bytes.kt - - -## Exclude Models which are causing problems -**/JSONRPCResponse.kt -**/Result.kt -**/ListToolsResult.kt -**/InitializeRequest.kt -**/InitializeResult.kt -**/ServerCapabilities.kt -**/InitializeRequestParams.kt -**/CreateMessageRequest.kt -**/CreateMessageRequestParams.kt -**/ClientCapabilities.kt -**/Tool.kt -**/ToolInputSchema.kt -**/ToolOutputSchema.kt - diff --git a/kotlin-sdk-core/build.gradle.kts b/kotlin-sdk-core/build.gradle.kts index 0e702c4..f4a2314 100644 --- a/kotlin-sdk-core/build.gradle.kts +++ b/kotlin-sdk-core/build.gradle.kts @@ -7,62 +7,6 @@ plugins { id("mcp.publishing") id("mcp.dokka") alias(libs.plugins.kotlinx.binary.compatibility.validator) - alias(libs.plugins.openapi.generator) -} - -/* -tasks.withType>().configureEach { - dependsOn(tasks.openApiGenerate) -} - -tasks.named("runKtlintCheckOverCommonMainSourceSet") { - dependsOn(tasks.openApiGenerate) -} - -// Also ensure it runs before other relevant tasks -tasks.withType().configureEach { - dependsOn(tasks.openApiGenerate) -} - -tasks.withType().configureEach { - if (name.lowercase().contains("sourcesjar")) { - dependsOn(tasks.openApiGenerate) - } -} - */ - -openApiGenerate { - val schemaVersion = "2025-03-26" // or "2025-06-18" or "draft" - val schemaUrl = -// "https://raw.githubusercontent.com/modelcontextprotocol/modelcontextprotocol/refs/heads/main/schema/$schemaVersion/schema.json" - "https://raw.githubusercontent.com/modelcontextprotocol/modelcontextprotocol/refs/tags/$schemaVersion/schema/$schemaVersion/schema.json" - generatorName = "kotlin" - remoteInputSpec = schemaUrl - outputDir = layout.buildDirectory.dir("generated-sources/openapi").get().asFile.absolutePath - packageName = "io.modelcontextprotocol.kotlin.sdk.models" - modelPackage = "io.modelcontextprotocol.kotlin.sdk.models" - apiPackage = "io.modelcontextprotocol.kotlin.sdk.api" - generateModelTests = false - generateModelDocumentation = false - cleanupOutput = false - skipValidateSpec = true // do not validate spec - library.set("multiplatform") - ignoreFileOverride.set("${layout.projectDirectory}/.openapi-generator-ignore") - globalProperties.set( - mapOf( - "supportingFiles" to "", - "models" to "", // or generate all models - ), - ) - configOptions.set( - mapOf( - "omitGradleWrapper" to "true", - "enumPropertyNaming" to "UPPERCASE", - "dateLibrary" to "kotlinx-datetime", - "explicitApi" to "true", - "modelMutable" to "false", - ), - ) } // Generation library versions From 6ce534dba92a60f7ef65008eea0c948908ad4bd4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 14:46:26 +0200 Subject: [PATCH 239/449] chore(deps): bump com.anthropic:anthropic-java from 2.19.0 to 2.24.0 in /samples/kotlin-mcp-client in the other-dependencies group across 1 directory (#685) Bumps the other-dependencies group with 1 update in the /samples/kotlin-mcp-client directory: [com.anthropic:anthropic-java](https://github.com/anthropics/anthropic-sdk-java). Updates `com.anthropic:anthropic-java` from 2.19.0 to 2.24.0
Release notes

Sourced from com.anthropic:anthropic-java's releases.

v2.24.0

2.24.0 (2026-04-09)

Full Changelog: v2.23.0...v2.24.0

Features

v2.23.0

2.23.0 (2026-04-09)

Full Changelog: v2.22.0...v2.23.0

Features

  • api: Add beta advisor tool (17d360c)

Chores

  • client: add Models enum as option in Managed Agents (ebd7983)

v2.22.0

2.22.0 (2026-04-08)

Full Changelog: v2.21.0...v2.22.0

Features

  • api: add support for Claude Managed Agents (f410797)
  • bedrock: Create Bedrock Mantle client (#1066) (100dfb5)

Bug Fixes

  • client: fix examples for agents (7c11a93)
  • internal: run formatter (a2aa0c8)

v2.21.0

2.21.0 (2026-04-07)

Full Changelog: v2.20.0...v2.21.0

Features

  • api: Add support for claude-mythos-preview (b9ea13c)
  • vertex: add support for US multi-region endpoint (8e32b5f)

... (truncated)

Changelog

Sourced from com.anthropic:anthropic-java's changelog.

2.24.0 (2026-04-09)

Full Changelog: v2.23.0...v2.24.0

Features

2.23.0 (2026-04-09)

Full Changelog: v2.22.0...v2.23.0

Features

  • api: Add beta advisor tool (17d360c)

Chores

  • client: add Models enum as option in Managed Agents (ebd7983)

2.22.0 (2026-04-08)

Full Changelog: v2.21.0...v2.22.0

Features

  • api: add support for Claude Managed Agents (f410797)
  • bedrock: Create Bedrock Mantle client (#1066) (100dfb5)

Bug Fixes

  • client: fix examples for agents (7c11a93)
  • internal: run formatter (a2aa0c8)

2.21.0 (2026-04-07)

Full Changelog: v2.20.0...v2.21.0

Features

  • api: Add support for claude-mythos-preview (b9ea13c)
  • vertex: add support for US multi-region endpoint (8e32b5f)

2.20.0 (2026-04-01)

Full Changelog: v2.19.0...v2.20.0

... (truncated)

Commits

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-client/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index f56c437..9578551 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -anthropic = "2.19.0" +anthropic = "2.24.0" kotlin = "2.2.21" ktor = "3.3.3" mcp-kotlin = "0.11.0" From 081a635bd35a60a1f461999603fe1ad9a53582a4 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Tue, 14 Apr 2026 13:14:30 +0200 Subject: [PATCH 240/449] chore(ci): remove codecov (#691) Remove Codecov configuration and CI steps for uploading coverage/test reports. ## Motivation and Context - Codecov integration relies on third-party credentials - Codecov has not demonstrated clear value for the project: - Coverage analysis is already handled by kover, which runs locally and in CI - PR reviews still require manual inspection of tests regardless of codecov reports - Codecov flaky test detection is unreliable, it marks 3325 out of all tests as flaky with similar failure rates, making the signal useless (https://app.codecov.io/gh/modelcontextprotocol/kotlin-sdk/tests) --- .github/workflows/build.yml | 16 ------------ codecov.yml | 51 ------------------------------------- context7.json | 1 - 3 files changed, 68 deletions(-) delete mode 100644 codecov.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 08242f7..caaa3f0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -104,22 +104,6 @@ jobs: report_paths: '**/test-results/**/TEST-*.xml' truncate_stack_traces: false - - name: Upload coverage reports to Codecov - if: ${{ !cancelled() && matrix.os == 'ubuntu-latest' }} - uses: codecov/codecov-action@v6 - with: - token: ${{ secrets.CODECOV_TOKEN }} - report_type: coverage - directory: ./build/reports/kover - files: report.xml - - - name: Upload test results to Codecov - if: ${{ !cancelled() && matrix.os == 'ubuntu-latest' }} - uses: codecov/codecov-action@v6 - with: - token: ${{ secrets.CODECOV_TOKEN }} - report_type: test_results - - name: Archive Maven Local Repository if: ${{ success() && matrix.os == 'ubuntu-latest' }} diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index 9135aae..0000000 --- a/codecov.yml +++ /dev/null @@ -1,51 +0,0 @@ -# Codecov configuration for Mokksy and AI-Mocks -# https://docs.codecov.com/docs/codecov-yaml - -codecov: - require_ci_to_pass: yes - -coverage: - precision: 2 - round: down - status: - project: - default: - target: auto - threshold: 1% - patch: - default: - target: auto - threshold: 1% - -comment: - layout: "header, diff, flags, components" - behavior: default - require_changes: true - -component_management: - default_rules: - statuses: - - type: project - target: auto - individual_components: - - - component_id: kotlin-sdk-core - name: SDK Core - paths: - - kotlin-sdk-core/** - - - component_id: kotlin-sdk-client - name: SDK Client - paths: - - kotlin-sdk-client/** - - - component_id: kotlin-sdk-server - name: SDK Server - paths: - - kotlin-sdk-server/** - - - component_id: kotlin-sdk-testing - name: SDK Testing - paths: - - kotlin-sdk-testing/** - diff --git a/context7.json b/context7.json index a28c0fc..5fde250 100644 --- a/context7.json +++ b/context7.json @@ -12,7 +12,6 @@ "CONTRIBUTING.md", "LICENSE", "SECURITY.md", - "codecov.yml", "gradlew", "gradlew.bat", ".editorconfig", From 61fab2ffe081b6074697610b4c7bcf30ebeba285 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Tue, 14 Apr 2026 13:44:19 +0200 Subject: [PATCH 241/449] feat: add $schema field to ToolSchema per SEP-1613 (#696) Add optional `$schema` field to `ToolSchema`, establishing JSON Schema 2020-12 as the default dialect per SEP-1613. closes #422 ## How Has This Been Tested? unit tests ## 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- kotlin-sdk-core/api/kotlin-sdk-core.api | 16 +++--- .../kotlin/sdk/types/tools.kt | 8 +++ .../kotlin/sdk/types/ToolsTest.kt | 55 +++++++++++++++++++ 3 files changed, 72 insertions(+), 7 deletions(-) diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index d4f88ba..679b68d 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -5135,17 +5135,19 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ToolListChangedNotif public final class io/modelcontextprotocol/kotlin/sdk/types/ToolSchema { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema$Companion; public fun ()V - public fun (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lkotlinx/serialization/json/JsonObject; - public final fun component2 ()Ljava/util/List; - public final fun component3 ()Lkotlinx/serialization/json/JsonObject; - public final fun copy (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema; + public fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;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 component3 ()Ljava/util/List; + public final fun component4 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema; public fun equals (Ljava/lang/Object;)Z public final fun getDefs ()Lkotlinx/serialization/json/JsonObject; public final fun getProperties ()Lkotlinx/serialization/json/JsonObject; public final fun getRequired ()Ljava/util/List; + public final fun getSchema ()Ljava/lang/String; public final fun getType ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt index 4f642f4..a03e8ad 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt @@ -39,12 +39,14 @@ public fun CallToolResult.Companion.error(content: String, meta: JsonObject? = n * if both [title] and [annotations].[title] are not provided. * @property inputSchema A JSON Schema object defining the expected parameters for the tool. * Must be an object type schema. Defines what arguments the tool accepts. + * Defaults to the JSON Schema 2020-12 dialect when no explicit `$schema` is provided. * @property description A human-readable description of the tool and when to use it. * Clients can use this to improve the LLM's understanding of available tools. * It can be thought of like a "hint" to the model. * @property outputSchema An optional JSON Schema object defining the structure of the tool's output * returned in the [structuredContent][CallToolResult.structuredContent] field of a [CallToolResult]. * Must be an object type schema if provided. + * Defaults to the JSON Schema 2020-12 dialect when no explicit `$schema` is provided. * @property title Optional human-readable display name for this tool. * Intended for UI and end-user contexts, optimized to be easily understood * even by those unfamiliar with domain-specific terminology. @@ -77,7 +79,11 @@ public data class Tool( * A JSON Schema for tool input or output parameters. * * This is a simplified schema structure that must be of type "object". + * Defaults to the JSON Schema 2020-12 dialect when no explicit [schema] (`$schema`) is provided. * + * @property schema Optional URI identifying the JSON Schema dialect (e.g., + * `https://json-schema.org/draft/2020-12/schema`). Serialized as `$schema`. + * When absent, JSON Schema 2020-12 is assumed by default. * @property type Always "object" for tool schemas. * @property properties Optional map of property names to their schema definitions. * @property required Optional list of property names that are required. @@ -85,6 +91,8 @@ public data class Tool( */ @Serializable public data class ToolSchema( + @SerialName("\$schema") + val schema: String? = null, val properties: JsonObject? = null, val required: List? = null, @SerialName("\$defs") diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ToolsTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ToolsTest.kt index 543e12d..87f1644 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ToolsTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ToolsTest.kt @@ -158,6 +158,61 @@ class ToolsTest { assertEquals("language", tool.meta?.get("category")?.jsonPrimitive?.content) } + @Test + fun `should serialize ToolSchema with schema field`() { + val tool = Tool( + name = "typed-tool", + inputSchema = ToolSchema( + schema = "https://json-schema.org/draft/2020-12/schema", + properties = buildJsonObject { + put("name", buildJsonObject { put("type", "string") }) + }, + ), + ) + + verifySerialization( + tool, + McpJson, + $$""" + { + "name": "typed-tool", + "inputSchema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + } + """.trimIndent(), + ) + } + + @Test + fun `should deserialize ToolSchema with schema field`() { + val json = $$""" + { + "name": "typed-tool", + "inputSchema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + } + """.trimIndent() + + val tool = verifyDeserialization(McpJson, json) + + assertEquals("https://json-schema.org/draft/2020-12/schema", tool.inputSchema.schema) + assertNotNull(tool.inputSchema.properties) + } + @Test fun `should serialize ToolSchema with defs`() { val tool = toolWithDefs() From e74ef2db6fbfeb56888aa5ed495361960f5885e5 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Tue, 14 Apr 2026 15:27:32 +0200 Subject: [PATCH 242/449] feat(server): add tool name validation per SEP-986 (#695) Add warn-only validation for tool names at registration time, following the MCP tool naming standard (SEP-986) closes #417 ## How Has This Been Tested? unit tests ## 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- .../kotlin/sdk/server/ServerToolsTest.kt | 10 ++ .../kotlin/sdk/server/Server.kt | 4 +- .../sdk/server/utils/ToolNameValidation.kt | 60 +++++++++ .../server/utils/ToolNameValidationTest.kt | 123 ++++++++++++++++++ 4 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/utils/ToolNameValidation.kt create mode 100644 kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/utils/ToolNameValidationTest.kt diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsTest.kt index c2e8201..54c998e 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsTest.kt @@ -98,6 +98,16 @@ class ServerToolsTest : AbstractServerFeaturesTest() { assertFalse(toolListChangedNotificationReceived, "No notification should be sent when tool doesn't exist") } + @Test + fun `addTool should succeed with non-conforming tool name`() = runTest { + server.addTool("my invalid tool!", "Tool with non-conforming name", ToolSchema()) { + CallToolResult(listOf(TextContent("It works"))) + } + + val result = server.removeTool("my invalid tool!") + assertTrue(result, "Tool with non-conforming name should have been registered and removable") + } + @Test fun `removeTool should throw when tools capability is not supported`() = runTest { var toolListChangedNotificationReceived = false diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt index de219d4..d9b9b9a 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt @@ -1,6 +1,7 @@ package io.modelcontextprotocol.kotlin.sdk.server import io.github.oshai.kotlinlogging.KotlinLogging +import io.modelcontextprotocol.kotlin.sdk.server.utils.warnIfInvalidToolName import io.modelcontextprotocol.kotlin.sdk.shared.ProtocolOptions import io.modelcontextprotocol.kotlin.sdk.shared.RequestOptions import io.modelcontextprotocol.kotlin.sdk.shared.Transport @@ -303,7 +304,7 @@ public open class Server( logger.error { "Failed to add tool '${tool.name}': Server does not support tools capability" } "Server does not support tools capability. Enable it in ServerOptions." } - + warnIfInvalidToolName(tool.name) toolRegistry.add(RegisteredTool(tool, handler)) } @@ -357,6 +358,7 @@ public open class Server( logger.error { "Failed to add tools: Server does not support tools capability" } "Server does not support tools capability." } + toolsToAdd.forEach { warnIfInvalidToolName(it.tool.name) } toolRegistry.addAll(toolsToAdd) } diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/utils/ToolNameValidation.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/utils/ToolNameValidation.kt new file mode 100644 index 0000000..a63c2e4 --- /dev/null +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/utils/ToolNameValidation.kt @@ -0,0 +1,60 @@ +package io.modelcontextprotocol.kotlin.sdk.server.utils + +import io.github.oshai.kotlinlogging.KotlinLogging + +private val logger = KotlinLogging.logger("ToolNameValidation") + +private const val MAX_TOOL_NAME_LENGTH = 128 + +private const val SEP_986_URL = "https://github.com/modelcontextprotocol/modelcontextprotocol/issues/986" + +/** + * Validates a tool name against the MCP tool naming standard (SEP-986). + * Returns a list of warnings. An empty list means the name is valid. + */ +internal fun validateToolName(name: String): List { + if (name.isEmpty()) return listOf("Tool name cannot be empty") + + val invalidChars = name.toSet().filterNot { it.isAsciiAllowed() } + val remainingInvalidChars = invalidChars - setOf(' ', ',') + + return listOfNotNull( + "Tool name exceeds maximum length of $MAX_TOOL_NAME_LENGTH characters (current: ${name.length})" + .takeIf { name.length > MAX_TOOL_NAME_LENGTH }, + "Tool name contains spaces, which may cause parsing issues" + .takeIf { ' ' in invalidChars }, + "Tool name contains commas, which may cause parsing issues" + .takeIf { ',' in invalidChars }, + "Tool name contains invalid characters: ${remainingInvalidChars.joinToString(", ") { "\"$it\"" }}" + .takeIf { remainingInvalidChars.isNotEmpty() }, + "Allowed characters are: A-Z, a-z, 0-9, underscore (_), dash (-), and dot (.)" + .takeIf { invalidChars.isNotEmpty() }, + "Tool name starts or ends with a dash, which may cause parsing issues in some contexts" + .takeIf { name.startsWith('-') || name.endsWith('-') }, + "Tool name starts or ends with a dot, which may cause parsing issues in some contexts" + .takeIf { name.startsWith('.') || name.endsWith('.') }, + ) +} + +/** + * Validates the tool [name] and logs a warning if it does not conform to SEP-986. + * Does not block registration — tools with non-conforming names are still accepted. + */ +internal fun warnIfInvalidToolName(name: String) { + val warnings = validateToolName(name) + if (warnings.isEmpty()) return + + val warningLines = warnings.joinToString("\n") { " - $it" } + logger.warn { + """ + |Tool name validation warning for "$name": + |$warningLines + |Tool registration will proceed, but this may cause compatibility issues. + |Consider updating the tool name to conform to the MCP tool naming standard. + |See SEP-986: $SEP_986_URL + """.trimMargin() + } +} + +private fun Char.isAsciiAllowed(): Boolean = + this in 'a'..'z' || this in 'A'..'Z' || this in '0'..'9' || this == '_' || this == '-' || this == '.' diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/utils/ToolNameValidationTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/utils/ToolNameValidationTest.kt new file mode 100644 index 0000000..f6760ee --- /dev/null +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/utils/ToolNameValidationTest.kt @@ -0,0 +1,123 @@ +package io.modelcontextprotocol.kotlin.sdk.server.utils + +import io.kotest.matchers.booleans.shouldBeTrue +import io.kotest.matchers.collections.shouldBeEmpty +import io.kotest.matchers.collections.shouldContain +import io.kotest.matchers.collections.shouldHaveSize +import io.kotest.matchers.collections.shouldNotBeEmpty +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource + +class ToolNameValidationTest { + + @ParameterizedTest + @ValueSource( + strings = [ + "getUser", + "get_user_profile", + "user-profile-update", + "admin.tools.list", + "DATA_EXPORT_v2.1", + "a", + "Z", + "0", + "a-b.c_d", + ], + ) + fun `should return no warnings for valid tool names`(name: String) { + validateToolName(name).shouldBeEmpty() + } + + @Test + fun `should return no warnings for max length name`() { + validateToolName("a".repeat(128)).shouldBeEmpty() + } + + @Test + fun `should warn for empty name`() { + val warnings = validateToolName("") + warnings shouldHaveSize 1 + warnings shouldContain "Tool name cannot be empty" + } + + @Test + fun `should warn for name exceeding max length`() { + val warnings = validateToolName("a".repeat(129)) + warnings.shouldNotBeEmpty() + warnings.any { "exceeds maximum length" in it }.shouldBeTrue() + } + + @ParameterizedTest + @ValueSource(strings = ["get user profile", "my tool"]) + fun `should warn for names with spaces`(name: String) { + val warnings = validateToolName(name) + warnings shouldContain "Tool name contains spaces, which may cause parsing issues" + } + + @ParameterizedTest + @ValueSource(strings = ["get,user,profile", "a,b"]) + fun `should warn for names with commas`(name: String) { + val warnings = validateToolName(name) + warnings shouldContain "Tool name contains commas, which may cause parsing issues" + } + + @ParameterizedTest + @ValueSource(strings = ["user/profile/update", "a/b"]) + fun `should warn for names with forward slashes`(name: String) { + val warnings = validateToolName(name) + warnings.any { "invalid characters" in it }.shouldBeTrue() + } + + @ParameterizedTest + @ValueSource(strings = ["user@domain.com", "tool#1", "tool\$name"]) + fun `should warn for names with special characters`(name: String) { + val warnings = validateToolName(name) + warnings.any { "invalid characters" in it }.shouldBeTrue() + warnings shouldContain "Allowed characters are: A-Z, a-z, 0-9, underscore (_), dash (-), and dot (.)" + } + + @Test + fun `should warn for unicode characters`() { + val warnings = validateToolName("user-\u00f1ame") + warnings.any { "invalid characters" in it }.shouldBeTrue() + } + + @Test + fun `should warn for name starting with dash`() { + val warnings = validateToolName("-my-tool") + warnings shouldContain "Tool name starts or ends with a dash, which may cause parsing issues in some contexts" + } + + @Test + fun `should warn for name ending with dash`() { + val warnings = validateToolName("my-tool-") + warnings shouldContain "Tool name starts or ends with a dash, which may cause parsing issues in some contexts" + } + + @Test + fun `should warn for name starting with dot`() { + val warnings = validateToolName(".hidden") + warnings shouldContain "Tool name starts or ends with a dot, which may cause parsing issues in some contexts" + } + + @Test + fun `should warn for name ending with dot`() { + val warnings = validateToolName("config.") + warnings shouldContain "Tool name starts or ends with a dot, which may cause parsing issues in some contexts" + } + + @Test + fun `should produce multiple warnings for multiple issues`() { + val warnings = validateToolName("my tool,name") + warnings.any { "spaces" in it }.shouldBeTrue() + warnings.any { "commas" in it }.shouldBeTrue() + } + + @Test + fun `should not duplicate space or comma in generic invalid characters message`() { + val warnings = validateToolName("my tool") + warnings shouldContain "Tool name contains spaces, which may cause parsing issues" + warnings.none { it.startsWith("Tool name contains invalid characters") }.shouldBeTrue() + } +} From 2fb3393d54092ece319431f79e81877c3d0fbdf9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 22:26:04 +0200 Subject: [PATCH 243/449] chore(deps): bump com.anthropic:anthropic-java from 2.24.0 to 2.25.0 in /samples/kotlin-mcp-client in the other-dependencies group across 1 directory (#698) Bumps the other-dependencies group with 1 update in the /samples/kotlin-mcp-client directory: [com.anthropic:anthropic-java](https://github.com/anthropics/anthropic-sdk-java). Updates `com.anthropic:anthropic-java` from 2.24.0 to 2.25.0
Release notes

Sourced from com.anthropic:anthropic-java's releases.

v2.25.0

2.25.0 (2026-04-14)

Full Changelog: v2.24.0...v2.25.0

Features

  • api: mark Sonnet and Opus 4 as deprecated (81c750e)

Bug Fixes

  • streaming: add missing events (4ac1b6a)

Chores

  • client: reset .stats.yml (1d409ea)

Documentation

Changelog

Sourced from com.anthropic:anthropic-java's changelog.

2.25.0 (2026-04-14)

Full Changelog: v2.24.0...v2.25.0

Features

  • api: mark Sonnet and Opus 4 as deprecated (81c750e)

Bug Fixes

  • streaming: add missing events (4ac1b6a)

Chores

  • client: reset .stats.yml (1d409ea)

Documentation

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=com.anthropic:anthropic-java&package-manager=gradle&previous-version=2.24.0&new-version=2.25.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-client/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index 9578551..0db8b9f 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -anthropic = "2.24.0" +anthropic = "2.25.0" kotlin = "2.2.21" ktor = "3.3.3" mcp-kotlin = "0.11.0" From 9981a7b67aec290cf4382f4268f4b5d7798cb208 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 22:49:49 +0200 Subject: [PATCH 244/449] chore(deps): bump io.modelcontextprotocol:kotlin-sdk-client from 0.11.0 to 0.11.1 in /samples/kotlin-mcp-client (#699) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [io.modelcontextprotocol:kotlin-sdk-client](https://github.com/modelcontextprotocol/kotlin-sdk) from 0.11.0 to 0.11.1.
Release notes

Sourced from io.modelcontextprotocol:kotlin-sdk-client's releases.

0.11.1

Description

Fixes an SSE stream crash on Netty when clients connect to the Streamable HTTP GET endpoint.

Fixed

  • Fixed GET SSE stream crash on Netty in Streamable HTTP where appendSseHeaders() was called after response headers were already committed, causing UnsupportedOperationException and client retry loops by @​devcrocod in #681

Dependencies

  • mcp-kotlin to v0.11.0 in #672, #673, #675, #676
  • Ktor BOM to v3.3.3 in #650, #653, #656
  • Kotest assertions to v6.1.11 in #674
  • anthropic-java to v2.19.0 in #671
  • @​modelcontextprotocol/sdk to v1.29.0, @​types/node update in #677
  • hono to v4.12.12 in #680
  • @​hono/node-server to v1.19.13 in #679

Full Changelog: https://github.com/modelcontextprotocol/kotlin-sdk/compare/0.11.0...0.11.1

Commits
  • f02f5d3 release: bump mcp kotlin-sdk version to 0.11.1 (#682)
  • 8a7f9f8 fix(server): GET SSE stream crash on Netty in Streamable HTTP (#681)
  • cceca4d chore(deps): bump io.ktor:ktor-bom from 3.2.3 to 3.3.3 in /samples/kotlin-mcp...
  • 12171dc chore(deps): bump hono from 4.12.9 to 4.12.12 in /integration-test/src/jvmTes...
  • 66d1795 chore(deps): bump @​hono/node-server from 1.19.11 to 1.19.13 in /integration-t...
  • 8442ab8 chore(deps): bump com.anthropic:anthropic-java from 2.18.0 to 2.19.0 in /samp...
  • 20ac69f chore(deps): bump mcp-kotlin from 0.10.0 to 0.11.0 in /samples/simple-streama...
  • 4144db6 chore(deps): bump io.modelcontextprotocol:kotlin-sdk-client from 0.10.0 to 0....
  • 3f7f499 chore(deps): bump mcp-kotlin from 0.10.0 to 0.11.0 in /samples/kotlin-mcp-ser...
  • 7c87ed1 chore(deps): bump the kotest group with 3 updates (#674)
  • Additional commits viewable in compare view

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-client/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index 0db8b9f..695b94d 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -2,7 +2,7 @@ anthropic = "2.25.0" kotlin = "2.2.21" ktor = "3.3.3" -mcp-kotlin = "0.11.0" +mcp-kotlin = "0.11.1" shadow = "9.4.1" slf4j = "2.0.17" From ba265a52ad1e59eae33abf940fbd944eb18d6333 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 21:28:47 +0000 Subject: [PATCH 245/449] chore(deps): bump mcp-kotlin from 0.11.0 to 0.11.1 in /samples/kotlin-mcp-server (#700) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `mcp-kotlin` from 0.11.0 to 0.11.1. Updates `io.modelcontextprotocol:kotlin-sdk-client` from 0.11.0 to 0.11.1
Release notes

Sourced from io.modelcontextprotocol:kotlin-sdk-client's releases.

0.11.1

Description

Fixes an SSE stream crash on Netty when clients connect to the Streamable HTTP GET endpoint.

Fixed

  • Fixed GET SSE stream crash on Netty in Streamable HTTP where appendSseHeaders() was called after response headers were already committed, causing UnsupportedOperationException and client retry loops by @​devcrocod in #681

Dependencies

  • mcp-kotlin to v0.11.0 in #672, #673, #675, #676
  • Ktor BOM to v3.3.3 in #650, #653, #656
  • Kotest assertions to v6.1.11 in #674
  • anthropic-java to v2.19.0 in #671
  • @​modelcontextprotocol/sdk to v1.29.0, @​types/node update in #677
  • hono to v4.12.12 in #680
  • @​hono/node-server to v1.19.13 in #679

Full Changelog: https://github.com/modelcontextprotocol/kotlin-sdk/compare/0.11.0...0.11.1

Commits
  • f02f5d3 release: bump mcp kotlin-sdk version to 0.11.1 (#682)
  • 8a7f9f8 fix(server): GET SSE stream crash on Netty in Streamable HTTP (#681)
  • cceca4d chore(deps): bump io.ktor:ktor-bom from 3.2.3 to 3.3.3 in /samples/kotlin-mcp...
  • 12171dc chore(deps): bump hono from 4.12.9 to 4.12.12 in /integration-test/src/jvmTes...
  • 66d1795 chore(deps): bump @​hono/node-server from 1.19.11 to 1.19.13 in /integration-t...
  • 8442ab8 chore(deps): bump com.anthropic:anthropic-java from 2.18.0 to 2.19.0 in /samp...
  • 20ac69f chore(deps): bump mcp-kotlin from 0.10.0 to 0.11.0 in /samples/simple-streama...
  • 4144db6 chore(deps): bump io.modelcontextprotocol:kotlin-sdk-client from 0.10.0 to 0....
  • 3f7f499 chore(deps): bump mcp-kotlin from 0.10.0 to 0.11.0 in /samples/kotlin-mcp-ser...
  • 7c87ed1 chore(deps): bump the kotest group with 3 updates (#674)
  • Additional commits viewable in compare view

Updates `io.modelcontextprotocol:kotlin-sdk-server` from 0.11.0 to 0.11.1
Release notes

Sourced from io.modelcontextprotocol:kotlin-sdk-server's releases.

0.11.1

Description

Fixes an SSE stream crash on Netty when clients connect to the Streamable HTTP GET endpoint.

Fixed

  • Fixed GET SSE stream crash on Netty in Streamable HTTP where appendSseHeaders() was called after response headers were already committed, causing UnsupportedOperationException and client retry loops by @​devcrocod in #681

Dependencies

  • mcp-kotlin to v0.11.0 in #672, #673, #675, #676
  • Ktor BOM to v3.3.3 in #650, #653, #656
  • Kotest assertions to v6.1.11 in #674
  • anthropic-java to v2.19.0 in #671
  • @​modelcontextprotocol/sdk to v1.29.0, @​types/node update in #677
  • hono to v4.12.12 in #680
  • @​hono/node-server to v1.19.13 in #679

Full Changelog: https://github.com/modelcontextprotocol/kotlin-sdk/compare/0.11.0...0.11.1

Commits
  • f02f5d3 release: bump mcp kotlin-sdk version to 0.11.1 (#682)
  • 8a7f9f8 fix(server): GET SSE stream crash on Netty in Streamable HTTP (#681)
  • cceca4d chore(deps): bump io.ktor:ktor-bom from 3.2.3 to 3.3.3 in /samples/kotlin-mcp...
  • 12171dc chore(deps): bump hono from 4.12.9 to 4.12.12 in /integration-test/src/jvmTes...
  • 66d1795 chore(deps): bump @​hono/node-server from 1.19.11 to 1.19.13 in /integration-t...
  • 8442ab8 chore(deps): bump com.anthropic:anthropic-java from 2.18.0 to 2.19.0 in /samp...
  • 20ac69f chore(deps): bump mcp-kotlin from 0.10.0 to 0.11.0 in /samples/simple-streama...
  • 4144db6 chore(deps): bump io.modelcontextprotocol:kotlin-sdk-client from 0.10.0 to 0....
  • 3f7f499 chore(deps): bump mcp-kotlin from 0.10.0 to 0.11.0 in /samples/kotlin-mcp-ser...
  • 7c87ed1 chore(deps): bump the kotest group with 3 updates (#674)
  • Additional commits viewable in compare view

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-server/gradle/libs.versions.toml b/samples/kotlin-mcp-server/gradle/libs.versions.toml index 05007d0..3a03aed 100644 --- a/samples/kotlin-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-server/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] kotlin = "2.2.21" ktor = "3.3.3" -mcp-kotlin = "0.11.0" +mcp-kotlin = "0.11.1" slf4j = "2.0.17" shadow = "9.4.1" From 2d23827963c04dec67806e0011fcacdba5a90a07 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 00:17:16 +0200 Subject: [PATCH 246/449] chore(deps): bump mcp-kotlin from 0.11.0 to 0.11.1 in /samples/simple-streamable-server (#701) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `mcp-kotlin` from 0.11.0 to 0.11.1. Updates `io.modelcontextprotocol:kotlin-sdk-client` from 0.11.0 to 0.11.1
Release notes

Sourced from io.modelcontextprotocol:kotlin-sdk-client's releases.

0.11.1

Description

Fixes an SSE stream crash on Netty when clients connect to the Streamable HTTP GET endpoint.

Fixed

  • Fixed GET SSE stream crash on Netty in Streamable HTTP where appendSseHeaders() was called after response headers were already committed, causing UnsupportedOperationException and client retry loops by @​devcrocod in #681

Dependencies

  • mcp-kotlin to v0.11.0 in #672, #673, #675, #676
  • Ktor BOM to v3.3.3 in #650, #653, #656
  • Kotest assertions to v6.1.11 in #674
  • anthropic-java to v2.19.0 in #671
  • @​modelcontextprotocol/sdk to v1.29.0, @​types/node update in #677
  • hono to v4.12.12 in #680
  • @​hono/node-server to v1.19.13 in #679

Full Changelog: https://github.com/modelcontextprotocol/kotlin-sdk/compare/0.11.0...0.11.1

Commits
  • f02f5d3 release: bump mcp kotlin-sdk version to 0.11.1 (#682)
  • 8a7f9f8 fix(server): GET SSE stream crash on Netty in Streamable HTTP (#681)
  • cceca4d chore(deps): bump io.ktor:ktor-bom from 3.2.3 to 3.3.3 in /samples/kotlin-mcp...
  • 12171dc chore(deps): bump hono from 4.12.9 to 4.12.12 in /integration-test/src/jvmTes...
  • 66d1795 chore(deps): bump @​hono/node-server from 1.19.11 to 1.19.13 in /integration-t...
  • 8442ab8 chore(deps): bump com.anthropic:anthropic-java from 2.18.0 to 2.19.0 in /samp...
  • 20ac69f chore(deps): bump mcp-kotlin from 0.10.0 to 0.11.0 in /samples/simple-streama...
  • 4144db6 chore(deps): bump io.modelcontextprotocol:kotlin-sdk-client from 0.10.0 to 0....
  • 3f7f499 chore(deps): bump mcp-kotlin from 0.10.0 to 0.11.0 in /samples/kotlin-mcp-ser...
  • 7c87ed1 chore(deps): bump the kotest group with 3 updates (#674)
  • Additional commits viewable in compare view

Updates `io.modelcontextprotocol:kotlin-sdk-server` from 0.11.0 to 0.11.1
Release notes

Sourced from io.modelcontextprotocol:kotlin-sdk-server's releases.

0.11.1

Description

Fixes an SSE stream crash on Netty when clients connect to the Streamable HTTP GET endpoint.

Fixed

  • Fixed GET SSE stream crash on Netty in Streamable HTTP where appendSseHeaders() was called after response headers were already committed, causing UnsupportedOperationException and client retry loops by @​devcrocod in #681

Dependencies

  • mcp-kotlin to v0.11.0 in #672, #673, #675, #676
  • Ktor BOM to v3.3.3 in #650, #653, #656
  • Kotest assertions to v6.1.11 in #674
  • anthropic-java to v2.19.0 in #671
  • @​modelcontextprotocol/sdk to v1.29.0, @​types/node update in #677
  • hono to v4.12.12 in #680
  • @​hono/node-server to v1.19.13 in #679

Full Changelog: https://github.com/modelcontextprotocol/kotlin-sdk/compare/0.11.0...0.11.1

Commits
  • f02f5d3 release: bump mcp kotlin-sdk version to 0.11.1 (#682)
  • 8a7f9f8 fix(server): GET SSE stream crash on Netty in Streamable HTTP (#681)
  • cceca4d chore(deps): bump io.ktor:ktor-bom from 3.2.3 to 3.3.3 in /samples/kotlin-mcp...
  • 12171dc chore(deps): bump hono from 4.12.9 to 4.12.12 in /integration-test/src/jvmTes...
  • 66d1795 chore(deps): bump @​hono/node-server from 1.19.11 to 1.19.13 in /integration-t...
  • 8442ab8 chore(deps): bump com.anthropic:anthropic-java from 2.18.0 to 2.19.0 in /samp...
  • 20ac69f chore(deps): bump mcp-kotlin from 0.10.0 to 0.11.0 in /samples/simple-streama...
  • 4144db6 chore(deps): bump io.modelcontextprotocol:kotlin-sdk-client from 0.10.0 to 0....
  • 3f7f499 chore(deps): bump mcp-kotlin from 0.10.0 to 0.11.0 in /samples/kotlin-mcp-ser...
  • 7c87ed1 chore(deps): bump the kotest group with 3 updates (#674)
  • Additional commits viewable in compare view

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/simple-streamable-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/simple-streamable-server/gradle/libs.versions.toml b/samples/simple-streamable-server/gradle/libs.versions.toml index e74e9df..0cca026 100644 --- a/samples/simple-streamable-server/gradle/libs.versions.toml +++ b/samples/simple-streamable-server/gradle/libs.versions.toml @@ -2,7 +2,7 @@ kotlin = "2.2.21" ktor = "3.3.3" junit = "6.0.3" -mcp-kotlin = "0.11.0" +mcp-kotlin = "0.11.1" slf4j = "2.0.17" shadow = "9.4.1" From eb248035479d5c9ff9b44d1982d7b508fb185371 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 23:10:52 +0000 Subject: [PATCH 247/449] chore(deps): bump mcp-kotlin from 0.11.0 to 0.11.1 in /samples/weather-stdio-server (#702) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `mcp-kotlin` from 0.11.0 to 0.11.1. Updates `io.modelcontextprotocol:kotlin-sdk-client` from 0.11.0 to 0.11.1
Release notes

Sourced from io.modelcontextprotocol:kotlin-sdk-client's releases.

0.11.1

Description

Fixes an SSE stream crash on Netty when clients connect to the Streamable HTTP GET endpoint.

Fixed

  • Fixed GET SSE stream crash on Netty in Streamable HTTP where appendSseHeaders() was called after response headers were already committed, causing UnsupportedOperationException and client retry loops by @​devcrocod in #681

Dependencies

  • mcp-kotlin to v0.11.0 in #672, #673, #675, #676
  • Ktor BOM to v3.3.3 in #650, #653, #656
  • Kotest assertions to v6.1.11 in #674
  • anthropic-java to v2.19.0 in #671
  • @​modelcontextprotocol/sdk to v1.29.0, @​types/node update in #677
  • hono to v4.12.12 in #680
  • @​hono/node-server to v1.19.13 in #679

Full Changelog: https://github.com/modelcontextprotocol/kotlin-sdk/compare/0.11.0...0.11.1

Commits
  • f02f5d3 release: bump mcp kotlin-sdk version to 0.11.1 (#682)
  • 8a7f9f8 fix(server): GET SSE stream crash on Netty in Streamable HTTP (#681)
  • cceca4d chore(deps): bump io.ktor:ktor-bom from 3.2.3 to 3.3.3 in /samples/kotlin-mcp...
  • 12171dc chore(deps): bump hono from 4.12.9 to 4.12.12 in /integration-test/src/jvmTes...
  • 66d1795 chore(deps): bump @​hono/node-server from 1.19.11 to 1.19.13 in /integration-t...
  • 8442ab8 chore(deps): bump com.anthropic:anthropic-java from 2.18.0 to 2.19.0 in /samp...
  • 20ac69f chore(deps): bump mcp-kotlin from 0.10.0 to 0.11.0 in /samples/simple-streama...
  • 4144db6 chore(deps): bump io.modelcontextprotocol:kotlin-sdk-client from 0.10.0 to 0....
  • 3f7f499 chore(deps): bump mcp-kotlin from 0.10.0 to 0.11.0 in /samples/kotlin-mcp-ser...
  • 7c87ed1 chore(deps): bump the kotest group with 3 updates (#674)
  • Additional commits viewable in compare view

Updates `io.modelcontextprotocol:kotlin-sdk-server` from 0.11.0 to 0.11.1
Release notes

Sourced from io.modelcontextprotocol:kotlin-sdk-server's releases.

0.11.1

Description

Fixes an SSE stream crash on Netty when clients connect to the Streamable HTTP GET endpoint.

Fixed

  • Fixed GET SSE stream crash on Netty in Streamable HTTP where appendSseHeaders() was called after response headers were already committed, causing UnsupportedOperationException and client retry loops by @​devcrocod in #681

Dependencies

  • mcp-kotlin to v0.11.0 in #672, #673, #675, #676
  • Ktor BOM to v3.3.3 in #650, #653, #656
  • Kotest assertions to v6.1.11 in #674
  • anthropic-java to v2.19.0 in #671
  • @​modelcontextprotocol/sdk to v1.29.0, @​types/node update in #677
  • hono to v4.12.12 in #680
  • @​hono/node-server to v1.19.13 in #679

Full Changelog: https://github.com/modelcontextprotocol/kotlin-sdk/compare/0.11.0...0.11.1

Commits
  • f02f5d3 release: bump mcp kotlin-sdk version to 0.11.1 (#682)
  • 8a7f9f8 fix(server): GET SSE stream crash on Netty in Streamable HTTP (#681)
  • cceca4d chore(deps): bump io.ktor:ktor-bom from 3.2.3 to 3.3.3 in /samples/kotlin-mcp...
  • 12171dc chore(deps): bump hono from 4.12.9 to 4.12.12 in /integration-test/src/jvmTes...
  • 66d1795 chore(deps): bump @​hono/node-server from 1.19.11 to 1.19.13 in /integration-t...
  • 8442ab8 chore(deps): bump com.anthropic:anthropic-java from 2.18.0 to 2.19.0 in /samp...
  • 20ac69f chore(deps): bump mcp-kotlin from 0.10.0 to 0.11.0 in /samples/simple-streama...
  • 4144db6 chore(deps): bump io.modelcontextprotocol:kotlin-sdk-client from 0.10.0 to 0....
  • 3f7f499 chore(deps): bump mcp-kotlin from 0.10.0 to 0.11.0 in /samples/kotlin-mcp-ser...
  • 7c87ed1 chore(deps): bump the kotest group with 3 updates (#674)
  • Additional commits viewable in compare view

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/weather-stdio-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/weather-stdio-server/gradle/libs.versions.toml b/samples/weather-stdio-server/gradle/libs.versions.toml index cc2b53a..839a283 100644 --- a/samples/weather-stdio-server/gradle/libs.versions.toml +++ b/samples/weather-stdio-server/gradle/libs.versions.toml @@ -3,7 +3,7 @@ coroutines = "1.10.2" kotlin = "2.2.21" ktor = "3.3.3" logging = "8.0.01" -mcp-kotlin = "0.11.0" +mcp-kotlin = "0.11.1" shadow = "9.4.1" slf4j = "2.0.17" From bce6811b66367eafbc36b37153ff3ded2bb36883 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Wed, 15 Apr 2026 14:12:05 +0200 Subject: [PATCH 248/449] fix: validate protocol version header on initialization requests (#697) Validate `mcp-protocol-version` HTTP header during `InitializeRequest` Fix `DEFAULT_NEGOTIATED_PROTOCOL_VERSION` to match the spec fixes #547 ## How Has This Been Tested? Updated existing test (removed TODO referencing #547) to verify init request ## Breaking Changes none ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- .../kotlin/sdk/types/common.kt | 2 +- .../server/StreamableHttpServerTransport.kt | 41 ++++++++++--------- .../StreamableHttpServerTransportTest.kt | 30 ++++++++++++-- 3 files changed, 49 insertions(+), 24 deletions(-) diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt index ac867af..ca08356 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt @@ -12,7 +12,7 @@ import kotlinx.serialization.json.JsonObject public const val LATEST_PROTOCOL_VERSION: String = "2025-11-25" /** The default protocol version used when negotiation is not performed. */ -public const val DEFAULT_NEGOTIATED_PROTOCOL_VERSION: String = "2025-06-18" +public const val DEFAULT_NEGOTIATED_PROTOCOL_VERSION: String = "2025-03-26" /** All MCP protocol versions supported by this SDK. */ public val SUPPORTED_PROTOCOL_VERSIONS: List = listOf( diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt index 17e6a28..9c105f9 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt @@ -367,6 +367,7 @@ public class StreamableHttpServerTransport(private val configuration: Configurat ) return } + if (!validateProtocolVersion(call)) return if (messages.size > 1) { call.reject( HttpStatusCode.BadRequest, @@ -393,11 +394,23 @@ public class StreamableHttpServerTransport(private val configuration: Configurat return } + // Extract protocol version for priming event decision. + // For initialize requests, get from request params. + // For other requests, get from header (already validated). + val clientProtocolVersion = if (isInitializationRequest) { + val initRequest = messages.first() as JSONRPCRequest + (initRequest.params as? JsonObject)?.get("protocolVersion") + ?.let { McpJson.decodeFromJsonElement(it) } + ?: DEFAULT_NEGOTIATED_PROTOCOL_VERSION + } else { + call.request.header(MCP_PROTOCOL_VERSION_HEADER) ?: DEFAULT_NEGOTIATED_PROTOCOL_VERSION + } + val streamId = Uuid.random().toString() if (!configuration.enableJsonResponse) { call.appendSseHeaders() flushSse(session) // flush headers immediately - maybeSendPrimingEvent(streamId, session, call.request.header(MCP_PROTOCOL_VERSION_HEADER)) + maybeSendPrimingEvent(streamId, session, clientProtocolVersion) } streamMutex.withLock { @@ -456,7 +469,9 @@ public class StreamableHttpServerTransport(private val configuration: Configurat // SSE headers (Content-Type, Cache-Control, Connection) are already set by the framework's SSE handler flushSse(sseSession) streamsMapping[STANDALONE_SSE_STREAM_ID] = SessionContext(sseSession, call) - maybeSendPrimingEvent(STANDALONE_SSE_STREAM_ID, sseSession, call.request.header(MCP_PROTOCOL_VERSION_HEADER)) + val clientProtocolVersion = + call.request.header(MCP_PROTOCOL_VERSION_HEADER) ?: DEFAULT_NEGOTIATED_PROTOCOL_VERSION + maybeSendPrimingEvent(STANDALONE_SSE_STREAM_ID, sseSession, clientProtocolVersion) sseSession.coroutineContext.job.invokeOnCompletion { streamsMapping.remove(STANDALONE_SSE_STREAM_ID) } @@ -568,9 +583,9 @@ public class StreamableHttpServerTransport(private val configuration: Configurat return false } - val sessionHeaderValues = call.request.headers.getAll(MCP_SESSION_ID_HEADER) + val headerId = call.request.header(MCP_SESSION_ID_HEADER) - if (sessionHeaderValues.isNullOrEmpty()) { + if (headerId == null) { call.reject( HttpStatusCode.BadRequest, RPCError.ErrorCode.CONNECTION_CLOSED, @@ -579,17 +594,6 @@ public class StreamableHttpServerTransport(private val configuration: Configurat return false } - if (sessionHeaderValues.size > 1) { - call.reject( - HttpStatusCode.BadRequest, - RPCError.ErrorCode.CONNECTION_CLOSED, - "Bad Request: Mcp-Session-Id header must be a single value", - ) - return false - } - - val headerId = sessionHeaderValues.single() - return when (headerId) { sessionId -> true @@ -605,8 +609,7 @@ public class StreamableHttpServerTransport(private val configuration: Configurat } private suspend fun validateProtocolVersion(call: ApplicationCall): Boolean { - val protocolVersions = call.request.headers.getAll(MCP_PROTOCOL_VERSION_HEADER) - val version = protocolVersions?.lastOrNull() ?: DEFAULT_NEGOTIATED_PROTOCOL_VERSION + val version = call.request.headers[MCP_PROTOCOL_VERSION_HEADER] ?: return true return when (version) { !in SUPPORTED_PROTOCOL_VERSIONS -> { @@ -715,14 +718,14 @@ public class StreamableHttpServerTransport(private val configuration: Configurat private suspend fun maybeSendPrimingEvent( streamId: String, session: ServerSSESession?, - clientProtocolVersion: String? = null, + clientProtocolVersion: String, ) { val store = configuration.eventStore if (store == null || session == null) return // Priming events have empty data which older clients cannot handle. // Only send priming events to clients with protocol version >= 2025-11-25 // which includes the fix for handling empty SSE data. - if (clientProtocolVersion != null && clientProtocolVersion < MIN_PRIMING_EVENT_PROTOCOL_VERSION) return + if (clientProtocolVersion < MIN_PRIMING_EVENT_PROTOCOL_VERSION) return try { val primingEventId = store.storeEvent(streamId, JSONRPCEmptyMessage) session.send( diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt index 8b4f6fa..d00e10e 100644 --- a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt @@ -176,6 +176,31 @@ class StreamableHttpServerTransportTest { secondResponse.status shouldBe HttpStatusCode.BadRequest } + @Test + fun `init request with unsupported protocol version returns an HTTP error`() = testApplication { + configTestServer() + + val client = createTestClient() + + val transport = StreamableHttpServerTransport(enableJsonResponse = true) + transport.onMessage { message -> + if (message is JSONRPCRequest) { + transport.send(JSONRPCResponse(message.id, EmptyResult())) + } + } + + configureTransportEndpoint(transport) + + val initResponse = client.post(path) { + addStreamableHeaders() + header("mcp-protocol-version", "1900-01-01") + setBody(buildInitializeRequestPayload()) + } + + initResponse.status shouldBe HttpStatusCode.BadRequest + initResponse.headers[MCP_SESSION_ID_HEADER] shouldBe null + } + @Test fun `request with unsupported protocol version returns an HTTP error`() = testApplication { configTestServer() @@ -191,18 +216,15 @@ class StreamableHttpServerTransportTest { configureTransportEndpoint(transport) - val initPayload = buildInitializeRequestPayload() val initResponse = client.post(path) { addStreamableHeaders() - setBody(initPayload) + setBody(buildInitializeRequestPayload()) } initResponse.status shouldBe HttpStatusCode.OK val sessionId = initResponse.headers[MCP_SESSION_ID_HEADER] assertNotNull(sessionId) - // TODO When https://github.com/modelcontextprotocol/kotlin-sdk/issues/547 is fixed, - // check the incompatible mcp-protocol-version in the InitializeRequest and delete the part below val response = client.post(path) { addStreamableHeaders() header("mcp-session-id", sessionId) From e7b09d84f74e2891e139c73c4d4bca4ca28d54ef Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Wed, 15 Apr 2026 14:59:01 +0200 Subject: [PATCH 249/449] fix: propagate CancellationException instead of logging as error (#706) fixes #242 ## How Has This Been Tested? new and existing tests pass ## Breaking Changes none ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- .../kotlin/sdk/client/SseClientTransport.kt | 19 ++++--- .../kotlin/sdk/client/StdioClientTransport.kt | 10 +++- .../kotlin/sdk/shared/Protocol.kt | 5 ++ .../sdk/shared/WebSocketMcpTransport.kt | 4 ++ .../kotlin/sdk/shared/ProtocolTest.kt | 40 +++++++++++++++ .../kotlin/sdk/server/SSEServerTransport.kt | 6 +++ .../server/StreamableHttpServerTransport.kt | 50 +++++++++++++------ 7 files changed, 109 insertions(+), 25 deletions(-) diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseClientTransport.kt index 3c9d88b..a48db60 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseClientTransport.kt @@ -24,11 +24,12 @@ import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job +import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel -import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.ensureActive import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import kotlinx.serialization.SerializationException import kotlin.concurrent.atomics.ExperimentalAtomicApi import kotlin.time.Duration @@ -171,13 +172,15 @@ public class SseClientTransport( } override suspend fun closeResources() { - job?.cancelAndJoin() - try { - if (::session.isInitialized) session.cancel() - if (::scope.isInitialized) scope.cancel() - endpoint.cancel() - } catch (e: Throwable) { - _onError(e) + withContext(NonCancellable) { + job?.cancel() + try { + if (::session.isInitialized) session.cancel() + if (::scope.isInitialized) scope.cancel() + endpoint.cancel() + } catch (e: Throwable) { + _onError(e) + } } } } diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt index 12529cc..a61ea4b 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt @@ -21,6 +21,7 @@ import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job +import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.cancelAndJoin @@ -31,6 +32,7 @@ import kotlinx.coroutines.channels.consumeEach import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.isActive import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import kotlinx.coroutines.yield import kotlinx.io.Buffer import kotlinx.io.IOException @@ -241,8 +243,10 @@ public class StdioClientTransport @JvmOverloads public constructor( } override suspend fun closeResources() { - scope.stopProcessing("Closed") - scope.coroutineContext[Job]?.join() // Wait for all coroutines to complete + withContext(NonCancellable) { + scope.stopProcessing("Closed") + scope.coroutineContext[Job]?.join() // Wait for all coroutines to complete + } } private fun sendOutboundMessage(message: JSONRPCMessage, sink: Sink, mainScope: CoroutineScope) { @@ -264,6 +268,8 @@ public class StdioClientTransport @JvmOverloads public constructor( private suspend fun handleJSONRPCMessage(msg: JSONRPCMessage) { try { _onMessage.invoke(msg) + } catch (e: CancellationException) { + throw e } catch (e: Throwable) { logger.error(e) { "Error processing message." } runCatching { _onError.invoke(e) } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt index 43c0ae5..bc3cf76 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt @@ -277,12 +277,15 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio } try { handler(notification) + } catch (e: CancellationException) { + throw e } catch (cause: Throwable) { logger.error(cause) { "Error handling notification: ${notification.method}" } onError(cause) } } + @Suppress("ThrowsCount") private suspend fun onRequest(request: JSONRPCRequest) { logger.trace { "Received request: ${request.method} (id: ${request.id})" } @@ -300,6 +303,8 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio ), ), ) + } catch (e: CancellationException) { + throw e } catch (cause: Throwable) { logger.error(cause) { "Error sending method not found response" } onError(cause) diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt index 5f8bf9d..9fa1a0b 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt @@ -16,6 +16,7 @@ import kotlinx.coroutines.job import kotlinx.coroutines.launch import kotlin.concurrent.atomics.AtomicBoolean import kotlin.concurrent.atomics.ExperimentalAtomicApi +import kotlin.coroutines.cancellation.CancellationException /** WebSocket subprotocol identifier for MCP connections. */ public const val MCP_SUBPROTOCOL: String = "mcp" @@ -44,6 +45,7 @@ public abstract class WebSocketMcpTransport : AbstractTransport() { */ protected abstract suspend fun initializeSession() + @Suppress("ThrowsCount") override suspend fun start() { logger.debug { "Starting websocket transport" } @@ -74,6 +76,8 @@ public abstract class WebSocketMcpTransport : AbstractTransport() { try { val message = McpJson.decodeFromString(message.readText()) _onMessage.invoke(message) + } catch (e: CancellationException) { + throw e } catch (e: Exception) { _onError.invoke(e) throw e diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolTest.kt index b228071..b726c2f 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolTest.kt @@ -1,11 +1,14 @@ package io.modelcontextprotocol.kotlin.sdk.shared +import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.collections.shouldContainExactly +import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe import io.modelcontextprotocol.kotlin.sdk.types.CustomRequest import io.modelcontextprotocol.kotlin.sdk.types.EmptyResult import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse import io.modelcontextprotocol.kotlin.sdk.types.McpJson @@ -24,6 +27,7 @@ import kotlinx.serialization.json.encodeToJsonElement import kotlinx.serialization.json.int import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive +import kotlin.coroutines.cancellation.CancellationException import kotlin.test.BeforeTest import kotlin.test.Test @@ -126,6 +130,36 @@ class ProtocolTest { inFlight.await() } + @Test + fun `should propagate CancellationException from notification handler without calling onError`() = runTest { + protocol.connect(transport) + + protocol.fallbackNotificationHandler = { + throw CancellationException("test cancellation") + } + + shouldThrow { + transport.deliver(JSONRPCNotification(method = "test/notification")) + } + + protocol.errors shouldHaveSize 0 + } + + @Test + fun `should report non-cancellation exception from notification handler via onError`() = runTest { + protocol.connect(transport) + + protocol.fallbackNotificationHandler = { + throw IllegalStateException("handler failed") + } + + // Non-CE exceptions are caught and reported, not propagated + transport.deliver(JSONRPCNotification(method = "test/notification")) + + protocol.errors shouldHaveSize 1 + protocol.errors[0].message shouldBe "handler failed" + } + @Test fun `should create params object when request params are null`() = runTest { protocol.connect(transport) @@ -154,6 +188,12 @@ class ProtocolTest { } private class TestProtocol : Protocol(null) { + val errors = mutableListOf() + + override fun onError(error: Throwable) { + errors.add(error) + } + override fun assertCapabilityForMethod(method: Method) { // noop } diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt index f8c6581..fcf1a46 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt @@ -84,6 +84,8 @@ public class SseServerTransport(private val endpoint: String, private val sessio } call.receiveText() + } catch (e: CancellationException) { + throw e } catch (e: Exception) { call.respondText("Invalid message: ${e.message}", status = HttpStatusCode.BadRequest) _onError.invoke(e) @@ -92,6 +94,8 @@ public class SseServerTransport(private val endpoint: String, private val sessio try { handleMessage(body) + } catch (e: CancellationException) { + throw e } catch (e: Exception) { call.respondText("Error handling message $body: ${e.message}", status = HttpStatusCode.BadRequest) return @@ -108,6 +112,8 @@ public class SseServerTransport(private val endpoint: String, private val sessio try { val parsedMessage = McpJson.decodeFromString(message) _onMessage.invoke(parsedMessage) + } catch (e: CancellationException) { + throw e } catch (e: Exception) { _onError.invoke(e) throw e diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt index 9c105f9..497005a 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt @@ -28,15 +28,18 @@ import io.modelcontextprotocol.kotlin.sdk.types.RPCError import io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.REQUEST_TIMEOUT import io.modelcontextprotocol.kotlin.sdk.types.RequestId import io.modelcontextprotocol.kotlin.sdk.types.SUPPORTED_PROTOCOL_VERSIONS +import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.job import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.decodeFromJsonElement import kotlin.concurrent.atomics.AtomicBoolean import kotlin.concurrent.atomics.ExperimentalAtomicApi +import kotlin.coroutines.cancellation.CancellationException import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds import kotlin.uuid.ExperimentalUuidApi @@ -283,17 +286,19 @@ public class StreamableHttpServerTransport(private val configuration: Configurat } override suspend fun close() { - streamMutex.withLock { - streamsMapping.values.forEach { - try { - it.session?.close() - } catch (_: Exception) { + withContext(NonCancellable) { + streamMutex.withLock { + streamsMapping.values.forEach { + try { + it.session?.close() + } catch (_: Exception) { + } } + streamsMapping.clear() + requestToStreamMapping.clear() + requestToResponseMapping.clear() + invokeOnCloseCallback() } - streamsMapping.clear() - requestToStreamMapping.clear() - requestToResponseMapping.clear() - invokeOnCloseCallback() } } @@ -420,6 +425,8 @@ public class StreamableHttpServerTransport(private val configuration: Configurat call.coroutineContext.job.invokeOnCompletion { streamsMapping.remove(streamId) } messages.forEach { message -> _onMessage(message) } + } catch (e: CancellationException) { + throw e } catch (e: Exception) { call.reject( HttpStatusCode.BadRequest, @@ -498,12 +505,14 @@ public class StreamableHttpServerTransport(private val configuration: Configurat val streamId = requestToStreamMapping[requestId] ?: return val sessionContext = streamsMapping[streamId] ?: return - try { - sessionContext.session?.close() - } catch (e: Exception) { - _onError(e) - } finally { - streamsMapping.remove(streamId) + withContext(NonCancellable) { + try { + sessionContext.session?.close() + } catch (e: Exception) { + _onError(e) + } finally { + streamsMapping.remove(streamId) + } } } @@ -554,6 +563,8 @@ public class StreamableHttpServerTransport(private val configuration: Configurat id = eventId, data = McpJson.encodeToString(message), ) + } catch (e: CancellationException) { + throw e } catch (e: Exception) { _onError(IllegalStateException("Failed to replay event: ${e.message}", e)) } @@ -565,6 +576,8 @@ public class StreamableHttpServerTransport(private val configuration: Configurat streamsMapping.remove(streamId) throwable?.let { _onError(it) } } + } catch (e: CancellationException) { + throw e } catch (e: Exception) { _onError(e) } @@ -658,6 +671,8 @@ public class StreamableHttpServerTransport(private val configuration: Configurat private suspend fun flushSse(session: ServerSSESession?) { try { session?.send(data = "") + } catch (e: CancellationException) { + throw e } catch (e: Exception) { _onError(e) } @@ -709,6 +724,9 @@ public class StreamableHttpServerTransport(private val configuration: Configurat val eventId = configuration.eventStore?.storeEvent(streamId, message) try { session?.send(event = "message", id = eventId, data = McpJson.encodeToString(message)) + } catch (e: CancellationException) { + streamsMapping.remove(streamId) + throw e } catch (_: Exception) { streamsMapping.remove(streamId) } @@ -733,6 +751,8 @@ public class StreamableHttpServerTransport(private val configuration: Configurat retry = configuration.retryInterval?.inWholeMilliseconds, data = "", ) + } catch (e: CancellationException) { + throw e } catch (e: Exception) { _onError(e) } From 8e87cd86f494f4b92a128ae4bf2f1f19565537a5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 21:41:35 +0200 Subject: [PATCH 250/449] chore(deps): bump the kotlin group across 1 directory with 2 updates (#704) Bumps the kotlin group with 2 updates in the / directory: [org.jetbrains.kotlin:kotlin-gradle-plugin](https://github.com/JetBrains/kotlin) and [org.jetbrains.kotlin:kotlin-serialization](https://github.com/JetBrains/kotlin). Updates `org.jetbrains.kotlin:kotlin-gradle-plugin` from 2.3.10 to 2.3.20
Release notes

Sourced from org.jetbrains.kotlin:kotlin-gradle-plugin's releases.

Kotlin 2.3.20

Changelog

Analysis API. FIR

New Features

  • KT-78090 Implement stubs support for new conditional returns and holdsIn contracts

Fixes

  • KT-82948 'FirRegularClass' expected as a containing declaration, got 'FirTypeAliasImpl'
  • KT-83467 Package-level JSpecify annotations are ignored when coming from jars or libraries
  • KT-82057 K2. Cannot infer type parameter 'R' in Ktor routing post() function with explicit response type
  • KT-82846 AA: unresolved KtExpression.expressionType for the reference to the parameter with default value
  • KT-80485 False positive UNRESOLVED_REFERENCE on nested interface from super-super class in the super type position inside an anonymous object
  • KT-82772 Flaky false positive deprecation warning on PersistentMap.put in Kotlin repo in IDE mode
  • KT-76487 StdLibSourcesLazyDeclarationResolveTestGenerated.testWrappedInt is unstable
  • KT-82618 Various tests are failing with NPE in kt-master after updating the compiler on 19.11.25
  • KT-82076 Error querying members of JavaClass created for SymbolLightClassForAnnotationClass during library analysis
  • KT-71596 Include Js/Wasi checkers in AbstractLLFirDiagnosticsCollector
  • KT-82085 No OUTER_CLASS_ARGUMENTS_REQUIRED on type parameter bound in IDE
  • KT-81873 Provide a way of including traces in phase JFR events
  • KT-71929 Consider leaving the non-post-compute version at EnhancementSymbolsCache.enhancedFunctions

Analysis API. Infrastructure

  • KT-83173 Analysis API Tests: Library names with RC versions aren't sanitised
  • KT-65140 LL FIR: Implement AbstractFirPsiJsDiagnosticTest for LL FIR
  • KT-82212 [Analysis API, LL FIR] Implement AbstractDiagnosticsFirWasmTest and AbstractDiagnosticsFirWasmWasiTest for LL FIR

Analysis API. Light Classes

  • KT-82227 Value classes should expose regular static methods

Analysis API. PSI

  • KT-81710 'KtTypeReference.getTypeText' does not account for 'suspend' modifier on suspend lambdas
  • KT-82258 Prepare PSI for migration from context receivers to context parameters
  • KT-81074 KDoc: List rendering is broken

Analysis API. Providers and Caches

  • KT-82449 K2 IDE Analysis Freezes During Gradle Sync (Recursive Module Dependency Computation in KotlinModuleDependentsProviderBase.computeTransitiveDependents)
  • KT-82629 'collectDiagnostics' returns stale syntax error after editor fix
  • KT-74907 Analysis API: Apply platform-based library module content restrictions consistently

Analysis API. Standalone

  • KT-81107 AA: KtSourceModuleBuilder.sourceRoots doesn't works with symbolic links

... (truncated)

Changelog

Sourced from org.jetbrains.kotlin:kotlin-gradle-plugin's changelog.

2.3.20

Analysis API. FIR

New Features

  • KT-78090 Implement stubs support for new conditional returns and holdsIn contracts

Fixes

  • KT-82948 'FirRegularClass' expected as a containing declaration, got 'FirTypeAliasImpl'
  • KT-83467 Package-level JSpecify annotations are ignored when coming from jars or libraries
  • KT-82057 K2. Cannot infer type parameter 'R' in Ktor routing post() function with explicit response type
  • KT-82846 AA: unresolved KtExpression.expressionType for the reference to the parameter with default value
  • KT-80485 False positive UNRESOLVED_REFERENCE on nested interface from super-super class in the super type position inside an anonymous object
  • KT-82772 Flaky false positive deprecation warning on PersistentMap.put in Kotlin repo in IDE mode
  • KT-76487 StdLibSourcesLazyDeclarationResolveTestGenerated.testWrappedInt is unstable
  • KT-82618 Various tests are failing with NPE in kt-master after updating the compiler on 19.11.25
  • KT-82076 Error querying members of JavaClass created for SymbolLightClassForAnnotationClass during library analysis
  • KT-71596 Include Js/Wasi checkers in AbstractLLFirDiagnosticsCollector
  • KT-82085 No OUTER_CLASS_ARGUMENTS_REQUIRED on type parameter bound in IDE
  • KT-81873 Provide a way of including traces in phase JFR events
  • KT-71929 Consider leaving the non-post-compute version at EnhancementSymbolsCache.enhancedFunctions

Analysis API. Infrastructure

  • KT-83173 Analysis API Tests: Library names with RC versions aren't sanitised
  • KT-65140 LL FIR: Implement AbstractFirPsiJsDiagnosticTest for LL FIR
  • KT-82212 [Analysis API, LL FIR] Implement AbstractDiagnosticsFirWasmTest and AbstractDiagnosticsFirWasmWasiTest for LL FIR

Analysis API. Light Classes

  • KT-82227 Value classes should expose regular static methods

Analysis API. PSI

  • KT-81710 'KtTypeReference.getTypeText' does not account for 'suspend' modifier on suspend lambdas
  • KT-82258 Prepare PSI for migration from context receivers to context parameters
  • KT-81074 KDoc: List rendering is broken

Analysis API. Providers and Caches

  • KT-82449 K2 IDE Analysis Freezes During Gradle Sync (Recursive Module Dependency Computation in KotlinModuleDependentsProviderBase.computeTransitiveDependents)
  • KT-82629 'collectDiagnostics' returns stale syntax error after editor fix
  • KT-74907 Analysis API: Apply platform-based library module content restrictions consistently

Analysis API. Standalone

  • KT-81107 AA: KtSourceModuleBuilder.sourceRoots doesn't works with symbolic links

... (truncated)

Commits
  • d57eb4a Add ChangeLog for 2.3.20-RC3
  • 41886bb [Gradle] Update KGP npm tooling dependency versions
  • 210a9bd [Native] Do not mark iosX64 target as deprecate in DSL
  • 7ed446e Add ChangeLog for 2.3.20-RC2
  • 520eade [K/JVM] Fix incorrect optimization of local delegation
  • 7ef8bcc Reflection: add LazyKProperty to fix recent performance regression
  • 9fa7cf2 [Gradle] Link NoActiveThreadsAfterCompilerInvocationIT with KT-84566
  • d410596 [Gradle] Shutdown coroutines dispatcher threads after compiler invocation
  • 348430b [BTA] Fix compatibility between API 2.3.20 and KC 2.3.10
  • 4058f3c [CMP] HiddenFromObjC remove outdated offset check
  • Additional commits viewable in compare view

Updates `org.jetbrains.kotlin:kotlin-serialization` from 2.3.10 to 2.3.20
Release notes

Sourced from org.jetbrains.kotlin:kotlin-serialization's releases.

Kotlin 2.3.20

Changelog

Analysis API. FIR

New Features

  • KT-78090 Implement stubs support for new conditional returns and holdsIn contracts

Fixes

  • KT-82948 'FirRegularClass' expected as a containing declaration, got 'FirTypeAliasImpl'
  • KT-83467 Package-level JSpecify annotations are ignored when coming from jars or libraries
  • KT-82057 K2. Cannot infer type parameter 'R' in Ktor routing post() function with explicit response type
  • KT-82846 AA: unresolved KtExpression.expressionType for the reference to the parameter with default value
  • KT-80485 False positive UNRESOLVED_REFERENCE on nested interface from super-super class in the super type position inside an anonymous object
  • KT-82772 Flaky false positive deprecation warning on PersistentMap.put in Kotlin repo in IDE mode
  • KT-76487 StdLibSourcesLazyDeclarationResolveTestGenerated.testWrappedInt is unstable
  • KT-82618 Various tests are failing with NPE in kt-master after updating the compiler on 19.11.25
  • KT-82076 Error querying members of JavaClass created for SymbolLightClassForAnnotationClass during library analysis
  • KT-71596 Include Js/Wasi checkers in AbstractLLFirDiagnosticsCollector
  • KT-82085 No OUTER_CLASS_ARGUMENTS_REQUIRED on type parameter bound in IDE
  • KT-81873 Provide a way of including traces in phase JFR events
  • KT-71929 Consider leaving the non-post-compute version at EnhancementSymbolsCache.enhancedFunctions

Analysis API. Infrastructure

  • KT-83173 Analysis API Tests: Library names with RC versions aren't sanitised
  • KT-65140 LL FIR: Implement AbstractFirPsiJsDiagnosticTest for LL FIR
  • KT-82212 [Analysis API, LL FIR] Implement AbstractDiagnosticsFirWasmTest and AbstractDiagnosticsFirWasmWasiTest for LL FIR

Analysis API. Light Classes

  • KT-82227 Value classes should expose regular static methods

Analysis API. PSI

  • KT-81710 'KtTypeReference.getTypeText' does not account for 'suspend' modifier on suspend lambdas
  • KT-82258 Prepare PSI for migration from context receivers to context parameters
  • KT-81074 KDoc: List rendering is broken

Analysis API. Providers and Caches

  • KT-82449 K2 IDE Analysis Freezes During Gradle Sync (Recursive Module Dependency Computation in KotlinModuleDependentsProviderBase.computeTransitiveDependents)
  • KT-82629 'collectDiagnostics' returns stale syntax error after editor fix
  • KT-74907 Analysis API: Apply platform-based library module content restrictions consistently

Analysis API. Standalone

  • KT-81107 AA: KtSourceModuleBuilder.sourceRoots doesn't works with symbolic links

... (truncated)

Changelog

Sourced from org.jetbrains.kotlin:kotlin-serialization's changelog.

2.3.20

Analysis API. FIR

New Features

  • KT-78090 Implement stubs support for new conditional returns and holdsIn contracts

Fixes

  • KT-82948 'FirRegularClass' expected as a containing declaration, got 'FirTypeAliasImpl'
  • KT-83467 Package-level JSpecify annotations are ignored when coming from jars or libraries
  • KT-82057 K2. Cannot infer type parameter 'R' in Ktor routing post() function with explicit response type
  • KT-82846 AA: unresolved KtExpression.expressionType for the reference to the parameter with default value
  • KT-80485 False positive UNRESOLVED_REFERENCE on nested interface from super-super class in the super type position inside an anonymous object
  • KT-82772 Flaky false positive deprecation warning on PersistentMap.put in Kotlin repo in IDE mode
  • KT-76487 StdLibSourcesLazyDeclarationResolveTestGenerated.testWrappedInt is unstable
  • KT-82618 Various tests are failing with NPE in kt-master after updating the compiler on 19.11.25
  • KT-82076 Error querying members of JavaClass created for SymbolLightClassForAnnotationClass during library analysis
  • KT-71596 Include Js/Wasi checkers in AbstractLLFirDiagnosticsCollector
  • KT-82085 No OUTER_CLASS_ARGUMENTS_REQUIRED on type parameter bound in IDE
  • KT-81873 Provide a way of including traces in phase JFR events
  • KT-71929 Consider leaving the non-post-compute version at EnhancementSymbolsCache.enhancedFunctions

Analysis API. Infrastructure

  • KT-83173 Analysis API Tests: Library names with RC versions aren't sanitised
  • KT-65140 LL FIR: Implement AbstractFirPsiJsDiagnosticTest for LL FIR
  • KT-82212 [Analysis API, LL FIR] Implement AbstractDiagnosticsFirWasmTest and AbstractDiagnosticsFirWasmWasiTest for LL FIR

Analysis API. Light Classes

  • KT-82227 Value classes should expose regular static methods

Analysis API. PSI

  • KT-81710 'KtTypeReference.getTypeText' does not account for 'suspend' modifier on suspend lambdas
  • KT-82258 Prepare PSI for migration from context receivers to context parameters
  • KT-81074 KDoc: List rendering is broken

Analysis API. Providers and Caches

  • KT-82449 K2 IDE Analysis Freezes During Gradle Sync (Recursive Module Dependency Computation in KotlinModuleDependentsProviderBase.computeTransitiveDependents)
  • KT-82629 'collectDiagnostics' returns stale syntax error after editor fix
  • KT-74907 Analysis API: Apply platform-based library module content restrictions consistently

Analysis API. Standalone

  • KT-81107 AA: KtSourceModuleBuilder.sourceRoots doesn't works with symbolic links

... (truncated)

Commits
  • d57eb4a Add ChangeLog for 2.3.20-RC3
  • 41886bb [Gradle] Update KGP npm tooling dependency versions
  • 210a9bd [Native] Do not mark iosX64 target as deprecate in DSL
  • 7ed446e Add ChangeLog for 2.3.20-RC2
  • 520eade [K/JVM] Fix incorrect optimization of local delegation
  • 7ef8bcc Reflection: add LazyKProperty to fix recent performance regression
  • 9fa7cf2 [Gradle] Link NoActiveThreadsAfterCompilerInvocationIT with KT-84566
  • d410596 [Gradle] Shutdown coroutines dispatcher threads after compiler invocation
  • 348430b [BTA] Fix compatibility between API 2.3.20 and KC 2.3.10
  • 4058f3c [CMP] HiddenFromObjC remove outdated offset check
  • Additional commits viewable in compare view

Updates `org.jetbrains.kotlin:kotlin-serialization` from 2.3.10 to 2.3.20
Release notes

Sourced from org.jetbrains.kotlin:kotlin-serialization's releases.

Kotlin 2.3.20

Changelog

Analysis API. FIR

New Features

  • KT-78090 Implement stubs support for new conditional returns and holdsIn contracts

Fixes

  • KT-82948 'FirRegularClass' expected as a containing declaration, got 'FirTypeAliasImpl'
  • KT-83467 Package-level JSpecify annotations are ignored when coming from jars or libraries
  • KT-82057 K2. Cannot infer type parameter 'R' in Ktor routing post() function with explicit response type
  • KT-82846 AA: unresolved KtExpression.expressionType for the reference to the parameter with default value
  • KT-80485 False positive UNRESOLVED_REFERENCE on nested interface from super-super class in the super type position inside an anonymous object
  • KT-82772 Flaky false positive deprecation warning on PersistentMap.put in Kotlin repo in IDE mode
  • KT-76487 StdLibSourcesLazyDeclarationResolveTestGenerated.testWrappedInt is unstable
  • KT-82618 Various tests are failing with NPE in kt-master after updating the compiler on 19.11.25
  • KT-82076 Error querying members of JavaClass created for SymbolLightClassForAnnotationClass during library analysis
  • KT-71596 Include Js/Wasi checkers in AbstractLLFirDiagnosticsCollector
  • KT-82085 No OUTER_CLASS_ARGUMENTS_REQUIRED on type parameter bound in IDE
  • KT-81873 Provide a way of including traces in phase JFR events
  • KT-71929 Consider leaving the non-post-compute version at EnhancementSymbolsCache.enhancedFunctions

Analysis API. Infrastructure

  • KT-83173 Analysis API Tests: Library names with RC versions aren't sanitised
  • KT-65140 LL FIR: Implement AbstractFirPsiJsDiagnosticTest for LL FIR
  • KT-82212 [Analysis API, LL FIR] Implement AbstractDiagnosticsFirWasmTest and AbstractDiagnosticsFirWasmWasiTest for LL FIR

Analysis API. Light Classes

  • KT-82227 Value classes should expose regular static methods

Analysis API. PSI

  • KT-81710 'KtTypeReference.getTypeText' does not account for 'suspend' modifier on suspend lambdas
  • KT-82258 Prepare PSI for migration from context receivers to context parameters
  • KT-81074 KDoc: List rendering is broken

Analysis API. Providers and Caches

  • KT-82449 K2 IDE Analysis Freezes During Gradle Sync (Recursive Module Dependency Computation in KotlinModuleDependentsProviderBase.computeTransitiveDependents)
  • KT-82629 'collectDiagnostics' returns stale syntax error after editor fix
  • KT-74907 Analysis API: Apply platform-based library module content restrictions consistently

Analysis API. Standalone

  • KT-81107 AA: KtSourceModuleBuilder.sourceRoots doesn't works with symbolic links

... (truncated)

Changelog

Sourced from org.jetbrains.kotlin:kotlin-serialization's changelog.

2.3.20

Analysis API. FIR

New Features

  • KT-78090 Implement stubs support for new conditional returns and holdsIn contracts

Fixes

  • KT-82948 'FirRegularClass' expected as a containing declaration, got 'FirTypeAliasImpl'
  • KT-83467 Package-level JSpecify annotations are ignored when coming from jars or libraries
  • KT-82057 K2. Cannot infer type parameter 'R' in Ktor routing post() function with explicit response type
  • KT-82846 AA: unresolved KtExpression.expressionType for the reference to the parameter with default value
  • KT-80485 False positive UNRESOLVED_REFERENCE on nested interface from super-super class in the super type position inside an anonymous object
  • KT-82772 Flaky false positive deprecation warning on PersistentMap.put in Kotlin repo in IDE mode
  • KT-76487 StdLibSourcesLazyDeclarationResolveTestGenerated.testWrappedInt is unstable
  • KT-82618 Various tests are failing with NPE in kt-master after updating the compiler on 19.11.25
  • KT-82076 Error querying members of JavaClass created for SymbolLightClassForAnnotationClass during library analysis
  • KT-71596 Include Js/Wasi checkers in AbstractLLFirDiagnosticsCollector
  • KT-82085 No OUTER_CLASS_ARGUMENTS_REQUIRED on type parameter bound in IDE
  • KT-81873 Provide a way of including traces in phase JFR events
  • KT-71929 Consider leaving the non-post-compute version at EnhancementSymbolsCache.enhancedFunctions

Analysis API. Infrastructure

  • KT-83173 Analysis API Tests: Library names with RC versions aren't sanitised
  • KT-65140 LL FIR: Implement AbstractFirPsiJsDiagnosticTest for LL FIR
  • KT-82212 [Analysis API, LL FIR] Implement AbstractDiagnosticsFirWasmTest and AbstractDiagnosticsFirWasmWasiTest for LL FIR

Analysis API. Light Classes

  • KT-82227 Value classes should expose regular static methods

Analysis API. PSI

  • KT-81710 'KtTypeReference.getTypeText' does not account for 'suspend' modifier on suspend lambdas
  • KT-82258 Prepare PSI for migration from context receivers to context parameters
  • KT-81074 KDoc: List rendering is broken

Analysis API. Providers and Caches

  • KT-82449 K2 IDE Analysis Freezes During Gradle Sync (Recursive Module Dependency Computation in KotlinModuleDependentsProviderBase.computeTransitiveDependents)
  • KT-82629 'collectDiagnostics' returns stale syntax error after editor fix
  • KT-74907 Analysis API: Apply platform-based library module content restrictions consistently

Analysis API. Standalone

  • KT-81107 AA: KtSourceModuleBuilder.sourceRoots doesn't works with symbolic links

... (truncated)

Commits
  • d57eb4a Add ChangeLog for 2.3.20-RC3
  • 41886bb [Gradle] Update KGP npm tooling dependency versions
  • 210a9bd [Native] Do not mark iosX64 target as deprecate in DSL
  • 7ed446e Add ChangeLog for 2.3.20-RC2
  • 520eade [K/JVM] Fix incorrect optimization of local delegation
  • 7ef8bcc Reflection: add LazyKProperty to fix recent performance regression
  • 9fa7cf2 [Gradle] Link NoActiveThreadsAfterCompilerInvocationIT with KT-84566
  • d410596 [Gradle] Shutdown coroutines dispatcher threads after compiler invocation
  • 348430b [BTA] Fix compatibility between API 2.3.20 and KC 2.3.10
  • 4058f3c [CMP] HiddenFromObjC remove outdated offset check
  • Additional commits viewable in compare view

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0fb3135..ec12351 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ atomicfu = "0.32.1" binaryCompatibilityValidatorPlugin = "0.18.1" detekt = "2.0.0-alpha.2" dokka = "2.2.0" -kotlin = "2.3.10" +kotlin = "2.3.20" kover = "0.9.8" ktlint = "14.2.0" knit = "0.5.1" From c6e6f7ca56028b06518c18a84be267d0a00e7fb8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 22:17:01 +0200 Subject: [PATCH 251/449] chore(deps): bump org.jetbrains.kotlinx:kotlinx-serialization-json from 1.10.0 to 1.11.0 in the kotlinx group across 1 directory (#687) Bumps the kotlinx group with 1 update in the / directory: [org.jetbrains.kotlinx:kotlinx-serialization-json](https://github.com/Kotlin/kotlinx.serialization). Updates `org.jetbrains.kotlinx:kotlinx-serialization-json` from 1.10.0 to 1.11.0
Release notes

Sourced from org.jetbrains.kotlinx:kotlinx-serialization-json's releases.

1.11.0

This release is based on Kotlin 2.3.20 and provides a new Json exceptions API and some bugfixes and improvements.

Expose Json exceptions structure

To make working with exceptions easier and providing proper error codes in e.g., REST APIs, classes JsonException, JsonDecodingException, and JsonEncodingException are now public. They have relevant public properties, such as shortMessage, path, offset, and others. This API is currently experimental, and we're going to improve it further in the subsequent releases. See the linked issues for the details: #1930, #1877.

Ability to hide user input from exception messages for security/privacy reasons.

Historically, exception messages in kotlinx.serialization often included the input Json itself for debuggability reason. Such behavior may pose additional challenges for logging, analytics, and other systems, since a system is not always allowed to store user data due to privacy/security reasons, which imposes additional sanitation logic. To address this issue, a new property exceptionsWithDebugInfo is added to JsonConfiguration. Disable it to hide user input from exception messages. IMPORTANT: This behavior will be enabled by default when this property becomes stable. See #2590 for more details.

Bugfixes and improvements

  • CBOR: Relax value range check when decoding numbers (#3167)
  • Use a specialized writeDecimalLong method for IO stream integrations in Json (#3152)
Changelog

Sourced from org.jetbrains.kotlinx:kotlinx-serialization-json's changelog.

1.11.0 / 2026-04-10

This release is based on Kotlin 2.3.20 and provides new Json exceptions API and some bugfixes and improvements.

Expose Json exceptions structure

To make working with exceptions easier and providing proper error codes in e.g., REST APIs, classes JsonException, JsonDecodingException, and JsonEncodingException are now public. They have relevant public properties, such as shortMessage, path, offset, and others. This API is currently experimental, and we're going to improve it further in the subsequent releases. See the linked issues for the details: #1930, #1877.

Ability to hide user input from exception messages for security/privacy reasons.

Historically, exception messages in kotlinx.serialization often included the input Json itself for debuggability reason. Such behavior may pose additional challenges for logging, analytics, and other systems, since a system is not always allowed to store user data due to privacy/security reasons, which imposes additional sanitation logic. To address this issue, a new property exceptionsWithDebugInfo is added to JsonConfiguration. Disable it to hide user input from exception messages. IMPORTANT: This behavior will be enabled by default when this property becomes stable. See #2590 for more details.

Bugfixes and improvements

  • CBOR: Relax value range check when decoding numbers (#3167)
  • Use a specialized writeDecimalLong method for IO stream integrations in Json (#3152)
Commits
  • 6956af2 Prepare 1.11 release
  • 390d84c Merge remote-tracking branch 'origin/master' into dev
  • 431fe2d Use local repo for publishing (#3171)
  • 05c12b6 Add usage attribute to "testRepositories" configuration
  • a4e1f08 Bump Kover version to 0.9.8 release (#3174)
  • 304e858 Expose Json exceptions structure (#3145)
  • 4a0338e Included G Play SDK verification file for core-jvm (#3169)
  • 421f64c CBOR: Relax value range check when decoding numbers (#3167)
  • 85a4f12 KT-84955: mark apple x64 tagets as deprecated error
  • bd38b0e Remove dead code
  • Additional commits viewable in compare view

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ec12351..36478a9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,7 +21,7 @@ ktor = "3.3.3" logging = "8.0.01" mockk = "1.14.9" mokksy = "0.8.1" -serialization = "1.10.0" +serialization = "1.11.0" slf4j = "2.0.17" logback = "1.5.32" junit = "5.14.3" From c1f0450d66fec4dd5168ba6ae9cf4facc8192d78 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 15:25:44 +0200 Subject: [PATCH 252/449] chore(deps): bump hono from 4.12.12 to 4.12.14 in /integration-test/src/jvmTest/typescript (#707) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [hono](https://github.com/honojs/hono) from 4.12.12 to 4.12.14.
Release notes

Sourced from hono's releases.

v4.12.14

Security fixes

This release includes fixes for the following security issues:

Improper handling of JSX attribute names in hono/jsx SSR

Affects: hono/jsx. Fixes missing validation of JSX attribute names during server-side rendering, which could allow malformed attribute keys to corrupt the generated HTML output and inject unintended attributes or elements. GHSA-458j-xx4x-4375

Other changes

  • fix(aws-lambda): handle invalid header names in request processing (#4883) fa2c74fe

v4.12.13

What's Changed

New Contributors

Full Changelog: https://github.com/honojs/hono/compare/v4.12.12...v4.12.13

Commits
  • cf2d2b7 4.12.14
  • 66daa2e Merge commit from fork
  • fa2c74f fix(aws-lambda): handle invalid header names in request processing (#4883)
  • 3779927 4.12.13
  • faa6c46 feat(cache): add onCacheNotAvailable option (#4876)
  • f23e97b feat(trailing-slash): add skip option (#4862)
  • 1aa32fb fix(types): infer response type from last handler in app.on 9- and 10-handler...
  • See full diff in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=hono&package-manager=npm_and_yarn&previous-version=4.12.12&new-version=4.12.14)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/modelcontextprotocol/kotlin-sdk/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- integration-test/src/jvmTest/typescript/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index a246efa..f1f56f4 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -1197,9 +1197,9 @@ } }, "node_modules/hono": { - "version": "4.12.12", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.12.tgz", - "integrity": "sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q==", + "version": "4.12.14", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.14.tgz", + "integrity": "sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==", "license": "MIT", "engines": { "node": ">=16.9.0" From 74cda2e7420f77cf3273f61cdc821d69400d2d70 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 14:46:41 +0200 Subject: [PATCH 253/449] chore(deps): bump com.anthropic:anthropic-java from 2.25.0 to 2.26.0 in /samples/kotlin-mcp-client in the other-dependencies group across 1 directory (#709) Bumps the other-dependencies group with 1 update in the /samples/kotlin-mcp-client directory: [com.anthropic:anthropic-java](https://github.com/anthropics/anthropic-sdk-java). Updates `com.anthropic:anthropic-java` from 2.25.0 to 2.26.0
Release notes

Sourced from com.anthropic:anthropic-java's releases.

v2.26.0

2.26.0 (2026-04-16)

Full Changelog: v2.25.0...v2.26.0

Features

  • api: add claude-opus-4-7, token budgets and user_profiles (a240f4f)

Chores

  • ci: remove release-doctor workflow (5d22f5b)
Changelog

Sourced from com.anthropic:anthropic-java's changelog.

2.26.0 (2026-04-16)

Full Changelog: v2.25.0...v2.26.0

Features

  • api: add claude-opus-4-7, token budgets and user_profiles (a240f4f)

Chores

  • ci: remove release-doctor workflow (5d22f5b)
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=com.anthropic:anthropic-java&package-manager=gradle&previous-version=2.25.0&new-version=2.26.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-client/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index 695b94d..5de32f0 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -anthropic = "2.25.0" +anthropic = "2.26.0" kotlin = "2.2.21" ktor = "3.3.3" mcp-kotlin = "0.11.1" From 1d21fa85af079dacdc69a521d4e87910c0ccedcf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 21:27:27 +0000 Subject: [PATCH 254/449] chore(deps): bump actions/upload-pages-artifact from 4 to 5 in the all-actions group across 1 directory (#711) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the all-actions group with 1 update in the / directory: [actions/upload-pages-artifact](https://github.com/actions/upload-pages-artifact). Updates `actions/upload-pages-artifact` from 4 to 5
Release notes

Sourced from actions/upload-pages-artifact's releases.

v5.0.0

Changelog

See details of all code changes since previous release.

Commits

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/apidocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/apidocs.yaml b/.github/workflows/apidocs.yaml index 76f4434..995f7d8 100644 --- a/.github/workflows/apidocs.yaml +++ b/.github/workflows/apidocs.yaml @@ -41,7 +41,7 @@ jobs: ./gradlew knit docs:classes docs:knitCheck dokkaGenerate - name: Upload artifact - uses: actions/upload-pages-artifact@v4 + uses: actions/upload-pages-artifact@v5 with: path: build/dokka/html From f6ad3322284c78b74ed6a24d7743aaac7b7d9550 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 21:55:25 +0000 Subject: [PATCH 255/449] chore(deps): bump typescript from 6.0.2 to 6.0.3 in /integration-test/src/jvmTest/typescript in the all-dependencies group across 1 directory (#712) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the all-dependencies group with 1 update in the /integration-test/src/jvmTest/typescript directory: [typescript](https://github.com/microsoft/TypeScript). Updates `typescript` from 6.0.2 to 6.0.3
Release notes

Sourced from typescript's releases.

TypeScript 6.0.3

For release notes, check out the release announcement blog post.

Downloads are available on:

Commits
  • 050880c Bump version to 6.0.3 and LKG
  • eeae9dd 🤖 Pick PR #63401 (Also check package name validity in...) into release-6.0 (#...
  • ad1c695 🤖 Pick PR #63368 (Harden ATA package name filtering) into release-6.0 (#63372)
  • 0725fb4 🤖 Pick PR #63310 (Mark class property initializers as...) into release-6.0 (#...
  • See full diff in compare view

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- integration-test/src/jvmTest/typescript/package-lock.json | 8 ++++---- integration-test/src/jvmTest/typescript/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index f1f56f4..34e6bf4 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -18,7 +18,7 @@ "@types/node": "^25.6.0", "cors": "^2.8.6", "tsx": "^4.21.0", - "typescript": "^6.0.2" + "typescript": "^6.0.3" } }, "node_modules/@esbuild/aix-ppc64": { @@ -1737,9 +1737,9 @@ } }, "node_modules/typescript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", - "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", "bin": { diff --git a/integration-test/src/jvmTest/typescript/package.json b/integration-test/src/jvmTest/typescript/package.json index e38cca6..030c79c 100644 --- a/integration-test/src/jvmTest/typescript/package.json +++ b/integration-test/src/jvmTest/typescript/package.json @@ -19,6 +19,6 @@ "@types/node": "^25.6.0", "cors": "^2.8.6", "tsx": "^4.21.0", - "typescript": "^6.0.2" + "typescript": "^6.0.3" } } From bf8dc6d6b1b476364d779e1a177f905428bf74a2 Mon Sep 17 00:00:00 2001 From: Evan Date: Thu, 23 Apr 2026 10:22:18 -0400 Subject: [PATCH 256/449] feat(core): add extensions field to ClientCapabilities and ServerCapabilities (#678) Add support for MCP extension negotiation during the initialize handshake, as defined in the MCP extensions specification. Clients and servers can now advertise supported extensions via a new `extensions: Map?` field on their respective capabilities objects. ## Motivation and Context Extensions are supported part of the [MCP spec ](https://modelcontextprotocol.io/extensions/overview) and are available in other language libraries e.g. [Golang ](https://github.com/modelcontextprotocol/go-sdk/blob/862d78a265cb1f1b9692ff49cfb8bfd109b6ebc9/mcp/protocol.go#L215). ## How Has This Been Tested? Existing and added unit tests ## Breaking Changes Adding a new field to existing ClientCapabilities and ServerCapabilities fields will break already compiled clients. They will need to recompile on upgrading. ## 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed ## Additional context Co-authored-by: Pavel Gorgulov --- kotlin-sdk-core/api/kotlin-sdk-core.api | 21 ++-- .../kotlin/sdk/types/capabilities.dsl.kt | 26 +++++ .../kotlin/sdk/types/capabilities.kt | 8 ++ .../kotlin/sdk/types/CapabilitiesTest.kt | 97 +++++++++++++++++++ .../sdk/types/dsl/CapabilitiesDslTest.kt | 59 +++++++++++ .../kotlin/sdk/types/dsl/InitializeDslTest.kt | 14 +++ 6 files changed, 217 insertions(+), 8 deletions(-) diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index 679b68d..b6fe942 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -606,17 +606,19 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CancelledNotificatio public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Companion; public fun ()V - public fun (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;)V + public synthetic fun (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lkotlinx/serialization/json/JsonObject; public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots; public final fun component3 ()Lkotlinx/serialization/json/JsonObject; public final fun component4 ()Lkotlinx/serialization/json/JsonObject; - public final fun copy (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities; + public final fun component5 ()Ljava/util/Map; + public final fun copy (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities; public fun equals (Ljava/lang/Object;)Z public final fun getElicitation ()Lkotlinx/serialization/json/JsonObject; public final fun getExperimental ()Lkotlinx/serialization/json/JsonObject; + public final fun getExtensions ()Ljava/util/Map; public final fun getRoots ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots; public final fun getSampling ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I @@ -676,6 +678,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilitiesBu public final fun elicitation (Lkotlinx/serialization/json/JsonObject;)V public final fun experimental (Lkotlin/jvm/functions/Function1;)V public final fun experimental (Lkotlinx/serialization/json/JsonObject;)V + public final fun extensions (Ljava/util/Map;)V public final fun roots (Ljava/lang/Boolean;)V public static synthetic fun roots$default (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilitiesBuilder;Ljava/lang/Boolean;ILjava/lang/Object;)V public final fun sampling (Lkotlin/jvm/functions/Function1;)V @@ -4245,19 +4248,21 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/Sampling_dslKt { public final class io/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Companion; public fun ()V - public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools; public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources; public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts; public final fun component4 ()Lkotlinx/serialization/json/JsonObject; public final fun component5 ()Lkotlinx/serialization/json/JsonObject; public final fun component6 ()Lkotlinx/serialization/json/JsonObject; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities; + public final fun component7 ()Ljava/util/Map; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities; public fun equals (Ljava/lang/Object;)Z public final fun getCompletions ()Lkotlinx/serialization/json/JsonObject; public final fun getExperimental ()Lkotlinx/serialization/json/JsonObject; + public final fun getExtensions ()Ljava/util/Map; public final fun getLogging ()Lkotlinx/serialization/json/JsonObject; public final fun getPrompts ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts; public final fun getResources ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources; diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.dsl.kt index 146eabf..d936381 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.dsl.kt @@ -39,6 +39,7 @@ public class ClientCapabilitiesBuilder @PublishedApi internal constructor() { private var sampling: JsonObject? = null private var roots: ClientCapabilities.Roots? = null private var elicitation: JsonObject? = null + private var extensions: Map? = null private var experimental: JsonObject? = null /** @@ -132,6 +133,30 @@ public class ClientCapabilitiesBuilder @PublishedApi internal constructor() { */ public fun elicitation(block: JsonObjectBuilder.() -> Unit): Unit = elicitation(buildJsonObject(block)) + /** + * Defines extensions that the client supports. + * + * Extension identifiers use the format `{vendor-prefix}/{extension-name}`, + * e.g., `"io.modelcontextprotocol/ui"`. Each value is an extension-specific + * settings object; an empty [JsonObject] indicates no settings. + * + * Example: + * ```kotlin + * capabilities { + * extensions(mapOf( + * "io.modelcontextprotocol/ui" to buildJsonObject { + * put("mimeTypes", JsonArray(listOf(JsonPrimitive("text/html")))) + * } + * )) + * } + * ``` + * + * @param value The map of extension identifiers to their settings + */ + public fun extensions(value: Map) { + this.extensions = value + } + /** * Defines experimental, non-standard capabilities that the client supports. * @@ -177,6 +202,7 @@ public class ClientCapabilitiesBuilder @PublishedApi internal constructor() { sampling = sampling, roots = roots, elicitation = elicitation, + extensions = extensions, experimental = experimental, ) } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.kt index fae24c3..14248fa 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.kt @@ -45,6 +45,9 @@ public data class Implementation( * @property elicitation Present if the client supports elicitation from the server. * @property experimental Experimental, non-standard capabilities that the client supports. * Keys are capability names, values are capability-specific configuration objects. + * @property extensions Optional extensions that the client supports. + * Keys are extension identifiers (e.g., `"io.modelcontextprotocol/ui"`), + * values are extension-specific settings objects. */ @Serializable public data class ClientCapabilities( @@ -52,6 +55,7 @@ public data class ClientCapabilities( public val roots: Roots? = null, public val elicitation: JsonObject? = null, public val experimental: JsonObject? = null, + public val extensions: Map? = null, ) { /** @@ -93,6 +97,9 @@ public data class ClientCapabilities( * @property completions Present if the server supports argument autocompletion suggestions. * Keys are capability names, values are capability-specific configuration objects. * @property experimental Experimental, non-standard capabilities that the server supports. + * @property extensions Optional extensions that the server supports. + * Keys are extension identifiers (e.g., `"io.modelcontextprotocol/ui"`), + * values are extension-specific settings objects. */ @Serializable public data class ServerCapabilities( @@ -102,6 +109,7 @@ public data class ServerCapabilities( val logging: JsonObject? = null, val completions: JsonObject? = null, val experimental: JsonObject? = null, + val extensions: Map? = null, ) { /** diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CapabilitiesTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CapabilitiesTest.kt index 33d5653..171c249 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CapabilitiesTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CapabilitiesTest.kt @@ -182,6 +182,53 @@ class CapabilitiesTest { ) } + @Test + fun `should serialize ClientCapabilities with extensions`() { + val extensions = mapOf( + "io.modelcontextprotocol/ui" to buildJsonObject { + put("mimeTypes", buildJsonObject { put("text/html", true) }) + }, + ) + val capabilities = ClientCapabilities( + extensions = extensions, + ) + verifySerialization( + capabilities, + McpJson, + """ + { + "extensions": { + "io.modelcontextprotocol/ui": { + "mimeTypes": { + "text/html": true + } + } + } + } + """.trimIndent(), + ) + } + + @Test + fun `should serialize ClientCapabilities with empty extensions settings`() { + val capabilities = ClientCapabilities( + extensions = mapOf( + "io.modelcontextprotocol/ui" to EmptyJsonObject, + ), + ) + verifySerialization( + capabilities, + McpJson, + """ + { + "extensions": { + "io.modelcontextprotocol/ui": {} + } + } + """.trimIndent(), + ) + } + @Test fun `should serialize ClientCapabilities with experimental`() { val experimental = buildJsonObject { @@ -215,11 +262,15 @@ class CapabilitiesTest { val experimental = buildJsonObject { put("feature1", buildJsonObject { put("enabled", true) }) } + val extensions = mapOf( + "io.modelcontextprotocol/ui" to EmptyJsonObject, + ) val capabilities = ClientCapabilities( sampling = ClientCapabilities.sampling, roots = ClientCapabilities.Roots(listChanged = true), elicitation = ClientCapabilities.elicitation, experimental = experimental, + extensions = extensions, ) verifySerialization( capabilities, @@ -235,6 +286,9 @@ class CapabilitiesTest { "feature1": { "enabled": true } + }, + "extensions": { + "io.modelcontextprotocol/ui": {} } } """.trimIndent(), @@ -270,6 +324,7 @@ class CapabilitiesTest { assertNull(capabilities.roots) assertNull(capabilities.elicitation) assertNull(capabilities.experimental) + assertNull(capabilities.extensions) } @Test @@ -278,6 +333,11 @@ class CapabilitiesTest { sampling = ClientCapabilities.sampling, roots = ClientCapabilities.Roots(listChanged = false), elicitation = ClientCapabilities.elicitation, + extensions = mapOf( + "io.modelcontextprotocol/ui" to buildJsonObject { + put("mimeTypes", buildJsonObject { put("text/html", true) }) + }, + ), ) verifySerializationRoundTrip(original, McpJson) @@ -466,6 +526,32 @@ class CapabilitiesTest { ) } + @Test + fun `should serialize ServerCapabilities with extensions`() { + val capabilities = ServerCapabilities( + extensions = mapOf( + "io.modelcontextprotocol/ui" to EmptyJsonObject, + "com.example/custom" to buildJsonObject { + put("setting", "value") + }, + ), + ) + verifySerialization( + capabilities, + McpJson, + """ + { + "extensions": { + "io.modelcontextprotocol/ui": {}, + "com.example/custom": { + "setting": "value" + } + } + } + """.trimIndent(), + ) + } + @Test fun `should serialize ServerCapabilities with experimental`() { val experimental = buildJsonObject { @@ -499,6 +585,9 @@ class CapabilitiesTest { val experimental = buildJsonObject { put("feature1", buildJsonObject { put("enabled", true) }) } + val extensions = mapOf( + "io.modelcontextprotocol/ui" to EmptyJsonObject, + ) val capabilities = ServerCapabilities( tools = ServerCapabilities.Tools(listChanged = true), resources = ServerCapabilities.Resources( @@ -509,6 +598,7 @@ class CapabilitiesTest { logging = ServerCapabilities.Logging, completions = ServerCapabilities.Completions, experimental = experimental, + extensions = extensions, ) verifySerialization( capabilities, @@ -531,6 +621,9 @@ class CapabilitiesTest { "feature1": { "enabled": true } + }, + "extensions": { + "io.modelcontextprotocol/ui": {} } } """.trimIndent(), @@ -578,6 +671,7 @@ class CapabilitiesTest { assertNull(capabilities.logging) assertNull(capabilities.completions) assertNull(capabilities.experimental) + assertNull(capabilities.extensions) } @Test @@ -590,6 +684,9 @@ class CapabilitiesTest { ), prompts = ServerCapabilities.Prompts(listChanged = true), logging = ServerCapabilities.Logging, + extensions = mapOf( + "io.modelcontextprotocol/ui" to EmptyJsonObject, + ), ) verifySerializationRoundTrip(original, McpJson) diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CapabilitiesDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CapabilitiesDslTest.kt index e4a949f..53dd5c7 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CapabilitiesDslTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CapabilitiesDslTest.kt @@ -4,7 +4,9 @@ import io.kotest.matchers.nulls.shouldBeNull import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject import io.modelcontextprotocol.kotlin.sdk.types.buildInitializeRequest +import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.double import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.put @@ -31,6 +33,7 @@ class CapabilitiesDslTest { roots.shouldBeNull() elicitation.shouldBeNull() experimental.shouldBeNull() + extensions.shouldBeNull() } } @@ -56,6 +59,13 @@ class CapabilitiesDslTest { put("beta", false) put("maxConcurrency", 10) } + extensions( + mapOf( + "io.modelcontextprotocol/ui" to buildJsonObject { + put("mimeTypes", "text/html") + }, + ), + ) } info("Test", "1.0") } @@ -80,6 +90,11 @@ class CapabilitiesDslTest { get("beta")?.jsonPrimitive?.content shouldBe "false" get("maxConcurrency")?.jsonPrimitive?.content shouldBe "10" } + extensions shouldNotBeNull { + get("io.modelcontextprotocol/ui") shouldNotBeNull { + get("mimeTypes")?.jsonPrimitive?.content shouldBe "text/html" + } + } } } @@ -110,6 +125,50 @@ class CapabilitiesDslTest { requestNull.params.capabilities.roots?.listChanged.shouldBeNull() } + @Test + fun `capabilities should build with extensions containing empty settings`() { + val request = buildInitializeRequest { + protocolVersion = "2024-11-05" + capabilities { + extensions( + mapOf( + "io.modelcontextprotocol/ui" to EmptyJsonObject, + "com.example/custom" to EmptyJsonObject, + ), + ) + } + info("Test", "1.0") + } + + request.params.capabilities.shouldNotBeNull { + extensions shouldNotBeNull { + size shouldBe 2 + get("io.modelcontextprotocol/ui") shouldBe EmptyJsonObject + get("com.example/custom") shouldBe EmptyJsonObject + } + } + } + + @Test + fun `capabilities should overwrite extensions when set multiple times`() { + val request = buildInitializeRequest { + protocolVersion = "2024-11-05" + capabilities { + extensions(mapOf("io.modelcontextprotocol/ui" to EmptyJsonObject)) + extensions(mapOf("com.example/custom" to EmptyJsonObject)) // Should overwrite + } + info("Test", "1.0") + } + + request.params.capabilities.shouldNotBeNull { + extensions shouldNotBeNull { + size shouldBe 1 + get("com.example/custom") shouldBe EmptyJsonObject + get("io.modelcontextprotocol/ui").shouldBeNull() + } + } + } + @Test fun `capabilities should overwrite when same field set multiple times`() { val request = buildInitializeRequest { diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/InitializeDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/InitializeDslTest.kt index cb35115..aa53cce 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/InitializeDslTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/InitializeDslTest.kt @@ -5,6 +5,7 @@ import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject import io.modelcontextprotocol.kotlin.sdk.types.Implementation import io.modelcontextprotocol.kotlin.sdk.types.buildInitializeRequest import kotlinx.serialization.json.buildJsonObject @@ -30,6 +31,11 @@ class InitializeDslTest { experimental { put("custom", true) } + extensions( + mapOf( + "io.modelcontextprotocol/ui" to EmptyJsonObject, + ), + ) } info( name = "TestClient", @@ -45,6 +51,9 @@ class InitializeDslTest { roots?.listChanged shouldBe true elicitation?.get("mode")?.jsonPrimitive?.content shouldBe "interactive" experimental?.get("custom")?.jsonPrimitive?.content shouldBe "true" + extensions shouldNotBeNull { + get("io.modelcontextprotocol/ui") shouldBe EmptyJsonObject + } } request.params.clientInfo.shouldNotBeNull { name shouldBe "TestClient" @@ -74,6 +83,9 @@ class InitializeDslTest { val samplingObj = buildJsonObject { put("key", "value") } val elicitationObj = buildJsonObject { put("key", "value") } val experimentalObj = buildJsonObject { put("key", "value") } + val extensionsMap = mapOf( + "io.modelcontextprotocol/ui" to buildJsonObject { put("key", "value") }, + ) val request = buildInitializeRequest { protocolVersion = "1.0" @@ -81,6 +93,7 @@ class InitializeDslTest { sampling(samplingObj) elicitation(elicitationObj) experimental(experimentalObj) + extensions(extensionsMap) } info("Test", "1.0") } @@ -89,6 +102,7 @@ class InitializeDslTest { sampling shouldBe samplingObj elicitation shouldBe elicitationObj experimental shouldBe experimentalObj + extensions shouldBe extensionsMap } } From c946dfc3c261fd6334afe5d538e8327f2da00a0f Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Mon, 27 Apr 2026 15:07:03 +0200 Subject: [PATCH 257/449] sample: kotlinlang mcp server (#713) add new sample: kotlinlang mcp server with 2 tools - search - get_page ## How Has This Been Tested? inspector/unit ## Breaking Changes none ## 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- samples/README.md | 8 + samples/kotlinlang-mcp-server/Dockerfile | 14 + samples/kotlinlang-mcp-server/README.md | 105 ++++++++ .../kotlinlang-mcp-server/build.gradle.kts | 42 +++ .../kotlinlang-mcp-server/gradle.properties | 4 + .../gradle/libs.versions.toml | 31 +++ .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 48966 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 + samples/kotlinlang-mcp-server/gradlew | 248 ++++++++++++++++++ samples/kotlinlang-mcp-server/gradlew.bat | 93 +++++++ .../kotlinlang-mcp-server/settings.gradle.kts | 25 ++ .../kotlin/org/kotlinlang/mcp/Application.kt | 28 ++ .../org/kotlinlang/mcp/KotlinlangServer.kt | 118 +++++++++ .../kotlinlang/mcp/algolia/AlgoliaClient.kt | 38 +++ .../kotlinlang/mcp/algolia/AlgoliaModels.kt | 38 +++ .../org/kotlinlang/mcp/cache/TtlCache.kt | 71 +++++ .../org/kotlinlang/mcp/config/ServerConfig.kt | 18 ++ .../org/kotlinlang/mcp/content/PageFetcher.kt | 11 + .../org/kotlinlang/mcp/content/UrlMapper.kt | 20 ++ .../kotlinlang/mcp/tools/GetKotlinlangPage.kt | 55 ++++ .../kotlinlang/mcp/tools/SearchKotlinlang.kt | 62 +++++ .../src/main/resources/application.conf | 17 ++ .../src/main/resources/logback.xml | 15 ++ .../kotlinlang/mcp/KotlinlangServerTest.kt | 62 +++++ .../mcp/algolia/AlgoliaClientTest.kt | 166 ++++++++++++ .../mcp/algolia/AlgoliaModelsTest.kt | 126 +++++++++ .../org/kotlinlang/mcp/cache/TtlCacheTest.kt | 144 ++++++++++ .../kotlinlang/mcp/config/ServerConfigTest.kt | 35 +++ .../kotlinlang/mcp/content/PageFetcherTest.kt | 87 ++++++ .../kotlinlang/mcp/content/UrlMapperTest.kt | 91 +++++++ .../mcp/tools/GetKotlinlangPageTest.kt | 139 ++++++++++ .../mcp/tools/SearchKotlinlangTest.kt | 231 ++++++++++++++++ 32 files changed, 2149 insertions(+) create mode 100644 samples/kotlinlang-mcp-server/Dockerfile create mode 100644 samples/kotlinlang-mcp-server/README.md create mode 100644 samples/kotlinlang-mcp-server/build.gradle.kts create mode 100644 samples/kotlinlang-mcp-server/gradle.properties create mode 100644 samples/kotlinlang-mcp-server/gradle/libs.versions.toml create mode 100644 samples/kotlinlang-mcp-server/gradle/wrapper/gradle-wrapper.jar create mode 100644 samples/kotlinlang-mcp-server/gradle/wrapper/gradle-wrapper.properties create mode 100755 samples/kotlinlang-mcp-server/gradlew create mode 100644 samples/kotlinlang-mcp-server/gradlew.bat create mode 100644 samples/kotlinlang-mcp-server/settings.gradle.kts create mode 100644 samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/Application.kt create mode 100644 samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/KotlinlangServer.kt create mode 100644 samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/algolia/AlgoliaClient.kt create mode 100644 samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/algolia/AlgoliaModels.kt create mode 100644 samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/cache/TtlCache.kt create mode 100644 samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/config/ServerConfig.kt create mode 100644 samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/content/PageFetcher.kt create mode 100644 samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/content/UrlMapper.kt create mode 100644 samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/tools/GetKotlinlangPage.kt create mode 100644 samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/tools/SearchKotlinlang.kt create mode 100644 samples/kotlinlang-mcp-server/src/main/resources/application.conf create mode 100644 samples/kotlinlang-mcp-server/src/main/resources/logback.xml create mode 100644 samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/KotlinlangServerTest.kt create mode 100644 samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/algolia/AlgoliaClientTest.kt create mode 100644 samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/algolia/AlgoliaModelsTest.kt create mode 100644 samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/cache/TtlCacheTest.kt create mode 100644 samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/config/ServerConfigTest.kt create mode 100644 samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/content/PageFetcherTest.kt create mode 100644 samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/content/UrlMapperTest.kt create mode 100644 samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/tools/GetKotlinlangPageTest.kt create mode 100644 samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/tools/SearchKotlinlangTest.kt diff --git a/samples/README.md b/samples/README.md index 9789378..f831a74 100644 --- a/samples/README.md +++ b/samples/README.md @@ -9,6 +9,7 @@ For background on the protocol itself, see the [MCP documentation](https://model | Sample | Type | Transport | MCP Features | |--------------------------------------------------------|-------------------|-----------------|------------------------------------| | [simple-streamable-server](./simple-streamable-server) | Server | Streamable HTTP | Tools, Resources, Prompts, Logging | +| [kotlinlang-mcp-server](./kotlinlang-mcp-server) | Server | Streamable HTTP | Tools | | [kotlin-mcp-server](./kotlin-mcp-server) | Server | STDIO, SSE | Tools, Resources, Prompts | | [weather-stdio-server](./weather-stdio-server) | Server | STDIO | Tools | | [kotlin-mcp-client](./kotlin-mcp-client) | Client | STDIO | Tool discovery & invocation | @@ -30,6 +31,13 @@ A minimal Streamable HTTP server with optional Bearer token authentication. Demo (`greet`, `multi-greet`), a prompt template, a resource, and server-to-client logging notifications. [Read more →](./simple-streamable-server) +### Kotlinlang MCP Server + +A Streamable HTTP server that exposes the official Kotlin documentation (kotlinlang.org) to LLM +clients — full-text search via Algolia and page retrieval in markdown. Demonstrates wrapping a real +external API in an MCP server with in-memory caching. +[Read more →](./kotlinlang-mcp-server) + ### Kotlin MCP Server A multi-transport server supporting STDIO, SSE (plain), and SSE (Ktor plugin). Useful for exploring diff --git a/samples/kotlinlang-mcp-server/Dockerfile b/samples/kotlinlang-mcp-server/Dockerfile new file mode 100644 index 0000000..ada7e56 --- /dev/null +++ b/samples/kotlinlang-mcp-server/Dockerfile @@ -0,0 +1,14 @@ +FROM gradle:jdk21-alpine AS build +WORKDIR /project +COPY gradle/ gradle/ +COPY gradlew settings.gradle.kts build.gradle.kts ./ +COPY src/ src/ +RUN ./gradlew shadowJar --no-daemon + +FROM eclipse-temurin:21-jre-alpine +RUN addgroup -S appgroup && adduser -S appuser -G appgroup +WORKDIR /app +COPY --from=build /project/build/libs/*-all.jar app.jar +USER appuser +EXPOSE 8080 +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/samples/kotlinlang-mcp-server/README.md b/samples/kotlinlang-mcp-server/README.md new file mode 100644 index 0000000..df0dd3e --- /dev/null +++ b/samples/kotlinlang-mcp-server/README.md @@ -0,0 +1,105 @@ +# Kotlinlang MCP Server + +A Streamable HTTP MCP server that exposes the official +[Kotlin documentation](https://kotlinlang.org/docs/) to LLM clients — full-text search via +[Algolia](https://www.algolia.com/) plus page retrieval in markdown through kotlinlang.org's +`_llms` endpoints. + +## Overview + +This sample demonstrates an MCP server that wraps a real external documentation source. It uses the +recommended Streamable HTTP transport, supports multiple concurrent client sessions, and caches +responses in memory to reduce upstream load. The server is intentionally read-only — it exposes two +tools, no prompts, no resources. + +## Prerequisites + +- JDK 21+ +- Algolia credentials for `kotlinlang.org` (exported as environment variables, see + [Configuration](#configuration)) + +## Build & Run + +```shell +export ALGOLIA_APP_ID=... +export ALGOLIA_API_KEY=... +export ALGOLIA_INDEX_NAME=... + +./gradlew run +``` + +The server starts on `http://localhost:8080/mcp` by default. + +Connect with the [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector): + +```shell +npx @modelcontextprotocol/inspector +``` + +In the Inspector UI, select **Streamable HTTP** transport and enter `http://localhost:8080/mcp`. + +## MCP Capabilities + +### Tools + +| Name | Description | +|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------| +| `search_kotlinlang` | Full-text search across Kotlin documentation. Returns up to 5 results filtered to `/docs/` pages. Responses cached for 10 minutes. | +| `get_kotlinlang_page` | Fetches a documentation page as markdown via kotlinlang.org's `_llms` endpoint. Accepts a path relative to `/docs/`. Cached for 1 hour. | + +Example paths accepted by `get_kotlinlang_page`: `coroutines-overview`, +`multiplatform/compose-multiplatform-and-jetpack-compose`. Leading/trailing slashes and an optional +`.html` suffix are normalized before resolution. + +## Configuration + +| Variable | Required | Default | Description | +|----------------------|----------|-----------|--------------------------| +| `ALGOLIA_APP_ID` | yes | — | Algolia application ID | +| `ALGOLIA_API_KEY` | yes | — | Algolia search API key | +| `ALGOLIA_INDEX_NAME` | yes | — | Algolia index name | +| `SERVER_PORT` | no | `8080` | HTTP server port | +| `SERVER_HOST` | no | `0.0.0.0` | HTTP server bind address | + +The server does not provide fallback values for `ALGOLIA_*`. Startup fails if any of those +variables are missing. + +## Docker + +Multi-stage build with Alpine-based images. Runs as a non-root user. + +```shell +docker build -t kotlinlang-mcp-server . + +docker run \ + -p 8080:8080 \ + -e ALGOLIA_APP_ID=... \ + -e ALGOLIA_API_KEY=... \ + -e ALGOLIA_INDEX_NAME=... \ + kotlinlang-mcp-server +``` + +## Connecting an MCP client + +Any MCP client that supports Streamable HTTP transport can connect: + +```json +{ + "mcpServers": { + "kotlinlang": { + "url": "http://localhost:8080/mcp" + } + } +} +``` + +## Limitations + +This sample is intended **for demonstration only**. Before production use, consider: + +- **In-memory cache only** — cached data is lost on server restart. +- **No authentication or authorization** — run behind a trusted proxy or restrict network access. +- **No rate limiting** on outgoing requests to Algolia and kotlinlang.org. +- **Permissive CORS** (`anyHost()`) — restrict to specific origins for any non-local deployment. +- **Streamable HTTP only** — no STDIO transport for local-only usage. +- **Tools only** — no MCP resources or prompts are exposed. diff --git a/samples/kotlinlang-mcp-server/build.gradle.kts b/samples/kotlinlang-mcp-server/build.gradle.kts new file mode 100644 index 0000000..f46f006 --- /dev/null +++ b/samples/kotlinlang-mcp-server/build.gradle.kts @@ -0,0 +1,42 @@ +plugins { + alias(libs.plugins.kotlin.jvm) + alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.shadow) + application +} + +group = "org.example" +version = "0.1.0" + +application { + mainClass.set("org.kotlinlang.mcp.ApplicationKt") +} + +dependencies { + implementation(platform(libs.ktor.bom)) + implementation(libs.mcp.kotlin.server) + implementation(libs.ktor.server.netty) + implementation(libs.ktor.server.sse) + implementation(libs.ktor.server.content.negotiation) + implementation(libs.ktor.server.cors) + implementation(libs.ktor.client.cio) + implementation(libs.ktor.client.content.negotiation) + implementation(libs.ktor.serialization.kotlinx.json) + implementation(libs.kotlinx.serialization.json) + implementation(libs.kotlinx.coroutines.core) + implementation(libs.logback.classic) + + testImplementation(kotlin("test")) + testImplementation(libs.kotlinx.coroutines.test) + testImplementation(libs.ktor.client.mock) + testImplementation(libs.mcp.kotlin.client) + testImplementation(libs.mcp.kotlin.testing) +} + +tasks.test { + useJUnitPlatform() +} + +kotlin { + jvmToolchain(21) +} diff --git a/samples/kotlinlang-mcp-server/gradle.properties b/samples/kotlinlang-mcp-server/gradle.properties new file mode 100644 index 0000000..24a5976 --- /dev/null +++ b/samples/kotlinlang-mcp-server/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.configuration-cache=true +org.gradle.parallel=true +org.gradle.caching=true + diff --git a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml new file mode 100644 index 0000000..20dff78 --- /dev/null +++ b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml @@ -0,0 +1,31 @@ +[versions] +kotlin = "2.3.20" +ktor = "3.3.3" +mcp-kotlin = "0.11.1" +serialization = "1.10.0" +coroutines = "1.10.2" +logback = "1.5.32" +shadow = "9.4.1" + +[libraries] +ktor-bom = { group = "io.ktor", name = "ktor-bom", version.ref = "ktor" } +ktor-server-netty = { group = "io.ktor", name = "ktor-server-netty" } +ktor-server-sse = { group = "io.ktor", name = "ktor-server-sse" } +ktor-server-content-negotiation = { group = "io.ktor", name = "ktor-server-content-negotiation" } +ktor-server-cors = { group = "io.ktor", name = "ktor-server-cors" } +ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio" } +ktor-client-content-negotiation = { group = "io.ktor", name = "ktor-client-content-negotiation" } +ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json" } +mcp-kotlin-server = { group = "io.modelcontextprotocol", name = "kotlin-sdk-server", version.ref = "mcp-kotlin" } +mcp-kotlin-client = { group = "io.modelcontextprotocol", name = "kotlin-sdk-client", version.ref = "mcp-kotlin" } +mcp-kotlin-testing = { group = "io.modelcontextprotocol", name = "kotlin-sdk-testing", version.ref = "mcp-kotlin" } +kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "serialization" } +kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutines" } +logback-classic = { group = "ch.qos.logback", name = "logback-classic", version.ref = "logback" } +kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines" } +ktor-client-mock = { group = "io.ktor", name = "ktor-client-mock" } + +[plugins] +kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } +shadow = { id = "com.gradleup.shadow", version.ref = "shadow" } diff --git a/samples/kotlinlang-mcp-server/gradle/wrapper/gradle-wrapper.jar b/samples/kotlinlang-mcp-server/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..d997cfc60f4cff0e7451d19d49a82fa986695d07 GIT binary patch literal 48966 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

`Lem|V$A>eR0uN8Y&sdjtu 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

1bvi{DS7E@q1{$_8->K{_OWv2 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 literal 0 HcmV?d00001 diff --git a/samples/kotlinlang-mcp-server/gradle/wrapper/gradle-wrapper.properties b/samples/kotlinlang-mcp-server/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..c61a118 --- /dev/null +++ b/samples/kotlinlang-mcp-server/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/samples/kotlinlang-mcp-server/gradlew b/samples/kotlinlang-mcp-server/gradlew new file mode 100755 index 0000000..739907d --- /dev/null +++ b/samples/kotlinlang-mcp-server/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/samples/kotlinlang-mcp-server/gradlew.bat b/samples/kotlinlang-mcp-server/gradlew.bat new file mode 100644 index 0000000..c4bdd3a --- /dev/null +++ b/samples/kotlinlang-mcp-server/gradlew.bat @@ -0,0 +1,93 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/samples/kotlinlang-mcp-server/settings.gradle.kts b/samples/kotlinlang-mcp-server/settings.gradle.kts new file mode 100644 index 0000000..bf867e0 --- /dev/null +++ b/samples/kotlinlang-mcp-server/settings.gradle.kts @@ -0,0 +1,25 @@ +rootProject.name = "kotlinlang-mcp-server" + +plugins { + // Apply the foojay-resolver plugin to allow automatic download of JDKs + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" +} + +dependencyResolutionManagement { + repositories { + mavenLocal() + mavenCentral() + } + + versionCatalogs { + create("libs") { + val mcpKotlinVersion = providers.gradleProperty( + "mcp.kotlin.overrideVersion", + ).orNull + if (mcpKotlinVersion != null) { + logger.lifecycle("Using the override version $mcpKotlinVersion of MCP Kotlin SDK") + version("mcp-kotlin", mcpKotlinVersion) + } + } + } +} diff --git a/samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/Application.kt b/samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/Application.kt new file mode 100644 index 0000000..e50b1ad --- /dev/null +++ b/samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/Application.kt @@ -0,0 +1,28 @@ +package org.kotlinlang.mcp + +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.netty.* +import io.ktor.server.plugins.cors.routing.* +import io.modelcontextprotocol.kotlin.sdk.server.mcpStreamableHttp +import org.kotlinlang.mcp.config.toServerConfig + +fun main(args: Array) = EngineMain.main(args) + +fun Application.module() { + val config = environment.config.toServerConfig() + val kotlinlangServer = KotlinlangServer(config) + + monitor.subscribe(ApplicationStopped) { + kotlinlangServer.close() + } + + install(CORS) { + anyHost() + allowHeader(HttpHeaders.ContentType) + } + + mcpStreamableHttp { + kotlinlangServer.server + } +} diff --git a/samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/KotlinlangServer.kt b/samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/KotlinlangServer.kt new file mode 100644 index 0000000..0814342 --- /dev/null +++ b/samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/KotlinlangServer.kt @@ -0,0 +1,118 @@ +package org.kotlinlang.mcp + +import io.ktor.client.* +import io.ktor.client.engine.cio.* +import io.ktor.client.plugins.* +import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.serialization.kotlinx.json.* +import io.modelcontextprotocol.kotlin.sdk.server.Server +import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions +import io.modelcontextprotocol.kotlin.sdk.types.* +import kotlinx.serialization.json.* +import org.kotlinlang.mcp.algolia.AlgoliaClient +import org.kotlinlang.mcp.cache.TtlCache +import org.kotlinlang.mcp.config.ServerConfig +import org.kotlinlang.mcp.content.PageFetcher +import org.kotlinlang.mcp.tools.GetKotlinlangPage +import org.kotlinlang.mcp.tools.SearchKotlinlang +import org.slf4j.LoggerFactory +import java.io.Closeable +import kotlin.time.Duration.Companion.hours +import kotlin.time.Duration.Companion.minutes + +internal class KotlinlangServer(config: ServerConfig) : Closeable { + + private val logger = LoggerFactory.getLogger(KotlinlangServer::class.java) + + private val httpClient = HttpClient(CIO) { + install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true }) } + install(HttpTimeout) { requestTimeoutMillis = 10_000 } + expectSuccess = true + } + + private val searchCache = TtlCache(10.minutes) + private val pageCache = TtlCache(1.hours) + + private val algoliaClient = AlgoliaClient(config, httpClient) + private val pageFetcher = PageFetcher(httpClient) + private val searchTool = SearchKotlinlang(algoliaClient, searchCache) + private val pageTool = GetKotlinlangPage(pageFetcher, pageCache) + + val server: Server = Server( + serverInfo = Implementation(name = "kotlinlang-mcp-server", version = "1.0.0"), + options = ServerOptions( + capabilities = ServerCapabilities( + tools = ServerCapabilities.Tools(listChanged = false), + ), + ), + ) { + addTool( + name = "search_kotlinlang", + description = "Search across Kotlin documentation on kotlinlang.org to find relevant pages, " + + "code examples, API references, and guides. Use this tool when you need to answer questions " + + "about Kotlin language features, standard library, coroutines, multiplatform, tooling, or any " + + "other topic covered in the official Kotlin documentation. Returns up to 5 results with page " + + "titles, paths, and text snippets. To get the full content of a specific page, use the " + + "get_kotlinlang_page tool with the page path from the search results.", + inputSchema = ToolSchema( + properties = buildJsonObject { + putJsonObject("query") { + put("type", "string") + put( + "description", + "A search query to find relevant Kotlin documentation pages " + + "(e.g. 'coroutines', 'sealed classes', 'multiplatform setup')", + ) + } + }, + required = listOf("query"), + ), + toolAnnotations = ToolAnnotations(readOnlyHint = true, openWorldHint = true), + ) { request -> + val query = request.arguments?.get("query")?.jsonPrimitive?.content + ?: return@addTool CallToolResult( + content = listOf(TextContent(text = "Missing required argument: query")), + isError = true, + ) + logger.info("search_kotlinlang query=\"{}\"", query) + searchTool.handle(query) + } + + addTool( + name = "get_kotlinlang_page", + description = "Retrieve the full content of a specific Kotlin documentation page from " + + "kotlinlang.org in md format. Use this tool when you already know the " + + "page path (e.g., from search results) and need the complete content of that page rather " + + "than just a snippet. If the page is not found, use search_kotlinlang to discover the " + + "correct path.", + inputSchema = ToolSchema( + properties = buildJsonObject { + putJsonObject("path") { + put("type", "string") + put( + "description", + "Page path relative to /docs/, without extension. Use the page paths " + + "returned from search_kotlinlang results " + + "(e.g. 'coroutines-overview', " + + "'multiplatform/compose-multiplatform-and-jetpack-compose')", + ) + } + }, + required = listOf("path"), + ), + toolAnnotations = ToolAnnotations(readOnlyHint = true, openWorldHint = true), + ) { request -> + val path = request.arguments?.get("path")?.jsonPrimitive?.content + ?: return@addTool CallToolResult( + content = listOf(TextContent(text = "Missing required argument: path")), + isError = true, + ) + logger.info("get_kotlinlang_page path=\"{}\"", path) + pageTool.handle(path) + } + } + + override fun close() { + httpClient.close() + } +} diff --git a/samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/algolia/AlgoliaClient.kt b/samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/algolia/AlgoliaClient.kt new file mode 100644 index 0000000..0d0db42 --- /dev/null +++ b/samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/algolia/AlgoliaClient.kt @@ -0,0 +1,38 @@ +package org.kotlinlang.mcp.algolia + +import io.ktor.client.* +import io.ktor.client.call.* +import io.ktor.client.request.* +import io.ktor.http.* +import org.kotlinlang.mcp.config.ServerConfig + +internal class AlgoliaClient( + private val config: ServerConfig, + private val httpClient: HttpClient, +) { + suspend fun search(query: String): AlgoliaSearchResponse { + val response = httpClient.post(buildSearchUrl()) { + header("x-algolia-application-id", config.algoliaAppId) + header("x-algolia-api-key", config.algoliaApiKey) + contentType(ContentType.Application.Json) + setBody( + AlgoliaSearchRequest( + query = query, + hitsPerPage = HITS_PER_PAGE, + attributesToRetrieve = ATTRIBUTES_TO_RETRIEVE, + attributesToSnippet = ATTRIBUTES_TO_SNIPPET, + ) + ) + } + return response.body() + } + + private fun buildSearchUrl(): String = + "https://${config.algoliaAppId}-dsn.algolia.net/1/indexes/${config.algoliaIndexName}/query" + + companion object { + private const val HITS_PER_PAGE = 15 + private val ATTRIBUTES_TO_RETRIEVE = listOf("objectID", "mainTitle", "url", "headings") + private val ATTRIBUTES_TO_SNIPPET = listOf("content:40") + } +} diff --git a/samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/algolia/AlgoliaModels.kt b/samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/algolia/AlgoliaModels.kt new file mode 100644 index 0000000..88c465d --- /dev/null +++ b/samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/algolia/AlgoliaModels.kt @@ -0,0 +1,38 @@ +package org.kotlinlang.mcp.algolia + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +internal data class AlgoliaSearchRequest( + val query: String, + val hitsPerPage: Int, + val attributesToRetrieve: List, + val attributesToSnippet: List, +) + +@Serializable +internal data class AlgoliaSearchResponse( + val hits: List, +) + +@Serializable +internal data class AlgoliaHit( + val objectID: String, + val mainTitle: String? = null, + val url: String, + val headings: String? = null, + @SerialName("_snippetResult") + val snippetResult: SnippetResult? = null, +) + +@Serializable +internal data class SnippetResult( + val content: SnippetValue, +) + +@Serializable +internal data class SnippetValue( + val value: String, + val matchLevel: String, +) diff --git a/samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/cache/TtlCache.kt b/samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/cache/TtlCache.kt new file mode 100644 index 0000000..7ba8de1 --- /dev/null +++ b/samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/cache/TtlCache.kt @@ -0,0 +1,71 @@ +package org.kotlinlang.mcp.cache + +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.ensureActive +import java.util.concurrent.CancellationException +import java.util.concurrent.ConcurrentHashMap +import kotlin.time.Duration +import kotlin.time.TimeMark +import kotlin.time.TimeSource + +internal class TtlCache( + private val ttl: Duration, + private val timeSource: TimeSource = TimeSource.Monotonic, +) { + private class Entry(val value: V, val createdAt: TimeMark) + + private val map = ConcurrentHashMap>() + private val inFlight = ConcurrentHashMap>() + + fun get(key: K): V? { + val entry = map[key] ?: return null + if (entry.createdAt.elapsedNow() >= ttl) { + map.remove(key, entry) + return null + } + return entry.value + } + + fun put(key: K, value: V) { + map[key] = Entry(value, timeSource.markNow()) + evictExpired() + } + + private fun evictExpired() { + map.entries.removeIf { it.value.createdAt.elapsedNow() >= ttl } + } + + suspend fun getOrPut(key: K, loader: suspend () -> V): V { + while (true) { + get(key)?.let { return it } + + val deferred = CompletableDeferred() + val existing = inFlight.putIfAbsent(key, deferred) + if (existing != null) { + try { + return existing.await() + } catch (_: CancellationException) { + currentCoroutineContext().ensureActive() + // The loader's scope was cancelled, not ours — retry + continue + } + } + + try { + val value = loader() + put(key, value) + deferred.complete(value) + return value + } catch (e: CancellationException) { + deferred.cancel() + throw e + } catch (e: Throwable) { + deferred.completeExceptionally(e) + throw e + } finally { + inFlight.remove(key, deferred) + } + } + } +} diff --git a/samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/config/ServerConfig.kt b/samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/config/ServerConfig.kt new file mode 100644 index 0000000..0c61e13 --- /dev/null +++ b/samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/config/ServerConfig.kt @@ -0,0 +1,18 @@ +package org.kotlinlang.mcp.config + +import io.ktor.server.config.* + +internal data class ServerConfig( + val algoliaAppId: String, + val algoliaApiKey: String, + val algoliaIndexName: String, +) { + override fun toString(): String = + "ServerConfig(algoliaAppId=$algoliaAppId, algoliaIndexName=$algoliaIndexName)" +} + +internal fun ApplicationConfig.toServerConfig(): ServerConfig = ServerConfig( + algoliaAppId = property("algolia.appId").getString(), + algoliaApiKey = property("algolia.apiKey").getString(), + algoliaIndexName = property("algolia.indexName").getString(), +) diff --git a/samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/content/PageFetcher.kt b/samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/content/PageFetcher.kt new file mode 100644 index 0000000..a8495b2 --- /dev/null +++ b/samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/content/PageFetcher.kt @@ -0,0 +1,11 @@ +package org.kotlinlang.mcp.content + +import io.ktor.client.* +import io.ktor.client.request.* +import io.ktor.client.statement.* + +internal class PageFetcher(private val httpClient: HttpClient) { + suspend fun fetch(url: String): String { + return httpClient.get(url).bodyAsText() + } +} diff --git a/samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/content/UrlMapper.kt b/samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/content/UrlMapper.kt new file mode 100644 index 0000000..ab0a8ff --- /dev/null +++ b/samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/content/UrlMapper.kt @@ -0,0 +1,20 @@ +package org.kotlinlang.mcp.content + +private const val BASE_URL = "https://kotlinlang.org/docs" + +internal fun normalizePath(path: String): String = path.trim().trim('/').removeSuffix(".html") + +internal fun mapPathToUrl(path: String): String { + val normalized = normalizePath(path) + require(normalized.isNotBlank()) { "Path must not be empty" } + require(".." !in normalized.split('/')) { "Path must not contain '..' segments" } + + val lastSlash = normalized.lastIndexOf('/') + return if (lastSlash == -1) { + "$BASE_URL/_llms/$normalized.txt" + } else { + val dir = normalized.substring(0, lastSlash) + val file = normalized.substring(lastSlash + 1) + "$BASE_URL/$dir/_llms/$file.txt" + } +} diff --git a/samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/tools/GetKotlinlangPage.kt b/samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/tools/GetKotlinlangPage.kt new file mode 100644 index 0000000..d4a5097 --- /dev/null +++ b/samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/tools/GetKotlinlangPage.kt @@ -0,0 +1,55 @@ +package org.kotlinlang.mcp.tools + +import io.ktor.client.plugins.* +import io.ktor.http.* +import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import kotlinx.coroutines.CancellationException +import org.kotlinlang.mcp.cache.TtlCache +import org.kotlinlang.mcp.content.PageFetcher +import org.kotlinlang.mcp.content.mapPathToUrl +import org.kotlinlang.mcp.content.normalizePath +import org.slf4j.LoggerFactory + +internal class GetKotlinlangPage( + private val pageFetcher: PageFetcher, + private val cache: TtlCache, +) { + private val logger = LoggerFactory.getLogger(GetKotlinlangPage::class.java) + suspend fun handle(path: String): CallToolResult { + val normalizedPath = normalizePath(path) + return try { + val content = cache.getOrPut(normalizedPath) { + val url = mapPathToUrl(normalizedPath) + pageFetcher.fetch(url) + } + CallToolResult(content = listOf(TextContent(text = content))) + } catch (e: CancellationException) { + throw e + } catch (e: IllegalArgumentException) { + CallToolResult( + content = listOf(TextContent(text = "Invalid path: ${e.message}")), + isError = true, + ) + } catch (e: ClientRequestException) { + if (e.response.status != HttpStatusCode.NotFound) { + logger.error("Failed to fetch page path=\"{}\"", path, e) + } + val message = if (e.response.status == HttpStatusCode.NotFound) { + "Page not found: $path. Use search_kotlinlang to find the correct page path." + } else { + "Failed to fetch page: ${e.message}" + } + CallToolResult( + content = listOf(TextContent(text = message)), + isError = true, + ) + } catch (e: Exception) { + logger.error("Failed to fetch page path=\"{}\"", path, e) + CallToolResult( + content = listOf(TextContent(text = "Failed to fetch page: ${e.message ?: "unknown error"}")), + isError = true, + ) + } + } +} diff --git a/samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/tools/SearchKotlinlang.kt b/samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/tools/SearchKotlinlang.kt new file mode 100644 index 0000000..886f3f7 --- /dev/null +++ b/samples/kotlinlang-mcp-server/src/main/kotlin/org/kotlinlang/mcp/tools/SearchKotlinlang.kt @@ -0,0 +1,62 @@ +package org.kotlinlang.mcp.tools + +import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import kotlinx.coroutines.CancellationException +import org.kotlinlang.mcp.algolia.AlgoliaClient +import org.kotlinlang.mcp.algolia.AlgoliaHit +import org.kotlinlang.mcp.cache.TtlCache +import org.slf4j.LoggerFactory + +internal class SearchKotlinlang( + private val algoliaClient: AlgoliaClient, + private val cache: TtlCache, +) { + private val logger = LoggerFactory.getLogger(SearchKotlinlang::class.java) + + companion object { + private const val MAX_RESULTS = 5 + private val HTML_TAG_REGEX = Regex("<[^>]+>") + } + + suspend fun handle(query: String): CallToolResult { + return try { + val text = cache.getOrPut(query) { + val response = algoliaClient.search(query) + val filtered = response.hits + .filter { it.url.startsWith("/docs/") } + .take(MAX_RESULTS) + if (filtered.isEmpty()) { + "No results found for: $query" + } else { + formatResults(filtered) + } + } + CallToolResult(content = listOf(TextContent(text = text))) + } catch (e: CancellationException) { + throw e + } catch (e: Exception) { + logger.error("Search failed for query=\"{}\"", query, e) + CallToolResult( + content = listOf(TextContent(text = "Search failed: ${e.message ?: "unknown error"}")), + isError = true, + ) + } + } + + private fun formatResults(hits: List): String = + hits.mapIndexed { index, hit -> + val title = hit.mainTitle ?: "Untitled" + val path = extractPath(hit.url) + val snippetLine = hit.snippetResult?.content?.value + ?.let { "\n ${stripHtml(it)}" } + ?: "" + "${index + 1}. $title [$path]$snippetLine" + }.joinToString("\n\n") + + private fun extractPath(url: String): String = + url.removePrefix("/docs/").removeSuffix(".html") + + private fun stripHtml(text: String): String = + text.replace(HTML_TAG_REGEX, "") +} diff --git a/samples/kotlinlang-mcp-server/src/main/resources/application.conf b/samples/kotlinlang-mcp-server/src/main/resources/application.conf new file mode 100644 index 0000000..4908da5 --- /dev/null +++ b/samples/kotlinlang-mcp-server/src/main/resources/application.conf @@ -0,0 +1,17 @@ +ktor { + deployment { + port = 8080 + port = ${?SERVER_PORT} + host = "0.0.0.0" + host = ${?SERVER_HOST} + } + application { + modules = [ org.kotlinlang.mcp.ApplicationKt.module ] + } +} + +algolia { + appId = ${ALGOLIA_APP_ID} + apiKey = ${ALGOLIA_API_KEY} + indexName = ${ALGOLIA_INDEX_NAME} +} diff --git a/samples/kotlinlang-mcp-server/src/main/resources/logback.xml b/samples/kotlinlang-mcp-server/src/main/resources/logback.xml new file mode 100644 index 0000000..0ffe8a3 --- /dev/null +++ b/samples/kotlinlang-mcp-server/src/main/resources/logback.xml @@ -0,0 +1,15 @@ + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + diff --git a/samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/KotlinlangServerTest.kt b/samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/KotlinlangServerTest.kt new file mode 100644 index 0000000..c6786fb --- /dev/null +++ b/samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/KotlinlangServerTest.kt @@ -0,0 +1,62 @@ +package org.kotlinlang.mcp + +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.client.ClientOptions +import io.modelcontextprotocol.kotlin.sdk.testing.ChannelTransport +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import org.kotlinlang.mcp.config.ServerConfig +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +@OptIn(ExperimentalMcpApi::class) +class KotlinlangServerTest { + + private val testConfig = ServerConfig( + algoliaAppId = "test-app-id", + algoliaApiKey = "test-api-key", + algoliaIndexName = "test-index", + ) + + @Test + fun `server registers both tools with correct schema and annotations`() = runTest { + val kotlinlangServer = KotlinlangServer(testConfig) + kotlinlangServer.use { + val (clientTransport, serverTransport) = ChannelTransport.createLinkedPair() + + val client = Client( + clientInfo = Implementation(name = "test-client", version = "1.0"), + options = ClientOptions(), + ) + + joinAll( + launch { client.connect(clientTransport) }, + launch { kotlinlangServer.server.createSession(serverTransport) }, + ) + + val tools = client.listTools().tools + + assertEquals(2, tools.size) + + val searchTool = tools.find { it.name == "search_kotlinlang" } + val searchProps = searchTool?.inputSchema?.properties + assertTrue(searchProps?.containsKey("query") ?: false) + assertEquals(listOf("query"), searchTool.inputSchema.required) + assertEquals(true, searchTool.annotations?.readOnlyHint) + assertEquals(true, searchTool.annotations?.openWorldHint) + + val pageTool = tools.find { it.name == "get_kotlinlang_page" } + val pageProps = pageTool?.inputSchema?.properties + assertTrue(pageProps?.containsKey("path") ?: false) + assertEquals(listOf("path"), pageTool.inputSchema.required) + assertEquals(true, pageTool.annotations?.readOnlyHint) + assertEquals(true, pageTool.annotations?.openWorldHint) + + client.close() + } + } +} diff --git a/samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/algolia/AlgoliaClientTest.kt b/samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/algolia/AlgoliaClientTest.kt new file mode 100644 index 0000000..4f9dff6 --- /dev/null +++ b/samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/algolia/AlgoliaClientTest.kt @@ -0,0 +1,166 @@ +package org.kotlinlang.mcp.algolia + +import io.ktor.client.* +import io.ktor.client.engine.mock.* +import io.ktor.client.plugins.* +import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.http.* +import io.ktor.serialization.kotlinx.json.* +import kotlinx.coroutines.test.runTest +import kotlinx.serialization.json.Json +import org.kotlinlang.mcp.config.ServerConfig +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertTrue + +class AlgoliaClientTest { + + private val testConfig = ServerConfig( + algoliaAppId = "test-app-id", + algoliaApiKey = "test-api-key", + algoliaIndexName = "test-index", + ) + + private val json = Json { ignoreUnknownKeys = true } + + private fun createClient(mockEngine: MockEngine): AlgoliaClient { + val httpClient = HttpClient(mockEngine) { + install(ContentNegotiation) { json(json) } + expectSuccess = true + } + return AlgoliaClient(testConfig, httpClient) + } + + @Test + fun `search sends correct URL and headers`() = runTest { + val mockEngine = MockEngine { request -> + assertEquals( + "https://test-app-id-dsn.algolia.net/1/indexes/test-index/query", + request.url.toString(), + ) + assertEquals("test-app-id", request.headers["x-algolia-application-id"]) + assertEquals("test-api-key", request.headers["x-algolia-api-key"]) + assertEquals(HttpMethod.Post, request.method) + + respond( + content = """{"hits": []}""", + status = HttpStatusCode.OK, + headers = headersOf(HttpHeaders.ContentType, "application/json"), + ) + } + + val client = createClient(mockEngine) + client.search("coroutines") + } + + @Test + fun `search sends correct request body`() = runTest { + val mockEngine = MockEngine { request -> + val body = json.decodeFromString(request.body.toByteArray().decodeToString()) + assertEquals("coroutines", body.query) + assertEquals(15, body.hitsPerPage) + assertEquals(listOf("objectID", "mainTitle", "url", "headings"), body.attributesToRetrieve) + assertEquals(listOf("content:40"), body.attributesToSnippet) + + respond( + content = """{"hits": []}""", + status = HttpStatusCode.OK, + headers = headersOf(HttpHeaders.ContentType, "application/json"), + ) + } + + val client = createClient(mockEngine) + client.search("coroutines") + } + + @Test + fun `search parses response with hits`() = runTest { + val responseJson = """ + { + "hits": [ + { + "objectID": "abc123", + "mainTitle": "Coroutines overview", + "url": "/docs/coroutines-overview.html", + "headings": "Introduction | First coroutine", + "_snippetResult": { + "content": { + "value": "Kotlin provides coroutines support", + "matchLevel": "full" + } + } + } + ] + } + """.trimIndent() + + val mockEngine = MockEngine { + respond( + content = responseJson, + status = HttpStatusCode.OK, + headers = headersOf(HttpHeaders.ContentType, "application/json"), + ) + } + + val client = createClient(mockEngine) + val result = client.search("coroutines") + + assertEquals(1, result.hits.size) + val hit = result.hits[0] + assertEquals("abc123", hit.objectID) + assertEquals("Coroutines overview", hit.mainTitle) + assertEquals("/docs/coroutines-overview.html", hit.url) + assertEquals("Introduction | First coroutine", hit.headings) + assertEquals("Kotlin provides coroutines support", hit.snippetResult?.content?.value) + assertEquals("full", hit.snippetResult?.content?.matchLevel) + } + + @Test + fun `search returns empty list when no hits`() = runTest { + val mockEngine = MockEngine { + respond( + content = """{"hits": []}""", + status = HttpStatusCode.OK, + headers = headersOf(HttpHeaders.ContentType, "application/json"), + ) + } + + val client = createClient(mockEngine) + val result = client.search("nonexistent") + + assertTrue(result.hits.isEmpty()) + } + + @Test + fun `search throws on 4xx error`() = runTest { + val mockEngine = MockEngine { + respond( + content = """{"message": "Invalid Application-ID or API key"}""", + status = HttpStatusCode.Forbidden, + headers = headersOf(HttpHeaders.ContentType, "application/json"), + ) + } + + val client = createClient(mockEngine) + assertFailsWith { + client.search("coroutines") + } + } + + @Test + fun `search throws on 5xx error`() = runTest { + val mockEngine = MockEngine { + respond( + content = """{"message": "Internal server error"}""", + status = HttpStatusCode.InternalServerError, + headers = headersOf(HttpHeaders.ContentType, "application/json"), + ) + } + + val client = createClient(mockEngine) + assertFailsWith { + client.search("coroutines") + } + } +} diff --git a/samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/algolia/AlgoliaModelsTest.kt b/samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/algolia/AlgoliaModelsTest.kt new file mode 100644 index 0000000..10dbe5b --- /dev/null +++ b/samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/algolia/AlgoliaModelsTest.kt @@ -0,0 +1,126 @@ +package org.kotlinlang.mcp.algolia + +import kotlinx.serialization.json.Json +import kotlin.test.Test +import kotlin.test.assertEquals + +class AlgoliaModelsTest { + + private val json = Json { ignoreUnknownKeys = true } + + @Test + fun `serializes search request to JSON`() { + val request = AlgoliaSearchRequest( + query = "coroutines", + hitsPerPage = 15, + attributesToRetrieve = listOf("mainTitle", "url", "headings"), + attributesToSnippet = listOf("content:40"), + ) + + val encoded = json.encodeToString(request) + val decoded = json.decodeFromString(encoded) + + assertEquals(request, decoded) + } + + @Test + fun `parses full response with nested snippet`() { + val responseJson = """ + { + "hits": [ + { + "objectID": "abc123", + "mainTitle": "Coroutines overview", + "url": "/docs/coroutines-overview.html", + "headings": "Introduction | First coroutine", + "_snippetResult": { + "content": { + "value": "Kotlin provides coroutines support", + "matchLevel": "full" + } + } + } + ] + } + """.trimIndent() + + val response = json.decodeFromString(responseJson) + + assertEquals(1, response.hits.size) + val hit = response.hits[0] + assertEquals("abc123", hit.objectID) + assertEquals("Coroutines overview", hit.mainTitle) + assertEquals("/docs/coroutines-overview.html", hit.url) + assertEquals("Introduction | First coroutine", hit.headings) + assertEquals("Kotlin provides coroutines support", hit.snippetResult?.content?.value) + assertEquals("full", hit.snippetResult?.content?.matchLevel) + } + + @Test + fun `parses hit with missing nullable fields`() { + val responseJson = """ + { + "hits": [ + { + "objectID": "def456", + "url": "/docs/basic-syntax.html" + } + ] + } + """.trimIndent() + + val response = json.decodeFromString(responseJson) + + val hit = response.hits[0] + assertEquals("def456", hit.objectID) + assertEquals("/docs/basic-syntax.html", hit.url) + assertEquals(null, hit.mainTitle) + assertEquals(null, hit.headings) + assertEquals(null, hit.snippetResult) + } + + @Test + fun `parses response ignoring unknown fields`() { + val responseJson = """ + { + "hits": [ + { + "objectID": "ghi789", + "url": "/docs/functions.html", + "unknownField": 42, + "_snippetResult": { + "content": { + "value": "snippet text", + "matchLevel": "none", + "fullyHighlighted": false + }, + "anotherUnknown": "ignored" + } + } + ], + "nbHits": 100, + "page": 0, + "processingTimeMS": 5 + } + """.trimIndent() + + val response = json.decodeFromString(responseJson) + + assertEquals(1, response.hits.size) + assertEquals("ghi789", response.hits[0].objectID) + assertEquals("snippet text", response.hits[0].snippetResult?.content?.value) + } + + @Test + fun `parses response with empty hits list`() { + val responseJson = """ + { + "hits": [] + } + """.trimIndent() + + val response = json.decodeFromString(responseJson) + + assertEquals(emptyList(), response.hits) + } +} diff --git a/samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/cache/TtlCacheTest.kt b/samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/cache/TtlCacheTest.kt new file mode 100644 index 0000000..a191fe7 --- /dev/null +++ b/samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/cache/TtlCacheTest.kt @@ -0,0 +1,144 @@ +package org.kotlinlang.mcp.cache + +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull +import kotlin.time.Duration.Companion.minutes +import kotlin.time.TestTimeSource + +class TtlCacheTest { + + @Test + fun `get returns value that was put`() { + val cache = TtlCache(ttl = 10.minutes) + cache.put("key", "value") + assertEquals("value", cache.get("key")) + } + + @Test + fun `get returns null for missing key`() { + val cache = TtlCache(ttl = 10.minutes) + assertNull(cache.get("missing")) + } + + @Test + fun `get returns null after TTL expires`() { + val timeSource = TestTimeSource() + val cache = TtlCache(ttl = 10.minutes, timeSource = timeSource) + + cache.put("key", "value") + assertEquals("value", cache.get("key")) + + timeSource += 11.minutes + assertNull(cache.get("key")) + } + + @Test + fun `get returns value just before TTL expires`() { + val timeSource = TestTimeSource() + val cache = TtlCache(ttl = 10.minutes, timeSource = timeSource) + + cache.put("key", "value") + timeSource += 9.minutes + assertEquals("value", cache.get("key")) + } + + @Test + fun `put overwrites value and resets TTL`() { + val timeSource = TestTimeSource() + val cache = TtlCache(ttl = 10.minutes, timeSource = timeSource) + + cache.put("key", "old") + timeSource += 8.minutes + + cache.put("key", "new") + timeSource += 8.minutes + + assertEquals("new", cache.get("key")) + } + + @Test + fun `getOrPut returns cached value without calling loader`() = runTest { + val cache = TtlCache(ttl = 10.minutes) + cache.put("key", "cached") + + var loaderCalled = false + val result = cache.getOrPut("key") { + loaderCalled = true + "loaded" + } + + assertEquals("cached", result) + assertEquals(false, loaderCalled) + } + + @Test + fun `getOrPut calls loader on cache miss and caches result`() = runTest { + val cache = TtlCache(ttl = 10.minutes) + + val result = cache.getOrPut("key") { "loaded" } + + assertEquals("loaded", result) + assertEquals("loaded", cache.get("key")) + } + + @Test + fun `getOrPut calls loader again after TTL expires`() = runTest { + val timeSource = TestTimeSource() + val cache = TtlCache(ttl = 10.minutes, timeSource = timeSource) + + cache.getOrPut("key") { "first" } + timeSource += 11.minutes + + val result = cache.getOrPut("key") { "second" } + assertEquals("second", result) + } + + @Test + fun `put evicts expired entries`() { + val timeSource = TestTimeSource() + val cache = TtlCache(ttl = 10.minutes, timeSource = timeSource) + + cache.put("a", "1") + cache.put("b", "2") + timeSource += 11.minutes + + // Both entries are expired but still in memory. + // A new put triggers eviction of expired entries. + cache.put("c", "3") + + assertNull(cache.get("a")) + assertNull(cache.get("b")) + assertEquals("3", cache.get("c")) + } + + @Test + fun `put does not evict non-expired entries`() { + val timeSource = TestTimeSource() + val cache = TtlCache(ttl = 10.minutes, timeSource = timeSource) + + cache.put("a", "1") + timeSource += 5.minutes + cache.put("b", "2") + + assertEquals("1", cache.get("a")) + assertEquals("2", cache.get("b")) + } + + @Test + fun `concurrent put and get do not throw`() = runTest { + val cache = TtlCache(ttl = 10.minutes) + val jobs = (1..100).map { i -> + launch { + cache.put(i, "value-$i") + cache.get(i) + cache.put(i, "updated-$i") + cache.get(i) + } + } + jobs.joinAll() + } +} diff --git a/samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/config/ServerConfigTest.kt b/samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/config/ServerConfigTest.kt new file mode 100644 index 0000000..5714602 --- /dev/null +++ b/samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/config/ServerConfigTest.kt @@ -0,0 +1,35 @@ +package org.kotlinlang.mcp.config + +import io.ktor.server.config.* +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class ServerConfigTest { + + @Test + fun `toServerConfig maps all Algolia properties`() { + val config = MapApplicationConfig( + "algolia.appId" to "test-app-id", + "algolia.apiKey" to "test-api-key", + "algolia.indexName" to "test-index", + ) + + val serverConfig = config.toServerConfig() + + assertEquals("test-app-id", serverConfig.algoliaAppId) + assertEquals("test-api-key", serverConfig.algoliaApiKey) + assertEquals("test-index", serverConfig.algoliaIndexName) + } + + @Test + fun `toServerConfig throws when required property is missing`() { + val config = MapApplicationConfig( + "algolia.appId" to "test-app-id", + ) + + assertFailsWith { + config.toServerConfig() + } + } +} diff --git a/samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/content/PageFetcherTest.kt b/samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/content/PageFetcherTest.kt new file mode 100644 index 0000000..1cd3e6b --- /dev/null +++ b/samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/content/PageFetcherTest.kt @@ -0,0 +1,87 @@ +package org.kotlinlang.mcp.content + +import io.ktor.client.* +import io.ktor.client.engine.mock.* +import io.ktor.client.plugins.* +import io.ktor.http.* +import kotlinx.coroutines.test.runTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class PageFetcherTest { + + private fun createFetcher(mockEngine: MockEngine): PageFetcher { + val httpClient = HttpClient(mockEngine) { + expectSuccess = true + } + return PageFetcher(httpClient) + } + + @Test + fun `fetch sends GET request to provided URL`() = runTest { + val mockEngine = MockEngine { request -> + assertEquals("https://kotlinlang.org/docs/_llms/coroutines-overview.txt", request.url.toString()) + assertEquals(HttpMethod.Get, request.method) + + respond( + content = "# Coroutines overview", + status = HttpStatusCode.OK, + headers = headersOf(HttpHeaders.ContentType, "text/plain"), + ) + } + + val fetcher = createFetcher(mockEngine) + fetcher.fetch("https://kotlinlang.org/docs/_llms/coroutines-overview.txt") + } + + @Test + fun `fetch returns response body as text`() = runTest { + val pageContent = "# Coroutines\n\nKotlin provides coroutines support at the language level." + + val mockEngine = MockEngine { + respond( + content = pageContent, + status = HttpStatusCode.OK, + headers = headersOf(HttpHeaders.ContentType, "text/plain"), + ) + } + + val fetcher = createFetcher(mockEngine) + val result = fetcher.fetch("https://kotlinlang.org/docs/_llms/coroutines-overview.txt") + + assertEquals(pageContent, result) + } + + @Test + fun `fetch throws ClientRequestException on 404`() = runTest { + val mockEngine = MockEngine { + respond( + content = "Not Found", + status = HttpStatusCode.NotFound, + headers = headersOf(HttpHeaders.ContentType, "text/plain"), + ) + } + + val fetcher = createFetcher(mockEngine) + assertFailsWith { + fetcher.fetch("https://kotlinlang.org/docs/_llms/nonexistent.txt") + } + } + + @Test + fun `fetch throws ServerResponseException on 5xx`() = runTest { + val mockEngine = MockEngine { + respond( + content = "Internal Server Error", + status = HttpStatusCode.InternalServerError, + headers = headersOf(HttpHeaders.ContentType, "text/plain"), + ) + } + + val fetcher = createFetcher(mockEngine) + assertFailsWith { + fetcher.fetch("https://kotlinlang.org/docs/_llms/coroutines-overview.txt") + } + } +} diff --git a/samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/content/UrlMapperTest.kt b/samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/content/UrlMapperTest.kt new file mode 100644 index 0000000..c2076a3 --- /dev/null +++ b/samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/content/UrlMapperTest.kt @@ -0,0 +1,91 @@ +package org.kotlinlang.mcp.content + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class UrlMapperTest { + + @Test + fun `top-level path maps to _llms URL`() { + assertEquals( + "https://kotlinlang.org/docs/_llms/coroutines-overview.txt", + mapPathToUrl("coroutines-overview"), + ) + } + + @Test + fun `nested path maps to _llms URL`() { + assertEquals( + "https://kotlinlang.org/docs/multiplatform/_llms/compose.txt", + mapPathToUrl("multiplatform/compose"), + ) + } + + @Test + fun `deep nested path maps to _llms URL`() { + assertEquals( + "https://kotlinlang.org/docs/a/b/_llms/c.txt", + mapPathToUrl("a/b/c"), + ) + } + + @Test + fun `trims leading and trailing slashes`() { + assertEquals( + "https://kotlinlang.org/docs/_llms/coroutines-overview.txt", + mapPathToUrl("/coroutines-overview/"), + ) + } + + @Test + fun `strips html extension`() { + assertEquals( + "https://kotlinlang.org/docs/_llms/coroutines-overview.txt", + mapPathToUrl("coroutines-overview.html"), + ) + } + + @Test + fun `normalizes slashes and html extension together`() { + assertEquals( + "https://kotlinlang.org/docs/multiplatform/_llms/compose.txt", + mapPathToUrl("/multiplatform/compose.html"), + ) + } + + @Test + fun `empty string throws IllegalArgumentException`() { + assertFailsWith { + mapPathToUrl("") + } + } + + @Test + fun `only slashes throws IllegalArgumentException`() { + assertFailsWith { + mapPathToUrl("///") + } + } + + @Test + fun `blank path throws IllegalArgumentException`() { + assertFailsWith { + mapPathToUrl(" ") + } + } + + @Test + fun `path traversal throws IllegalArgumentException`() { + assertFailsWith { + mapPathToUrl("../../secret") + } + } + + @Test + fun `path traversal in the middle throws IllegalArgumentException`() { + assertFailsWith { + mapPathToUrl("multiplatform/../secret") + } + } +} diff --git a/samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/tools/GetKotlinlangPageTest.kt b/samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/tools/GetKotlinlangPageTest.kt new file mode 100644 index 0000000..18f0cfe --- /dev/null +++ b/samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/tools/GetKotlinlangPageTest.kt @@ -0,0 +1,139 @@ +package org.kotlinlang.mcp.tools + +import io.ktor.client.* +import io.ktor.client.engine.mock.* +import io.ktor.http.* +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import kotlinx.coroutines.test.runTest +import org.kotlinlang.mcp.cache.TtlCache +import org.kotlinlang.mcp.content.PageFetcher +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull +import kotlin.test.assertTrue +import kotlin.time.Duration.Companion.hours + +class GetKotlinlangPageTest { + + private fun createTool(mockEngine: MockEngine): GetKotlinlangPage { + val httpClient = HttpClient(mockEngine) { + expectSuccess = true + } + val pageFetcher = PageFetcher(httpClient) + val cache = TtlCache(ttl = 1.hours) + return GetKotlinlangPage(pageFetcher, cache) + } + + @Test + fun `handle returns page content for valid path`() = runTest { + val pageContent = "# Coroutines\n\nKotlin provides coroutines support." + val mockEngine = MockEngine { request -> + assertEquals( + "https://kotlinlang.org/docs/_llms/coroutines-overview.txt", + request.url.toString(), + ) + respond( + content = pageContent, + status = HttpStatusCode.OK, + headers = headersOf(HttpHeaders.ContentType, "text/plain"), + ) + } + val tool = createTool(mockEngine) + + val result = tool.handle("coroutines-overview") + val text = (result.content.first() as TextContent).text + + assertNull(result.isError) + assertEquals(pageContent, text) + } + + @Test + fun `handle returns cached content on second call`() = runTest { + var callCount = 0 + val mockEngine = MockEngine { + callCount++ + respond( + content = "# Page content", + status = HttpStatusCode.OK, + headers = headersOf(HttpHeaders.ContentType, "text/plain"), + ) + } + val tool = createTool(mockEngine) + + val result1 = tool.handle("coroutines-overview") + val result2 = tool.handle("coroutines-overview") + + assertEquals(1, callCount) + assertEquals( + (result1.content.first() as TextContent).text, + (result2.content.first() as TextContent).text, + ) + } + + @Test + fun `handle returns error with hint on 404`() = runTest { + val mockEngine = MockEngine { + respond( + content = "Not Found", + status = HttpStatusCode.NotFound, + headers = headersOf(HttpHeaders.ContentType, "text/plain"), + ) + } + val tool = createTool(mockEngine) + + val result = tool.handle("nonexistent-page") + val text = (result.content.first() as TextContent).text + + assertEquals(true, result.isError) + assertTrue(text.contains("Page not found: nonexistent-page")) + assertTrue(text.contains("search_kotlinlang")) + } + + @Test + fun `handle returns error on network failure`() = runTest { + val mockEngine = MockEngine { + respond( + content = "Internal Server Error", + status = HttpStatusCode.InternalServerError, + headers = headersOf(HttpHeaders.ContentType, "text/plain"), + ) + } + val tool = createTool(mockEngine) + + val result = tool.handle("coroutines-overview") + + assertEquals(true, result.isError) + val text = (result.content.first() as TextContent).text + assertTrue(text.startsWith("Failed to fetch page:")) + } + + @Test + fun `handle returns error on empty path`() = runTest { + val mockEngine = MockEngine { + respond( + content = "should not reach", + status = HttpStatusCode.OK, + headers = headersOf(HttpHeaders.ContentType, "text/plain"), + ) + } + val tool = createTool(mockEngine) + + val result = tool.handle("") + + assertEquals(true, result.isError) + val text = (result.content.first() as TextContent).text + assertTrue(text.contains("Invalid path:")) + } + + @Test + fun `handle rethrows CancellationException`() = runTest { + val mockEngine = MockEngine { + throw kotlinx.coroutines.CancellationException("cancelled") + } + val tool = createTool(mockEngine) + + kotlin.test.assertFailsWith { + tool.handle("coroutines-overview") + } + } +} diff --git a/samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/tools/SearchKotlinlangTest.kt b/samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/tools/SearchKotlinlangTest.kt new file mode 100644 index 0000000..7e10420 --- /dev/null +++ b/samples/kotlinlang-mcp-server/src/test/kotlin/org/kotlinlang/mcp/tools/SearchKotlinlangTest.kt @@ -0,0 +1,231 @@ +package org.kotlinlang.mcp.tools + +import io.ktor.client.* +import io.ktor.client.engine.mock.* +import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.http.* +import io.ktor.serialization.kotlinx.json.* +import kotlinx.coroutines.test.runTest +import kotlinx.serialization.json.Json +import org.kotlinlang.mcp.algolia.AlgoliaClient +import org.kotlinlang.mcp.cache.TtlCache +import org.kotlinlang.mcp.config.ServerConfig +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull +import kotlin.test.assertTrue +import kotlin.time.Duration.Companion.minutes +import kotlin.time.TestTimeSource + +class SearchKotlinlangTest { + + private val testConfig = ServerConfig( + algoliaAppId = "test-app-id", + algoliaApiKey = "test-api-key", + algoliaIndexName = "test-index", + ) + + private val json = Json { ignoreUnknownKeys = true } + + private fun createSearchTool( + mockEngine: MockEngine, + timeSource: TestTimeSource = TestTimeSource(), + ): SearchKotlinlang { + val httpClient = HttpClient(mockEngine) { + install(ContentNegotiation) { json(json) } + } + val algoliaClient = AlgoliaClient(testConfig, httpClient) + val cache = TtlCache(ttl = 10.minutes, timeSource = timeSource) + return SearchKotlinlang(algoliaClient, cache) + } + + private fun mockEngineWithHits(hitsJson: String): MockEngine = MockEngine { + respond( + content = """{"hits": [$hitsJson]}""", + status = HttpStatusCode.OK, + headers = headersOf(HttpHeaders.ContentType, "application/json"), + ) + } + + private fun hit( + objectID: String = "id1", + mainTitle: String? = "Title", + url: String = "/docs/test.html", + snippet: String? = "some text", + matchLevel: String = "full", + ): String = buildString { + append("""{"objectID":"$objectID","url":"$url"""") + if (mainTitle != null) append(""","mainTitle":"$mainTitle"""") + if (snippet != null) { + append(""","_snippetResult":{"content":{"value":"$snippet","matchLevel":"$matchLevel"}}""") + } + append("}") + } + + @Test + fun `filters out non-docs hits`() = runTest { + val engine = mockEngineWithHits( + listOf( + hit(objectID = "1", url = "/docs/coroutines.html", mainTitle = "Coroutines"), + hit(objectID = "2", url = "https://kotlinlang.org/api/core/kotlin/", mainTitle = "API"), + hit(objectID = "3", url = "/docs/flow.html", mainTitle = "Flow"), + ).joinToString(",") + ) + val tool = createSearchTool(engine) + + val result = tool.handle("coroutines") + val text = result.content.first().let { (it as io.modelcontextprotocol.kotlin.sdk.types.TextContent).text } + + assertTrue(text.contains("Coroutines")) + assertTrue(text.contains("Flow")) + assertTrue(!text.contains("API")) + } + + @Test + fun `takes only top 5 after filtering`() = runTest { + val hits = (1..10).map { i -> + hit(objectID = "id$i", url = "/docs/page$i.html", mainTitle = "Page $i", snippet = "text $i") + } + val engine = mockEngineWithHits(hits.joinToString(",")) + val tool = createSearchTool(engine) + + val result = tool.handle("kotlin") + val text = result.content.first().let { (it as io.modelcontextprotocol.kotlin.sdk.types.TextContent).text } + + for (i in 1..5) assertTrue(text.contains("Page $i"), "Should contain Page $i") + for (i in 6..10) assertTrue(!text.contains("Page $i"), "Should not contain Page $i") + } + + @Test + fun `formats results with title path and snippet`() = runTest { + val engine = mockEngineWithHits( + hit( + objectID = "1", + url = "/docs/coroutines-overview.html", + mainTitle = "Coroutines overview", + snippet = "Kotlin provides coroutines support", + ) + ) + val tool = createSearchTool(engine) + + val result = tool.handle("coroutines") + val text = result.content.first().let { (it as io.modelcontextprotocol.kotlin.sdk.types.TextContent).text } + + assertEquals( + """ + 1. Coroutines overview [coroutines-overview] + Kotlin provides coroutines support + """.trimIndent(), + text, + ) + } + + @Test + fun `strips HTML tags from snippet`() = runTest { + val engine = mockEngineWithHits( + hit(snippet = "coroutines are great") + ) + val tool = createSearchTool(engine) + + val result = tool.handle("coroutines") + val text = result.content.first().let { (it as io.modelcontextprotocol.kotlin.sdk.types.TextContent).text } + + assertTrue(text.contains("coroutines are great")) + assertTrue(!text.contains("")) + assertTrue(!text.contains("")) + } + + @Test + fun `uses Untitled when mainTitle is null`() = runTest { + val engine = mockEngineWithHits( + hit(mainTitle = null, url = "/docs/basics.html", snippet = "some text") + ) + val tool = createSearchTool(engine) + + val result = tool.handle("basics") + val text = result.content.first().let { (it as io.modelcontextprotocol.kotlin.sdk.types.TextContent).text } + + assertTrue(text.contains("Untitled [basics]")) + } + + @Test + fun `omits snippet line when snippetResult is null`() = runTest { + val engine = mockEngineWithHits( + hit(url = "/docs/basics.html", mainTitle = "Basics", snippet = null) + ) + val tool = createSearchTool(engine) + + val result = tool.handle("basics") + val text = result.content.first().let { (it as io.modelcontextprotocol.kotlin.sdk.types.TextContent).text } + + assertEquals("1. Basics [basics]", text) + } + + @Test + fun `returns no results message when all hits are filtered out`() = runTest { + val engine = mockEngineWithHits( + hit(url = "https://kotlinlang.org/api/core/kotlin/", mainTitle = "API") + ) + val tool = createSearchTool(engine) + + val result = tool.handle("something") + val text = result.content.first().let { (it as io.modelcontextprotocol.kotlin.sdk.types.TextContent).text } + + assertNull(result.isError) + assertEquals("No results found for: something", text) + } + + @Test + fun `returns isError true when Algolia fails`() = runTest { + val engine = MockEngine { + respond( + content = """{"message": "Internal server error"}""", + status = HttpStatusCode.InternalServerError, + headers = headersOf(HttpHeaders.ContentType, "application/json"), + ) + } + val tool = createSearchTool(engine) + + val result = tool.handle("coroutines") + + assertEquals(true, result.isError) + val text = result.content.first().let { (it as io.modelcontextprotocol.kotlin.sdk.types.TextContent).text } + assertTrue(text.startsWith("Search failed:")) + } + + @Test + fun `caches result and does not call Algolia on second request`() = runTest { + var callCount = 0 + val engine = MockEngine { + callCount++ + respond( + content = """{"hits": [${hit(url = "/docs/test.html")}]}""", + status = HttpStatusCode.OK, + headers = headersOf(HttpHeaders.ContentType, "application/json"), + ) + } + val tool = createSearchTool(engine) + + tool.handle("coroutines") + tool.handle("coroutines") + + assertEquals(1, callCount) + } + + @Test + fun `extracts path from nested URL`() = runTest { + val engine = mockEngineWithHits( + hit( + url = "/docs/multiplatform/compose.html", + mainTitle = "Compose", + snippet = "text", + ) + ) + val tool = createSearchTool(engine) + + val result = tool.handle("compose") + val text = result.content.first().let { (it as io.modelcontextprotocol.kotlin.sdk.types.TextContent).text } + + assertTrue(text.contains("[multiplatform/compose]")) + } +} From 18f4c477ef22b7d06dcb8d49e9baa81601adfbb8 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Mon, 27 Apr 2026 16:27:41 +0200 Subject: [PATCH 258/449] chore: remove idea files related to detekt (#714) - detekt idea plugin version (1.23.8) is incompatible with the gradle plugin version (2.0.0-alpha.2) - remove the annoying popup shown when the idea starts image --- .idea/detekt.xml | 9 --------- .idea/externalDependencies.xml | 7 ------- .idea/ktlint-plugin.xml | 7 ------- 3 files changed, 23 deletions(-) delete mode 100644 .idea/detekt.xml delete mode 100644 .idea/externalDependencies.xml delete mode 100644 .idea/ktlint-plugin.xml diff --git a/.idea/detekt.xml b/.idea/detekt.xml deleted file mode 100644 index d89f908..0000000 --- a/.idea/detekt.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - diff --git a/.idea/externalDependencies.xml b/.idea/externalDependencies.xml deleted file mode 100644 index 0a081df..0000000 --- a/.idea/externalDependencies.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/.idea/ktlint-plugin.xml b/.idea/ktlint-plugin.xml deleted file mode 100644 index e8bd90c..0000000 --- a/.idea/ktlint-plugin.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - DISTRACT_FREE - DEFAULT - - \ No newline at end of file From 5b4ea4b774cb75d36264e4951d8e990b2e47d895 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 17:03:48 +0200 Subject: [PATCH 259/449] chore(deps): bump the kotlin group across 1 directory with 2 updates (#723) Bumps the kotlin group with 2 updates in the / directory: [org.jetbrains.kotlin:kotlin-gradle-plugin](https://github.com/JetBrains/kotlin) and [org.jetbrains.kotlin:kotlin-serialization](https://github.com/JetBrains/kotlin). Updates `org.jetbrains.kotlin:kotlin-gradle-plugin` from 2.3.20 to 2.3.21

Release notes

Sourced from org.jetbrains.kotlin:kotlin-gradle-plugin's releases.

Kotlin 2.3.21

Changelog

Backend. Wasm

  • KT-84610 [Wasm] Failed to compile klibs in IC mode

Compiler

  • KT-84566 Prevent launching Default dispatcher threads from IJ SDK in kotlin compiler
  • KT-85358 Native: roll back the workaround for KT-84678 once MapLibre has been properly fixed
  • KT-85626 @JvmRecord in commonMain breaks compileCommonMainKotlinMetadata with "Cannot access 'java.lang.Record'"
  • KT-85405 Postpone/Revert DontIgnoreUpperBoundViolatedOnImplicitArguments
  • KT-84678 K/N: Undefined symbol from SPM-added ObjC frameworks when linking iOS target
  • KT-85021 False positive SUBCLASS_CANT_CALL_COMPANION_PROTECTED_NON_STATIC error in multi-module project

JavaScript

  • KT-82395 Support top-level declarations from compiler plugins in JS incremental compilation
  • KT-84475 K/JS: false-positive exportability warnings in multi-module project
  • KT-84633 Kotlin/JS: "Serializer for class not found" error when IR output granularity is whole-program
  • KT-85047 Kotlin/JS: @JsStatic on suspend fun of class companion generates incorrect d.ts
  • KT-84517 K/JS: bad mappings data in outputted Kotlin stdlib source map

Libraries

  • KT-71848 Kotlinx.metadata: Add CompilerPluginData into Km API

Native. C and ObjC Import

  • KT-85399 Kotlin/Native: TypeCastException when casting ObjC Protocol MetaClass with genericSafeCasts enabled
  • KT-85508 K/N: TypeCastException when using nw_parameters_create_secure_tcp block parameter on 2.3.20

Tools. Gradle

  • KT-84729 Update Gradle plugin-publish version to enable configuration cache badge on Gradle plugins portal

Tools. Gradle. Compiler plugins

  • KT-85257 AGP/Compose: MergeMappingFileTask clears R8 artifacts due to @OutputDirectory annotation on AGP 9.1+

Tools. Scripts

  • KT-85105 Scripts: JVM backend internal error (IR lowering) when scratch file contains anonymous object
  • KT-85103 Exception while generating code when explain destructuring decls
  • KT-84842 scriptCompilationClasspathFromContext behavior changed from 2.3.10 to 2.3.20
  • KT-85029 Kotlin Scripting: ScriptDiagnostic reports "at null" instead of error location

Tools. Statistics (FUS)

... (truncated)

Changelog

Sourced from org.jetbrains.kotlin:kotlin-gradle-plugin's changelog.

2.3.21

Backend. Wasm

  • KT-84610 [Wasm] Failed to compile klibs in IC mode

Compiler

  • KT-84566 Prevent launching Default dispatcher threads from IJ SDK in kotlin compiler
  • KT-85358 Native: roll back the workaround for KT-84678 once MapLibre has been properly fixed
  • KT-85626 @JvmRecord in commonMain breaks compileCommonMainKotlinMetadata with "Cannot access 'java.lang.Record'"
  • KT-85405 Postpone/Revert DontIgnoreUpperBoundViolatedOnImplicitArguments
  • KT-84678 K/N: Undefined symbol from SPM-added ObjC frameworks when linking iOS target
  • KT-85021 False positive SUBCLASS_CANT_CALL_COMPANION_PROTECTED_NON_STATIC error in multi-module project

JavaScript

  • KT-82395 Support top-level declarations from compiler plugins in JS incremental compilation
  • KT-84475 K/JS: false-positive exportability warnings in multi-module project
  • KT-84633 Kotlin/JS: "Serializer for class not found" error when IR output granularity is whole-program
  • KT-85047 Kotlin/JS: @JsStatic on suspend fun of class companion generates incorrect d.ts
  • KT-84517 K/JS: bad mappings data in outputted Kotlin stdlib source map

Libraries

  • KT-71848 Kotlinx.metadata: Add CompilerPluginData into Km API

Native. C and ObjC Import

  • KT-85399 Kotlin/Native: TypeCastException when casting ObjC Protocol MetaClass with genericSafeCasts enabled
  • KT-85508 K/N: TypeCastException when using nw_parameters_create_secure_tcp block parameter on 2.3.20

Tools. Gradle

  • KT-84729 Update Gradle plugin-publish version to enable configuration cache badge on Gradle plugins portal

Tools. Gradle. Compiler plugins

  • KT-85257 AGP/Compose: MergeMappingFileTask clears R8 artifacts due to @OutputDirectory annotation on AGP 9.1+

Tools. Scripts

  • KT-85105 Scripts: JVM backend internal error (IR lowering) when scratch file contains anonymous object
  • KT-85103 Exception while generating code when explain destructuring decls
  • KT-84842 scriptCompilationClasspathFromContext behavior changed from 2.3.10 to 2.3.20
  • KT-85029 Kotlin Scripting: ScriptDiagnostic reports "at null" instead of error location

Tools. Statistics (FUS)

  • KT-85628 KGP: composite build FUS metrics fail on access of 'configurationTimeMetrics'
Commits
  • fea1ad8 Add ChangeLog for 2.3.21-RC2
  • 09c341e disable swift export execution tests in order to update macos
  • 67a0868 Avoid accessing KotlinNativeLink taskProvider when task was not executed
  • f89e5db [K/N] Disable TSAN in runtime tests
  • 45d6c85 [K/N] Don't generate generic safe casts for Objective-C types
  • 9261a6f [K/N][tests] Add a reproducer for KT-85508
  • c9ab9db [K/N][tests] Add a reproducer for KT-85399
  • 502e844 Explain: fix for destructuring declarations
  • 0c26485 Explain: fix for object literals
  • 68a9e3f [minor] fix testdata name in explain test
  • Additional commits viewable in compare view

Updates `org.jetbrains.kotlin:kotlin-serialization` from 2.3.20 to 2.3.21
Release notes

Sourced from org.jetbrains.kotlin:kotlin-serialization's releases.

Kotlin 2.3.21

Changelog

Backend. Wasm

  • KT-84610 [Wasm] Failed to compile klibs in IC mode

Compiler

  • KT-84566 Prevent launching Default dispatcher threads from IJ SDK in kotlin compiler
  • KT-85358 Native: roll back the workaround for KT-84678 once MapLibre has been properly fixed
  • KT-85626 @JvmRecord in commonMain breaks compileCommonMainKotlinMetadata with "Cannot access 'java.lang.Record'"
  • KT-85405 Postpone/Revert DontIgnoreUpperBoundViolatedOnImplicitArguments
  • KT-84678 K/N: Undefined symbol from SPM-added ObjC frameworks when linking iOS target
  • KT-85021 False positive SUBCLASS_CANT_CALL_COMPANION_PROTECTED_NON_STATIC error in multi-module project

JavaScript

  • KT-82395 Support top-level declarations from compiler plugins in JS incremental compilation
  • KT-84475 K/JS: false-positive exportability warnings in multi-module project
  • KT-84633 Kotlin/JS: "Serializer for class not found" error when IR output granularity is whole-program
  • KT-85047 Kotlin/JS: @JsStatic on suspend fun of class companion generates incorrect d.ts
  • KT-84517 K/JS: bad mappings data in outputted Kotlin stdlib source map

Libraries

  • KT-71848 Kotlinx.metadata: Add CompilerPluginData into Km API

Native. C and ObjC Import

  • KT-85399 Kotlin/Native: TypeCastException when casting ObjC Protocol MetaClass with genericSafeCasts enabled
  • KT-85508 K/N: TypeCastException when using nw_parameters_create_secure_tcp block parameter on 2.3.20

Tools. Gradle

  • KT-84729 Update Gradle plugin-publish version to enable configuration cache badge on Gradle plugins portal

Tools. Gradle. Compiler plugins

  • KT-85257 AGP/Compose: MergeMappingFileTask clears R8 artifacts due to @OutputDirectory annotation on AGP 9.1+

Tools. Scripts

  • KT-85105 Scripts: JVM backend internal error (IR lowering) when scratch file contains anonymous object
  • KT-85103 Exception while generating code when explain destructuring decls
  • KT-84842 scriptCompilationClasspathFromContext behavior changed from 2.3.10 to 2.3.20
  • KT-85029 Kotlin Scripting: ScriptDiagnostic reports "at null" instead of error location

Tools. Statistics (FUS)

... (truncated)

Changelog

Sourced from org.jetbrains.kotlin:kotlin-serialization's changelog.

2.3.21

Backend. Wasm

  • KT-84610 [Wasm] Failed to compile klibs in IC mode

Compiler

  • KT-84566 Prevent launching Default dispatcher threads from IJ SDK in kotlin compiler
  • KT-85358 Native: roll back the workaround for KT-84678 once MapLibre has been properly fixed
  • KT-85626 @JvmRecord in commonMain breaks compileCommonMainKotlinMetadata with "Cannot access 'java.lang.Record'"
  • KT-85405 Postpone/Revert DontIgnoreUpperBoundViolatedOnImplicitArguments
  • KT-84678 K/N: Undefined symbol from SPM-added ObjC frameworks when linking iOS target
  • KT-85021 False positive SUBCLASS_CANT_CALL_COMPANION_PROTECTED_NON_STATIC error in multi-module project

JavaScript

  • KT-82395 Support top-level declarations from compiler plugins in JS incremental compilation
  • KT-84475 K/JS: false-positive exportability warnings in multi-module project
  • KT-84633 Kotlin/JS: "Serializer for class not found" error when IR output granularity is whole-program
  • KT-85047 Kotlin/JS: @JsStatic on suspend fun of class companion generates incorrect d.ts
  • KT-84517 K/JS: bad mappings data in outputted Kotlin stdlib source map

Libraries

  • KT-71848 Kotlinx.metadata: Add CompilerPluginData into Km API

Native. C and ObjC Import

  • KT-85399 Kotlin/Native: TypeCastException when casting ObjC Protocol MetaClass with genericSafeCasts enabled
  • KT-85508 K/N: TypeCastException when using nw_parameters_create_secure_tcp block parameter on 2.3.20

Tools. Gradle

  • KT-84729 Update Gradle plugin-publish version to enable configuration cache badge on Gradle plugins portal

Tools. Gradle. Compiler plugins

  • KT-85257 AGP/Compose: MergeMappingFileTask clears R8 artifacts due to @OutputDirectory annotation on AGP 9.1+

Tools. Scripts

  • KT-85105 Scripts: JVM backend internal error (IR lowering) when scratch file contains anonymous object
  • KT-85103 Exception while generating code when explain destructuring decls
  • KT-84842 scriptCompilationClasspathFromContext behavior changed from 2.3.10 to 2.3.20
  • KT-85029 Kotlin Scripting: ScriptDiagnostic reports "at null" instead of error location

Tools. Statistics (FUS)

  • KT-85628 KGP: composite build FUS metrics fail on access of 'configurationTimeMetrics'
Commits
  • fea1ad8 Add ChangeLog for 2.3.21-RC2
  • 09c341e disable swift export execution tests in order to update macos
  • 67a0868 Avoid accessing KotlinNativeLink taskProvider when task was not executed
  • f89e5db [K/N] Disable TSAN in runtime tests
  • 45d6c85 [K/N] Don't generate generic safe casts for Objective-C types
  • 9261a6f [K/N][tests] Add a reproducer for KT-85508
  • c9ab9db [K/N][tests] Add a reproducer for KT-85399
  • 502e844 Explain: fix for destructuring declarations
  • 0c26485 Explain: fix for object literals
  • 68a9e3f [minor] fix testdata name in explain test
  • Additional commits viewable in compare view

Updates `org.jetbrains.kotlin:kotlin-serialization` from 2.3.20 to 2.3.21
Release notes

Sourced from org.jetbrains.kotlin:kotlin-serialization's releases.

Kotlin 2.3.21

Changelog

Backend. Wasm

  • KT-84610 [Wasm] Failed to compile klibs in IC mode

Compiler

  • KT-84566 Prevent launching Default dispatcher threads from IJ SDK in kotlin compiler
  • KT-85358 Native: roll back the workaround for KT-84678 once MapLibre has been properly fixed
  • KT-85626 @JvmRecord in commonMain breaks compileCommonMainKotlinMetadata with "Cannot access 'java.lang.Record'"
  • KT-85405 Postpone/Revert DontIgnoreUpperBoundViolatedOnImplicitArguments
  • KT-84678 K/N: Undefined symbol from SPM-added ObjC frameworks when linking iOS target
  • KT-85021 False positive SUBCLASS_CANT_CALL_COMPANION_PROTECTED_NON_STATIC error in multi-module project

JavaScript

  • KT-82395 Support top-level declarations from compiler plugins in JS incremental compilation
  • KT-84475 K/JS: false-positive exportability warnings in multi-module project
  • KT-84633 Kotlin/JS: "Serializer for class not found" error when IR output granularity is whole-program
  • KT-85047 Kotlin/JS: @JsStatic on suspend fun of class companion generates incorrect d.ts
  • KT-84517 K/JS: bad mappings data in outputted Kotlin stdlib source map

Libraries

  • KT-71848 Kotlinx.metadata: Add CompilerPluginData into Km API

Native. C and ObjC Import

  • KT-85399 Kotlin/Native: TypeCastException when casting ObjC Protocol MetaClass with genericSafeCasts enabled
  • KT-85508 K/N: TypeCastException when using nw_parameters_create_secure_tcp block parameter on 2.3.20

Tools. Gradle

  • KT-84729 Update Gradle plugin-publish version to enable configuration cache badge on Gradle plugins portal

Tools. Gradle. Compiler plugins

  • KT-85257 AGP/Compose: MergeMappingFileTask clears R8 artifacts due to @OutputDirectory annotation on AGP 9.1+

Tools. Scripts

  • KT-85105 Scripts: JVM backend internal error (IR lowering) when scratch file contains anonymous object
  • KT-85103 Exception while generating code when explain destructuring decls
  • KT-84842 scriptCompilationClasspathFromContext behavior changed from 2.3.10 to 2.3.20
  • KT-85029 Kotlin Scripting: ScriptDiagnostic reports "at null" instead of error location

Tools. Statistics (FUS)

... (truncated)

Changelog

Sourced from org.jetbrains.kotlin:kotlin-serialization's changelog.

2.3.21

Backend. Wasm

  • KT-84610 [Wasm] Failed to compile klibs in IC mode

Compiler

  • KT-84566 Prevent launching Default dispatcher threads from IJ SDK in kotlin compiler
  • KT-85358 Native: roll back the workaround for KT-84678 once MapLibre has been properly fixed
  • KT-85626 @JvmRecord in commonMain breaks compileCommonMainKotlinMetadata with "Cannot access 'java.lang.Record'"
  • KT-85405 Postpone/Revert DontIgnoreUpperBoundViolatedOnImplicitArguments
  • KT-84678 K/N: Undefined symbol from SPM-added ObjC frameworks when linking iOS target
  • KT-85021 False positive SUBCLASS_CANT_CALL_COMPANION_PROTECTED_NON_STATIC error in multi-module project

JavaScript

  • KT-82395 Support top-level declarations from compiler plugins in JS incremental compilation
  • KT-84475 K/JS: false-positive exportability warnings in multi-module project
  • KT-84633 Kotlin/JS: "Serializer for class not found" error when IR output granularity is whole-program
  • KT-85047 Kotlin/JS: @JsStatic on suspend fun of class companion generates incorrect d.ts
  • KT-84517 K/JS: bad mappings data in outputted Kotlin stdlib source map

Libraries

  • KT-71848 Kotlinx.metadata: Add CompilerPluginData into Km API

Native. C and ObjC Import

  • KT-85399 Kotlin/Native: TypeCastException when casting ObjC Protocol MetaClass with genericSafeCasts enabled
  • KT-85508 K/N: TypeCastException when using nw_parameters_create_secure_tcp block parameter on 2.3.20

Tools. Gradle

  • KT-84729 Update Gradle plugin-publish version to enable configuration cache badge on Gradle plugins portal

Tools. Gradle. Compiler plugins

  • KT-85257 AGP/Compose: MergeMappingFileTask clears R8 artifacts due to @OutputDirectory annotation on AGP 9.1+

Tools. Scripts

  • KT-85105 Scripts: JVM backend internal error (IR lowering) when scratch file contains anonymous object
  • KT-85103 Exception while generating code when explain destructuring decls
  • KT-84842 scriptCompilationClasspathFromContext behavior changed from 2.3.10 to 2.3.20
  • KT-85029 Kotlin Scripting: ScriptDiagnostic reports "at null" instead of error location

Tools. Statistics (FUS)

  • KT-85628 KGP: composite build FUS metrics fail on access of 'configurationTimeMetrics'
Commits
  • fea1ad8 Add ChangeLog for 2.3.21-RC2
  • 09c341e disable swift export execution tests in order to update macos
  • 67a0868 Avoid accessing KotlinNativeLink taskProvider when task was not executed
  • f89e5db [K/N] Disable TSAN in runtime tests
  • 45d6c85 [K/N] Don't generate generic safe casts for Objective-C types
  • 9261a6f [K/N][tests] Add a reproducer for KT-85508
  • c9ab9db [K/N][tests] Add a reproducer for KT-85399
  • 502e844 Explain: fix for destructuring declarations
  • 0c26485 Explain: fix for object literals
  • 68a9e3f [minor] fix testdata name in explain test
  • Additional commits viewable in compare view

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 36478a9..ca67d7e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ atomicfu = "0.32.1" binaryCompatibilityValidatorPlugin = "0.18.1" detekt = "2.0.0-alpha.2" dokka = "2.2.0" -kotlin = "2.3.20" +kotlin = "2.3.21" kover = "0.9.8" ktlint = "14.2.0" knit = "0.5.1" From 0835fbd7ed62173c5ee78ca2075eb95178c2f825 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 15:31:26 +0000 Subject: [PATCH 260/449] chore(deps): bump kotlin from 2.2.21 to 2.3.21 in /samples/simple-streamable-server (#725) Bumps `kotlin` from 2.2.21 to 2.3.21. Updates `org.jetbrains.kotlin.jvm` from 2.2.21 to 2.3.21
Release notes

Sourced from org.jetbrains.kotlin.jvm's releases.

Kotlin 2.3.21

Changelog

Backend. Wasm

  • KT-84610 [Wasm] Failed to compile klibs in IC mode

Compiler

  • KT-84566 Prevent launching Default dispatcher threads from IJ SDK in kotlin compiler
  • KT-85358 Native: roll back the workaround for KT-84678 once MapLibre has been properly fixed
  • KT-85626 @JvmRecord in commonMain breaks compileCommonMainKotlinMetadata with "Cannot access 'java.lang.Record'"
  • KT-85405 Postpone/Revert DontIgnoreUpperBoundViolatedOnImplicitArguments
  • KT-84678 K/N: Undefined symbol from SPM-added ObjC frameworks when linking iOS target
  • KT-85021 False positive SUBCLASS_CANT_CALL_COMPANION_PROTECTED_NON_STATIC error in multi-module project

JavaScript

  • KT-82395 Support top-level declarations from compiler plugins in JS incremental compilation
  • KT-84475 K/JS: false-positive exportability warnings in multi-module project
  • KT-84633 Kotlin/JS: "Serializer for class not found" error when IR output granularity is whole-program
  • KT-85047 Kotlin/JS: @JsStatic on suspend fun of class companion generates incorrect d.ts
  • KT-84517 K/JS: bad mappings data in outputted Kotlin stdlib source map

Libraries

  • KT-71848 Kotlinx.metadata: Add CompilerPluginData into Km API

Native. C and ObjC Import

  • KT-85399 Kotlin/Native: TypeCastException when casting ObjC Protocol MetaClass with genericSafeCasts enabled
  • KT-85508 K/N: TypeCastException when using nw_parameters_create_secure_tcp block parameter on 2.3.20

Tools. Gradle

  • KT-84729 Update Gradle plugin-publish version to enable configuration cache badge on Gradle plugins portal

Tools. Gradle. Compiler plugins

  • KT-85257 AGP/Compose: MergeMappingFileTask clears R8 artifacts due to @OutputDirectory annotation on AGP 9.1+

Tools. Scripts

  • KT-85105 Scripts: JVM backend internal error (IR lowering) when scratch file contains anonymous object
  • KT-85103 Exception while generating code when explain destructuring decls
  • KT-84842 scriptCompilationClasspathFromContext behavior changed from 2.3.10 to 2.3.20
  • KT-85029 Kotlin Scripting: ScriptDiagnostic reports "at null" instead of error location

Tools. Statistics (FUS)

... (truncated)

Changelog

Sourced from org.jetbrains.kotlin.jvm's changelog.

2.3.21

Backend. Wasm

  • KT-84610 [Wasm] Failed to compile klibs in IC mode

Compiler

  • KT-84566 Prevent launching Default dispatcher threads from IJ SDK in kotlin compiler
  • KT-85358 Native: roll back the workaround for KT-84678 once MapLibre has been properly fixed
  • KT-85626 @JvmRecord in commonMain breaks compileCommonMainKotlinMetadata with "Cannot access 'java.lang.Record'"
  • KT-85405 Postpone/Revert DontIgnoreUpperBoundViolatedOnImplicitArguments
  • KT-84678 K/N: Undefined symbol from SPM-added ObjC frameworks when linking iOS target
  • KT-85021 False positive SUBCLASS_CANT_CALL_COMPANION_PROTECTED_NON_STATIC error in multi-module project

JavaScript

  • KT-82395 Support top-level declarations from compiler plugins in JS incremental compilation
  • KT-84475 K/JS: false-positive exportability warnings in multi-module project
  • KT-84633 Kotlin/JS: "Serializer for class not found" error when IR output granularity is whole-program
  • KT-85047 Kotlin/JS: @JsStatic on suspend fun of class companion generates incorrect d.ts
  • KT-84517 K/JS: bad mappings data in outputted Kotlin stdlib source map

Libraries

  • KT-71848 Kotlinx.metadata: Add CompilerPluginData into Km API

Native. C and ObjC Import

  • KT-85399 Kotlin/Native: TypeCastException when casting ObjC Protocol MetaClass with genericSafeCasts enabled
  • KT-85508 K/N: TypeCastException when using nw_parameters_create_secure_tcp block parameter on 2.3.20

Tools. Gradle

  • KT-84729 Update Gradle plugin-publish version to enable configuration cache badge on Gradle plugins portal

Tools. Gradle. Compiler plugins

  • KT-85257 AGP/Compose: MergeMappingFileTask clears R8 artifacts due to @OutputDirectory annotation on AGP 9.1+

Tools. Scripts

  • KT-85105 Scripts: JVM backend internal error (IR lowering) when scratch file contains anonymous object
  • KT-85103 Exception while generating code when explain destructuring decls
  • KT-84842 scriptCompilationClasspathFromContext behavior changed from 2.3.10 to 2.3.20
  • KT-85029 Kotlin Scripting: ScriptDiagnostic reports "at null" instead of error location

Tools. Statistics (FUS)

  • KT-85628 KGP: composite build FUS metrics fail on access of 'configurationTimeMetrics'

... (truncated)

Commits
  • fea1ad8 Add ChangeLog for 2.3.21-RC2
  • 09c341e disable swift export execution tests in order to update macos
  • 67a0868 Avoid accessing KotlinNativeLink taskProvider when task was not executed
  • f89e5db [K/N] Disable TSAN in runtime tests
  • 45d6c85 [K/N] Don't generate generic safe casts for Objective-C types
  • 9261a6f [K/N][tests] Add a reproducer for KT-85508
  • c9ab9db [K/N][tests] Add a reproducer for KT-85399
  • 502e844 Explain: fix for destructuring declarations
  • 0c26485 Explain: fix for object literals
  • 68a9e3f [minor] fix testdata name in explain test
  • Additional commits viewable in compare view

Updates `org.jetbrains.kotlin.plugin.serialization` from 2.2.21 to 2.3.21
Release notes

Sourced from org.jetbrains.kotlin.plugin.serialization's releases.

Kotlin 2.3.21

Changelog

Backend. Wasm

  • KT-84610 [Wasm] Failed to compile klibs in IC mode

Compiler

  • KT-84566 Prevent launching Default dispatcher threads from IJ SDK in kotlin compiler
  • KT-85358 Native: roll back the workaround for KT-84678 once MapLibre has been properly fixed
  • KT-85626 @JvmRecord in commonMain breaks compileCommonMainKotlinMetadata with "Cannot access 'java.lang.Record'"
  • KT-85405 Postpone/Revert DontIgnoreUpperBoundViolatedOnImplicitArguments
  • KT-84678 K/N: Undefined symbol from SPM-added ObjC frameworks when linking iOS target
  • KT-85021 False positive SUBCLASS_CANT_CALL_COMPANION_PROTECTED_NON_STATIC error in multi-module project

JavaScript

  • KT-82395 Support top-level declarations from compiler plugins in JS incremental compilation
  • KT-84475 K/JS: false-positive exportability warnings in multi-module project
  • KT-84633 Kotlin/JS: "Serializer for class not found" error when IR output granularity is whole-program
  • KT-85047 Kotlin/JS: @JsStatic on suspend fun of class companion generates incorrect d.ts
  • KT-84517 K/JS: bad mappings data in outputted Kotlin stdlib source map

Libraries

  • KT-71848 Kotlinx.metadata: Add CompilerPluginData into Km API

Native. C and ObjC Import

  • KT-85399 Kotlin/Native: TypeCastException when casting ObjC Protocol MetaClass with genericSafeCasts enabled
  • KT-85508 K/N: TypeCastException when using nw_parameters_create_secure_tcp block parameter on 2.3.20

Tools. Gradle

  • KT-84729 Update Gradle plugin-publish version to enable configuration cache badge on Gradle plugins portal

Tools. Gradle. Compiler plugins

  • KT-85257 AGP/Compose: MergeMappingFileTask clears R8 artifacts due to @OutputDirectory annotation on AGP 9.1+

Tools. Scripts

  • KT-85105 Scripts: JVM backend internal error (IR lowering) when scratch file contains anonymous object
  • KT-85103 Exception while generating code when explain destructuring decls
  • KT-84842 scriptCompilationClasspathFromContext behavior changed from 2.3.10 to 2.3.20
  • KT-85029 Kotlin Scripting: ScriptDiagnostic reports "at null" instead of error location

Tools. Statistics (FUS)

... (truncated)

Changelog

Sourced from org.jetbrains.kotlin.plugin.serialization's changelog.

2.3.21

Backend. Wasm

  • KT-84610 [Wasm] Failed to compile klibs in IC mode

Compiler

  • KT-84566 Prevent launching Default dispatcher threads from IJ SDK in kotlin compiler
  • KT-85358 Native: roll back the workaround for KT-84678 once MapLibre has been properly fixed
  • KT-85626 @JvmRecord in commonMain breaks compileCommonMainKotlinMetadata with "Cannot access 'java.lang.Record'"
  • KT-85405 Postpone/Revert DontIgnoreUpperBoundViolatedOnImplicitArguments
  • KT-84678 K/N: Undefined symbol from SPM-added ObjC frameworks when linking iOS target
  • KT-85021 False positive SUBCLASS_CANT_CALL_COMPANION_PROTECTED_NON_STATIC error in multi-module project

JavaScript

  • KT-82395 Support top-level declarations from compiler plugins in JS incremental compilation
  • KT-84475 K/JS: false-positive exportability warnings in multi-module project
  • KT-84633 Kotlin/JS: "Serializer for class not found" error when IR output granularity is whole-program
  • KT-85047 Kotlin/JS: @JsStatic on suspend fun of class companion generates incorrect d.ts
  • KT-84517 K/JS: bad mappings data in outputted Kotlin stdlib source map

Libraries

  • KT-71848 Kotlinx.metadata: Add CompilerPluginData into Km API

Native. C and ObjC Import

  • KT-85399 Kotlin/Native: TypeCastException when casting ObjC Protocol MetaClass with genericSafeCasts enabled
  • KT-85508 K/N: TypeCastException when using nw_parameters_create_secure_tcp block parameter on 2.3.20

Tools. Gradle

  • KT-84729 Update Gradle plugin-publish version to enable configuration cache badge on Gradle plugins portal

Tools. Gradle. Compiler plugins

  • KT-85257 AGP/Compose: MergeMappingFileTask clears R8 artifacts due to @OutputDirectory annotation on AGP 9.1+

Tools. Scripts

  • KT-85105 Scripts: JVM backend internal error (IR lowering) when scratch file contains anonymous object
  • KT-85103 Exception while generating code when explain destructuring decls
  • KT-84842 scriptCompilationClasspathFromContext behavior changed from 2.3.10 to 2.3.20
  • KT-85029 Kotlin Scripting: ScriptDiagnostic reports "at null" instead of error location

Tools. Statistics (FUS)

  • KT-85628 KGP: composite build FUS metrics fail on access of 'configurationTimeMetrics'

... (truncated)

Commits
  • fea1ad8 Add ChangeLog for 2.3.21-RC2
  • 09c341e disable swift export execution tests in order to update macos
  • 67a0868 Avoid accessing KotlinNativeLink taskProvider when task was not executed
  • f89e5db [K/N] Disable TSAN in runtime tests
  • 45d6c85 [K/N] Don't generate generic safe casts for Objective-C types
  • 9261a6f [K/N][tests] Add a reproducer for KT-85508
  • c9ab9db [K/N][tests] Add a reproducer for KT-85399
  • 502e844 Explain: fix for destructuring declarations
  • 0c26485 Explain: fix for object literals
  • 68a9e3f [minor] fix testdata name in explain test
  • Additional commits viewable in compare view

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/simple-streamable-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/simple-streamable-server/gradle/libs.versions.toml b/samples/simple-streamable-server/gradle/libs.versions.toml index 0cca026..dc668c0 100644 --- a/samples/simple-streamable-server/gradle/libs.versions.toml +++ b/samples/simple-streamable-server/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin = "2.2.21" +kotlin = "2.3.21" ktor = "3.3.3" junit = "6.0.3" mcp-kotlin = "0.11.1" From 3c917cc97a97f9a8aea1169a30308c67a2cdf338 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 16:21:38 +0000 Subject: [PATCH 261/449] chore(deps): bump com.anthropic:anthropic-java from 2.26.0 to 2.27.0 in /samples/kotlin-mcp-client in the other-dependencies group across 1 directory (#719) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [//]: # (dependabot-start) ⚠️ **Dependabot is rebasing this PR** ⚠️ Rebasing might not happen immediately, so don't worry if this takes some time. Note: if you make any changes to this PR yourself, they will take precedence over the rebase. --- [//]: # (dependabot-end) Bumps the other-dependencies group with 1 update in the /samples/kotlin-mcp-client directory: [com.anthropic:anthropic-java](https://github.com/anthropics/anthropic-sdk-java). Updates `com.anthropic:anthropic-java` from 2.26.0 to 2.27.0
Release notes

Sourced from com.anthropic:anthropic-java's releases.

v2.27.0

2.27.0 (2026-04-23)

Full Changelog: v2.26.0...v2.27.0

Features

  • api: CMA Memory public beta (d95526a)

Bug Fixes

  • api: fix errors in api spec (9d5a18a)
  • api: restore missing features (e737fbf)

Chores

Changelog

Sourced from com.anthropic:anthropic-java's changelog.

2.27.0 (2026-04-23)

Full Changelog: v2.26.0...v2.27.0

Features

  • api: CMA Memory public beta (d95526a)

Bug Fixes

  • api: fix errors in api spec (9d5a18a)
  • api: restore missing features (e737fbf)

Chores

Commits

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-client/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index 5de32f0..bbb6bb7 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -anthropic = "2.26.0" +anthropic = "2.27.0" kotlin = "2.2.21" ktor = "3.3.3" mcp-kotlin = "0.11.1" From 9817b174f9e1a4f3ebf6855e41ff666b53a04b1c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 16:39:46 +0000 Subject: [PATCH 262/449] chore(deps): bump org.jetbrains.kotlin.jvm from 2.2.21 to 2.3.21 in /samples/kotlin-mcp-client (#721) Bumps [org.jetbrains.kotlin.jvm](https://github.com/JetBrains/kotlin) from 2.2.21 to 2.3.21.
Changelog

Sourced from org.jetbrains.kotlin.jvm's changelog.

2.3.21

Backend. Wasm

  • KT-84610 [Wasm] Failed to compile klibs in IC mode

Compiler

  • KT-84566 Prevent launching Default dispatcher threads from IJ SDK in kotlin compiler
  • KT-85358 Native: roll back the workaround for KT-84678 once MapLibre has been properly fixed
  • KT-85626 @JvmRecord in commonMain breaks compileCommonMainKotlinMetadata with "Cannot access 'java.lang.Record'"
  • KT-85405 Postpone/Revert DontIgnoreUpperBoundViolatedOnImplicitArguments
  • KT-84678 K/N: Undefined symbol from SPM-added ObjC frameworks when linking iOS target
  • KT-85021 False positive SUBCLASS_CANT_CALL_COMPANION_PROTECTED_NON_STATIC error in multi-module project

JavaScript

  • KT-82395 Support top-level declarations from compiler plugins in JS incremental compilation
  • KT-84475 K/JS: false-positive exportability warnings in multi-module project
  • KT-84633 Kotlin/JS: "Serializer for class not found" error when IR output granularity is whole-program
  • KT-85047 Kotlin/JS: @JsStatic on suspend fun of class companion generates incorrect d.ts
  • KT-84517 K/JS: bad mappings data in outputted Kotlin stdlib source map

Libraries

  • KT-71848 Kotlinx.metadata: Add CompilerPluginData into Km API

Native. C and ObjC Import

  • KT-85399 Kotlin/Native: TypeCastException when casting ObjC Protocol MetaClass with genericSafeCasts enabled
  • KT-85508 K/N: TypeCastException when using nw_parameters_create_secure_tcp block parameter on 2.3.20

Tools. Gradle

  • KT-84729 Update Gradle plugin-publish version to enable configuration cache badge on Gradle plugins portal

Tools. Gradle. Compiler plugins

  • KT-85257 AGP/Compose: MergeMappingFileTask clears R8 artifacts due to @OutputDirectory annotation on AGP 9.1+

Tools. Scripts

  • KT-85105 Scripts: JVM backend internal error (IR lowering) when scratch file contains anonymous object
  • KT-85103 Exception while generating code when explain destructuring decls
  • KT-84842 scriptCompilationClasspathFromContext behavior changed from 2.3.10 to 2.3.20
  • KT-85029 Kotlin Scripting: ScriptDiagnostic reports "at null" instead of error location

Tools. Statistics (FUS)

  • KT-85628 KGP: composite build FUS metrics fail on access of 'configurationTimeMetrics'

... (truncated)

Commits
  • fea1ad8 Add ChangeLog for 2.3.21-RC2
  • 09c341e disable swift export execution tests in order to update macos
  • 67a0868 Avoid accessing KotlinNativeLink taskProvider when task was not executed
  • f89e5db [K/N] Disable TSAN in runtime tests
  • 45d6c85 [K/N] Don't generate generic safe casts for Objective-C types
  • 9261a6f [K/N][tests] Add a reproducer for KT-85508
  • c9ab9db [K/N][tests] Add a reproducer for KT-85399
  • 502e844 Explain: fix for destructuring declarations
  • 0c26485 Explain: fix for object literals
  • 68a9e3f [minor] fix testdata name in explain test
  • Additional commits viewable in compare view

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-client/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index bbb6bb7..69ebdde 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] anthropic = "2.27.0" -kotlin = "2.2.21" +kotlin = "2.3.21" ktor = "3.3.3" mcp-kotlin = "0.11.1" shadow = "9.4.1" From 1d9304264e3a9d81e0d84511fdae683abc7f6fef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 21:30:51 +0200 Subject: [PATCH 263/449] chore(deps): bump kotlin from 2.2.21 to 2.3.21 in /samples/kotlin-mcp-server (#722) Bumps `kotlin` from 2.2.21 to 2.3.21. Updates `org.jetbrains.kotlin.jvm` from 2.2.21 to 2.3.21
Release notes

Sourced from org.jetbrains.kotlin.jvm's releases.

Kotlin 2.3.21

Changelog

Backend. Wasm

  • KT-84610 [Wasm] Failed to compile klibs in IC mode

Compiler

  • KT-84566 Prevent launching Default dispatcher threads from IJ SDK in kotlin compiler
  • KT-85358 Native: roll back the workaround for KT-84678 once MapLibre has been properly fixed
  • KT-85626 @JvmRecord in commonMain breaks compileCommonMainKotlinMetadata with "Cannot access 'java.lang.Record'"
  • KT-85405 Postpone/Revert DontIgnoreUpperBoundViolatedOnImplicitArguments
  • KT-84678 K/N: Undefined symbol from SPM-added ObjC frameworks when linking iOS target
  • KT-85021 False positive SUBCLASS_CANT_CALL_COMPANION_PROTECTED_NON_STATIC error in multi-module project

JavaScript

  • KT-82395 Support top-level declarations from compiler plugins in JS incremental compilation
  • KT-84475 K/JS: false-positive exportability warnings in multi-module project
  • KT-84633 Kotlin/JS: "Serializer for class not found" error when IR output granularity is whole-program
  • KT-85047 Kotlin/JS: @JsStatic on suspend fun of class companion generates incorrect d.ts
  • KT-84517 K/JS: bad mappings data in outputted Kotlin stdlib source map

Libraries

  • KT-71848 Kotlinx.metadata: Add CompilerPluginData into Km API

Native. C and ObjC Import

  • KT-85399 Kotlin/Native: TypeCastException when casting ObjC Protocol MetaClass with genericSafeCasts enabled
  • KT-85508 K/N: TypeCastException when using nw_parameters_create_secure_tcp block parameter on 2.3.20

Tools. Gradle

  • KT-84729 Update Gradle plugin-publish version to enable configuration cache badge on Gradle plugins portal

Tools. Gradle. Compiler plugins

  • KT-85257 AGP/Compose: MergeMappingFileTask clears R8 artifacts due to @OutputDirectory annotation on AGP 9.1+

Tools. Scripts

  • KT-85105 Scripts: JVM backend internal error (IR lowering) when scratch file contains anonymous object
  • KT-85103 Exception while generating code when explain destructuring decls
  • KT-84842 scriptCompilationClasspathFromContext behavior changed from 2.3.10 to 2.3.20
  • KT-85029 Kotlin Scripting: ScriptDiagnostic reports "at null" instead of error location

Tools. Statistics (FUS)

... (truncated)

Changelog

Sourced from org.jetbrains.kotlin.jvm's changelog.

2.3.21

Backend. Wasm

  • KT-84610 [Wasm] Failed to compile klibs in IC mode

Compiler

  • KT-84566 Prevent launching Default dispatcher threads from IJ SDK in kotlin compiler
  • KT-85358 Native: roll back the workaround for KT-84678 once MapLibre has been properly fixed
  • KT-85626 @JvmRecord in commonMain breaks compileCommonMainKotlinMetadata with "Cannot access 'java.lang.Record'"
  • KT-85405 Postpone/Revert DontIgnoreUpperBoundViolatedOnImplicitArguments
  • KT-84678 K/N: Undefined symbol from SPM-added ObjC frameworks when linking iOS target
  • KT-85021 False positive SUBCLASS_CANT_CALL_COMPANION_PROTECTED_NON_STATIC error in multi-module project

JavaScript

  • KT-82395 Support top-level declarations from compiler plugins in JS incremental compilation
  • KT-84475 K/JS: false-positive exportability warnings in multi-module project
  • KT-84633 Kotlin/JS: "Serializer for class not found" error when IR output granularity is whole-program
  • KT-85047 Kotlin/JS: @JsStatic on suspend fun of class companion generates incorrect d.ts
  • KT-84517 K/JS: bad mappings data in outputted Kotlin stdlib source map

Libraries

  • KT-71848 Kotlinx.metadata: Add CompilerPluginData into Km API

Native. C and ObjC Import

  • KT-85399 Kotlin/Native: TypeCastException when casting ObjC Protocol MetaClass with genericSafeCasts enabled
  • KT-85508 K/N: TypeCastException when using nw_parameters_create_secure_tcp block parameter on 2.3.20

Tools. Gradle

  • KT-84729 Update Gradle plugin-publish version to enable configuration cache badge on Gradle plugins portal

Tools. Gradle. Compiler plugins

  • KT-85257 AGP/Compose: MergeMappingFileTask clears R8 artifacts due to @OutputDirectory annotation on AGP 9.1+

Tools. Scripts

  • KT-85105 Scripts: JVM backend internal error (IR lowering) when scratch file contains anonymous object
  • KT-85103 Exception while generating code when explain destructuring decls
  • KT-84842 scriptCompilationClasspathFromContext behavior changed from 2.3.10 to 2.3.20
  • KT-85029 Kotlin Scripting: ScriptDiagnostic reports "at null" instead of error location

Tools. Statistics (FUS)

  • KT-85628 KGP: composite build FUS metrics fail on access of 'configurationTimeMetrics'

... (truncated)

Commits
  • fea1ad8 Add ChangeLog for 2.3.21-RC2
  • 09c341e disable swift export execution tests in order to update macos
  • 67a0868 Avoid accessing KotlinNativeLink taskProvider when task was not executed
  • f89e5db [K/N] Disable TSAN in runtime tests
  • 45d6c85 [K/N] Don't generate generic safe casts for Objective-C types
  • 9261a6f [K/N][tests] Add a reproducer for KT-85508
  • c9ab9db [K/N][tests] Add a reproducer for KT-85399
  • 502e844 Explain: fix for destructuring declarations
  • 0c26485 Explain: fix for object literals
  • 68a9e3f [minor] fix testdata name in explain test
  • Additional commits viewable in compare view

Updates `org.jetbrains.kotlin.plugin.serialization` from 2.2.21 to 2.3.21
Release notes

Sourced from org.jetbrains.kotlin.plugin.serialization's releases.

Kotlin 2.3.21

Changelog

Backend. Wasm

  • KT-84610 [Wasm] Failed to compile klibs in IC mode

Compiler

  • KT-84566 Prevent launching Default dispatcher threads from IJ SDK in kotlin compiler
  • KT-85358 Native: roll back the workaround for KT-84678 once MapLibre has been properly fixed
  • KT-85626 @JvmRecord in commonMain breaks compileCommonMainKotlinMetadata with "Cannot access 'java.lang.Record'"
  • KT-85405 Postpone/Revert DontIgnoreUpperBoundViolatedOnImplicitArguments
  • KT-84678 K/N: Undefined symbol from SPM-added ObjC frameworks when linking iOS target
  • KT-85021 False positive SUBCLASS_CANT_CALL_COMPANION_PROTECTED_NON_STATIC error in multi-module project

JavaScript

  • KT-82395 Support top-level declarations from compiler plugins in JS incremental compilation
  • KT-84475 K/JS: false-positive exportability warnings in multi-module project
  • KT-84633 Kotlin/JS: "Serializer for class not found" error when IR output granularity is whole-program
  • KT-85047 Kotlin/JS: @JsStatic on suspend fun of class companion generates incorrect d.ts
  • KT-84517 K/JS: bad mappings data in outputted Kotlin stdlib source map

Libraries

  • KT-71848 Kotlinx.metadata: Add CompilerPluginData into Km API

Native. C and ObjC Import

  • KT-85399 Kotlin/Native: TypeCastException when casting ObjC Protocol MetaClass with genericSafeCasts enabled
  • KT-85508 K/N: TypeCastException when using nw_parameters_create_secure_tcp block parameter on 2.3.20

Tools. Gradle

  • KT-84729 Update Gradle plugin-publish version to enable configuration cache badge on Gradle plugins portal

Tools. Gradle. Compiler plugins

  • KT-85257 AGP/Compose: MergeMappingFileTask clears R8 artifacts due to @OutputDirectory annotation on AGP 9.1+

Tools. Scripts

  • KT-85105 Scripts: JVM backend internal error (IR lowering) when scratch file contains anonymous object
  • KT-85103 Exception while generating code when explain destructuring decls
  • KT-84842 scriptCompilationClasspathFromContext behavior changed from 2.3.10 to 2.3.20
  • KT-85029 Kotlin Scripting: ScriptDiagnostic reports "at null" instead of error location

Tools. Statistics (FUS)

... (truncated)

Changelog

Sourced from org.jetbrains.kotlin.plugin.serialization's changelog.

2.3.21

Backend. Wasm

  • KT-84610 [Wasm] Failed to compile klibs in IC mode

Compiler

  • KT-84566 Prevent launching Default dispatcher threads from IJ SDK in kotlin compiler
  • KT-85358 Native: roll back the workaround for KT-84678 once MapLibre has been properly fixed
  • KT-85626 @JvmRecord in commonMain breaks compileCommonMainKotlinMetadata with "Cannot access 'java.lang.Record'"
  • KT-85405 Postpone/Revert DontIgnoreUpperBoundViolatedOnImplicitArguments
  • KT-84678 K/N: Undefined symbol from SPM-added ObjC frameworks when linking iOS target
  • KT-85021 False positive SUBCLASS_CANT_CALL_COMPANION_PROTECTED_NON_STATIC error in multi-module project

JavaScript

  • KT-82395 Support top-level declarations from compiler plugins in JS incremental compilation
  • KT-84475 K/JS: false-positive exportability warnings in multi-module project
  • KT-84633 Kotlin/JS: "Serializer for class not found" error when IR output granularity is whole-program
  • KT-85047 Kotlin/JS: @JsStatic on suspend fun of class companion generates incorrect d.ts
  • KT-84517 K/JS: bad mappings data in outputted Kotlin stdlib source map

Libraries

  • KT-71848 Kotlinx.metadata: Add CompilerPluginData into Km API

Native. C and ObjC Import

  • KT-85399 Kotlin/Native: TypeCastException when casting ObjC Protocol MetaClass with genericSafeCasts enabled
  • KT-85508 K/N: TypeCastException when using nw_parameters_create_secure_tcp block parameter on 2.3.20

Tools. Gradle

  • KT-84729 Update Gradle plugin-publish version to enable configuration cache badge on Gradle plugins portal

Tools. Gradle. Compiler plugins

  • KT-85257 AGP/Compose: MergeMappingFileTask clears R8 artifacts due to @OutputDirectory annotation on AGP 9.1+

Tools. Scripts

  • KT-85105 Scripts: JVM backend internal error (IR lowering) when scratch file contains anonymous object
  • KT-85103 Exception while generating code when explain destructuring decls
  • KT-84842 scriptCompilationClasspathFromContext behavior changed from 2.3.10 to 2.3.20
  • KT-85029 Kotlin Scripting: ScriptDiagnostic reports "at null" instead of error location

Tools. Statistics (FUS)

  • KT-85628 KGP: composite build FUS metrics fail on access of 'configurationTimeMetrics'

... (truncated)

Commits
  • fea1ad8 Add ChangeLog for 2.3.21-RC2
  • 09c341e disable swift export execution tests in order to update macos
  • 67a0868 Avoid accessing KotlinNativeLink taskProvider when task was not executed
  • f89e5db [K/N] Disable TSAN in runtime tests
  • 45d6c85 [K/N] Don't generate generic safe casts for Objective-C types
  • 9261a6f [K/N][tests] Add a reproducer for KT-85508
  • c9ab9db [K/N][tests] Add a reproducer for KT-85399
  • 502e844 Explain: fix for destructuring declarations
  • 0c26485 Explain: fix for object literals
  • 68a9e3f [minor] fix testdata name in explain test
  • Additional commits viewable in compare view

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-server/gradle/libs.versions.toml b/samples/kotlin-mcp-server/gradle/libs.versions.toml index 3a03aed..a6c07dc 100644 --- a/samples/kotlin-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-server/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin = "2.2.21" +kotlin = "2.3.21" ktor = "3.3.3" mcp-kotlin = "0.11.1" slf4j = "2.0.17" From ec606dbc2483e80d2a82a6b27b1588a828f29d9e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 22:07:10 +0200 Subject: [PATCH 264/449] chore(deps): bump kotlin from 2.2.21 to 2.3.21 in /samples/weather-stdio-server (#726) Bumps `kotlin` from 2.2.21 to 2.3.21. Updates `org.jetbrains.kotlin.jvm` from 2.2.21 to 2.3.21
Changelog

Sourced from org.jetbrains.kotlin.jvm's changelog.

2.3.21

Backend. Wasm

  • KT-84610 [Wasm] Failed to compile klibs in IC mode

Compiler

  • KT-84566 Prevent launching Default dispatcher threads from IJ SDK in kotlin compiler
  • KT-85358 Native: roll back the workaround for KT-84678 once MapLibre has been properly fixed
  • KT-85626 @JvmRecord in commonMain breaks compileCommonMainKotlinMetadata with "Cannot access 'java.lang.Record'"
  • KT-85405 Postpone/Revert DontIgnoreUpperBoundViolatedOnImplicitArguments
  • KT-84678 K/N: Undefined symbol from SPM-added ObjC frameworks when linking iOS target
  • KT-85021 False positive SUBCLASS_CANT_CALL_COMPANION_PROTECTED_NON_STATIC error in multi-module project

JavaScript

  • KT-82395 Support top-level declarations from compiler plugins in JS incremental compilation
  • KT-84475 K/JS: false-positive exportability warnings in multi-module project
  • KT-84633 Kotlin/JS: "Serializer for class not found" error when IR output granularity is whole-program
  • KT-85047 Kotlin/JS: @JsStatic on suspend fun of class companion generates incorrect d.ts
  • KT-84517 K/JS: bad mappings data in outputted Kotlin stdlib source map

Libraries

  • KT-71848 Kotlinx.metadata: Add CompilerPluginData into Km API

Native. C and ObjC Import

  • KT-85399 Kotlin/Native: TypeCastException when casting ObjC Protocol MetaClass with genericSafeCasts enabled
  • KT-85508 K/N: TypeCastException when using nw_parameters_create_secure_tcp block parameter on 2.3.20

Tools. Gradle

  • KT-84729 Update Gradle plugin-publish version to enable configuration cache badge on Gradle plugins portal

Tools. Gradle. Compiler plugins

  • KT-85257 AGP/Compose: MergeMappingFileTask clears R8 artifacts due to @OutputDirectory annotation on AGP 9.1+

Tools. Scripts

  • KT-85105 Scripts: JVM backend internal error (IR lowering) when scratch file contains anonymous object
  • KT-85103 Exception while generating code when explain destructuring decls
  • KT-84842 scriptCompilationClasspathFromContext behavior changed from 2.3.10 to 2.3.20
  • KT-85029 Kotlin Scripting: ScriptDiagnostic reports "at null" instead of error location

Tools. Statistics (FUS)

  • KT-85628 KGP: composite build FUS metrics fail on access of 'configurationTimeMetrics'

... (truncated)

Commits
  • fea1ad8 Add ChangeLog for 2.3.21-RC2
  • 09c341e disable swift export execution tests in order to update macos
  • 67a0868 Avoid accessing KotlinNativeLink taskProvider when task was not executed
  • f89e5db [K/N] Disable TSAN in runtime tests
  • 45d6c85 [K/N] Don't generate generic safe casts for Objective-C types
  • 9261a6f [K/N][tests] Add a reproducer for KT-85508
  • c9ab9db [K/N][tests] Add a reproducer for KT-85399
  • 502e844 Explain: fix for destructuring declarations
  • 0c26485 Explain: fix for object literals
  • 68a9e3f [minor] fix testdata name in explain test
  • Additional commits viewable in compare view

Updates `org.jetbrains.kotlin.plugin.serialization` from 2.2.21 to 2.3.21
Changelog

Sourced from org.jetbrains.kotlin.plugin.serialization's changelog.

2.3.21

Backend. Wasm

  • KT-84610 [Wasm] Failed to compile klibs in IC mode

Compiler

  • KT-84566 Prevent launching Default dispatcher threads from IJ SDK in kotlin compiler
  • KT-85358 Native: roll back the workaround for KT-84678 once MapLibre has been properly fixed
  • KT-85626 @JvmRecord in commonMain breaks compileCommonMainKotlinMetadata with "Cannot access 'java.lang.Record'"
  • KT-85405 Postpone/Revert DontIgnoreUpperBoundViolatedOnImplicitArguments
  • KT-84678 K/N: Undefined symbol from SPM-added ObjC frameworks when linking iOS target
  • KT-85021 False positive SUBCLASS_CANT_CALL_COMPANION_PROTECTED_NON_STATIC error in multi-module project

JavaScript

  • KT-82395 Support top-level declarations from compiler plugins in JS incremental compilation
  • KT-84475 K/JS: false-positive exportability warnings in multi-module project
  • KT-84633 Kotlin/JS: "Serializer for class not found" error when IR output granularity is whole-program
  • KT-85047 Kotlin/JS: @JsStatic on suspend fun of class companion generates incorrect d.ts
  • KT-84517 K/JS: bad mappings data in outputted Kotlin stdlib source map

Libraries

  • KT-71848 Kotlinx.metadata: Add CompilerPluginData into Km API

Native. C and ObjC Import

  • KT-85399 Kotlin/Native: TypeCastException when casting ObjC Protocol MetaClass with genericSafeCasts enabled
  • KT-85508 K/N: TypeCastException when using nw_parameters_create_secure_tcp block parameter on 2.3.20

Tools. Gradle

  • KT-84729 Update Gradle plugin-publish version to enable configuration cache badge on Gradle plugins portal

Tools. Gradle. Compiler plugins

  • KT-85257 AGP/Compose: MergeMappingFileTask clears R8 artifacts due to @OutputDirectory annotation on AGP 9.1+

Tools. Scripts

  • KT-85105 Scripts: JVM backend internal error (IR lowering) when scratch file contains anonymous object
  • KT-85103 Exception while generating code when explain destructuring decls
  • KT-84842 scriptCompilationClasspathFromContext behavior changed from 2.3.10 to 2.3.20
  • KT-85029 Kotlin Scripting: ScriptDiagnostic reports "at null" instead of error location

Tools. Statistics (FUS)

  • KT-85628 KGP: composite build FUS metrics fail on access of 'configurationTimeMetrics'

... (truncated)

Commits
  • fea1ad8 Add ChangeLog for 2.3.21-RC2
  • 09c341e disable swift export execution tests in order to update macos
  • 67a0868 Avoid accessing KotlinNativeLink taskProvider when task was not executed
  • f89e5db [K/N] Disable TSAN in runtime tests
  • 45d6c85 [K/N] Don't generate generic safe casts for Objective-C types
  • 9261a6f [K/N][tests] Add a reproducer for KT-85508
  • c9ab9db [K/N][tests] Add a reproducer for KT-85399
  • 502e844 Explain: fix for destructuring declarations
  • 0c26485 Explain: fix for object literals
  • 68a9e3f [minor] fix testdata name in explain test
  • Additional commits viewable in compare view

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/weather-stdio-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/weather-stdio-server/gradle/libs.versions.toml b/samples/weather-stdio-server/gradle/libs.versions.toml index 839a283..19874f4 100644 --- a/samples/weather-stdio-server/gradle/libs.versions.toml +++ b/samples/weather-stdio-server/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] coroutines = "1.10.2" -kotlin = "2.2.21" +kotlin = "2.3.21" ktor = "3.3.3" logging = "8.0.01" mcp-kotlin = "0.11.1" From 12ace760cb9e40c820be14ccd6f278f08e1115b5 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Tue, 28 Apr 2026 17:10:11 +0200 Subject: [PATCH 265/449] chore: remove redundant JUnit XML report configuration (#728) The `junitXml.required`, `includeSystemOutLog` and `includeSystemErrLog` properties all default to `true` in Gradle JUnitXmlReport, so setting them explicitly was a no-op. The latter two are also @Incubating API --- buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts b/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts index a331139..2903cff 100644 --- a/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts +++ b/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts @@ -45,11 +45,6 @@ kotlin { events("failed") } systemProperty("kotest.output.ansi", "true") - reports { - junitXml.required.set(true) - junitXml.includeSystemOutLog.set(true) - junitXml.includeSystemErrLog.set(true) - } } } macosArm64() From 7474a33c5641b0096ffd84d7aeb9181f4aa976fb Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Tue, 28 Apr 2026 17:30:32 +0200 Subject: [PATCH 266/449] chore: drop deprecated watchosX64 and tvosX64 targets (#727) Drop the deprecated `watchosX64` and `tvosX64` ## Breaking Changes consumers that depended on `watchosX64` or `tvosX64` artifacts will need to migrate to the corresponding ARM/simulator targets. ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [x] 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 --- kotlin-sdk-client/build.gradle.kts | 2 -- kotlin-sdk-core/build.gradle.kts | 2 -- kotlin-sdk-testing/build.gradle.kts | 2 -- test-utils/build.gradle.kts | 2 -- 4 files changed, 8 deletions(-) diff --git a/kotlin-sdk-client/build.gradle.kts b/kotlin-sdk-client/build.gradle.kts index f50266c..a3645b7 100644 --- a/kotlin-sdk-client/build.gradle.kts +++ b/kotlin-sdk-client/build.gradle.kts @@ -14,10 +14,8 @@ kotlin { iosArm64() iosX64() iosSimulatorArm64() - watchosX64() watchosArm64() watchosSimulatorArm64() - tvosX64() tvosArm64() tvosSimulatorArm64() js { diff --git a/kotlin-sdk-core/build.gradle.kts b/kotlin-sdk-core/build.gradle.kts index f4a2314..967dae3 100644 --- a/kotlin-sdk-core/build.gradle.kts +++ b/kotlin-sdk-core/build.gradle.kts @@ -35,10 +35,8 @@ kotlin { iosArm64() iosX64() iosSimulatorArm64() - watchosX64() watchosArm64() watchosSimulatorArm64() - tvosX64() tvosArm64() tvosSimulatorArm64() js { diff --git a/kotlin-sdk-testing/build.gradle.kts b/kotlin-sdk-testing/build.gradle.kts index 902665c..e318f6f 100644 --- a/kotlin-sdk-testing/build.gradle.kts +++ b/kotlin-sdk-testing/build.gradle.kts @@ -14,10 +14,8 @@ kotlin { iosArm64() iosX64() iosSimulatorArm64() - watchosX64() watchosArm64() watchosSimulatorArm64() - tvosX64() tvosArm64() tvosSimulatorArm64() js { diff --git a/test-utils/build.gradle.kts b/test-utils/build.gradle.kts index 91574e0..6e91257 100644 --- a/test-utils/build.gradle.kts +++ b/test-utils/build.gradle.kts @@ -11,10 +11,8 @@ kotlin { iosArm64() iosX64() iosSimulatorArm64() - watchosX64() watchosArm64() watchosSimulatorArm64() - tvosX64() tvosArm64() tvosSimulatorArm64() js { From f434fcaff13777dcdf9d876979ff6b0b3f4507f2 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Tue, 28 Apr 2026 17:51:48 +0200 Subject: [PATCH 267/449] feat!: SEP-1577 sampling with tools (#718) Implements SEP-1577 sampling With tools closes #409 ## How Has This Been Tested? unit + integration ## Breaking Changes - binary changes - MediaContent -> SamplingMessageContent - ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [x] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- .../sdk/conformance/ConformanceTools.kt | 4 +- .../kotlin/sdk/client/ClientTest.kt | 16 +- .../kotlin/sdk/server/SamplingTest.kt | 251 ++++++++++++++++++ kotlin-sdk-client/api/kotlin-sdk-client.api | 1 + .../kotlin/sdk/client/Client.kt | 24 ++ .../kotlin/sdk/client/SamplingValidation.kt | 24 ++ .../client/ClientSamplingValidationTest.kt | 98 +++++++ kotlin-sdk-core/api/kotlin-sdk-core.api | 219 +++++++++++++-- .../kotlin/sdk/shared/Protocol.kt | 17 +- .../kotlin/sdk/types/capabilities.dsl.kt | 30 +-- .../kotlin/sdk/types/capabilities.kt | 47 +++- .../kotlin/sdk/types/content.kt | 73 ++++- .../kotlin/sdk/types/initialize.dsl.kt | 8 +- .../kotlin/sdk/types/sampling.dsl.kt | 8 +- .../kotlin/sdk/types/sampling.kt | 89 ++++++- .../kotlin/sdk/types/serializers.kt | 100 +++++++ .../kotlin/sdk/types/CapabilitiesTest.kt | 63 ++++- .../kotlin/sdk/types/ContentTest.kt | 138 ++++++++++ .../kotlin/sdk/types/InitializeTest.kt | 2 +- .../kotlin/sdk/types/SamplingTest.kt | 238 ++++++++++++++++- .../sdk/types/dsl/CapabilitiesDslTest.kt | 18 +- .../kotlin/sdk/types/dsl/ContentDslTest.kt | 24 +- .../kotlin/sdk/types/dsl/InitializeDslTest.kt | 15 +- .../kotlin/sdk/types/dsl/SamplingDslTest.kt | 12 +- .../kotlin/sdk/server/ClientConnection.kt | 32 ++- .../kotlin/sdk/server/SamplingValidation.kt | 51 ++++ .../kotlin/sdk/server/SamplingTest.kt | 86 ++++++ 27 files changed, 1553 insertions(+), 135 deletions(-) create mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SamplingTest.kt create mode 100644 kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SamplingValidation.kt create mode 100644 kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientSamplingValidationTest.kt create mode 100644 kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SamplingValidation.kt create mode 100644 kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SamplingTest.kt diff --git a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTools.kt b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTools.kt index 54d5c8c..dc126f6 100644 --- a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTools.kt +++ b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTools.kt @@ -176,11 +176,11 @@ fun Server.registerConformanceTools() { CreateMessageRequest( CreateMessageRequestParams( maxTokens = 10000, - messages = listOf(SamplingMessage(Role.User, TextContent(prompt))), + messages = listOf(SamplingMessage(Role.User, listOf(TextContent(prompt)))), ), ), ) - CallToolResult(listOf(TextContent(result.content.toString()))) + CallToolResult(listOf(TextContent(result.content.joinToString("\n") { it.toString() }))) } // 9. Elicitation diff --git a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt index d0acda5..e5f6638 100644 --- a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt +++ b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt @@ -106,7 +106,7 @@ class ClientTest { ), options = ClientOptions( capabilities = ClientCapabilities( - sampling = EmptyJsonObject, + sampling = ClientCapabilities.sampling, ), ), ) @@ -152,7 +152,7 @@ class ClientTest { ), options = ClientOptions( capabilities = ClientCapabilities( - sampling = EmptyJsonObject, + sampling = ClientCapabilities.sampling, ), ), ) @@ -339,7 +339,7 @@ class ClientTest { val client = Client( clientInfo = Implementation(name = "test client", version = "1.0"), options = ClientOptions( - capabilities = ClientCapabilities(sampling = EmptyJsonObject), + capabilities = ClientCapabilities(sampling = ClientCapabilities.sampling), ), ) @@ -633,18 +633,16 @@ class ClientTest { ), options = ClientOptions( capabilities = ClientCapabilities( - sampling = EmptyJsonObject, + sampling = ClientCapabilities.sampling, ), ), ) client.setRequestHandler(Method.Defined.SamplingCreateMessage) { _, _ -> CreateMessageResult( - model = "test-model", role = Role.Assistant, - content = TextContent( - text = "Test response", - ), + content = TextContent(text = "Test response"), + model = "test-model", ) } @@ -670,7 +668,7 @@ class ClientTest { val client = Client( clientInfo = Implementation(name = "test client", version = "1.0"), options = ClientOptions( - capabilities = ClientCapabilities(sampling = EmptyJsonObject), + capabilities = ClientCapabilities(sampling = ClientCapabilities.sampling), ), ) diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SamplingTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SamplingTest.kt new file mode 100644 index 0000000..f161d8e --- /dev/null +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SamplingTest.kt @@ -0,0 +1,251 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.client.ClientOptions +import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport +import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest +import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageResult +import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.IncludeContext +import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.Role +import io.modelcontextprotocol.kotlin.sdk.types.SamplingMessage +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.StopReason +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.sdk.types.Tool +import io.modelcontextprotocol.kotlin.sdk.types.ToolChoice +import io.modelcontextprotocol.kotlin.sdk.types.ToolResultContent +import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema +import io.modelcontextprotocol.kotlin.sdk.types.ToolUseContent +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.buildJsonObject +import org.junit.jupiter.api.assertDoesNotThrow +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class SamplingTest { + + private val dummyTool = Tool( + name = "t", + inputSchema = ToolSchema(properties = buildJsonObject { }, required = emptyList()), + ) + + private val weatherTool = Tool( + name = "get_weather", + description = "Return the current temperature in Celsius.", + inputSchema = ToolSchema( + properties = buildJsonObject { + put("location", buildJsonObject { put("type", JsonPrimitive("string")) }) + }, + required = listOf("location"), + ), + ) + + private val minimalMessages = listOf(SamplingMessage(Role.User, TextContent("hi"))) + + /** + * Builds a connected [Server]+[Client] pair using [InMemoryTransport]. + * + * @param clientCapabilities the capabilities the client advertises during initialize + * @param samplingHandler the handler the client uses to respond to sampling requests + * @return Pair of (server, sessionId) ready for [Server.createMessage] calls. + */ + private fun buildPair( + clientCapabilities: ClientCapabilities = ClientCapabilities( + sampling = ClientCapabilities.Sampling(), + ), + samplingHandler: (CreateMessageRequest) -> CreateMessageResult = { _ -> + CreateMessageResult(role = Role.Assistant, content = TextContent("ok"), model = "m") + }, + ): Pair { + val server = Server( + serverInfo = Implementation(name = "srv", version = "1.0"), + options = ServerOptions(capabilities = ServerCapabilities()), + ) + + val client = Client( + clientInfo = Implementation(name = "cli", version = "1.0"), + options = ClientOptions(capabilities = clientCapabilities), + ) + + client.setRequestHandler(Method.Defined.SamplingCreateMessage) { req, _ -> + samplingHandler(req) + } + + val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() + + var sessionId: String? = null + runBlocking { + val sessionDeferred = CompletableDeferred() + launch { client.connect(clientTransport) } + launch { + val session = server.createSession(serverTransport) + sessionDeferred.complete(session.sessionId) + } + sessionId = sessionDeferred.await() + } + + return Pair(server, checkNotNull(sessionId)) + } + + // ============================================================================ + // Server.createMessage — capability enforcement (SEP-1577) + // ============================================================================ + + @Test + fun `tools field rejected when client has no sampling tools capability`() { + val (server, sessionId) = buildPair() + assertFailsWith { + runBlocking { + server.createMessage( + sessionId = sessionId, + params = CreateMessageRequest( + params = CreateMessageRequestParams( + maxTokens = 100, + messages = minimalMessages, + tools = listOf(dummyTool), + ), + ), + ) + } + } + } + + @Test + fun `toolChoice field rejected when client has no sampling tools capability`() { + val (server, sessionId) = buildPair() + assertFailsWith { + runBlocking { + server.createMessage( + sessionId = sessionId, + params = CreateMessageRequest( + params = CreateMessageRequestParams( + maxTokens = 100, + messages = minimalMessages, + toolChoice = ToolChoice(), + ), + ), + ) + } + } + } + + @Test + fun `includeContext with no sampling context capability succeeds with a warning`() { + val (server, sessionId) = buildPair() + assertDoesNotThrow { + runBlocking { + server.createMessage( + sessionId = sessionId, + params = CreateMessageRequest( + params = CreateMessageRequestParams( + maxTokens = 100, + messages = minimalMessages, + includeContext = IncludeContext.ThisServer, + ), + ), + ) + } + } + } + + // ============================================================================ + // End-to-end tool-loop integration + // ============================================================================ + + @Test + fun `server sends tools, client returns tool_use then final text`() { + var turn = 0 + val (server, sessionId) = buildPair( + clientCapabilities = ClientCapabilities( + sampling = ClientCapabilities.Sampling(tools = EmptyJsonObject), + ), + samplingHandler = { _ -> + turn++ + if (turn == 1) { + CreateMessageResult( + role = Role.Assistant, + content = listOf( + TextContent("Let me check."), + ToolUseContent( + id = "call_1", + name = "get_weather", + input = buildJsonObject { put("location", JsonPrimitive("London")) }, + ), + ), + model = "test", + stopReason = StopReason.ToolUse, + ) + } else { + CreateMessageResult( + role = Role.Assistant, + content = TextContent("The temperature in London is 20°C."), + model = "test", + stopReason = StopReason.EndTurn, + ) + } + }, + ) + + runBlocking { + val messages = mutableListOf( + SamplingMessage(Role.User, TextContent("What is the weather in London?")), + ) + + // — Turn 1: server sends tools, expects tool_use stop reason + val first = server.createMessage( + sessionId = sessionId, + params = CreateMessageRequest( + params = CreateMessageRequestParams( + maxTokens = 256, + messages = messages.toList(), + tools = listOf(weatherTool), + toolChoice = ToolChoice(mode = ToolChoice.Mode.Auto), + ), + ), + ) + assertEquals(StopReason.ToolUse, first.stopReason) + assertEquals(2, first.content.size) + + // — Append assistant turn and inject tool result + messages.add(SamplingMessage(Role.Assistant, first.content)) + val toolUse = first.content.filterIsInstance().single() + messages.add( + SamplingMessage( + Role.User, + ToolResultContent( + toolUseId = toolUse.id, + content = listOf(TextContent("""{"tempC":20}""")), + ), + ), + ) + + // — Turn 2: server sends updated history, expects final text + val second = server.createMessage( + sessionId = sessionId, + params = CreateMessageRequest( + params = CreateMessageRequestParams( + maxTokens = 256, + messages = messages.toList(), + tools = listOf(weatherTool), + toolChoice = ToolChoice(mode = ToolChoice.Mode.Auto), + ), + ), + ) + assertEquals(StopReason.EndTurn, second.stopReason) + assertEquals(1, second.content.size) + val text = second.content.single() as TextContent + assertEquals("The temperature in London is 20°C.", text.text) + + server.close() + } + } +} diff --git a/kotlin-sdk-client/api/kotlin-sdk-client.api b/kotlin-sdk-client/api/kotlin-sdk-client.api index da0e752..8b6fb79 100644 --- a/kotlin-sdk-client/api/kotlin-sdk-client.api +++ b/kotlin-sdk-client/api/kotlin-sdk-client.api @@ -41,6 +41,7 @@ public class io/modelcontextprotocol/kotlin/sdk/client/Client : io/modelcontextp public static synthetic fun subscribeResource$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun unsubscribeResource (Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 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; + protected fun wrapRequestHandler (Lio/modelcontextprotocol/kotlin/sdk/types/Method;Lkotlin/jvm/functions/Function3;)Lkotlin/jvm/functions/Function3; } public final class io/modelcontextprotocol/kotlin/sdk/client/ClientKt { diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt index 34b9a5b..49b20d7 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt @@ -4,6 +4,7 @@ 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.RequestHandlerExtra import io.modelcontextprotocol.kotlin.sdk.shared.RequestOptions import io.modelcontextprotocol.kotlin.sdk.shared.Transport import io.modelcontextprotocol.kotlin.sdk.types.BooleanSchema @@ -13,6 +14,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities import io.modelcontextprotocol.kotlin.sdk.types.CompleteRequest import io.modelcontextprotocol.kotlin.sdk.types.CompleteResult +import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest import io.modelcontextprotocol.kotlin.sdk.types.DoubleSchema import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequest import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestFormParams @@ -45,7 +47,9 @@ import io.modelcontextprotocol.kotlin.sdk.types.PingRequest import io.modelcontextprotocol.kotlin.sdk.types.PrimitiveSchemaDefinition import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult +import io.modelcontextprotocol.kotlin.sdk.types.Request import io.modelcontextprotocol.kotlin.sdk.types.RequestMeta +import io.modelcontextprotocol.kotlin.sdk.types.RequestResult import io.modelcontextprotocol.kotlin.sdk.types.Root import io.modelcontextprotocol.kotlin.sdk.types.RootsListChangedNotification import io.modelcontextprotocol.kotlin.sdk.types.SUPPORTED_PROTOCOL_VERSIONS @@ -323,6 +327,26 @@ public open class Client(private val clientInfo: Implementation, options: Client } } + /** + * Wraps incoming-request handlers with SEP-1577 client-side enforcement. + * + * For `sampling/createMessage`: if the incoming request carries `tools` or + * `toolChoice` but this client did not advertise [ClientCapabilities.Sampling.tools], + * the wrapper throws an [McpException] with JSON-RPC error code `InvalidParams` + * before the user-supplied handler runs. Matches the TypeScript SDK wrapper in + * `Client.setRequestHandler`. + */ + override fun wrapRequestHandler( + method: Method, + block: suspend (T, RequestHandlerExtra) -> RequestResult?, + ): suspend (T, RequestHandlerExtra) -> RequestResult? { + if (method != Method.Defined.SamplingCreateMessage) return block + return { request, extra -> + (request as? CreateMessageRequest)?.let { validateSamplingToolsCapability(it, capabilities) } + block(request, extra) + } + } + /** * Sends a ping request to the server to check connectivity. * diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SamplingValidation.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SamplingValidation.kt new file mode 100644 index 0000000..1290cb0 --- /dev/null +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SamplingValidation.kt @@ -0,0 +1,24 @@ +package io.modelcontextprotocol.kotlin.sdk.client + +import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest +import io.modelcontextprotocol.kotlin.sdk.types.McpException +import io.modelcontextprotocol.kotlin.sdk.types.RPCError + +/** + * Validates SEP-1577 client-side enforcement: an incoming `sampling/createMessage` + * request that carries `tools` or `toolChoice` requires the client to have advertised + * the `sampling.tools` sub-capability. When the capability is missing, throws an + * [McpException] with JSON-RPC error code `InvalidParams`, matching TypeScript SDK + * client enforcement. + */ +internal fun validateSamplingToolsCapability(request: CreateMessageRequest, capabilities: ClientCapabilities) { + val params = request.params + if (params.tools == null && params.toolChoice == null) return + if (capabilities.sampling?.tools != null) return + val field = if (params.tools != null) "tools" else "toolChoice" + throw McpException( + code = RPCError.ErrorCode.INVALID_PARAMS, + message = "Client does not support sampling with tools but request contains $field parameter", + ) +} diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientSamplingValidationTest.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientSamplingValidationTest.kt new file mode 100644 index 0000000..7775a08 --- /dev/null +++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientSamplingValidationTest.kt @@ -0,0 +1,98 @@ +package io.modelcontextprotocol.kotlin.sdk.client + +import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest +import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject +import io.modelcontextprotocol.kotlin.sdk.types.McpException +import io.modelcontextprotocol.kotlin.sdk.types.RPCError +import io.modelcontextprotocol.kotlin.sdk.types.Role +import io.modelcontextprotocol.kotlin.sdk.types.SamplingMessage +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.sdk.types.Tool +import io.modelcontextprotocol.kotlin.sdk.types.ToolChoice +import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema +import kotlinx.serialization.json.buildJsonObject +import org.junit.jupiter.api.assertDoesNotThrow +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class ClientSamplingValidationTest { + + private val dummyTool = Tool( + name = "t", + inputSchema = ToolSchema(properties = buildJsonObject { }, required = emptyList()), + ) + + private val minimalMessages = listOf(SamplingMessage(Role.User, TextContent("hi"))) + + private val noToolsCaps = ClientCapabilities(sampling = ClientCapabilities.sampling) + private val withToolsCaps = ClientCapabilities( + sampling = ClientCapabilities.Sampling(tools = EmptyJsonObject), + ) + + @Test + fun `request without tools or toolChoice is always accepted`() { + val request = CreateMessageRequest( + CreateMessageRequestParams(maxTokens = 10, messages = minimalMessages), + ) + assertDoesNotThrow { validateSamplingToolsCapability(request, noToolsCaps) } + } + + @Test + fun `tools without sampling tools capability throws InvalidParams`() { + val request = CreateMessageRequest( + CreateMessageRequestParams( + maxTokens = 10, + messages = minimalMessages, + tools = listOf(dummyTool), + ), + ) + val exception = assertFailsWith { + validateSamplingToolsCapability(request, noToolsCaps) + } + assertEquals(RPCError.ErrorCode.INVALID_PARAMS, exception.code) + check(exception.message?.contains("tools") == true) + } + + @Test + fun `toolChoice without sampling tools capability throws InvalidParams`() { + val request = CreateMessageRequest( + CreateMessageRequestParams( + maxTokens = 10, + messages = minimalMessages, + toolChoice = ToolChoice(mode = ToolChoice.Mode.Required), + ), + ) + val exception = assertFailsWith { + validateSamplingToolsCapability(request, noToolsCaps) + } + assertEquals(RPCError.ErrorCode.INVALID_PARAMS, exception.code) + check(exception.message?.contains("toolChoice") == true) + } + + @Test + fun `tools with sampling tools capability is accepted`() { + val request = CreateMessageRequest( + CreateMessageRequestParams( + maxTokens = 10, + messages = minimalMessages, + tools = listOf(dummyTool), + ), + ) + assertDoesNotThrow { validateSamplingToolsCapability(request, withToolsCaps) } + } + + @Test + fun `toolChoice with sampling tools capability is accepted`() { + val request = CreateMessageRequest( + CreateMessageRequestParams( + maxTokens = 10, + messages = minimalMessages, + toolChoice = ToolChoice(mode = ToolChoice.Mode.Auto), + ), + ) + assertDoesNotThrow { validateSamplingToolsCapability(request, withToolsCaps) } + } +} diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index b6fe942..c843aea 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -79,6 +79,7 @@ public abstract class io/modelcontextprotocol/kotlin/sdk/shared/Protocol { public final fun setFallbackRequestHandler (Lkotlin/jvm/functions/Function3;)V public final fun setNotificationHandler (Lio/modelcontextprotocol/kotlin/sdk/types/Method;Lkotlin/jvm/functions/Function1;)V public final fun setRequestHandlerInternal (Lio/modelcontextprotocol/kotlin/sdk/types/Method;Lkotlin/jvm/functions/Function3;)V + protected fun wrapRequestHandler (Lio/modelcontextprotocol/kotlin/sdk/types/Method;Lkotlin/jvm/functions/Function3;)Lkotlin/jvm/functions/Function3; } public final class io/modelcontextprotocol/kotlin/sdk/shared/ProtocolKt { @@ -606,21 +607,23 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CancelledNotificatio public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Companion; public fun ()V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;)V public synthetic fun (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lkotlinx/serialization/json/JsonObject; + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling; public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots; public final fun component3 ()Lkotlinx/serialization/json/JsonObject; public final fun component4 ()Lkotlinx/serialization/json/JsonObject; public final fun component5 ()Ljava/util/Map; - public final fun copy (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities; public fun equals (Ljava/lang/Object;)Z public final fun getElicitation ()Lkotlinx/serialization/json/JsonObject; public final fun getExperimental ()Lkotlinx/serialization/json/JsonObject; public final fun getExtensions ()Ljava/util/Map; public final fun getRoots ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots; - public final fun getSampling ()Lkotlinx/serialization/json/JsonObject; + public final fun getSampling ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -638,7 +641,7 @@ public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ClientCapa public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Companion { public final fun getElicitation ()Lkotlinx/serialization/json/JsonObject; - public final fun getSampling ()Lkotlinx/serialization/json/JsonObject; + public final fun getSampling ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling; public final fun serializer ()Lkotlinx/serialization/KSerializer; } @@ -671,6 +674,37 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$R public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling$Companion; + public fun ()V + public fun (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lkotlinx/serialization/json/JsonObject; + public final fun component2 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling; + public fun equals (Ljava/lang/Object;)Z + public final fun getContext ()Lkotlinx/serialization/json/JsonObject; + public final fun getTools ()Lkotlinx/serialization/json/JsonObject; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling; + 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/ClientCapabilities$Sampling;)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/ClientCapabilities$Sampling$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilitiesBuilder { public fun ()V public final fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities; @@ -681,8 +715,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilitiesBu public final fun extensions (Ljava/util/Map;)V public final fun roots (Ljava/lang/Boolean;)V public static synthetic fun roots$default (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilitiesBuilder;Ljava/lang/Boolean;ILjava/lang/Object;)V - public final fun sampling (Lkotlin/jvm/functions/Function1;)V - public final fun sampling (Lkotlinx/serialization/json/JsonObject;)V + public final fun sampling (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling;)V } public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/ClientNotification : io/modelcontextprotocol/kotlin/sdk/types/Notification { @@ -934,6 +967,8 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ContentTypes : java/ public static final field IMAGE Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; public static final field RESOURCE_LINK Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; public static final field TEXT Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; + public static final field TOOL_RESULT Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; + public static final field TOOL_USE Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; public static fun getEntries ()Lkotlin/enums/EnumEntries; public final fun getValue ()Ljava/lang/String; public static fun valueOf (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; @@ -1006,9 +1041,11 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest public final class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams$Companion; - public synthetic fun (ILjava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext;Ljava/lang/Double;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun (ILjava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext;Ljava/lang/Double;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (ILjava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext;Ljava/lang/Double;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (ILjava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext;Ljava/lang/Double;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()I + public final fun component10 ()Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice; + public final fun component11-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; public final fun component2 ()Ljava/util/List; public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences; public final fun component4 ()Ljava/lang/String; @@ -1016,9 +1053,9 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest public final fun component6 ()Ljava/lang/Double; public final fun component7 ()Ljava/util/List; public final fun component8 ()Lkotlinx/serialization/json/JsonObject; - public final fun component9-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; - public final fun copy-qtvSJzw (ILjava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext;Ljava/lang/Double;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams; - public static synthetic fun copy-qtvSJzw$default (Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams;ILjava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext;Ljava/lang/Double;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams; + public final fun component9 ()Ljava/util/List; + public final fun copy-APmi5RE (ILjava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext;Ljava/lang/Double;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams; + public static synthetic fun copy-APmi5RE$default (Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams;ILjava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext;Ljava/lang/Double;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams; public fun equals (Ljava/lang/Object;)Z public final fun getIncludeContext ()Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext; public final fun getMaxTokens ()I @@ -1029,6 +1066,8 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest public final fun getStopSequences ()Ljava/util/List; public final fun getSystemPrompt ()Ljava/lang/String; public final fun getTemperature ()Ljava/lang/Double; + public final fun getToolChoice ()Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice; + public final fun getTools ()Ljava/util/List; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -1050,17 +1089,19 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest public final class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageResult : io/modelcontextprotocol/kotlin/sdk/types/ClientResult { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageResult$Companion; - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageContent;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageContent;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/Role; - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent; + public final fun component2 ()Ljava/util/List; public final fun component3 ()Ljava/lang/String; public final fun component4-6olV-UY ()Ljava/lang/String; public final fun component5 ()Lkotlinx/serialization/json/JsonObject; - public final fun copy-ctVX1tw (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageResult; - public static synthetic fun copy-ctVX1tw$default (Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageResult;Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageResult; + public final fun copy-ctVX1tw (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageResult; + public static synthetic fun copy-ctVX1tw$default (Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageResult;Lio/modelcontextprotocol/kotlin/sdk/types/Role;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageResult; public fun equals (Ljava/lang/Object;)Z - public final fun getContent ()Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent; + public final fun getContent ()Ljava/util/List; public fun getMeta ()Lkotlinx/serialization/json/JsonObject; public final fun getModel ()Ljava/lang/String; public final fun getRole ()Lio/modelcontextprotocol/kotlin/sdk/types/Role; @@ -2937,7 +2978,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/McpException : java/ public final fun getData ()Lkotlinx/serialization/json/JsonElement; } -public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/MediaContent : io/modelcontextprotocol/kotlin/sdk/types/ContentBlock { +public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/MediaContent : io/modelcontextprotocol/kotlin/sdk/types/ContentBlock, io/modelcontextprotocol/kotlin/sdk/types/SamplingMessageContent { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent$Companion; } @@ -4199,13 +4240,18 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/Roots_dslKt { public final class io/modelcontextprotocol/kotlin/sdk/types/SamplingMessage { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessage$Companion; - public fun (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;)V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageContent;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageContent;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/Role; - public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;)Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessage; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessage;Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessage; + public final fun component2 ()Ljava/util/List; + public final fun component3 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessage; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessage;Lio/modelcontextprotocol/kotlin/sdk/types/Role;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessage; public fun equals (Ljava/lang/Object;)Z - public final fun getContent ()Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent; + public final fun getContent ()Ljava/util/List; + public final fun getMeta ()Lkotlinx/serialization/json/JsonObject; public final fun getRole ()Lio/modelcontextprotocol/kotlin/sdk/types/Role; public fun hashCode ()I public fun toString ()Ljava/lang/String; @@ -4233,6 +4279,15 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/SamplingMessageBuild public final fun user (Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;)V } +public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/SamplingMessageContent : io/modelcontextprotocol/kotlin/sdk/types/WithMeta { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageContent$Companion; + public abstract fun getType ()Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/SamplingMessageContent$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/Sampling_dslKt { public static final fun assistant (Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageBuilder;Lkotlin/jvm/functions/Function0;)V public static final fun assistantAudio (Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageBuilder;Lkotlin/jvm/functions/Function1;)V @@ -4508,6 +4563,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/StopReason$Companion public final fun getEndTurn--MeDoko ()Ljava/lang/String; public final fun getMaxTokens--MeDoko ()Ljava/lang/String; public final fun getStopSequence--MeDoko ()Ljava/lang/String; + public final fun getToolUse--MeDoko ()Ljava/lang/String; public final fun serializer ()Lkotlinx/serialization/KSerializer; } @@ -5077,6 +5133,49 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations$Comp public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ToolChoice { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice$Companion; + public fun ()V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice$Mode;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice$Mode;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice$Mode; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice$Mode;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice;Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice$Mode;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice; + public fun equals (Ljava/lang/Object;)Z + public final fun getMode ()Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice$Mode; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ToolChoice$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice; + 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/ToolChoice;)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/ToolChoice$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ToolChoice$Mode : java/lang/Enum { + public static final field Auto Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice$Mode; + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice$Mode$Companion; + public static final field None Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice$Mode; + public static final field Required Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice$Mode; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice$Mode; + public static fun values ()[Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice$Mode; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ToolChoice$Mode$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/ToolExecution { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ToolExecution$Companion; public fun ()V @@ -5137,6 +5236,43 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ToolListChangedNotif public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ToolResultContent : io/modelcontextprotocol/kotlin/sdk/types/SamplingMessageContent { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ToolResultContent$Companion; + public fun (Ljava/lang/String;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/lang/String;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/List; + public final fun component3 ()Lkotlinx/serialization/json/JsonObject; + public final fun component4 ()Ljava/lang/Boolean; + public final fun component5 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Ljava/lang/String;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolResultContent; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ToolResultContent;Ljava/lang/String;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolResultContent; + public fun equals (Ljava/lang/Object;)Z + public final fun getContent ()Ljava/util/List; + public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public final fun getStructuredContent ()Lkotlinx/serialization/json/JsonObject; + public final fun getToolUseId ()Ljava/lang/String; + public fun getType ()Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; + public fun hashCode ()I + public final fun isError ()Ljava/lang/Boolean; + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ToolResultContent$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ToolResultContent$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolResultContent; + 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/ToolResultContent;)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/ToolResultContent$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/ToolSchema { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema$Companion; public fun ()V @@ -5173,6 +5309,41 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ToolSchema$Companion public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ToolUseContent : io/modelcontextprotocol/kotlin/sdk/types/SamplingMessageContent { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ToolUseContent$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Lkotlinx/serialization/json/JsonObject; + public final fun component4 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolUseContent; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ToolUseContent;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolUseContent; + public fun equals (Ljava/lang/Object;)Z + public final fun getId ()Ljava/lang/String; + public final fun getInput ()Lkotlinx/serialization/json/JsonObject; + public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public final fun getName ()Ljava/lang/String; + public fun getType ()Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ToolUseContent$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ToolUseContent$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolUseContent; + 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/ToolUseContent;)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/ToolUseContent$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/ToolsKt { public static final fun error (Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult$Companion;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult; public static synthetic fun error$default (Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult$Companion;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult; diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt index bc3cf76..75cebd3 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt @@ -556,16 +556,31 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio block: suspend (T, RequestHandlerExtra) -> RequestResult?, ) { assertRequestHandlerCapability(method) + val wrapped = wrapRequestHandler(method, block) _requestHandlers.update { current -> current.put(method.value) { jSONRPCRequest, extraHandler -> val request = jSONRPCRequest.fromJSON() - val response = block(request as T, extraHandler) + val response = wrapped(request as T, extraHandler) response } } } + /** + * Subclass hook to wrap an incoming-request handler before it is registered. + * + * Called once by [setRequestHandler] during registration. Subclasses may return a + * new function that, when invoked, performs additional checks (capability gates, + * schema validation, etc.) before delegating to [block]. The default implementation + * is the identity. + */ + @Suppress("UNUSED_PARAMETER") + protected open fun wrapRequestHandler( + method: Method, + block: suspend (T, RequestHandlerExtra) -> RequestResult?, + ): suspend (T, RequestHandlerExtra) -> RequestResult? = block + /** * Removes the request handler for the given method. */ diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.dsl.kt index d936381..75e923b 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.dsl.kt @@ -21,7 +21,7 @@ import kotlinx.serialization.json.buildJsonObject * val request = buildInitializeRequest { * protocolVersion = "1.0" * capabilities { - * sampling(ClientCapabilities.sampling) + * sampling(ClientCapabilities.Sampling()) * roots(listChanged = true) * experimental { * put("customFeature", JsonPrimitive(true)) @@ -36,46 +36,32 @@ import kotlinx.serialization.json.buildJsonObject */ @McpDsl public class ClientCapabilitiesBuilder @PublishedApi internal constructor() { - private var sampling: JsonObject? = null + private var sampling: ClientCapabilities.Sampling? = null private var roots: ClientCapabilities.Roots? = null private var elicitation: JsonObject? = null private var extensions: Map? = null private var experimental: JsonObject? = null /** - * Indicates that the client supports sampling from an LLM. + * Sampling capability configuration. See [ClientCapabilities.Sampling]. * - * Use [ClientCapabilities.sampling] for default empty configuration. + * Pass `ClientCapabilities.Sampling()` to enable base sampling with no sub-capabilities. + * Construct `ClientCapabilities.Sampling(tools = EmptyJsonObject, context = EmptyJsonObject)` + * directly to enable SEP-1577 sub-capabilities. * * Example: * ```kotlin * capabilities { - * sampling(ClientCapabilities.sampling) + * sampling(ClientCapabilities.Sampling(tools = EmptyJsonObject)) * } * ``` * * @param value The sampling capability configuration */ - public fun sampling(value: JsonObject) { + public fun sampling(value: ClientCapabilities.Sampling) { this.sampling = value } - /** - * Indicates that the client supports sampling from an LLM with custom configuration. - * - * Example: - * ```kotlin - * capabilities { - * sampling { - * put("temperature", JsonPrimitive(0.7)) - * } - * } - * ``` - * - * @param block Lambda for building the sampling configuration - */ - public fun sampling(block: JsonObjectBuilder.() -> Unit): Unit = sampling(buildJsonObject(block)) - /** * Indicates that the client supports listing roots. * diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.kt index 14248fa..e13a750 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.kt @@ -41,6 +41,7 @@ public data class Implementation( * supports that capability. * * @property sampling Present if the client supports sampling from an LLM. + * Use [ClientCapabilities.Sampling] to configure SEP-1577 sub-capabilities (tools, context). * @property roots Present if the client supports listing roots. * @property elicitation Present if the client supports elicitation from the server. * @property experimental Experimental, non-standard capabilities that the client supports. @@ -51,7 +52,7 @@ public data class Implementation( */ @Serializable public data class ClientCapabilities( - public val sampling: JsonObject? = null, + public val sampling: Sampling? = null, public val roots: Roots? = null, public val elicitation: JsonObject? = null, public val experimental: JsonObject? = null, @@ -59,11 +60,51 @@ public data class ClientCapabilities( ) { /** - * @property sampling convenience value to enable the sampling capability + * Source-compatibility constructor retaining the pre-SEP-1577 `sampling: JsonObject?` + * shape. Any non-null [sampling] is converted to an empty [Sampling] (sub-capabilities + * cannot be recovered from the old opaque `JsonObject`). + */ + @Deprecated( + "ClientCapabilities.sampling is now typed. Pass a ClientCapabilities.Sampling? " + + "instead of JsonObject?.", + ReplaceWith( + "ClientCapabilities(sampling?.let { ClientCapabilities.Sampling() }, " + + "roots, elicitation, experimental, extensions)", + ), + ) + public constructor( + sampling: JsonObject?, + roots: Roots? = null, + elicitation: JsonObject? = null, + experimental: JsonObject? = null, + extensions: Map? = null, + ) : this( + sampling = sampling?.let { Sampling() }, + roots = roots, + elicitation = elicitation, + experimental = experimental, + extensions = extensions, + ) + + /** + * sub-capabilities for sampling. + * + * @property context Present if the client supports context inclusion via + * [CreateMessageRequestParams.includeContext] with values other than [IncludeContext.None]. + * Servers SHOULD avoid non-`none` values when this field is absent. + * @property tools Present if the client supports tool use via + * [CreateMessageRequestParams.tools] / [CreateMessageRequestParams.toolChoice]. + * Servers MUST NOT send `tools`/`toolChoice` when this field is absent. + */ + @Serializable + public data class Sampling(public val context: JsonObject? = null, public val tools: JsonObject? = null) + + /** + * @property sampling convenience value to enable the base sampling capability with no sub-capabilities * @property elicitation convenience value to enable the elicitation capability */ public companion object { - public val sampling: JsonObject = EmptyJsonObject + public val sampling: Sampling = Sampling() public val elicitation: JsonObject = EmptyJsonObject } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/content.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/content.kt index a1c9e4f..d30c22e 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/content.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/content.kt @@ -29,6 +29,12 @@ public enum class ContentTypes(public val value: String) { @SerialName("resource") EMBEDDED_RESOURCE("resource"), + + @SerialName("tool_use") + TOOL_USE("tool_use"), + + @SerialName("tool_result") + TOOL_RESULT("tool_result"), } /** @@ -43,9 +49,27 @@ public sealed interface ContentBlock : WithMeta { /** * Content block that carries media data such as text, images, or audio. + * + * Every [MediaContent] is also a valid [SamplingMessageContent]; the sampling content + * hierarchy additionally admits [ToolUseContent] and [ToolResultContent]. */ @Serializable(with = MediaContentPolymorphicSerializer::class) -public sealed interface MediaContent : ContentBlock +public sealed interface MediaContent : + ContentBlock, + SamplingMessageContent + +/** + * Content block that can appear inside a [SamplingMessage] or [CreateMessageResult]. + * + * Implemented by [TextContent], [ImageContent], [AudioContent], [ToolUseContent], + * and [ToolResultContent]. + * + * @property type discriminator identifying the content block subtype + */ +@Serializable(with = SamplingMessageContentPolymorphicSerializer::class) +public sealed interface SamplingMessageContent : WithMeta { + public val type: ContentTypes +} /** * Text provided to or from an LLM. @@ -174,3 +198,50 @@ public data class EmbeddedResource( @EncodeDefault public override val type: ContentTypes = ContentTypes.EMBEDDED_RESOURCE } + +/** + * A request from the assistant to invoke a tool during sampling. + * + * @property id Unique identifier for this tool use; matches a subsequent + * [ToolResultContent.toolUseId] that reports the result. + * @property name The tool name (must match a tool declared in the sampling request's tools list). + * @property input The arguments to pass to the tool, conforming to the tool's input schema. + * @property meta property/parameter is reserved by MCP to allow clients and servers + * to attach additional metadata to their interactions. + */ +@Serializable +public data class ToolUseContent( + val id: String, + val name: String, + val input: JsonObject, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : SamplingMessageContent { + @EncodeDefault + public override val type: ContentTypes = ContentTypes.TOOL_USE +} + +/** + * The result of a tool call previously requested via [ToolUseContent], supplied back + * to the assistant on the next sampling turn. + * + * @property toolUseId The id of the [ToolUseContent] this result corresponds to. + * @property content The unstructured result, following the same shape as [CallToolResult.content]. + * @property structuredContent Optional structured result; if the tool declared an output schema, + * this SHOULD conform to it. + * @property isError Whether the tool call ended in error. Defaults to absent (treated as false). + * @property meta property/parameter is reserved by MCP to allow clients and servers + * to attach additional metadata to their interactions. + */ +@Serializable +public data class ToolResultContent( + val toolUseId: String, + val content: List = emptyList(), + val structuredContent: JsonObject? = null, + val isError: Boolean? = null, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : SamplingMessageContent { + @EncodeDefault + public override val type: ContentTypes = ContentTypes.TOOL_RESULT +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/initialize.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/initialize.dsl.kt index 2cb58b7..f3e1575 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/initialize.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/initialize.dsl.kt @@ -21,7 +21,7 @@ import kotlin.contracts.contract * val request = buildInitializeRequest { * protocolVersion = "2024-11-05" * capabilities { - * sampling(ClientCapabilities.sampling) + * sampling(ClientCapabilities.Sampling()) * roots(listChanged = true) * } * info("MyClient", "1.0.0") @@ -33,7 +33,7 @@ import kotlin.contracts.contract * val request = buildInitializeRequest { * protocolVersion = "2024-11-05" * capabilities { - * sampling(ClientCapabilities.sampling) + * sampling(ClientCapabilities.Sampling()) * experimental { * put("feature", JsonPrimitive(true)) * } @@ -94,7 +94,7 @@ public class InitializeRequestBuilder @PublishedApi internal constructor() : Req * Example: * ```kotlin * capabilities(ClientCapabilities( - * sampling = ClientCapabilities.sampling, + * sampling = ClientCapabilities.Sampling(), * roots = ClientCapabilities.Roots(listChanged = true) * )) * ``` @@ -113,7 +113,7 @@ public class InitializeRequestBuilder @PublishedApi internal constructor() : Req * Example: * ```kotlin * capabilities { - * sampling(ClientCapabilities.sampling) + * sampling(ClientCapabilities.Sampling()) * roots(listChanged = true) * elicitation(ClientCapabilities.elicitation) * } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.dsl.kt index 1bc00e2..4752ff5 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.dsl.kt @@ -112,8 +112,8 @@ public class CreateMessageRequestBuilder @PublishedApi internal constructor() : * Example: * ```kotlin * messages(listOf( - * SamplingMessage(Role.User, TextContent("Hello")), - * SamplingMessage(Role.Assistant, TextContent("Hi!")) + * SamplingMessage(Role.User, listOf(TextContent("Hello"))), + * SamplingMessage(Role.Assistant, listOf(TextContent("Hi!"))) * )) * ``` */ @@ -246,7 +246,7 @@ public class SamplingMessageBuilder @PublishedApi internal constructor() { * ``` */ public fun user(content: MediaContent) { - messages.add(SamplingMessage(Role.User, content)) + messages.add(SamplingMessage(Role.User, listOf(content))) } /** @@ -258,7 +258,7 @@ public class SamplingMessageBuilder @PublishedApi internal constructor() { * ``` */ public fun assistant(content: MediaContent) { - messages.add(SamplingMessage(Role.Assistant, content)) + messages.add(SamplingMessage(Role.Assistant, listOf(content))) } @PublishedApi diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.kt index 97fc0c0..211fdc7 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.kt @@ -75,11 +75,33 @@ public data class ModelPreferences( * * Used in sampling requests to provide conversation context and history to the LLM. * + * Under SEP-1577, [content] is a list of [SamplingMessageContent] blocks rather than a + * single block. On the wire, a list of size 1 is serialised as a single object (wire-compatible + * with pre-SEP single-block content); a list of size 2+ is serialised as a JSON array. + * * @property role The role of the message sender (user, assistant, or system). - * @property content The content of the message. Can be text, image, or audio content. + * @property content The content blocks of the message; MUST contain at least one block. + * @property meta Optional metadata reserved by MCP for clients and servers. */ @Serializable -public data class SamplingMessage(val role: Role, val content: MediaContent) +public data class SamplingMessage( + val role: Role, + @Serializable(with = SamplingContentSerializer::class) + val content: List, + @SerialName("_meta") + val meta: JsonObject? = null, +) { + /** + * Convenience constructor for a single-block message. Wraps [content] in a + * singleton list so call sites can write `SamplingMessage(Role.User, TextContent("hi"))` + * without the explicit `listOf(...)`. + */ + public constructor( + role: Role, + content: SamplingMessageContent, + meta: JsonObject? = null, + ) : this(role, listOf(content), meta) +} // ============================================================================ // sampling/createMessage @@ -184,6 +206,12 @@ public data class CreateMessageRequest(override val params: CreateMessageRequest * @property stopSequences Optional list of sequences that will stop generation if encountered. * @property metadata Optional metadata to pass through to the LLM provider. * The format of this metadata is provider-specific. + * @property tools Optional list of tools the model may use during generation. + * The client MUST return an error if this field is present but the client did not advertise + * [ClientCapabilities.Sampling.tools]. + * @property toolChoice Optional policy controlling how the model uses the provided [tools]. + * The client MUST return an error if this field is present but the client did not advertise + * [ClientCapabilities.Sampling.tools]. * @property meta Optional metadata for this request. May include a progressToken for * out-of-band progress notifications. */ @@ -197,6 +225,8 @@ public data class CreateMessageRequestParams( val temperature: Double? = null, val stopSequences: List? = null, val metadata: JsonObject? = null, + val tools: List? = null, + val toolChoice: ToolChoice? = null, @SerialName("_meta") override val meta: RequestMeta? = null, ) : RequestParams @@ -226,22 +256,37 @@ public enum class IncludeContext { * to inspect the response (human in the loop) and decide whether to allow the server to see it. * * @property role The role of the message sender. Typically [Role.Assistant] for LLM-generated responses. - * @property content The generated content. Can be text, image, or audio content. - * @property model The name of the model that generated the message (e.g., "claude-3-opus-20240229", - * "gpt-4-turbo-preview"). This helps the server understand which model was used. + * @property content The generated content blocks; at least one block is required. + * @property model The name of the model that generated the message (e.g., "claude-3-opus-20240229"). * @property stopReason The reason why sampling stopped, if known. - * Common values: "end_turn", "max_tokens", "stop_sequence", "content_filter" + * Common values: [StopReason.EndTurn], [StopReason.StopSequence], [StopReason.MaxTokens], + * [StopReason.ToolUse]. * @property meta Optional metadata for this response. */ @Serializable public data class CreateMessageResult( val role: Role, - val content: MediaContent, + @Serializable(with = SamplingContentSerializer::class) + val content: List, val model: String, val stopReason: StopReason? = null, @SerialName("_meta") override val meta: JsonObject? = null, -) : ClientResult +) : ClientResult { + /** + * Convenience constructor for a single-block response. Wraps [content] in a + * singleton list so call sites can write + * `CreateMessageResult(Role.Assistant, TextContent("ok"), "model-name")` + * without the explicit `listOf(...)`. + */ + public constructor( + role: Role, + content: SamplingMessageContent, + model: String, + stopReason: StopReason? = null, + meta: JsonObject? = null, + ) : this(role, listOf(content), model, stopReason, meta) +} /** * The reason why the LLM stopped generating tokens. @@ -255,10 +300,38 @@ public value class StopReason(public val value: String) { * @property EndTurn generation ended naturally * @property StopSequence a stop sequence was encountered * @property MaxTokens the maximum token limit was reached + * @property ToolUse the assistant issued a tool call (see SEP-1577) */ public companion object { public val EndTurn: StopReason = StopReason("endTurn") public val StopSequence: StopReason = StopReason("stopSequence") public val MaxTokens: StopReason = StopReason("maxTokens") + public val ToolUse: StopReason = StopReason("toolUse") + } +} + +/** + * Controls tool-selection behaviour for [CreateMessageRequest] under SEP-1577. + * + * @property mode Tool-use policy: + * - [Mode.Auto]: the model decides whether to use tools (default when [mode] is absent) + * - [Mode.Required]: the model MUST use at least one tool before completing + * - [Mode.None]: the model MUST NOT use any tool + */ +@Serializable +public data class ToolChoice(public val mode: Mode? = null) { + /** + * Allowed values of [ToolChoice.mode] per SEP-1577. + */ + @Serializable + public enum class Mode { + @SerialName("auto") + Auto, + + @SerialName("required") + Required, + + @SerialName("none") + None, } } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt index 3e25957..4a1abc6 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt @@ -4,13 +4,18 @@ import io.github.oshai.kotlinlogging.KotlinLogging import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.KSerializer import kotlinx.serialization.SerializationException +import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonContentPolymorphicSerializer +import kotlinx.serialization.json.JsonDecoder import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonEncoder +import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.contentOrNull import kotlinx.serialization.json.jsonObject @@ -121,6 +126,101 @@ internal object MediaContentPolymorphicSerializer : } } +/** + * Polymorphic serializer for [SamplingMessageContent] types. + * Determines the concrete type based on the "type" field in JSON. + */ +internal object SamplingMessageContentPolymorphicSerializer : + JsonContentPolymorphicSerializer(SamplingMessageContent::class) { + override fun selectDeserializer(element: JsonElement): DeserializationStrategy = + when (element.getType()) { + ContentTypes.TEXT.value -> TextContent.serializer() + + ContentTypes.IMAGE.value -> ImageContent.serializer() + + ContentTypes.AUDIO.value -> AudioContent.serializer() + + ContentTypes.TOOL_USE.value -> ToolUseContent.serializer() + + ContentTypes.TOOL_RESULT.value -> ToolResultContent.serializer() + + else -> throw SerializationException( + "Unknown sampling message content type: ${element.getTypeOrNull()}", + ) + } +} + +/** + * Wire-format serializer for `List` honouring SEP-1577's + * single-object-or-array content shape. + * + * **Why this exists.** SEP-1577 widens the `content` field of [SamplingMessage] and + * [CreateMessageResult] from a single content block to either a single block or an + * array of blocks. Pre-SEP peers only understand a single object on the wire; post-SEP + * peers may send arrays when a turn carries multiple blocks (e.g. an assistant reply + * containing `[TextContent, ToolUseContent]` during a tool-loop step). + * + * The Kotlin API uses `List` uniformly to avoid branching in + * caller code. This serializer bridges the type difference: + * + * - **Read:** accepts both shapes; a single JSON object becomes `listOf(block)`, an + * array becomes the decoded list. + * - **Write:** size-1 list → single object (wire-compatible with pre-SEP peers); + * size≥2 → array; empty list throws (sampling content has no valid empty meaning + * per spec). + */ +internal object SamplingContentSerializer : KSerializer> { + + private val listSerializer = ListSerializer(SamplingMessageContentPolymorphicSerializer) + + override val descriptor: SerialDescriptor = listSerializer.descriptor + + override fun serialize(encoder: Encoder, value: List) { + check(value.isNotEmpty()) { "content must contain at least one block" } + val jsonEncoder = encoder as? JsonEncoder + ?: throw SerializationException("SamplingContentSerializer requires a Json encoder") + if (value.size == 1) { + jsonEncoder.encodeJsonElement( + jsonEncoder.json.encodeToJsonElement( + SamplingMessageContentPolymorphicSerializer, + value[0], + ), + ) + } else { + jsonEncoder.encodeJsonElement( + jsonEncoder.json.encodeToJsonElement(listSerializer, value), + ) + } + } + + override fun deserialize(decoder: Decoder): List { + val jsonDecoder = decoder as? JsonDecoder + ?: throw SerializationException("SamplingContentSerializer requires a Json decoder") + return decodeFromElement(jsonDecoder, jsonDecoder.decodeJsonElement()) + } + + private fun decodeFromElement(jsonDecoder: JsonDecoder, element: JsonElement): List = + when (element) { + is JsonArray -> { + if (element.isEmpty()) { + throw SerializationException("content must contain at least one block") + } + jsonDecoder.json.decodeFromJsonElement(listSerializer, element) + } + + is JsonObject -> listOf( + jsonDecoder.json.decodeFromJsonElement( + SamplingMessageContentPolymorphicSerializer, + element, + ), + ) + + else -> throw SerializationException( + "content must be a JSON object or array of objects, got $element", + ) + } +} + // ============================================================================ // Resource Serializers // ============================================================================ diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CapabilitiesTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CapabilitiesTest.kt index 171c249..8b2e4f8 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CapabilitiesTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CapabilitiesTest.kt @@ -1,6 +1,7 @@ package io.modelcontextprotocol.kotlin.sdk.types import io.kotest.assertions.json.shouldEqualJson +import io.kotest.matchers.shouldBe import io.modelcontextprotocol.kotlin.test.utils.verifyDeserialization import io.modelcontextprotocol.kotlin.test.utils.verifySerialization import io.modelcontextprotocol.kotlin.test.utils.verifySerializationRoundTrip @@ -8,6 +9,7 @@ import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.put import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertNotNull import kotlin.test.assertNull class CapabilitiesTest { @@ -119,7 +121,7 @@ class CapabilitiesTest { @Test fun `should serialize ClientCapabilities with sampling`() { val capabilities = ClientCapabilities( - sampling = ClientCapabilities.sampling, + sampling = ClientCapabilities.Sampling(), ) verifySerialization( capabilities, @@ -266,7 +268,7 @@ class CapabilitiesTest { "io.modelcontextprotocol/ui" to EmptyJsonObject, ) val capabilities = ClientCapabilities( - sampling = ClientCapabilities.sampling, + sampling = ClientCapabilities.Sampling(), roots = ClientCapabilities.Roots(listChanged = true), elicitation = ClientCapabilities.elicitation, experimental = experimental, @@ -309,7 +311,7 @@ class CapabilitiesTest { val capabilities = verifyDeserialization(McpJson, json) - assertEquals(EmptyJsonObject, capabilities.sampling) + assertEquals(ClientCapabilities.Sampling(), capabilities.sampling) assertEquals(true, capabilities.roots?.listChanged) assertEquals(EmptyJsonObject, capabilities.elicitation) } @@ -330,7 +332,7 @@ class CapabilitiesTest { @Test fun `should serialize and deserialize ClientCapabilities round trip`() { val original = ClientCapabilities( - sampling = ClientCapabilities.sampling, + sampling = ClientCapabilities.Sampling(), roots = ClientCapabilities.Roots(listChanged = false), elicitation = ClientCapabilities.elicitation, extensions = mapOf( @@ -734,10 +736,11 @@ class CapabilitiesTest { } """.trimIndent() - val capabilities = verifyDeserialization(McpJson, json) + // Sampling is now a typed struct; unknown fields are ignored on deserialization + val capabilities = McpJson.decodeFromString(json) - // Should not fail - additionalProperties are allowed - assertEquals("customValue", capabilities.sampling?.get("customProperty")?.toString()?.trim('"')) + // Should not fail - additionalProperties are allowed (unknown fields are ignored by Sampling) + assertNotNull(capabilities.sampling) } @Test @@ -755,4 +758,50 @@ class CapabilitiesTest { // Should not fail - additionalProperties are allowed assertEquals("debug", capabilities.logging?.get("level")?.toString()?.trim('"')) } + + // ============================================================================ + // ClientCapabilities.Sampling sub-capabilities (SEP-1577) + // ============================================================================ + + @Test + fun `empty sampling serialises as empty object`() { + val caps = ClientCapabilities(sampling = ClientCapabilities.Sampling()) + val json = McpJson.encodeToString(ClientCapabilities.serializer(), caps) + check("\"sampling\":{}" in json) { json } + } + + @Test + fun `sampling with tools sub-capability serialises tools`() { + val caps = ClientCapabilities(sampling = ClientCapabilities.Sampling(tools = EmptyJsonObject)) + val json = McpJson.encodeToString(ClientCapabilities.serializer(), caps) + check("\"tools\":{}" in json) { json } + } + + @Test + fun `sampling with context sub-capability serialises context`() { + val caps = ClientCapabilities(sampling = ClientCapabilities.Sampling(context = EmptyJsonObject)) + val json = McpJson.encodeToString(ClientCapabilities.serializer(), caps) + check("\"context\":{}" in json) { json } + } + + @Test + fun `sampling with combined tools and context round-trips`() { + val original = ClientCapabilities( + sampling = ClientCapabilities.Sampling( + context = EmptyJsonObject, + tools = EmptyJsonObject, + ), + ) + val json = McpJson.encodeToString(ClientCapabilities.serializer(), original) + val decoded = McpJson.decodeFromString(ClientCapabilities.serializer(), json) + decoded shouldBe original + decoded.sampling?.tools shouldBe EmptyJsonObject + decoded.sampling?.context shouldBe EmptyJsonObject + } + + @Test + fun `absent sampling means unsupported`() { + val caps = ClientCapabilities() + caps.sampling shouldBe null + } } diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ContentTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ContentTest.kt index f110326..c6dee5d 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ContentTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ContentTest.kt @@ -1,17 +1,26 @@ package io.modelcontextprotocol.kotlin.sdk.types +import io.kotest.matchers.shouldBe +import io.kotest.matchers.types.shouldBeInstanceOf import io.modelcontextprotocol.kotlin.test.utils.verifyDeserialization import io.modelcontextprotocol.kotlin.test.utils.verifySerialization +import kotlinx.serialization.SerializationException +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.put import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFailsWith import kotlin.test.assertIs import kotlin.test.assertNull class ContentTest { + private val samplingContentSerializer = ListSerializer(SamplingMessageContent.serializer()) + @Test fun `should serialize TextContent with minimal fields`() { val content = TextContent(text = "Hello, MCP!") @@ -308,4 +317,133 @@ class ContentTest { assertIs(content[1]) assertIs(content[2]) } + + // ============================================================================ + // SamplingMessageContent hierarchy (SEP-1577) + // ============================================================================ + + @Test + fun `TextContent implements SamplingMessageContent`() { + val c: SamplingMessageContent = TextContent("hi") + c.shouldBeInstanceOf() + } + + @Test + fun `ImageContent implements SamplingMessageContent`() { + val c: SamplingMessageContent = ImageContent(data = "AA==", mimeType = "image/png") + c.shouldBeInstanceOf() + } + + @Test + fun `AudioContent implements SamplingMessageContent`() { + val c: SamplingMessageContent = AudioContent(data = "AA==", mimeType = "audio/wav") + c.shouldBeInstanceOf() + } + + // ============================================================================ + // ToolUseContent + // ============================================================================ + + @Test + fun `ToolUseContent round-trips through McpJson`() { + val input = buildJsonObject { put("location", JsonPrimitive("London")) } + val original = ToolUseContent(id = "call_1", name = "get_weather", input = input) + val encoded = McpJson.encodeToString(ToolUseContent.serializer(), original) + val decoded = McpJson.decodeFromString(ToolUseContent.serializer(), encoded) + decoded shouldBe original + } + + @Test + fun `ToolUseContent serialises with type discriminator tool_use`() { + val c = ToolUseContent(id = "x", name = "n", input = JsonObject(emptyMap())) + val json = McpJson.encodeToString(ToolUseContent.serializer(), c) + check("\"type\":\"tool_use\"" in json) { "missing discriminator in: $json" } + } + + @Test + fun `ToolUseContent preserves meta when present`() { + val meta = buildJsonObject { put("cacheControl", JsonPrimitive("ephemeral")) } + val c = ToolUseContent(id = "x", name = "n", input = JsonObject(emptyMap()), meta = meta) + val json = McpJson.encodeToString(ToolUseContent.serializer(), c) + val decoded = McpJson.decodeFromString(ToolUseContent.serializer(), json) + decoded.meta shouldBe meta + } + + // ============================================================================ + // ToolResultContent + // ============================================================================ + + @Test + fun `ToolResultContent round-trips with text content block`() { + val original = ToolResultContent( + toolUseId = "call_1", + content = listOf(TextContent("20°C sunny")), + ) + val encoded = McpJson.encodeToString(ToolResultContent.serializer(), original) + val decoded = McpJson.decodeFromString(ToolResultContent.serializer(), encoded) + decoded shouldBe original + } + + @Test + fun `ToolResultContent round-trips with mixed content including resource_link`() { + val original = ToolResultContent( + toolUseId = "call_2", + content = listOf( + TextContent("see file"), + ResourceLink(name = "log", uri = "file:///tmp/a.log"), + ), + ) + val encoded = McpJson.encodeToString(ToolResultContent.serializer(), original) + val decoded = McpJson.decodeFromString(ToolResultContent.serializer(), encoded) + decoded shouldBe original + } + + @Test + fun `ToolResultContent serialises with discriminator and preserves structuredContent and isError`() { + val original = ToolResultContent( + toolUseId = "call_3", + content = emptyList(), + structuredContent = buildJsonObject { put("temp", JsonPrimitive(20)) }, + isError = true, + ) + val json = McpJson.encodeToString(ToolResultContent.serializer(), original) + check("\"type\":\"tool_result\"" in json) { "missing discriminator in: $json" } + val decoded = McpJson.decodeFromString(ToolResultContent.serializer(), json) + decoded shouldBe original + } + + // ============================================================================ + // SamplingMessageContentPolymorphicSerializer + // ============================================================================ + + @Test + fun `SamplingMessageContent polymorphic serializer decodes text`() { + val json = """[{"type":"text","text":"hi"}]""" + val list = McpJson.decodeFromString(samplingContentSerializer, json) + list[0].shouldBeInstanceOf().text shouldBe "hi" + } + + @Test + fun `SamplingMessageContent polymorphic serializer decodes tool_use`() { + val json = """[{"type":"tool_use","id":"a","name":"n","input":{}}]""" + val list = McpJson.decodeFromString(samplingContentSerializer, json) + val use = list[0].shouldBeInstanceOf() + use.id shouldBe "a" + use.name shouldBe "n" + } + + @Test + fun `SamplingMessageContent polymorphic serializer decodes tool_result`() { + val json = """[{"type":"tool_result","toolUseId":"a","content":[]}]""" + val list = McpJson.decodeFromString(samplingContentSerializer, json) + list[0].shouldBeInstanceOf().toolUseId shouldBe "a" + } + + @Test + fun `SamplingMessageContent polymorphic serializer throws on unknown discriminator`() { + val json = """[{"type":"bogus","x":1}]""" + assertFailsWith { + McpJson.decodeFromString(samplingContentSerializer, json) + } + } } diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/InitializeTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/InitializeTest.kt index 3da2a8b..ed97c18 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/InitializeTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/InitializeTest.kt @@ -21,7 +21,7 @@ class InitializeTest { InitializeRequestParams( protocolVersion = "2024-11-05", capabilities = ClientCapabilities( - sampling = ClientCapabilities.sampling, + sampling = ClientCapabilities.Sampling(), roots = ClientCapabilities.Roots(listChanged = true), elicitation = ClientCapabilities.elicitation, experimental = buildJsonObject { diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/SamplingTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/SamplingTest.kt index 696f322..0432aba 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/SamplingTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/SamplingTest.kt @@ -1,7 +1,13 @@ package io.modelcontextprotocol.kotlin.sdk.types +import io.kotest.matchers.shouldBe +import io.kotest.matchers.types.shouldBeInstanceOf import io.modelcontextprotocol.kotlin.test.utils.verifyDeserialization import io.modelcontextprotocol.kotlin.test.utils.verifySerialization +import kotlinx.serialization.Serializable +import kotlinx.serialization.SerializationException +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.double import kotlinx.serialization.json.jsonPrimitive @@ -14,6 +20,21 @@ import kotlin.test.assertNotNull class SamplingTest { + @Serializable + private data class Holder( + @Serializable(with = SamplingContentSerializer::class) + val content: List, + ) + + private val dummyTool = Tool( + name = "get_weather", + description = "returns weather", + inputSchema = ToolSchema( + properties = buildJsonObject { }, + required = emptyList(), + ), + ) + @Test fun `should serialize ModelHint`() { val hint = ModelHint(name = "claude-3-5-sonnet") @@ -66,7 +87,7 @@ class SamplingTest { fun `should serialize SamplingMessage`() { val message = SamplingMessage( role = Role.User, - content = TextContent(text = "Summarize the latest release."), + content = listOf(TextContent(text = "Summarize the latest release.")), ) verifySerialization( @@ -93,11 +114,11 @@ class SamplingTest { messages = listOf( SamplingMessage( role = Role.User, - content = TextContent(text = "You are a helpful assistant."), + content = listOf(TextContent(text = "You are a helpful assistant.")), ), SamplingMessage( role = Role.User, - content = TextContent(text = "Provide a short summary."), + content = listOf(TextContent(text = "Provide a short summary.")), ), ), modelPreferences = ModelPreferences( @@ -202,7 +223,7 @@ class SamplingTest { val message = params.messages.first() assertEquals(Role.User, message.role) - val content = assertIs(message.content) + val content = assertIs(message.content.single()) assertEquals("Draft a project update.", content.text) val preferences = assertNotNull(params.modelPreferences) @@ -259,7 +280,7 @@ class SamplingTest { val result = verifyDeserialization(McpJson, json) assertEquals(Role.Assistant, result.role) - val text = assertIs(result.content) + val text = assertIs(result.content.single()) assertEquals("Summary complete.", text.text) assertEquals("gpt-4o", result.model) assertEquals(StopReason.StopSequence, result.stopReason) @@ -267,4 +288,211 @@ class SamplingTest { assertNotNull(meta) assertEquals(1200.5, meta["latencyMs"]?.jsonPrimitive?.double) } + + // ============================================================================ + // SamplingMessage shape (SEP-1577) + // ============================================================================ + + @Test + fun `SamplingMessage content is a list of SamplingMessageContent`() { + val m = SamplingMessage( + role = Role.User, + content = listOf(TextContent("hi")), + ) + m.content.size shouldBe 1 + (m.content[0] as TextContent).text shouldBe "hi" + } + + @Test + fun `SamplingMessage single-element content serialises as single object`() { + val m = SamplingMessage(role = Role.User, content = listOf(TextContent("hi"))) + val json = McpJson.encodeToString(SamplingMessage.serializer(), m) + check("""{"role":"user","content":""" in json) { "expected role/content prefix, got $json" } + check("\"type\":\"text\"" in json && "\"text\":\"hi\"" in json) { + "expected single-object content with text discriminator, got $json" + } + check("\"content\":[" !in json) { "expected single-object wire, but array form found: $json" } + } + + @Test + fun `SamplingMessage multi-element content serialises as array`() { + val m = SamplingMessage( + role = Role.Assistant, + content = listOf( + TextContent("Let me use a tool"), + ToolUseContent(id = "c1", name = "get_weather", input = JsonObject(emptyMap())), + ), + ) + val json = McpJson.encodeToString(SamplingMessage.serializer(), m) + check("\"content\":[" in json) { "expected array wire form, got $json" } + } + + @Test + fun `SamplingMessage _meta round-trips`() { + val meta = buildJsonObject { put("k", JsonPrimitive("v")) } + val m = SamplingMessage(role = Role.User, content = listOf(TextContent("hi")), meta = meta) + val json = McpJson.encodeToString(SamplingMessage.serializer(), m) + val decoded = McpJson.decodeFromString(SamplingMessage.serializer(), json) + decoded.meta shouldBe meta + } + + // ============================================================================ + // CreateMessageRequestParams: tools / toolChoice + StopReason.ToolUse + // ============================================================================ + + @Test + fun `CreateMessageRequestParams tools and toolChoice default to null`() { + val params = CreateMessageRequestParams(maxTokens = 100, messages = emptyList()) + params.tools shouldBe null + params.toolChoice shouldBe null + } + + @Test + fun `CreateMessageRequestParams round-trips tools and toolChoice`() { + val original = CreateMessageRequestParams( + maxTokens = 100, + messages = emptyList(), + tools = listOf(dummyTool), + toolChoice = ToolChoice(mode = ToolChoice.Mode.Required), + ) + val encoded = McpJson.encodeToString(CreateMessageRequestParams.serializer(), original) + val decoded = McpJson.decodeFromString(CreateMessageRequestParams.serializer(), encoded) + decoded.tools?.single()?.name shouldBe "get_weather" + decoded.toolChoice shouldBe ToolChoice(mode = ToolChoice.Mode.Required) + } + + @Test + fun `StopReason ToolUse serialises as toolUse`() { + StopReason.ToolUse.value shouldBe "toolUse" + } + + // ============================================================================ + // CreateMessageResult shape (SEP-1577) + // ============================================================================ + + @Test + fun `CreateMessageResult single-block content serialises as single object`() { + val r = CreateMessageResult( + role = Role.Assistant, + content = TextContent("42"), + model = "test-model", + stopReason = StopReason.EndTurn, + ) + val json = McpJson.encodeToString(CreateMessageResult.serializer(), r) + check("\"content\":{" in json) { "expected single-object content wire form, got $json" } + check("\"content\":[" !in json) { "expected NOT to use array wire form for size-1, got $json" } + } + + @Test + fun `CreateMessageResult multi-block content with ToolUse stopReason round-trips`() { + val r = CreateMessageResult( + role = Role.Assistant, + content = listOf( + TextContent("Let me use a tool"), + ToolUseContent(id = "c1", name = "get_weather", input = JsonObject(emptyMap())), + ), + model = "test-model", + stopReason = StopReason.ToolUse, + ) + val json = McpJson.encodeToString(CreateMessageResult.serializer(), r) + val decoded = McpJson.decodeFromString(CreateMessageResult.serializer(), json) + decoded shouldBe r + decoded.stopReason shouldBe StopReason.ToolUse + decoded.content.size shouldBe 2 + } + + @Test + fun `CreateMessageResult pre-SEP single-object wire decodes correctly`() { + val json = """{"role":"assistant","content":{"type":"text","text":"hi"},"model":"m"}""" + val decoded = McpJson.decodeFromString(CreateMessageResult.serializer(), json) + decoded.content.size shouldBe 1 + (decoded.content[0] as TextContent).text shouldBe "hi" + } + + // ============================================================================ + // SamplingContentSerializer (single-or-array wire heuristic) + // ============================================================================ + + @Test + fun `SamplingContentSerializer decodes single object into list of one`() { + val json = """{"content":{"type":"text","text":"hi"}}""" + val h = McpJson.decodeFromString(Holder.serializer(), json) + h.content.size shouldBe 1 + h.content[0].shouldBeInstanceOf().text shouldBe "hi" + } + + @Test + fun `SamplingContentSerializer decodes array into list`() { + val json = """{"content":[{"type":"text","text":"a"},{"type":"text","text":"b"}]}""" + val h = McpJson.decodeFromString(Holder.serializer(), json) + h.content.size shouldBe 2 + } + + @Test + fun `SamplingContentSerializer encodes list of size one as a single object`() { + val h = Holder(content = listOf(TextContent("hi"))) + val json = McpJson.encodeToString(Holder.serializer(), h) + json shouldBe """{"content":{"text":"hi","type":"text"}}""" + } + + @Test + fun `SamplingContentSerializer encodes list of size two as an array`() { + val h = Holder(content = listOf(TextContent("a"), TextContent("b"))) + val json = McpJson.encodeToString(Holder.serializer(), h) + json shouldBe """{"content":[{"text":"a","type":"text"},{"text":"b","type":"text"}]}""" + } + + @Test + fun `SamplingContentSerializer encoding an empty list throws`() { + val h = Holder(content = emptyList()) + assertFailsWith { + McpJson.encodeToString(Holder.serializer(), h) + } + } + + @Test + fun `SamplingContentSerializer decoding an empty array throws`() { + val json = """{"content":[]}""" + assertFailsWith { + McpJson.decodeFromString(Holder.serializer(), json) + } + } + + // ============================================================================ + // ToolChoice + // ============================================================================ + + @Test + fun `ToolChoice round-trips auto mode`() { + val original = ToolChoice(mode = ToolChoice.Mode.Auto) + val json = McpJson.encodeToString(ToolChoice.serializer(), original) + json shouldBe """{"mode":"auto"}""" + McpJson.decodeFromString(ToolChoice.serializer(), json) shouldBe original + } + + @Test + fun `ToolChoice round-trips required mode`() { + val original = ToolChoice(mode = ToolChoice.Mode.Required) + McpJson.decodeFromString( + ToolChoice.serializer(), + McpJson.encodeToString(ToolChoice.serializer(), original), + ) shouldBe original + } + + @Test + fun `ToolChoice round-trips none mode`() { + val original = ToolChoice(mode = ToolChoice.Mode.None) + McpJson.decodeFromString( + ToolChoice.serializer(), + McpJson.encodeToString(ToolChoice.serializer(), original), + ) shouldBe original + } + + @Test + fun `ToolChoice absent mode serialises as empty object and deserialises to null mode`() { + val original = ToolChoice() + val json = McpJson.encodeToString(ToolChoice.serializer(), original) + json shouldBe """{}""" + McpJson.decodeFromString(ToolChoice.serializer(), json) shouldBe ToolChoice(mode = null) + } } diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CapabilitiesDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CapabilitiesDslTest.kt index 53dd5c7..ee2fe43 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CapabilitiesDslTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CapabilitiesDslTest.kt @@ -4,10 +4,10 @@ import io.kotest.matchers.nulls.shouldBeNull import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject import io.modelcontextprotocol.kotlin.sdk.types.buildInitializeRequest import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.double import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.put import kotlin.test.Test @@ -42,11 +42,7 @@ class CapabilitiesDslTest { val request = buildInitializeRequest { protocolVersion = "2024-11-05" capabilities { - sampling { - put("temperature", 0.7) - put("maxTokens", 1000) - put("topP", 0.95) - } + sampling(ClientCapabilities.Sampling(tools = EmptyJsonObject)) roots(listChanged = true) elicitation { put("mode", "interactive") @@ -72,9 +68,7 @@ class CapabilitiesDslTest { request.params.capabilities.shouldNotBeNull { sampling shouldNotBeNull { - get("temperature")?.jsonPrimitive?.double shouldBe 0.7 - get("maxTokens")?.jsonPrimitive?.content shouldBe "1000" - get("topP")?.jsonPrimitive?.double shouldBe 0.95 + tools shouldBe EmptyJsonObject } roots shouldNotBeNull { listChanged shouldBe true @@ -174,8 +168,8 @@ class CapabilitiesDslTest { val request = buildInitializeRequest { protocolVersion = "2024-11-05" capabilities { - sampling { put("temperature", 0.5) } - sampling { put("temperature", 0.9) } // Should overwrite + sampling(ClientCapabilities.Sampling()) + sampling(ClientCapabilities.Sampling(tools = EmptyJsonObject)) // Should overwrite experimental { put("feature", "v1") } experimental { put("feature", "v2") } // Should overwrite } @@ -184,7 +178,7 @@ class CapabilitiesDslTest { request.params.capabilities.shouldNotBeNull { sampling shouldNotBeNull { - get("temperature")?.jsonPrimitive?.double shouldBe 0.9 + tools shouldBe EmptyJsonObject } experimental shouldNotBeNull { get("feature")?.jsonPrimitive?.content shouldBe "v2" diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ContentDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ContentDslTest.kt index 180a308..4801d02 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ContentDslTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ContentDslTest.kt @@ -42,7 +42,7 @@ class ContentDslTest { } } - (request.params.messages[0].content as TextContent).shouldNotBeNull { + (request.params.messages[0].content.single() as TextContent).shouldNotBeNull { text shouldBe "Hello, world!" annotations.shouldBeNull() meta.shouldBeNull() @@ -71,7 +71,7 @@ class ContentDslTest { } } - (request.params.messages[0].content as TextContent).shouldNotBeNull { + (request.params.messages[0].content.single() as TextContent).shouldNotBeNull { text shouldBe "Analyze the quarterly sales report for Q4 2025" annotations shouldNotBeNull { audience shouldBe listOf(Role.User, Role.Assistant) @@ -98,7 +98,7 @@ class ContentDslTest { } } - (request.params.messages[0].content as TextContent).text shouldBe "Hello 🌍! Ça va? Москва 北京 مرحبا" + (request.params.messages[0].content.single() as TextContent).text shouldBe "Hello 🌍! Ça va? Москва 北京 مرحبا" } @Test @@ -112,7 +112,7 @@ class ContentDslTest { } } - (request.params.messages[0].content as TextContent).text shouldBe "" + (request.params.messages[0].content.single() as TextContent).text shouldBe "" } @Test @@ -144,7 +144,7 @@ class ContentDslTest { } } - (request.params.messages[0].content as ImageContent).shouldNotBeNull { + (request.params.messages[0].content.single() as ImageContent).shouldNotBeNull { data shouldBe "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" mimeType shouldBe "image/png" @@ -177,7 +177,7 @@ class ContentDslTest { } } - (request.params.messages[0].content as ImageContent).shouldNotBeNull { + (request.params.messages[0].content.single() as ImageContent).shouldNotBeNull { mimeType shouldBe "image/jpeg" annotations shouldNotBeNull { audience shouldBe listOf(Role.Assistant) @@ -208,7 +208,7 @@ class ContentDslTest { } } } - (request.params.messages[0].content as ImageContent).mimeType shouldBe mime + (request.params.messages[0].content.single() as ImageContent).mimeType shouldBe mime } } @@ -256,7 +256,7 @@ class ContentDslTest { } } - (request.params.messages[0].content as AudioContent).shouldNotBeNull { + (request.params.messages[0].content.single() as AudioContent).shouldNotBeNull { data shouldBe "UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAAABmYWN0BAAAAAAAAABkYXRhAAAAAA==" mimeType shouldBe "audio/wav" annotations.shouldBeNull() @@ -288,7 +288,7 @@ class ContentDslTest { } } - (request.params.messages[0].content as AudioContent).shouldNotBeNull { + (request.params.messages[0].content.single() as AudioContent).shouldNotBeNull { mimeType shouldBe "audio/mpeg" annotations shouldNotBeNull { audience shouldBe listOf(Role.User) @@ -349,7 +349,7 @@ class ContentDslTest { } } } - (requestMin.params.messages[0].content as TextContent).annotations?.priority shouldBe 0.0 + (requestMin.params.messages[0].content.single() as TextContent).annotations?.priority shouldBe 0.0 // Priority = 1.0 (maximum) val requestMax = buildCreateMessageRequest { @@ -361,7 +361,7 @@ class ContentDslTest { } } } - (requestMax.params.messages[0].content as TextContent).annotations?.priority shouldBe 1.0 + (requestMax.params.messages[0].content.single() as TextContent).annotations?.priority shouldBe 1.0 } @Test @@ -376,7 +376,7 @@ class ContentDslTest { } } - (request.params.messages[0].content as TextContent).annotations shouldNotBeNull { + (request.params.messages[0].content.single() as TextContent).annotations shouldNotBeNull { priority shouldBe 0.5 audience shouldBe listOf(Role.User) lastModified.shouldBeNull() diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/InitializeDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/InitializeDslTest.kt index aa53cce..17e5ae6 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/InitializeDslTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/InitializeDslTest.kt @@ -9,7 +9,6 @@ import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject import io.modelcontextprotocol.kotlin.sdk.types.Implementation import io.modelcontextprotocol.kotlin.sdk.types.buildInitializeRequest import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.int import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.put import kotlin.test.Test @@ -21,9 +20,7 @@ class InitializeDslTest { val request = buildInitializeRequest { protocolVersion = "2024-11-05" capabilities { - sampling { - put("maxTokens", 100) - } + sampling(ClientCapabilities.Sampling()) roots(listChanged = true) elicitation { put("mode", "interactive") @@ -47,7 +44,7 @@ class InitializeDslTest { request.params.protocolVersion shouldBe "2024-11-05" request.params.capabilities.shouldNotBeNull { - sampling?.get("maxTokens")?.jsonPrimitive?.int shouldBe 100 + sampling shouldNotBeNull { } roots?.listChanged shouldBe true elicitation?.get("mode")?.jsonPrimitive?.content shouldBe "interactive" experimental?.get("custom")?.jsonPrimitive?.content shouldBe "true" @@ -79,8 +76,8 @@ class InitializeDslTest { } @Test - fun `capabilities DSL should support direct JsonObject values`() { - val samplingObj = buildJsonObject { put("key", "value") } + fun `capabilities DSL should support direct typed values`() { + val samplingValue = ClientCapabilities.Sampling(tools = EmptyJsonObject) val elicitationObj = buildJsonObject { put("key", "value") } val experimentalObj = buildJsonObject { put("key", "value") } val extensionsMap = mapOf( @@ -90,7 +87,7 @@ class InitializeDslTest { val request = buildInitializeRequest { protocolVersion = "1.0" capabilities { - sampling(samplingObj) + sampling(samplingValue) elicitation(elicitationObj) experimental(experimentalObj) extensions(extensionsMap) @@ -99,7 +96,7 @@ class InitializeDslTest { } request.params.capabilities.shouldNotBeNull { - sampling shouldBe samplingObj + sampling shouldBe samplingValue elicitation shouldBe elicitationObj experimental shouldBe experimentalObj extensions shouldBe extensionsMap diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/SamplingDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/SamplingDslTest.kt index c53a400..10be8b0 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/SamplingDslTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/SamplingDslTest.kt @@ -71,7 +71,7 @@ class SamplingDslTest { request.params.stopSequences shouldBe listOf("STOP") request.params.messages shouldHaveSize 5 - (request.params.messages[2].content as TextContent).shouldNotBeNull { + (request.params.messages[2].content.single() as TextContent).shouldNotBeNull { text shouldBe "Text with annotations" annotations shouldNotBeNull { audience shouldBe listOf(Role.User) @@ -81,13 +81,13 @@ class SamplingDslTest { meta?.get("key")?.jsonPrimitive?.content shouldBe "value" } - (request.params.messages[3].content as ImageContent).shouldNotBeNull { + (request.params.messages[3].content.single() as ImageContent).shouldNotBeNull { data shouldBe "base64image" mimeType shouldBe "image/png" annotations?.priority shouldBe 0.5 } - (request.params.messages[4].content as AudioContent).shouldNotBeNull { + (request.params.messages[4].content.single() as AudioContent).shouldNotBeNull { data shouldBe "base64audio" mimeType shouldBe "audio/wav" } @@ -102,7 +102,7 @@ class SamplingDslTest { @Test fun `buildCreateMessageRequest should support direct assignments`() { - val messages = listOf(SamplingMessage(Role.User, TextContent("Hello"))) + val messages = listOf(SamplingMessage(Role.User, listOf(TextContent("Hello")))) val preferences = ModelPreferences(costPriority = 0.1) val request = buildCreateMessageRequest { maxTokens = 100 @@ -126,8 +126,8 @@ class SamplingDslTest { assistant(content) } } - request.params.messages[0].content shouldBe content - request.params.messages[1].content shouldBe content + request.params.messages[0].content.single() shouldBe content + request.params.messages[1].content.single() shouldBe content } @Test diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnection.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnection.kt index 23dbe89..e64b0da 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnection.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnection.kt @@ -11,6 +11,7 @@ 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.IncludeContext import io.modelcontextprotocol.kotlin.sdk.types.ListRootsRequest import io.modelcontextprotocol.kotlin.sdk.types.ListRootsResult import io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel @@ -200,13 +201,34 @@ internal class ClientConnectionImpl(private val session: ServerSession) : Client } override suspend fun createMessage(request: CreateMessageRequest, options: RequestOptions?): CreateMessageResult { - with(request.params) { - logger.debug { - "Creating message with ${messages.size} messages, maxTokens=$maxTokens, " + - "temperature=$temperature, " + - "systemPrompt=${if (systemPrompt != null) "present" else "absent"}" + val caps = session.clientCapabilities + val params = request.params + + if (params.tools != null || params.toolChoice != null) { + requireNotNull(caps?.sampling?.tools) { + "Client did not advertise sampling.tools capability; cannot send " + + "tools/toolChoice in sampling/createMessage request." } } + + if (params.includeContext != null && params.includeContext != IncludeContext.None) { + if (caps?.sampling?.context == null) { + logger.warn { + "Client did not advertise sampling.context capability but server requested " + + "includeContext=${params.includeContext}. This is soft-deprecated and may be " + + "rejected by future spec versions." + } + } + } + + validateSamplingMessages(params.messages) + + logger.debug { + "Creating message with ${params.messages.size} messages, maxTokens=${params.maxTokens}, " + + "temperature=${params.temperature}, " + + "systemPrompt=${if (params.systemPrompt != null) "present" else "absent"}, " + + "tools=${params.tools?.size ?: 0}, toolChoice=${params.toolChoice?.mode}" + } logger.trace { "Full createMessage params: $request" } return request(request, options) } diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SamplingValidation.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SamplingValidation.kt new file mode 100644 index 0000000..ef40808 --- /dev/null +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SamplingValidation.kt @@ -0,0 +1,51 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.modelcontextprotocol.kotlin.sdk.types.SamplingMessage +import io.modelcontextprotocol.kotlin.sdk.types.ToolResultContent +import io.modelcontextprotocol.kotlin.sdk.types.ToolUseContent + +/** + * Validates tool_use / tool_result rules on the last two messages of [messages]. + * + * Only the boundary between the previous message and the final message matters, because + * earlier turns were already validated when they were appended; the sole freshly-built + * portion of a sampling request is its tail. + * + * Rules enforced: + * + * 1. If the last message contains any `tool_result` block, it MUST contain only + * `tool_result` blocks (no mixing with text/image/audio/tool_use). + * 2. If the last message contains any `tool_result`, the previous message MUST contain + * matching `tool_use` blocks. + * 3. If the previous message contains `tool_use` blocks, the last message's + * `tool_result` ids MUST form exactly the same set. + * + * On the first violation throws [IllegalArgumentException]. No-op when there are fewer + * than two messages or no tool_use / tool_result blocks are involved. + */ +internal fun validateSamplingMessages(messages: List) { + if (messages.isEmpty()) return + + val last = messages.last().content + val hasToolResult = last.any { it is ToolResultContent } + + val previous = messages.getOrNull(messages.size - 2)?.content.orEmpty() + val hasPreviousToolUse = previous.any { it is ToolUseContent } + + if (hasToolResult) { + require(last.all { it is ToolResultContent }) { + "The last message must contain only tool_result content if any is present" + } + require(hasPreviousToolUse) { + "tool_result blocks are not matching any tool_use from the previous message" + } + } + + if (hasPreviousToolUse) { + val toolUseIds = previous.filterIsInstance().map { it.id }.toSet() + val toolResultIds = last.filterIsInstance().map { it.toolUseId }.toSet() + require(toolUseIds == toolResultIds) { + "ids of tool_result blocks and tool_use blocks from previous message do not match" + } + } +} diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SamplingTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SamplingTest.kt new file mode 100644 index 0000000..2b5446b --- /dev/null +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SamplingTest.kt @@ -0,0 +1,86 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.modelcontextprotocol.kotlin.sdk.types.Role +import io.modelcontextprotocol.kotlin.sdk.types.SamplingMessage +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.sdk.types.ToolResultContent +import io.modelcontextprotocol.kotlin.sdk.types.ToolUseContent +import kotlinx.serialization.json.JsonObject +import org.junit.jupiter.api.assertDoesNotThrow +import kotlin.test.Test +import kotlin.test.assertFailsWith + +/** + * Unit tests for [validateSamplingMessages]: pure-function checks of the SEP-1577 + * last-two-messages tool_use / tool_result rules. + * + * End-to-end and capability-enforcement tests for `Server.createMessage` live in the + * `integration-test` module under the same package. + */ +class SamplingTest { + + private fun toolUse(id: String) = ToolUseContent(id = id, name = "t", input = JsonObject(emptyMap())) + private fun toolResult(id: String) = ToolResultContent(toolUseId = id, content = emptyList()) + + @Test + fun `validate empty message list is valid`() { + assertDoesNotThrow { validateSamplingMessages(emptyList()) } + } + + @Test + fun `validate text-only conversation is valid`() { + assertDoesNotThrow { + validateSamplingMessages( + listOf( + SamplingMessage(Role.User, TextContent("hi")), + SamplingMessage(Role.Assistant, TextContent("hello")), + ), + ) + } + } + + @Test + fun `validate matched tool_use and tool_result at boundary is valid`() { + assertDoesNotThrow { + validateSamplingMessages( + listOf( + SamplingMessage(Role.Assistant, listOf(TextContent("using tool"), toolUse("c1"))), + SamplingMessage(Role.User, toolResult("c1")), + ), + ) + } + } + + @Test + fun `validate orphan tool_result with no previous message fails`() { + assertFailsWith { + validateSamplingMessages( + listOf(SamplingMessage(Role.User, toolResult("missing"))), + ) + } + } + + @Test + fun `validate tool_result mixed with text in last message fails`() { + assertFailsWith { + validateSamplingMessages( + listOf( + SamplingMessage(Role.Assistant, toolUse("c1")), + SamplingMessage(Role.User, listOf(toolResult("c1"), TextContent("extra"))), + ), + ) + } + } + + @Test + fun `validate tool_result ids must match tool_use ids in previous message`() { + assertFailsWith { + validateSamplingMessages( + listOf( + SamplingMessage(Role.Assistant, toolUse("c1")), + SamplingMessage(Role.User, toolResult("wrong_id")), + ), + ) + } + } +} From 66d9efb59917d341d84ef71aedfb905ccc2c72c6 Mon Sep 17 00:00:00 2001 From: johny <69754601+jskjw157@users.noreply.github.com> Date: Wed, 29 Apr 2026 02:12:28 +0900 Subject: [PATCH 268/449] test(streamable-http): add missing integration tests for pagination, bad request, and logging (#613) This PR adds the remaining non-security integration tests for the Streamable HTTP transport as outlined in #183 and left over from #486. ### Changes included: - **Pagination**: Added cursor-based pagination tests for Prompts, Resources, and Tools to `Abstract*IntegrationTest`. - **Bad Request**: Added tests to verify that invalid cursor parameters correctly throw an `McpException` with `INTERNAL_ERROR`. - **Logging**: Added `LoggingIntegrationTestStreamableHttp` to specifically test `setLevel` and logging message notifications over Streamable HTTP. *Note: Security (Allow/Deny) tests are intentionally excluded as discussed in #486, pending further server auth model implementation.* Co-authored-by: Pavel Gorgulov --- .../kotlin/AbstractPromptIntegrationTest.kt | 73 +++++++++++++++++ .../kotlin/AbstractResourceIntegrationTest.kt | 79 ++++++++++++++++++ .../kotlin/AbstractToolIntegrationTest.kt | 80 +++++++++++++++++++ 3 files changed, 232 insertions(+) diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt index d14ae88..eaa51ad 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt @@ -6,9 +6,15 @@ import io.kotest.matchers.string.shouldContain import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequestParams import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult +import io.modelcontextprotocol.kotlin.sdk.types.ListPromptsRequest +import io.modelcontextprotocol.kotlin.sdk.types.ListPromptsResult import io.modelcontextprotocol.kotlin.sdk.types.McpException +import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.PaginatedRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.Prompt import io.modelcontextprotocol.kotlin.sdk.types.PromptArgument import io.modelcontextprotocol.kotlin.sdk.types.PromptMessage +import io.modelcontextprotocol.kotlin.sdk.types.RPCError import io.modelcontextprotocol.kotlin.sdk.types.Role import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.types.TextContent @@ -18,7 +24,9 @@ import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows +import java.util.concurrent.CopyOnWriteArrayList import kotlin.test.assertEquals +import kotlin.test.assertFailsWith import kotlin.test.assertNotNull import kotlin.test.assertTrue @@ -697,4 +705,69 @@ abstract class AbstractPromptIntegrationTest : KotlinTestBase() { exception.message shouldBe expectedMessage } } + + @Test + fun testListPromptsPagination() = runBlocking(Dispatchers.IO) { + val receivedCursors = CopyOnWriteArrayList() + val page1 = listOf(Prompt(name = "p-1", description = "desc"), Prompt(name = "p-2", description = "desc")) + val page2 = listOf(Prompt(name = "p-3", description = "desc"), Prompt(name = "p-4", description = "desc")) + val page3 = listOf(Prompt(name = "p-5", description = "desc")) + + server.sessions.forEach { (_, session) -> + session.setRequestHandler(Method.Defined.PromptsList) { request, _ -> + receivedCursors += request.cursor + when (request.cursor) { + null -> ListPromptsResult(prompts = page1, nextCursor = "cursor-2") + "cursor-2" -> ListPromptsResult(prompts = page2, nextCursor = "cursor-3") + "cursor-3" -> ListPromptsResult(prompts = page3, nextCursor = null) + else -> error("Unexpected cursor: ${request.cursor}") + } + } + } + + val collected = mutableListOf() + var cursor: String? = null + do { + val request = if (cursor == null) { + ListPromptsRequest() + } else { + ListPromptsRequest(PaginatedRequestParams(cursor = cursor)) + } + val response = client.listPrompts(request) + collected += response.prompts + cursor = response.nextCursor + } while (cursor != null) + + assertEquals( + listOf(null, "cursor-2", "cursor-3"), + receivedCursors.toList(), + "Client must forward each nextCursor into the next request", + ) + assertEquals( + listOf("p-1", "p-2", "p-3", "p-4", "p-5"), + collected.map { it.name }, + "Client must accumulate pages in order without duplicates or reordering", + ) + } + + @Test + fun testListPromptsInvalidCursor() = runBlocking(Dispatchers.IO) { + val invalidCursor = "not-a-valid-cursor" + + server.sessions.forEach { (_, session) -> + session.setRequestHandler(Method.Defined.PromptsList) { _, _ -> + throw McpException( + code = RPCError.ErrorCode.INVALID_PARAMS, + message = "Invalid cursor: $invalidCursor", + ) + } + } + + val exception = assertFailsWith { + client.listPrompts(ListPromptsRequest(PaginatedRequestParams(cursor = invalidCursor))) + } + + assertEquals(RPCError.ErrorCode.INVALID_PARAMS, exception.code) + assertEquals("Invalid cursor: $invalidCursor", exception.message) + } } diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt index 4fc53a5..da7d85b 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt @@ -1,11 +1,16 @@ package io.modelcontextprotocol.kotlin.sdk.integration.kotlin import io.modelcontextprotocol.kotlin.sdk.types.BlobResourceContents +import io.modelcontextprotocol.kotlin.sdk.types.ListResourcesRequest +import io.modelcontextprotocol.kotlin.sdk.types.ListResourcesResult import io.modelcontextprotocol.kotlin.sdk.types.McpException +import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.PaginatedRequestParams import io.modelcontextprotocol.kotlin.sdk.types.RPCError import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequestParams import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult +import io.modelcontextprotocol.kotlin.sdk.types.Resource import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.types.SubscribeRequest import io.modelcontextprotocol.kotlin.sdk.types.SubscribeRequestParams @@ -18,8 +23,10 @@ import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows +import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.atomic.AtomicBoolean import kotlin.test.assertEquals +import kotlin.test.assertFailsWith import kotlin.test.assertNotNull import kotlin.test.assertTrue @@ -309,4 +316,76 @@ abstract class AbstractResourceIntegrationTest : KotlinTestBase() { assertTrue(result.contents.isNotEmpty(), "Result contents should not be empty") } } + + @Test + fun testListResourcesPagination() = runBlocking(Dispatchers.IO) { + val receivedCursors = CopyOnWriteArrayList() + val page1 = listOf( + Resource(uri = "test://r-1", name = "r-1"), + Resource(uri = "test://r-2", name = "r-2"), + Resource(uri = "test://r-3", name = "r-3"), + ) + val page2 = listOf( + Resource(uri = "test://r-4", name = "r-4"), + Resource(uri = "test://r-5", name = "r-5"), + ) + val page3 = listOf(Resource(uri = "test://r-6", name = "r-6")) + + server.sessions.forEach { (_, session) -> + session.setRequestHandler(Method.Defined.ResourcesList) { request, _ -> + receivedCursors += request.cursor + when (request.cursor) { + null -> ListResourcesResult(resources = page1, nextCursor = "cursor-2") + "cursor-2" -> ListResourcesResult(resources = page2, nextCursor = "cursor-3") + "cursor-3" -> ListResourcesResult(resources = page3, nextCursor = null) + else -> error("Unexpected cursor: ${request.cursor}") + } + } + } + + val collected = mutableListOf() + var cursor: String? = null + do { + val request = if (cursor == null) { + ListResourcesRequest() + } else { + ListResourcesRequest(PaginatedRequestParams(cursor = cursor)) + } + val response = client.listResources(request) + collected += response.resources + cursor = response.nextCursor + } while (cursor != null) + + assertEquals( + listOf(null, "cursor-2", "cursor-3"), + receivedCursors.toList(), + "Client must forward each nextCursor into the next request", + ) + assertEquals( + listOf("test://r-1", "test://r-2", "test://r-3", "test://r-4", "test://r-5", "test://r-6"), + collected.map { it.uri }, + "Client must accumulate pages in order without duplicates or reordering", + ) + } + + @Test + fun testListResourcesInvalidCursor() = runBlocking(Dispatchers.IO) { + val invalidCursor = "not-a-valid-cursor" + + server.sessions.forEach { (_, session) -> + session.setRequestHandler(Method.Defined.ResourcesList) { _, _ -> + throw McpException( + code = RPCError.ErrorCode.INVALID_PARAMS, + message = "Invalid cursor: $invalidCursor", + ) + } + } + + val exception = assertFailsWith { + client.listResources(ListResourcesRequest(PaginatedRequestParams(cursor = invalidCursor))) + } + + assertEquals(RPCError.ErrorCode.INVALID_PARAMS, exception.code) + assertEquals("Invalid cursor: $invalidCursor", exception.message) + } } diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt index 7da82cc..b802122 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt @@ -6,8 +6,15 @@ import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequestParams import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult import io.modelcontextprotocol.kotlin.sdk.types.ContentBlock import io.modelcontextprotocol.kotlin.sdk.types.ImageContent +import io.modelcontextprotocol.kotlin.sdk.types.ListToolsRequest +import io.modelcontextprotocol.kotlin.sdk.types.ListToolsResult +import io.modelcontextprotocol.kotlin.sdk.types.McpException +import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.PaginatedRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.RPCError import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.sdk.types.Tool import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -24,7 +31,9 @@ import org.junit.jupiter.api.Test import java.text.DecimalFormat import java.text.DecimalFormatSymbols import java.util.Locale +import java.util.concurrent.CopyOnWriteArrayList import kotlin.test.assertEquals +import kotlin.test.assertFailsWith import kotlin.test.assertNotNull import kotlin.test.assertTrue @@ -791,4 +800,75 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() { "Error message should indicate the tool was not found", ) } + + @Test + fun testListToolsPagination() = runBlocking(Dispatchers.IO) { + val receivedCursors = CopyOnWriteArrayList() + val page1 = listOf( + Tool(name = "t-1", inputSchema = ToolSchema()), + Tool(name = "t-2", inputSchema = ToolSchema()), + ) + val page2 = listOf( + Tool(name = "t-3", inputSchema = ToolSchema()), + Tool(name = "t-4", inputSchema = ToolSchema()), + ) + val page3 = listOf(Tool(name = "t-5", inputSchema = ToolSchema())) + + server.sessions.forEach { (_, session) -> + session.setRequestHandler(Method.Defined.ToolsList) { request, _ -> + receivedCursors += request.cursor + when (request.cursor) { + null -> ListToolsResult(tools = page1, nextCursor = "cursor-2") + "cursor-2" -> ListToolsResult(tools = page2, nextCursor = "cursor-3") + "cursor-3" -> ListToolsResult(tools = page3, nextCursor = null) + else -> error("Unexpected cursor: ${request.cursor}") + } + } + } + + val collected = mutableListOf() + var cursor: String? = null + do { + val request = if (cursor == null) { + ListToolsRequest() + } else { + ListToolsRequest(PaginatedRequestParams(cursor = cursor)) + } + val response = client.listTools(request) + collected += response.tools + cursor = response.nextCursor + } while (cursor != null) + + assertEquals( + listOf(null, "cursor-2", "cursor-3"), + receivedCursors.toList(), + "Client must forward each nextCursor into the next request", + ) + assertEquals( + listOf("t-1", "t-2", "t-3", "t-4", "t-5"), + collected.map { it.name }, + "Client must accumulate pages in order without duplicates or reordering", + ) + } + + @Test + fun testListToolsInvalidCursor() = runBlocking(Dispatchers.IO) { + val invalidCursor = "not-a-valid-cursor" + + server.sessions.forEach { (_, session) -> + session.setRequestHandler(Method.Defined.ToolsList) { _, _ -> + throw McpException( + code = RPCError.ErrorCode.INVALID_PARAMS, + message = "Invalid cursor: $invalidCursor", + ) + } + } + + val exception = assertFailsWith { + client.listTools(ListToolsRequest(PaginatedRequestParams(cursor = invalidCursor))) + } + + assertEquals(RPCError.ErrorCode.INVALID_PARAMS, exception.code) + assertEquals("Invalid cursor: $invalidCursor", exception.message) + } } From a238fc0a2ee5af6fc0fd1a080fa964ca79f4e6ff Mon Sep 17 00:00:00 2001 From: Amr Youssef Date: Wed, 29 Apr 2026 03:09:05 -0700 Subject: [PATCH 269/449] fix(server): evict stale GET SSE stream mapping on reconnect (#716) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Motivation and Context When a GET SSE stream disconnects and the client reconnects on the same session, the new GET is silently rejected because the previous stream's `STANDALONE_SSE_STREAM_ID` entry in `streamsMapping` hasn't been cleaned up yet. Cleanup via `invokeOnCompletion` is asynchronous — there is a race window where the stale entry persists and blocks reconnection. Additionally, Ktor's `sse {}` handler commits 200 OK headers before `handleGetRequest` runs, so the intended `call.reject(HttpStatusCode.Conflict, ...)` cannot change the already-committed HTTP status. On all tested engines (test host, CIO, Netty), clients receive a 200 OK with an immediately-closed empty SSE stream rather than a 409 Conflict. Server-side `CallLogging` reports 409 (from the response object, not the wire status), which is misleading. ### What this PR does 1. **Explicitly closes the old SSE session** before establishing a new stream. If the old session is already dead, `close()` is a no-op. This is the synchronous cleanup that the async `invokeOnCompletion` couldn't provide. 2. **Replaces `invokeOnCompletion` with `try/finally`** around `awaitCancellation()` — cleanup runs during cancellation propagation (earlier in the lifecycle) rather than after job completion. An identity check (`=== newContext`) ensures only this stream's entry is removed, not a replacement's. 3. **Adds identity-based removal in `emitOnStream`** — a failed `send()` on an old stream can no longer accidentally evict the new stream's mapping. 4. **Rethrows `CancellationException`** instead of swallowing it, preserving structured concurrency. The TypeScript reference implementation avoids this race via synchronous `ReadableStream.cancel()` cleanup. The Kotlin coroutine equivalent (`invokeOnCompletion`) is inherently async. The Go SDK uses `defer stream.release()` under a mutex for the same reason. The Java SDK uses unconditional `AtomicReference.set()` replacement with `compareAndExchange` on close. Fixes #715 ## How Has This Been Tested? All tests run against latest `main` (`bf8dc6d`). ktlint and detekt pass clean. - 2 new unit tests (Ktor test host): reconnect after disconnect, concurrent stream takeover - 2 new integration tests (real CIO embedded server): same scenarios over real HTTP - Manual verification against a live Ktor+Netty MCP server (curl): confirmed 200 OK on the wire with immediately-closed stream on reconnect (pre-fix), live stream on reconnect (post-fix) - All existing server unit tests pass (76 total) - All existing integration tests pass (306 total) ## Breaking Changes The server no longer returns 409 Conflict for a second GET SSE stream on the same session. Instead, the old stream is explicitly closed and the new stream takes over. The 409 was already unreachable as an HTTP status — Ktor's `sse {}` handler commits 200 OK before the transport handler runs on all tested engines (test host, CIO, Netty), so clients never received 409 on the wire. ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed ## Additional context This bug likely became reachable after #681 (April 10) fixed the Netty `appendSseHeaders` crash that previously prevented GET SSE from working at all on Netty. Cross-SDK comparison of how each SDK handles a second GET SSE on the same session: | SDK | Behavior | Cleanup mechanism | Stale mapping bug? | |-----|----------|-------------------|-------------------| | TypeScript (reference) | 409 Conflict | Synchronous `ReadableStream.cancel()` | No | | Go | 409 Conflict | `defer stream.release()` under mutex | No | | Java | Replaces (no 409) | `AtomicReference.set()` + `compareAndExchange` on close | No | | C# | 400 Bad Request | Boolean flag, never reset (no reconnect support) | No | | Kotlin (before fix) | 409 (unreachable on wire) | Async `invokeOnCompletion` | **Yes** | | Kotlin (this PR) | Closes old + replaces | `try/finally` + identity-based removal | No | --------- Co-authored-by: devcrocod --- .../StreamableHttpSseReconnectTest.kt | 171 ++++++++++++++++++ .../server/StreamableHttpServerTransport.kt | 54 ++++-- .../StreamableHttpServerTransportTest.kt | 101 +++++++++++ 3 files changed, 309 insertions(+), 17 deletions(-) create mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/StreamableHttpSseReconnectTest.kt diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/StreamableHttpSseReconnectTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/StreamableHttpSseReconnectTest.kt new file mode 100644 index 0000000..751f8dd --- /dev/null +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/StreamableHttpSseReconnectTest.kt @@ -0,0 +1,171 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.streamablehttp + +import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.shouldBe +import io.ktor.client.HttpClient +import io.ktor.client.plugins.sse.SSE +import io.ktor.client.request.header +import io.ktor.client.request.post +import io.ktor.client.request.prepareGet +import io.ktor.client.request.setBody +import io.ktor.client.statement.bodyAsChannel +import io.ktor.http.ContentType +import io.ktor.http.HttpHeaders +import io.ktor.http.HttpStatusCode +import io.ktor.http.contentType +import io.ktor.utils.io.readUTF8Line +import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest +import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest +import io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION +import io.modelcontextprotocol.kotlin.sdk.types.toJSON +import io.modelcontextprotocol.kotlin.test.utils.actualPort +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.json.Json +import kotlin.test.Test +import kotlin.test.assertNotNull +import io.ktor.client.engine.cio.CIO as ClientCIO + +private const val SESSION_ID_HEADER = "mcp-session-id" +private const val PROTOCOL_VERSION_HEADER = "mcp-protocol-version" + +/** + * Integration tests for GET SSE stream reconnection using a real embedded CIO server. + * + * Verifies that the transport correctly evicts stale STANDALONE_SSE_STREAM_ID + * entries when a client reconnects after a disconnect, rather than silently + * rejecting the new stream. + */ +class StreamableHttpSseReconnectTest : AbstractStreamableHttpIntegrationTest() { + + /** + * Verifies that after a GET SSE stream disconnects and the client + * immediately reconnects, the server evicts the stale stream mapping + * and allows the new stream to succeed. + */ + @Test + fun `GET SSE reconnect after disconnect should succeed`(): Unit = runBlocking(Dispatchers.IO) { + var server: StreamableHttpTestServer? = null + var httpClient: HttpClient? = null + + try { + server = initTestServer("reconnect-test") + val port = server.ktorServer.actualPort() + val mcpUrl = "http://$URL:$port/mcp" + + httpClient = HttpClient(ClientCIO) { install(SSE) } + + // Step 1: Initialize session via POST + val initResponse = httpClient.post(mcpUrl) { + contentType(ContentType.Application.Json) + header( + HttpHeaders.Accept, + "${ContentType.Application.Json}, ${ContentType.Text.EventStream}", + ) + setBody(Json.encodeToString(buildInitPayload())) + } + initResponse.status shouldBe HttpStatusCode.OK + val sessionId = assertNotNull(initResponse.headers[SESSION_ID_HEADER]) + + // Step 2: Open GET SSE stream, consume the flush event, then disconnect + httpClient.prepareGet(mcpUrl) { + header(HttpHeaders.Accept, ContentType.Text.EventStream.toString()) + header(SESSION_ID_HEADER, sessionId) + header(PROTOCOL_VERSION_HEADER, LATEST_PROTOCOL_VERSION) + }.execute { response -> + response.status shouldBe HttpStatusCode.OK + response.bodyAsChannel().readUTF8Line() + } + + // Step 3: Immediately reconnect. The transport detects that the + // previous stream's coroutine is no longer active and evicts the + // stale mapping, allowing the new stream to succeed. + httpClient.prepareGet(mcpUrl) { + header(HttpHeaders.Accept, ContentType.Text.EventStream.toString()) + header(SESSION_ID_HEADER, sessionId) + header(PROTOCOL_VERSION_HEADER, LATEST_PROTOCOL_VERSION) + }.execute { response -> + response.status shouldBe HttpStatusCode.OK + response.headers[SESSION_ID_HEADER] shouldBe sessionId + + val channel = response.bodyAsChannel() + val firstLine = channel.readUTF8Line() + firstLine.shouldNotBeNull() + channel.isClosedForRead shouldBe false + } + } finally { + httpClient?.close() + server?.ktorServer?.stopSuspend(1000, 2000) + } + } + + /** + * Verifies that a second concurrent GET SSE stream on the same session + * closes the old stream and takes over. The new stream should be live. + */ + @Test + fun `concurrent GET SSE stream closes old stream and takes over`(): Unit = runBlocking(Dispatchers.IO) { + var server: StreamableHttpTestServer? = null + var httpClient: HttpClient? = null + + try { + server = initTestServer("takeover-test") + val port = server.ktorServer.actualPort() + val mcpUrl = "http://$URL:$port/mcp" + + httpClient = HttpClient(ClientCIO) { install(SSE) } + + // Step 1: Initialize session via POST + val initResponse = httpClient.post(mcpUrl) { + contentType(ContentType.Application.Json) + header( + HttpHeaders.Accept, + "${ContentType.Application.Json}, ${ContentType.Text.EventStream}", + ) + setBody(Json.encodeToString(buildInitPayload())) + } + initResponse.status shouldBe HttpStatusCode.OK + val sessionId = assertNotNull(initResponse.headers[SESSION_ID_HEADER]) + + // Step 2: Open first GET SSE stream and keep it open + httpClient.prepareGet(mcpUrl) { + header(HttpHeaders.Accept, ContentType.Text.EventStream.toString()) + header(SESSION_ID_HEADER, sessionId) + header(PROTOCOL_VERSION_HEADER, LATEST_PROTOCOL_VERSION) + }.execute { firstResponse -> + firstResponse.status shouldBe HttpStatusCode.OK + firstResponse.bodyAsChannel().readUTF8Line() + + // Step 3: Open a second GET — closes old stream, new one takes over + httpClient.prepareGet(mcpUrl) { + header(HttpHeaders.Accept, ContentType.Text.EventStream.toString()) + header(SESSION_ID_HEADER, sessionId) + header(PROTOCOL_VERSION_HEADER, LATEST_PROTOCOL_VERSION) + }.execute { secondResponse -> + secondResponse.status shouldBe HttpStatusCode.OK + secondResponse.headers[SESSION_ID_HEADER] shouldBe sessionId + + // New stream is alive + val secondChannel = secondResponse.bodyAsChannel() + val firstLine = secondChannel.readUTF8Line() + firstLine.shouldNotBeNull() + secondChannel.isClosedForRead shouldBe false + } + } + } finally { + httpClient?.close() + server?.ktorServer?.stopSuspend(1000, 2000) + } + } + + private fun buildInitPayload(): JSONRPCRequest = InitializeRequest( + InitializeRequestParams( + protocolVersion = LATEST_PROTOCOL_VERSION, + capabilities = ClientCapabilities(), + clientInfo = Implementation(name = "reconnect-test-client", version = "1.0.0"), + ), + ).toJSON() +} diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt index 497005a..f232001 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt @@ -464,27 +464,40 @@ public class StreamableHttpServerTransport(private val configuration: Configurat } } - if (STANDALONE_SSE_STREAM_ID in streamsMapping) { - call.reject( - HttpStatusCode.Conflict, - RPCError.ErrorCode.CONNECTION_CLOSED, - "Conflict: Only one SSE stream is allowed per session", - ) - return - } - // SSE headers (Content-Type, Cache-Control, Connection) are already set by the framework's SSE handler flushSse(sseSession) - streamsMapping[STANDALONE_SSE_STREAM_ID] = SessionContext(sseSession, call) + val newContext = SessionContext(sseSession, call) + streamMutex.withLock { + streamsMapping[STANDALONE_SSE_STREAM_ID]?.let { existingContext -> + // Close the previous SSE session. If alive, this cancels the old + // coroutine (which will hit its identity-guarded finally — that finally + // won't double-remove, since we replace the mapping below). + try { + existingContext.session?.close() + } catch (e: CancellationException) { + throw e + } catch (_: Exception) { + // Ignore — the old stream may already be closed. + } + // Evict the stale mapping — the old session is closed either way. + streamsMapping.remove(STANDALONE_SSE_STREAM_ID) + } + streamsMapping[STANDALONE_SSE_STREAM_ID] = newContext + } val clientProtocolVersion = call.request.header(MCP_PROTOCOL_VERSION_HEADER) ?: DEFAULT_NEGOTIATED_PROTOCOL_VERSION maybeSendPrimingEvent(STANDALONE_SSE_STREAM_ID, sseSession, clientProtocolVersion) - sseSession.coroutineContext.job.invokeOnCompletion { - streamsMapping.remove(STANDALONE_SSE_STREAM_ID) - } // Keep the SSE connection open until the client disconnects or the transport is closed. - // Without this, the Ktor sse{} handler returns immediately, closing the stream. - awaitCancellation() + // Cleanup uses try/finally (runs during cancellation propagation) instead of + // invokeOnCompletion (runs after job completion) to minimize the window between + // disconnect and mapping removal. Identity check ensures only this stream's entry + // is removed — not a replacement that arrived in the meantime. + try { + awaitCancellation() + } finally { + // Atomic compare-and-remove — only evict our own entry, not a replacement's. + streamsMapping.remove(STANDALONE_SSE_STREAM_ID, newContext) + } } /** Handles an HTTP DELETE request by closing the session and the transport. */ @@ -725,10 +738,17 @@ public class StreamableHttpServerTransport(private val configuration: Configurat try { session?.send(event = "message", id = eventId, data = McpJson.encodeToString(message)) } catch (e: CancellationException) { - streamsMapping.remove(streamId) + // Atomic compare-and-remove — only evict this stream's entry, not a replacement's. + val current = streamsMapping[streamId] + if (current != null && current.session === session) { + streamsMapping.remove(streamId, current) + } throw e } catch (_: Exception) { - streamsMapping.remove(streamId) + val current = streamsMapping[streamId] + if (current != null && current.session === session) { + streamsMapping.remove(streamId, current) + } } } diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt index d00e10e..8b413d8 100644 --- a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt @@ -460,6 +460,107 @@ class StreamableHttpServerTransportTest { } } + @Test + fun `second concurrent GET SSE closes old stream and takes over`() = testApplication { + val mcpPath = "/mcp" + + application { + mcpStreamableHttp(mcpPath) { + Server( + Implementation("test-server", "1.0.0"), + ServerOptions(capabilities = ServerCapabilities()), + ) + } + } + + val client = createTestClient() + + // Step 1: Initialize session via POST + val initResponse = client.post(mcpPath) { + addStreamableHeaders() + setBody(buildInitializeRequestPayload()) + } + initResponse.status shouldBe HttpStatusCode.OK + val sessionId = assertNotNull(initResponse.headers[MCP_SESSION_ID_HEADER]) + + // Step 2: Open first GET SSE stream + client.prepareGet(mcpPath) { + header(HttpHeaders.Accept, ContentType.Text.EventStream.toString()) + header(MCP_SESSION_ID_HEADER, sessionId) + header("mcp-protocol-version", LATEST_PROTOCOL_VERSION) + }.execute { firstResponse -> + firstResponse.status shouldBe HttpStatusCode.OK + firstResponse.bodyAsChannel().readUTF8Line() + + // Step 3: Open a second GET — the transport closes the old session + // and the new stream takes over. + client.prepareGet(mcpPath) { + header(HttpHeaders.Accept, ContentType.Text.EventStream.toString()) + header(MCP_SESSION_ID_HEADER, sessionId) + header("mcp-protocol-version", LATEST_PROTOCOL_VERSION) + }.execute { secondResponse -> + secondResponse.status shouldBe HttpStatusCode.OK + secondResponse.headers[MCP_SESSION_ID_HEADER] shouldBe sessionId + + // New stream is alive + val secondChannel = secondResponse.bodyAsChannel() + val firstLine = secondChannel.readUTF8Line() + firstLine.shouldNotBeNull() + secondChannel.isClosedForRead shouldBe false + } + } + } + + @Test + fun `GET SSE reconnect after previous stream disconnects should succeed`() = testApplication { + val mcpPath = "/mcp" + + application { + mcpStreamableHttp(mcpPath) { + Server( + Implementation("test-server", "1.0.0"), + ServerOptions(capabilities = ServerCapabilities()), + ) + } + } + + val client = createTestClient() + + // Step 1: Initialize session via POST + val initResponse = client.post(mcpPath) { + addStreamableHeaders() + setBody(buildInitializeRequestPayload()) + } + initResponse.status shouldBe HttpStatusCode.OK + val sessionId = assertNotNull(initResponse.headers[MCP_SESSION_ID_HEADER]) + + // Step 2: Open and then close a GET SSE stream + client.prepareGet(mcpPath) { + header(HttpHeaders.Accept, ContentType.Text.EventStream.toString()) + header(MCP_SESSION_ID_HEADER, sessionId) + header("mcp-protocol-version", LATEST_PROTOCOL_VERSION) + }.execute { response -> + response.status shouldBe HttpStatusCode.OK + response.bodyAsChannel().readUTF8Line() + } + + // Step 3: Immediately reconnect — the transport should close the stale + // stream and allow the new one. + client.prepareGet(mcpPath) { + header(HttpHeaders.Accept, ContentType.Text.EventStream.toString()) + header(MCP_SESSION_ID_HEADER, sessionId) + header("mcp-protocol-version", LATEST_PROTOCOL_VERSION) + }.execute { response -> + response.status shouldBe HttpStatusCode.OK + response.headers[MCP_SESSION_ID_HEADER] shouldBe sessionId + + val channel = response.bodyAsChannel() + val firstLine = channel.readUTF8Line() + firstLine.shouldNotBeNull() + channel.isClosedForRead shouldBe false + } + } + @Test fun `GET SSE stream includes Mcp-Session-Id header and stays open`() = testApplication { val mcpPath = "/mcp" From 64804a05c227389440f5a4c6cf2712f527b54b77 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Wed, 29 Apr 2026 12:31:49 +0200 Subject: [PATCH 270/449] refactor: explicit locked detekt config (#729) Migrate detekt to a convention plugin in `buildSrc`, switch the project to a fully explicit YAML config (`buildUponDefaultConfig = false`), fix the real findings that this stricter setup revealed, and remove `@Suppress` annotations that became unnecessary after the rule changes. ## Motivation and Context - Explicit visibility into the rule set - Lock against detekt version upgrades ## Breaking Changes None ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- .claude/skills/kdoc/SKILL.md | 77 +- .github/workflows/build.yml | 2 +- build.gradle.kts | 13 - buildSrc/build.gradle.kts | 1 + .../src/main/kotlin/mcp.detekt.gradle.kts | 23 + .../main/kotlin/mcp.multiplatform.gradle.kts | 9 - config/detekt/detekt.yml | 869 +++++++++++++++++- .../sdk/conformance/ConformanceTools.kt | 1 - gradle/libs.versions.toml | 4 +- integration-test/build.gradle.kts | 1 + integration-test/detekt-baseline-test.xml | 17 - kotlin-sdk-client/build.gradle.kts | 1 + .../detekt-baseline-commonMainSourceSet.xml | 14 - .../detekt-baseline-commonTestSourceSet.xml | 8 - kotlin-sdk-client/detekt-baseline-main.xml | 14 +- kotlin-sdk-client/detekt-baseline-test.xml | 14 - .../kotlin/sdk/client/Client.kt | 19 +- .../kotlin/sdk/client/StdioClientTransport.kt | 3 +- .../client/StreamableHttpClientTransport.kt | 25 +- .../http/StreamableHttpClientTransportTest.kt | 1 - .../kotlin/sdk/client/MockMcp.kt | 4 - .../sdk/client/StreamableHttpClientTest.kt | 4 +- kotlin-sdk-core/build.gradle.kts | 1 + .../detekt-baseline-commonMainSourceSet.xml | 27 - kotlin-sdk-core/detekt-baseline-main.xml | 25 - .../sdk/shared/AbstractClientTransport.kt | 6 +- .../kotlin/sdk/shared/AbstractTransport.kt | 8 +- .../kotlin/sdk/shared/Protocol.kt | 61 +- .../kotlin/sdk/shared/ReadBuffer.kt | 3 +- .../kotlin/sdk/shared/Transport.kt | 6 +- .../sdk/shared/WebSocketMcpTransport.kt | 1 - .../kotlin/sdk/types/McpException.kt | 10 +- .../kotlin/sdk/types/PingRequest.kt | 3 +- .../kotlin/sdk/types/common.kt | 9 +- .../kotlin/sdk/types/completion.kt | 4 +- .../kotlin/sdk/types/content.kt | 6 +- .../kotlin/sdk/types/elicitation.kt | 15 +- .../kotlin/sdk/types/jsonRpc.kt | 11 +- .../kotlin/sdk/types/methods.kt | 3 +- .../kotlin/sdk/types/notification.kt | 12 +- .../kotlin/sdk/types/request.kt | 13 +- .../kotlin/sdk/types/resources.kt | 12 +- .../kotlin/sdk/types/roots.kt | 2 +- .../kotlin/sdk/types/schema.kt | 20 +- .../kotlin/sdk/types/serializers.kt | 4 - .../kotlin/sdk/types/tasks.kt | 23 +- .../kotlin/sdk/types/tools.kt | 2 +- .../sdk/utils/PathSegmentTemplateMatcher.kt | 9 +- .../sdk/utils/ResourceTemplateMatcher.kt | 3 +- .../kotlin/sdk/types/ElicitationTest.kt | 3 - .../kotlin/sdk/types/InitializeTest.kt | 1 - .../kotlin/sdk/types/PrimitiveSchemaTest.kt | 2 - .../kotlin/sdk/types/SamplingTest.kt | 1 - .../kotlin/sdk/types/ToolsTest.kt | 1 - .../kotlin/sdk/types/dsl/SamplingDslTest.kt | 1 - kotlin-sdk-server/build.gradle.kts | 1 + .../detekt-baseline-commonMainSourceSet.xml | 13 - kotlin-sdk-server/detekt-baseline-main.xml | 23 - .../kotlin/sdk/server/ClientConnection.kt | 2 - .../kotlin/sdk/server/Feature.kt | 1 + .../sdk/server/FeatureNotificationService.kt | 3 +- .../kotlin/sdk/server/KtorServer.kt | 4 - .../kotlin/sdk/server/SSEServerTransport.kt | 4 +- .../kotlin/sdk/server/Server.kt | 14 +- .../kotlin/sdk/server/ServerSession.kt | 25 +- .../kotlin/sdk/server/StdioServerTransport.kt | 4 - .../server/StreamableHttpServerTransport.kt | 14 +- .../WebSocketMcpKtorServerExtensions.kt | 2 - .../sdk/server/AbstractKtorExtensionsTest.kt | 1 - .../sdk/server/StdioServerTransportTest.kt | 3 - kotlin-sdk-testing/build.gradle.kts | 1 + .../kotlin/sdk/testing/ChannelTransport.kt | 20 +- .../sdk/testing/ChannelTransportTest.kt | 1 - .../kotlin/sdk/package.kt | 26 +- test-utils/build.gradle.kts | 1 + test-utils/detekt-baseline-main.xml | 20 + test-utils/detekt-baseline.xml | 8 - .../kotlin/test/utils/Retry.kt | 1 - .../kotlin/test/utils/TypeScriptRunner.kt | 2 - .../kotlin/test/utils/processUtils.kt | 1 - 80 files changed, 1113 insertions(+), 514 deletions(-) create mode 100644 buildSrc/src/main/kotlin/mcp.detekt.gradle.kts delete mode 100644 integration-test/detekt-baseline-test.xml delete mode 100644 kotlin-sdk-client/detekt-baseline-commonMainSourceSet.xml delete mode 100644 kotlin-sdk-client/detekt-baseline-commonTestSourceSet.xml delete mode 100644 kotlin-sdk-client/detekt-baseline-test.xml delete mode 100644 kotlin-sdk-core/detekt-baseline-commonMainSourceSet.xml delete mode 100644 kotlin-sdk-server/detekt-baseline-commonMainSourceSet.xml create mode 100644 test-utils/detekt-baseline-main.xml delete mode 100644 test-utils/detekt-baseline.xml diff --git a/.claude/skills/kdoc/SKILL.md b/.claude/skills/kdoc/SKILL.md index 4760293..cfacfc3 100644 --- a/.claude/skills/kdoc/SKILL.md +++ b/.claude/skills/kdoc/SKILL.md @@ -5,40 +5,62 @@ description: "Add KDoc documentation to Kotlin public API. Use whenever the user # KDoc Generator -Add KDoc comments to public Kotlin API declarations. +Add KDoc to all public Kotlin declarations (classes, interfaces, objects, functions, properties, enums, type aliases, annotations — including `@Deprecated`). Read the implementation, not just the signature, to write accurate descriptions. -## What to document +## Tag placement -All public declarations (no explicit `private`/`internal`/`protected`): -- Classes, interfaces, objects, sealed classes/interfaces -- Functions, extension functions -- Properties -- Enum classes and entries -- Type aliases, annotation classes +| Where the declaration lives | Tag | +|---|---| +| Primary-ctor `val` / `var` (public, internal, or protected) | `@property` in class KDoc | +| Primary-ctor `name: Type` (no `val`/`var`) | `@param` in class KDoc | +| Primary-ctor `private val` / `private var` | `@param` in class KDoc | +| Body of class/object/interface/sealed interface | inline `/** */` above the declaration | +| Function parameter | `@param` | +| Non-Unit return type | `@return` | -Include `@Deprecated` elements. +## Style -## Context +- Summary: one concise sentence, third-person verb ("Creates", "Returns", "Represents"). Expand to 2-3 sentences only when genuinely complex. +- **Example block**: mandatory for DSL builders (show full receiver-scope usage); add for complex functions or non-obvious extension functions; skip for one-liners and simple getters. +- KDoc links (`[ClassName]`, `[methodName]`) only where they add navigational value. +- Don't write `@throws` or "suspend" notes. +- Rewrite existing KDoc if incomplete or low quality. -Read the implementation, not just the signature, to write accurate descriptions. Understanding what the code actually does prevents superficial or misleading documentation. +## Examples -## KDoc format - -**Class/interface example:** +**Class with mixed primary-constructor parameters:** ````kotlin /** * Manages active client sessions and their lifecycle. * - * Sessions are created on first connection and cleaned up - * when the transport closes. - * * @property maxSessions upper limit on concurrent sessions - * @property timeout idle timeout before a session is evicted + * @param scope coroutine scope for session lifecycle tasks + * @param secret used internally for session token signing */ +public class SessionManager( + public val maxSessions: Int, // public val → @property + scope: CoroutineScope, // no val/var → @param + private val secret: ByteArray, // private val → @param +) ```` -**Function example:** +**Body and interface properties (inline `/** */`):** + +````kotlin +/** Represents an entity that includes additional metadata. */ +public interface WithMeta { + /** Optional metadata attached to this entity. */ + public val meta: JsonObject? +} + +public class Connection(public val id: String) { + /** Capabilities negotiated during the handshake, or `null` before initialize completes. */ + public val capabilities: ServerCapabilities? get() = ... +} +```` + +**Function with Example block:** ````kotlin /** @@ -46,10 +68,7 @@ Read the implementation, not just the signature, to write accurate descriptions. * * Example: * ```kotlin - * server.addTool( - * name = "echo", - * description = "Echoes input back", - * ) { request -> + * server.addTool(name = "echo", description = "Echoes input back") { request -> * CallToolResult(content = listOf(TextContent("Echo: ${request.arguments}"))) * } * ``` @@ -60,15 +79,3 @@ Read the implementation, not just the signature, to write accurate descriptions. * @return the registered tool definition */ ```` - -## Rules - -- Summary: concise first sentence starting with a third-person verb ("Creates", "Returns", "Represents"). Expand to 2-3 sentences only when genuinely complex -- **@property** in class-level KDoc for all public properties (never as individual KDoc on the property); **@param** for function parameters -- **@return** for non-Unit return types -- **Example block**: add for DSL builders, complex functions, extension functions with non-obvious usage. Skip for trivial one-liners and simple getters -- **DSL builders**: always include an Example showing full usage with the receiver scope — this is critical for discoverability -- KDoc links (`[ClassName]`, `[methodName]`): only where it adds clear navigational value -- No **@throws** — don't document exceptions -- No **suspend** notes — coroutine nature is visible from the signature -- **Existing KDoc**: rewrite if incomplete (missing @param/@return/@property) or low quality \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index caaa3f0..ad4cc7a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,7 +34,7 @@ jobs: include: - os: ubuntu-latest job-name: "Linux Tests" - gradle-tasks: "ktlintCheck build" + gradle-tasks: "build" max-workers: 3 - os: macos-latest diff --git a/build.gradle.kts b/build.gradle.kts index 72c5553..2c2dbe3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,8 +1,5 @@ -import dev.detekt.gradle.extensions.FailOnSeverity - plugins { id("mcp.dokka") - alias(libs.plugins.detekt) alias(libs.plugins.ktlint) alias(libs.plugins.kover) } @@ -23,16 +20,6 @@ dependencies { subprojects { apply(plugin = "org.jlleitschuh.gradle.ktlint") apply(plugin = "org.jetbrains.kotlinx.kover") - - if (name != "conformance-test" && name != "docs") { - apply(plugin = "dev.detekt") - - detekt { - config = files("$rootDir/config/detekt/detekt.yml") - buildUponDefaultConfig = true - failOnSeverity.set(FailOnSeverity.Error) - } - } } dokka { diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 84676a4..4a057ce 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -8,6 +8,7 @@ repositories { } dependencies { + implementation(libs.detekt.gradle) implementation(libs.kotlin.gradle) implementation(libs.kotlin.serialization) implementation(libs.kotlinx.atomicfu.gradle) diff --git a/buildSrc/src/main/kotlin/mcp.detekt.gradle.kts b/buildSrc/src/main/kotlin/mcp.detekt.gradle.kts new file mode 100644 index 0000000..3c1419e --- /dev/null +++ b/buildSrc/src/main/kotlin/mcp.detekt.gradle.kts @@ -0,0 +1,23 @@ +import dev.detekt.gradle.Detekt +import dev.detekt.gradle.extensions.FailOnSeverity + +plugins { + id("dev.detekt") +} + +detekt { + config = files("$rootDir/config/detekt/detekt.yml") + buildUponDefaultConfig = false + parallel = true + failOnSeverity = FailOnSeverity.Error +} + +tasks.withType().configureEach { + exclude("**/generated/**", "**/generated-sources/**", "**/build/**") +} + +pluginManager.withPlugin("org.jetbrains.kotlin.multiplatform") { + tasks.named("detekt").configure { + dependsOn("detektMainJvm") + } +} diff --git a/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts b/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts index 2903cff..98a0a5e 100644 --- a/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts +++ b/buildSrc/src/main/kotlin/mcp.multiplatform.gradle.kts @@ -57,12 +57,3 @@ kotlin { explicitApi = ExplicitApiMode.Strict jvmToolchain(21) } - -tasks.named("detekt").configure { - dependsOn( - "detektMainJvm", - "detektCommonMainSourceSet", - "detektTestJvm", - "detektCommonTestSourceSet", - ) -} diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml index 4d44734..98fce0e 100644 --- a/config/detekt/detekt.yml +++ b/config/detekt/detekt.yml @@ -3,41 +3,840 @@ config: warningsAsErrors: false checkExhaustiveness: false -complexity: - LargeClass: - excludes: &testFolders - - '**/test/**' - - '**/commonTest/**' - - '**/jvmTest/**' - - '**/jsTest/**' - - '**/iosTest/**' - LongMethod: - excludes: *testFolders +processors: + active: false -coroutines: - InjectDispatcher: - excludes: *testFolders - -empty-blocks: - EmptyFunctionBlock: - excludes: *testFolders +console-reports: + active: true + exclude: + - 'ProjectStatisticsReport' + - 'ComplexityReport' + - 'NotificationReport' + - 'IssuesReport' + - 'FileBasedIssuesReport' + # - 'LiteIssuesReport' comments: - UndocumentedPublicClass: - active: true - excludes: &testAndGeneratedFolders - - '**/test/**' - - '**/commonTest/**' - - '**/jvmTest/**' - - '**/jsTest/**' - - '**/iosTest/**' - - '**/generated-sources/**' - ignoreDefaultCompanionObject: true - UndocumentedPublicFunction: - active: true - excludes: *testAndGeneratedFolders - UndocumentedPublicProperty: - active: true - excludes: *testAndGeneratedFolders - searchProtectedProperty: false - ignoreEnumEntries: true + active: true + AbsentOrWrongFileLicense: + active: false + licenseTemplateIsRegex: false + licenseTemplate: '' + DeprecatedBlockTag: + active: true + DocumentationOverPrivateFunction: + active: false + DocumentationOverPrivateProperty: + active: false + EndOfSentenceFormat: + active: false + endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)' + KDocReferencesNonPublicProperty: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + OutdatedDocumentation: + active: false + matchTypeParameters: true + matchDeclarationsOrder: true + allowParamOnConstructorProperties: false + UndocumentedPublicClass: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + searchInNestedClass: true + searchInInnerClass: true + searchInInnerObject: true + searchInInnerInterface: true + searchInProtectedClass: false + ignoreDefaultCompanionObject: true + UndocumentedPublicFunction: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + searchProtectedFunction: false + UndocumentedPublicProperty: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + searchProtectedProperty: false + ignoreEnumEntries: false + +complexity: + active: true + CognitiveComplexMethod: + active: false + allowedComplexity: 20 + ComplexCondition: + active: true + allowedConditions: 4 + ComplexInterface: + active: false + allowedDefinitions: 10 + includeStaticDeclarations: false + includePrivateDeclarations: false + ignoreOverloaded: false + CyclomaticComplexMethod: + active: false + allowedComplexity: 15 + ignoreSingleWhenExpression: false + ignoreSimpleWhenEntries: false + ignoreNestingFunctions: false + ignoreLocalFunctions: false + nestingFunctions: + - 'also' + - 'apply' + - 'forEach' + - 'isNotNull' + - 'ifNull' + - 'let' + - 'run' + - 'use' + - 'with' + LabeledExpression: + active: false + ignoredLabels: [] + LargeClass: + active: false + allowedLines: 700 + LongMethod: + active: false + allowedLines: 110 + LongParameterList: + active: false + allowedFunctionParameters: 8 + allowedConstructorParameters: 8 + ignoreDefaultParameters: false + ignoreDataClasses: true + ignoreAnnotatedParameter: [] + MethodOverloading: + active: false + allowedOverloads: 6 + NamedArguments: + active: false + allowedArguments: 3 + ignoreMethods: [] + ignoreArgumentsMatchingNames: false + NestedBlockDepth: + active: true + allowedDepth: 4 + NestedScopeFunctions: + active: false + allowedDepth: 1 + functions: + - 'kotlin.apply' + - 'kotlin.run' + - 'kotlin.with' + - 'kotlin.let' + - 'kotlin.also' + ReplaceSafeCallChainWithRun: + active: false + StringLiteralDuplication: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + allowedDuplications: 3 + ignoreAnnotation: true + allowedWithLengthLessThan: 5 + ignoreStringsRegex: '$^' + TooManyFunctions: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + allowedFunctionsPerFile: 20 + allowedFunctionsPerClass: 20 + allowedFunctionsPerInterface: 20 + allowedFunctionsPerObject: 20 + allowedFunctionsPerEnum: 11 + ignoreDeprecated: true + ignorePrivate: true + ignoreInternal: true + ignoreOverridden: false + ignoreAnnotatedFunctions: [] + +coroutines: + active: true + CoroutineLaunchedInTestWithoutRunTest: + active: false + GlobalCoroutineUsage: + active: true + InjectDispatcher: + active: false + dispatcherNames: + - 'IO' + - 'Default' + - 'Unconfined' + RedundantSuspendModifier: + active: true + SleepInsteadOfDelay: + active: true + SuspendFunInFinallySection: + active: false + SuspendFunSwallowedCancellation: + active: false + SuspendFunWithCoroutineScopeReceiver: + active: false + aliases: ['SuspendFunctionOnCoroutineScope'] + SuspendFunWithFlowReturnType: + active: true + +empty-blocks: + active: true + EmptyCatchBlock: + active: true + allowedExceptionNameRegex: '_|(ignore|expected).*' + EmptyClassBlock: + active: true + EmptyDefaultConstructor: + active: true + EmptyDoWhileBlock: + active: true + EmptyElseBlock: + active: true + EmptyFinallyBlock: + active: true + EmptyForBlock: + active: true + EmptyFunctionBlock: + active: true + ignoreOverridden: false + EmptyIfBlock: + active: true + EmptyInitBlock: + active: true + EmptyKotlinFile: + active: true + EmptySecondaryConstructor: + active: true + EmptyTryBlock: + active: true + EmptyWhenBlock: + active: true + EmptyWhileBlock: + active: true + +exceptions: + active: true + ErrorUsageWithThrowable: + active: false + ExceptionRaisedInUnexpectedLocation: + active: true + methodNames: + - 'equals' + - 'finalize' + - 'hashCode' + - 'toString' + InstanceOfCheckForException: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + NotImplementedDeclaration: + active: true + ObjectExtendsThrowable: + active: true + PrintStackTrace: + active: true + RethrowCaughtException: + active: true + ReturnFromFinally: + active: true + ignoreLabeled: false + SwallowedException: + active: true + ignoredExceptionTypes: + - 'InterruptedException' + - 'MalformedURLException' + - 'NumberFormatException' + - 'ParseException' + allowedExceptionNameRegex: '_|(ignore|expected).*' + ThrowingExceptionFromFinally: + active: true + ThrowingExceptionInMain: + active: false + ThrowingExceptionsWithoutMessageOrCause: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + exceptions: + - 'ArrayIndexOutOfBoundsException' + - 'Exception' + - 'IllegalArgumentException' + - 'IllegalMonitorStateException' + - 'IllegalStateException' + - 'IndexOutOfBoundsException' + - 'NullPointerException' + - 'RuntimeException' + - 'Throwable' + ThrowingNewInstanceOfSameException: + active: true + TooGenericExceptionCaught: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + exceptionNames: + - 'ArrayIndexOutOfBoundsException' + - 'Error' + - 'Exception' + - 'IllegalMonitorStateException' + - 'IndexOutOfBoundsException' + - 'NullPointerException' + - 'RuntimeException' + - 'Throwable' + allowedExceptionNameRegex: '_|(ignore|expected).*' + TooGenericExceptionThrown: + active: false + exceptionNames: + - 'Error' + - 'Exception' + - 'RuntimeException' + - 'Throwable' + +naming: + active: true + BooleanPropertyNaming: + active: false + allowedPattern: '^(is|has|are)' + ClassNaming: + active: true + aliases: ['ClassName'] + classPattern: '[A-Z][a-zA-Z0-9]*' + ConstructorParameterNaming: + active: true + parameterPattern: '[a-z][A-Za-z0-9]*' + privateParameterPattern: '[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + EnumNaming: + active: true + aliases: ['EnumEntryName'] + enumEntryPattern: '[A-Z][_a-zA-Z0-9]*' + ForbiddenClassName: + active: false + forbiddenName: [] + FunctionNameMaxLength: + active: false + aliases: ['FunctionMaxNameLength'] + maximumFunctionNameLength: 30 + FunctionNameMinLength: + active: false + aliases: ['FunctionMinNameLength'] + minimumFunctionNameLength: 3 + FunctionNaming: + active: true + aliases: ['FunctionName'] + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + functionPattern: '[a-z][a-zA-Z0-9]*' + excludeClassPattern: '$^' + FunctionParameterNaming: + active: true + parameterPattern: '[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + InvalidPackageDeclaration: + active: true + aliases: ['PackageDirectoryMismatch'] + rootPackage: '' + requireRootInDeclaration: false + LambdaParameterNaming: + active: false + parameterPattern: '[a-z][A-Za-z0-9]*|_' + MatchingDeclarationName: + active: false + mustBeFirst: true + multiplatformTargets: + - 'ios' + - 'android' + - 'js' + - 'jvm' + - 'native' + - 'iosArm64' + - 'iosX64' + - 'macosX64' + - 'mingwX64' + - 'linuxX64' + MemberNameEqualsClassName: + active: true + ignoreOverridden: true + NoNameShadowing: + active: true + NonBooleanPropertyPrefixedWithIs: + active: true + ObjectPropertyNaming: + active: true + aliases: ['ObjectPropertyName'] + constantPattern: '[A-Za-z][_A-Za-z0-9]*' + propertyPattern: '[A-Za-z][_A-Za-z0-9]*' + privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*' + PackageNaming: + active: true + aliases: ['PackageName'] + packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*' + TopLevelPropertyNaming: + active: true + constantPattern: '[A-Z][_A-Z0-9]*' + propertyPattern: '[A-Za-z][_A-Za-z0-9]*' + privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*' + VariableMaxLength: + active: false + maximumVariableNameLength: 64 + VariableMinLength: + active: false + minimumVariableNameLength: 1 + VariableNaming: + active: false + aliases: ['PropertyName'] + variablePattern: '[a-z][A-Za-z0-9]*' + privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + +performance: + active: true + ArrayPrimitive: + active: true + CouldBeSequence: + active: false + allowedOperations: 3 + ForEachOnRange: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + SpreadOperator: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + UnnecessaryInitOnArray: + active: false + UnnecessaryPartOfBinaryExpression: + active: true + UnnecessaryTemporaryInstantiation: + active: true + UnnecessaryTypeCasting: + active: true + +potential-bugs: + active: true + AvoidReferentialEquality: + active: true + forbiddenTypePatterns: + - 'kotlin.String' + CastNullableToNonNullableType: + active: true + ignorePlatformTypes: true + CastToNullableType: + active: false + CharArrayToStringCall: + active: false + Deprecation: + active: false + aliases: ['DEPRECATION'] + excludeImportStatements: false + DontDowncastCollectionTypes: + active: false + DoubleMutabilityForCollection: + active: true + aliases: ['DoubleMutability'] + mutableTypes: + - 'kotlin.collections.MutableList' + - 'kotlin.collections.MutableMap' + - 'kotlin.collections.MutableSet' + - 'java.util.ArrayList' + - 'java.util.LinkedHashSet' + - 'java.util.HashSet' + - 'java.util.LinkedHashMap' + - 'java.util.HashMap' + ElseCaseInsteadOfExhaustiveWhen: + active: false + ignoredSubjectTypes: [] + EqualsAlwaysReturnsTrueOrFalse: + active: true + EqualsWithHashCodeExist: + active: true + ExitOutsideMain: + active: false + ExplicitGarbageCollectionCall: + active: true + HasPlatformType: + active: true + IgnoredReturnValue: + active: true + restrictToConfig: true + returnValueAnnotations: + - 'CheckResult' + - '*.CheckResult' + - 'CheckReturnValue' + - '*.CheckReturnValue' + ignoreReturnValueAnnotations: + - 'CanIgnoreReturnValue' + - '*.CanIgnoreReturnValue' + returnValueTypes: + - 'kotlin.Function*' + - 'kotlin.sequences.Sequence' + - 'kotlinx.coroutines.flow.*Flow' + - 'java.util.stream.*Stream' + ignoreFunctionCall: [] + ImplicitDefaultLocale: + active: true + ImplicitUnitReturnType: + active: false + ignoreAnnotated: ['Test'] + allowExplicitReturnType: true + InvalidRange: + active: true + IteratorHasNextCallsNextMethod: + active: true + IteratorNotThrowingNoSuchElementException: + active: true + LateinitUsage: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + ignoreOnClassesPattern: '' + MapGetWithNotNullAssertionOperator: + active: true + MissingPackageDeclaration: + active: true + excludes: ['**/*.kts'] + MissingSuperCall: + active: false + mustInvokeSuperAnnotations: + - 'androidx.annotation.CallSuper' + - 'javax.annotation.OverridingMethodsMustInvokeSuper' + MissingUseCall: + active: false + ignoreClass: + - 'java.io.ByteArrayInputStream' + - 'java.io.ByteArrayOutputStream' + NullCheckOnMutableProperty: + active: false + NullableToStringCall: + active: false + PropertyUsedBeforeDeclaration: + active: false + UnconditionalJumpStatementInLoop: + active: false + UnnamedParameterUse: + active: false + allowAdjacentDifferentTypeParams: true + allowSingleParamUse: true + ignoreArgumentsMatchingNames: true + ignoreFunctionCall: [] + UnnecessaryNotNullCheck: + active: false + UnnecessaryNotNullOperator: + active: true + UnnecessarySafeCall: + active: true + UnreachableCatchBlock: + active: true + UnreachableCode: + active: true + UnsafeCallOnNullableType: + active: true + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**'] + UnsafeCast: + active: true + aliases: ['UNCHECKED_CAST'] + UnusedUnaryOperator: + active: true + UselessPostfixExpression: + active: true + WrongEqualsTypeParameter: + active: true + +style: + active: true + AbstractClassCanBeConcreteClass: + active: true + AbstractClassCanBeInterface: + active: true + AlsoCouldBeApply: + active: false + BracesOnIfStatements: + active: false + singleLine: 'never' + multiLine: 'always' + BracesOnWhenStatements: + active: false + singleLine: 'necessary' + multiLine: 'consistent' + CanBeNonNullable: + active: false + CascadingCallWrapping: + active: false + includeElvis: true + ClassOrdering: + active: false + CollapsibleIfStatements: + active: false + DataClassContainsFunctions: + active: false + conversionFunctionPrefix: + - 'to' + allowOperators: false + DataClassShouldBeImmutable: + active: false + DestructuringDeclarationWithTooManyEntries: + active: true + maxDestructuringEntries: 3 + DoubleNegativeExpression: + active: false + DoubleNegativeLambda: + active: false + negativeFunctions: + - reason: 'Use `takeIf` instead.' + value: 'takeUnless' + - reason: 'Use `all` instead.' + value: 'none' + negativeFunctionNameParts: + - 'not' + - 'non' + EqualsNullCall: + active: true + EqualsOnSignatureLine: + active: false + ExplicitCollectionElementAccessMethod: + active: false + ExplicitItLambdaMultipleParameters: + active: true + ExplicitItLambdaParameter: + active: false + ExpressionBodySyntax: + active: false + includeLineWrapping: false + ForbiddenAnnotation: + active: true + annotations: + - reason: 'it is a java annotation. Use `Suppress` instead.' + value: 'java.lang.SuppressWarnings' + - reason: 'it is a java annotation. Use `kotlin.Deprecated` instead.' + value: 'java.lang.Deprecated' + - reason: 'it is a java annotation. Use `kotlin.annotation.MustBeDocumented` instead.' + value: 'java.lang.annotation.Documented' + - reason: 'it is a java annotation. Use `kotlin.annotation.Target` instead.' + value: 'java.lang.annotation.Target' + - reason: 'it is a java annotation. Use `kotlin.annotation.Retention` instead.' + value: 'java.lang.annotation.Retention' + - reason: 'it is a java annotation. Use `kotlin.annotation.Repeatable` instead.' + value: 'java.lang.annotation.Repeatable' + ForbiddenComment: + active: true + comments: + - reason: 'Forbidden FIXME todo marker in comment, please fix the problem.' + value: 'FIXME:' + - reason: 'Forbidden STOPSHIP todo marker in comment, please address the problem before shipping the code.' + value: 'STOPSHIP:' + - reason: 'Forbidden TODO todo marker in comment, please do the changes.' + value: 'TODO:' + allowedPatterns: '' + ForbiddenImport: + active: false + forbiddenImports: [] + allowedImports: [] + ForbiddenMethodCall: + active: true + methods: + - reason: 'print does not allow you to configure the output stream. Use a logger instead.' + value: 'kotlin.io.print' + - reason: 'println does not allow you to configure the output stream. Use a logger instead.' + value: 'kotlin.io.println' + - reason: 'using `BigDecimal(Double)` can result in unexpected floating point precision behavior. Use `BigDecimal.valueOf(Double)` or `String.toBigDecimalOrNull()` instead.' + value: 'java.math.BigDecimal.(kotlin.Double)' + - reason: 'using `BigDecimal(String)` can result in a `NumberFormatException`. Use `String.toBigDecimalOrNull()`' + value: 'java.math.BigDecimal.(kotlin.String)' + - reason: 'It is marked as obsolete. Use `kotlin.time.measureTime` instead.' + value: 'kotlin.system.measureTimeMillis' + ForbiddenNamedParam: + active: false + methods: [] + ForbiddenOptIn: + active: false + markerClasses: [] + ForbiddenSuppress: + active: false + rules: [] + ForbiddenVoid: + active: true + ignoreOverridden: false + ignoreUsageInGenerics: false + FunctionOnlyReturningConstant: + active: true + ignoreOverridableFunction: true + ignoreActualFunction: true + excludedFunctions: [] + LoopWithTooManyJumpStatements: + active: true + maxJumpCount: 1 + MagicNumber: + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/*.kts'] + ignoreNumbers: + - '-1' + - '0' + - '1' + - '2' + ignoreHashCodeFunction: true + ignorePropertyDeclaration: true + ignoreLocalVariableDeclaration: true + ignoreConstantDeclaration: true + ignoreCompanionObjectPropertyDeclaration: true + ignoreAnnotation: false + ignoreNamedArgument: true + ignoreEnums: false + ignoreRanges: false + ignoreExtensionFunctions: true + MandatoryBracesLoops: + active: false + MaxChainedCallsOnSameLine: + active: false + maxChainedCalls: 5 + MaxLineLength: + active: false + maxLineLength: 120 + excludePackageStatements: true + excludeImportStatements: true + excludeCommentStatements: false + excludeRawStrings: true + MayBeConstant: + active: true + ModifierOrder: + active: true + MultilineLambdaItParameter: + active: false + MultilineRawStringIndentation: + active: false + indentSize: 4 + trimmingMethods: + - 'trimIndent' + - 'trimMargin' + NestedClassesVisibility: + active: true + NewLineAtEndOfFile: + active: true + NoTabs: + active: false + NullableBooleanCheck: + active: false + ObjectLiteralToLambda: + active: true + OptionalAbstractKeyword: + active: true + OptionalUnit: + active: false + ProtectedMemberInFinalClass: + active: true + RangeUntilInsteadOfRangeTo: + active: true + RedundantConstructorKeyword: + active: false + RedundantExplicitType: + active: false + RedundantHigherOrderMapUsage: + active: true + RedundantVisibilityModifier: + active: false + ReturnCount: + active: false + max: 2 + excludedFunctions: + - 'equals' + excludeLabeled: false + excludeReturnFromLambda: true + excludeGuardClauses: false + SafeCast: + active: true + SerialVersionUIDInSerializableClass: + active: true + SpacingAfterPackageAndImports: + active: false + StringShouldBeRawString: + active: false + maxEscapedCharacterCount: 2 + ignoredCharacters: [] + ThrowsCount: + active: false + max: 2 + excludeGuardClauses: false + TrailingWhitespace: + active: false + TrimMultilineRawString: + active: false + trimmingMethods: + - 'trimIndent' + - 'trimMargin' + UnderscoresInNumericLiterals: + active: false + acceptableLength: 4 + allowNonStandardGrouping: false + UnnecessaryAny: + active: false + UnnecessaryApply: + active: true + UnnecessaryBackticks: + active: false + UnnecessaryBracesAroundTrailingLambda: + active: false + UnnecessaryFilter: + active: true + UnnecessaryFullyQualifiedName: + active: true + UnnecessaryInheritance: + active: true + UnnecessaryInnerClass: + active: false + UnnecessaryLet: + active: false + UnnecessaryParentheses: + active: false + allowForUnclearPrecedence: false + UnnecessaryReversed: + active: false + UnusedImport: + active: false + additionalOperatorSet: [] + UnusedParameter: + active: true + aliases: ['UNUSED_PARAMETER', 'unused'] + allowedNames: 'ignored|expected' + UnusedPrivateClass: + active: true + aliases: ['unused'] + UnusedPrivateFunction: + active: true + aliases: ['unused'] + allowedNames: '' + UnusedPrivateProperty: + active: true + aliases: ['unused'] + allowedNames: 'ignored|expected|serialVersionUID' + UnusedVariable: + active: true + aliases: ['UNUSED_VARIABLE', 'unused'] + allowedNames: 'ignored|_' + UseAnyOrNoneInsteadOfFind: + active: true + UseArrayLiteralsInAnnotations: + active: true + UseCheckNotNull: + active: true + UseCheckOrError: + active: true + UseDataClass: + active: false + allowVars: false + UseEmptyCounterpart: + active: false + UseIfEmptyOrIfBlank: + active: false + UseIfInsteadOfWhen: + active: false + ignoreWhenContainingVariableDeclaration: false + UseIsNullOrEmpty: + active: true + UseLet: + active: false + UseOrEmpty: + active: true + UseRequire: + active: true + UseRequireNotNull: + active: true + UseSumOfInsteadOfFlatMapSize: + active: false + UselessCallOnNotNull: + active: true + UtilityClassWithPublicConstructor: + active: true + VarCouldBeVal: + active: true + aliases: ['CanBeVal'] + ignoreLateinitVar: false + WildcardImport: + active: false + excludeImports: + - 'java.util.*' diff --git a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTools.kt b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTools.kt index dc126f6..e288272 100644 --- a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTools.kt +++ b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTools.kt @@ -45,7 +45,6 @@ internal const val WAV_BASE64 = "UklGRiYAAABXQVZFZm10IBAAAAABAAEAQB8AAAB9AAACABA private val logger = KotlinLogging.logger {} -@Suppress("LongMethod") fun Server.registerConformanceTools() { // 1. Simple text addTool( diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ca67d7e..360a468 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ # plugins version atomicfu = "0.32.1" binaryCompatibilityValidatorPlugin = "0.18.1" -detekt = "2.0.0-alpha.2" +detekt = "2.0.0-alpha.3" dokka = "2.2.0" kotlin = "2.3.21" kover = "0.9.8" @@ -28,6 +28,7 @@ junit = "5.14.3" [libraries] # Plugins +detekt-gradle = { module = "dev.detekt:detekt-gradle-plugin", version.ref = "detekt" } dokka-gradle = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" } kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } kotlin-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" } @@ -75,7 +76,6 @@ ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio", version.ref = " ktor-server-cio = { group = "io.ktor", name = "ktor-server-cio", version.ref = "ktor" } [plugins] -detekt = { id = "dev.detekt", version.ref = "detekt" } kotlinx-binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binaryCompatibilityValidatorPlugin" } kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" } diff --git a/integration-test/build.gradle.kts b/integration-test/build.gradle.kts index 30afe9e..7a3a4a3 100644 --- a/integration-test/build.gradle.kts +++ b/integration-test/build.gradle.kts @@ -1,5 +1,6 @@ plugins { id("mcp.multiplatform") + id("mcp.detekt") } kotlin { diff --git a/integration-test/detekt-baseline-test.xml b/integration-test/detekt-baseline-test.xml deleted file mode 100644 index 2df611a..0000000 --- a/integration-test/detekt-baseline-test.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - AbstractClassCanBeConcreteClass:AbstractPromptIntegrationTest.kt:AbstractPromptIntegrationTest$AbstractPromptIntegrationTest - AbstractClassCanBeConcreteClass:AbstractResourceIntegrationTest.kt:AbstractResourceIntegrationTest$AbstractResourceIntegrationTest - AbstractClassCanBeConcreteClass:AbstractToolIntegrationTest.kt:AbstractToolIntegrationTest$AbstractToolIntegrationTest - AbstractClassCanBeConcreteClass:BaseTransportTest.kt:BaseTransportTest$BaseTransportTest - CyclomaticComplexMethod:AbstractToolIntegrationTest.kt:AbstractToolIntegrationTest$private fun setupCalculatorTool - ForbiddenComment:StdioClientTransportTest.kt:StdioClientTransportTest$// TODO: fix running on windows - MatchingDeclarationName:PromptIntegrationTestSse.kt:SchemaPromptIntegrationTestSse : AbstractPromptIntegrationTest - ThrowsCount:AbstractPromptIntegrationTest.kt:AbstractPromptIntegrationTest$override fun configureServer - UseCheckOrError:ClientTest.kt:ClientTest.<no name provided>$throw IllegalStateException("Test error") - UseOrEmpty:AbstractToolIntegrationTest.kt:AbstractToolIntegrationTest$(request.params.arguments?.get("tags") as? JsonArray)?.mapNotNull { (it as? JsonPrimitive)?.content } ?: emptyList() - UseRequire:AbstractPromptIntegrationTest.kt:AbstractPromptIntegrationTest$throw IllegalArgumentException("Missing required argument: $argName") - - diff --git a/kotlin-sdk-client/build.gradle.kts b/kotlin-sdk-client/build.gradle.kts index a3645b7..ae7a314 100644 --- a/kotlin-sdk-client/build.gradle.kts +++ b/kotlin-sdk-client/build.gradle.kts @@ -4,6 +4,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl plugins { id("mcp.multiplatform") + id("mcp.detekt") id("mcp.publishing") id("mcp.dokka") alias(libs.plugins.kotlinx.binary.compatibility.validator) diff --git a/kotlin-sdk-client/detekt-baseline-commonMainSourceSet.xml b/kotlin-sdk-client/detekt-baseline-commonMainSourceSet.xml deleted file mode 100644 index 5cddbe1..0000000 --- a/kotlin-sdk-client/detekt-baseline-commonMainSourceSet.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - CyclomaticComplexMethod:StdioClientTransport.kt:StdioClientTransport$override suspend fun initialize - LongMethod:StdioClientTransport.kt:StdioClientTransport$override suspend fun initialize - MaxLineLength:StdioClientTransport.kt:StdioClientTransport$* - ThrowsCount:SseClientTransport.kt:SseClientTransport$private suspend fun CoroutineScope.collectMessages - ThrowsCount:StreamableHttpClientTransport.kt:StreamableHttpClientTransport$@Suppress("ReturnCount", "CyclomaticComplexMethod") override suspend fun performSend - TooGenericExceptionCaught:Client.kt:Client$error: Throwable - TooGenericExceptionCaught:SseClientTransport.kt:SseClientTransport$e: Throwable - TooGenericExceptionCaught:StdioClientTransport.kt:StdioClientTransport$e: Throwable - - diff --git a/kotlin-sdk-client/detekt-baseline-commonTestSourceSet.xml b/kotlin-sdk-client/detekt-baseline-commonTestSourceSet.xml deleted file mode 100644 index cab4322..0000000 --- a/kotlin-sdk-client/detekt-baseline-commonTestSourceSet.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - MaxLineLength:StreamableHttpClientTransportTest.kt:StreamableHttpClientTransportTest$"""data: {"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"upload-123","progress":50,"total":100}}""" - MaxLineLength:StreamableHttpClientTransportTest.kt:StreamableHttpClientTransportTest$"""data: {"jsonrpc":"2.0","method":"notifications/resumed","params":{"fromToken":"$lastEventIdSent"}}""" - - diff --git a/kotlin-sdk-client/detekt-baseline-main.xml b/kotlin-sdk-client/detekt-baseline-main.xml index 8411ab0..c34e75c 100644 --- a/kotlin-sdk-client/detekt-baseline-main.xml +++ b/kotlin-sdk-client/detekt-baseline-main.xml @@ -2,18 +2,6 @@ - CyclomaticComplexMethod:StdioClientTransport.kt:StdioClientTransport$override suspend fun initialize - InjectDispatcher:StdioClientTransport.kt:StdioClientTransport$Default - InjectDispatcher:StreamableHttpClientTransport.kt:StreamableHttpClientTransport$Default - LongMethod:StdioClientTransport.kt:StdioClientTransport$override suspend fun initialize - MaxLineLength:StdioClientTransport.kt:StdioClientTransport$* - NoNameShadowing:StdioClientTransport.kt:StdioClientTransport$source - ThrowsCount:SseClientTransport.kt:SseClientTransport$private suspend fun CoroutineScope.collectMessages - ThrowsCount:StreamableHttpClientTransport.kt:StreamableHttpClientTransport$@Suppress("ReturnCount", "CyclomaticComplexMethod") override suspend fun performSend - TooGenericExceptionCaught:Client.kt:Client$error: Throwable - TooGenericExceptionCaught:SseClientTransport.kt:SseClientTransport$e: Throwable - TooGenericExceptionCaught:StdioClientTransport.kt:StdioClientTransport$e: Throwable - UseCheckOrError:Client.kt:Client$throw IllegalStateException( "Server does not support resource subscriptions (required for $method)", ) - UseCheckOrError:Client.kt:Client$throw IllegalStateException("Client does not support roots capability.") + LoopWithTooManyJumpStatements:StreamableHttpClientTransport.kt:StreamableHttpClientTransport$while diff --git a/kotlin-sdk-client/detekt-baseline-test.xml b/kotlin-sdk-client/detekt-baseline-test.xml deleted file mode 100644 index dd6adfa..0000000 --- a/kotlin-sdk-client/detekt-baseline-test.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - AbstractClassCanBeConcreteClass:AbstractStreamableHttpClientTest.kt:AbstractStreamableHttpClientTest$AbstractStreamableHttpClientTest - ForbiddenComment:StreamableHttpClientTest.kt:StreamableHttpClientTest$// TODO: how to get notifications via Client API? - LongParameterList:MockMcp.kt:MockMcp$fun handleJSONRPCRequest - LongParameterList:MockMcp.kt:MockMcp$fun handleWithResult - LongParameterList:MockMcp.kt:MockMcp$fun onInitialize - MaxLineLength:StreamableHttpClientTransportTest.kt:StreamableHttpClientTransportTest$"""data: {"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"upload-123","progress":50,"total":100}}""" - MaxLineLength:StreamableHttpClientTransportTest.kt:StreamableHttpClientTransportTest$"""data: {"jsonrpc":"2.0","method":"notifications/resumed","params":{"fromToken":"$lastEventIdSent"}}""" - UseOrEmpty:ClientMetaParameterTest.kt:ClientMetaParameterTest$exception.message ?: "" - - diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt index 49b20d7..d71c091 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt @@ -83,7 +83,7 @@ private val logger = KotlinLogging.logger {} * Options for configuring the MCP client. * * @property capabilities The capabilities this client supports. - * @property enforceStrictCapabilities Whether to strictly enforce capabilities when interacting with the server. + * @param enforceStrictCapabilities Whether to strictly enforce capabilities when interacting with the server. */ public class ClientOptions( public val capabilities: ClientCapabilities = ClientCapabilities(), @@ -125,7 +125,6 @@ public suspend fun mcpClient( * @param clientInfo Information about the client implementation (name, version). * @param options Configuration options for this client. */ -@Suppress("TooManyFunctions") public open class Client(private val clientInfo: Implementation, options: ClientOptions = ClientOptions()) : Protocol(options) { @@ -242,7 +241,7 @@ public open class Client(private val clientInfo: Implementation, options: Client Method.Defined.CompletionComplete, -> { checkNotNull(serverCapabilities?.prompts) { - throw IllegalStateException("Server does not support prompts (required for $method)") + "Server does not support prompts (required for $method)" } } @@ -255,10 +254,10 @@ public open class Client(private val clientInfo: Implementation, options: Client val resCaps = serverCapabilities?.resources ?: error("Server does not support resources (required for $method)") - if (method == Method.Defined.ResourcesSubscribe && resCaps.subscribe != true) { - throw IllegalStateException( - "Server does not support resource subscriptions (required for $method)", - ) + if (method == Method.Defined.ResourcesSubscribe) { + check(resCaps.subscribe == true) { + "Server does not support resource subscriptions (required for $method)" + } } } @@ -556,9 +555,8 @@ public open class Client(private val clientInfo: Implementation, options: Client * @throws IllegalStateException If the client does not support roots. */ public fun removeRoot(uri: String): Boolean { - if (capabilities.roots == null) { - logger.error { "Failed to remove root '$uri': Client does not support roots capability" } - throw IllegalStateException("Client does not support roots capability.") + checkNotNull(capabilities.roots) { + "Client does not support roots capability." } logger.info { "Removing root: $uri" } val oldMap = roots.getAndUpdate { current -> current.remove(uri) } @@ -636,7 +634,6 @@ public open class Client(private val clientInfo: Implementation, options: Client // --- Internal Handlers --- - @Suppress("ReturnCount") private fun applyElicitationDefaults(request: ElicitRequest, result: ElicitResult): ElicitResult { if (result.action != ElicitResult.Action.Accept) return result val formParams = request.params as? ElicitRequestFormParams ?: return result diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt index a61ea4b..63f681c 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt @@ -227,7 +227,6 @@ public class StdioClientTransport @JvmOverloads public constructor( } override suspend fun performSend(message: JSONRPCMessage, options: TransportSendOptions?) { - @Suppress("SwallowedException") try { sendChannel.send(message) } catch (e: CancellationException) { @@ -290,7 +289,7 @@ public class StdioClientTransport @JvmOverloads public constructor( ) { val buffer = Buffer() try { - source.use { source -> + source.use { while (isActive) { val bytesRead = source.readAtMostTo(buffer, BUFFER_SIZE) if (bytesRead == -1L) { diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt index 999e25a..c27ed79 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt @@ -54,6 +54,7 @@ private const val MCP_RESUMPTION_TOKEN_HEADER = "Last-Event-ID" * Represents an error from the Streamable HTTP transport. * * @property code HTTP status code associated with the error, or `null` if unavailable + * @param message detailed error description appended to the exception message */ public class StreamableHttpError(public val code: Int? = null, message: String? = null) : Exception("Streamable HTTP error: $message") @@ -65,14 +66,17 @@ private sealed interface ConnectResult { } /** - * Client transport for Streamable HTTP: this implements the MCP Streamable HTTP transport specification. - * It will connect to a server using HTTP POST for sending messages and HTTP GET with Server-Sent Events - * for receiving messages. + * Client transport implementing the MCP Streamable HTTP transport specification. * - * @property sessionId session identifier assigned by the server after initialization, or `null` before connection - * @property protocolVersion MCP protocol version negotiated with the server, or `null` before connection + * Sends messages via HTTP POST and receives messages via HTTP GET with Server-Sent Events. + * Supports automatic SSE reconnection with exponential backoff, stream resumption via the + * `Last-Event-ID` header, and explicit session termination. + * + * @param client Ktor HTTP client used for all requests + * @param url MCP endpoint URL + * @param reconnectionOptions reconnection backoff and retry-limit settings for the SSE stream + * @param requestBuilder builder applied to every outgoing HTTP request, e.g. for adding auth headers */ -@Suppress("TooManyFunctions") public class StreamableHttpClientTransport( private val client: HttpClient, private val url: String, @@ -98,8 +102,11 @@ public class StreamableHttpClientTransport( override val logger: KLogger = KotlinLogging.logger {} + /** Session identifier assigned by the server after initialization, or `null` before connection. */ public var sessionId: String? = null private set + + /** MCP protocol version negotiated with the server, or `null` before connection. */ public var protocolVersion: String? = null private var sseJob: Job? = null @@ -121,7 +128,6 @@ public class StreamableHttpClientTransport( /** * Sends a single message with optional resumption support */ - @Suppress("ReturnCount", "CyclomaticComplexMethod", "LongMethod", "TooGenericExceptionCaught", "ThrowsCount") override suspend fun performSend(message: JSONRPCMessage, options: TransportSendOptions?) { logger.debug { "Client sending message via POST to $url: ${McpJson.encodeToString(message)}" } @@ -260,7 +266,6 @@ public class StreamableHttpClientTransport( var attempt = 0 var needsDelay = initialServerRetryDelay != null - @Suppress("LoopWithTooManyJumpStatements") while (isActive) { // Delay before (re)connection: skip only for first fresh SSE connection if (needsDelay) { @@ -296,7 +301,6 @@ public class StreamableHttpClientTransport( } } - @Suppress("TooGenericExceptionCaught") private suspend fun connectSse(lastEventId: String?): ConnectResult { logger.debug { "Client attempting to start SSE session at url: $url" } return try { @@ -363,7 +367,6 @@ public class StreamableHttpClientTransport( } } - @Suppress("TooGenericExceptionCaught") private suspend fun collectSse( session: ClientSSESession, replayMessageId: RequestId?, @@ -408,7 +411,6 @@ public class StreamableHttpClientTransport( return SseStreamResult(hasPrimingEvent, receivedResponse, localLastEventId, localServerRetryDelay) } - @Suppress("CyclomaticComplexMethod") private suspend fun handleInlineSse( response: HttpResponse, replayMessageId: RequestId?, @@ -455,7 +457,6 @@ public class StreamableHttpClientTransport( } } - @Suppress("LoopWithTooManyJumpStatements") while (!channel.isClosedForRead) { val line = channel.readUTF8Line() ?: break if (line.isEmpty()) { diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt index 67b2f07..8810552 100644 --- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt @@ -834,7 +834,6 @@ class StreamableHttpClientTransportTest { transport.close() } - @Suppress("DEPRECATION") @Test fun testDeprecatedConstructorStillWorks() = runTest { val mockEngine = MockEngine { _ -> diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt index fb71904..de9d2b3 100644 --- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt +++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt @@ -39,7 +39,6 @@ internal class MockMcp(verbose: Boolean = false) : AutoCloseable { val url = "${mokksy.baseUrl()}/mcp" - @Suppress("LongParameterList") fun onInitialize( clientName: String? = null, sessionId: String, @@ -117,7 +116,6 @@ internal class MockMcp(verbose: Boolean = false) : AutoCloseable { } } - @Suppress("LongParameterList") fun handleWithResult( httpMethod: HttpMethod = HttpMethod.Post, jsonRpcMethod: String, @@ -153,7 +151,6 @@ internal class MockMcp(verbose: Boolean = false) : AutoCloseable { } } - @Suppress("LongParameterList") fun handleWithResult( httpMethod: HttpMethod = HttpMethod.Post, jsonRpcMethod: String, @@ -178,7 +175,6 @@ internal class MockMcp(verbose: Boolean = false) : AutoCloseable { ) } - @Suppress("LongParameterList") fun handleJSONRPCRequest( httpMethod: HttpMethod = HttpMethod.Post, jsonRpcMethod: String, diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt index a1fe89e..43be8c9 100644 --- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt +++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt @@ -118,16 +118,16 @@ internal class StreamableHttpClientTest : AbstractStreamableHttpClientTest() { ServerSentEvent( event = "message", id = "1", - data = @Suppress("MaxLineLength") //language=json + data = """{"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"upload-123","progress":50,"total":100}}""", ), ) delay(200.milliseconds) emit( ServerSentEvent( - data = @Suppress("MaxLineLength") //language=json + data = """{"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"upload-123","progress":50,"total":100}}""", ), ) diff --git a/kotlin-sdk-core/build.gradle.kts b/kotlin-sdk-core/build.gradle.kts index 967dae3..1d19f5d 100644 --- a/kotlin-sdk-core/build.gradle.kts +++ b/kotlin-sdk-core/build.gradle.kts @@ -4,6 +4,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl plugins { id("mcp.multiplatform") + id("mcp.detekt") id("mcp.publishing") id("mcp.dokka") alias(libs.plugins.kotlinx.binary.compatibility.validator) diff --git a/kotlin-sdk-core/detekt-baseline-commonMainSourceSet.xml b/kotlin-sdk-core/detekt-baseline-commonMainSourceSet.xml deleted file mode 100644 index d774a5b..0000000 --- a/kotlin-sdk-core/detekt-baseline-commonMainSourceSet.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - CyclomaticComplexMethod:jsonUtils.kt:@OptIn(ExperimentalUnsignedTypes::class, ExperimentalSerializationApi::class) private fun convertToJsonElement: JsonElement - LongMethod:Protocol.kt:Protocol$public suspend fun <T : RequestResult> request: T - MagicNumber:completion.kt:CompleteResult.Completion$100 - MatchingDeclarationName:capabilities.dsl.kt:ClientCapabilitiesBuilder - MatchingDeclarationName:methods.kt:Method - MaxLineLength:AbstractClientTransport.kt:AbstractClientTransport$* - MaxLineLength:ClientTransportState.kt:ClientTransportState.Initializing$* - MaxLineLength:Protocol.kt:Protocol$"Received progress notification: token=${notification.params.progressToken}, progress=${notification.params.progress}/${notification.params.total}" - MaxLineLength:Protocol.kt:Protocol$* - MaxLineLength:Protocol.kt:RequestOptions$* - MaxLineLength:Transport.kt:Transport$* - MaxLineLength:TransportSendOptions.kt:TransportSendOptions$* - ReturnCount:ReadBuffer.kt:ReadBuffer$public fun readMessage: JSONRPCMessage? - ThrowsCount:AbstractClientTransport.kt:AbstractClientTransport$public override suspend fun send - TooGenericExceptionCaught:AbstractClientTransport.kt:AbstractClientTransport$e: Exception - TooGenericExceptionCaught:Protocol.kt:Protocol$cause: Throwable - TooGenericExceptionCaught:Protocol.kt:Protocol$error: Throwable - TooGenericExceptionCaught:Protocol.kt:Protocol$sendError: Throwable - TooGenericExceptionCaught:ReadBuffer.kt:ReadBuffer$e: Exception - TooGenericExceptionCaught:WebSocketMcpTransport.kt:WebSocketMcpTransport$e: Exception - TooManyFunctions:Protocol.kt:Protocol - - diff --git a/kotlin-sdk-core/detekt-baseline-main.xml b/kotlin-sdk-core/detekt-baseline-main.xml index a22fbcd..d483d7e 100644 --- a/kotlin-sdk-core/detekt-baseline-main.xml +++ b/kotlin-sdk-core/detekt-baseline-main.xml @@ -2,31 +2,6 @@ - CyclomaticComplexMethod:jsonUtils.kt:@OptIn(ExperimentalUnsignedTypes::class, ExperimentalSerializationApi::class) private fun convertToJsonElement: JsonElement - InjectDispatcher:utils.jvm.kt:IO - InstanceOfCheckForException:AbstractClientTransport.kt:AbstractClientTransport$e is McpException - LongMethod:Protocol.kt:Protocol$public suspend fun <T : RequestResult> request: T - MagicNumber:completion.kt:CompleteResult.Completion$100 - MatchingDeclarationName:capabilities.dsl.kt:ClientCapabilitiesBuilder - MatchingDeclarationName:methods.kt:Method - MaxLineLength:AbstractClientTransport.kt:AbstractClientTransport$* - MaxLineLength:ClientTransportState.kt:ClientTransportState.Initializing$* - MaxLineLength:Protocol.kt:Protocol$"Received progress notification: token=${notification.params.progressToken}, progress=${notification.params.progress}/${notification.params.total}" - MaxLineLength:Protocol.kt:Protocol$* - MaxLineLength:Protocol.kt:RequestOptions$* - MaxLineLength:Transport.kt:Transport$* - MaxLineLength:TransportSendOptions.kt:TransportSendOptions$* - NoNameShadowing:Protocol.kt:Protocol$error - ReturnCount:ReadBuffer.kt:ReadBuffer$public fun readMessage: JSONRPCMessage? - ThrowsCount:AbstractClientTransport.kt:AbstractClientTransport$public override suspend fun send - TooGenericExceptionCaught:AbstractClientTransport.kt:AbstractClientTransport$e: Exception - TooGenericExceptionCaught:Protocol.kt:Protocol$cause: Throwable - TooGenericExceptionCaught:Protocol.kt:Protocol$error: Throwable - TooGenericExceptionCaught:Protocol.kt:Protocol$sendError: Throwable - TooGenericExceptionCaught:ReadBuffer.kt:ReadBuffer$e: Exception - TooGenericExceptionCaught:WebSocketMcpTransport.kt:WebSocketMcpTransport$e: Exception - TooManyFunctions:Protocol.kt:Protocol UnsafeCallOnNullableType:Protocol.kt:Protocol$response!! - UseCheckNotNull:Protocol.kt:Protocol$check(error != null) diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/AbstractClientTransport.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/AbstractClientTransport.kt index 5f67999..27013d3 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/AbstractClientTransport.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/AbstractClientTransport.kt @@ -99,8 +99,10 @@ public abstract class AbstractClientTransport : AbstractTransport() { * proceeding with further operations. It is often used to enforce state invariants within the transport's * lifecycle mechanics, enabling robust error handling and debugging in case of unexpected state transitions. * - * @param expected The expected transport state. An exception will be thrown if the actual state does not match this value. - * @throws IllegalStateException If the current state does not match the expected state. + * @param expected The expected transport state. + * An exception will be thrown if the actual state does not match this value. + * @param lazyMessage Function that builds the exception message from the actual state when the check fails. + * Defaults to a message that compares the expected state against the observed one. */ protected fun checkState( expected: ClientTransportState, diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport.kt index 7b88100..a738289 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport.kt @@ -6,11 +6,13 @@ import kotlin.concurrent.atomics.AtomicBoolean import kotlin.concurrent.atomics.ExperimentalAtomicApi /** - * Implements [onClose], [onError] and [onMessage] functions of [Transport] providing - * corresponding [_onClose], [_onError] and [_onMessage] properties to use for an implementation. + * Base class for [Transport] implementations that handles the bookkeeping for [onClose], [onError] + * and [onMessage] callback registration, exposing the composed callbacks to subclasses for invocation. + * + * The callbacks are composed: each call to [onClose], [onError], or [onMessage] chains the new block + * after any previously registered ones. */ @OptIn(ExperimentalAtomicApi::class) -@Suppress("PropertyName") public abstract class AbstractTransport : Transport { private val onCloseCalled = AtomicBoolean(false) private var _onClose: (() -> Unit) = {} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt index 75cebd3..8f86c03 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt @@ -53,22 +53,16 @@ public typealias ProgressCallback = (Progress) -> Unit /** * Additional initialization options. * + * @property enforceStrictCapabilities whether to restrict emitted requests to only those that the + * remote side has indicated it can handle through its advertised capabilities. + * This does NOT affect checking of _local_ side capabilities, as it is considered a logic error + * to mis-specify those. + * Currently defaults to `false` for backwards compatibility with SDK versions that did not advertise + * capabilities correctly; in the future, this will default to `true`. * @property timeout default timeout for outgoing requests */ public open class ProtocolOptions( - /** - * Whether to restrict emitted requests to only those that the remote side has indicated - * that they can handle, through their advertised capabilities. - * - * Note that this DOES NOT affect checking of _local_ side capabilities, as it is - * considered a logic error to mis-specify those. - * - * Currently, this defaults to false, for backwards compatibility with SDK versions - * that did not advertise capabilities correctly. - * In the future, this will default to true. - */ public var enforceStrictCapabilities: Boolean = false, - public var timeout: Duration = DEFAULT_REQUEST_TIMEOUT, ) @@ -80,18 +74,18 @@ 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. + * @param relatedRequestId if present, used to indicate to the transport which incoming request to + * associate this outgoing message with. + * @param resumptionToken the resumption token used to continue long-running requests that were interrupted. + * Allows clients to reconnect and continue from where they left off, if supported by the transport. + * @param onResumptionToken callback invoked when the resumption token changes, if supported by the transport. + * 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. + * If set, requests progress notifications from the remote end (if supported); + * when progress notifications are received, this callback is 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. + * If exceeded, a [McpException] with code `RequestTimeout` is raised from [Protocol.request]. + * If not specified, [DEFAULT_REQUEST_TIMEOUT] is used. */ public class RequestOptions( relatedRequestId: RequestId? = null, @@ -147,19 +141,18 @@ internal val COMPLETED = CompletableDeferred(Unit).also { it.complete(Unit) } * Implements MCP protocol framing on top of a pluggable transport, including * features like request/response linking, notifications, and progress. * - * @property transport the active transport, or `null` if not connected - * @property requestHandlers registered request handlers keyed by method name - * @property notificationHandlers registered notification handlers keyed by method name - * @property responseHandlers pending response handlers keyed by request ID - * @property progressHandlers registered progress callbacks keyed by progress token + * @property options protocol-level configuration; `null` falls back to defaults */ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptions?) { + /** The active transport, or `null` if not connected. */ public var transport: Transport? = null private set private val _requestHandlers: AtomicRef RequestResult?>> = atomic(persistentMapOf()) + + /** Registered request handlers keyed by method name. */ public val requestHandlers: Map< String, suspend ( @@ -171,17 +164,23 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio private val _notificationHandlers = atomic(persistentMapOf Unit>()) + + /** Registered notification handlers keyed by method name. */ public val notificationHandlers: Map Unit> get() = _notificationHandlers.value private val _responseHandlers: AtomicRef Unit>> = atomic(persistentMapOf()) + + /** Pending response handlers keyed by request ID. */ public val responseHandlers: Map Unit> get() = _responseHandlers.value private val _progressHandlers: AtomicRef> = atomic(persistentMapOf()) + + /** Registered progress callbacks keyed by progress token. */ public val progressHandlers: Map get() = _progressHandlers.value @@ -285,7 +284,6 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio } } - @Suppress("ThrowsCount") private suspend fun onRequest(request: JSONRPCRequest) { logger.trace { "Received request: ${request.method} (id: ${request.id})" } @@ -312,7 +310,6 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio return } - @Suppress("TooGenericExceptionCaught", "InstanceOfCheckForException") try { val result = handler(request, RequestHandlerExtra()) logger.trace { "Request handled successfully: ${request.method} (id: ${request.id})" } @@ -395,7 +392,7 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio if (response != null) { handler(response, null) } else { - check(error != null) + checkNotNull(error) val mcpException = McpException( code = error.error.code, message = error.error.message, @@ -479,8 +476,8 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio try { @Suppress("UNCHECKED_CAST") result.complete(response!!.result as T) - } catch (error: Throwable) { - result.completeExceptionally(error) + } catch (e: Throwable) { + result.completeExceptionally(e) } } } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBuffer.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBuffer.kt index d4c8b43..c0c853f 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBuffer.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBuffer.kt @@ -39,7 +39,6 @@ public class ReadBuffer { val line = readNextLine() ?: return null if (line.isBlank()) continue - @Suppress("TooGenericExceptionCaught") val message = try { deserializeMessage(line) } catch (e: Exception) { @@ -78,7 +77,7 @@ public class ReadBuffer { if (braceIndex == -1) return null val trimmed = line.substring(braceIndex) - @Suppress("TooGenericExceptionCaught") + return try { deserializeMessage(trimmed) } catch (ignored: Exception) { diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Transport.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Transport.kt index cb57547..a89c102 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Transport.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Transport.kt @@ -21,9 +21,9 @@ 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. + * @param message the JSON-RPC message to send, either a request or a response + * @param options optional transport-specific options that control sending behavior; + * different transport implementations may support different options */ public suspend fun send(message: JSONRPCMessage, options: TransportSendOptions? = null) diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt index 9fa1a0b..baf3723 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt @@ -45,7 +45,6 @@ public abstract class WebSocketMcpTransport : AbstractTransport() { */ protected abstract suspend fun initializeSession() - @Suppress("ThrowsCount") override suspend fun start() { logger.debug { "Starting websocket transport" } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/McpException.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/McpException.kt index 6b12492..56c851b 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/McpException.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/McpException.kt @@ -6,11 +6,11 @@ import kotlin.jvm.JvmOverloads /** * Represents an error specific to the MCP protocol. * - * @property code The MCP/JSON‑RPC error code. - * @property data Optional additional error payload as a JSON element; `null` when not provided. - * @param message The error message. Used verbatim as [Exception.message] — no error code prefix is prepended. - * Defaults to `"MCP error $code"` when not provided. - * @param cause The original cause. + * @property code the MCP/JSON-RPC error code + * @param message the error message; used verbatim as [Exception.message] without an error-code prefix. + * Defaults to `"MCP error $code"` when not provided. + * @property data optional additional error payload as a JSON element + * @param cause the original cause */ public class McpException @JvmOverloads public constructor( public val code: Int, diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PingRequest.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PingRequest.kt index c45ed30..8280404 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PingRequest.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PingRequest.kt @@ -7,7 +7,7 @@ import kotlinx.serialization.Serializable /** * Represents a ping request used to check if the connection is alive. * - * @property meta optional request metadata + * @property params optional request parameters carrying metadata */ @Serializable public data class PingRequest(override val params: BaseRequestParams? = null) : @@ -17,6 +17,7 @@ public data class PingRequest(override val params: BaseRequestParams? = null) : @EncodeDefault override val method: Method = Method.Defined.Ping + /** Convenience accessor for [params]'s metadata. */ public val meta: RequestMeta? get() = params?.meta } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt index ca08356..f65b1ed 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt @@ -28,11 +28,10 @@ public val SUPPORTED_PROTOCOL_VERSIONS: List = listOf( /** * Represents an entity that includes additional metadata in its responses. - * - * @property meta optional metadata attached to this entity */ @Serializable public sealed interface WithMeta { + /** Optional metadata attached to this entity. */ @SerialName("_meta") public val meta: JsonObject? } @@ -91,9 +90,6 @@ public data class Icon( ) { /** * The theme context for which an icon is designed. - * - * @property Light Icon designed for use with a light background (typically darker icon). - * @property Dark Icon designed for use with a dark background (typically lighter icon). */ @Serializable public enum class Theme { @@ -128,11 +124,10 @@ public enum class Role { * * References are used to point to other entities (prompts, resources, etc.) * without including their full definitions. - * - * @property type discriminator identifying the reference subtype */ @Serializable(with = ReferencePolymorphicSerializer::class) public sealed interface Reference { + /** Discriminator identifying the reference subtype. */ public val type: ReferenceType } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.kt index c782e3e..1eb5329 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.kt @@ -12,7 +12,6 @@ import kotlinx.serialization.json.JsonObject * A request from the client to the server to ask for completion options. * * @property params The request parameters containing the argument to complete and its context. - * @property meta optional request metadata */ @Serializable public data class CompleteRequest(override val params: CompleteRequestParams) : ClientRequest { @@ -38,6 +37,7 @@ public data class CompleteRequest(override val params: CompleteRequestParams) : public val context: Context? get() = params.context + /** Convenience accessor for [params]'s metadata. */ public val meta: RequestMeta? get() = params.meta } @@ -48,6 +48,8 @@ public data class CompleteRequest(override val params: CompleteRequestParams) : * @property argument The argument's information for which completion options are requested. * @property ref A reference to either a prompt or resource template to complete within. * @property context Additional, optional context for generating completions. + * @property meta Optional metadata for this request. May include a progressToken for + * out-of-band progress notifications. */ @Serializable public data class CompleteRequestParams( diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/content.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/content.kt index d30c22e..815e5f6 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/content.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/content.kt @@ -39,11 +39,10 @@ public enum class ContentTypes(public val value: String) { /** * Base interface for all content blocks in the protocol. - * - * @property type discriminator identifying the content block subtype */ @Serializable(with = ContentBlockPolymorphicSerializer::class) public sealed interface ContentBlock : WithMeta { + /** Discriminator identifying the content block subtype. */ public val type: ContentTypes } @@ -63,11 +62,10 @@ public sealed interface MediaContent : * * Implemented by [TextContent], [ImageContent], [AudioContent], [ToolUseContent], * and [ToolResultContent]. - * - * @property type discriminator identifying the content block subtype */ @Serializable(with = SamplingMessageContentPolymorphicSerializer::class) public sealed interface SamplingMessageContent : WithMeta { + /** discriminator identifying the content block subtype */ public val type: ContentTypes } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.kt index bb303f0..a0ee74c 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.kt @@ -46,11 +46,10 @@ public data class ElicitRequest(override val params: ElicitRequestParams) : Serv * Represents the parameters for an `elicitation/create` request. * * Implementations: [ElicitRequestFormParams], [ElicitRequestURLParams]. - * - * @property message The message to present to the user describing what information is being requested. */ @Serializable(with = ElicitRequestParamsSerializer::class) public sealed interface ElicitRequestParams : RequestParams { + /** The message to present to the user describing what information is being requested. */ public val message: String /** @@ -64,7 +63,6 @@ public sealed interface ElicitRequestParams : RequestParams { * Each property must be a primitive type (string, number, boolean). * @property required Optional list of property names that must be provided by the user. * If omitted, all fields are considered optional. - * @property type Always "object" for elicitation schemas. */ @Serializable public data class RequestedSchema( @@ -73,6 +71,7 @@ public sealed interface ElicitRequestParams : RequestParams { val properties: Map, val required: List? = null, ) { + /** Always "object" for elicitation schemas. */ @EncodeDefault val type: String = "object" } @@ -91,7 +90,7 @@ public sealed interface ElicitRequestParams : RequestParams { ReplaceWith("ElicitRequestFormParams(message, requestedSchema = requestedSchema, meta = meta)"), DeprecationLevel.WARNING, ) -@Suppress("FunctionNaming", "FunctionName") +@Suppress("FunctionName") public fun ElicitRequestParams( message: String, requestedSchema: ElicitRequestParams.RequestedSchema, @@ -113,7 +112,6 @@ public fun ElicitRequestParams( * later via `tasks/result`. * @property requestedSchema A restricted subset of JSON Schema. Only top-level properties * are allowed, without nesting. - * @property mode The elicitation mode discriminator, always `"form"`. * @property meta Optional metadata. May include a progressToken for out-of-band progress notifications. */ @Serializable @@ -124,6 +122,7 @@ public data class ElicitRequestFormParams( @SerialName("_meta") override val meta: RequestMeta? = null, ) : ElicitRequestParams { + /** The elicitation mode discriminator, always `"form"`. */ @EncodeDefault public val mode: String = "form" } @@ -141,7 +140,6 @@ public data class ElicitRequestFormParams( * @property task If specified, the caller is requesting task-augmented execution. The request * will return a [CreateTaskResult] immediately, and the actual result can be retrieved * later via `tasks/result`. - * @property mode The elicitation mode discriminator, always `"url"`. * @property meta Optional metadata. May include a progressToken for out-of-band progress notifications. */ @Serializable @@ -153,6 +151,7 @@ public data class ElicitRequestURLParams( @SerialName("_meta") override val meta: RequestMeta? = null, ) : ElicitRequestParams { + /** The elicitation mode discriminator, always `"url"`. */ @EncodeDefault public val mode: String = "url" } @@ -182,10 +181,6 @@ public data class ElicitResult( /** * The user's response action to an elicitation request. - * - * @property Accept User submitted the form/confirmed the action. Content will be provided. - * @property Decline User explicitly declined the action. No content provided. - * @property Cancel User dismissed the dialog without making an explicit choice. No content provided. */ @Serializable public enum class Action { diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt index 1a9ea2e..7eefdb5 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt @@ -104,11 +104,10 @@ internal fun JSONRPCNotification.fromJSON(): Notification = * Base interface for all JSON-RPC 2.0 messages. * * All messages in the MCP protocol follow the JSON-RPC 2.0 specification. - * - * @property jsonrpc the JSON-RPC protocol version, always `"2.0"` */ @Serializable(with = JSONRPCMessagePolymorphicSerializer::class) public sealed interface JSONRPCMessage { + /** The JSON-RPC protocol version, always `"2.0"`. */ public val jsonrpc: String } @@ -131,7 +130,6 @@ public data object JSONRPCEmptyMessage : JSONRPCMessage { * The server or client (depending on a direction) must respond with either a * [JSONRPCResponse] or [JSONRPCError] that has the same [id]. * - * @property jsonrpc Always "2.0" to indicate JSON-RPC 2.0 protocol. * @property id A unique identifier for this request. The response will include the same ID. * Can be a string or number. UUID string is used by default. * @property method The name of the method to invoke (e.g., "tools/list", "resources/read"). @@ -144,6 +142,7 @@ public data class JSONRPCRequest @JvmOverloads public constructor( val method: String, val params: JsonElement? = null, ) : JSONRPCMessage { + /** Always `"2.0"` to indicate JSON-RPC 2.0 protocol. */ @EncodeDefault override val jsonrpc: String = JSONRPC_VERSION @@ -186,13 +185,13 @@ public data class JSONRPCRequest @JvmOverloads public constructor( * * Examples: progress updates, resource change notifications, log messages. * - * @property jsonrpc Always "2.0" to indicate JSON-RPC 2.0 protocol. * @property method The name of the notification method (e.g., "notifications/progress", * "notifications/resources/updated"). * @property params Optional parameters for the notification. Structure depends on the specific method. */ @Serializable public data class JSONRPCNotification(val method: String, val params: JsonElement? = null) : JSONRPCMessage { + /** Always `"2.0"` to indicate JSON-RPC 2.0 protocol. */ @EncodeDefault override val jsonrpc: String = JSONRPC_VERSION } @@ -207,12 +206,12 @@ public data class JSONRPCNotification(val method: String, val params: JsonElemen * Sent in response to a [JSONRPCRequest] when the method execution succeeds. * The [id] must match the [id] of the original request. * - * @property jsonrpc Always "2.0" to indicate JSON-RPC 2.0 protocol. * @property id The identifier from the original request. Used to match responses to requests. * @property result The result of the method execution. Structure depends on the method that was called. */ @Serializable public data class JSONRPCResponse(val id: RequestId, val result: RequestResult = EmptyResult()) : JSONRPCMessage { + /** Always `"2.0"` to indicate JSON-RPC 2.0 protocol. */ @EncodeDefault override val jsonrpc: String = JSONRPC_VERSION } @@ -227,12 +226,12 @@ public data class JSONRPCResponse(val id: RequestId, val result: RequestResult = * Sent in response to a [JSONRPCRequest] when the method execution fails. * The [id] must match the [id] of the original request. * - * @property jsonrpc Always "2.0" to indicate JSON-RPC 2.0 protocol. * @property id The identifier from the original request. Used to match error responses to requests. * @property error Details about the error that occurred, including error code and message. */ @Serializable public data class JSONRPCError(val id: RequestId?, val error: RPCError) : JSONRPCMessage { + /** Always `"2.0"` to indicate JSON-RPC 2.0 protocol. */ @EncodeDefault override val jsonrpc: String = JSONRPC_VERSION } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/methods.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/methods.kt index dae2fc7..19f4da5 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/methods.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/methods.kt @@ -4,11 +4,10 @@ import kotlinx.serialization.Serializable /** * Represents a method in the protocol, which can be predefined or custom. - * - * @property value the string representation of this method name */ @Serializable(with = MethodSerializer::class) public sealed interface Method { + /** The string representation of this method name. */ public val value: String /** diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/notification.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/notification.kt index 24be040..3d33c21 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/notification.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/notification.kt @@ -11,13 +11,13 @@ import kotlinx.serialization.json.JsonObject /** * Represents a notification in the protocol. - * - * @property method the notification method identifier - * @property params optional notification parameters */ @Serializable(with = NotificationPolymorphicSerializer::class) public sealed interface Notification { + /** The notification method identifier. */ public val method: Method + + /** Optional notification parameters. */ public val params: NotificationParams? } @@ -34,9 +34,7 @@ public sealed interface ClientNotification : Notification public sealed interface ServerNotification : Notification /** - * Interface for notification parameter types. - * - * @property meta Optional metadata for the notification. + * Interface for notification parameter types. Inherits the optional `_meta` field from [WithMeta]. */ @Serializable public sealed interface NotificationParams : WithMeta @@ -76,13 +74,13 @@ public class Progress( * @property method The custom method name. By convention, custom methods often contain * organization-specific prefixes (e.g., "mycompany/custom_event"). * @property params Raw JSON parameters for the custom notification, if present. - * @property meta optional metadata for this notification */ @Serializable public data class CustomNotification(override val method: Method, override val params: BaseNotificationParams? = null) : ClientNotification, ServerNotification { + /** Convenience accessor for [params]'s metadata. */ public val meta: JsonObject? get() = params?.meta } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.kt index 9f52ec9..aacab9a 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.kt @@ -15,11 +15,11 @@ import kotlin.jvm.JvmInline * Metadata attached to a request's `_meta` field. * * @property json the raw JSON object containing the metadata - * @property progressToken optional progress token for tracking request progress */ @JvmInline @Serializable public value class RequestMeta(public val json: JsonObject) { + /** Optional progress token for tracking request progress. */ public val progressToken: ProgressToken? get() = json["progressToken"]?.let { element -> when { @@ -72,14 +72,14 @@ public data class BaseRequestParams(@SerialName("_meta") override val meta: Requ /** * Represents a request in the protocol. - * - * @property method the request method identifier - * @property params optional request parameters */ @OptIn(ExperimentalSerializationApi::class) @Serializable(with = RequestPolymorphicSerializer::class) public sealed interface Request { + /** The request method identifier. */ public val method: Method + + /** Optional request parameters. */ public val params: RequestParams? } @@ -142,7 +142,7 @@ public sealed interface ServerResult : RequestResult /** * An empty result for a request containing optional metadata. * - * @param meta Additional metadata for the response. Defaults to an empty JSON object. + * @property meta Additional metadata for the response. Defaults to an empty JSON object. */ @Serializable public data class EmptyResult(@SerialName("_meta") override val meta: JsonObject? = null) : @@ -151,11 +151,10 @@ public data class EmptyResult(@SerialName("_meta") override val meta: JsonObject /** * Represents a paginated result. - * - * @property nextCursor opaque token for retrieving the next page of results, or `null` if no more results */ @Serializable public sealed interface PaginatedResult : RequestResult { + /** Opaque token for retrieving the next page of results, or `null` if no more results. */ public val nextCursor: String? } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.kt index 86ce3ce..1afa0e6 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.kt @@ -117,15 +117,14 @@ public data class ResourceTemplateReference(val uri: String) : Reference { } /** - * The contents of a specific resource or sub-resource. - * - * @property uri The URI of this resource. - * @property mimeType The MIME type of this resource, if known. - * @property meta Optional metadata for this response. + * The contents of a specific resource or sub-resource. Carries the optional `_meta` field via [WithMeta]. */ @Serializable(with = ResourceContentsPolymorphicSerializer::class) public sealed interface ResourceContents : WithMeta { + /** The URI of this resource. */ public val uri: String + + /** The MIME type of this resource, if known. */ public val mimeType: String? } @@ -136,6 +135,7 @@ public sealed interface ResourceContents : WithMeta { * This must only be set if the item can actually be represented as text (not binary data). * @property uri The URI of this resource. * @property mimeType The MIME type of this resource, if known. + * @property meta Optional metadata for these contents. */ @Serializable public data class TextResourceContents( @@ -152,6 +152,7 @@ public data class TextResourceContents( * @property blob A base64-encoded string representing the binary data of the item. * @property uri The URI of this resource. * @property mimeType The MIME type of this resource, if known. + * @property meta Optional metadata for these contents. */ @Serializable public data class BlobResourceContents( @@ -167,6 +168,7 @@ public data class BlobResourceContents( * * @property uri The URI of this resource. * @property mimeType The MIME type of this resource, if known. + * @property meta Optional metadata for these contents. */ @Serializable public data class UnknownResourceContents( diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.kt index 194ea4f..7ee4f9e 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.kt @@ -53,7 +53,6 @@ public data class Root( * `listChanged = true` to receive these requests. * * @property params Optional request parameters containing metadata. - * @property meta optional request metadata */ @Serializable public data class ListRootsRequest(override val params: BaseRequestParams? = null) : ServerRequest { @@ -61,6 +60,7 @@ public data class ListRootsRequest(override val params: BaseRequestParams? = nul @EncodeDefault override val method: Method = Method.Defined.RootsList + /** Convenience accessor for [params]'s metadata. */ public val meta: RequestMeta? get() = params?.meta } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/schema.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/schema.kt index 479cc66..0982500 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/schema.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/schema.kt @@ -24,7 +24,6 @@ public sealed interface PrimitiveSchemaDefinition * @property minLength Minimum string length. * @property maxLength Maximum string length. * @property format Optional format constraint (e.g., email, URI, date). - * @property type JSON Schema type discriminator, always `"string"`. * @property default Optional default value. */ @Serializable @@ -36,6 +35,7 @@ public data class StringSchema( val format: StringSchemaFormat? = null, val default: String? = null, ) : PrimitiveSchemaDefinition { + /** JSON Schema type discriminator, always `"string"`. */ @EncodeDefault public val type: String = "string" } @@ -73,7 +73,6 @@ public sealed interface NumberSchemaDefinition : PrimitiveSchemaDefinition * @property description Optional description for the field. * @property minimum Minimum allowed value. * @property maximum Maximum allowed value. - * @property type JSON Schema type discriminator, always `"integer"`. * @property default Optional default value. */ @Serializable @@ -84,6 +83,7 @@ public data class IntegerSchema( val maximum: Int? = null, val default: Int? = null, ) : NumberSchemaDefinition { + /** JSON Schema type discriminator, always `"integer"`. */ @EncodeDefault val type: String = "integer" } @@ -95,7 +95,6 @@ public data class IntegerSchema( * @property description Optional description for the field. * @property minimum Minimum allowed value. * @property maximum Maximum allowed value. - * @property type JSON Schema type discriminator, always `"number"`. * @property default Optional default value. */ @Serializable @@ -106,6 +105,7 @@ public data class DoubleSchema( val maximum: Double? = null, val default: Double? = null, ) : NumberSchemaDefinition { + /** JSON Schema type discriminator, always `"number"`. */ @EncodeDefault val type: String = "number" } @@ -115,7 +115,6 @@ public data class DoubleSchema( * * @property title Optional display title for the field. * @property description Optional description for the field. - * @property type JSON Schema type discriminator, always `"boolean"`. * @property default Optional default value. */ @Serializable @@ -124,6 +123,7 @@ public data class BooleanSchema( val description: String? = null, val default: Boolean? = null, ) : PrimitiveSchemaDefinition { + /** JSON Schema type discriminator, always `"boolean"`. */ @EncodeDefault val type: String = "boolean" } @@ -161,7 +161,6 @@ public sealed interface SingleSelectEnumSchema : EnumSchemaDefinition * @property title Optional display title for the field. * @property description Optional description for the field. * @property enumValues Array of enum values to choose from. - * @property type JSON Schema type discriminator, always `"string"`. * @property default Optional default value. */ @Serializable @@ -172,6 +171,7 @@ public data class UntitledSingleSelectEnumSchema( val enumValues: List, val default: String? = null, ) : SingleSelectEnumSchema { + /** JSON Schema type discriminator, always `"string"`. */ @EncodeDefault val type: String = "string" } @@ -182,7 +182,6 @@ public data class UntitledSingleSelectEnumSchema( * @property title Optional display title for the field. * @property description Optional description for the field. * @property oneOf Array of enum options with values and display labels. - * @property type JSON Schema type discriminator, always `"string"`. * @property default Optional default value. */ @Serializable @@ -192,6 +191,7 @@ public data class TitledSingleSelectEnumSchema( val oneOf: List, val default: String? = null, ) : SingleSelectEnumSchema { + /** JSON Schema type discriminator, always `"string"`. */ @EncodeDefault val type: String = "string" } @@ -205,7 +205,6 @@ public data class TitledSingleSelectEnumSchema( * @property description Optional description for the field. * @property enumValues Array of enum values to choose from. * @property enumNames Display names for enum values. Non-standard according to JSON Schema 2020-12. - * @property type JSON Schema type discriminator, always `"string"`. * @property default Optional default value. */ @Deprecated("Use TitledSingleSelectEnumSchema instead") @@ -218,6 +217,7 @@ public data class LegacyTitledEnumSchema( val enumNames: List? = null, val default: String? = null, ) : EnumSchemaDefinition { + /** JSON Schema type discriminator, always `"string"`. */ @EncodeDefault val type: String = "string" } @@ -238,7 +238,6 @@ public sealed interface MultiSelectEnumSchema : EnumSchemaDefinition * @property minItems Minimum number of items to select. * @property maxItems Maximum number of items to select. * @property items Schema for the array items. - * @property type JSON Schema type discriminator, always `"array"`. * @property default Optional default value. */ @Serializable @@ -250,13 +249,13 @@ public data class UntitledMultiSelectEnumSchema( val items: Items, val default: List? = null, ) : MultiSelectEnumSchema { + /** JSON Schema type discriminator, always `"array"`. */ @EncodeDefault val type: String = "array" /** * Schema for the array items with plain enum values. * - * @property type JSON Schema type discriminator, always `"string"`. * @property enumValues Array of enum values to choose from. */ @Serializable @@ -264,6 +263,7 @@ public data class UntitledMultiSelectEnumSchema( @SerialName("enum") val enumValues: List, ) { + /** JSON Schema type discriminator, always `"string"`. */ @EncodeDefault val type: String = "string" } @@ -277,7 +277,6 @@ public data class UntitledMultiSelectEnumSchema( * @property minItems Minimum number of items to select. * @property maxItems Maximum number of items to select. * @property items Schema for array items with enum options and display labels. - * @property type JSON Schema type discriminator, always `"array"`. * @property default Optional default value. */ @Serializable @@ -289,6 +288,7 @@ public data class TitledMultiSelectEnumSchema( val items: Items, val default: List? = null, ) : MultiSelectEnumSchema { + /** JSON Schema type discriminator, always `"array"`. */ @EncodeDefault val type: String = "array" diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt index 4a1abc6..ab85e6d 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt @@ -424,7 +424,6 @@ private fun selectClientResultDeserializer(element: JsonElement): Deserializatio * Selects the appropriate deserializer for server results based on JSON content. * Returns null if the structure doesn't match any known server result type. */ -@Suppress("CyclomaticComplexMethod") private fun selectServerResultDeserializer(element: JsonElement): DeserializationStrategy? { val jsonObject = element.jsonObject return when { @@ -530,7 +529,6 @@ internal object RequestIdPolymorphicSerializer : JsonContentPolymorphicSerialize internal object ElicitRequestParamsSerializer : JsonContentPolymorphicSerializer( ElicitRequestParams::class, ) { - @Suppress("ThrowsCount") override fun selectDeserializer(element: JsonElement): DeserializationStrategy { val mode = when (val modeElement = element.jsonObject["mode"]) { null -> null @@ -559,7 +557,6 @@ internal object ElicitRequestParamsSerializer : JsonContentPolymorphicSerializer internal object PrimitiveSchemaDefinitionSerializer : JsonContentPolymorphicSerializer( PrimitiveSchemaDefinition::class, ) { - @Suppress("ThrowsCount") override fun selectDeserializer(element: JsonElement): DeserializationStrategy { val obj = element.jsonObject val typeElement = obj["type"] @@ -589,7 +586,6 @@ internal object PrimitiveSchemaDefinitionSerializer : JsonContentPolymorphicSeri } } - @Suppress("DEPRECATION") private fun selectStringTypeDeserializer( obj: Map, ): DeserializationStrategy = when { diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tasks.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tasks.kt index 0f154e5..7363561 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tasks.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tasks.kt @@ -17,22 +17,27 @@ public const val RELATED_TASK_META_KEY: String = "io.modelcontextprotocol/relate /** * Common fields shared by all types representing the task state. - * - * @property taskId the task identifier - * @property status current task state - * @property statusMessage optional human-readable message describing the current task state - * @property createdAt ISO 8601 timestamp when the task was created - * @property lastUpdatedAt ISO 8601 timestamp when the task was last updated - * @property ttl actual retention duration from creation in milliseconds, or `null` for unlimited - * @property pollInterval suggested polling interval in milliseconds */ public sealed interface TaskFields { + /** The task identifier. */ public val taskId: String + + /** Current task state. */ public val status: TaskStatus + + /** Optional human-readable message describing the current task state. */ public val statusMessage: String? + + /** ISO 8601 timestamp when the task was created. */ public val createdAt: String + + /** ISO 8601 timestamp when the task was last updated. */ public val lastUpdatedAt: String + + /** Actual retention duration from creation in milliseconds, or `null` for unlimited. */ public val ttl: Long? + + /** Suggested polling interval in milliseconds. */ public val pollInterval: Long? } @@ -237,13 +242,13 @@ public data class GetTaskPayloadRequestParams( * For example, a tools/call task would return the [CallToolResult] structure. * * @property json the raw JSON object containing the result payload - * @property meta Optional metadata for this response. */ @JvmInline @Serializable public value class GetTaskPayloadResult(public val json: JsonObject) : ClientResult, ServerResult { + /** Optional metadata for this response, extracted from the `_meta` field of [json]. */ override val meta: JsonObject? get() = json["_meta"]?.takeIf { it is JsonObject } as? JsonObject diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt index a03e8ad..2ff890a 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt @@ -84,7 +84,6 @@ public data class Tool( * @property schema Optional URI identifying the JSON Schema dialect (e.g., * `https://json-schema.org/draft/2020-12/schema`). Serialized as `$schema`. * When absent, JSON Schema 2020-12 is assumed by default. - * @property type Always "object" for tool schemas. * @property properties Optional map of property names to their schema definitions. * @property required Optional list of property names that are required. * @property defs Optional schema definitions available to references in [properties]. Serialized as `$defs`. @@ -98,6 +97,7 @@ public data class ToolSchema( @SerialName("\$defs") val defs: JsonObject? = null, ) { + /** Always `"object"` for tool schemas. */ @OptIn(ExperimentalSerializationApi::class) @EncodeDefault val type: String = "object" diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/PathSegmentTemplateMatcher.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/PathSegmentTemplateMatcher.kt index c5619d5..aa2abb9 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/PathSegmentTemplateMatcher.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/PathSegmentTemplateMatcher.kt @@ -66,11 +66,9 @@ private const val VARIABLE_MATCH_SCORE = 1 * no normalizer is available, so dot-segment traversal is **not** mitigated at this * layer — handlers on those targets must normalize paths themselves. * - * @param resourceTemplate The resource template to match against. Must follow RFC 6570 Level 1 syntax. - * @param maxUriLength Maximum allowed length for incoming URIs. Defaults to 2048. - * @param maxDepth Maximum allowed segment count for the template/uri. Defaults to 50. - * - * @property resourceTemplate The resource template against which resource URIs will be matched. + * @property resourceTemplate the resource template to match against; must follow RFC 6570 Level 1 syntax + * @param maxDepth maximum allowed segment count for the template/uri (defaults to 50) + * @param maxUriLength maximum allowed length for incoming URIs (defaults to 2048) */ public class PathSegmentTemplateMatcher @JvmOverloads constructor( override val resourceTemplate: ResourceTemplate, @@ -118,7 +116,6 @@ public class PathSegmentTemplateMatcher @JvmOverloads constructor( variableIndices = vars.toImmutableMap() } - @Suppress("ReturnCount") override fun match(resourceUri: String): MatchResult? { if (resourceUri.length > maxUriLength) { logger.debug { "URL is too long (max=$maxUriLength)" } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/ResourceTemplateMatcher.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/ResourceTemplateMatcher.kt index 867cc9f..d99bfda 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/ResourceTemplateMatcher.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/utils/ResourceTemplateMatcher.kt @@ -32,11 +32,10 @@ public class MatchResult(public val variables: Map, public val s * candidate URIs against that template via [match]. The returned [MatchResult.score] * must reflect match specificity so that a selection algorithm can prefer the most * specific template when multiple templates match the same URI. - * - * @property resourceTemplate the template to match URIs against */ public interface ResourceTemplateMatcher { + /** The template to match URIs against. */ public val resourceTemplate: ResourceTemplate /** diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ElicitationTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ElicitationTest.kt index 5392890..1a41863 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ElicitationTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ElicitationTest.kt @@ -176,7 +176,6 @@ class ElicitationTest { // ── Deprecated compat ─────────────────────────────────────────────── @Test - @Suppress("DEPRECATION") fun `deprecated factory function should create ElicitRequestFormParams`() { val schema = ElicitRequestParams.RequestedSchema( properties = mapOf("name" to StringSchema()), @@ -188,7 +187,6 @@ class ElicitationTest { } @Test - @Suppress("DEPRECATION") fun `deprecated requestedSchema should return schema for form mode`() { val schema = ElicitRequestParams.RequestedSchema( properties = mapOf("x" to BooleanSchema()), @@ -198,7 +196,6 @@ class ElicitationTest { } @Test - @Suppress("DEPRECATION") fun `deprecated requestedSchema should return null for URL mode`() { val request = ElicitRequest( ElicitRequestURLParams(message = "m", elicitationId = "id", url = "https://example.com"), diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/InitializeTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/InitializeTest.kt index ed97c18..e5797b9 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/InitializeTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/InitializeTest.kt @@ -15,7 +15,6 @@ import kotlin.test.assertNull class InitializeTest { @Test - @Suppress("LongMethod") fun `should serialize InitializeRequest with capabilities and meta`() { val request = InitializeRequest( InitializeRequestParams( diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PrimitiveSchemaTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PrimitiveSchemaTest.kt index 954fb1e..904454f 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PrimitiveSchemaTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/PrimitiveSchemaTest.kt @@ -397,7 +397,6 @@ class PrimitiveSchemaTest { // ── LegacyTitledEnumSchema ──────────────────────────────────────────── - @Suppress("DEPRECATION") @Test fun `should serialize LegacyTitledEnumSchema`() { verifySerialization( @@ -420,7 +419,6 @@ class PrimitiveSchemaTest { ) } - @Suppress("DEPRECATION") @Test fun `should deserialize LegacyTitledEnumSchema from JSON`() { val json = """ diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/SamplingTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/SamplingTest.kt index 0432aba..62a82cd 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/SamplingTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/SamplingTest.kt @@ -106,7 +106,6 @@ class SamplingTest { } @Test - @Suppress("LongMethod") fun `should serialize CreateMessageRequest with all fields`() { val request = CreateMessageRequest( CreateMessageRequestParams( diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ToolsTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ToolsTest.kt index 87f1644..d5e1d5a 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ToolsTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ToolsTest.kt @@ -37,7 +37,6 @@ class ToolsTest { } @Test - @Suppress("LongMethod") fun `should serialize Tool with annotations and schemas`() { val inputSchema = ToolSchema( properties = buildJsonObject { diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/SamplingDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/SamplingDslTest.kt index 10be8b0..98817c1 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/SamplingDslTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/SamplingDslTest.kt @@ -28,7 +28,6 @@ import kotlin.test.Test @OptIn(ExperimentalMcpApi::class) class SamplingDslTest { @Test - @Suppress("LongMethod") fun `buildCreateMessageRequest should build with all fields`() { val request = buildCreateMessageRequest { maxTokens = 1000 diff --git a/kotlin-sdk-server/build.gradle.kts b/kotlin-sdk-server/build.gradle.kts index 126c516..193eb90 100644 --- a/kotlin-sdk-server/build.gradle.kts +++ b/kotlin-sdk-server/build.gradle.kts @@ -1,5 +1,6 @@ plugins { id("mcp.multiplatform") + id("mcp.detekt") id("mcp.publishing") id("mcp.dokka") alias(libs.plugins.kotlinx.binary.compatibility.validator) diff --git a/kotlin-sdk-server/detekt-baseline-commonMainSourceSet.xml b/kotlin-sdk-server/detekt-baseline-commonMainSourceSet.xml deleted file mode 100644 index d383ec9..0000000 --- a/kotlin-sdk-server/detekt-baseline-commonMainSourceSet.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - MagicNumber:StdioServerTransport.kt:StdioServerTransport$8192 - MaxLineLength:SSEServerTransport.kt:SseServerTransport$"SSEServerTransport already started! If using Server class, note that connect() calls start() automatically." - MaxLineLength:SSEServerTransport.kt:SseServerTransport$* - ReturnCount:KtorServer.kt:private suspend fun existingStreamableTransport: StreamableHttpServerTransport? - TooGenericExceptionCaught:SSEServerTransport.kt:SseServerTransport$e: Exception - TooGenericExceptionCaught:StdioServerTransport.kt:StdioServerTransport$e: Throwable - TooManyFunctions:KtorServer.kt:io.modelcontextprotocol.kotlin.sdk.server.KtorServer.kt - - diff --git a/kotlin-sdk-server/detekt-baseline-main.xml b/kotlin-sdk-server/detekt-baseline-main.xml index a27f38a..aad11fa 100644 --- a/kotlin-sdk-server/detekt-baseline-main.xml +++ b/kotlin-sdk-server/detekt-baseline-main.xml @@ -2,29 +2,6 @@ - InjectDispatcher:FeatureNotificationService.kt:FeatureNotificationService$Default - LongParameterList:KtorServer.kt:private suspend fun RoutingContext.streamableTransport: StreamableHttpServerTransport? - LongParameterList:StreamableHttpServerTransport.kt:StreamableHttpServerTransport.Configuration - MagicNumber:StdioServerTransport.kt:StdioServerTransport$8192 - MaxLineLength:SSEServerTransport.kt:SseServerTransport$"SSEServerTransport already started! If using Server class, note that connect() calls start() automatically." - MaxLineLength:SSEServerTransport.kt:SseServerTransport$* - NoNameShadowing:FeatureNotificationService.kt:FeatureNotificationService${ it.remove(job) } - ReturnCount:KtorServer.kt:private suspend fun existingStreamableTransport: StreamableHttpServerTransport? - TooGenericExceptionCaught:SSEServerTransport.kt:SseServerTransport$e: Exception - TooGenericExceptionCaught:StdioServerTransport.kt:StdioServerTransport$e: Throwable - TooManyFunctions:KtorServer.kt:io.modelcontextprotocol.kotlin.sdk.server.KtorServer.kt UnsafeCallOnNullableType:StreamableHttpServerTransport.kt:StreamableHttpServerTransport$responseRequestId!! - UseCheckNotNull:Server.kt:Server$check(options.capabilities.prompts != null) { logger.error { "Failed to add prompt '${prompt.name}': Server does not support prompts capability" } "Server does not support prompts capability." } - UseCheckNotNull:Server.kt:Server$check(options.capabilities.prompts != null) { logger.error { "Failed to add prompts: Server does not support prompts capability" } "Server does not support prompts capability." } - UseCheckNotNull:Server.kt:Server$check(options.capabilities.prompts != null) { logger.error { "Failed to remove prompt '$name': Server does not support prompts capability" } "Server does not support prompts capability." } - UseCheckNotNull:Server.kt:Server$check(options.capabilities.prompts != null) { logger.error { "Failed to remove prompts: Server does not support prompts capability" } "Server does not support prompts capability." } - UseCheckNotNull:Server.kt:Server$check(options.capabilities.resources != null) { logger.error { "Failed to add resource '$name': Server does not support resources capability" } "Server does not support resources capability." } - UseCheckNotNull:Server.kt:Server$check(options.capabilities.resources != null) { logger.error { "Failed to add resources: Server does not support resources capability" } "Server does not support resources capability." } - UseCheckNotNull:Server.kt:Server$check(options.capabilities.resources != null) { logger.error { "Failed to remove resource '$uri': Server does not support resources capability" } "Server does not support resources capability." } - UseCheckNotNull:Server.kt:Server$check(options.capabilities.resources != null) { logger.error { "Failed to remove resources: Server does not support resources capability" } "Server does not support resources capability." } - UseCheckNotNull:Server.kt:Server$check(options.capabilities.tools != null) { logger.error { "Failed to add tool '${tool.name}': Server does not support tools capability" } "Server does not support tools capability. Enable it in ServerOptions." } - UseCheckNotNull:Server.kt:Server$check(options.capabilities.tools != null) { logger.error { "Failed to add tools: Server does not support tools capability" } "Server does not support tools capability." } - UseCheckNotNull:Server.kt:Server$check(options.capabilities.tools != null) { logger.error { "Failed to remove tool '$name': Server does not support tools capability" } "Server does not support tools capability." } - UseCheckNotNull:Server.kt:Server$check(options.capabilities.tools != null) { logger.error { "Failed to remove tools: Server does not support tools capability" } "Server does not support tools capability." } diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnection.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnection.kt index e64b0da..9b4b2f3 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnection.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnection.kt @@ -33,7 +33,6 @@ private val logger = KotlinLogging.logger {} * communication through notifications, requests, and other operations. * This interface defines various methods to facilitate the interaction. */ -@Suppress("TooManyFunctions") public interface ClientConnection { /** @@ -176,7 +175,6 @@ public interface ClientConnection { public suspend fun sendElicitationComplete(notification: ElicitationCompleteNotification) } -@Suppress("TooManyFunctions") internal class ClientConnectionImpl(private val session: ServerSession) : ClientConnection { override val sessionId: String get() = session.sessionId diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Feature.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Feature.kt index ce55f3e..a892c6c 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Feature.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Feature.kt @@ -64,6 +64,7 @@ public data class RegisteredResource( * A registered resource template with its associated read handler. * * @property resourceTemplate The [ResourceTemplate] definition (RFC 6570 URI template). + * @property matcher Pre-built matcher used to test incoming URIs against [resourceTemplate]. * @property readHandler A suspend function invoked when a client reads a URI that matches * this template. The second parameter contains the URI variables extracted from the match. */ diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/FeatureNotificationService.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/FeatureNotificationService.kt index d996b2d..42c2b8f 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/FeatureNotificationService.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/FeatureNotificationService.kt @@ -38,6 +38,7 @@ private sealed class NotificationEvent(open val timestamp: Long) /** * Represents an event for a notification. * + * @property timestamp Timestamp at which this event was created. * @property notification The notification associated with the event. */ private class SendEvent(override val timestamp: Long, val notification: Notification) : NotificationEvent(timestamp) @@ -340,7 +341,7 @@ internal class FeatureNotificationService( emitJobs.getAndUpdate { it.add(job) } // Register completion - job.invokeOnCompletion { + job.invokeOnCompletion { _ -> emitJobs.getAndUpdate { it.remove(job) } } diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt index 806a479..3f03a7a 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt @@ -1,5 +1,3 @@ -@file:Suppress("TooManyFunctions") - package io.modelcontextprotocol.kotlin.sdk.server import io.github.oshai.kotlinlogging.KotlinLogging @@ -172,7 +170,6 @@ private fun Application.mcpStreamableHttp( * that creates and returns the [Server] to handle the connection. */ @KtorDsl -@Suppress("LongParameterList") public fun Application.mcpStreamableHttp( path: String = "/mcp", enableDnsRebindingProtection: Boolean = false, @@ -247,7 +244,6 @@ private fun Application.mcpStatelessStreamableHttp( * that creates and returns the [Server] to handle the connection. */ @KtorDsl -@Suppress("LongParameterList") public fun Application.mcpStatelessStreamableHttp( path: String = "/mcp", enableDnsRebindingProtection: Boolean = false, diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt index fcf1a46..a6d4618 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt @@ -27,13 +27,15 @@ internal const val SESSION_ID_PARAM = "sessionId" * * Creates a new SSE server transport, which will direct the client to POST messages to the relative or absolute URL identified by `_endpoint`. * - * @property sessionId unique identifier for this transport session, generated randomly on creation + * @param endpoint relative or absolute URL the client will POST messages to + * @param session active SSE session used to deliver server-to-client events */ @OptIn(ExperimentalAtomicApi::class) public class SseServerTransport(private val endpoint: String, private val session: ServerSSESession) : AbstractTransport() { private val initialized: AtomicBoolean = AtomicBoolean(false) + /** Unique identifier for this transport session, generated randomly on creation. */ @OptIn(ExperimentalUuidApi::class) public val sessionId: String = Uuid.random().toString() diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt index d9b9b9a..1b7bfe7 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt @@ -64,7 +64,8 @@ private val logger = KotlinLogging.logger {} * Configuration options for the MCP server. * * @property capabilities The capabilities this server supports. - * @property enforceStrictCapabilities Whether to strictly enforce capabilities when interacting with clients. + * @param enforceStrictCapabilities Whether to strictly enforce capabilities when interacting with clients. + * Forwarded to [ProtocolOptions.enforceStrictCapabilities]. * @property resourceTemplateMatcherFactory The factory used to create [ResourceTemplateMatcher] instances * for matching resource URIs against registered templates. Defaults to [PathSegmentTemplateMatcher.factory]. */ @@ -92,13 +93,12 @@ public class ServerOptions( * Currently, after subscription to a resource, the server will NOT send the subscription confirmation * as this response schema is not defined in the protocol. * - * @param serverInfo Information about this server implementation (name, version). - * @param options Configuration options for the server. - * @param instructionsProvider Optional provider for instructions from the server to the client about how to use + * @property serverInfo Information about this server implementation (name, version). + * @property options Configuration options for the server. + * @property instructionsProvider Optional provider for instructions from the server to the client about how to use * this server. The provider is called each time a new session is started to support dynamic instructions. * @param block A block to configure the mcp server. */ -@Suppress("TooManyFunctions") public open class Server( protected val serverInfo: Implementation, protected val options: ServerOptions, @@ -312,9 +312,9 @@ public open class Server( * Registers a single tool. The client can then call this tool. * * @param name The name of the tool. - * @param title An optional human-readable name of the tool for display purposes. * @param description A human-readable description of what the tool does. * @param inputSchema The expected input schema for the tool. + * @param title An optional human-readable name of the tool for display purposes. * @param outputSchema The optional expected output schema for the tool. * @param toolAnnotations Optional additional tool information. * @param execution Optional execution-related properties, such as task-augmented execution support. @@ -322,7 +322,6 @@ public open class Server( * @param handler A suspend function that handles executing the tool when called by the client. * @throws IllegalStateException If the server does not support tools. */ - @Suppress("LongParameterList") public fun addTool( name: String, description: String, @@ -645,7 +644,6 @@ public open class Server( } // Execute the tool handler and catch any errors - @Suppress("TooGenericExceptionCaught") return try { logger.trace { "Executing tool ${requestParams.name} with input: ${requestParams.arguments}" } tool.run { diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt index 2f55c76..e7da26f 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt @@ -39,17 +39,28 @@ import kotlin.uuid.Uuid private val logger = KotlinLogging.logger {} /** - * Represents a server session. + * Represents an active server-side session in the Model Context Protocol. * - * @property sessionId unique identifier for this session, generated randomly on creation + * Owns the server side of a single client connection on top of [Protocol]. Drives the + * initialize/initialized handshake, tracks the negotiated client capabilities and version, + * asserts capability requirements before dispatching requests and notifications, and exposes + * server-to-client operations such as sampling, root listing, elicitation, logging, and + * list-change notifications. + * + * Equality and hashing are based on [sessionId], so a session can be used as a key in + * routing structures. + * + * @property serverInfo server name and version reported to the client during initialization + * @param options server capabilities and protocol options used by [Protocol] + * @property instructions optional human-readable instructions for the client, sent in the initialize result */ -@Suppress("TooManyFunctions") public open class ServerSession( protected val serverInfo: Implementation, options: ServerOptions, protected val instructions: String?, ) : Protocol(options) { + /** Unique identifier for this session, generated on creation. */ @OptIn(ExperimentalUuidApi::class) public val sessionId: String = Uuid.random().toString() @@ -60,14 +71,10 @@ public open class ServerSession( private val _clientCapabilities: AtomicRef = atomic(null) private val _clientVersion: AtomicRef = atomic(null) - /** - * The client's reported capabilities after initialization. - */ + /** Capabilities reported by the client during initialization, or `null` before the handshake completes. */ public val clientCapabilities: ClientCapabilities? get() = _clientCapabilities.value - /** - * The client's version information after initialization. - */ + /** Client implementation information reported during initialization, or `null` before the handshake completes. */ public val clientVersion: Implementation? get() = _clientVersion.value /** diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport.kt index 8fd46dd..a4d588c 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport.kt @@ -72,7 +72,6 @@ public class StdioServerTransport(private val inputStream: Source, outputStream: private fun launchReadingJob(): Job { val job = scope.launch { val buf = Buffer() - @Suppress("TooGenericExceptionCaught") try { while (isActive) { val bytesRead = inputStream.readAtMostTo(buf, READ_BUFFER_SIZE) @@ -103,7 +102,6 @@ public class StdioServerTransport(private val inputStream: Source, outputStream: private fun launchProcessingJob(): Job { val job = scope.launch { - @Suppress("TooGenericExceptionCaught") try { for (chunk in readChannel) { readBuffer.append(chunk) @@ -123,7 +121,6 @@ public class StdioServerTransport(private val inputStream: Source, outputStream: private fun launchSendingJob(): Job { val job = scope.launch { - @Suppress("TooGenericExceptionCaught") try { for (message in writeChannel) { val json = serializeMessage(message) @@ -147,7 +144,6 @@ public class StdioServerTransport(private val inputStream: Source, outputStream: } private suspend fun processReadBuffer() { - @Suppress("TooGenericExceptionCaught") while (true) { val message = try { readBuffer.readMessage() diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt index f232001..870a92c 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt @@ -74,10 +74,8 @@ private data class SessionContext(val session: ServerSSESession?, val call: Appl * - No session validation is performed * * @param configuration Transport configuration. See [Configuration] for available options. - * @property sessionId session identifier assigned after initialization, or `null` in stateless mode */ @OptIn(ExperimentalUuidApi::class, ExperimentalAtomicApi::class) -@Suppress("TooManyFunctions") public class StreamableHttpServerTransport(private val configuration: Configuration) : AbstractTransport() { @Deprecated("Use default constructor with explicit Configuration()") @@ -100,7 +98,6 @@ public class StreamableHttpServerTransport(private val configuration: Configurat * @param retryIntervalMillis Retry interval in milliseconds for event handling or reconnection attempts. * Defaults to `null`. */ - @Suppress("MaxLineLength") @Deprecated( "Use constructor with Configuration: StreamableHttpServerTransport(Configuration(enableJsonResponse = ...))", replaceWith = ReplaceWith( @@ -153,6 +150,7 @@ public class StreamableHttpServerTransport(private val configuration: Configurat } } + /** Session identifier assigned after initialization in stateful mode, or `null` in stateless mode. */ public var sessionId: String? = null private set @@ -215,7 +213,6 @@ public class StreamableHttpServerTransport(private val configuration: Configurat } } - @Suppress("CyclomaticComplexMethod", "ReturnCount") override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { val responseRequestId: RequestId? = when (message) { is JSONRPCResponse -> message.id @@ -329,7 +326,6 @@ public class StreamableHttpServerTransport(private val configuration: Configurat /** * Handles POST requests containing JSON-RPC messages */ - @Suppress("CyclomaticComplexMethod", "LongMethod", "ReturnCount", "TooGenericExceptionCaught") public suspend fun handlePostRequest(session: ServerSSESession?, call: ApplicationCall) { try { if (!configuration.enableJsonResponse && session == null) { @@ -438,7 +434,6 @@ public class StreamableHttpServerTransport(private val configuration: Configurat } /** Handles an HTTP GET request by establishing a standalone SSE stream for server-initiated notifications. */ - @Suppress("ReturnCount") public suspend fun handleGetRequest(session: ServerSSESession?, call: ApplicationCall) { // NOTE: enableJsonResponse only controls how POST responses are delivered (JSON vs. SSE). // The standalone GET SSE stream is always supported — it is the only channel available @@ -512,7 +507,6 @@ public class StreamableHttpServerTransport(private val configuration: Configurat * Closes the SSE stream associated with the given [requestId], prompting the client to reconnect. * Useful for implementing polling behavior for long-running operations. */ - @Suppress("ReturnCount", "TooGenericExceptionCaught") public suspend fun closeSseStream(requestId: RequestId) { if (configuration.enableJsonResponse) return val streamId = requestToStreamMapping[requestId] ?: return @@ -529,7 +523,6 @@ public class StreamableHttpServerTransport(private val configuration: Configurat } } - @Suppress("TooGenericExceptionCaught") private suspend fun replayEvents(store: EventStore, lastEventId: String, session: ServerSSESession) { val call: ApplicationCall = session.call @@ -596,7 +589,6 @@ public class StreamableHttpServerTransport(private val configuration: Configurat } } - @Suppress("ReturnCount") private suspend fun validateSession(call: ApplicationCall): Boolean { if (sessionIdGenerator == null) return true @@ -655,7 +647,6 @@ public class StreamableHttpServerTransport(private val configuration: Configurat } } - @Suppress("ReturnCount") private fun validateHeaders(call: ApplicationCall): String? { if (!configuration.enableDnsRebindingProtection) return null @@ -680,7 +671,6 @@ public class StreamableHttpServerTransport(private val configuration: Configurat return null } - @Suppress("TooGenericExceptionCaught") private suspend fun flushSse(session: ServerSSESession?) { try { session?.send(data = "") @@ -691,7 +681,6 @@ public class StreamableHttpServerTransport(private val configuration: Configurat } } - @Suppress("ReturnCount") private suspend fun parseBody(call: ApplicationCall): List? { val maxSize = configuration.maxRequestBodySize val contentLength = call.request.header(HttpHeaders.ContentLength)?.toLongOrNull() ?: 0L @@ -752,7 +741,6 @@ public class StreamableHttpServerTransport(private val configuration: Configurat } } - @Suppress("TooGenericExceptionCaught") private suspend fun maybeSendPrimingEvent( streamId: String, session: ServerSSESession?, diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt index c2cb2c4..a05daef 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt @@ -1,5 +1,3 @@ -@file:Suppress("TooManyFunctions") - package io.modelcontextprotocol.kotlin.sdk.server import io.github.oshai.kotlinlogging.KotlinLogging diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/AbstractKtorExtensionsTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/AbstractKtorExtensionsTest.kt index 28e21b9..7639bc6 100644 --- a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/AbstractKtorExtensionsTest.kt +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/AbstractKtorExtensionsTest.kt @@ -14,7 +14,6 @@ import io.ktor.utils.io.readUTF8Line import io.modelcontextprotocol.kotlin.sdk.types.Implementation import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities -@Suppress("AbstractClassCanBeConcreteClass") abstract class AbstractKtorExtensionsTest { protected val sseContentType = ContentType("text", "event-stream") diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransportTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransportTest.kt index dcbb7a4..32123a6 100644 --- a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransportTest.kt +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransportTest.kt @@ -299,21 +299,18 @@ class StdioServerTransportTest { server.close() } - @Suppress("unused") private fun inputErrors() = listOf( IOException("simulated read failure"), RuntimeException("unexpected read exception"), OutOfMemoryError("unexpected read error"), ) - @Suppress("unused") private fun outputErrors() = listOf( IOException("simulated write failure"), RuntimeException("unexpected write exception"), OutOfMemoryError("unexpected write error"), ) - @Suppress("unused") private fun handlerErrors() = listOf( RuntimeException("handler failure"), IOException("handler IO failure"), diff --git a/kotlin-sdk-testing/build.gradle.kts b/kotlin-sdk-testing/build.gradle.kts index e318f6f..bbb0388 100644 --- a/kotlin-sdk-testing/build.gradle.kts +++ b/kotlin-sdk-testing/build.gradle.kts @@ -4,6 +4,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl plugins { id("mcp.multiplatform") + id("mcp.detekt") id("mcp.publishing") id("mcp.dokka") alias(libs.plugins.kotlinx.binary.compatibility.validator) diff --git a/kotlin-sdk-testing/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/testing/ChannelTransport.kt b/kotlin-sdk-testing/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/testing/ChannelTransport.kt index e6d19f7..ae5f487 100644 --- a/kotlin-sdk-testing/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/testing/ChannelTransport.kt +++ b/kotlin-sdk-testing/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/testing/ChannelTransport.kt @@ -27,9 +27,8 @@ import kotlinx.coroutines.yield * communication of JSON-RPC messages. This is useful in scenarios where communication is * message-based and channels are used as the underlying mechanism for delivery. * - * @constructor Initializes the transport with the specified [SendChannel] and [ReceiveChannel]. - * @property sendChannel The channel used to send JSON-RPC messages. - * @property receiveChannel The channel used to receive JSON-RPC messages. + * @param sendChannel The channel used to send JSON-RPC messages. + * @param receiveChannel The channel used to receive JSON-RPC messages. * @param dispatcher The coroutine dispatcher for the event loop. Defaults to [Dispatchers.Default]. */ @ExperimentalMcpApi @@ -75,11 +74,19 @@ public class ChannelTransport( /** * Creates a pair of interconnected [ChannelTransport] objects for bidirectional communication. * - * This method sets up a client and server transport using Kotlin channels, enabling messages sent - * through one transport to be received by the other. It is typically used for testing or in-memory - * communication without external networking. + * Sets up a client and server transport using Kotlin channels, so that messages sent through one + * transport are received by the other. Typically used for testing or in-memory communication + * without external networking. + * + * Example: + * ```kotlin + * val (clientTransport, serverTransport) = ChannelTransport.createLinkedPair() + * server.connect(serverTransport) + * client.connect(clientTransport) + * ``` * * @param capacity The buffer capacity of the internal channels used for communication. Defaults to 256. + * @param dispatcher The coroutine dispatcher for the event loop. Defaults to [Dispatchers.Default]. * @return A [LinkedTransports] instance containing the client and server transports. */ public fun createLinkedPair( @@ -106,7 +113,6 @@ public class ChannelTransport( override suspend fun initialize() { logger.info { "ChannelTransport starting message processing" } val started = CompletableDeferred() - @Suppress("TooGenericExceptionCaught") scope.launch(CoroutineName("ChannelTransport#${hashCode()}-event-loop")) { try { // Signal that event loop has started, then yield to ensure we're ready diff --git a/kotlin-sdk-testing/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/testing/ChannelTransportTest.kt b/kotlin-sdk-testing/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/testing/ChannelTransportTest.kt index 7fc41bb..4aae932 100644 --- a/kotlin-sdk-testing/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/testing/ChannelTransportTest.kt +++ b/kotlin-sdk-testing/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/testing/ChannelTransportTest.kt @@ -186,7 +186,6 @@ class ChannelTransportTest { val id = ((msg as JSONRPCRequest).id as RequestId.NumberId).value.toInt() received.add(id) if (id == 2) { - @Suppress("TooGenericExceptionThrown") throw RuntimeException("Error processing message 2") } if (received.size == 4) { diff --git a/kotlin-sdk/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/package.kt b/kotlin-sdk/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/package.kt index 59faee4..13bf272 100644 --- a/kotlin-sdk/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/package.kt +++ b/kotlin-sdk/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/package.kt @@ -1,25 +1,3 @@ -@file:Suppress("ktlint:standard:no-empty-class-body", "ktlint:standard:kdoc") -/** - * # MCP Kotlin SDK - * - * A Kotlin Multiplatform implementation of the Model Context Protocol (MCP). - * - * This is the main SDK module that provides a convenient single dependency - * for all MCP functionality including: - * - * - Core protocol types and utilities ([kotlin-sdk-core]) - * - Client implementations ([kotlin-sdk-client]) - * - Server implementations ([kotlin-sdk-server]) - * - * ## Usage - * - * Add this dependency to your project to get access to all MCP Kotlin SDK functionality: - * - * ```kotlin - * implementation("io.modelcontextprotocol:kotlin-sdk:$version") - * ``` - * - * This will transitively include all core, client, and server components. - */ - package io.modelcontextprotocol.kotlin.sdk + +private class Stub diff --git a/test-utils/build.gradle.kts b/test-utils/build.gradle.kts index 6e91257..c6baf2d 100644 --- a/test-utils/build.gradle.kts +++ b/test-utils/build.gradle.kts @@ -4,6 +4,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl plugins { id("mcp.multiplatform") + id("mcp.detekt") } kotlin { diff --git a/test-utils/detekt-baseline-main.xml b/test-utils/detekt-baseline-main.xml new file mode 100644 index 0000000..431f3d9 --- /dev/null +++ b/test-utils/detekt-baseline-main.xml @@ -0,0 +1,20 @@ + + + + + ForbiddenMethodCall:Retry.kt:RetryExtension$println( "[Retry] Failure on attempt $attempt/$maxAttempts for ${ describeTest( extensionContext, ) }: ${lastError.message}", ) + ForbiddenMethodCall:Retry.kt:RetryExtension$println( "[Retry] Giving up after $attempt attempts for ${ describeTest( extensionContext, ) }: ${lastError.message}", ) + ForbiddenMethodCall:TypeScriptRunner.kt:TypeScriptRunner$println("Adding exports to ${file.absolutePath}") + ForbiddenMethodCall:TypeScriptRunner.kt:TypeScriptRunner$println("Command failed: ${e.message}") + ForbiddenMethodCall:TypeScriptRunner.kt:TypeScriptRunner$println("Detected pnpm-specific protocols or workspace. Attempting pnpm install...") + ForbiddenMethodCall:TypeScriptRunner.kt:TypeScriptRunner$println("Executing: ${fullCommand.joinToString(" ")}") + ForbiddenMethodCall:TypeScriptRunner.kt:TypeScriptRunner$println("Installing TypeScript dependencies in ${typescriptDir.absolutePath}") + ForbiddenMethodCall:TypeScriptRunner.kt:TypeScriptRunner$println("[TS-INSTALL] $it") + ForbiddenMethodCall:TypeScriptRunner.kt:TypeScriptRunner$println("npm install failed. Patching package.json and trying again...") + ForbiddenMethodCall:processUtils.kt:println("Warning: Error reading process error stream: ${e.message}") + ForbiddenMethodCall:processUtils.kt:println("Warning: Error reading process output: ${e.message}") + ForbiddenMethodCall:processUtils.kt:println("[$prefix] $it") + ForbiddenMethodCall:processUtils.kt:println("[$prefix][err] $it") + ForbiddenVoid:Retry.kt:RetryExtension$Void + + diff --git a/test-utils/detekt-baseline.xml b/test-utils/detekt-baseline.xml deleted file mode 100644 index 5abd0b4..0000000 --- a/test-utils/detekt-baseline.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - CyclomaticComplexMethod:Retry.kt:RetryExtension$private fun executeWithRetry - ThrowsCount:Retry.kt:RetryExtension$private fun executeWithRetry - - diff --git a/test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/Retry.kt b/test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/Retry.kt index a37e6e3..1dcf585 100644 --- a/test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/Retry.kt +++ b/test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/Retry.kt @@ -16,7 +16,6 @@ import java.util.Optional @ExtendWith(RetryExtension::class) public annotation class Retry(val times: Int = 3, val delayMs: Long = 1000) -@Suppress("ForbiddenVoid") // needed for Java API interop public class RetryExtension : InvocationInterceptor { override fun interceptTestMethod( invocation: Invocation, diff --git a/test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/TypeScriptRunner.kt b/test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/TypeScriptRunner.kt index e7f5798..ff9292e 100644 --- a/test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/TypeScriptRunner.kt +++ b/test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/TypeScriptRunner.kt @@ -18,7 +18,6 @@ public object TypeScriptRunner { * @param logPrefix Prefix for log lines * @return The started Process */ - @Suppress("LongParameterList") public fun run( typescriptDir: File, scriptPath: String, @@ -54,7 +53,6 @@ public object TypeScriptRunner { * @param typescriptDir The directory containing package.json * @throws RuntimeException if npm install fails */ - @Suppress("ReturnCount") public fun installDependencies(typescriptDir: File) { require(typescriptDir.isDirectory()) { "Type script directory does not exist: ${typescriptDir.absolutePath}" } println("Installing TypeScript dependencies in ${typescriptDir.absolutePath}") diff --git a/test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/processUtils.kt b/test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/processUtils.kt index 71fcfa6..a80c825 100644 --- a/test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/processUtils.kt +++ b/test-utils/src/jvmMain/kotlin/io/modelcontextprotocol/kotlin/test/utils/processUtils.kt @@ -14,7 +14,6 @@ import kotlin.time.Duration.Companion.seconds */ public fun killProcessOnPort(port: Int) { val killCommand = if (isWindows) { - @Suppress("MaxLineLength") listOf( "cmd.exe", "/c", From c339d8cb8656ae419d6149b3342a568ba0119351 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Wed, 29 Apr 2026 13:27:42 +0200 Subject: [PATCH 271/449] release: bump version to 0.12.0 (#731) 0.11.1 -> 0.12.0 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index ca4a217..e145506 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,4 +8,4 @@ kotlin.daemon.jvmargs=-Xmx4G kotlin.native.ignoreDisabledTargets=true group=io.modelcontextprotocol -version=0.11.1 +version=0.12.0 From 5cb3c1de1219e36abdb409856a4d6edaf3e8b779 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Tue, 5 May 2026 11:05:59 +0200 Subject: [PATCH 272/449] fix: enable DNS rebinding protection by default (#659) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add `DnsRebindingProtection` Ktor route-scoped plugin and enable it by default across all http transports ## Motivation and Context DNS rebinding protection was disabled by default (`enableDnsRebindingProtection = false`), and sse endpoints (`Route.mcp()`) had zero protection. Same vulnerability as typescript sdk [GHSA-w48q-cv73-mx4w](https://github.com/modelcontextprotocol/typescript-sdk/security/advisories/GHSA-w48q-cv73-mx4w) ## How Has This Been Tested? all tests pass ## Breaking Changes - `Route.mcp()`, `Application.mcp()` signatures changed (new `enableDnsRebindingProtection`, `allowedHosts`, `allowedOrigins` params), binary incompatible on JVM - `enableDnsRebindingProtection` default changed from `false` to `true` on `mcpStreamableHttp`/`mcpStatelessStreamableHttp` - Default allowed hosts: `localhost`, `127.0.0.1`, `[::1]` — opt out with `enableDnsRebindingProtection = false` - Host validation is now port-agnostic (matches ts sdk) - Origin validation now allows missing `Origin` header (non-browser clients) - `Configuration.enableDnsRebindingProtection`, `.allowedHosts`, `.allowedOrigins` deprecated in favor of the plugin ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [x] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- .../sdk/conformance/ConformanceServer.kt | 2 - kotlin-sdk-server/api/kotlin-sdk-server.api | 21 +- .../kotlin/sdk/server/HostValidation.kt | 185 +++++++++ .../kotlin/sdk/server/KtorServer.kt | 99 ++++- .../server/StreamableHttpServerTransport.kt | 31 +- .../sdk/server/DnsRebindingProtectionTest.kt | 382 ++++++++++++++++++ .../server/KtorApplicationExtensionsTest.kt | 4 +- .../sdk/server/KtorRouteExtensionsTest.kt | 6 +- .../StreamableHttpServerTransportTest.kt | 8 + 9 files changed, 703 insertions(+), 35 deletions(-) create mode 100644 kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/HostValidation.kt create mode 100644 kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/DnsRebindingProtectionTest.kt diff --git a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceServer.kt b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceServer.kt index 03d5ee3..8a18957 100644 --- a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceServer.kt +++ b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceServer.kt @@ -12,8 +12,6 @@ fun main() { val port = System.getenv("MCP_PORT")?.toIntOrNull() ?: 3001 embeddedServer(CIO, port = port) { mcpStreamableHttp( - enableDnsRebindingProtection = true, - allowedHosts = listOf("localhost", "127.0.0.1", "localhost:$port", "127.0.0.1:$port"), eventStore = InMemoryEventStore(), ) { createConformanceServer() diff --git a/kotlin-sdk-server/api/kotlin-sdk-server.api b/kotlin-sdk-server/api/kotlin-sdk-server.api index 80da5b5..f400e80 100644 --- a/kotlin-sdk-server/api/kotlin-sdk-server.api +++ b/kotlin-sdk-server/api/kotlin-sdk-server.api @@ -32,16 +32,31 @@ public final class io/modelcontextprotocol/kotlin/sdk/server/ClientConnection$De 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 final class io/modelcontextprotocol/kotlin/sdk/server/DnsRebindingProtectionConfig { + public fun ()V + public final fun getAllowedHosts ()Ljava/util/List; + public final fun getAllowedOrigins ()Ljava/util/List; + public final fun setAllowedHosts (Ljava/util/List;)V + public final fun setAllowedOrigins (Ljava/util/List;)V +} + public abstract interface class io/modelcontextprotocol/kotlin/sdk/server/EventStore { public abstract fun getStreamIdForEventId (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun replayEventsAfter (Ljava/lang/String;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun storeEvent (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } +public final class io/modelcontextprotocol/kotlin/sdk/server/HostValidationKt { + public static final fun getDnsRebindingProtection ()Lio/ktor/server/application/RouteScopedPlugin; +} + public final class io/modelcontextprotocol/kotlin/sdk/server/KtorServerKt { - public static final fun mcp (Lio/ktor/server/application/Application;Lkotlin/jvm/functions/Function1;)V - public static final fun mcp (Lio/ktor/server/routing/Route;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V - public static final fun mcp (Lio/ktor/server/routing/Route;Lkotlin/jvm/functions/Function1;)V + public static final fun mcp (Lio/ktor/server/application/Application;ZLjava/util/List;Ljava/util/List;Lkotlin/jvm/functions/Function1;)V + public static final fun mcp (Lio/ktor/server/routing/Route;Ljava/lang/String;ZLjava/util/List;Ljava/util/List;Lkotlin/jvm/functions/Function1;)V + public static final fun mcp (Lio/ktor/server/routing/Route;ZLjava/util/List;Ljava/util/List;Lkotlin/jvm/functions/Function1;)V + public static synthetic fun mcp$default (Lio/ktor/server/application/Application;ZLjava/util/List;Ljava/util/List;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V + public static synthetic fun mcp$default (Lio/ktor/server/routing/Route;Ljava/lang/String;ZLjava/util/List;Ljava/util/List;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V + public static synthetic fun mcp$default (Lio/ktor/server/routing/Route;ZLjava/util/List;Ljava/util/List;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V public static final fun mcpStatelessStreamableHttp (Lio/ktor/server/application/Application;Ljava/lang/String;ZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/jvm/functions/Function1;)V public static synthetic fun mcpStatelessStreamableHttp$default (Lio/ktor/server/application/Application;Ljava/lang/String;ZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V public static final fun mcpStreamableHttp (Lio/ktor/server/application/Application;Ljava/lang/String;ZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/jvm/functions/Function1;)V diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/HostValidation.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/HostValidation.kt new file mode 100644 index 0000000..88b56bc --- /dev/null +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/HostValidation.kt @@ -0,0 +1,185 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.ktor.http.ContentType +import io.ktor.http.HttpHeaders +import io.ktor.http.HttpStatusCode +import io.ktor.http.parseUrl +import io.ktor.server.application.ApplicationCall +import io.ktor.server.application.RouteScopedPlugin +import io.ktor.server.application.createRouteScopedPlugin +import io.ktor.server.request.header +import io.ktor.server.response.respondText +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCError +import io.modelcontextprotocol.kotlin.sdk.types.McpJson +import io.modelcontextprotocol.kotlin.sdk.types.RPCError + +/** + * Default list of hostnames allowed for localhost DNS rebinding protection. + * Matches the TypeScript SDK's `localhostAllowedHostnames()`. + */ +internal val LOCALHOST_ALLOWED_HOSTS: List = listOf("localhost", "127.0.0.1", "[::1]") + +/** + * Characters that are valid in a URL but must not appear in an HTTP `Host` header. + * Rejecting them prevents the parser from accepting malformed values + * (e.g. `evil.com@localhost`, `host/path`) that a generic URL parser would silently allow. + */ +private val FORBIDDEN_HOST_CHARS: CharArray = charArrayOf('/', '@', '?', '#') + +/** + * Extracts the hostname from a Host header value, stripping the port. + * + * Only accepts the strict `host [ ":" port ]` / `"[" ipv6 "]" [ ":" port ]` + * format defined by RFC 7230. Values containing URL-only characters + * (`/`, `@`, `?`, `#`) or whitespace are rejected. + * + * Examples: + * - `"localhost:3000"` → `"localhost"` + * - `"127.0.0.1:8080"` → `"127.0.0.1"` + * - `"[::1]:3000"` → `"[::1]"` + * - `"example.com"` → `"example.com"` + * - `"evil.com@localhost"` → `null` + * + * @return the hostname, or `null` if the value is blank, malformed, or contains forbidden characters. + */ +internal fun extractHostname(hostHeader: String): String? = when { + hostHeader.isBlank() -> null + + hostHeader.any { it in FORBIDDEN_HOST_CHARS || it.isWhitespace() } -> null + + hostHeader.startsWith("[") -> { + val end = hostHeader.indexOf(']') + when { + end <= 1 -> null + isValidPortSuffix(hostHeader.substring(end + 1)) -> hostHeader.substring(0, end + 1) + else -> null + } + } + + else -> { + val colon = hostHeader.indexOf(':') + when { + colon < 0 -> hostHeader + colon == 0 -> null + isValidPortSuffix(hostHeader.substring(colon)) -> hostHeader.substring(0, colon) + else -> null + } + } +} + +/** + * Checks that [tail] is either empty (no port part) or `":"` followed by zero or more digits, + * matching RFC 7230 §3.2.3 where `port = *DIGIT`. Non-empty non-digit characters are rejected. + */ +private fun isValidPortSuffix(tail: String): Boolean = when { + tail.isEmpty() -> true + tail[0] != ':' -> false + else -> tail.substring(1).all { it.isDigit() } +} + +/** + * Extracts the hostname from an `Origin` header value. + * + * Uses Ktor's [parseUrl] to avoid reimplementing RFC 6454 parsing. + * Scheme, port, userinfo, and path are discarded — only the host matters for + * DNS-rebinding protection. The result is **not** normalized to lower-case; + * callers should apply [String.lowercase] if they need case-insensitive comparison. + * + * @return the hostname, or `null` if [origin] cannot be parsed or has no host part. + */ +internal fun extractOriginHost(origin: String): String? = parseUrl(origin)?.host?.takeIf(String::isNotEmpty) + +/** + * Configuration for the [DnsRebindingProtection] Ktor route-scoped plugin. + * + * @property allowedHosts List of hostnames allowed in the `Host` header. + * Comparison is port-agnostic and case-insensitive. + * Defaults to `localhost`, `127.0.0.1`, `[::1]`. + * An empty list will reject **all** requests. + * @property allowedOrigins Optional list of allowed `Origin` values. Entries are parsed as URLs + * (via [parseUrl]) and compared by **hostname only** — scheme and port are ignored. + * If `null`, origin validation is disabled. + * If configured, requests **with** an `Origin` header whose hostname is not in the list + * are rejected, but requests **without** an `Origin` header are allowed (non-browser clients). + */ +public class DnsRebindingProtectionConfig { + public var allowedHosts: List = LOCALHOST_ALLOWED_HOSTS + public var allowedOrigins: List? = null +} + +/** + * Ktor route-scoped plugin that validates `Host` and `Origin` headers + * to protect against DNS rebinding attacks. + * + * Install on a route to intercept all requests **before** handlers: + * ```kotlin + * route("/mcp") { + * install(DnsRebindingProtection) { + * allowedHosts = listOf("myapp.com", "localhost") + * } + * // handlers... + * } + * ``` + */ +public val DnsRebindingProtection: RouteScopedPlugin = + createRouteScopedPlugin( + "MCP-DnsRebindingProtection", + ::DnsRebindingProtectionConfig, + ) { + val hosts: Set = pluginConfig.allowedHosts.mapTo(mutableSetOf()) { + extractHostname(it)?.lowercase() + ?: error("Invalid host in DnsRebindingProtection.allowedHosts: '$it'") + } + val originHosts: Set? = pluginConfig.allowedOrigins?.mapTo(mutableSetOf()) { + extractOriginHost(it)?.lowercase() + ?: error("Invalid origin in DnsRebindingProtection.allowedOrigins: '$it'") + } + + onCall { call -> + val hostHeader = call.request.header(HttpHeaders.Host) + val hostname = hostHeader?.let { extractHostname(it) }?.lowercase() + + if (hostname == null) { + call.rejectDnsValidation("Invalid Host header: (malformed or missing)") + return@onCall + } + if (hostname !in hosts) { + call.rejectDnsValidation("Invalid Host: $hostname") + return@onCall + } + + if (originHosts != null) { + val originHeader = call.request.header(HttpHeaders.Origin) + // Allow requests without Origin (non-browser clients cannot perform DNS rebinding) + if (originHeader != null) { + val originHost = extractOriginHost(originHeader)?.lowercase() + if (originHost == null) { + call.rejectDnsValidation("Invalid Origin header: (unparseable)") + return@onCall + } + if (originHost !in originHosts) { + call.rejectDnsValidation("Invalid Origin host: $originHost") + return@onCall + } + } + } + } + } + +/** + * Responds with a 403 Forbidden JSON-RPC error without requiring ContentNegotiation. + */ +private suspend fun ApplicationCall.rejectDnsValidation(message: String) { + val error = JSONRPCError( + id = null, + error = RPCError( + code = -32_000, + message = message, + ), + ) + respondText( + McpJson.encodeToString(error), + ContentType.Application.Json, + HttpStatusCode.Forbidden, + ) +} diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt index 3f03a7a..d6191df 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt @@ -37,14 +37,26 @@ private val logger = KotlinLogging.logger {} * Use [Application.mcp] if you want SSE to be installed automatically. * * @param path the URL path to register the SSE endpoint. + * @param enableDnsRebindingProtection whether to install [DnsRebindingProtection] on this route. Defaults to `true`. + * @param allowedHosts hostnames allowed in the `Host` header. Defaults to `localhost`, `127.0.0.1`, `[::1]`. + * @param allowedOrigins origins allowed in the `Origin` header, compared by hostname only + * (scheme and port are ignored). Requests without an `Origin` header are allowed. + * Pass `null` to skip origin validation. * @param block factory block with access to the [ServerSSESession] * that creates and returns the [Server] to handle the connection. * @throws IllegalStateException if the [SSE] plugin is not installed. */ @KtorDsl -public fun Route.mcp(path: String, block: ServerSSESession.() -> Server) { +@Suppress("LongParameterList") +public fun Route.mcp( + path: String, + enableDnsRebindingProtection: Boolean = true, + allowedHosts: List? = null, + allowedOrigins: List? = null, + block: ServerSSESession.() -> Server, +) { route(path) { - mcp(block) + mcp(enableDnsRebindingProtection, allowedHosts, allowedOrigins, block) } } @@ -55,12 +67,22 @@ public fun Route.mcp(path: String, block: ServerSSESession.() -> Server) { * **Precondition:** the [SSE] plugin must be installed on the application before calling this function. * Use [Application.mcp] if you want SSE to be installed automatically. * + * @param enableDnsRebindingProtection whether to install [DnsRebindingProtection] on this route. Defaults to `true`. + * @param allowedHosts hostnames allowed in the `Host` header. Defaults to `localhost`, `127.0.0.1`, `[::1]`. + * @param allowedOrigins origins allowed in the `Origin` header, compared by hostname only + * (scheme and port are ignored). Requests without an `Origin` header are allowed. + * Pass `null` to skip origin validation. * @param block factory block with access to the [ServerSSESession] * that creates and returns the [Server] to handle the connection. * @throws IllegalStateException if the [SSE] plugin is not installed. */ @KtorDsl -public fun Route.mcp(block: ServerSSESession.() -> Server) { +public fun Route.mcp( + enableDnsRebindingProtection: Boolean = true, + allowedHosts: List? = null, + allowedOrigins: List? = null, + block: ServerSSESession.() -> Server, +) { try { plugin(SSE) } catch (e: MissingApplicationPluginException) { @@ -72,6 +94,8 @@ public fun Route.mcp(block: ServerSSESession.() -> Server) { ) } + installDnsRebindingProtection(enableDnsRebindingProtection, allowedHosts, allowedOrigins) + val transportManager = TransportManager() sse { @@ -91,21 +115,35 @@ public fun Route.mcp(block: ServerSSESession.() -> Server) { * Automatically installs [ContentNegotiation][io.ktor.server.plugins.contentnegotiation.ContentNegotiation] * with [McpJson][io.modelcontextprotocol.kotlin.sdk.types.McpJson] and [SSE]. * + * @param enableDnsRebindingProtection whether to install [DnsRebindingProtection] on this route. Defaults to `true`. + * @param allowedHosts hostnames allowed in the `Host` header. Defaults to `localhost`, `127.0.0.1`, `[::1]`. + * @param allowedOrigins origins allowed in the `Origin` header, compared by hostname only + * (scheme and port are ignored). Requests without an `Origin` header are allowed. + * Pass `null` to skip origin validation. * @param block factory block with access to the [ServerSSESession] * that creates and returns the [Server] to handle the connection. */ @KtorDsl -public fun Application.mcp(block: ServerSSESession.() -> Server) { +public fun Application.mcp( + enableDnsRebindingProtection: Boolean = true, + allowedHosts: List? = null, + allowedOrigins: List? = null, + block: ServerSSESession.() -> Server, +) { installMcpContentNegotiation() install(SSE) routing { - mcp(block) + mcp(enableDnsRebindingProtection, allowedHosts, allowedOrigins, block) } } +@Suppress("LongParameterList") private fun Application.mcpStreamableHttp( path: String = "/mcp", + enableDnsRebindingProtection: Boolean, + allowedHosts: List?, + allowedOrigins: List?, configuration: StreamableHttpServerTransport.Configuration, block: RoutingContext.() -> Server, ) { @@ -116,6 +154,8 @@ private fun Application.mcpStreamableHttp( routing { route(path) { + installDnsRebindingProtection(enableDnsRebindingProtection, allowedHosts, allowedOrigins) + // Set Mcp-Session-Id on GET responses before Ktor's sse {} commits headers. intercept(ApplicationCallPipeline.Plugins) { if (context.request.httpMethod == HttpMethod.Get) { @@ -160,10 +200,12 @@ private fun Application.mcpStreamableHttp( * with [McpJson][io.modelcontextprotocol.kotlin.sdk.types.McpJson] and [SSE]. * * @param path The base path for the MCP Streamable HTTP endpoint. Defaults to "/mcp". - * @param enableDnsRebindingProtection Enables DNS rebinding attack protection for the endpoint. Defaults to false. - * @param allowedHosts A list of hostnames allowed to access the endpoint. If `null`, no restrictions are applied. - * @param allowedOrigins A list of origins allowed to perform cross-origin requests (CORS). - * If `null`, no restrictions are applied. + * @param enableDnsRebindingProtection Enables DNS rebinding attack protection for the endpoint. Defaults to `true`. + * @param allowedHosts A list of hostnames allowed to access the endpoint. + * If `null` and DNS rebinding protection is enabled, defaults to `localhost`, `127.0.0.1`, `[::1]`. + * @param allowedOrigins A list of allowed `Origin` header values, compared by hostname only + * (scheme and port are ignored). Requests without an `Origin` header are allowed. + * If `null`, origin validation is disabled. * @param eventStore An optional [EventStore] instance to enable resumable event stream functionality. * Allows storing and replaying events. * @param block factory block with access to the [RoutingContext] (for reading request headers) @@ -172,7 +214,7 @@ private fun Application.mcpStreamableHttp( @KtorDsl public fun Application.mcpStreamableHttp( path: String = "/mcp", - enableDnsRebindingProtection: Boolean = false, + enableDnsRebindingProtection: Boolean = true, allowedHosts: List? = null, allowedOrigins: List? = null, eventStore: EventStore? = null, @@ -180,10 +222,10 @@ public fun Application.mcpStreamableHttp( ) { mcpStreamableHttp( path = path, + enableDnsRebindingProtection = enableDnsRebindingProtection, + allowedHosts = allowedHosts, + allowedOrigins = allowedOrigins, configuration = StreamableHttpServerTransport.Configuration( - enableDnsRebindingProtection = enableDnsRebindingProtection, - allowedHosts = allowedHosts, - allowedOrigins = allowedOrigins, eventStore = eventStore, enableJsonResponse = true, ), @@ -191,8 +233,12 @@ public fun Application.mcpStreamableHttp( ) } +@Suppress("LongParameterList") private fun Application.mcpStatelessStreamableHttp( path: String = "/mcp", + enableDnsRebindingProtection: Boolean, + allowedHosts: List?, + allowedOrigins: List?, configuration: StreamableHttpServerTransport.Configuration, block: RoutingContext.() -> Server, ) { @@ -201,6 +247,8 @@ private fun Application.mcpStatelessStreamableHttp( routing { route(path) { + installDnsRebindingProtection(enableDnsRebindingProtection, allowedHosts, allowedOrigins) + post { mcpStatelessStreamableHttpEndpoint( configuration = configuration, @@ -236,9 +284,12 @@ private fun Application.mcpStatelessStreamableHttp( * with [McpJson][io.modelcontextprotocol.kotlin.sdk.types.McpJson] and [SSE]. * * @param path The URL path where the server listens for incoming JSON-RPC requests. Defaults to "/mcp". - * @param enableDnsRebindingProtection Determines whether DNS rebinding protection is enabled. Defaults to `false`. - * @param allowedHosts A list of allowed hostnames. If null, host filtering is disabled. - * @param allowedOrigins A list of allowed origins for CORS. If null, origin filtering is disabled. + * @param enableDnsRebindingProtection Determines whether DNS rebinding protection is enabled. Defaults to `true`. + * @param allowedHosts A list of allowed hostnames. If `null` and DNS rebinding protection is enabled, + * defaults to `localhost`, `127.0.0.1`, `[::1]`. + * @param allowedOrigins A list of allowed `Origin` header values, compared by hostname only + * (scheme and port are ignored). Requests without an `Origin` header are allowed. + * If `null`, origin validation is disabled. * @param eventStore An optional [EventStore] implementation to provide resumability and event replay support. * @param block factory block with access to the [RoutingContext] (for reading request headers) * that creates and returns the [Server] to handle the connection. @@ -246,7 +297,7 @@ private fun Application.mcpStatelessStreamableHttp( @KtorDsl public fun Application.mcpStatelessStreamableHttp( path: String = "/mcp", - enableDnsRebindingProtection: Boolean = false, + enableDnsRebindingProtection: Boolean = true, allowedHosts: List? = null, allowedOrigins: List? = null, eventStore: EventStore? = null, @@ -254,10 +305,10 @@ public fun Application.mcpStatelessStreamableHttp( ) { mcpStatelessStreamableHttp( path = path, + enableDnsRebindingProtection = enableDnsRebindingProtection, + allowedHosts = allowedHosts, + allowedOrigins = allowedOrigins, configuration = StreamableHttpServerTransport.Configuration( - enableDnsRebindingProtection = enableDnsRebindingProtection, - allowedHosts = allowedHosts, - allowedOrigins = allowedOrigins, eventStore = eventStore, enableJsonResponse = true, ), @@ -395,3 +446,11 @@ private suspend fun RoutingContext.streamableTransport( return transport } + +private fun Route.installDnsRebindingProtection(enabled: Boolean, hosts: List?, origins: List?) { + if (!enabled) return + install(DnsRebindingProtection) { + allowedHosts = hosts ?: LOCALHOST_ALLOWED_HOSTS + origins?.let { allowedOrigins = it } + } +} diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt index 870a92c..ae8a39d 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt @@ -136,8 +136,20 @@ public class StreamableHttpServerTransport(private val configuration: Configurat */ public class Configuration( public val enableJsonResponse: Boolean = false, + @Deprecated( + message = "Use install(DnsRebindingProtection) on your Ktor route instead", + level = DeprecationLevel.WARNING, + ) public val enableDnsRebindingProtection: Boolean = false, + @Deprecated( + message = "Use install(DnsRebindingProtection) on your Ktor route instead", + level = DeprecationLevel.WARNING, + ) public val allowedHosts: List? = null, + @Deprecated( + message = "Use install(DnsRebindingProtection) on your Ktor route instead", + level = DeprecationLevel.WARNING, + ) public val allowedOrigins: List? = null, public val eventStore: EventStore? = null, public val retryInterval: Duration? = null, @@ -300,7 +312,12 @@ public class StreamableHttpServerTransport(private val configuration: Configurat } /** - * Handles an incoming HTTP request, whether GET, POST or DELETE + * Handles an incoming HTTP request, whether GET, POST or DELETE. + * + * **Note:** this method does not perform DNS rebinding protection on its own. + * When using this transport outside the [mcpStreamableHttp] / [mcpStatelessStreamableHttp] + * DSL, install the [DnsRebindingProtection] plugin on your Ktor route + * (or apply equivalent middleware) to validate `Host` / `Origin` headers. */ public suspend fun handleRequest(session: ServerSSESession?, call: ApplicationCall) { validateHeaders(call)?.let { reason -> @@ -651,10 +668,13 @@ public class StreamableHttpServerTransport(private val configuration: Configurat if (!configuration.enableDnsRebindingProtection) return null configuration.allowedHosts?.let { hosts -> - val hostHeader = call.request.headers[HttpHeaders.Host]?.lowercase() - val allowedHostsLowercase = hosts.map { it.lowercase() } + val hostHeader = call.request.headers[HttpHeaders.Host] + val hostname = hostHeader?.let { extractHostname(it) }?.lowercase() + val allowedHostsLowercase = hosts.map { + extractHostname(it)?.lowercase() ?: it.lowercase() + } - if (hostHeader == null || hostHeader !in allowedHostsLowercase) { + if (hostname == null || hostname !in allowedHostsLowercase) { return "Invalid Host header: $hostHeader" } } @@ -663,7 +683,8 @@ public class StreamableHttpServerTransport(private val configuration: Configurat val originHeader = call.request.headers[HttpHeaders.Origin]?.lowercase() val allowedOriginsLowercase = origins.map { it.lowercase() } - if (originHeader == null || originHeader !in allowedOriginsLowercase) { + // Allow requests without Origin (non-browser clients cannot perform DNS rebinding) + if (originHeader != null && originHeader !in allowedOriginsLowercase) { return "Invalid Origin header: $originHeader" } } diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/DnsRebindingProtectionTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/DnsRebindingProtectionTest.kt new file mode 100644 index 0000000..96c44ef --- /dev/null +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/DnsRebindingProtectionTest.kt @@ -0,0 +1,382 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.kotest.assertions.ktor.client.shouldHaveStatus +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain +import io.kotest.matchers.string.shouldNotContain +import io.ktor.client.request.header +import io.ktor.client.request.post +import io.ktor.client.statement.bodyAsText +import io.ktor.http.ContentType +import io.ktor.http.HttpHeaders +import io.ktor.http.HttpStatusCode +import io.ktor.http.contentType +import io.ktor.server.application.install +import io.ktor.server.response.respondText +import io.ktor.server.routing.post +import io.ktor.server.routing.route +import io.ktor.server.routing.routing +import io.ktor.server.sse.SSE +import io.ktor.server.testing.ApplicationTestBuilder +import io.ktor.server.testing.testApplication +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import org.junit.jupiter.params.provider.ValueSource +import kotlin.test.Test + +class DnsRebindingProtectionTest { + + companion object { + @JvmStatic + fun invalidHostCases(): List = listOf( + Arguments.of("", "Invalid Host header: (malformed or missing)"), + Arguments.of("evil.com", "Invalid Host: evil.com"), + ) + + @JvmStatic + fun extractHostnameAcceptCases(): List = listOf( + Arguments.of("localhost", "localhost"), + Arguments.of("localhost:3000", "localhost"), + Arguments.of("127.0.0.1:8080", "127.0.0.1"), + Arguments.of("[::1]", "[::1]"), + Arguments.of("[::1]:3000", "[::1]"), + Arguments.of("localhost:", "localhost"), + Arguments.of("[::1]:", "[::1]"), + ) + + @JvmStatic + fun extractHostnameRejectCases(): List = listOf( + "", // empty + "evil.com@localhost", // userinfo + "evil.com/path", // path + "evil.com?q=1", // query + "evil.com#frag", // fragment + "[::1", // malformed IPv6 + "[]", // empty brackets + "[]:3000", // empty brackets + port + " localhost", // leading whitespace + "localhost ", // trailing whitespace + "localhost\t:80", // embedded tab + "localhost:abc", // non-numeric port + "localhost:-1", // negative port + "[::1]:abc", // non-numeric IPv6 port + ":80", // leading colon + ) + + @JvmStatic + fun extractOriginHostAcceptCases(): List = listOf( + Arguments.of("http://example.com", "example.com"), + Arguments.of("http://example.com:8080", "example.com"), + Arguments.of("https://Example.COM", "Example.COM"), + Arguments.of("https://example.com", "example.com"), + ) + + @JvmStatic + fun extractOriginHostRejectCases(): List = listOf( + "example.com", // no scheme + "not-a-url", // unparseable + "", // empty + ) + } + + private fun testWithPlugin( + config: DnsRebindingProtectionConfig.() -> Unit = {}, + test: suspend ApplicationTestBuilder.() -> Unit, + ): Unit = testApplication { + application { + install(SSE) + routing { + route("/mcp") { + install(DnsRebindingProtection, config) + post { call.respondText("ok") } + } + } + } + test() + } + + @ParameterizedTest + @ValueSource(strings = ["localhost", "127.0.0.1", "[::1]", "localhost:3000", "[::1]:8080"]) + fun `plugin accepts valid Host header`(hostHeader: String) = testWithPlugin { + val response = client.post("/mcp") { + header(HttpHeaders.Host, hostHeader) + } + response.shouldHaveStatus(HttpStatusCode.OK) + response.bodyAsText() shouldBe "ok" + } + + @ParameterizedTest + @MethodSource("invalidHostCases") + fun `plugin rejects invalid Host header`(hostHeader: String, expectedBody: String) = testWithPlugin { + val response = client.post("/mcp") { + header(HttpHeaders.Host, hostHeader) + } + response.shouldHaveStatus(HttpStatusCode.Forbidden) + response.bodyAsText() shouldContain expectedBody + } + + @Test + fun `plugin does not echo raw malformed Host in rejection`() = testWithPlugin { + val response = client.post("/mcp") { + header(HttpHeaders.Host, "evil.com@localhost") + } + response.shouldHaveStatus(HttpStatusCode.Forbidden) + response.bodyAsText() shouldNotContain "evil.com@localhost" + response.bodyAsText() shouldContain "malformed or missing" + } + + @Test + fun `plugin allows request without Origin header`() = testWithPlugin( + config = { allowedOrigins = listOf("http://localhost:3000") }, + ) { + val response = client.post("/mcp") { + header(HttpHeaders.Host, "localhost") + } + response.shouldHaveStatus(HttpStatusCode.OK) + response.bodyAsText() shouldBe "ok" + } + + @Test + fun `plugin rejects disallowed Origin`() = testWithPlugin( + config = { allowedOrigins = listOf("http://localhost:3000") }, + ) { + val response = client.post("/mcp") { + header(HttpHeaders.Host, "localhost") + header(HttpHeaders.Origin, "http://evil.com") + } + response.shouldHaveStatus(HttpStatusCode.Forbidden) + response.bodyAsText() shouldContain "Invalid Origin host: evil.com" + } + + @Test + fun `plugin allows matching Origin`() = testWithPlugin( + config = { allowedOrigins = listOf("http://localhost:3000") }, + ) { + val response = client.post("/mcp") { + header(HttpHeaders.Host, "localhost") + header(HttpHeaders.Origin, "http://localhost:3000") + } + response.shouldHaveStatus(HttpStatusCode.OK) + response.bodyAsText() shouldBe "ok" + } + + @Test + fun `plugin allows Origin with different port but same hostname`() = testWithPlugin( + config = { allowedOrigins = listOf("http://localhost:3000") }, + ) { + val response = client.post("/mcp") { + header(HttpHeaders.Host, "localhost") + header(HttpHeaders.Origin, "http://localhost:9999") + } + response.shouldHaveStatus(HttpStatusCode.OK) + response.bodyAsText() shouldBe "ok" + } + + @Test + fun `plugin allows Origin with different scheme but same hostname`() = testWithPlugin( + config = { allowedOrigins = listOf("http://localhost:3000") }, + ) { + val response = client.post("/mcp") { + header(HttpHeaders.Host, "localhost") + header(HttpHeaders.Origin, "https://localhost:3000") + } + response.shouldHaveStatus(HttpStatusCode.OK) + response.bodyAsText() shouldBe "ok" + } + + @Test + fun `plugin rejects unparseable Origin header`() = testWithPlugin( + config = { allowedOrigins = listOf("http://localhost:3000") }, + ) { + val response = client.post("/mcp") { + header(HttpHeaders.Host, "localhost") + header(HttpHeaders.Origin, "not-a-url") + } + response.shouldHaveStatus(HttpStatusCode.Forbidden) + response.bodyAsText() shouldContain "Invalid Origin header: (unparseable)" + } + + @Test + fun `plugin with custom allowedHosts accepts matching host`() = testWithPlugin( + config = { allowedHosts = listOf("myapp.com") }, + ) { + val response = client.post("/mcp") { + header(HttpHeaders.Host, "myapp.com:443") + } + response.shouldHaveStatus(HttpStatusCode.OK) + response.bodyAsText() shouldBe "ok" + } + + @Test + fun `plugin with custom allowedHosts rejects non-matching host`() = testWithPlugin( + config = { allowedHosts = listOf("myapp.com") }, + ) { + val response = client.post("/mcp") { + header(HttpHeaders.Host, "localhost") + } + response.shouldHaveStatus(HttpStatusCode.Forbidden) + } + + @Test + fun `host validation is case insensitive`() = testWithPlugin( + config = { allowedHosts = listOf("MyApp.COM") }, + ) { + val response = client.post("/mcp") { + header(HttpHeaders.Host, "myapp.com") + } + response.shouldHaveStatus(HttpStatusCode.OK) + response.bodyAsText() shouldBe "ok" + } + + @Test + fun `origin validation is case insensitive`() = testWithPlugin( + config = { + allowedHosts = listOf("localhost") + allowedOrigins = listOf("https://MyApp.COM") + }, + ) { + val response = client.post("/mcp") { + header(HttpHeaders.Host, "localhost") + header(HttpHeaders.Origin, "https://myapp.com") + } + response.shouldHaveStatus(HttpStatusCode.OK) + response.bodyAsText() shouldBe "ok" + } + + @Test + fun `plugin with empty allowedHosts rejects all requests`() = testWithPlugin( + config = { allowedHosts = emptyList() }, + ) { + val localhostResponse = client.post("/mcp") { + header(HttpHeaders.Host, "localhost") + } + localhostResponse.shouldHaveStatus(HttpStatusCode.Forbidden) + localhostResponse.bodyAsText() shouldContain "Invalid Host: localhost" + + val otherResponse = client.post("/mcp") { + header(HttpHeaders.Host, "myapp.com") + } + otherResponse.shouldHaveStatus(HttpStatusCode.Forbidden) + otherResponse.bodyAsText() shouldContain "Invalid Host: myapp.com" + } + + @Test + fun `plugin fails to install when allowedHosts contains an invalid host`() { + val ex = shouldThrow { + testWithPlugin( + config = { allowedHosts = listOf("https://example.com") }, + ) { + client.post("/mcp") { header(HttpHeaders.Host, "example.com") } + } + } + ex.message shouldContain "https://example.com" + } + + @Test + fun `plugin fails to install when allowedOrigins contains an invalid origin`() { + val ex = shouldThrow { + testWithPlugin( + config = { allowedOrigins = listOf("not-a-url") }, + ) { + client.post("/mcp") { header(HttpHeaders.Host, "localhost") } + } + } + ex.message shouldContain "not-a-url" + } + + @Test + fun `Route mcp with DNS protection enabled rejects non-localhost`() = testApplication { + application { + install(SSE) + routing { + mcp { testServer() } + } + } + + val response = client.post("/") { + header(HttpHeaders.Host, "evil.com") + contentType(ContentType.Application.Json) + } + response.shouldHaveStatus(HttpStatusCode.Forbidden) + } + + @Test + fun `Route mcp with DNS protection disabled allows any host`() = testApplication { + application { + install(SSE) + routing { + mcp(enableDnsRebindingProtection = false) { testServer() } + } + } + + val response = client.post("/") { + header(HttpHeaders.Host, "evil.com") + contentType(ContentType.Application.Json) + } + // Not 403 — the request reaches the handler (may get 400 for missing sessionId, etc.) + response.shouldHaveStatus(HttpStatusCode.BadRequest) + } + + @Test + fun `mcpStreamableHttp rejects non-localhost Host by default`() = testApplication { + application { + mcpStreamableHttp { testServer() } + } + + val response = client.post("/mcp") { + header(HttpHeaders.Host, "evil.com") + contentType(ContentType.Application.Json) + } + response.shouldHaveStatus(HttpStatusCode.Forbidden) + } + + @Test + fun `mcpStatelessStreamableHttp rejects non-localhost Host by default`() = testApplication { + application { + mcpStatelessStreamableHttp { testServer() } + } + + val response = client.post("/mcp") { + header(HttpHeaders.Host, "evil.com") + contentType(ContentType.Application.Json) + } + response.shouldHaveStatus(HttpStatusCode.Forbidden) + } + + // -- extractHostname unit tests -- + + @ParameterizedTest + @MethodSource("extractHostnameAcceptCases") + fun `extractHostname accepts valid host`(input: String, expected: String) { + extractHostname(input) shouldBe expected + } + + @ParameterizedTest + @MethodSource("extractHostnameRejectCases") + fun `extractHostname rejects invalid host`(input: String) { + extractHostname(input) shouldBe null + } + + // -- extractOriginHost unit tests -- + + @ParameterizedTest + @MethodSource("extractOriginHostAcceptCases") + fun `extractOriginHost extracts hostname`(input: String, expected: String) { + extractOriginHost(input) shouldBe expected + } + + @ParameterizedTest + @MethodSource("extractOriginHostRejectCases") + fun `extractOriginHost returns null for invalid origin`(input: String) { + extractOriginHost(input) shouldBe null + } + + private fun testServer(): Server = Server( + serverInfo = Implementation(name = "test-server", version = "1.0.0"), + options = ServerOptions(capabilities = ServerCapabilities()), + ) +} diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorApplicationExtensionsTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorApplicationExtensionsTest.kt index 05200e5..9888867 100644 --- a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorApplicationExtensionsTest.kt +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorApplicationExtensionsTest.kt @@ -24,9 +24,9 @@ class KtorApplicationExtensionsTest : AbstractKtorExtensionsTest() { * added to the same application. */ @Test - fun `Application mcp should installs SSE and coexist with other routes`() = testApplication { + fun `Application mcp should install SSE and coexist with other routes`() = testApplication { application { - mcp { testServer() } + mcp(enableDnsRebindingProtection = false) { testServer() } routing { get("/health") { call.respondText("healthy") } diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorRouteExtensionsTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorRouteExtensionsTest.kt index 5ba4448..d6b95b4 100644 --- a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorRouteExtensionsTest.kt +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorRouteExtensionsTest.kt @@ -61,7 +61,7 @@ class KtorRouteExtensionsTest : AbstractKtorExtensionsTest() { route("/api/mcp") { get("/test") { call.respondText("test-endpoint") } - mcp { testServer() } + mcp(enableDnsRebindingProtection = false) { testServer() } } } } @@ -91,7 +91,7 @@ class KtorRouteExtensionsTest : AbstractKtorExtensionsTest() { route("/v1") { route("/services") { route("/mcp") { - mcp { testServer() } + mcp(enableDnsRebindingProtection = false) { testServer() } } } } @@ -112,7 +112,7 @@ class KtorRouteExtensionsTest : AbstractKtorExtensionsTest() { routing { route("/api") { - mcp("/mcp-endpoint") { testServer() } + mcp("/mcp-endpoint", enableDnsRebindingProtection = false) { testServer() } } } } diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt index 8b413d8..f6dda15 100644 --- a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt @@ -477,6 +477,7 @@ class StreamableHttpServerTransportTest { // Step 1: Initialize session via POST val initResponse = client.post(mcpPath) { + header(HttpHeaders.Host, "localhost") addStreamableHeaders() setBody(buildInitializeRequestPayload()) } @@ -485,6 +486,7 @@ class StreamableHttpServerTransportTest { // Step 2: Open first GET SSE stream client.prepareGet(mcpPath) { + header(HttpHeaders.Host, "localhost") header(HttpHeaders.Accept, ContentType.Text.EventStream.toString()) header(MCP_SESSION_ID_HEADER, sessionId) header("mcp-protocol-version", LATEST_PROTOCOL_VERSION) @@ -495,6 +497,7 @@ class StreamableHttpServerTransportTest { // Step 3: Open a second GET — the transport closes the old session // and the new stream takes over. client.prepareGet(mcpPath) { + header(HttpHeaders.Host, "localhost") header(HttpHeaders.Accept, ContentType.Text.EventStream.toString()) header(MCP_SESSION_ID_HEADER, sessionId) header("mcp-protocol-version", LATEST_PROTOCOL_VERSION) @@ -528,6 +531,7 @@ class StreamableHttpServerTransportTest { // Step 1: Initialize session via POST val initResponse = client.post(mcpPath) { + header(HttpHeaders.Host, "localhost") addStreamableHeaders() setBody(buildInitializeRequestPayload()) } @@ -536,6 +540,7 @@ class StreamableHttpServerTransportTest { // Step 2: Open and then close a GET SSE stream client.prepareGet(mcpPath) { + header(HttpHeaders.Host, "localhost") header(HttpHeaders.Accept, ContentType.Text.EventStream.toString()) header(MCP_SESSION_ID_HEADER, sessionId) header("mcp-protocol-version", LATEST_PROTOCOL_VERSION) @@ -547,6 +552,7 @@ class StreamableHttpServerTransportTest { // Step 3: Immediately reconnect — the transport should close the stale // stream and allow the new one. client.prepareGet(mcpPath) { + header(HttpHeaders.Host, "localhost") header(HttpHeaders.Accept, ContentType.Text.EventStream.toString()) header(MCP_SESSION_ID_HEADER, sessionId) header("mcp-protocol-version", LATEST_PROTOCOL_VERSION) @@ -578,6 +584,7 @@ class StreamableHttpServerTransportTest { // Step 1: Initialize session via POST val initResponse = client.post(mcpPath) { + header(HttpHeaders.Host, "localhost") addStreamableHeaders() setBody(buildInitializeRequestPayload()) } @@ -586,6 +593,7 @@ class StreamableHttpServerTransportTest { // Step 2: Open GET SSE stream with session ID client.prepareGet(mcpPath) { + header(HttpHeaders.Host, "localhost") header(HttpHeaders.Accept, ContentType.Text.EventStream.toString()) header(MCP_SESSION_ID_HEADER, sessionId) header("mcp-protocol-version", LATEST_PROTOCOL_VERSION) From 49e1072c66a34d4af9100264abb728881bd41aac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 11:34:35 +0200 Subject: [PATCH 273/449] chore(deps): bump the infrastructure group across 1 directory with 2 updates (#752) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the infrastructure group with 2 updates in the / directory: [io.github.oshai:kotlin-logging](https://github.com/oshai/kotlin-logging) and [io.netty:netty-bom](https://github.com/netty/netty). Updates `io.github.oshai:kotlin-logging` from 8.0.01 to 8.0.02
Release notes

Sourced from io.github.oshai:kotlin-logging's releases.

8.0.02

What's Changed

Full Changelog: https://github.com/oshai/kotlin-logging/compare/8.0.01...8.0.02

Commits
  • accd3ca bump version to 8.0.03
  • 2bd058a bump version to 8.0.02
  • 8ab1a8a Fix GraalVM native build error caused by stale substitution target in v8.x (#...
  • See full diff in compare view

Updates `io.netty:netty-bom` from 4.2.12.Final to 4.2.13.Final
Release notes

Sourced from io.netty:netty-bom's releases.

netty-4.2.13.Final

CVEs Fixed

  • CVE-2026-42586 (netty-codec-redis)
  • CVE-2026-42578 (netty-handler-proxy)
  • CVE-2026-42577 (netty-transport-native-epoll)
  • CVE-2026-42587 (netty-codec-http, netty-codec-http2)
  • CVE-2026-41417 (netty-codec-http)
  • CVE-2026-42581 (netty-codec-http)
  • CVE-2026-42580 (netty-codec-http)
  • CVE-2026-42585 (netty-codec-http)
  • CVE-2026-42579 (netty-codec-dns)
  • CVE-2026-42582 (netty-codec-http3)
  • CVE-2026-42583 (netty-codec, netty-codec-compression)
  • CVE-2026-42584 (netty-codec-http)
  • CVE-2026-XXXXX (netty-codec-mqtt)

What's Changed

... (truncated)

Commits
  • b3844c8 [maven-release-plugin] prepare release netty-4.2.13.Final
  • 82f47fa Merge commit from fork
  • ada0999 Merge commit from fork
  • b4051e2 Fix BrotliDecoder not forwarding all decompressed chunks
  • 67207c1 Merge commit from fork
  • 541ca7c Merge commit from fork
  • 943edb3 Fix codec-dns tests
  • 6459a28 Merge commit from fork
  • b4ba61b Fix checkstyle in HttpObjectDecoder
  • 977661f Merge commit from fork
  • Additional commits viewable in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 360a468..63a75c5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,7 +9,7 @@ kover = "0.9.8" ktlint = "14.2.0" knit = "0.5.1" mavenPublish = "0.36.0" -netty = "4.2.12.Final" +netty = "4.2.13.Final" # libraries version awaitility = "4.3.0" @@ -18,7 +18,7 @@ coroutines = "1.10.2" kotest = "6.1.11" kotlinx-io = "0.9.0" ktor = "3.3.3" -logging = "8.0.01" +logging = "8.0.02" mockk = "1.14.9" mokksy = "0.8.1" serialization = "1.11.0" From c1210009a5d3b9d5c89d21684f7d5cfd3ebbb17e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 11:53:25 +0200 Subject: [PATCH 274/449] chore(deps): bump io.modelcontextprotocol:kotlin-sdk-client from 0.11.1 to 0.12.0 in /samples/kotlin-mcp-client (#743) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [io.modelcontextprotocol:kotlin-sdk-client](https://github.com/modelcontextprotocol/kotlin-sdk) from 0.11.1 to 0.12.0.
Release notes

Sourced from io.modelcontextprotocol:kotlin-sdk-client's releases.

0.12.0

Description

Adds sampling-with-tools per SEP-1577, JSON Schema dialect declaration on tool schemas, server-side tool name validation, and capability extensions; fixes SSE reconnect on the same session and stops swallowing CancellationException.

Breaking Changes

Sampling messages can carry tool calls and results (SEP-1577) by @​devcrocod in #718

SamplingMessage.content is now typed as a new SamplingMessageContent supertype, with MediaContent (text/image/audio), ToolUseContent, and ToolResultContent as variants. MediaContent still exists and now extends SamplingMessageContent, so most source code that constructs sampling messages compiles unchanged — but the binary signatures of SamplingMessage, CreateMessageResult, and related types have changed. ClientCapabilities.sampling is also now a typed Sampling object instead of a raw JsonObject. Recompilation is required; consumers reading sampling as JsonObject must migrate to the typed accessors.

watchosX64 and tvosX64 Kotlin/Native targets removed by @​devcrocod in #727

These targets have been deprecated upstream. Migrate to the corresponding ARM/simulator targets (watchosArm64, watchosSimulatorArm64, tvosArm64, tvosSimulatorArm64).

Features

  • Add $schema field to ToolSchema, declaring JSON Schema 2020-12 as the default dialect (SEP-1613) by @​devcrocod in #696
  • Validate tool names at registration time on the server, following the MCP tool naming standard (SEP-986) by @​devcrocod in #695
  • Add extensions field to ClientCapabilities and ServerCapabilities for advertising supported MCP extensions during the initialize handshake by @​eritscher in #678

Fixed

  • Validate the mcp-protocol-version HTTP header on initialization requests and correct DEFAULT_NEGOTIATED_PROTOCOL_VERSION to match the spec by @​devcrocod in #697
  • Propagate CancellationException instead of logging it as an error by @​devcrocod in #706
  • Evict stale GET SSE stream mapping on reconnect so a new GET on the same session is no longer silently rejected by the leftover entry from the previous stream by @​amr in #716

Maintenance

  • Add kotlinlang MCP server sample with search and get_page tools by @​devcrocod in #713
  • Migrate detekt to a buildSrc convention plugin with a fully explicit, version-locked YAML config by @​devcrocod in #729
  • Add Streamable HTTP integration tests for pagination, bad-request handling, and logging by @​jskjw157 in #613
  • Remove unused OpenAPI Generator plugin and configuration by @​devcrocod in #683
  • Remove Codecov configuration in favor of kover by @​devcrocod in #691
  • Remove redundant JUnit XML report configuration by @​devcrocod in #728
  • Remove IDEA files tied to the incompatible detekt IDE plugin by @​devcrocod in #714
  • Add Node.js setup to the release workflow so TypeScript integration tests run on macOS runners by @​devcrocod in #684

Dependencies

  • Kotlin to v2.3.21 in #704, #723
  • kotlinx.serialization-json to v1.11.0 in #687

New Contributors

Full Changelog: https://github.com/modelcontextprotocol/kotlin-sdk/compare/0.11.1...0.12.0

Commits
  • c339d8c release: bump version to 0.12.0 (#731)
  • 64804a0 refactor: explicit locked detekt config (#729)
  • a238fc0 fix(server): evict stale GET SSE stream mapping on reconnect (#716)
  • 66d9efb test(streamable-http): add missing integration tests for pagination, bad requ...
  • f434fca feat!: SEP-1577 sampling with tools (#718)
  • 7474a33 chore: drop deprecated watchosX64 and tvosX64 targets (#727)
  • 12ace76 chore: remove redundant JUnit XML report configuration (#728)
  • ec606db chore(deps): bump kotlin from 2.2.21 to 2.3.21 in /samples/weather-stdio-serv...
  • 1d93042 chore(deps): bump kotlin from 2.2.21 to 2.3.21 in /samples/kotlin-mcp-server ...
  • 9817b17 chore(deps): bump org.jetbrains.kotlin.jvm from 2.2.21 to 2.3.21 in /samples/...
  • Additional commits viewable in compare view

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-client/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index 69ebdde..dd1ec3f 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -2,7 +2,7 @@ anthropic = "2.27.0" kotlin = "2.3.21" ktor = "3.3.3" -mcp-kotlin = "0.11.1" +mcp-kotlin = "0.12.0" shadow = "9.4.1" slf4j = "2.0.17" From 9561624ba999dae728d38aaf4f4076e21e218557 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 10:13:00 +0000 Subject: [PATCH 275/449] chore(deps): bump mcp-kotlin from 0.11.1 to 0.12.0 in /samples/kotlin-mcp-server (#744) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `mcp-kotlin` from 0.11.1 to 0.12.0. Updates `io.modelcontextprotocol:kotlin-sdk-client` from 0.11.1 to 0.12.0
Release notes

Sourced from io.modelcontextprotocol:kotlin-sdk-client's releases.

0.12.0

Description

Adds sampling-with-tools per SEP-1577, JSON Schema dialect declaration on tool schemas, server-side tool name validation, and capability extensions; fixes SSE reconnect on the same session and stops swallowing CancellationException.

Breaking Changes

Sampling messages can carry tool calls and results (SEP-1577) by @​devcrocod in #718

SamplingMessage.content is now typed as a new SamplingMessageContent supertype, with MediaContent (text/image/audio), ToolUseContent, and ToolResultContent as variants. MediaContent still exists and now extends SamplingMessageContent, so most source code that constructs sampling messages compiles unchanged — but the binary signatures of SamplingMessage, CreateMessageResult, and related types have changed. ClientCapabilities.sampling is also now a typed Sampling object instead of a raw JsonObject. Recompilation is required; consumers reading sampling as JsonObject must migrate to the typed accessors.

watchosX64 and tvosX64 Kotlin/Native targets removed by @​devcrocod in #727

These targets have been deprecated upstream. Migrate to the corresponding ARM/simulator targets (watchosArm64, watchosSimulatorArm64, tvosArm64, tvosSimulatorArm64).

Features

  • Add $schema field to ToolSchema, declaring JSON Schema 2020-12 as the default dialect (SEP-1613) by @​devcrocod in #696
  • Validate tool names at registration time on the server, following the MCP tool naming standard (SEP-986) by @​devcrocod in #695
  • Add extensions field to ClientCapabilities and ServerCapabilities for advertising supported MCP extensions during the initialize handshake by @​eritscher in #678

Fixed

  • Validate the mcp-protocol-version HTTP header on initialization requests and correct DEFAULT_NEGOTIATED_PROTOCOL_VERSION to match the spec by @​devcrocod in #697
  • Propagate CancellationException instead of logging it as an error by @​devcrocod in #706
  • Evict stale GET SSE stream mapping on reconnect so a new GET on the same session is no longer silently rejected by the leftover entry from the previous stream by @​amr in #716

Maintenance

  • Add kotlinlang MCP server sample with search and get_page tools by @​devcrocod in #713
  • Migrate detekt to a buildSrc convention plugin with a fully explicit, version-locked YAML config by @​devcrocod in #729
  • Add Streamable HTTP integration tests for pagination, bad-request handling, and logging by @​jskjw157 in #613
  • Remove unused OpenAPI Generator plugin and configuration by @​devcrocod in #683
  • Remove Codecov configuration in favor of kover by @​devcrocod in #691
  • Remove redundant JUnit XML report configuration by @​devcrocod in #728
  • Remove IDEA files tied to the incompatible detekt IDE plugin by @​devcrocod in #714
  • Add Node.js setup to the release workflow so TypeScript integration tests run on macOS runners by @​devcrocod in #684

Dependencies

  • Kotlin to v2.3.21 in #704, #723
  • kotlinx.serialization-json to v1.11.0 in #687

New Contributors

Full Changelog: https://github.com/modelcontextprotocol/kotlin-sdk/compare/0.11.1...0.12.0

Commits
  • c339d8c release: bump version to 0.12.0 (#731)
  • 64804a0 refactor: explicit locked detekt config (#729)
  • a238fc0 fix(server): evict stale GET SSE stream mapping on reconnect (#716)
  • 66d9efb test(streamable-http): add missing integration tests for pagination, bad requ...
  • f434fca feat!: SEP-1577 sampling with tools (#718)
  • 7474a33 chore: drop deprecated watchosX64 and tvosX64 targets (#727)
  • 12ace76 chore: remove redundant JUnit XML report configuration (#728)
  • ec606db chore(deps): bump kotlin from 2.2.21 to 2.3.21 in /samples/weather-stdio-serv...
  • 1d93042 chore(deps): bump kotlin from 2.2.21 to 2.3.21 in /samples/kotlin-mcp-server ...
  • 9817b17 chore(deps): bump org.jetbrains.kotlin.jvm from 2.2.21 to 2.3.21 in /samples/...
  • Additional commits viewable in compare view

Updates `io.modelcontextprotocol:kotlin-sdk-server` from 0.11.1 to 0.12.0
Release notes

Sourced from io.modelcontextprotocol:kotlin-sdk-server's releases.

0.12.0

Description

Adds sampling-with-tools per SEP-1577, JSON Schema dialect declaration on tool schemas, server-side tool name validation, and capability extensions; fixes SSE reconnect on the same session and stops swallowing CancellationException.

Breaking Changes

Sampling messages can carry tool calls and results (SEP-1577) by @​devcrocod in #718

SamplingMessage.content is now typed as a new SamplingMessageContent supertype, with MediaContent (text/image/audio), ToolUseContent, and ToolResultContent as variants. MediaContent still exists and now extends SamplingMessageContent, so most source code that constructs sampling messages compiles unchanged — but the binary signatures of SamplingMessage, CreateMessageResult, and related types have changed. ClientCapabilities.sampling is also now a typed Sampling object instead of a raw JsonObject. Recompilation is required; consumers reading sampling as JsonObject must migrate to the typed accessors.

watchosX64 and tvosX64 Kotlin/Native targets removed by @​devcrocod in #727

These targets have been deprecated upstream. Migrate to the corresponding ARM/simulator targets (watchosArm64, watchosSimulatorArm64, tvosArm64, tvosSimulatorArm64).

Features

  • Add $schema field to ToolSchema, declaring JSON Schema 2020-12 as the default dialect (SEP-1613) by @​devcrocod in #696
  • Validate tool names at registration time on the server, following the MCP tool naming standard (SEP-986) by @​devcrocod in #695
  • Add extensions field to ClientCapabilities and ServerCapabilities for advertising supported MCP extensions during the initialize handshake by @​eritscher in #678

Fixed

  • Validate the mcp-protocol-version HTTP header on initialization requests and correct DEFAULT_NEGOTIATED_PROTOCOL_VERSION to match the spec by @​devcrocod in #697
  • Propagate CancellationException instead of logging it as an error by @​devcrocod in #706
  • Evict stale GET SSE stream mapping on reconnect so a new GET on the same session is no longer silently rejected by the leftover entry from the previous stream by @​amr in #716

Maintenance

  • Add kotlinlang MCP server sample with search and get_page tools by @​devcrocod in #713
  • Migrate detekt to a buildSrc convention plugin with a fully explicit, version-locked YAML config by @​devcrocod in #729
  • Add Streamable HTTP integration tests for pagination, bad-request handling, and logging by @​jskjw157 in #613
  • Remove unused OpenAPI Generator plugin and configuration by @​devcrocod in #683
  • Remove Codecov configuration in favor of kover by @​devcrocod in #691
  • Remove redundant JUnit XML report configuration by @​devcrocod in #728
  • Remove IDEA files tied to the incompatible detekt IDE plugin by @​devcrocod in #714
  • Add Node.js setup to the release workflow so TypeScript integration tests run on macOS runners by @​devcrocod in #684

Dependencies

  • Kotlin to v2.3.21 in #704, #723
  • kotlinx.serialization-json to v1.11.0 in #687

New Contributors

Full Changelog: https://github.com/modelcontextprotocol/kotlin-sdk/compare/0.11.1...0.12.0

Commits
  • c339d8c release: bump version to 0.12.0 (#731)
  • 64804a0 refactor: explicit locked detekt config (#729)
  • a238fc0 fix(server): evict stale GET SSE stream mapping on reconnect (#716)
  • 66d9efb test(streamable-http): add missing integration tests for pagination, bad requ...
  • f434fca feat!: SEP-1577 sampling with tools (#718)
  • 7474a33 chore: drop deprecated watchosX64 and tvosX64 targets (#727)
  • 12ace76 chore: remove redundant JUnit XML report configuration (#728)
  • ec606db chore(deps): bump kotlin from 2.2.21 to 2.3.21 in /samples/weather-stdio-serv...
  • 1d93042 chore(deps): bump kotlin from 2.2.21 to 2.3.21 in /samples/kotlin-mcp-server ...
  • 9817b17 chore(deps): bump org.jetbrains.kotlin.jvm from 2.2.21 to 2.3.21 in /samples/...
  • Additional commits viewable in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-server/gradle/libs.versions.toml b/samples/kotlin-mcp-server/gradle/libs.versions.toml index a6c07dc..2eccae2 100644 --- a/samples/kotlin-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-server/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] kotlin = "2.3.21" ktor = "3.3.3" -mcp-kotlin = "0.11.1" +mcp-kotlin = "0.12.0" slf4j = "2.0.17" shadow = "9.4.1" From 22e48089be89524dd6301fca30eec31775668711 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 10:27:26 +0000 Subject: [PATCH 276/449] chore(deps): bump mcp-kotlin from 0.11.1 to 0.12.0 in /samples/weather-stdio-server (#750) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `mcp-kotlin` from 0.11.1 to 0.12.0. Updates `io.modelcontextprotocol:kotlin-sdk-client` from 0.11.1 to 0.12.0
Release notes

Sourced from io.modelcontextprotocol:kotlin-sdk-client's releases.

0.12.0

Description

Adds sampling-with-tools per SEP-1577, JSON Schema dialect declaration on tool schemas, server-side tool name validation, and capability extensions; fixes SSE reconnect on the same session and stops swallowing CancellationException.

Breaking Changes

Sampling messages can carry tool calls and results (SEP-1577) by @​devcrocod in #718

SamplingMessage.content is now typed as a new SamplingMessageContent supertype, with MediaContent (text/image/audio), ToolUseContent, and ToolResultContent as variants. MediaContent still exists and now extends SamplingMessageContent, so most source code that constructs sampling messages compiles unchanged — but the binary signatures of SamplingMessage, CreateMessageResult, and related types have changed. ClientCapabilities.sampling is also now a typed Sampling object instead of a raw JsonObject. Recompilation is required; consumers reading sampling as JsonObject must migrate to the typed accessors.

watchosX64 and tvosX64 Kotlin/Native targets removed by @​devcrocod in #727

These targets have been deprecated upstream. Migrate to the corresponding ARM/simulator targets (watchosArm64, watchosSimulatorArm64, tvosArm64, tvosSimulatorArm64).

Features

  • Add $schema field to ToolSchema, declaring JSON Schema 2020-12 as the default dialect (SEP-1613) by @​devcrocod in #696
  • Validate tool names at registration time on the server, following the MCP tool naming standard (SEP-986) by @​devcrocod in #695
  • Add extensions field to ClientCapabilities and ServerCapabilities for advertising supported MCP extensions during the initialize handshake by @​eritscher in #678

Fixed

  • Validate the mcp-protocol-version HTTP header on initialization requests and correct DEFAULT_NEGOTIATED_PROTOCOL_VERSION to match the spec by @​devcrocod in #697
  • Propagate CancellationException instead of logging it as an error by @​devcrocod in #706
  • Evict stale GET SSE stream mapping on reconnect so a new GET on the same session is no longer silently rejected by the leftover entry from the previous stream by @​amr in #716

Maintenance

  • Add kotlinlang MCP server sample with search and get_page tools by @​devcrocod in #713
  • Migrate detekt to a buildSrc convention plugin with a fully explicit, version-locked YAML config by @​devcrocod in #729
  • Add Streamable HTTP integration tests for pagination, bad-request handling, and logging by @​jskjw157 in #613
  • Remove unused OpenAPI Generator plugin and configuration by @​devcrocod in #683
  • Remove Codecov configuration in favor of kover by @​devcrocod in #691
  • Remove redundant JUnit XML report configuration by @​devcrocod in #728
  • Remove IDEA files tied to the incompatible detekt IDE plugin by @​devcrocod in #714
  • Add Node.js setup to the release workflow so TypeScript integration tests run on macOS runners by @​devcrocod in #684

Dependencies

  • Kotlin to v2.3.21 in #704, #723
  • kotlinx.serialization-json to v1.11.0 in #687

New Contributors

Full Changelog: https://github.com/modelcontextprotocol/kotlin-sdk/compare/0.11.1...0.12.0

Commits
  • c339d8c release: bump version to 0.12.0 (#731)
  • 64804a0 refactor: explicit locked detekt config (#729)
  • a238fc0 fix(server): evict stale GET SSE stream mapping on reconnect (#716)
  • 66d9efb test(streamable-http): add missing integration tests for pagination, bad requ...
  • f434fca feat!: SEP-1577 sampling with tools (#718)
  • 7474a33 chore: drop deprecated watchosX64 and tvosX64 targets (#727)
  • 12ace76 chore: remove redundant JUnit XML report configuration (#728)
  • ec606db chore(deps): bump kotlin from 2.2.21 to 2.3.21 in /samples/weather-stdio-serv...
  • 1d93042 chore(deps): bump kotlin from 2.2.21 to 2.3.21 in /samples/kotlin-mcp-server ...
  • 9817b17 chore(deps): bump org.jetbrains.kotlin.jvm from 2.2.21 to 2.3.21 in /samples/...
  • Additional commits viewable in compare view

Updates `io.modelcontextprotocol:kotlin-sdk-server` from 0.11.1 to 0.12.0
Release notes

Sourced from io.modelcontextprotocol:kotlin-sdk-server's releases.

0.12.0

Description

Adds sampling-with-tools per SEP-1577, JSON Schema dialect declaration on tool schemas, server-side tool name validation, and capability extensions; fixes SSE reconnect on the same session and stops swallowing CancellationException.

Breaking Changes

Sampling messages can carry tool calls and results (SEP-1577) by @​devcrocod in #718

SamplingMessage.content is now typed as a new SamplingMessageContent supertype, with MediaContent (text/image/audio), ToolUseContent, and ToolResultContent as variants. MediaContent still exists and now extends SamplingMessageContent, so most source code that constructs sampling messages compiles unchanged — but the binary signatures of SamplingMessage, CreateMessageResult, and related types have changed. ClientCapabilities.sampling is also now a typed Sampling object instead of a raw JsonObject. Recompilation is required; consumers reading sampling as JsonObject must migrate to the typed accessors.

watchosX64 and tvosX64 Kotlin/Native targets removed by @​devcrocod in #727

These targets have been deprecated upstream. Migrate to the corresponding ARM/simulator targets (watchosArm64, watchosSimulatorArm64, tvosArm64, tvosSimulatorArm64).

Features

  • Add $schema field to ToolSchema, declaring JSON Schema 2020-12 as the default dialect (SEP-1613) by @​devcrocod in #696
  • Validate tool names at registration time on the server, following the MCP tool naming standard (SEP-986) by @​devcrocod in #695
  • Add extensions field to ClientCapabilities and ServerCapabilities for advertising supported MCP extensions during the initialize handshake by @​eritscher in #678

Fixed

  • Validate the mcp-protocol-version HTTP header on initialization requests and correct DEFAULT_NEGOTIATED_PROTOCOL_VERSION to match the spec by @​devcrocod in #697
  • Propagate CancellationException instead of logging it as an error by @​devcrocod in #706
  • Evict stale GET SSE stream mapping on reconnect so a new GET on the same session is no longer silently rejected by the leftover entry from the previous stream by @​amr in #716

Maintenance

  • Add kotlinlang MCP server sample with search and get_page tools by @​devcrocod in #713
  • Migrate detekt to a buildSrc convention plugin with a fully explicit, version-locked YAML config by @​devcrocod in #729
  • Add Streamable HTTP integration tests for pagination, bad-request handling, and logging by @​jskjw157 in #613
  • Remove unused OpenAPI Generator plugin and configuration by @​devcrocod in #683
  • Remove Codecov configuration in favor of kover by @​devcrocod in #691
  • Remove redundant JUnit XML report configuration by @​devcrocod in #728
  • Remove IDEA files tied to the incompatible detekt IDE plugin by @​devcrocod in #714
  • Add Node.js setup to the release workflow so TypeScript integration tests run on macOS runners by @​devcrocod in #684

Dependencies

  • Kotlin to v2.3.21 in #704, #723
  • kotlinx.serialization-json to v1.11.0 in #687

New Contributors

Full Changelog: https://github.com/modelcontextprotocol/kotlin-sdk/compare/0.11.1...0.12.0

Commits
  • c339d8c release: bump version to 0.12.0 (#731)
  • 64804a0 refactor: explicit locked detekt config (#729)
  • a238fc0 fix(server): evict stale GET SSE stream mapping on reconnect (#716)
  • 66d9efb test(streamable-http): add missing integration tests for pagination, bad requ...
  • f434fca feat!: SEP-1577 sampling with tools (#718)
  • 7474a33 chore: drop deprecated watchosX64 and tvosX64 targets (#727)
  • 12ace76 chore: remove redundant JUnit XML report configuration (#728)
  • ec606db chore(deps): bump kotlin from 2.2.21 to 2.3.21 in /samples/weather-stdio-serv...
  • 1d93042 chore(deps): bump kotlin from 2.2.21 to 2.3.21 in /samples/kotlin-mcp-server ...
  • 9817b17 chore(deps): bump org.jetbrains.kotlin.jvm from 2.2.21 to 2.3.21 in /samples/...
  • Additional commits viewable in compare view

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/weather-stdio-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/weather-stdio-server/gradle/libs.versions.toml b/samples/weather-stdio-server/gradle/libs.versions.toml index 19874f4..b294c80 100644 --- a/samples/weather-stdio-server/gradle/libs.versions.toml +++ b/samples/weather-stdio-server/gradle/libs.versions.toml @@ -3,7 +3,7 @@ coroutines = "1.10.2" kotlin = "2.3.21" ktor = "3.3.3" logging = "8.0.01" -mcp-kotlin = "0.11.1" +mcp-kotlin = "0.12.0" shadow = "9.4.1" slf4j = "2.0.17" From 3e4a5e0c1d4f508c660cff0094a30b2033eb0810 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 14:18:24 +0200 Subject: [PATCH 277/449] chore(deps): bump kotlin from 2.3.20 to 2.3.21 in /samples/kotlinlang-mcp-server (#748) Bumps `kotlin` from 2.3.20 to 2.3.21. Updates `org.jetbrains.kotlin.jvm` from 2.3.20 to 2.3.21
Release notes

Sourced from org.jetbrains.kotlin.jvm's releases.

Kotlin 2.3.21

Changelog

Backend. Wasm

  • KT-84610 [Wasm] Failed to compile klibs in IC mode

Compiler

  • KT-84566 Prevent launching Default dispatcher threads from IJ SDK in kotlin compiler
  • KT-85358 Native: roll back the workaround for KT-84678 once MapLibre has been properly fixed
  • KT-85626 @JvmRecord in commonMain breaks compileCommonMainKotlinMetadata with "Cannot access 'java.lang.Record'"
  • KT-85405 Postpone/Revert DontIgnoreUpperBoundViolatedOnImplicitArguments
  • KT-84678 K/N: Undefined symbol from SPM-added ObjC frameworks when linking iOS target
  • KT-85021 False positive SUBCLASS_CANT_CALL_COMPANION_PROTECTED_NON_STATIC error in multi-module project

JavaScript

  • KT-82395 Support top-level declarations from compiler plugins in JS incremental compilation
  • KT-84475 K/JS: false-positive exportability warnings in multi-module project
  • KT-84633 Kotlin/JS: "Serializer for class not found" error when IR output granularity is whole-program
  • KT-85047 Kotlin/JS: @JsStatic on suspend fun of class companion generates incorrect d.ts
  • KT-84517 K/JS: bad mappings data in outputted Kotlin stdlib source map

Libraries

  • KT-71848 Kotlinx.metadata: Add CompilerPluginData into Km API

Native. C and ObjC Import

  • KT-85399 Kotlin/Native: TypeCastException when casting ObjC Protocol MetaClass with genericSafeCasts enabled
  • KT-85508 K/N: TypeCastException when using nw_parameters_create_secure_tcp block parameter on 2.3.20

Tools. Gradle

  • KT-84729 Update Gradle plugin-publish version to enable configuration cache badge on Gradle plugins portal

Tools. Gradle. Compiler plugins

  • KT-85257 AGP/Compose: MergeMappingFileTask clears R8 artifacts due to @OutputDirectory annotation on AGP 9.1+

Tools. Scripts

  • KT-85105 Scripts: JVM backend internal error (IR lowering) when scratch file contains anonymous object
  • KT-85103 Exception while generating code when explain destructuring decls
  • KT-84842 scriptCompilationClasspathFromContext behavior changed from 2.3.10 to 2.3.20
  • KT-85029 Kotlin Scripting: ScriptDiagnostic reports "at null" instead of error location

Tools. Statistics (FUS)

... (truncated)

Changelog

Sourced from org.jetbrains.kotlin.jvm's changelog.

2.3.21

Backend. Wasm

  • KT-84610 [Wasm] Failed to compile klibs in IC mode

Compiler

  • KT-84566 Prevent launching Default dispatcher threads from IJ SDK in kotlin compiler
  • KT-85358 Native: roll back the workaround for KT-84678 once MapLibre has been properly fixed
  • KT-85626 @JvmRecord in commonMain breaks compileCommonMainKotlinMetadata with "Cannot access 'java.lang.Record'"
  • KT-85405 Postpone/Revert DontIgnoreUpperBoundViolatedOnImplicitArguments
  • KT-84678 K/N: Undefined symbol from SPM-added ObjC frameworks when linking iOS target
  • KT-85021 False positive SUBCLASS_CANT_CALL_COMPANION_PROTECTED_NON_STATIC error in multi-module project

JavaScript

  • KT-82395 Support top-level declarations from compiler plugins in JS incremental compilation
  • KT-84475 K/JS: false-positive exportability warnings in multi-module project
  • KT-84633 Kotlin/JS: "Serializer for class not found" error when IR output granularity is whole-program
  • KT-85047 Kotlin/JS: @JsStatic on suspend fun of class companion generates incorrect d.ts
  • KT-84517 K/JS: bad mappings data in outputted Kotlin stdlib source map

Libraries

  • KT-71848 Kotlinx.metadata: Add CompilerPluginData into Km API

Native. C and ObjC Import

  • KT-85399 Kotlin/Native: TypeCastException when casting ObjC Protocol MetaClass with genericSafeCasts enabled
  • KT-85508 K/N: TypeCastException when using nw_parameters_create_secure_tcp block parameter on 2.3.20

Tools. Gradle

  • KT-84729 Update Gradle plugin-publish version to enable configuration cache badge on Gradle plugins portal

Tools. Gradle. Compiler plugins

  • KT-85257 AGP/Compose: MergeMappingFileTask clears R8 artifacts due to @OutputDirectory annotation on AGP 9.1+

Tools. Scripts

  • KT-85105 Scripts: JVM backend internal error (IR lowering) when scratch file contains anonymous object
  • KT-85103 Exception while generating code when explain destructuring decls
  • KT-84842 scriptCompilationClasspathFromContext behavior changed from 2.3.10 to 2.3.20
  • KT-85029 Kotlin Scripting: ScriptDiagnostic reports "at null" instead of error location

Tools. Statistics (FUS)

  • KT-85628 KGP: composite build FUS metrics fail on access of 'configurationTimeMetrics'
Commits
  • fea1ad8 Add ChangeLog for 2.3.21-RC2
  • 09c341e disable swift export execution tests in order to update macos
  • 67a0868 Avoid accessing KotlinNativeLink taskProvider when task was not executed
  • f89e5db [K/N] Disable TSAN in runtime tests
  • 45d6c85 [K/N] Don't generate generic safe casts for Objective-C types
  • 9261a6f [K/N][tests] Add a reproducer for KT-85508
  • c9ab9db [K/N][tests] Add a reproducer for KT-85399
  • 502e844 Explain: fix for destructuring declarations
  • 0c26485 Explain: fix for object literals
  • 68a9e3f [minor] fix testdata name in explain test
  • Additional commits viewable in compare view

Updates `org.jetbrains.kotlin.plugin.serialization` from 2.3.20 to 2.3.21
Release notes

Sourced from org.jetbrains.kotlin.plugin.serialization's releases.

Kotlin 2.3.21

Changelog

Backend. Wasm

  • KT-84610 [Wasm] Failed to compile klibs in IC mode

Compiler

  • KT-84566 Prevent launching Default dispatcher threads from IJ SDK in kotlin compiler
  • KT-85358 Native: roll back the workaround for KT-84678 once MapLibre has been properly fixed
  • KT-85626 @JvmRecord in commonMain breaks compileCommonMainKotlinMetadata with "Cannot access 'java.lang.Record'"
  • KT-85405 Postpone/Revert DontIgnoreUpperBoundViolatedOnImplicitArguments
  • KT-84678 K/N: Undefined symbol from SPM-added ObjC frameworks when linking iOS target
  • KT-85021 False positive SUBCLASS_CANT_CALL_COMPANION_PROTECTED_NON_STATIC error in multi-module project

JavaScript

  • KT-82395 Support top-level declarations from compiler plugins in JS incremental compilation
  • KT-84475 K/JS: false-positive exportability warnings in multi-module project
  • KT-84633 Kotlin/JS: "Serializer for class not found" error when IR output granularity is whole-program
  • KT-85047 Kotlin/JS: @JsStatic on suspend fun of class companion generates incorrect d.ts
  • KT-84517 K/JS: bad mappings data in outputted Kotlin stdlib source map

Libraries

  • KT-71848 Kotlinx.metadata: Add CompilerPluginData into Km API

Native. C and ObjC Import

  • KT-85399 Kotlin/Native: TypeCastException when casting ObjC Protocol MetaClass with genericSafeCasts enabled
  • KT-85508 K/N: TypeCastException when using nw_parameters_create_secure_tcp block parameter on 2.3.20

Tools. Gradle

  • KT-84729 Update Gradle plugin-publish version to enable configuration cache badge on Gradle plugins portal

Tools. Gradle. Compiler plugins

  • KT-85257 AGP/Compose: MergeMappingFileTask clears R8 artifacts due to @OutputDirectory annotation on AGP 9.1+

Tools. Scripts

  • KT-85105 Scripts: JVM backend internal error (IR lowering) when scratch file contains anonymous object
  • KT-85103 Exception while generating code when explain destructuring decls
  • KT-84842 scriptCompilationClasspathFromContext behavior changed from 2.3.10 to 2.3.20
  • KT-85029 Kotlin Scripting: ScriptDiagnostic reports "at null" instead of error location

Tools. Statistics (FUS)

... (truncated)

Changelog

Sourced from org.jetbrains.kotlin.plugin.serialization's changelog.

2.3.21

Backend. Wasm

  • KT-84610 [Wasm] Failed to compile klibs in IC mode

Compiler

  • KT-84566 Prevent launching Default dispatcher threads from IJ SDK in kotlin compiler
  • KT-85358 Native: roll back the workaround for KT-84678 once MapLibre has been properly fixed
  • KT-85626 @JvmRecord in commonMain breaks compileCommonMainKotlinMetadata with "Cannot access 'java.lang.Record'"
  • KT-85405 Postpone/Revert DontIgnoreUpperBoundViolatedOnImplicitArguments
  • KT-84678 K/N: Undefined symbol from SPM-added ObjC frameworks when linking iOS target
  • KT-85021 False positive SUBCLASS_CANT_CALL_COMPANION_PROTECTED_NON_STATIC error in multi-module project

JavaScript

  • KT-82395 Support top-level declarations from compiler plugins in JS incremental compilation
  • KT-84475 K/JS: false-positive exportability warnings in multi-module project
  • KT-84633 Kotlin/JS: "Serializer for class not found" error when IR output granularity is whole-program
  • KT-85047 Kotlin/JS: @JsStatic on suspend fun of class companion generates incorrect d.ts
  • KT-84517 K/JS: bad mappings data in outputted Kotlin stdlib source map

Libraries

  • KT-71848 Kotlinx.metadata: Add CompilerPluginData into Km API

Native. C and ObjC Import

  • KT-85399 Kotlin/Native: TypeCastException when casting ObjC Protocol MetaClass with genericSafeCasts enabled
  • KT-85508 K/N: TypeCastException when using nw_parameters_create_secure_tcp block parameter on 2.3.20

Tools. Gradle

  • KT-84729 Update Gradle plugin-publish version to enable configuration cache badge on Gradle plugins portal

Tools. Gradle. Compiler plugins

  • KT-85257 AGP/Compose: MergeMappingFileTask clears R8 artifacts due to @OutputDirectory annotation on AGP 9.1+

Tools. Scripts

  • KT-85105 Scripts: JVM backend internal error (IR lowering) when scratch file contains anonymous object
  • KT-85103 Exception while generating code when explain destructuring decls
  • KT-84842 scriptCompilationClasspathFromContext behavior changed from 2.3.10 to 2.3.20
  • KT-85029 Kotlin Scripting: ScriptDiagnostic reports "at null" instead of error location

Tools. Statistics (FUS)

  • KT-85628 KGP: composite build FUS metrics fail on access of 'configurationTimeMetrics'
Commits
  • fea1ad8 Add ChangeLog for 2.3.21-RC2
  • 09c341e disable swift export execution tests in order to update macos
  • 67a0868 Avoid accessing KotlinNativeLink taskProvider when task was not executed
  • f89e5db [K/N] Disable TSAN in runtime tests
  • 45d6c85 [K/N] Don't generate generic safe casts for Objective-C types
  • 9261a6f [K/N][tests] Add a reproducer for KT-85508
  • c9ab9db [K/N][tests] Add a reproducer for KT-85399
  • 502e844 Explain: fix for destructuring declarations
  • 0c26485 Explain: fix for object literals
  • 68a9e3f [minor] fix testdata name in explain test
  • Additional commits viewable in compare view

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlinlang-mcp-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml index 20dff78..967e7ff 100644 --- a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin = "2.3.20" +kotlin = "2.3.21" ktor = "3.3.3" mcp-kotlin = "0.11.1" serialization = "1.10.0" From b0093523566e8c4facd3dbde46a7a7cb1997c325 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 15:04:13 +0200 Subject: [PATCH 278/449] chore(deps): bump org.jetbrains.kotlinx:kotlinx-serialization-json from 1.10.0 to 1.11.0 in /samples/kotlinlang-mcp-server (#746) Bumps [org.jetbrains.kotlinx:kotlinx-serialization-json](https://github.com/Kotlin/kotlinx.serialization) from 1.10.0 to 1.11.0.
Release notes

Sourced from org.jetbrains.kotlinx:kotlinx-serialization-json's releases.

1.11.0

This release is based on Kotlin 2.3.20 and provides a new Json exceptions API and some bugfixes and improvements.

Expose Json exceptions structure

To make working with exceptions easier and providing proper error codes in e.g., REST APIs, classes JsonException, JsonDecodingException, and JsonEncodingException are now public. They have relevant public properties, such as shortMessage, path, offset, and others. This API is currently experimental, and we're going to improve it further in the subsequent releases. See the linked issues for the details: #1930, #1877.

Ability to hide user input from exception messages for security/privacy reasons.

Historically, exception messages in kotlinx.serialization often included the input Json itself for debuggability reason. Such behavior may pose additional challenges for logging, analytics, and other systems, since a system is not always allowed to store user data due to privacy/security reasons, which imposes additional sanitation logic. To address this issue, a new property exceptionsWithDebugInfo is added to JsonConfiguration. Disable it to hide user input from exception messages. IMPORTANT: This behavior will be enabled by default when this property becomes stable. See #2590 for more details.

Bugfixes and improvements

  • CBOR: Relax value range check when decoding numbers (#3167)
  • Use a specialized writeDecimalLong method for IO stream integrations in Json (#3152)
Changelog

Sourced from org.jetbrains.kotlinx:kotlinx-serialization-json's changelog.

1.11.0 / 2026-04-10

This release is based on Kotlin 2.3.20 and provides new Json exceptions API and some bugfixes and improvements.

Expose Json exceptions structure

To make working with exceptions easier and providing proper error codes in e.g., REST APIs, classes JsonException, JsonDecodingException, and JsonEncodingException are now public. They have relevant public properties, such as shortMessage, path, offset, and others. This API is currently experimental, and we're going to improve it further in the subsequent releases. See the linked issues for the details: #1930, #1877.

Ability to hide user input from exception messages for security/privacy reasons.

Historically, exception messages in kotlinx.serialization often included the input Json itself for debuggability reason. Such behavior may pose additional challenges for logging, analytics, and other systems, since a system is not always allowed to store user data due to privacy/security reasons, which imposes additional sanitation logic. To address this issue, a new property exceptionsWithDebugInfo is added to JsonConfiguration. Disable it to hide user input from exception messages. IMPORTANT: This behavior will be enabled by default when this property becomes stable. See #2590 for more details.

Bugfixes and improvements

  • CBOR: Relax value range check when decoding numbers (#3167)
  • Use a specialized writeDecimalLong method for IO stream integrations in Json (#3152)
Commits
  • 6956af2 Prepare 1.11 release
  • 390d84c Merge remote-tracking branch 'origin/master' into dev
  • 431fe2d Use local repo for publishing (#3171)
  • 05c12b6 Add usage attribute to "testRepositories" configuration
  • a4e1f08 Bump Kover version to 0.9.8 release (#3174)
  • 304e858 Expose Json exceptions structure (#3145)
  • 4a0338e Included G Play SDK verification file for core-jvm (#3169)
  • 421f64c CBOR: Relax value range check when decoding numbers (#3167)
  • 85a4f12 KT-84955: mark apple x64 tagets as deprecated error
  • bd38b0e Remove dead code
  • Additional commits viewable in compare view

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlinlang-mcp-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml index 967e7ff..2150c10 100644 --- a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml @@ -2,7 +2,7 @@ kotlin = "2.3.21" ktor = "3.3.3" mcp-kotlin = "0.11.1" -serialization = "1.10.0" +serialization = "1.11.0" coroutines = "1.10.2" logback = "1.5.32" shadow = "9.4.1" From 93e5ed2c06a1e88b9981b92713557fc5f16acb4d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 16:03:42 +0200 Subject: [PATCH 279/449] chore(deps): bump the other-dependencies group across 5 directories with 3 updates (#753) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the other-dependencies group with 2 updates in the /samples/kotlin-mcp-client directory: [gradle-wrapper](https://github.com/gradle/gradle) and [com.anthropic:anthropic-java](https://github.com/anthropics/anthropic-sdk-java). Bumps the other-dependencies group with 1 update in the /samples/kotlin-mcp-server directory: [gradle-wrapper](https://github.com/gradle/gradle). Bumps the other-dependencies group with 1 update in the /samples/kotlinlang-mcp-server directory: [gradle-wrapper](https://github.com/gradle/gradle). Bumps the other-dependencies group with 1 update in the /samples/simple-streamable-server directory: [gradle-wrapper](https://github.com/gradle/gradle). Bumps the other-dependencies group with 2 updates in the /samples/weather-stdio-server directory: [gradle-wrapper](https://github.com/gradle/gradle) and [io.github.oshai:kotlin-logging-jvm](https://github.com/oshai/kotlin-logging). Updates `gradle-wrapper` from 9.4.1 to 9.5.0
Release notes

Sourced from gradle-wrapper's releases.

9.5.0

The Gradle team is excited to announce Gradle 9.5.0.

Here are the highlights of this release:

  • Task provenance in reports and failure messages
  • Type-safe accessors for precompiled Kotlin Settings plugins

Read the Release Notes

We would like to thank the following community members for their contributions to this release of Gradle: atm1020, mataha, Adam, Attila Kelemen, Benedikt Ritter, Björn Kautler, Caro Silva Rode, CHANHAN, Dmitry Nezavitin, Eng Zer Jun, KugelLibelle, Madalin Valceleanu, Markus Gaisbauer, Oliver Kopp, Philip Wedemann, ploober, Roberto Perez Alcolea, Rohit Anand, Suvrat Acharya, Ujwal Suresh Vanjare, Victor Merkulov

Upgrade instructions

Switch your build to use Gradle 9.5.0 by updating your wrapper:

./gradlew wrapper --gradle-version=9.5.0 && ./gradlew
wrapper

See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

Reporting problems

If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

9.5.0 RC4

... (truncated)

Commits

Updates `com.anthropic:anthropic-java` from 2.27.0 to 2.28.0
Release notes

Sourced from com.anthropic:anthropic-java's releases.

v2.28.0

2.28.0 (2026-05-04)

Full Changelog: v2.27.0...v2.28.0

Features

  • api: improve Managed Agents APIs (8453f3e)
  • client: add Workload Identity Federation, interactive OAuth, and auth profiles (b5f1d02)
  • client: more robust error parsing (d523547)
  • support setting headers via env (03bb766)

Chores

Documentation

  • clarify forwards compat behavior (f48cfb4)
Changelog

Sourced from com.anthropic:anthropic-java's changelog.

2.28.0 (2026-05-04)

Full Changelog: v2.27.0...v2.28.0

Features

  • api: improve Managed Agents APIs (8453f3e)
  • client: add Workload Identity Federation, interactive OAuth, and auth profiles (b5f1d02)
  • client: more robust error parsing (d523547)
  • support setting headers via env (03bb766)

Chores

Documentation

  • clarify forwards compat behavior (f48cfb4)
Commits
  • e448de8 chore: release main
  • b1899db feat(client): more robust error parsing
  • 959647a codegen metadata
  • c8cc791 feat(client): add Workload Identity Federation, interactive OAuth, and auth p...
  • 14e9142 docs: clarify forwards compat behavior
  • ac01a78 codegen metadata
  • 5dda387 codegen metadata
  • 84b5f41 codegen metadata
  • af86b69 codegen metadata
  • 8194aad ci: pin trigger-release-please github action
  • Additional commits viewable in compare view

Updates `gradle-wrapper` from 9.4.1 to 9.5.0
Release notes

Sourced from gradle-wrapper's releases.

9.5.0

The Gradle team is excited to announce Gradle 9.5.0.

Here are the highlights of this release:

  • Task provenance in reports and failure messages
  • Type-safe accessors for precompiled Kotlin Settings plugins

Read the Release Notes

We would like to thank the following community members for their contributions to this release of Gradle: atm1020, mataha, Adam, Attila Kelemen, Benedikt Ritter, Björn Kautler, Caro Silva Rode, CHANHAN, Dmitry Nezavitin, Eng Zer Jun, KugelLibelle, Madalin Valceleanu, Markus Gaisbauer, Oliver Kopp, Philip Wedemann, ploober, Roberto Perez Alcolea, Rohit Anand, Suvrat Acharya, Ujwal Suresh Vanjare, Victor Merkulov

Upgrade instructions

Switch your build to use Gradle 9.5.0 by updating your wrapper:

./gradlew wrapper --gradle-version=9.5.0 && ./gradlew
wrapper

See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

Reporting problems

If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

9.5.0 RC4

... (truncated)

Commits

Updates `gradle-wrapper` from 9.4.1 to 9.5.0
Release notes

Sourced from gradle-wrapper's releases.

9.5.0

The Gradle team is excited to announce Gradle 9.5.0.

Here are the highlights of this release:

  • Task provenance in reports and failure messages
  • Type-safe accessors for precompiled Kotlin Settings plugins

Read the Release Notes

We would like to thank the following community members for their contributions to this release of Gradle: atm1020, mataha, Adam, Attila Kelemen, Benedikt Ritter, Björn Kautler, Caro Silva Rode, CHANHAN, Dmitry Nezavitin, Eng Zer Jun, KugelLibelle, Madalin Valceleanu, Markus Gaisbauer, Oliver Kopp, Philip Wedemann, ploober, Roberto Perez Alcolea, Rohit Anand, Suvrat Acharya, Ujwal Suresh Vanjare, Victor Merkulov

Upgrade instructions

Switch your build to use Gradle 9.5.0 by updating your wrapper:

./gradlew wrapper --gradle-version=9.5.0 && ./gradlew
wrapper

See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

Reporting problems

If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

9.5.0 RC4

... (truncated)

Commits

Updates `gradle-wrapper` from 9.4.1 to 9.5.0
Release notes

Sourced from gradle-wrapper's releases.

9.5.0

The Gradle team is excited to announce Gradle 9.5.0.

Here are the highlights of this release:

  • Task provenance in reports and failure messages
  • Type-safe accessors for precompiled Kotlin Settings plugins

Read the Release Notes

We would like to thank the following community members for their contributions to this release of Gradle: atm1020, mataha, Adam, Attila Kelemen, Benedikt Ritter, Björn Kautler, Caro Silva Rode, CHANHAN, Dmitry Nezavitin, Eng Zer Jun, KugelLibelle, Madalin Valceleanu, Markus Gaisbauer, Oliver Kopp, Philip Wedemann, ploober, Roberto Perez Alcolea, Rohit Anand, Suvrat Acharya, Ujwal Suresh Vanjare, Victor Merkulov

Upgrade instructions

Switch your build to use Gradle 9.5.0 by updating your wrapper:

./gradlew wrapper --gradle-version=9.5.0 && ./gradlew
wrapper

See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

Reporting problems

If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

9.5.0 RC4

... (truncated)

Commits

Updates `gradle-wrapper` from 9.4.1 to 9.5.0
Release notes

Sourced from gradle-wrapper's releases.

9.5.0

The Gradle team is excited to announce Gradle 9.5.0.

Here are the highlights of this release:

  • Task provenance in reports and failure messages
  • Type-safe accessors for precompiled Kotlin Settings plugins

Read the Release Notes

We would like to thank the following community members for their contributions to this release of Gradle: atm1020, mataha, Adam, Attila Kelemen, Benedikt Ritter, Björn Kautler, Caro Silva Rode, CHANHAN, Dmitry Nezavitin, Eng Zer Jun, KugelLibelle, Madalin Valceleanu, Markus Gaisbauer, Oliver Kopp, Philip Wedemann, ploober, Roberto Perez Alcolea, Rohit Anand, Suvrat Acharya, Ujwal Suresh Vanjare, Victor Merkulov

Upgrade instructions

Switch your build to use Gradle 9.5.0 by updating your wrapper:

./gradlew wrapper --gradle-version=9.5.0 && ./gradlew
wrapper

See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

Reporting problems

If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

9.5.0 RC4

... (truncated)

Commits

Updates `gradle-wrapper` from 9.4.1 to 9.5.0
Release notes

Sourced from gradle-wrapper's releases.

9.5.0

The Gradle team is excited to announce Gradle 9.5.0.

Here are the highlights of this release:

  • Task provenance in reports and failure messages
  • Type-safe accessors for precompiled Kotlin Settings plugins

Read the Release Notes

We would like to thank the following community members for their contributions to this release of Gradle: atm1020, mataha, Adam, Attila Kelemen, Benedikt Ritter, Björn Kautler, Caro Silva Rode, CHANHAN, Dmitry Nezavitin, Eng Zer Jun, KugelLibelle, Madalin Valceleanu, Markus Gaisbauer, Oliver Kopp, Philip Wedemann, ploober, Roberto Perez Alcolea, Rohit Anand, Suvrat Acharya, Ujwal Suresh Vanjare, Victor Merkulov

Upgrade instructions

Switch your build to use Gradle 9.5.0 by updating your wrapper:

./gradlew wrapper --gradle-version=9.5.0 && ./gradlew
wrapper

See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

Reporting problems

If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

9.5.0 RC4

... (truncated)

Commits

Updates `gradle-wrapper` from 9.4.1 to 9.5.0
Release notes

Sourced from gradle-wrapper's releases.

9.5.0

The Gradle team is excited to announce Gradle 9.5.0.

Here are the highlights of this release:

  • Task provenance in reports and failure messages
  • Type-safe accessors for precompiled Kotlin Settings plugins

Read the Release Notes

We would like to thank the following community members for their contributions to this release of Gradle: atm1020, mataha, Adam, Attila Kelemen, Benedikt Ritter, Björn Kautler, Caro Silva Rode, CHANHAN, Dmitry Nezavitin, Eng Zer Jun, KugelLibelle, Madalin Valceleanu, Markus Gaisbauer, Oliver Kopp, Philip Wedemann, ploober, Roberto Perez Alcolea, Rohit Anand, Suvrat Acharya, Ujwal Suresh Vanjare, Victor Merkulov

Upgrade instructions

Switch your build to use Gradle 9.5.0 by updating your wrapper:

./gradlew wrapper --gradle-version=9.5.0 && ./gradlew
wrapper

See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

Reporting problems

If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

9.5.0 RC4

... (truncated)

Commits

Updates `gradle-wrapper` from 9.4.1 to 9.5.0
Release notes

Sourced from gradle-wrapper's releases.

9.5.0

The Gradle team is excited to announce Gradle 9.5.0.

Here are the highlights of this release:

  • Task provenance in reports and failure messages
  • Type-safe accessors for precompiled Kotlin Settings plugins

Read the Release Notes

We would like to thank the following community members for their contributions to this release of Gradle: atm1020, mataha, Adam, Attila Kelemen, Benedikt Ritter, Björn Kautler, Caro Silva Rode, CHANHAN, Dmitry Nezavitin, Eng Zer Jun, KugelLibelle, Madalin Valceleanu, Markus Gaisbauer, Oliver Kopp, Philip Wedemann, ploober, Roberto Perez Alcolea, Rohit Anand, Suvrat Acharya, Ujwal Suresh Vanjare, Victor Merkulov

Upgrade instructions

Switch your build to use Gradle 9.5.0 by updating your wrapper:

./gradlew wrapper --gradle-version=9.5.0 && ./gradlew
wrapper

See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

Reporting problems

If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

9.5.0 RC4

... (truncated)

Commits

Updates `gradle-wrapper` from 9.4.1 to 9.5.0
Release notes

Sourced from gradle-wrapper's releases.

9.5.0

The Gradle team is excited to announce Gradle 9.5.0.

Here are the highlights of this release:

  • Task provenance in reports and failure messages
  • Type-safe accessors for precompiled Kotlin Settings plugins

Read the Release Notes

We would like to thank the following community members for their contributions to this release of Gradle: atm1020, mataha, Adam, Attila Kelemen, Benedikt Ritter, Björn Kautler, Caro Silva Rode, CHANHAN, Dmitry Nezavitin, Eng Zer Jun, KugelLibelle, Madalin Valceleanu, Markus Gaisbauer, Oliver Kopp, Philip Wedemann, ploober, Roberto Perez Alcolea, Rohit Anand, Suvrat Acharya, Ujwal Suresh Vanjare, Victor Merkulov

Upgrade instructions

Switch your build to use Gradle 9.5.0 by updating your wrapper:

./gradlew wrapper --gradle-version=9.5.0 && ./gradlew
wrapper

See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

Reporting problems

If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

9.5.0 RC4

... (truncated)

Commits

Updates `gradle-wrapper` from 9.4.1 to 9.5.0
Release notes

Sourced from gradle-wrapper's releases.

9.5.0

The Gradle team is excited to announce Gradle 9.5.0.

Here are the highlights of this release:

  • Task provenance in reports and failure messages
  • Type-safe accessors for precompiled Kotlin Settings plugins

Read the Release Notes

We would like to thank the following community members for their contributions to this release of Gradle: atm1020, mataha, Adam, Attila Kelemen, Benedikt Ritter, Björn Kautler, Caro Silva Rode, CHANHAN, Dmitry Nezavitin, Eng Zer Jun, KugelLibelle, Madalin Valceleanu, Markus Gaisbauer, Oliver Kopp, Philip Wedemann, ploober, Roberto Perez Alcolea, Rohit Anand, Suvrat Acharya, Ujwal Suresh Vanjare, Victor Merkulov

Upgrade instructions

Switch your build to use Gradle 9.5.0 by updating your wrapper:

./gradlew wrapper --gradle-version=9.5.0 && ./gradlew
wrapper

See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

Reporting problems

If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

9.5.0 RC4

... (truncated)

Commits

Updates `gradle-wrapper` from 9.4.1 to 9.5.0
Release notes

Sourced from gradle-wrapper's releases.

9.5.0

The Gradle team is excited to announce Gradle 9.5.0.

Here are the highlights of this release:

  • Task provenance in reports and failure messages
  • Type-safe accessors for precompiled Kotlin Settings plugins

Read the Release Notes

We would like to thank the following community members for their contributions to this release of Gradle: atm1020, mataha, Adam, Attila Kelemen, Benedikt Ritter, Björn Kautler, Caro Silva Rode, CHANHAN, Dmitry Nezavitin, Eng Zer Jun, KugelLibelle, Madalin Valceleanu, Markus Gaisbauer, Oliver Kopp, Philip Wedemann, ploober, Roberto Perez Alcolea, Rohit Anand, Suvrat Acharya, Ujwal Suresh Vanjare, Victor Merkulov

Upgrade instructions

Switch your build to use Gradle 9.5.0 by updating your wrapper:

./gradlew wrapper --gradle-version=9.5.0 && ./gradlew
wrapper

See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

Reporting problems

If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

9.5.0 RC4

... (truncated)

Commits

Updates `gradle-wrapper` from 9.4.1 to 9.5.0
Release notes

Sourced from gradle-wrapper's releases.

9.5.0

The Gradle team is excited to announce Gradle 9.5.0.

Here are the highlights of this release:

  • Task provenance in reports and failure messages
  • Type-safe accessors for precompiled Kotlin Settings plugins

Read the Release Notes

We would like to thank the following community members for their contributions to this release of Gradle: atm1020, mataha, Adam, Attila Kelemen, Benedikt Ritter, Björn Kautler, Caro Silva Rode, CHANHAN, Dmitry Nezavitin, Eng Zer Jun, KugelLibelle, Madalin Valceleanu, Markus Gaisbauer, Oliver Kopp, Philip Wedemann, ploober, Roberto Perez Alcolea, Rohit Anand, Suvrat Acharya, Ujwal Suresh Van... _Description has been truncated_ Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../gradle/libs.versions.toml | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 48966 -> 48462 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +- samples/kotlin-mcp-client/gradlew | 2 +- samples/kotlin-mcp-client/gradlew.bat | 31 +--- .../gradle/wrapper/gradle-wrapper.jar | Bin 48966 -> 48462 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +- samples/kotlin-mcp-server/gradlew | 2 +- samples/kotlin-mcp-server/gradlew.bat | 31 +--- .../gradle/wrapper/gradle-wrapper.jar | Bin 48966 -> 48462 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +- samples/kotlinlang-mcp-server/gradlew | 2 +- samples/kotlinlang-mcp-server/gradlew.bat | 175 ++++++++---------- .../gradle/wrapper/gradle-wrapper.jar | Bin 48966 -> 48462 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +- samples/simple-streamable-server/gradlew | 2 +- samples/simple-streamable-server/gradlew.bat | 31 +--- .../gradle/libs.versions.toml | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 48966 -> 48462 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +- samples/weather-stdio-server/gradlew | 2 +- samples/weather-stdio-server/gradlew.bat | 31 +--- 22 files changed, 144 insertions(+), 189 deletions(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index dd1ec3f..2959a9a 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -anthropic = "2.27.0" +anthropic = "2.28.0" kotlin = "2.3.21" ktor = "3.3.3" mcp-kotlin = "0.12.0" diff --git a/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.jar b/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.jar index d997cfc60f4cff0e7451d19d49a82fa986695d07..b1b8ef56b44f16b14dc800fa8103a6d89abb526f 100644 GIT binary patch delta 39760 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 e(*aw}>J`&Ep$5$o2mVP)i30-M3~u@DBh0 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 delta 40115 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 e{Sg5G5zENPNVU~b5#<2@M#Os+0czL&{q{eZe&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -65,7 +65,7 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :execute @rem Setup the command line @@ -73,21 +73,10 @@ goto fail @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.jar b/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.jar index d997cfc60f4cff0e7451d19d49a82fa986695d07..b1b8ef56b44f16b14dc800fa8103a6d89abb526f 100644 GIT binary patch delta 39760 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 e(*aw}>J`&Ep$5$o2mVP)i30-M3~u@DBh0 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 delta 40115 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 e{Sg5G5zENPNVU~b5#<2@M#Os+0czL&{q{eZe&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -65,7 +65,7 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :execute @rem Setup the command line @@ -73,21 +73,10 @@ goto fail @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/samples/kotlinlang-mcp-server/gradle/wrapper/gradle-wrapper.jar b/samples/kotlinlang-mcp-server/gradle/wrapper/gradle-wrapper.jar index d997cfc60f4cff0e7451d19d49a82fa986695d07..b1b8ef56b44f16b14dc800fa8103a6d89abb526f 100644 GIT binary patch delta 39760 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 e(*aw}>J`&Ep$5$o2mVP)i30-M3~u@DBh0 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 delta 40115 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 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel + +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/samples/simple-streamable-server/gradle/wrapper/gradle-wrapper.jar b/samples/simple-streamable-server/gradle/wrapper/gradle-wrapper.jar index d997cfc60f4cff0e7451d19d49a82fa986695d07..b1b8ef56b44f16b14dc800fa8103a6d89abb526f 100644 GIT binary patch delta 39760 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 e(*aw}>J`&Ep$5$o2mVP)i30-M3~u@DBh0 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 delta 40115 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 e{Sg5G5zENPNVU~b5#<2@M#Os+0czL&{q{eZe&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -65,7 +65,7 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :execute @rem Setup the command line @@ -73,21 +73,10 @@ goto fail @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/samples/weather-stdio-server/gradle/libs.versions.toml b/samples/weather-stdio-server/gradle/libs.versions.toml index b294c80..1fc2e6a 100644 --- a/samples/weather-stdio-server/gradle/libs.versions.toml +++ b/samples/weather-stdio-server/gradle/libs.versions.toml @@ -2,7 +2,7 @@ coroutines = "1.10.2" kotlin = "2.3.21" ktor = "3.3.3" -logging = "8.0.01" +logging = "8.0.02" mcp-kotlin = "0.12.0" shadow = "9.4.1" slf4j = "2.0.17" diff --git a/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.jar b/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.jar index d997cfc60f4cff0e7451d19d49a82fa986695d07..b1b8ef56b44f16b14dc800fa8103a6d89abb526f 100644 GIT binary patch delta 39760 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 e(*aw}>J`&Ep$5$o2mVP)i30-M3~u@DBh0 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 delta 40115 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 e{Sg5G5zENPNVU~b5#<2@M#Os+0czL&{q{eZe&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -65,7 +65,7 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :execute @rem Setup the command line @@ -73,21 +73,10 @@ goto fail @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% From 3c092c65e80c278337afdf2c2f9e193d64f640d7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 16:41:55 +0200 Subject: [PATCH 280/449] chore(deps): bump gradle-wrapper from 9.4.1 to 9.5.0 (#747) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [gradle-wrapper](https://github.com/gradle/gradle) from 9.4.1 to 9.5.0.

Release notes

Sourced from gradle-wrapper's releases.

9.5.0

The Gradle team is excited to announce Gradle 9.5.0.

Here are the highlights of this release:

  • Task provenance in reports and failure messages
  • Type-safe accessors for precompiled Kotlin Settings plugins

Read the Release Notes

We would like to thank the following community members for their contributions to this release of Gradle: atm1020, mataha, Adam, Attila Kelemen, Benedikt Ritter, Björn Kautler, Caro Silva Rode, CHANHAN, Dmitry Nezavitin, Eng Zer Jun, KugelLibelle, Madalin Valceleanu, Markus Gaisbauer, Oliver Kopp, Philip Wedemann, ploober, Roberto Perez Alcolea, Rohit Anand, Suvrat Acharya, Ujwal Suresh Vanjare, Victor Merkulov

Upgrade instructions

Switch your build to use Gradle 9.5.0 by updating your wrapper:

./gradlew wrapper --gradle-version=9.5.0 && ./gradlew
wrapper

See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

Reporting problems

If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

9.5.0 RC4

... (truncated)

Commits

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/wrapper/gradle-wrapper.jar | Bin 48966 -> 48462 bytes gradle/wrapper/gradle-wrapper.properties | 4 +- gradlew | 2 +- gradlew.bat | 175 +++++++++++------------ 4 files changed, 86 insertions(+), 95 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d997cfc60f4cff0e7451d19d49a82fa986695d07..b1b8ef56b44f16b14dc800fa8103a6d89abb526f 100644 GIT binary patch delta 39760 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 e(*aw}>J`&Ep$5$o2mVP)i30-M3~u@DBh0 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 delta 40115 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 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel + +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% From b20eec587bf64563704406a26c02dd2a40cc9a5b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 07:50:37 +0000 Subject: [PATCH 281/449] chore(deps): bump mcp-kotlin from 0.11.1 to 0.12.0 in /samples/kotlinlang-mcp-server (#745) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `mcp-kotlin` from 0.11.1 to 0.12.0. Updates `io.modelcontextprotocol:kotlin-sdk-server` from 0.11.1 to 0.12.0
Release notes

Sourced from io.modelcontextprotocol:kotlin-sdk-server's releases.

0.12.0

Description

Adds sampling-with-tools per SEP-1577, JSON Schema dialect declaration on tool schemas, server-side tool name validation, and capability extensions; fixes SSE reconnect on the same session and stops swallowing CancellationException.

Breaking Changes

Sampling messages can carry tool calls and results (SEP-1577) by @​devcrocod in #718

SamplingMessage.content is now typed as a new SamplingMessageContent supertype, with MediaContent (text/image/audio), ToolUseContent, and ToolResultContent as variants. MediaContent still exists and now extends SamplingMessageContent, so most source code that constructs sampling messages compiles unchanged — but the binary signatures of SamplingMessage, CreateMessageResult, and related types have changed. ClientCapabilities.sampling is also now a typed Sampling object instead of a raw JsonObject. Recompilation is required; consumers reading sampling as JsonObject must migrate to the typed accessors.

watchosX64 and tvosX64 Kotlin/Native targets removed by @​devcrocod in #727

These targets have been deprecated upstream. Migrate to the corresponding ARM/simulator targets (watchosArm64, watchosSimulatorArm64, tvosArm64, tvosSimulatorArm64).

Features

  • Add $schema field to ToolSchema, declaring JSON Schema 2020-12 as the default dialect (SEP-1613) by @​devcrocod in #696
  • Validate tool names at registration time on the server, following the MCP tool naming standard (SEP-986) by @​devcrocod in #695
  • Add extensions field to ClientCapabilities and ServerCapabilities for advertising supported MCP extensions during the initialize handshake by @​eritscher in #678

Fixed

  • Validate the mcp-protocol-version HTTP header on initialization requests and correct DEFAULT_NEGOTIATED_PROTOCOL_VERSION to match the spec by @​devcrocod in #697
  • Propagate CancellationException instead of logging it as an error by @​devcrocod in #706
  • Evict stale GET SSE stream mapping on reconnect so a new GET on the same session is no longer silently rejected by the leftover entry from the previous stream by @​amr in #716

Maintenance

  • Add kotlinlang MCP server sample with search and get_page tools by @​devcrocod in #713
  • Migrate detekt to a buildSrc convention plugin with a fully explicit, version-locked YAML config by @​devcrocod in #729
  • Add Streamable HTTP integration tests for pagination, bad-request handling, and logging by @​jskjw157 in #613
  • Remove unused OpenAPI Generator plugin and configuration by @​devcrocod in #683
  • Remove Codecov configuration in favor of kover by @​devcrocod in #691
  • Remove redundant JUnit XML report configuration by @​devcrocod in #728
  • Remove IDEA files tied to the incompatible detekt IDE plugin by @​devcrocod in #714
  • Add Node.js setup to the release workflow so TypeScript integration tests run on macOS runners by @​devcrocod in #684

Dependencies

  • Kotlin to v2.3.21 in #704, #723
  • kotlinx.serialization-json to v1.11.0 in #687

New Contributors

Full Changelog: https://github.com/modelcontextprotocol/kotlin-sdk/compare/0.11.1...0.12.0

Commits
  • c339d8c release: bump version to 0.12.0 (#731)
  • 64804a0 refactor: explicit locked detekt config (#729)
  • a238fc0 fix(server): evict stale GET SSE stream mapping on reconnect (#716)
  • 66d9efb test(streamable-http): add missing integration tests for pagination, bad requ...
  • f434fca feat!: SEP-1577 sampling with tools (#718)
  • 7474a33 chore: drop deprecated watchosX64 and tvosX64 targets (#727)
  • 12ace76 chore: remove redundant JUnit XML report configuration (#728)
  • ec606db chore(deps): bump kotlin from 2.2.21 to 2.3.21 in /samples/weather-stdio-serv...
  • 1d93042 chore(deps): bump kotlin from 2.2.21 to 2.3.21 in /samples/kotlin-mcp-server ...
  • 9817b17 chore(deps): bump org.jetbrains.kotlin.jvm from 2.2.21 to 2.3.21 in /samples/...
  • Additional commits viewable in compare view

Updates `io.modelcontextprotocol:kotlin-sdk-client` from 0.11.1 to 0.12.0
Release notes

Sourced from io.modelcontextprotocol:kotlin-sdk-client's releases.

0.12.0

Description

Adds sampling-with-tools per SEP-1577, JSON Schema dialect declaration on tool schemas, server-side tool name validation, and capability extensions; fixes SSE reconnect on the same session and stops swallowing CancellationException.

Breaking Changes

Sampling messages can carry tool calls and results (SEP-1577) by @​devcrocod in #718

SamplingMessage.content is now typed as a new SamplingMessageContent supertype, with MediaContent (text/image/audio), ToolUseContent, and ToolResultContent as variants. MediaContent still exists and now extends SamplingMessageContent, so most source code that constructs sampling messages compiles unchanged — but the binary signatures of SamplingMessage, CreateMessageResult, and related types have changed. ClientCapabilities.sampling is also now a typed Sampling object instead of a raw JsonObject. Recompilation is required; consumers reading sampling as JsonObject must migrate to the typed accessors.

watchosX64 and tvosX64 Kotlin/Native targets removed by @​devcrocod in #727

These targets have been deprecated upstream. Migrate to the corresponding ARM/simulator targets (watchosArm64, watchosSimulatorArm64, tvosArm64, tvosSimulatorArm64).

Features

  • Add $schema field to ToolSchema, declaring JSON Schema 2020-12 as the default dialect (SEP-1613) by @​devcrocod in #696
  • Validate tool names at registration time on the server, following the MCP tool naming standard (SEP-986) by @​devcrocod in #695
  • Add extensions field to ClientCapabilities and ServerCapabilities for advertising supported MCP extensions during the initialize handshake by @​eritscher in #678

Fixed

  • Validate the mcp-protocol-version HTTP header on initialization requests and correct DEFAULT_NEGOTIATED_PROTOCOL_VERSION to match the spec by @​devcrocod in #697
  • Propagate CancellationException instead of logging it as an error by @​devcrocod in #706
  • Evict stale GET SSE stream mapping on reconnect so a new GET on the same session is no longer silently rejected by the leftover entry from the previous stream by @​amr in #716

Maintenance

  • Add kotlinlang MCP server sample with search and get_page tools by @​devcrocod in #713
  • Migrate detekt to a buildSrc convention plugin with a fully explicit, version-locked YAML config by @​devcrocod in #729
  • Add Streamable HTTP integration tests for pagination, bad-request handling, and logging by @​jskjw157 in #613
  • Remove unused OpenAPI Generator plugin and configuration by @​devcrocod in #683
  • Remove Codecov configuration in favor of kover by @​devcrocod in #691
  • Remove redundant JUnit XML report configuration by @​devcrocod in #728
  • Remove IDEA files tied to the incompatible detekt IDE plugin by @​devcrocod in #714
  • Add Node.js setup to the release workflow so TypeScript integration tests run on macOS runners by @​devcrocod in #684

Dependencies

  • Kotlin to v2.3.21 in #704, #723
  • kotlinx.serialization-json to v1.11.0 in #687

New Contributors

Full Changelog: https://github.com/modelcontextprotocol/kotlin-sdk/compare/0.11.1...0.12.0

Commits
  • c339d8c release: bump version to 0.12.0 (#731)
  • 64804a0 refactor: explicit locked detekt config (#729)
  • a238fc0 fix(server): evict stale GET SSE stream mapping on reconnect (#716)
  • 66d9efb test(streamable-http): add missing integration tests for pagination, bad requ...
  • f434fca feat!: SEP-1577 sampling with tools (#718)
  • 7474a33 chore: drop deprecated watchosX64 and tvosX64 targets (#727)
  • 12ace76 chore: remove redundant JUnit XML report configuration (#728)
  • ec606db chore(deps): bump kotlin from 2.2.21 to 2.3.21 in /samples/weather-stdio-serv...
  • 1d93042 chore(deps): bump kotlin from 2.2.21 to 2.3.21 in /samples/kotlin-mcp-server ...
  • 9817b17 chore(deps): bump org.jetbrains.kotlin.jvm from 2.2.21 to 2.3.21 in /samples/...
  • Additional commits viewable in compare view

Updates `io.modelcontextprotocol:kotlin-sdk-testing` from 0.11.1 to 0.12.0
Release notes

Sourced from io.modelcontextprotocol:kotlin-sdk-testing's releases.

0.12.0

Description

Adds sampling-with-tools per SEP-1577, JSON Schema dialect declaration on tool schemas, server-side tool name validation, and capability extensions; fixes SSE reconnect on the same session and stops swallowing CancellationException.

Breaking Changes

Sampling messages can carry tool calls and results (SEP-1577) by @​devcrocod in #718

SamplingMessage.content is now typed as a new SamplingMessageContent supertype, with MediaContent (text/image/audio), ToolUseContent, and ToolResultContent as variants. MediaContent still exists and now extends SamplingMessageContent, so most source code that constructs sampling messages compiles unchanged — but the binary signatures of SamplingMessage, CreateMessageResult, and related types have changed. ClientCapabilities.sampling is also now a typed Sampling object instead of a raw JsonObject. Recompilation is required; consumers reading sampling as JsonObject must migrate to the typed accessors.

watchosX64 and tvosX64 Kotlin/Native targets removed by @​devcrocod in #727

These targets have been deprecated upstream. Migrate to the corresponding ARM/simulator targets (watchosArm64, watchosSimulatorArm64, tvosArm64, tvosSimulatorArm64).

Features

  • Add $schema field to ToolSchema, declaring JSON Schema 2020-12 as the default dialect (SEP-1613) by @​devcrocod in #696
  • Validate tool names at registration time on the server, following the MCP tool naming standard (SEP-986) by @​devcrocod in #695
  • Add extensions field to ClientCapabilities and ServerCapabilities for advertising supported MCP extensions during the initialize handshake by @​eritscher in #678

Fixed

  • Validate the mcp-protocol-version HTTP header on initialization requests and correct DEFAULT_NEGOTIATED_PROTOCOL_VERSION to match the spec by @​devcrocod in #697
  • Propagate CancellationException instead of logging it as an error by @​devcrocod in #706
  • Evict stale GET SSE stream mapping on reconnect so a new GET on the same session is no longer silently rejected by the leftover entry from the previous stream by @​amr in #716

Maintenance

  • Add kotlinlang MCP server sample with search and get_page tools by @​devcrocod in #713
  • Migrate detekt to a buildSrc convention plugin with a fully explicit, version-locked YAML config by @​devcrocod in #729
  • Add Streamable HTTP integration tests for pagination, bad-request handling, and logging by @​jskjw157 in #613
  • Remove unused OpenAPI Generator plugin and configuration by @​devcrocod in #683
  • Remove Codecov configuration in favor of kover by @​devcrocod in #691
  • Remove redundant JUnit XML report configuration by @​devcrocod in #728
  • Remove IDEA files tied to the incompatible detekt IDE plugin by @​devcrocod in #714
  • Add Node.js setup to the release workflow so TypeScript integration tests run on macOS runners by @​devcrocod in #684

Dependencies

  • Kotlin to v2.3.21 in #704, #723
  • kotlinx.serialization-json to v1.11.0 in #687

New Contributors

Full Changelog: https://github.com/modelcontextprotocol/kotlin-sdk/compare/0.11.1...0.12.0

Commits
  • c339d8c release: bump version to 0.12.0 (#731)
  • 64804a0 refactor: explicit locked detekt config (#729)
  • a238fc0 fix(server): evict stale GET SSE stream mapping on reconnect (#716)
  • 66d9efb test(streamable-http): add missing integration tests for pagination, bad requ...
  • f434fca feat!: SEP-1577 sampling with tools (#718)
  • 7474a33 chore: drop deprecated watchosX64 and tvosX64 targets (#727)
  • 12ace76 chore: remove redundant JUnit XML report configuration (#728)
  • ec606db chore(deps): bump kotlin from 2.2.21 to 2.3.21 in /samples/weather-stdio-serv...
  • 1d93042 chore(deps): bump kotlin from 2.2.21 to 2.3.21 in /samples/kotlin-mcp-server ...
  • 9817b17 chore(deps): bump org.jetbrains.kotlin.jvm from 2.2.21 to 2.3.21 in /samples/...
  • Additional commits viewable in compare view

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlinlang-mcp-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml index 2150c10..723deb5 100644 --- a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] kotlin = "2.3.21" ktor = "3.3.3" -mcp-kotlin = "0.11.1" +mcp-kotlin = "0.12.0" serialization = "1.11.0" coroutines = "1.10.2" logback = "1.5.32" From bf2e339908ddc0f5494e1cb8313097b9f54b2313 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 10:16:40 +0200 Subject: [PATCH 282/449] chore(deps): bump mcp-kotlin from 0.11.1 to 0.12.0 in /samples/simple-streamable-server (#749) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `mcp-kotlin` from 0.11.1 to 0.12.0. Updates `io.modelcontextprotocol:kotlin-sdk-client` from 0.11.1 to 0.12.0
Release notes

Sourced from io.modelcontextprotocol:kotlin-sdk-client's releases.

0.12.0

Description

Adds sampling-with-tools per SEP-1577, JSON Schema dialect declaration on tool schemas, server-side tool name validation, and capability extensions; fixes SSE reconnect on the same session and stops swallowing CancellationException.

Breaking Changes

Sampling messages can carry tool calls and results (SEP-1577) by @​devcrocod in #718

SamplingMessage.content is now typed as a new SamplingMessageContent supertype, with MediaContent (text/image/audio), ToolUseContent, and ToolResultContent as variants. MediaContent still exists and now extends SamplingMessageContent, so most source code that constructs sampling messages compiles unchanged — but the binary signatures of SamplingMessage, CreateMessageResult, and related types have changed. ClientCapabilities.sampling is also now a typed Sampling object instead of a raw JsonObject. Recompilation is required; consumers reading sampling as JsonObject must migrate to the typed accessors.

watchosX64 and tvosX64 Kotlin/Native targets removed by @​devcrocod in #727

These targets have been deprecated upstream. Migrate to the corresponding ARM/simulator targets (watchosArm64, watchosSimulatorArm64, tvosArm64, tvosSimulatorArm64).

Features

  • Add $schema field to ToolSchema, declaring JSON Schema 2020-12 as the default dialect (SEP-1613) by @​devcrocod in #696
  • Validate tool names at registration time on the server, following the MCP tool naming standard (SEP-986) by @​devcrocod in #695
  • Add extensions field to ClientCapabilities and ServerCapabilities for advertising supported MCP extensions during the initialize handshake by @​eritscher in #678

Fixed

  • Validate the mcp-protocol-version HTTP header on initialization requests and correct DEFAULT_NEGOTIATED_PROTOCOL_VERSION to match the spec by @​devcrocod in #697
  • Propagate CancellationException instead of logging it as an error by @​devcrocod in #706
  • Evict stale GET SSE stream mapping on reconnect so a new GET on the same session is no longer silently rejected by the leftover entry from the previous stream by @​amr in #716

Maintenance

  • Add kotlinlang MCP server sample with search and get_page tools by @​devcrocod in #713
  • Migrate detekt to a buildSrc convention plugin with a fully explicit, version-locked YAML config by @​devcrocod in #729
  • Add Streamable HTTP integration tests for pagination, bad-request handling, and logging by @​jskjw157 in #613
  • Remove unused OpenAPI Generator plugin and configuration by @​devcrocod in #683
  • Remove Codecov configuration in favor of kover by @​devcrocod in #691
  • Remove redundant JUnit XML report configuration by @​devcrocod in #728
  • Remove IDEA files tied to the incompatible detekt IDE plugin by @​devcrocod in #714
  • Add Node.js setup to the release workflow so TypeScript integration tests run on macOS runners by @​devcrocod in #684

Dependencies

  • Kotlin to v2.3.21 in #704, #723
  • kotlinx.serialization-json to v1.11.0 in #687

New Contributors

Full Changelog: https://github.com/modelcontextprotocol/kotlin-sdk/compare/0.11.1...0.12.0

Commits
  • c339d8c release: bump version to 0.12.0 (#731)
  • 64804a0 refactor: explicit locked detekt config (#729)
  • a238fc0 fix(server): evict stale GET SSE stream mapping on reconnect (#716)
  • 66d9efb test(streamable-http): add missing integration tests for pagination, bad requ...
  • f434fca feat!: SEP-1577 sampling with tools (#718)
  • 7474a33 chore: drop deprecated watchosX64 and tvosX64 targets (#727)
  • 12ace76 chore: remove redundant JUnit XML report configuration (#728)
  • ec606db chore(deps): bump kotlin from 2.2.21 to 2.3.21 in /samples/weather-stdio-serv...
  • 1d93042 chore(deps): bump kotlin from 2.2.21 to 2.3.21 in /samples/kotlin-mcp-server ...
  • 9817b17 chore(deps): bump org.jetbrains.kotlin.jvm from 2.2.21 to 2.3.21 in /samples/...
  • Additional commits viewable in compare view

Updates `io.modelcontextprotocol:kotlin-sdk-server` from 0.11.1 to 0.12.0
Release notes

Sourced from io.modelcontextprotocol:kotlin-sdk-server's releases.

0.12.0

Description

Adds sampling-with-tools per SEP-1577, JSON Schema dialect declaration on tool schemas, server-side tool name validation, and capability extensions; fixes SSE reconnect on the same session and stops swallowing CancellationException.

Breaking Changes

Sampling messages can carry tool calls and results (SEP-1577) by @​devcrocod in #718

SamplingMessage.content is now typed as a new SamplingMessageContent supertype, with MediaContent (text/image/audio), ToolUseContent, and ToolResultContent as variants. MediaContent still exists and now extends SamplingMessageContent, so most source code that constructs sampling messages compiles unchanged — but the binary signatures of SamplingMessage, CreateMessageResult, and related types have changed. ClientCapabilities.sampling is also now a typed Sampling object instead of a raw JsonObject. Recompilation is required; consumers reading sampling as JsonObject must migrate to the typed accessors.

watchosX64 and tvosX64 Kotlin/Native targets removed by @​devcrocod in #727

These targets have been deprecated upstream. Migrate to the corresponding ARM/simulator targets (watchosArm64, watchosSimulatorArm64, tvosArm64, tvosSimulatorArm64).

Features

  • Add $schema field to ToolSchema, declaring JSON Schema 2020-12 as the default dialect (SEP-1613) by @​devcrocod in #696
  • Validate tool names at registration time on the server, following the MCP tool naming standard (SEP-986) by @​devcrocod in #695
  • Add extensions field to ClientCapabilities and ServerCapabilities for advertising supported MCP extensions during the initialize handshake by @​eritscher in #678

Fixed

  • Validate the mcp-protocol-version HTTP header on initialization requests and correct DEFAULT_NEGOTIATED_PROTOCOL_VERSION to match the spec by @​devcrocod in #697
  • Propagate CancellationException instead of logging it as an error by @​devcrocod in #706
  • Evict stale GET SSE stream mapping on reconnect so a new GET on the same session is no longer silently rejected by the leftover entry from the previous stream by @​amr in #716

Maintenance

  • Add kotlinlang MCP server sample with search and get_page tools by @​devcrocod in #713
  • Migrate detekt to a buildSrc convention plugin with a fully explicit, version-locked YAML config by @​devcrocod in #729
  • Add Streamable HTTP integration tests for pagination, bad-request handling, and logging by @​jskjw157 in #613
  • Remove unused OpenAPI Generator plugin and configuration by @​devcrocod in #683
  • Remove Codecov configuration in favor of kover by @​devcrocod in #691
  • Remove redundant JUnit XML report configuration by @​devcrocod in #728
  • Remove IDEA files tied to the incompatible detekt IDE plugin by @​devcrocod in #714
  • Add Node.js setup to the release workflow so TypeScript integration tests run on macOS runners by @​devcrocod in #684

Dependencies

  • Kotlin to v2.3.21 in #704, #723
  • kotlinx.serialization-json to v1.11.0 in #687

New Contributors

Full Changelog: https://github.com/modelcontextprotocol/kotlin-sdk/compare/0.11.1...0.12.0

Commits
  • c339d8c release: bump version to 0.12.0 (#731)
  • 64804a0 refactor: explicit locked detekt config (#729)
  • a238fc0 fix(server): evict stale GET SSE stream mapping on reconnect (#716)
  • 66d9efb test(streamable-http): add missing integration tests for pagination, bad requ...
  • f434fca feat!: SEP-1577 sampling with tools (#718)
  • 7474a33 chore: drop deprecated watchosX64 and tvosX64 targets (#727)
  • 12ace76 chore: remove redundant JUnit XML report configuration (#728)
  • ec606db chore(deps): bump kotlin from 2.2.21 to 2.3.21 in /samples/weather-stdio-serv...
  • 1d93042 chore(deps): bump kotlin from 2.2.21 to 2.3.21 in /samples/kotlin-mcp-server ...
  • 9817b17 chore(deps): bump org.jetbrains.kotlin.jvm from 2.2.21 to 2.3.21 in /samples/...
  • Additional commits viewable in compare view

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/simple-streamable-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/simple-streamable-server/gradle/libs.versions.toml b/samples/simple-streamable-server/gradle/libs.versions.toml index dc668c0..3296f05 100644 --- a/samples/simple-streamable-server/gradle/libs.versions.toml +++ b/samples/simple-streamable-server/gradle/libs.versions.toml @@ -2,7 +2,7 @@ kotlin = "2.3.21" ktor = "3.3.3" junit = "6.0.3" -mcp-kotlin = "0.11.1" +mcp-kotlin = "0.12.0" slf4j = "2.0.17" shadow = "9.4.1" From 8a814292cff713cecb7339ef4a4c8444d6982fb7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 08:39:40 +0000 Subject: [PATCH 283/449] chore(deps): bump zod from 4.3.6 to 4.4.3 in /integration-test/src/jvmTest/typescript in the all-dependencies group across 1 directory (#751) Bumps the all-dependencies group with 1 update in the /integration-test/src/jvmTest/typescript directory: [zod](https://github.com/colinhacks/zod). Updates `zod` from 4.3.6 to 4.4.3
Release notes

Sourced from zod's releases.

v4.4.3

Commits:

  • 4c2fa95ce3f3390fbc522324e406b4e9e89b88f9 docs: use Zernio primary wordmark for gold sponsor logo
  • 2aeec83eb135e3a83756e973ef44845fc5a455d2 docs: prune lapsed gold sponsors and rebalance logo sizing
  • 7391be88ac1ee5cd02057f5ccc012a1f5df4efd0 docs: prune lapsed silver/bronze sponsors and add active ones
  • 2c703322a21b4e2b12f33f49ea8430c451a68b4f docs: normalize bronze sponsor logos to github avatar pattern
  • 9195250cab0e7950efe39c3926d6c203b4b0a170 docs: remove Mintlify from bronze sponsors (churned)
  • b8dffe9e62f17e6571e6249d05cc5102b54d94e4 docs: remove Numeric and Speakeasy (2+ missed monthly cycles)
  • 1cab69383fcdeae2a366d5e2a2fc4d8fc765d168 fix(v4): restore catch handling for absent object keys (#5937) (#5939)
  • c2be4f819064eed62c7c350a2d399b5faecd15f8 fix(v4): generalize optin/fallback to transform; restore preprocess on absent keys (#5941)
  • f3c9ec03ba7a28ae72d25cc295f38674bee0f559 4.4.3
  • 1fb56a5c18c27102dbc92260a4007c7732a0ccca docs: document release procedure in AGENTS.md

v4.4.2

Commits:

  • 0c62df0ea19fd05abdf90473e9eef7eea530fab2 Clean up docs navigation and stale labels (#5901)
  • 20cc794895cc8604fe0c87d83a5d1c3f89fad0ac chore: add security policy and refresh tooling deps
  • 6fbe07b0177efdd1bf1c0b05160e70d7a0702337 fix(docs): heading anchor links now include the hash so it doesnt scoll all the way up, follows navbar logic (#5791)
  • 4bbed1b1c73eca4ce9e59b1189ed236aa6c8b5bd Tighten discriminated union option typing
  • bbac3e567e7fccfaaf7cdc97f1ce30c295e2c908 Update PR guidance for agents
  • cf0dc942a32805c292fff59ade20a7ace980735a Merge remote-tracking branch 'origin/main' into fix-discriminated-union-key-constraint
  • 292c894a5fd2aa42e527900b83d8d7a3009a709c docs: add Zernio gold sponsor
  • 1fc9f311c28dcf80d0bb5a36b177086cbc3d8eca docs: document codec inversion
  • 1373c85da9aeff704a9762d27bc58699618aefb7 docs: remove AI disclosure guidance
  • e20d02b473c08e3a4e557bc610b1b5fac079b649 chore: ignore triage notes
  • e58ea4d91b1dfe8194b73508203213cbc7e9c936 docs: test Zod Mini tab code heights
  • 905761a5d127e8d5dd2ebb3bc88c75cb0b8149ff docs: document preprocess input type narrowing
  • bf64bac850d4dee2b7dde7e64909d5d796d32043 chore: tighten test guidance in AGENTS.md
  • 8ec4e73f4c4693b6361ad591be40fb41eb8a9f95 chore: update play.ts scratch
  • 02c2baf7d0d615872fa4528a8020603b71211702 Make z.preprocess defer optionality to inner schema (#5929)
  • 88015df8e25c44fb5385eb3ef28935119cd5edea fix(docs): drop deprecated baseUrl from tsconfig
  • c59d4474e3b4cad1b323462186cf607178ce8267 4.4.2

v4.4.1

Commits:

  • 481f7be4238c83ed58183f921b2646f340a91c6a ci: gate release publishing on full test workflow
  • 95ccab423aec720b2523c3a64cdc7e3204537cc7 test(v3): restore optional undefined expectations
  • cede2c63739a5823d6aa5093d291e9a111da943d fix(v4): reject tuple holes before required defaults (#5900)
  • edd0bf0f5ada4a8dc581c259407d7bbad0a71ea7 release: 4.4.1
  • 180d83d1dbe6a59260710cc8637a3dea2281ee56 docs: remove Jazz featured sponsor

v4.4.0

4.4.0

This is a minor release with a wide set of correctness and soundness fixes. Some fixes intentionally make Zod stricter, so code that depended on previously accepted invalid or ambiguous inputs may need small updates.

Potentially breaking bug fixes

... (truncated)

Commits
  • 1fb56a5 docs: document release procedure in AGENTS.md
  • f3c9ec0 4.4.3
  • c2be4f8 fix(v4): generalize optin/fallback to transform; restore preprocess on absent...
  • 1cab693 fix(v4): restore catch handling for absent object keys (#5937) (#5939)
  • b8dffe9 docs: remove Numeric and Speakeasy (2+ missed monthly cycles)
  • 9195250 docs: remove Mintlify from bronze sponsors (churned)
  • 2c70332 docs: normalize bronze sponsor logos to github avatar pattern
  • 7391be8 docs: prune lapsed silver/bronze sponsors and add active ones
  • 2aeec83 docs: prune lapsed gold sponsors and rebalance logo sizing
  • 4c2fa95 docs: use Zernio primary wordmark for gold sponsor logo
  • Additional commits viewable in compare view
Maintainer changes

This version was pushed to npm by GitHub Actions, a new releaser for zod since your current version.


Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- integration-test/src/jvmTest/typescript/package-lock.json | 8 ++++---- integration-test/src/jvmTest/typescript/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index 34e6bf4..0d258ed 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "@modelcontextprotocol/sdk": "^1.29.0", "express": "^5.2.1", - "zod": "^4.3.6" + "zod": "^4.4.3" }, "devDependencies": { "@types/cors": "^2.8.19", @@ -1797,9 +1797,9 @@ "license": "ISC" }, "node_modules/zod": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/integration-test/src/jvmTest/typescript/package.json b/integration-test/src/jvmTest/typescript/package.json index 030c79c..d648fa9 100644 --- a/integration-test/src/jvmTest/typescript/package.json +++ b/integration-test/src/jvmTest/typescript/package.json @@ -11,7 +11,7 @@ "dependencies": { "@modelcontextprotocol/sdk": "^1.29.0", "express": "^5.2.1", - "zod": "^4.3.6" + "zod": "^4.4.3" }, "devDependencies": { "@types/cors": "^2.8.19", From 7b474720f46c9e9d416140601960e35d66254815 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 May 2026 13:49:33 +0200 Subject: [PATCH 284/449] chore(deps): bump hono from 4.12.14 to 4.12.18 in /integration-test/src/jvmTest/typescript (#755) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [hono](https://github.com/honojs/hono) from 4.12.14 to 4.12.18.
Release notes

Sourced from hono's releases.

v4.12.18

Security fixes

This release includes fixes for the following security issues:

Cache Middleware ignores Vary: Authorization / Vary: Cookie leading to cross-user cache leakage

Affects: Cache Middleware. Fixes missing cache-skip handling for Vary: Authorization and Vary: Cookie, where a response cached for one authenticated user could be served to other users. GHSA-p77w-8qqv-26rm

CSS Declaration Injection via Style Object Values in JSX SSR

Affects: hono/jsx. Fixes a missing CSS-context escape for style object values and property names, where untrusted input could inject additional CSS declarations. The impact is limited to CSS and does not allow JavaScript execution. GHSA-qp7p-654g-cw7p

Improper validation of NumericDate claims (exp, nbf, iat) in JWT verify()

Affects: hono/utils/jwt. Fixes improper validation of exp, nbf, and iat claims, where falsy, non-finite, or non-numeric values could silently bypass time-based checks instead of being rejected per RFC 7519. GHSA-hm8q-7f3q-5f36


Users who use the JWT helper, hono/jsx, or the Cache middleware are strongly encouraged to upgrade to this version.

v4.12.17

What's Changed

New Contributors

Full Changelog: https://github.com/honojs/hono/compare/v4.12.16...v4.12.17

v4.12.16

Security fixes

This release includes fixes for the following security issues:

Unvalidated JSX Tag Names in hono/jsx May Allow HTML Injection

Affects: hono/jsx. Fixes missing validation of JSX tag names when using jsx() or createElement(), which could allow HTML injection if untrusted input is used as the tag name. GHSA-69xw-7hcm-h432

bodyLimit() can be bypassed for chunked / unknown-length requests

Affects: Body Limit Middleware. Fixes late enforcement for request bodies without a reliable Content-Length (e.g. chunked requests), where oversized requests could reach handlers and return successful responses before being rejected. GHSA-9vqf-7f2p-gf9v

v4.12.15

What's Changed

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=hono&package-manager=npm_and_yarn&previous-version=4.12.14&new-version=4.12.18)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/modelcontextprotocol/kotlin-sdk/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- integration-test/src/jvmTest/typescript/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index 0d258ed..b71ef4c 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -1197,9 +1197,9 @@ } }, "node_modules/hono": { - "version": "4.12.14", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.14.tgz", - "integrity": "sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==", + "version": "4.12.18", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.18.tgz", + "integrity": "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==", "license": "MIT", "engines": { "node": ">=16.9.0" From bab4b0bc606bc193a863b2c428e247e963cc9f8b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 May 2026 12:11:20 +0000 Subject: [PATCH 285/449] chore(deps): bump @types/node from 25.6.0 to 25.6.2 in /integration-test/src/jvmTest/typescript in the all-dependencies group across 1 directory (#762) Bumps the all-dependencies group with 1 update in the /integration-test/src/jvmTest/typescript directory: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node). Updates `@types/node` from 25.6.0 to 25.6.2
Commits

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- integration-test/src/jvmTest/typescript/package-lock.json | 8 ++++---- integration-test/src/jvmTest/typescript/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index b71ef4c..d66c9ee 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -15,7 +15,7 @@ "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^25.6.0", + "@types/node": "^25.6.2", "cors": "^2.8.6", "tsx": "^4.21.0", "typescript": "^6.0.3" @@ -579,9 +579,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.6.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", - "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "version": "25.6.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.2.tgz", + "integrity": "sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/integration-test/src/jvmTest/typescript/package.json b/integration-test/src/jvmTest/typescript/package.json index d648fa9..6ec358a 100644 --- a/integration-test/src/jvmTest/typescript/package.json +++ b/integration-test/src/jvmTest/typescript/package.json @@ -16,7 +16,7 @@ "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^25.6.0", + "@types/node": "^25.6.2", "cors": "^2.8.6", "tsx": "^4.21.0", "typescript": "^6.0.3" From 6d34e6ce03b87b95570ce67f666c942a5107fcf2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 May 2026 12:48:46 +0000 Subject: [PATCH 286/449] chore(deps): bump fast-uri from 3.1.0 to 3.1.2 in /integration-test/src/jvmTest/typescript (#756) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.0 to 3.1.2.
Release notes

Sourced from fast-uri's releases.

v3.1.2

⚠️ Security Release

What's Changed

Full Changelog: https://github.com/fastify/fast-uri/compare/v3.1.1...v3.1.2

v3.1.1

⚠️ Security Release

What's Changed

New Contributors

Full Changelog: https://github.com/fastify/fast-uri/compare/v3.1.0...v3.1.1

Commits
  • 919dd8e Bumped v3.1.2
  • c65ba57 fixup: linting
  • 6c86c17 Merge commit from fork
  • a95158a Handle malformed fragment decoding without throwing (#171)
  • cea547c Bumped v3.1.1
  • 876ce79 Merge commit from fork
  • dcdf690 ci: add lock-threads workflow (#169)
  • c860e65 build(deps-dev): bump neostandard from 0.12.2 to 0.13.0 (#167)
  • 9b4c6dc build(deps): bump fastify/workflows/.github/workflows/plugins-ci.yml (#166)
  • 85d09a9 build(deps): bump fastify/workflows/.github/workflows/plugins-ci-package-mana...
  • Additional commits viewable in compare view

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- integration-test/src/jvmTest/typescript/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index d66c9ee..6aa468d 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -1033,9 +1033,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", "funding": [ { "type": "github", From 664c0da900297cef8589a418d6517c86b9a51353 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 May 2026 18:44:47 +0200 Subject: [PATCH 287/449] chore(deps): bump com.anthropic:anthropic-java from 2.28.0 to 2.30.0 in /samples/kotlin-mcp-client in the other-dependencies group across 1 directory (#757) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [//]: # (dependabot-start) ⚠️ **Dependabot is rebasing this PR** ⚠️ Rebasing might not happen immediately, so don't worry if this takes some time. Note: if you make any changes to this PR yourself, they will take precedence over the rebase. --- [//]: # (dependabot-end) Bumps the other-dependencies group with 1 update in the /samples/kotlin-mcp-client directory: [com.anthropic:anthropic-java](https://github.com/anthropics/anthropic-sdk-java). Updates `com.anthropic:anthropic-java` from 2.28.0 to 2.30.0
Release notes

Sourced from com.anthropic:anthropic-java's releases.

v2.30.0

2.30.0 (2026-05-06)

Full Changelog: v2.29.0...v2.30.0

Features

  • api: add support for Managed Agents multiagents and outcomes, webhooks, vault validation (e641d5f)
  • api: add support for Managed Agents multiagents and outcomes, webhooks, vault validation (6067599)
  • client: support proxy authentication (d91fea2)

Bug Fixes

  • api: Adjust webhook configuration (34ea5b4)

Chores

v2.29.0

2.29.0 (2026-05-05)

Full Changelog: v2.28.0...v2.29.0

Features

  • client: allow targeting a workspace for OIDC federation token exchange (578003a)

Performance Improvements

  • client: create one json mapper (556ef49)

Chores

  • remove duplicated dokka setup (d6b94f4)

Documentation

Changelog

Sourced from com.anthropic:anthropic-java's changelog.

2.30.0 (2026-05-06)

Full Changelog: v2.29.0...v2.30.0

Features

  • api: add support for Managed Agents multiagents and outcomes, webhooks, vault validation (e641d5f)
  • api: add support for Managed Agents multiagents and outcomes, webhooks, vault validation (6067599)
  • client: support proxy authentication (d91fea2)

Bug Fixes

  • api: Adjust webhook configuration (34ea5b4)

Chores

2.29.0 (2026-05-05)

Full Changelog: v2.28.0...v2.29.0

Features

  • client: allow targeting a workspace for OIDC federation token exchange (578003a)

Performance Improvements

  • client: create one json mapper (556ef49)

Chores

  • remove duplicated dokka setup (d6b94f4)

Documentation

Commits
  • 5f413b5 chore: release main
  • 46baee8 fix(api): Adjust webhook configuration
  • 0ce73ea feat(api): add support for Managed Agents multiagents and outcomes, webhooks,...
  • 337eb3b chore: fix merge errors
  • 0a95f16 feat(api): add support for Managed Agents multiagents and outcomes, webhooks,...
  • 93912dd feat(client): support proxy authentication
  • 1fc9ab1 chore: release main
  • 9e4cbbd feat(client): allow targeting a workspace for OIDC federation token exchange
  • e8e5894 perf(client): create one json mapper
  • 726e6f4 chore: remove duplicated dokka setup
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=com.anthropic:anthropic-java&package-manager=gradle&previous-version=2.28.0&new-version=2.30.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-client/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index 2959a9a..6d4a979 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -anthropic = "2.28.0" +anthropic = "2.31.0" kotlin = "2.3.21" ktor = "3.3.3" mcp-kotlin = "0.12.0" From 9947353a48e13b3871d5b9ca1c87c7e15a0e92c5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 May 2026 17:04:01 +0000 Subject: [PATCH 288/449] chore(deps): bump coroutines from 1.10.2 to 1.11.0 in /samples/kotlinlang-mcp-server (#758) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `coroutines` from 1.10.2 to 1.11.0. Updates `org.jetbrains.kotlinx:kotlinx-coroutines-core` from 1.10.2 to 1.11.0
Release notes

Sourced from org.jetbrains.kotlinx:kotlinx-coroutines-core's releases.

1.11.0

Various

  • Kotlin was updated to 2.2.20 (#4545).
  • Improved the published jar files (#3842, #4599).
  • Various documentation improvements, including complete rewrites of structured concurrency and error handling-related KDoc (#4433, #4596).

Breaking changes and deprecations

  • Moved Promise-related functions from JS and Wasm/JS to the new web target. On Wasm/JS, this is a breaking change. Before the change, Promise on Wasm/JS could work with arbitrary Kotlin types, but now, only JsAny subtypes are accepted (#4563).
  • Changed handling of coroutine exceptions that can't be propagated on JS and Wasm/JS. B efore, exceptions were logged, but now, they are reported to the JS runtime (#4451, #4631).
  • Deprecated using CoroutineDispatcher as the coroutine context key; now, ContinuationInterceptor has to be used instead (#4333).
  • Advanced the deprecation levels on kotlinx-coroutines-test APIs (#4604).
  • Added lint functions that mark passing a Job to coroutine builders as deprecated (#4435).

Bug fixes and improvements

  • Added a callsInPlace(EXACTLY_ONCE) contract to runBlocking in code shared between JVM and Native (#4368).
  • Added a callsInPlace(EXACTLY_ONCE) contract to suspendCancellableCoroutine (#4574).
  • Fixed flowOn incorrectly handling ThreadContextElement updates (#4403).
  • Fixed exceptions in user-supplied Thread.UncaughtExceptionHandler instances causing the internal coroutines machinery to fail (#4516).
  • Fixed CoroutineDispatcher.asScheduler in the RxJava integration not cancelling outstanding work when a Worker gets cancelled, which led to memory leaks in some scenarios (#4615).
  • Fixed SharedFlow entering an invalid state when a subscriber and an emitter are cancelled simultaneously (#4583).
  • Fixed an R8 optimization leading to shareIn/stateIn coroutines getting garbage-collected (#4646). Thanks, @​solevic!

Small additions

  • Added CompletableDeferred.asDeferred for obtaining a read-only Deferred view (#4408).
  • Added SharedFlow.asFlow for obtaining a Flow view with hidden hot flow semantics (#4530). Thanks, @​g000sha256!
  • Added a StateFlow.collectLatest overload returning Nothing to assist with finding unreachable code (#4454).
  • Added ReceiveChannel.consumeTo for consuming a ReceiveChannel into a MutableCollection (#4520).
  • Added a StateFlow<T>.onSubscription overload returning a StateFlow<T>, similar to SharedFlow<T>.onSubscription returning SharedFlow<T> (#4275). Thanks, @​xit0c!
  • Added terminal Flow operators for collecting a Flow to a Map (#1541).

Changelog relative to version 1.11.0

No changes, only the version is increased.

1.11.0-rc02

Restored binary compatibility with 1.10.2 and older versions on Wasm/JS for usages of Promise-related functions (#4661).

1.11.0-rc01

Various

  • Kotlin was updated to 2.2.20 (#4545).
  • Improved the published jar files (#3842, #4599).
  • Various documentation improvements, including complete rewrites of structured concurrency and error handling-related KDoc (#4433, #4596).

Breaking changes and deprecations

... (truncated)

Changelog

Sourced from org.jetbrains.kotlinx:kotlinx-coroutines-core's changelog.

Version 1.11.0

Various

  • Kotlin was updated to 2.2.20 (#4545).
  • Improved the published jar files (#3842, #4599).
  • Various documentation improvements, including complete rewrites of structured concurrency and error handling-related KDoc (#4433, #4596).

Breaking changes and deprecations

  • Moved Promise-related functions from JS and Wasm/JS to the new web target. On Wasm/JS, this is a breaking change. Before the change, Promise on Wasm/JS could work with arbitrary Kotlin types, but now, only JsAny subtypes are accepted (#4563).
  • Changed handling of coroutine exceptions that can't be propagated on JS and Wasm/JS. Before, exceptions were logged, but now, they are reported to the JS runtime (#4451, #4631).
  • Deprecated using CoroutineDispatcher as the coroutine context key; now, ContinuationInterceptor has to be used instead (#4333).
  • Advanced the deprecation levels on kotlinx-coroutines-test APIs (#4604).
  • Added lint functions that mark passing a Job to coroutine builders as deprecated (#4435).

Bug fixes and improvements

  • Added a callsInPlace(EXACTLY_ONCE) contract to runBlocking in code shared between JVM and Native (#4368).
  • Added a callsInPlace(EXACTLY_ONCE) contract to suspendCancellableCoroutine (#4574).
  • Fixed flowOn incorrectly handling ThreadContextElement updates (#4403).
  • Fixed exceptions in user-supplied Thread.UncaughtExceptionHandler instances causing the internal coroutines machinery to fail (#4516).
  • Fixed CoroutineDispatcher.asScheduler in the RxJava integration not cancelling outstanding work when a Worker gets cancelled, which led to memory leaks in some scenarios (#4615).
  • Fixed SharedFlow entering an invalid state when a subscriber and an emitter are cancelled simultaneously (#4583).
  • Fixed an R8 optimization leading to shareIn/stateIn coroutines getting garbage-collected (#4646). Thanks, @​solevic!

Small additions

  • Added CompletableDeferred.asDeferred for obtaining a read-only Deferred view (#4408).
  • Added SharedFlow.asFlow for obtaining a Flow view with hidden hot flow semantics (#4530). Thanks, @​g000sha256!
  • Added a StateFlow.collectLatest overload returning Nothing to assist with finding unreachable code (#4454).
  • Added ReceiveChannel.consumeTo for consuming a ReceiveChannel into a MutableCollection (#4520).
  • Added a StateFlow<T>.onSubscription overload returning a StateFlow<T>, similar to SharedFlow<T>.onSubscription returning SharedFlow<T> (#4275). Thanks, @​xit0c!
  • Added terminal Flow operators for collecting a Flow to a Map (#1541).

Changelog relative to version 1.11.0

No changes, only the version is increased.

Version 1.11.0-rc02

Restored binary compatibility with 1.10.2 and older versions on Wasm/JS for usages of Promise-related functions (#4661).

Version 1.11.0-rc01

Various

  • Kotlin was updated to 2.2.20 (#4545).
  • Improved the published jar files (#3842, #4599).
  • Various documentation improvements, including complete rewrites of structured concurrency and error handling-related KDoc (#4433, #4596).

... (truncated)

Commits
  • 8564f65 Version 1.11.0
  • a4c6af9 Merge remote-tracking branch 'origin/master' into develop
  • ef917b4 KT-84955: mark apple x64 tagets as deprecated error (#4645)
  • 5ebc421 Update the release procedure description (#4670)
  • 95f46a0 Remove old maven repository settings (#4672)
  • b4f4f0a Fix package name of ToMapCollectionSamplesTest. (#4674)
  • 86738dc Added templates to the issue creation wizard (#4654)
  • 330fcc2 Version 1.11.0-rc02
  • e31cef6 Merge remote-tracking branch 'origin/master' into develop
  • dc6e9f6 Restore Promise-related functions on Wasm/JS as HIDDEN (#4661)
  • Additional commits viewable in compare view

Updates `org.jetbrains.kotlinx:kotlinx-coroutines-test` from 1.10.2 to 1.11.0
Release notes

Sourced from org.jetbrains.kotlinx:kotlinx-coroutines-test's releases.

1.11.0

Various

  • Kotlin was updated to 2.2.20 (#4545).
  • Improved the published jar files (#3842, #4599).
  • Various documentation improvements, including complete rewrites of structured concurrency and error handling-related KDoc (#4433, #4596).

Breaking changes and deprecations

  • Moved Promise-related functions from JS and Wasm/JS to the new web target. On Wasm/JS, this is a breaking change. Before the change, Promise on Wasm/JS could work with arbitrary Kotlin types, but now, only JsAny subtypes are accepted (#4563).
  • Changed handling of coroutine exceptions that can't be propagated on JS and Wasm/JS. B efore, exceptions were logged, but now, they are reported to the JS runtime (#4451, #4631).
  • Deprecated using CoroutineDispatcher as the coroutine context key; now, ContinuationInterceptor has to be used instead (#4333).
  • Advanced the deprecation levels on kotlinx-coroutines-test APIs (#4604).
  • Added lint functions that mark passing a Job to coroutine builders as deprecated (#4435).

Bug fixes and improvements

  • Added a callsInPlace(EXACTLY_ONCE) contract to runBlocking in code shared between JVM and Native (#4368).
  • Added a callsInPlace(EXACTLY_ONCE) contract to suspendCancellableCoroutine (#4574).
  • Fixed flowOn incorrectly handling ThreadContextElement updates (#4403).
  • Fixed exceptions in user-supplied Thread.UncaughtExceptionHandler instances causing the internal coroutines machinery to fail (#4516).
  • Fixed CoroutineDispatcher.asScheduler in the RxJava integration not cancelling outstanding work when a Worker gets cancelled, which led to memory leaks in some scenarios (#4615).
  • Fixed SharedFlow entering an invalid state when a subscriber and an emitter are cancelled simultaneously (#4583).
  • Fixed an R8 optimization leading to shareIn/stateIn coroutines getting garbage-collected (#4646). Thanks, @​solevic!

Small additions

  • Added CompletableDeferred.asDeferred for obtaining a read-only Deferred view (#4408).
  • Added SharedFlow.asFlow for obtaining a Flow view with hidden hot flow semantics (#4530). Thanks, @​g000sha256!
  • Added a StateFlow.collectLatest overload returning Nothing to assist with finding unreachable code (#4454).
  • Added ReceiveChannel.consumeTo for consuming a ReceiveChannel into a MutableCollection (#4520).
  • Added a StateFlow<T>.onSubscription overload returning a StateFlow<T>, similar to SharedFlow<T>.onSubscription returning SharedFlow<T> (#4275). Thanks, @​xit0c!
  • Added terminal Flow operators for collecting a Flow to a Map (#1541).

Changelog relative to version 1.11.0

No changes, only the version is increased.

1.11.0-rc02

Restored binary compatibility with 1.10.2 and older versions on Wasm/JS for usages of Promise-related functions (#4661).

1.11.0-rc01

Various

  • Kotlin was updated to 2.2.20 (#4545).
  • Improved the published jar files (#3842, #4599).
  • Various documentation improvements, including complete rewrites of structured concurrency and error handling-related KDoc (#4433, #4596).

Breaking changes and deprecations

... (truncated)

Changelog

Sourced from org.jetbrains.kotlinx:kotlinx-coroutines-test's changelog.

Version 1.11.0

Various

  • Kotlin was updated to 2.2.20 (#4545).
  • Improved the published jar files (#3842, #4599).
  • Various documentation improvements, including complete rewrites of structured concurrency and error handling-related KDoc (#4433, #4596).

Breaking changes and deprecations

  • Moved Promise-related functions from JS and Wasm/JS to the new web target. On Wasm/JS, this is a breaking change. Before the change, Promise on Wasm/JS could work with arbitrary Kotlin types, but now, only JsAny subtypes are accepted (#4563).
  • Changed handling of coroutine exceptions that can't be propagated on JS and Wasm/JS. Before, exceptions were logged, but now, they are reported to the JS runtime (#4451, #4631).
  • Deprecated using CoroutineDispatcher as the coroutine context key; now, ContinuationInterceptor has to be used instead (#4333).
  • Advanced the deprecation levels on kotlinx-coroutines-test APIs (#4604).
  • Added lint functions that mark passing a Job to coroutine builders as deprecated (#4435).

Bug fixes and improvements

  • Added a callsInPlace(EXACTLY_ONCE) contract to runBlocking in code shared between JVM and Native (#4368).
  • Added a callsInPlace(EXACTLY_ONCE) contract to suspendCancellableCoroutine (#4574).
  • Fixed flowOn incorrectly handling ThreadContextElement updates (#4403).
  • Fixed exceptions in user-supplied Thread.UncaughtExceptionHandler instances causing the internal coroutines machinery to fail (#4516).
  • Fixed CoroutineDispatcher.asScheduler in the RxJava integration not cancelling outstanding work when a Worker gets cancelled, which led to memory leaks in some scenarios (#4615).
  • Fixed SharedFlow entering an invalid state when a subscriber and an emitter are cancelled simultaneously (#4583).
  • Fixed an R8 optimization leading to shareIn/stateIn coroutines getting garbage-collected (#4646). Thanks, @​solevic!

Small additions

  • Added CompletableDeferred.asDeferred for obtaining a read-only Deferred view (#4408).
  • Added SharedFlow.asFlow for obtaining a Flow view with hidden hot flow semantics (#4530). Thanks, @​g000sha256!
  • Added a StateFlow.collectLatest overload returning Nothing to assist with finding unreachable code (#4454).
  • Added ReceiveChannel.consumeTo for consuming a ReceiveChannel into a MutableCollection (#4520).
  • Added a StateFlow<T>.onSubscription overload returning a StateFlow<T>, similar to SharedFlow<T>.onSubscription returning SharedFlow<T> (#4275). Thanks, @​xit0c!
  • Added terminal Flow operators for collecting a Flow to a Map (#1541).

Changelog relative to version 1.11.0

No changes, only the version is increased.

Version 1.11.0-rc02

Restored binary compatibility with 1.10.2 and older versions on Wasm/JS for usages of Promise-related functions (#4661).

Version 1.11.0-rc01

Various

  • Kotlin was updated to 2.2.20 (#4545).
  • Improved the published jar files (#3842, #4599).
  • Various documentation improvements, including complete rewrites of structured concurrency and error handling-related KDoc (#4433, #4596).

... (truncated)

Commits
  • 8564f65 Version 1.11.0
  • a4c6af9 Merge remote-tracking branch 'origin/master' into develop
  • ef917b4 KT-84955: mark apple x64 tagets as deprecated error (#4645)
  • 5ebc421 Update the release procedure description (#4670)
  • 95f46a0 Remove old maven repository settings (#4672)
  • b4f4f0a Fix package name of ToMapCollectionSamplesTest. (#4674)
  • 86738dc Added templates to the issue creation wizard (#4654)
  • 330fcc2 Version 1.11.0-rc02
  • e31cef6 Merge remote-tracking branch 'origin/master' into develop
  • dc6e9f6 Restore Promise-related functions on Wasm/JS as HIDDEN (#4661)
  • Additional commits viewable in compare view

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlinlang-mcp-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml index 723deb5..5b7988e 100644 --- a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml @@ -3,7 +3,7 @@ kotlin = "2.3.21" ktor = "3.3.3" mcp-kotlin = "0.12.0" serialization = "1.11.0" -coroutines = "1.10.2" +coroutines = "1.11.0" logback = "1.5.32" shadow = "9.4.1" From a967df1eac5b69df7d4df522612255f38073f69b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 May 2026 19:29:28 +0200 Subject: [PATCH 289/449] chore(deps): bump org.jetbrains.kotlinx:kotlinx-coroutines-test from 1.10.2 to 1.11.0 in /samples/weather-stdio-server (#759) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [org.jetbrains.kotlinx:kotlinx-coroutines-test](https://github.com/Kotlin/kotlinx.coroutines) from 1.10.2 to 1.11.0.
Release notes

Sourced from org.jetbrains.kotlinx:kotlinx-coroutines-test's releases.

1.11.0

Various

  • Kotlin was updated to 2.2.20 (#4545).
  • Improved the published jar files (#3842, #4599).
  • Various documentation improvements, including complete rewrites of structured concurrency and error handling-related KDoc (#4433, #4596).

Breaking changes and deprecations

  • Moved Promise-related functions from JS and Wasm/JS to the new web target. On Wasm/JS, this is a breaking change. Before the change, Promise on Wasm/JS could work with arbitrary Kotlin types, but now, only JsAny subtypes are accepted (#4563).
  • Changed handling of coroutine exceptions that can't be propagated on JS and Wasm/JS. B efore, exceptions were logged, but now, they are reported to the JS runtime (#4451, #4631).
  • Deprecated using CoroutineDispatcher as the coroutine context key; now, ContinuationInterceptor has to be used instead (#4333).
  • Advanced the deprecation levels on kotlinx-coroutines-test APIs (#4604).
  • Added lint functions that mark passing a Job to coroutine builders as deprecated (#4435).

Bug fixes and improvements

  • Added a callsInPlace(EXACTLY_ONCE) contract to runBlocking in code shared between JVM and Native (#4368).
  • Added a callsInPlace(EXACTLY_ONCE) contract to suspendCancellableCoroutine (#4574).
  • Fixed flowOn incorrectly handling ThreadContextElement updates (#4403).
  • Fixed exceptions in user-supplied Thread.UncaughtExceptionHandler instances causing the internal coroutines machinery to fail (#4516).
  • Fixed CoroutineDispatcher.asScheduler in the RxJava integration not cancelling outstanding work when a Worker gets cancelled, which led to memory leaks in some scenarios (#4615).
  • Fixed SharedFlow entering an invalid state when a subscriber and an emitter are cancelled simultaneously (#4583).
  • Fixed an R8 optimization leading to shareIn/stateIn coroutines getting garbage-collected (#4646). Thanks, @​solevic!

Small additions

  • Added CompletableDeferred.asDeferred for obtaining a read-only Deferred view (#4408).
  • Added SharedFlow.asFlow for obtaining a Flow view with hidden hot flow semantics (#4530). Thanks, @​g000sha256!
  • Added a StateFlow.collectLatest overload returning Nothing to assist with finding unreachable code (#4454).
  • Added ReceiveChannel.consumeTo for consuming a ReceiveChannel into a MutableCollection (#4520).
  • Added a StateFlow<T>.onSubscription overload returning a StateFlow<T>, similar to SharedFlow<T>.onSubscription returning SharedFlow<T> (#4275). Thanks, @​xit0c!
  • Added terminal Flow operators for collecting a Flow to a Map (#1541).

Changelog relative to version 1.11.0

No changes, only the version is increased.

1.11.0-rc02

Restored binary compatibility with 1.10.2 and older versions on Wasm/JS for usages of Promise-related functions (#4661).

1.11.0-rc01

Various

  • Kotlin was updated to 2.2.20 (#4545).
  • Improved the published jar files (#3842, #4599).
  • Various documentation improvements, including complete rewrites of structured concurrency and error handling-related KDoc (#4433, #4596).

Breaking changes and deprecations

... (truncated)

Changelog

Sourced from org.jetbrains.kotlinx:kotlinx-coroutines-test's changelog.

Version 1.11.0

Various

  • Kotlin was updated to 2.2.20 (#4545).
  • Improved the published jar files (#3842, #4599).
  • Various documentation improvements, including complete rewrites of structured concurrency and error handling-related KDoc (#4433, #4596).

Breaking changes and deprecations

  • Moved Promise-related functions from JS and Wasm/JS to the new web target. On Wasm/JS, this is a breaking change. Before the change, Promise on Wasm/JS could work with arbitrary Kotlin types, but now, only JsAny subtypes are accepted (#4563).
  • Changed handling of coroutine exceptions that can't be propagated on JS and Wasm/JS. Before, exceptions were logged, but now, they are reported to the JS runtime (#4451, #4631).
  • Deprecated using CoroutineDispatcher as the coroutine context key; now, ContinuationInterceptor has to be used instead (#4333).
  • Advanced the deprecation levels on kotlinx-coroutines-test APIs (#4604).
  • Added lint functions that mark passing a Job to coroutine builders as deprecated (#4435).

Bug fixes and improvements

  • Added a callsInPlace(EXACTLY_ONCE) contract to runBlocking in code shared between JVM and Native (#4368).
  • Added a callsInPlace(EXACTLY_ONCE) contract to suspendCancellableCoroutine (#4574).
  • Fixed flowOn incorrectly handling ThreadContextElement updates (#4403).
  • Fixed exceptions in user-supplied Thread.UncaughtExceptionHandler instances causing the internal coroutines machinery to fail (#4516).
  • Fixed CoroutineDispatcher.asScheduler in the RxJava integration not cancelling outstanding work when a Worker gets cancelled, which led to memory leaks in some scenarios (#4615).
  • Fixed SharedFlow entering an invalid state when a subscriber and an emitter are cancelled simultaneously (#4583).
  • Fixed an R8 optimization leading to shareIn/stateIn coroutines getting garbage-collected (#4646). Thanks, @​solevic!

Small additions

  • Added CompletableDeferred.asDeferred for obtaining a read-only Deferred view (#4408).
  • Added SharedFlow.asFlow for obtaining a Flow view with hidden hot flow semantics (#4530). Thanks, @​g000sha256!
  • Added a StateFlow.collectLatest overload returning Nothing to assist with finding unreachable code (#4454).
  • Added ReceiveChannel.consumeTo for consuming a ReceiveChannel into a MutableCollection (#4520).
  • Added a StateFlow<T>.onSubscription overload returning a StateFlow<T>, similar to SharedFlow<T>.onSubscription returning SharedFlow<T> (#4275). Thanks, @​xit0c!
  • Added terminal Flow operators for collecting a Flow to a Map (#1541).

Changelog relative to version 1.11.0

No changes, only the version is increased.

Version 1.11.0-rc02

Restored binary compatibility with 1.10.2 and older versions on Wasm/JS for usages of Promise-related functions (#4661).

Version 1.11.0-rc01

Various

  • Kotlin was updated to 2.2.20 (#4545).
  • Improved the published jar files (#3842, #4599).
  • Various documentation improvements, including complete rewrites of structured concurrency and error handling-related KDoc (#4433, #4596).

... (truncated)

Commits
  • 8564f65 Version 1.11.0
  • a4c6af9 Merge remote-tracking branch 'origin/master' into develop
  • ef917b4 KT-84955: mark apple x64 tagets as deprecated error (#4645)
  • 5ebc421 Update the release procedure description (#4670)
  • 95f46a0 Remove old maven repository settings (#4672)
  • b4f4f0a Fix package name of ToMapCollectionSamplesTest. (#4674)
  • 86738dc Added templates to the issue creation wizard (#4654)
  • 330fcc2 Version 1.11.0-rc02
  • e31cef6 Merge remote-tracking branch 'origin/master' into develop
  • dc6e9f6 Restore Promise-related functions on Wasm/JS as HIDDEN (#4661)
  • Additional commits viewable in compare view

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/weather-stdio-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/weather-stdio-server/gradle/libs.versions.toml b/samples/weather-stdio-server/gradle/libs.versions.toml index 1fc2e6a..8eee298 100644 --- a/samples/weather-stdio-server/gradle/libs.versions.toml +++ b/samples/weather-stdio-server/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -coroutines = "1.10.2" +coroutines = "1.11.0" kotlin = "2.3.21" ktor = "3.3.3" logging = "8.0.02" From d6027fd46d8b17cc4b5587041d5a2128b2f528bc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 May 2026 20:09:56 +0200 Subject: [PATCH 290/449] chore(deps): bump the kotlinx group across 1 directory with 3 updates (#760) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the kotlinx group with 3 updates in the / directory: [org.jetbrains.kotlinx:kotlinx-coroutines-core](https://github.com/Kotlin/kotlinx.coroutines), [org.jetbrains.kotlinx:kotlinx-coroutines-core-wasm-js](https://github.com/Kotlin/kotlinx.coroutines) and [org.jetbrains.kotlinx:kotlinx-coroutines-test](https://github.com/Kotlin/kotlinx.coroutines). Updates `org.jetbrains.kotlinx:kotlinx-coroutines-core` from 1.10.2 to 1.11.0
Release notes

Sourced from org.jetbrains.kotlinx:kotlinx-coroutines-core's releases.

1.11.0

Various

  • Kotlin was updated to 2.2.20 (#4545).
  • Improved the published jar files (#3842, #4599).
  • Various documentation improvements, including complete rewrites of structured concurrency and error handling-related KDoc (#4433, #4596).

Breaking changes and deprecations

  • Moved Promise-related functions from JS and Wasm/JS to the new web target. On Wasm/JS, this is a breaking change. Before the change, Promise on Wasm/JS could work with arbitrary Kotlin types, but now, only JsAny subtypes are accepted (#4563).
  • Changed handling of coroutine exceptions that can't be propagated on JS and Wasm/JS. B efore, exceptions were logged, but now, they are reported to the JS runtime (#4451, #4631).
  • Deprecated using CoroutineDispatcher as the coroutine context key; now, ContinuationInterceptor has to be used instead (#4333).
  • Advanced the deprecation levels on kotlinx-coroutines-test APIs (#4604).
  • Added lint functions that mark passing a Job to coroutine builders as deprecated (#4435).

Bug fixes and improvements

  • Added a callsInPlace(EXACTLY_ONCE) contract to runBlocking in code shared between JVM and Native (#4368).
  • Added a callsInPlace(EXACTLY_ONCE) contract to suspendCancellableCoroutine (#4574).
  • Fixed flowOn incorrectly handling ThreadContextElement updates (#4403).
  • Fixed exceptions in user-supplied Thread.UncaughtExceptionHandler instances causing the internal coroutines machinery to fail (#4516).
  • Fixed CoroutineDispatcher.asScheduler in the RxJava integration not cancelling outstanding work when a Worker gets cancelled, which led to memory leaks in some scenarios (#4615).
  • Fixed SharedFlow entering an invalid state when a subscriber and an emitter are cancelled simultaneously (#4583).
  • Fixed an R8 optimization leading to shareIn/stateIn coroutines getting garbage-collected (#4646). Thanks, @​solevic!

Small additions

  • Added CompletableDeferred.asDeferred for obtaining a read-only Deferred view (#4408).
  • Added SharedFlow.asFlow for obtaining a Flow view with hidden hot flow semantics (#4530). Thanks, @​g000sha256!
  • Added a StateFlow.collectLatest overload returning Nothing to assist with finding unreachable code (#4454).
  • Added ReceiveChannel.consumeTo for consuming a ReceiveChannel into a MutableCollection (#4520).
  • Added a StateFlow<T>.onSubscription overload returning a StateFlow<T>, similar to SharedFlow<T>.onSubscription returning SharedFlow<T> (#4275). Thanks, @​xit0c!
  • Added terminal Flow operators for collecting a Flow to a Map (#1541).

Changelog relative to version 1.11.0

No changes, only the version is increased.

1.11.0-rc02

Restored binary compatibility with 1.10.2 and older versions on Wasm/JS for usages of Promise-related functions (#4661).

1.11.0-rc01

Various

  • Kotlin was updated to 2.2.20 (#4545).
  • Improved the published jar files (#3842, #4599).
  • Various documentation improvements, including complete rewrites of structured concurrency and error handling-related KDoc (#4433, #4596).

Breaking changes and deprecations

... (truncated)

Changelog

Sourced from org.jetbrains.kotlinx:kotlinx-coroutines-core's changelog.

Version 1.11.0

Various

  • Kotlin was updated to 2.2.20 (#4545).
  • Improved the published jar files (#3842, #4599).
  • Various documentation improvements, including complete rewrites of structured concurrency and error handling-related KDoc (#4433, #4596).

Breaking changes and deprecations

  • Moved Promise-related functions from JS and Wasm/JS to the new web target. On Wasm/JS, this is a breaking change. Before the change, Promise on Wasm/JS could work with arbitrary Kotlin types, but now, only JsAny subtypes are accepted (#4563).
  • Changed handling of coroutine exceptions that can't be propagated on JS and Wasm/JS. Before, exceptions were logged, but now, they are reported to the JS runtime (#4451, #4631).
  • Deprecated using CoroutineDispatcher as the coroutine context key; now, ContinuationInterceptor has to be used instead (#4333).
  • Advanced the deprecation levels on kotlinx-coroutines-test APIs (#4604).
  • Added lint functions that mark passing a Job to coroutine builders as deprecated (#4435).

Bug fixes and improvements

  • Added a callsInPlace(EXACTLY_ONCE) contract to runBlocking in code shared between JVM and Native (#4368).
  • Added a callsInPlace(EXACTLY_ONCE) contract to suspendCancellableCoroutine (#4574).
  • Fixed flowOn incorrectly handling ThreadContextElement updates (#4403).
  • Fixed exceptions in user-supplied Thread.UncaughtExceptionHandler instances causing the internal coroutines machinery to fail (#4516).
  • Fixed CoroutineDispatcher.asScheduler in the RxJava integration not cancelling outstanding work when a Worker gets cancelled, which led to memory leaks in some scenarios (#4615).
  • Fixed SharedFlow entering an invalid state when a subscriber and an emitter are cancelled simultaneously (#4583).
  • Fixed an R8 optimization leading to shareIn/stateIn coroutines getting garbage-collected (#4646). Thanks, @​solevic!

Small additions

  • Added CompletableDeferred.asDeferred for obtaining a read-only Deferred view (#4408).
  • Added SharedFlow.asFlow for obtaining a Flow view with hidden hot flow semantics (#4530). Thanks, @​g000sha256!
  • Added a StateFlow.collectLatest overload returning Nothing to assist with finding unreachable code (#4454).
  • Added ReceiveChannel.consumeTo for consuming a ReceiveChannel into a MutableCollection (#4520).
  • Added a StateFlow<T>.onSubscription overload returning a StateFlow<T>, similar to SharedFlow<T>.onSubscription returning SharedFlow<T> (#4275). Thanks, @​xit0c!
  • Added terminal Flow operators for collecting a Flow to a Map (#1541).

Changelog relative to version 1.11.0

No changes, only the version is increased.

Version 1.11.0-rc02

Restored binary compatibility with 1.10.2 and older versions on Wasm/JS for usages of Promise-related functions (#4661).

Version 1.11.0-rc01

Various

  • Kotlin was updated to 2.2.20 (#4545).
  • Improved the published jar files (#3842, #4599).
  • Various documentation improvements, including complete rewrites of structured concurrency and error handling-related KDoc (#4433, #4596).

... (truncated)

Commits
  • 8564f65 Version 1.11.0
  • a4c6af9 Merge remote-tracking branch 'origin/master' into develop
  • ef917b4 KT-84955: mark apple x64 tagets as deprecated error (#4645)
  • 5ebc421 Update the release procedure description (#4670)
  • 95f46a0 Remove old maven repository settings (#4672)
  • b4f4f0a Fix package name of ToMapCollectionSamplesTest. (#4674)
  • 86738dc Added templates to the issue creation wizard (#4654)
  • 330fcc2 Version 1.11.0-rc02
  • e31cef6 Merge remote-tracking branch 'origin/master' into develop
  • dc6e9f6 Restore Promise-related functions on Wasm/JS as HIDDEN (#4661)
  • Additional commits viewable in compare view

Updates `org.jetbrains.kotlinx:kotlinx-coroutines-core-wasm-js` from 1.10.2 to 1.11.0
Release notes

Sourced from org.jetbrains.kotlinx:kotlinx-coroutines-core-wasm-js's releases.

1.11.0

Various

  • Kotlin was updated to 2.2.20 (#4545).
  • Improved the published jar files (#3842, #4599).
  • Various documentation improvements, including complete rewrites of structured concurrency and error handling-related KDoc (#4433, #4596).

Breaking changes and deprecations

  • Moved Promise-related functions from JS and Wasm/JS to the new web target. On Wasm/JS, this is a breaking change. Before the change, Promise on Wasm/JS could work with arbitrary Kotlin types, but now, only JsAny subtypes are accepted (#4563).
  • Changed handling of coroutine exceptions that can't be propagated on JS and Wasm/JS. B efore, exceptions were logged, but now, they are reported to the JS runtime (#4451, #4631).
  • Deprecated using CoroutineDispatcher as the coroutine context key; now, ContinuationInterceptor has to be used instead (#4333).
  • Advanced the deprecation levels on kotlinx-coroutines-test APIs (#4604).
  • Added lint functions that mark passing a Job to coroutine builders as deprecated (#4435).

Bug fixes and improvements

  • Added a callsInPlace(EXACTLY_ONCE) contract to runBlocking in code shared between JVM and Native (#4368).
  • Added a callsInPlace(EXACTLY_ONCE) contract to suspendCancellableCoroutine (#4574).
  • Fixed flowOn incorrectly handling ThreadContextElement updates (#4403).
  • Fixed exceptions in user-supplied Thread.UncaughtExceptionHandler instances causing the internal coroutines machinery to fail (#4516).
  • Fixed CoroutineDispatcher.asScheduler in the RxJava integration not cancelling outstanding work when a Worker gets cancelled, which led to memory leaks in some scenarios (#4615).
  • Fixed SharedFlow entering an invalid state when a subscriber and an emitter are cancelled simultaneously (#4583).
  • Fixed an R8 optimization leading to shareIn/stateIn coroutines getting garbage-collected (#4646). Thanks, @​solevic!

Small additions

  • Added CompletableDeferred.asDeferred for obtaining a read-only Deferred view (#4408).
  • Added SharedFlow.asFlow for obtaining a Flow view with hidden hot flow semantics (#4530). Thanks, @​g000sha256!
  • Added a StateFlow.collectLatest overload returning Nothing to assist with finding unreachable code (#4454).
  • Added ReceiveChannel.consumeTo for consuming a ReceiveChannel into a MutableCollection (#4520).
  • Added a StateFlow<T>.onSubscription overload returning a StateFlow<T>, similar to SharedFlow<T>.onSubscription returning SharedFlow<T> (#4275). Thanks, @​xit0c!
  • Added terminal Flow operators for collecting a Flow to a Map (#1541).

Changelog relative to version 1.11.0

No changes, only the version is increased.

1.11.0-rc02

Restored binary compatibility with 1.10.2 and older versions on Wasm/JS for usages of Promise-related functions (#4661).

1.11.0-rc01

Various

  • Kotlin was updated to 2.2.20 (#4545).
  • Improved the published jar files (#3842, #4599).
  • Various documentation improvements, including complete rewrites of structured concurrency and error handling-related KDoc (#4433, #4596).

Breaking changes and deprecations

... (truncated)

Changelog

Sourced from org.jetbrains.kotlinx:kotlinx-coroutines-core-wasm-js's changelog.

Version 1.11.0

Various

  • Kotlin was updated to 2.2.20 (#4545).
  • Improved the published jar files (#3842, #4599).
  • Various documentation improvements, including complete rewrites of structured concurrency and error handling-related KDoc (#4433, #4596).

Breaking changes and deprecations

  • Moved Promise-related functions from JS and Wasm/JS to the new web target. On Wasm/JS, this is a breaking change. Before the change, Promise on Wasm/JS could work with arbitrary Kotlin types, but now, only JsAny subtypes are accepted (#4563).
  • Changed handling of coroutine exceptions that can't be propagated on JS and Wasm/JS. Before, exceptions were logged, but now, they are reported to the JS runtime (#4451, #4631).
  • Deprecated using CoroutineDispatcher as the coroutine context key; now, ContinuationInterceptor has to be used instead (#4333).
  • Advanced the deprecation levels on kotlinx-coroutines-test APIs (#4604).
  • Added lint functions that mark passing a Job to coroutine builders as deprecated (#4435).

Bug fixes and improvements

  • Added a callsInPlace(EXACTLY_ONCE) contract to runBlocking in code shared between JVM and Native (#4368).
  • Added a callsInPlace(EXACTLY_ONCE) contract to suspendCancellableCoroutine (#4574).
  • Fixed flowOn incorrectly handling ThreadContextElement updates (#4403).
  • Fixed exceptions in user-supplied Thread.UncaughtExceptionHandler instances causing the internal coroutines machinery to fail (#4516).
  • Fixed CoroutineDispatcher.asScheduler in the RxJava integration not cancelling outstanding work when a Worker gets cancelled, which led to memory leaks in some scenarios (#4615).
  • Fixed SharedFlow entering an invalid state when a subscriber and an emitter are cancelled simultaneously (#4583).
  • Fixed an R8 optimization leading to shareIn/stateIn coroutines getting garbage-collected (#4646). Thanks, @​solevic!

Small additions

  • Added CompletableDeferred.asDeferred for obtaining a read-only Deferred view (#4408).
  • Added SharedFlow.asFlow for obtaining a Flow view with hidden hot flow semantics (#4530). Thanks, @​g000sha256!
  • Added a StateFlow.collectLatest overload returning Nothing to assist with finding unreachable code (#4454).
  • Added ReceiveChannel.consumeTo for consuming a ReceiveChannel into a MutableCollection (#4520).
  • Added a StateFlow<T>.onSubscription overload returning a StateFlow<T>, similar to SharedFlow<T>.onSubscription returning SharedFlow<T> (#4275). Thanks, @​xit0c!
  • Added terminal Flow operators for collecting a Flow to a Map (#1541).

Changelog relative to version 1.11.0

No changes, only the version is increased.

Version 1.11.0-rc02

Restored binary compatibility with 1.10.2 and older versions on Wasm/JS for usages of Promise-related functions (#4661).

Version 1.11.0-rc01

Various

  • Kotlin was updated to 2.2.20 (#4545).
  • Improved the published jar files (#3842, #4599).
  • Various documentation improvements, including complete rewrites of structured concurrency and error handling-related KDoc (#4433, #4596).

... (truncated)

Commits
  • 8564f65 Version 1.11.0
  • a4c6af9 Merge remote-tracking branch 'origin/master' into develop
  • ef917b4 KT-84955: mark apple x64 tagets as deprecated error (#4645)
  • 5ebc421 Update the release procedure description (#4670)
  • 95f46a0 Remove old maven repository settings (#4672)
  • b4f4f0a Fix package name of ToMapCollectionSamplesTest. (#4674)
  • 86738dc Added templates to the issue creation wizard (#4654)
  • 330fcc2 Version 1.11.0-rc02
  • e31cef6 Merge remote-tracking branch 'origin/master' into develop
  • dc6e9f6 Restore Promise-related functions on Wasm/JS as HIDDEN (#4661)
  • Additional commits viewable in compare view

Updates `org.jetbrains.kotlinx:kotlinx-coroutines-test` from 1.10.2 to 1.11.0
Release notes

Sourced from org.jetbrains.kotlinx:kotlinx-coroutines-test's releases.

1.11.0

Various

  • Kotlin was updated to 2.2.20 (#4545).
  • Improved the published jar files (#3842, #4599).
  • Various documentation improvements, including complete rewrites of structured concurrency and error handling-related KDoc (#4433, #4596).

Breaking changes and deprecations

  • Moved Promise-related functions from JS and Wasm/JS to the new web target. On Wasm/JS, this is a breaking change. Before the change, Promise on Wasm/JS could work with arbitrary Kotlin types, but now, only JsAny subtypes are accepted (#4563).
  • Changed handling of coroutine exceptions that can't be propagated on JS and Wasm/JS. B efore, exceptions were logged, but now, they are reported to the JS runtime (#4451, #4631).
  • Deprecated using CoroutineDispatcher as the coroutine context key; now, ContinuationInterceptor has to be used instead (#4333).
  • Advanced the deprecation levels on kotlinx-coroutines-test APIs (#4604).
  • Added lint functions that mark passing a Job to coroutine builders as deprecated (#4435).

Bug fixes and improvements

  • Added a callsInPlace(EXACTLY_ONCE) contract to runBlocking in code shared between JVM and Native (#4368).
  • Added a callsInPlace(EXACTLY_ONCE) contract to suspendCancellableCoroutine (#4574).
  • Fixed flowOn incorrectly handling ThreadContextElement updates (#4403).
  • Fixed exceptions in user-supplied Thread.UncaughtExceptionHandler instances causing the internal coroutines machinery to fail (#4516).
  • Fixed CoroutineDispatcher.asScheduler in the RxJava integration not cancelling outstanding work when a Worker gets cancelled, which led to memory leaks in some scenarios (#4615).
  • Fixed SharedFlow entering an invalid state when a subscriber and an emitter are cancelled simultaneously (#4583).
  • Fixed an R8 optimization leading to shareIn/stateIn coroutines getting garbage-collected (#4646). Thanks, @​solevic!

Small additions

  • Added CompletableDeferred.asDeferred for obtaining a read-only Deferred view (#4408).
  • Added SharedFlow.asFlow for obtaining a Flow view with hidden hot flow semantics (#4530). Thanks, @​g000sha256!
  • Added a StateFlow.collectLatest overload returning Nothing to assist with finding unreachable code (#4454).
  • Added ReceiveChannel.consumeTo for consuming a ReceiveChannel into a MutableCollection (#4520).
  • Added a StateFlow<T>.onSubscription overload returning a StateFlow<T>, similar to SharedFlow<T>.onSubscription returning SharedFlow<T> (#4275). Thanks, @​xit0c!
  • Added terminal Flow operators for collecting a Flow to a Map (#1541).

Changelog relative to version 1.11.0

No changes, only the version is increased.

1.11.0-rc02

Restored binary compatibility with 1.10.2 and older versions on Wasm/JS for usages of Promise-related functions (#4661).

1.11.0-rc01

Various

  • Kotlin was updated to 2.2.20 (#4545).
  • Improved the published jar files (#3842, #4599).
  • Various documentation improvements, including complete rewrites of structured concurrency and error handling-related KDoc (#4433, #4596).

Breaking changes and deprecations

... (truncated)

Changelog

Sourced from org.jetbrains.kotlinx:kotlinx-coroutines-test's changelog.

Version 1.11.0

Various

  • Kotlin was updated to 2.2.20 (#4545).
  • Improved the published jar files (#3842, #4599).
  • Various documentation improvements, including complete rewrites of structured concurrency and error handling-related KDoc (#4433, #4596).

Breaking changes and deprecations

  • Moved Promise-related functions from JS and Wasm/JS to the new web target. On Wasm/JS, this is a breaking change. Before the change, Promise on Wasm/JS could work with arbitrary Kotlin types, but now, only JsAny subtypes are accepted (#4563).
  • Changed handling of coroutine exceptions that can't be propagated on JS and Wasm/JS. Before, exceptions were logged, but now, they are reported to the JS runtime (#4451, #4631).
  • Deprecated using CoroutineDispatcher as the coroutine context key; now, ContinuationInterceptor has to be used instead (#4333).
  • Advanced the deprecation levels on kotlinx-coroutines-test APIs (#4604).
  • Added lint functions that mark passing a Job to coroutine builders as deprecated (#4435).

Bug fixes and improvements

  • Added a callsInPlace(EXACTLY_ONCE) contract to runBlocking in code shared between JVM and Native (#4368).
  • Added a callsInPlace(EXACTLY_ONCE) contract to suspendCancellableCoroutine (#4574).
  • Fixed flowOn incorrectly handling ThreadContextElement updates (#4403).
  • Fixed exceptions in user-supplied Thread.UncaughtExceptionHandler instances causing the internal coroutines machinery to fail (#4516).
  • Fixed CoroutineDispatcher.asScheduler in the RxJava integration not cancelling outstanding work when a Worker gets cancelled, which led to memory leaks in some scenarios (#4615).
  • Fixed SharedFlow entering an invalid state when a subscriber and an emitter are cancelled simultaneously (#4583).
  • Fixed an R8 optimization leading to shareIn/stateIn coroutines getting garbage-collected (#4646). Thanks, @​solevic!

Small additions

  • Added CompletableDeferred.asDeferred for obtaining a read-only Deferred view (#4408).
  • Added SharedFlow.asFlow for obtaining a Flow view with hidden hot flow semantics (#4530). Thanks, @​g000sha256!
  • Added a StateFlow.collectLatest overload returning Nothing to assist with finding unreachable code (#4454).
  • Added ReceiveChannel.consumeTo for consuming a ReceiveChannel into a MutableCollection (#4520).
  • Added a StateFlow<T>.onSubscription overload returning a StateFlow<T>, similar to SharedFlow<T>.onSubscription returning SharedFlow<T> (#4275). Thanks, @​xit0c!
  • Added terminal Flow operators for collecting a Flow to a Map (#1541).

Changelog relative to version 1.11.0

No changes, only the version is increased.

Version 1.11.0-rc02

Restored binary compatibility with 1.10.2 and older versions on Wasm/JS for usages of Promise-related functions (#4661).

Version 1.11.0-rc01

Various

  • Kotlin was updated to 2.2.20 (#4545).
  • Improved the published jar files (#3842, #4599).
  • Various documentation improvements, including complete rewrites of structured concurrency and error handling-related KDoc (#4433, #4596).

... (truncated)

Commits
  • 8564f65 Version 1.11.0
  • a4c6af9 Merge remote-tracking branch 'origin/master' into develop
  • ef917b4 KT-84955: mark apple x64 tagets as deprecated error (#4645)
  • 5ebc421 Update the release procedure description (#4670)
  • 95f46a0 Remove old maven repository settings (#4672)
  • b4f4f0a Fix package name of ToMapCollectionSamplesTest. (#4674)
  • 86738dc Added templates to the issue creation wizard (#4654)
  • 330fcc2 Version 1.11.0-rc02
  • e31cef6 Merge remote-tracking branch 'origin/master' into develop
  • dc6e9f6 Restore Promise-related functions on Wasm/JS as HIDDEN (#4661)
  • Additional commits viewable in compare view

Updates `org.jetbrains.kotlinx:kotlinx-coroutines-core-wasm-js` from 1.10.2 to 1.11.0
Release notes

Sourced from org.jetbrains.kotlinx:kotlinx-coroutines-core-wasm-js's releases.

1.11.0

Various

  • Kotlin was updated to 2.2.20 (#4545).
  • Improved the published jar files (#3842, #4599).
  • Various documentation improvements, including complete rewrites of structured concurrency and error handling-related KDoc (#4433, #4596).

Breaking changes and deprecations

  • Moved Promise-related functions from JS and Wasm/JS to the new web target. On Wasm/JS, this is a breaking change. Before the change, Promise on Wasm/JS could work with arbitrary Kotlin types, but now, only JsAny subtypes are accepted (#4563).
  • Changed handling of coroutine exceptions that can't be propagated on JS and Wasm/JS. B efore, exceptions were logged, but now, they are reported to the JS runtime (#4451, #4631).
  • Deprecated using CoroutineDispatcher as the coroutine context key; now, ContinuationInterceptor has to be used instead (#4333).
  • Advanced the deprecation levels on kotlinx-coroutines-test APIs (#4604).
  • Added lint functions that mark passing a Job to coroutine builders as deprecated (#4435).

Bug fixes and improvements

  • Added a callsInPlace(EXACTLY_ONCE) contract to runBlocking in code shared between JVM and Native (#4368).
  • Added a callsInPlace(EXACTLY_ONCE) contract to suspendCancellableCoroutine (#4574).
  • Fixed flowOn incorrectly handling ThreadContextElement updates (#4403).
  • Fixed exceptions in user-supplied Thread.UncaughtExceptionHandler instances causing the internal coroutines machinery to fail (#4516).
  • Fixed CoroutineDispatcher.asScheduler in the RxJava integration not cancelling outstanding work when a Worker gets cancelled, which led to memory leaks in some scenarios (#4615).
  • Fixed SharedFlow entering an invalid state when a subscriber and an emitter are cancelled simultaneously (#4583).
  • Fixed an R8 optimization leading to shareIn/stateIn coroutines getting garbage-collected (#4646). Thanks, @​solevic!

Small additions

  • Added CompletableDeferred.asDeferred for obtaining a read-only Deferred view (#4408).
  • Added SharedFlow.asFlow for obtaining a Flow view with hidden hot flow semantics (#4530). Thanks, @​g000sha256!
  • Added a StateFlow.collectLatest overload returning Nothing to assist with finding unreachable code (#4454).
  • Added ReceiveChannel.consumeTo for consuming a ReceiveChannel into a MutableCollection (#4520).
  • Added a StateFlow<T>.onSubscription overload returning a StateFlow<T>, similar to SharedFlow<T>.onSubscription returning SharedFlow<T> (#4275). Thanks, @​xit0c!
  • Added terminal Flow operators for collecting a Flow to a Map (#1541).

Changelog relative to version 1.11.0

No changes, only the version is increased.

1.11.0-rc02

Restored binary compatibility with 1.10.2 and older versions on Wasm/JS for usages of Promise-related functions (#4661).

1.11.0-rc01

Various

  • Kotlin was updated to 2.2.20 (#4545).
  • Improved the published jar files (#3842, #4599).
  • Various documentation improvements, including complete rewrites of structured concurrency and error handling-related KDoc (#4433, #4596).

Breaking changes and deprecations

... (truncated)

Changelog

Sourced from org.jetbrains.kotlinx:kotlinx-coroutines-core-wasm-js's changelog.

Version 1.11.0

Various

  • Kotlin was updated to 2.2.20 (#4545).
  • Improved the published jar files (#3842, #4599).
  • Various documentation improvements, including complete rewrites of structured concurrency and error handling-related KDoc (#4433, #4596).

Breaking changes and deprecations

  • Moved Promise-related functions from JS and Wasm/JS to the new web target. On Wasm/JS, this is a breaking change. Before the change, Promise on Wasm/JS could work with arbitrary Kotlin types, but now, only JsAny subtypes are accepted (#4563).
  • Changed handling of coroutine exceptions that can't be propagated on JS and Wasm/JS. Before, exceptions were logged, but now, they are reported to the JS runtime (#4451, #4631).
  • Deprecated using CoroutineDispatcher as the coroutine context key; now, ContinuationInterceptor has to be used instead (#4333).
  • Advanced the deprecation levels on kotlinx-coroutines-test APIs (#4604).
  • Added lint functions that mark passing a Job to coroutine builders as deprecated (#4435).

Bug fixes and improvements

  • Added a callsInPlace(EXACTLY_ONCE) contract to runBlocking in code shared between JVM and Native (#4368).
  • Added a callsInPlace(EXACTLY_ONCE) contract to suspendCancellableCoroutine (#4574).
  • Fixed flowOn incorrectly handling ThreadContextElement updates (#4403).
  • Fixed exceptions in user-supplied Thread.UncaughtExceptionHandler instances causing the internal coroutines machinery to fail (#4516).
  • Fixed CoroutineDispatcher.asScheduler in the RxJava integration not cancelling outstanding work when a Worker gets cancelled, which led to memory leaks in some scenarios (#4615).
  • Fixed SharedFlow entering an invalid state when a subscriber and an emitter are cancelled simultaneously (#4583).
  • Fixed an R8 optimization leading to shareIn/stateIn coroutines getting garbage-collected (#4646). Thanks, @​solevic!

Small additions

  • Added CompletableDeferred.asDeferred for obtaining a read-only Deferred view (#4408).
  • Added SharedFlow.asFlow for obtaining a Flow view with hidden hot flow semantics (#4530). Thanks, @​g000sha256!
  • Added a StateFlow.collectLatest overload returning Nothing to assist with finding unreachable code (#4454).
  • Added ReceiveChannel.consumeTo for consuming a ReceiveChannel into a MutableCollection (#4520).
  • Added a StateFlow<T>.onSubscription overload returning a StateFlow<T>, similar to SharedFlow<T>.onSubscription returning SharedFlow<T> (#4275). Thanks, @​xit0c!
  • Added terminal Flow operators for collecting a Flow to a Map (#1541).

Changelog relative to version 1.11.0

No changes, only the version is increased.

Version 1.11.0-rc02

Restored binary compatibility with 1.10.2 and older versions on Wasm/JS for usages of Promise-related functions (#4661).

Version 1.11.0-rc01

Various

  • Kotlin was updated to 2.2.20 (#4545).
  • Improved the published jar files (#3842, #4599).
  • Various documentation improvements, including complete rewrites of structured concurrency and error handling-related KDoc (#4433, #4596).

... (truncated)

Commits
  • 8564f65 Version 1.11.0
  • a4c6af9 Merge remote-tracking branch 'origin/master' into develop
  • ef917b4 KT-84955: mark apple x64 tagets as deprecated error (#4645)
  • 5ebc421 Update the release procedure description (#4670)
  • 95f46a0 Remove old maven repository settings (#4672)
  • b4f4f0a Fix package name of ToMapCollectionSamplesTest. (#4674)
  • 86738dc Added templates to the issue creation wizard (#4654)
  • 330fcc2 Version 1.11.0-rc02
  • e31cef6 Merge remote-tracking branch 'origin/master' into develop
  • dc6e9f6 Restore Promise-related functions on Wasm/JS as HIDDEN (#4661)
  • Additional commits viewable in compare view

Updates `org.jetbrains.kotlinx:kotlinx-coroutines-test` from 1.10.2 to 1.11.0
Release notes

Sourced from org.jetbrains.kotlinx:kotlinx-coroutines-test's releases.

1.11.0

Various

  • Kotlin was updated to 2.2.20 (#4545).
  • Improved the published jar files (#3842, #4599).
  • Various documentation improvements, including complete rewrites of structured concurrency and error handling-related KDoc (#4433, #4596).

Breaking changes and deprecations

  • Moved Promise-related functions from JS and Wasm/JS to the new web Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 63a75c5..a1f2cc0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,7 +14,7 @@ netty = "4.2.13.Final" # libraries version awaitility = "4.3.0" collections-immutable = "0.4.0" -coroutines = "1.10.2" +coroutines = "1.11.0" kotest = "6.1.11" kotlinx-io = "0.9.0" ktor = "3.3.3" From 44e012e4eefc9ad1128a814c0be09769613367ba Mon Sep 17 00:00:00 2001 From: johny <69754601+jskjw157@users.noreply.github.com> Date: Sat, 16 May 2026 01:44:16 +0900 Subject: [PATCH 291/449] fix(client): invoke onClose when SSE session disconnects (#437) (#738) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes the SSE client transport so that registered `onClose` callbacks fire when the underlying SSE session ends — whether the server closes the stream gracefully or an error event terminates it. closes #437 ## Motivation and Context `SseClientTransport.collectMessages` already runs `closeResources()` in its `finally` block when `session.incoming` completes, but it never invoked the `onClose` callback chain. As a result, the MCP `Client` (and any user code calling `transport.onClose { ... }`) had no way to detect that the server-side SSE connection had dropped. The other client transports already do this correctly: - `StdioClientTransport` calls `invokeOnCloseCallback()` from its read-loop `finally` (kotlin-sdk-client/src/.../StdioClientTransport.kt:224). - The protected `AbstractTransport.invokeOnCloseCallback()` helper guards against duplicate firing with an `AtomicBoolean`, so it is safe to call from both the read-loop `finally` and the user-initiated `close()` path. The fix mirrors that established pattern in one line. ## How Has This Been Tested? Added `onClose callback fires when the SSE stream is disconnected by the server` to `SseClientTransportTest`. The test: 1. Starts the transport against a mock SSE engine. 2. Closes the SSE byte channel server-side via a new `MockSseClientEngine.disconnectSseStream()` helper. 3. Asserts the registered `onClose` callback fires (using Kotest `eventually`, since the close propagates on a real-time dispatcher). I also verified the test fails before the fix and passes after. ``` ./gradlew :kotlin-sdk-client:jvmTest ./gradlew apiCheck ktlintCheck detekt ``` ## Breaking Changes None. `invokeOnCloseCallback()` is `protected` on `AbstractTransport`; this PR only adds a call site inside `SseClientTransport`. No public API surface changes. ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- .../kotlin/sdk/client/SseClientTransport.kt | 1 + .../sdk/client/sse/MockSseClientEngine.kt | 8 ++++++ .../sdk/client/sse/SseClientTransportTest.kt | 27 +++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseClientTransport.kt index a48db60..0295eb9 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseClientTransport.kt @@ -136,6 +136,7 @@ public class SseClientTransport( throw e } finally { closeResources() + invokeOnCloseCallback() } } diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/sse/MockSseClientEngine.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/sse/MockSseClientEngine.kt index 3627b6c..14af546 100644 --- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/sse/MockSseClientEngine.kt +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/sse/MockSseClientEngine.kt @@ -53,6 +53,14 @@ internal open class MockSseClientEngine( job.cancel() } + /** + * Closes the SSE byte channel without disposing the engine, simulating a server-side + * disconnect of the SSE stream. + */ + fun disconnectSseStream() { + channel.close() + } + private fun responseContext(): CoroutineContext = dispatcher + Job() private suspend fun respondWithSseEndpointEvent(data: HttpRequestData): HttpResponseData { diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/sse/SseClientTransportTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/sse/SseClientTransportTest.kt index 6cc7f6e..319ba2a 100644 --- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/sse/SseClientTransportTest.kt +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/sse/SseClientTransportTest.kt @@ -1,5 +1,6 @@ package io.modelcontextprotocol.kotlin.sdk.client.sse +import io.kotest.assertions.nondeterministic.eventually import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.shouldBe import io.ktor.client.HttpClient @@ -9,6 +10,7 @@ import io.modelcontextprotocol.kotlin.sdk.client.SseClientTransport import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification import kotlinx.coroutines.test.runTest import kotlin.test.Test +import kotlin.time.Duration.Companion.seconds class SseClientTransportTest { @@ -90,6 +92,31 @@ class SseClientTransportTest { engine.close() } + @Test + fun `onClose callback fires when the SSE stream is disconnected by the server`() = runTest { + // Given + val sseUrl = "http://example.com/api/mcp/sse" + + // And + val engine = CapturingSseClientEngine(endpoint = "/messages?sessionId=abc") + val transport = sseTransport(sseUrl, engine) + var onCloseFired = false + transport.onClose { onCloseFired = true } + + // When + transport.start() + engine.disconnectSseStream() + + // Then + eventually(2.seconds) { + onCloseFired shouldBe true + } + + // Cleanup + transport.close() + engine.close() + } + private fun sseTransport(sseUrl: String, engine: CapturingSseClientEngine) = SseClientTransport(HttpClient(engine) { install(SSE) }, sseUrl) From 664f51b9cc210466c02699f5146130f86c4be98b Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Mon, 18 May 2026 12:17:26 +0200 Subject: [PATCH 292/449] test: remove mokksy dependency (#754) Drops`dev.mokksy:mokksy` dependency ## Motivation and Context `mokksy` is a niche, single-maintainer library with very limited external adoption, introduced by its own author. Keeping a test-only dependency on it is unnecessary supply-chain exposure given that the two patterns already used in this repo, `MockEngine` for transport-level tests and `embeddedServer` for integration-level tests, cover everything it provided. ## Breaking Changes none ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- gradle/libs.versions.toml | 2 - ...mableHttpClientTransportIntegrationTest.kt | 270 ++++++++++++++++ kotlin-sdk-client/build.gradle.kts | 1 - .../http/StreamableHttpClientTransportTest.kt | 73 +++++ .../AbstractStreamableHttpClientTest.kt | 42 --- .../kotlin/sdk/client/MockMcp.kt | 237 -------------- .../sdk/client/StreamableHttpClientTest.kt | 293 ------------------ 7 files changed, 343 insertions(+), 575 deletions(-) create mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/StreamableHttpClientTransportIntegrationTest.kt delete mode 100644 kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractStreamableHttpClientTest.kt delete mode 100644 kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt delete mode 100644 kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a1f2cc0..5b04ca4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -20,7 +20,6 @@ kotlinx-io = "0.9.0" ktor = "3.3.3" logging = "8.0.02" mockk = "1.14.9" -mokksy = "0.8.1" serialization = "1.11.0" slf4j = "2.0.17" logback = "1.5.32" @@ -65,7 +64,6 @@ kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-cor ktor-client-mock = { group = "io.ktor", name = "ktor-client-mock", version.ref = "ktor" } ktor-server-test-host = { group = "io.ktor", name = "ktor-server-test-host", version.ref = "ktor" } mockk = { module = "io.mockk:mockk", version.ref = "mockk" } -mokksy = { group = "dev.mokksy", name = "mokksy", version.ref = "mokksy" } netty-bom = { group = "io.netty", name = "netty-bom", version.ref = "netty" } slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" } logback-classic = { group = "ch.qos.logback", name = "logback-classic", version.ref = "logback" } diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/StreamableHttpClientTransportIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/StreamableHttpClientTransportIntegrationTest.kt new file mode 100644 index 0000000..598825f --- /dev/null +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/StreamableHttpClientTransportIntegrationTest.kt @@ -0,0 +1,270 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.streamablehttp + +import io.kotest.matchers.collections.shouldContain +import io.kotest.matchers.shouldBe +import io.ktor.client.HttpClient +import io.ktor.client.plugins.sse.SSE +import io.ktor.http.ContentType +import io.ktor.http.HttpStatusCode +import io.ktor.server.engine.embeddedServer +import io.ktor.server.request.receiveText +import io.ktor.server.response.header +import io.ktor.server.response.respond +import io.ktor.server.response.respondText +import io.ktor.server.response.respondTextWriter +import io.ktor.server.routing.Route +import io.ktor.server.routing.delete +import io.ktor.server.routing.get +import io.ktor.server.routing.post +import io.ktor.server.routing.routing +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.client.ClientOptions +import io.modelcontextprotocol.kotlin.sdk.client.StreamableHttpClientTransport +import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.Tool +import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema +import io.modelcontextprotocol.kotlin.test.utils.actualPort +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import kotlinx.serialization.json.putJsonObject +import kotlin.test.Test +import io.ktor.client.engine.cio.CIO as ClientCIO +import io.ktor.server.cio.CIO as ServerCIO + +private const val SESSION_ID_HEADER = "mcp-session-id" +private const val HOST = "127.0.0.1" +private const val MCP_PATH = "/mcp" + +private val INITIALIZE_RESULT = """ + { + "capabilities": {"tools": {"listChanged": false}}, + "protocolVersion": "2025-03-26", + "serverInfo": {"name": "test-server", "version": "1.0.0"} + } +""".trimIndent() + +private val TOOLS_LIST_RESULT = """ + { + "tools": [ + { + "name": "get_weather", + "title": "Weather Information Provider", + "description": "Get current weather information for a location", + "inputSchema": { + "type": "object", + "properties": { + "location": {"type": "string", "description": "City name or zip code"} + }, + "required": ["location"] + }, + "outputSchema": { + "type": "object", + "properties": { + "temperature": {"type": "number", "description": "Temperature, Celsius"} + }, + "required": ["temperature"] + }, + "_meta": {} + } + ] + } +""".trimIndent() + +private const val PROGRESS_NOTIFICATION_1 = + """{"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"upload-123","progress":50,"total":100}}""" + +private const val PROGRESS_NOTIFICATION_2 = + """{"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"upload-123","progress":75,"total":100}}""" + +internal class StreamableHttpClientTransportIntegrationTest { + + @Test + fun `client receives progress notifications via GET SSE and lists tools`(): Unit = runBlocking(Dispatchers.IO) { + val sessionId = "sid-progress" + runWithServer({ + mcpPostRoute(sessionId, extraResults = mapOf("tools/list" to TOOLS_LIST_RESULT)) + get(MCP_PATH) { + call.response.header(SESSION_ID_HEADER, sessionId) + call.respondTextWriter(contentType = ContentType.Text.EventStream) { + write(sseEvent(id = "1", data = PROGRESS_NOTIFICATION_1)) + flush() + delay(200) + write(sseEvent(id = "2", data = PROGRESS_NOTIFICATION_2)) + flush() + awaitCancellation() + } + } + }) { url -> + val client = newClient() + client.connect(StreamableHttpClientTransport(client = newHttpClient(), url = url)) + + val listToolsResult = client.listTools() + + listToolsResult.tools shouldContain Tool( + name = "get_weather", + title = "Weather Information Provider", + description = "Get current weather information for a location", + inputSchema = ToolSchema( + properties = buildJsonObject { + putJsonObject("location") { + put("type", "string") + put("description", "City name or zip code") + } + }, + required = listOf("location"), + ), + outputSchema = ToolSchema( + properties = buildJsonObject { + putJsonObject("temperature") { + put("type", "number") + put("description", "Temperature, Celsius") + } + }, + required = listOf("temperature"), + ), + annotations = null, + meta = EmptyJsonObject, + ) + + client.close() + } + } + + @Test + fun `terminateSession sends DELETE request and clears sessionId`(): Unit = runBlocking(Dispatchers.IO) { + val sessionId = "sid-terminate" + runWithServer({ + mcpPostRoute(sessionId) + get(MCP_PATH) { + call.response.header(SESSION_ID_HEADER, sessionId) + call.respondTextWriter(contentType = ContentType.Text.EventStream) { + awaitCancellation() + } + } + delete(MCP_PATH) { + call.respond(HttpStatusCode.OK) + } + }) { url -> + val client = newClient() + val transport = StreamableHttpClientTransport(client = newHttpClient(), url = url) + client.connect(transport) + + transport.sessionId shouldBe sessionId + transport.terminateSession() + transport.sessionId shouldBe null + + client.close() + } + } + + @Test + fun `client survives 405 on GET SSE`(): Unit = runBlocking(Dispatchers.IO) { + val sessionId = "sid-405" + runWithServer({ + mcpPostRoute(sessionId, extraResults = mapOf("ping" to "{}")) + get(MCP_PATH) { + call.respondText( + text = "", + contentType = ContentType.Text.EventStream, + status = HttpStatusCode.MethodNotAllowed, + ) + } + }) { url -> + val client = newClient() + client.connect(StreamableHttpClientTransport(client = newHttpClient(), url = url)) + + client.ping() + + client.close() + } + } + + @Test + fun `client survives non streaming JSON response on GET SSE`(): Unit = runBlocking(Dispatchers.IO) { + val sessionId = "sid-json" + runWithServer({ + mcpPostRoute(sessionId, extraResults = mapOf("ping" to "{}")) + get(MCP_PATH) { + call.response.header(SESSION_ID_HEADER, sessionId) + call.respondText( + text = "", + contentType = ContentType.Application.Json, + status = HttpStatusCode.OK, + ) + } + }) { url -> + val client = newClient() + client.connect(StreamableHttpClientTransport(client = newHttpClient(), url = url)) + + client.ping() + + client.close() + } + } + + private fun Route.mcpPostRoute(sessionId: String, extraResults: Map = emptyMap()) { + post(MCP_PATH) { + val body = call.receiveText() + val parsed = Json.parseToJsonElement(body).jsonObject + val method = parsed["method"]?.jsonPrimitive?.content + val idJson = parsed["id"]?.toString() ?: "null" + call.response.header(SESSION_ID_HEADER, sessionId) + when { + method == "initialize" -> call.respondText( + text = jsonRpcResult(idJson, INITIALIZE_RESULT), + contentType = ContentType.Application.Json, + status = HttpStatusCode.OK, + ) + + method == "notifications/initialized" -> call.respond(HttpStatusCode.Accepted) + + method != null && method in extraResults -> call.respondText( + text = jsonRpcResult(idJson, extraResults.getValue(method)), + contentType = ContentType.Application.Json, + status = HttpStatusCode.OK, + ) + + else -> call.respond(HttpStatusCode.NotFound) + } + } + } + + private suspend fun runWithServer(routes: Route.() -> Unit, block: suspend (url: String) -> Unit) { + val server = embeddedServer(ServerCIO, host = HOST, port = 0) { + routing { routes() } + }.startSuspend(wait = false) + try { + val port = server.actualPort() + block("http://$HOST:$port$MCP_PATH") + } finally { + server.stopSuspend(1000, 2000) + } + } + + private fun newClient(): Client = Client( + clientInfo = Implementation(name = "client1", version = "1.0.0"), + options = ClientOptions(capabilities = ClientCapabilities()), + ) + + private fun newHttpClient(): HttpClient = HttpClient(ClientCIO) { + install(SSE) + } + + private fun jsonRpcResult(idJson: String, resultJson: String): String = + """{"jsonrpc":"2.0","id":$idJson,"result":$resultJson}""" + + private fun sseEvent(id: String, data: String): String = buildString { + append("event: message\n") + append("id: $id\n") + append("data: $data\n\n") + } +} diff --git a/kotlin-sdk-client/build.gradle.kts b/kotlin-sdk-client/build.gradle.kts index ae7a314..fbb1300 100644 --- a/kotlin-sdk-client/build.gradle.kts +++ b/kotlin-sdk-client/build.gradle.kts @@ -55,7 +55,6 @@ kotlin { implementation(libs.ktor.client.apache5) implementation(libs.mockk) implementation(libs.junit.jupiter.params) - implementation(libs.mokksy) implementation(dependencies.platform(libs.netty.bom)) runtimeOnly(libs.slf4j.simple) } diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt index 8810552..b380f64 100644 --- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt @@ -478,6 +478,79 @@ class StreamableHttpClientTransportTest { } } + @Test + fun testSkipEmptySSE() = runTest { + val transport = createTransport { request -> + if (request.method == HttpMethod.Post) { + val sseContent = buildString { + // Event 1: empty data — must be skipped without error + appendLine("id: a") + appendLine("data:") + appendLine() + // Event 2: whitespace-only data — must be skipped without error + appendLine("id: b") + appendLine("data: \t ") + appendLine() + // Event 3: multi-line data lines that together form a valid JSON-RPC response + appendLine("id: c") + appendLine("event: message") + appendLine("data: {") + appendLine("""data: "jsonrpc":"2.0",""") + appendLine("""data: "id":"test-1",""") + appendLine("""data: "result":{""") + appendLine("""data: "protocolVersion":"2025-06-18",""") + appendLine("""data: "capabilities":{},""") + appendLine("""data: "serverInfo":{"name":"server","version":"1.0.0"}""") + appendLine("data: }") + appendLine("data: }") + appendLine() + } + + respond( + content = ByteReadChannel(sseContent), + status = HttpStatusCode.OK, + headers = headersOf( + HttpHeaders.ContentType, + ContentType.Text.EventStream.toString(), + ), + ) + } else { + respond("", HttpStatusCode.OK) + } + } + + val receivedMessages = mutableListOf() + val messageReceived = CompletableDeferred() + + transport.onMessage { message -> + receivedMessages.add(message) + if (!messageReceived.isCompleted) { + messageReceived.complete(Unit) + } + } + + transport.start() + + transport.send( + JSONRPCRequest( + id = "test-1", + method = "initialize", + params = buildJsonObject { }, + ), + ) + + eventually { + messageReceived.await() + } + + receivedMessages shouldHaveSize 1 + val response = receivedMessages[0] + response.shouldBeInstanceOf() + response.id shouldBe RequestId.StringId("test-1") + + transport.close() + } + @Test fun testInlineSSEInResponse() = runTest { val transport = createTransport { request -> diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractStreamableHttpClientTest.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractStreamableHttpClientTest.kt deleted file mode 100644 index 4a04e81..0000000 --- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/AbstractStreamableHttpClientTest.kt +++ /dev/null @@ -1,42 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.client - -import io.ktor.client.HttpClient -import io.ktor.client.engine.apache5.Apache5 -import io.ktor.client.plugins.logging.LogLevel -import io.ktor.client.plugins.logging.Logging -import io.ktor.client.plugins.sse.SSE -import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.TestInstance - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -internal abstract class AbstractStreamableHttpClientTest { - - // start mokksy on random port - protected val mockMcp: MockMcp = MockMcp(verbose = true) - - @AfterEach - fun afterEach() { - mockMcp.verifyNoUnexpectedRequests() - } - - @AfterAll - fun afterAll() { - mockMcp.verifyNoUnmatchedStubs() - mockMcp.close() - } - - protected suspend fun connect(client: Client) { - client.connect( - StreamableHttpClientTransport( - url = mockMcp.url, - client = HttpClient(Apache5) { - install(SSE) - install(Logging) { - level = LogLevel.ALL - } - }, - ), - ) - } -} diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt deleted file mode 100644 index de9d2b3..0000000 --- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/MockMcp.kt +++ /dev/null @@ -1,237 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.client - -import dev.mokksy.mokksy.BuildingStep -import dev.mokksy.mokksy.Mokksy -import dev.mokksy.mokksy.StubConfiguration -import dev.mokksy.mokksy.shutdown -import dev.mokksy.mokksy.start -import io.ktor.http.ContentType -import io.ktor.http.HttpMethod -import io.ktor.http.HttpStatusCode -import io.ktor.sse.ServerSentEvent -import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest -import io.modelcontextprotocol.kotlin.sdk.types.RequestId -import kotlinx.coroutines.flow.Flow -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.contentOrNull -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive -import kotlinx.serialization.json.putJsonObject - -const val MCP_SESSION_ID_HEADER = "MCP-Session-Id" - -internal class MockMcp(verbose: Boolean = false) : AutoCloseable { - - private val mokksy: Mokksy = Mokksy(verbose = verbose).apply { - start() - } - - fun verifyNoUnexpectedRequests() { - mokksy.verifyNoUnexpectedRequests() - } - - fun verifyNoUnmatchedStubs() { - mokksy.verifyNoUnmatchedStubs() - } - - val url = "${mokksy.baseUrl()}/mcp" - - fun onInitialize( - clientName: String? = null, - sessionId: String, - protocolVersion: String = "2025-03-26", - serverName: String = "Mock MCP Server", - serverVersion: String = "1.0.0", - capabilities: JsonObject = buildJsonObject { - putJsonObject("tools") { - put("listChanged", JsonPrimitive(false)) - } - }, - ) { - val predicates = if (clientName != null) { - arrayOf<(JSONRPCRequest?) -> Boolean>({ - it?.params?.jsonObject - ?.get("clientInfo")?.jsonObject - ?.get("name")?.jsonPrimitive - ?.contentOrNull == clientName - }) - } else { - emptyArray() - } - - handleWithResult( - jsonRpcMethod = "initialize", - sessionId = sessionId, - bodyPredicates = predicates, - // language=json - result = """ - { - "capabilities": $capabilities, - "protocolVersion": "$protocolVersion", - "serverInfo": { - "name": "$serverName", - "version": "$serverVersion" - }, - "_meta": { - "foo": "bar" - } - } - """.trimIndent(), - ) - } - - fun onJSONRPCRequest( - httpMethod: HttpMethod = HttpMethod.Post, - jsonRpcMethod: String, - expectedSessionId: String? = null, - vararg bodyPredicates: (JSONRPCRequest) -> Boolean, - ): BuildingStep = mokksy.method( - configuration = StubConfiguration(removeAfterMatch = true), - httpMethod = httpMethod, - requestType = JSONRPCRequest::class, - ) { - path("/mcp") - expectedSessionId?.let { - containsHeader(MCP_SESSION_ID_HEADER, it) - } - bodyMatchesPredicate( - description = "JSON-RPC version is '2.0'", - predicate = - { - it!!.jsonrpc == "2.0" - }, - ) - bodyMatchesPredicate( - description = "JSON-RPC Method should be '$jsonRpcMethod'", - predicate = - { - it!!.method == jsonRpcMethod - }, - ) - bodyPredicates.forEach { predicate -> - bodyMatchesPredicate(predicate = { predicate.invoke(it!!) }) - } - } - - fun handleWithResult( - httpMethod: HttpMethod = HttpMethod.Post, - jsonRpcMethod: String, - expectedSessionId: String? = null, - sessionId: String, - contentType: ContentType = ContentType.Application.Json, - statusCode: HttpStatusCode = HttpStatusCode.OK, - vararg bodyPredicates: (JSONRPCRequest) -> Boolean, - result: () -> JsonObject, - ) { - onJSONRPCRequest( - httpMethod = httpMethod, - jsonRpcMethod = jsonRpcMethod, - expectedSessionId = expectedSessionId, - bodyPredicates = bodyPredicates, - ) respondsWith { - val requestId = when (val id = request.body().id) { - is RequestId.NumberId -> id.value.toString() - is RequestId.StringId -> "\"${id.value}\"" - } - val resultObject = result.invoke() - // language=json - body = """ - { - "jsonrpc": "2.0", - "id": $requestId, - "result": $resultObject - } - """.trimIndent() - this.contentType = contentType - headers += MCP_SESSION_ID_HEADER to sessionId - httpStatus = statusCode - } - } - - fun handleWithResult( - httpMethod: HttpMethod = HttpMethod.Post, - jsonRpcMethod: String, - expectedSessionId: String? = null, - sessionId: String, - contentType: ContentType = ContentType.Application.Json, - statusCode: HttpStatusCode = HttpStatusCode.OK, - vararg bodyPredicates: (JSONRPCRequest) -> Boolean, - result: String, - ) { - handleWithResult( - httpMethod = httpMethod, - jsonRpcMethod = jsonRpcMethod, - expectedSessionId = expectedSessionId, - sessionId = sessionId, - contentType = contentType, - statusCode = statusCode, - bodyPredicates = bodyPredicates, - result = { - Json.parseToJsonElement(result).jsonObject - }, - ) - } - - fun handleJSONRPCRequest( - httpMethod: HttpMethod = HttpMethod.Post, - jsonRpcMethod: String, - expectedSessionId: String? = null, - sessionId: String, - contentType: ContentType = ContentType.Application.Json, - statusCode: HttpStatusCode = HttpStatusCode.OK, - vararg bodyPredicates: (JSONRPCRequest?) -> Boolean, - bodyBuilder: () -> String = { "" }, - ) { - onJSONRPCRequest( - httpMethod = httpMethod, - jsonRpcMethod = jsonRpcMethod, - expectedSessionId = expectedSessionId, - bodyPredicates = bodyPredicates, - ) respondsWith { - body = bodyBuilder.invoke() - this.contentType = contentType - headers += MCP_SESSION_ID_HEADER to sessionId - httpStatus = statusCode - } - } - - fun onSubscribe(httpMethod: HttpMethod = HttpMethod.Post, sessionId: String): BuildingStep = mokksy.method( - httpMethod = httpMethod, - name = "MCP GETs", - requestType = Any::class, - ) { - path("/mcp") - containsHeader(MCP_SESSION_ID_HEADER, sessionId) - containsHeader("Accept", "application/json,text/event-stream") - containsHeader("Cache-Control", "no-store") - } - - fun handleSubscribeWithGet(sessionId: String, block: () -> Flow) { - onSubscribe( - httpMethod = HttpMethod.Get, - sessionId = sessionId, - ) respondsWithSseStream { - headers += MCP_SESSION_ID_HEADER to sessionId - this.flow = block.invoke() - } - } - - fun mockUnsubscribeRequest(sessionId: String) { - mokksy.delete( - configuration = StubConfiguration(removeAfterMatch = true), - requestType = JSONRPCRequest::class, - ) { - path("/mcp") - containsHeader(MCP_SESSION_ID_HEADER, sessionId) - } respondsWith { - body = null - } - } - - override fun close() { - mokksy.shutdown() - } -} diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt deleted file mode 100644 index 43be8c9..0000000 --- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt +++ /dev/null @@ -1,293 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.client - -import io.kotest.matchers.collections.shouldContain -import io.kotest.matchers.shouldBe -import io.ktor.http.ContentType -import io.ktor.http.HttpMethod -import io.ktor.http.HttpStatusCode -import io.ktor.sse.ServerSentEvent -import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities -import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject -import io.modelcontextprotocol.kotlin.sdk.types.Implementation -import io.modelcontextprotocol.kotlin.sdk.types.Tool -import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.runBlocking -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.put -import kotlinx.serialization.json.putJsonObject -import org.junit.jupiter.api.TestInstance -import kotlin.test.Test -import kotlin.time.Duration.Companion.milliseconds -import kotlin.time.Duration.Companion.seconds -import kotlin.uuid.ExperimentalUuidApi -import kotlin.uuid.Uuid - -@OptIn(ExperimentalUuidApi::class) -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -internal class StreamableHttpClientTest : AbstractStreamableHttpClientTest() { - - @Test - fun `Should skip empty SSE`(): Unit = runBlocking { - val client = Client( - clientInfo = Implementation( - name = "client1", - version = "1.0.0", - ), - options = ClientOptions( - capabilities = ClientCapabilities(), - ), - ) - val sessionId = Uuid.random().toString() - - mockMcp.onJSONRPCRequest( - httpMethod = HttpMethod.Post, - jsonRpcMethod = "initialize", - ).respondsWithStream { - headers += MCP_SESSION_ID_HEADER to sessionId - flow = flowOf( - "id: ${Uuid.random()}\n", - "data:\n", // empty data - "\n", - "id: ${Uuid.random()}\n", - "data: \t \n", // tabs and spaces - "\n", - "id: ${Uuid.random()}\n", - "event: message\n", - // multiline data - "data: {\n", - "data: \"result\":{\n" + - "data: \"protocolVersion\":\"2025-06-18\",\n" + - "data: \"capabilities\":{},\n" + - "data: \"serverInfo\":{\"name\":\"simple-streamable-http-server\",\"version\":\"1.0.0\"}\n" + - "data: },\n" + - "data: \"jsonrpc\":\"2.0\",\n" + - "data: \"id\":\"7ce065b0678f49e5b04ce5a0fcc7d518\"\n" + - "data: }\n", - "\n", - ) - } - - mockMcp.handleJSONRPCRequest( - jsonRpcMethod = "notifications/initialized", - expectedSessionId = sessionId, - sessionId = sessionId, - statusCode = HttpStatusCode.Accepted, - ) - - mockMcp.handleSubscribeWithGet(sessionId) { - emptyFlow() - } - - connect(client) - } - - @Test - fun `test streamableHttpClient`() = runBlocking { - val client = Client( - clientInfo = Implementation( - name = "client1", - version = "1.0.0", - ), - options = ClientOptions( - capabilities = ClientCapabilities(), - ), - ) - - val sessionId = Uuid.random().toString() - - mockMcp.onInitialize( - clientName = "client1", - sessionId = sessionId, - ) - - mockMcp.handleJSONRPCRequest( - jsonRpcMethod = "notifications/initialized", - expectedSessionId = sessionId, - sessionId = sessionId, - statusCode = HttpStatusCode.Accepted, - ) - - mockMcp.handleSubscribeWithGet(sessionId) { - flow { - delay(500.milliseconds) - emit( - ServerSentEvent( - event = "message", - id = "1", - //language=json - data = - """{"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"upload-123","progress":50,"total":100}}""", - ), - ) - delay(200.milliseconds) - emit( - ServerSentEvent( - //language=json - data = - """{"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"upload-123","progress":50,"total":100}}""", - ), - ) - } - } - - // TODO: how to get notifications via Client API? - - mockMcp.handleWithResult( - jsonRpcMethod = "tools/list", - sessionId = sessionId, - // language=json - result = """ - { - "tools": [ - { - "name": "get_weather", - "title": "Weather Information Provider", - "description": "Get current weather information for a location", - "inputSchema": { - "type": "object", - "properties": { - "location": { - "type": "string", - "description": "City name or zip code" - } - }, - "required": ["location"] - }, - "outputSchema": { - "type": "object", - "properties": { - "temperature": { - "type": "number", - "description": "Temperature, Celsius" - } - }, - "required": ["temperature"] - }, - "_meta": {} - } - ] - } - """.trimIndent(), - ) - - connect(client) - - val listToolsResult = client.listTools() - - listToolsResult.tools shouldContain Tool( - name = "get_weather", - title = "Weather Information Provider", - description = "Get current weather information for a location", - inputSchema = ToolSchema( - properties = buildJsonObject { - putJsonObject("location") { - put("type", "string") - put("description", "City name or zip code") - } - }, - required = listOf("location"), - ), - outputSchema = ToolSchema( - properties = buildJsonObject { - putJsonObject("temperature") { - put("type", "number") - put("description", "Temperature, Celsius") - } - }, - required = listOf("temperature"), - ), - annotations = null, - meta = EmptyJsonObject, - ) - - client.close() - } - - @Test - fun `terminateSession sends DELETE request`() = runBlocking { - val client = Client( - clientInfo = Implementation(name = "client1", version = "1.0.0"), - options = ClientOptions(capabilities = ClientCapabilities()), - ) - val sessionId = Uuid.random().toString() - - mockMcp.onInitialize(clientName = "client1", sessionId = sessionId) - mockMcp.handleJSONRPCRequest( - jsonRpcMethod = "notifications/initialized", - expectedSessionId = sessionId, - sessionId = sessionId, - statusCode = HttpStatusCode.Accepted, - ) - mockMcp.handleSubscribeWithGet(sessionId) { emptyFlow() } - - connect(client) - - mockMcp.mockUnsubscribeRequest(sessionId = sessionId) - (client.transport as StreamableHttpClientTransport).terminateSession() - (client.transport as StreamableHttpClientTransport).sessionId shouldBe null - - client.close() - } - - @Test - fun `handle MethodNotAllowed`() = runBlocking { - checkSupportNonStreamingResponse( - ContentType.Text.EventStream, - HttpStatusCode.MethodNotAllowed, - ) - } - - @Test - fun `handle non-streaming response`() = runBlocking { - checkSupportNonStreamingResponse( - ContentType.Application.Json, - HttpStatusCode.OK, - ) - } - - private suspend fun checkSupportNonStreamingResponse(contentType: ContentType, statusCode: HttpStatusCode) { - val sessionId = "SID_${Uuid.random().toHexString()}" - val clientName = "client-${Uuid.random().toHexString()}" - val client = Client( - clientInfo = Implementation(name = clientName, version = "1.0.0"), - options = ClientOptions( - capabilities = ClientCapabilities(), - ), - ) - - mockMcp.onInitialize(clientName = clientName, sessionId = sessionId) - - mockMcp.handleJSONRPCRequest( - jsonRpcMethod = "notifications/initialized", - expectedSessionId = sessionId, - sessionId = sessionId, - statusCode = HttpStatusCode.Accepted, - ) - - mockMcp.onSubscribe( - httpMethod = HttpMethod.Get, - sessionId = sessionId, - ) respondsWith { - headers += MCP_SESSION_ID_HEADER to sessionId - body = null - httpStatus = statusCode - this.contentType = contentType - } - - mockMcp.handleWithResult(jsonRpcMethod = "ping", sessionId = sessionId) { - buildJsonObject {} - } - - connect(client) - - delay(1.seconds) - - client.ping() // connection is still alive - - client.close() - } -} From ebc53f0ded3a309dcd6458bca16fcb52450b11d2 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Mon, 18 May 2026 13:22:26 +0200 Subject: [PATCH 293/449] feat!: capabilities for tasks, elicitation (#732) closes #544 Replaces opaque `JsonObject?` modelling of `elicitation` with a typed `ClientCapabilities.Elicitation` (`form`, `url`), and adds the new `tasks` capability on both `ClientCapabilities` and `ServerCapabilities` (with `list`, `cancel`, per-request-type augmentation). Wires capability assertions for `tasks/*` methods and `notifications/tasks/status` into `Client` and `ServerSession` ## How Has This Been Tested? unit tests ## Breaking Changes yes now elicitation is a specific class in capabilities ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [x] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- .../kotlin/sdk/client/ClientTest.kt | 4 +- .../kotlin/sdk/client/Client.kt | 31 ++ .../sdk/client/ClientAssertCapabilityTest.kt | 152 ++++++++++ kotlin-sdk-core/api/kotlin-sdk-core.api | 281 ++++++++++++++++-- .../kotlin/sdk/types/capabilities.dsl.kt | 22 +- .../kotlin/sdk/types/capabilities.kt | 122 +++++++- .../kotlin/sdk/types/CapabilitiesTest.kt | 121 +++++++- .../sdk/types/dsl/CapabilitiesDslTest.kt | 11 +- .../kotlin/sdk/types/dsl/InitializeDslTest.kt | 12 +- .../kotlin/sdk/server/ServerSession.kt | 34 +++ .../ServerSessionAssertCapabilityTest.kt | 147 +++++++++ 11 files changed, 872 insertions(+), 65 deletions(-) create mode 100644 kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientAssertCapabilityTest.kt create mode 100644 kotlin-sdk-server/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSessionAssertCapabilityTest.kt diff --git a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt index e5f6638..5c00b23 100644 --- a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt +++ b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt @@ -1058,7 +1058,7 @@ class ClientTest { Implementation(name = "test client", version = "1.0"), ClientOptions( capabilities = ClientCapabilities( - elicitation = EmptyJsonObject, + elicitation = ClientCapabilities.Elicitation(), ), ), ) @@ -1270,7 +1270,7 @@ class ClientTest { ): Pair = kotlinx.coroutines.coroutineScope { val client = Client( Implementation(name = "test client", version = "1.0"), - ClientOptions(capabilities = ClientCapabilities(elicitation = EmptyJsonObject)), + ClientOptions(capabilities = ClientCapabilities(elicitation = ClientCapabilities.Elicitation())), ) client.setElicitationHandler(handler) diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt index d71c091..ee865af 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt @@ -174,6 +174,7 @@ public open class Client(private val clientInfo: Implementation, options: Client "prompts" -> caps?.prompts != null "resources" -> caps?.resources != null "tools" -> caps?.tools != null + "tasks" -> caps?.tasks != null else -> true } @@ -267,6 +268,12 @@ public open class Client(private val clientInfo: Implementation, options: Client } } + Method.Defined.TasksGet, + Method.Defined.TasksResult, + Method.Defined.TasksList, + Method.Defined.TasksCancel, + -> assertTasksCapabilityForMethod(method) + Method.Defined.Initialize, Method.Defined.Ping -> { // No specific capability required } @@ -277,6 +284,24 @@ public open class Client(private val clientInfo: Implementation, options: Client } } + private fun assertTasksCapabilityForMethod(method: Method) { + val tasks = serverCapabilities?.tasks + ?: error("Server does not support tasks (required for $method)") + when (method) { + Method.Defined.TasksList -> checkNotNull(tasks.list) { + "Server does not support listing tasks (required for $method)" + } + + Method.Defined.TasksCancel -> checkNotNull(tasks.cancel) { + "Server does not support cancelling tasks (required for $method)" + } + + else -> { + // TasksGet, TasksResult: base tasks capability suffices. + } + } + } + override fun assertNotificationCapability(method: Method) { when (method) { Method.Defined.NotificationsRootsListChanged -> { @@ -285,6 +310,12 @@ public open class Client(private val clientInfo: Implementation, options: Client } } + Method.Defined.NotificationsTasksStatus -> { + checkNotNull(capabilities.tasks) { + "Client does not support tasks (required for $method)" + } + } + Method.Defined.NotificationsInitialized, Method.Defined.NotificationsCancelled, Method.Defined.NotificationsProgress, diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientAssertCapabilityTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientAssertCapabilityTest.kt new file mode 100644 index 0000000..2f322ec --- /dev/null +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientAssertCapabilityTest.kt @@ -0,0 +1,152 @@ +package io.modelcontextprotocol.kotlin.sdk.client + +import io.kotest.matchers.string.shouldContain +import io.modelcontextprotocol.kotlin.sdk.shared.Transport +import io.modelcontextprotocol.kotlin.sdk.shared.TransportSendOptions +import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.InitializeResult +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse +import io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION +import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import kotlinx.coroutines.test.runTest +import kotlin.test.Test +import kotlin.test.assertFailsWith + +/** + * Tests for the protected helper [Client.assertCapability]. + * + * `assertCapability(capability, method)` is `protected`, so this suite uses + * a small subclass [TestClient] that re-exports it via [TestClient.exposedAssertCapability]. + * Server capabilities are seeded by completing the initialization handshake through + * [CapabilitiesTransport], which replays a configurable [ServerCapabilities] in its + * `initialize` response. + */ +class ClientAssertCapabilityTest { + + @Test + fun `assertCapability tasks throws when server has no tasks capability`() = runTest { + val client = newTestClient(serverCapabilities = ServerCapabilities()) + val ex = assertFailsWith { + client.exposedAssertCapability("tasks", "tasks/list") + } + ex.message.orEmpty() shouldContain "Server does not support tasks" + } + + @Test + fun `TasksGet throws when server has no tasks capability`() = runTest { + val client = newTestClient(serverCapabilities = ServerCapabilities()) + val ex = assertFailsWith { + client.exposedAssertCapabilityForMethod(Method.Defined.TasksGet) + } + ex.message.orEmpty() shouldContain "Server does not support tasks" + } + + @Test + fun `TasksGet does not throw when server declared tasks`() = runTest { + val client = newTestClient( + serverCapabilities = ServerCapabilities(tasks = ServerCapabilities.Tasks()), + ) + client.exposedAssertCapabilityForMethod(Method.Defined.TasksGet) + } + + @Test + fun `TasksList throws when server tasks list is null`() = runTest { + val client = newTestClient( + serverCapabilities = ServerCapabilities(tasks = ServerCapabilities.Tasks()), + ) + val ex = assertFailsWith { + client.exposedAssertCapabilityForMethod(Method.Defined.TasksList) + } + ex.message.orEmpty() shouldContain "Server does not support listing tasks" + } + + @Test + fun `TasksCancel throws when server tasks cancel is null`() = runTest { + val client = newTestClient( + serverCapabilities = ServerCapabilities(tasks = ServerCapabilities.Tasks()), + ) + val ex = assertFailsWith { + client.exposedAssertCapabilityForMethod(Method.Defined.TasksCancel) + } + ex.message.orEmpty() shouldContain "Server does not support cancelling tasks" + } + + @Test + fun `NotificationsTasksStatus throws when client has no tasks capability`() = runTest { + val client = newTestClient(clientCapabilities = ClientCapabilities()) + val ex = assertFailsWith { + client.exposedAssertNotificationCapability(Method.Defined.NotificationsTasksStatus) + } + ex.message.orEmpty() shouldContain "Client does not support tasks" + } + + private suspend fun newTestClient( + serverCapabilities: ServerCapabilities = ServerCapabilities(), + clientCapabilities: ClientCapabilities = ClientCapabilities(), + ): TestClient { + val client = TestClient( + Implementation("test-client", "1.0.0"), + ClientOptions(capabilities = clientCapabilities), + ) + val transport = CapabilitiesTransport(serverCapabilities) + client.connect(transport) + return client + } + + /** + * Test-only [Client] subclass that exposes the protected + * [Client.assertCapability], [Client.assertCapabilityForMethod], and + * [Client.assertNotificationCapability] helpers to the test code. + */ + private class TestClient(clientInfo: Implementation, options: ClientOptions = ClientOptions()) : + Client(clientInfo, options) { + fun exposedAssertCapability(capability: String, method: String): Unit = assertCapability(capability, method) + fun exposedAssertCapabilityForMethod(method: Method): Unit = assertCapabilityForMethod(method) + fun exposedAssertNotificationCapability(method: Method): Unit = assertNotificationCapability(method) + } + + /** + * Minimal in-memory [Transport] that responds to `initialize` with a configurable + * [ServerCapabilities]. Other JSON-RPC traffic (e.g. the `notifications/initialized` + * sent by [Client.connect]) is silently consumed. + */ + private class CapabilitiesTransport(private val serverCapabilities: ServerCapabilities) : Transport { + private var onMessageBlock: (suspend (JSONRPCMessage) -> Unit)? = null + private var onCloseBlock: (() -> Unit)? = null + + override suspend fun start() = Unit + + override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { + if (message is JSONRPCRequest && message.method == "initialize") { + onMessageBlock?.invoke( + JSONRPCResponse( + id = message.id, + result = InitializeResult( + protocolVersion = LATEST_PROTOCOL_VERSION, + capabilities = serverCapabilities, + serverInfo = Implementation("mock-server", "1.0.0"), + ), + ), + ) + } + } + + override suspend fun close() { + onCloseBlock?.invoke() + } + + override fun onMessage(block: suspend (JSONRPCMessage) -> Unit) { + onMessageBlock = block + } + + override fun onClose(block: () -> Unit) { + onCloseBlock = block + } + + override fun onError(block: (Throwable) -> Unit) = Unit + } +} diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index c843aea..b588eef 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -607,23 +607,25 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CancelledNotificatio public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Companion; public fun ()V - public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;)V public synthetic fun (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling; public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots; - public final fun component3 ()Lkotlinx/serialization/json/JsonObject; - public final fun component4 ()Lkotlinx/serialization/json/JsonObject; - public final fun component5 ()Ljava/util/Map; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities; + public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation; + public final fun component4 ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks; + public final fun component5 ()Lkotlinx/serialization/json/JsonObject; + public final fun component6 ()Ljava/util/Map; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities; public fun equals (Ljava/lang/Object;)Z - public final fun getElicitation ()Lkotlinx/serialization/json/JsonObject; + public final fun getElicitation ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation; public final fun getExperimental ()Lkotlinx/serialization/json/JsonObject; public final fun getExtensions ()Ljava/util/Map; public final fun getRoots ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots; public final fun getSampling ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling; + public final fun getTasks ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -640,11 +642,42 @@ public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ClientCapa } public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Companion { - public final fun getElicitation ()Lkotlinx/serialization/json/JsonObject; + public final fun getElicitation ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation; public final fun getSampling ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Sampling; public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation$Companion; + public fun ()V + public fun (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lkotlinx/serialization/json/JsonObject; + public final fun component2 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation; + public fun equals (Ljava/lang/Object;)Z + public final fun getForm ()Lkotlinx/serialization/json/JsonObject; + public final fun getUrl ()Lkotlinx/serialization/json/JsonObject; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation; + 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/ClientCapabilities$Elicitation;)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/ClientCapabilities$Elicitation$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots$Companion; public fun ()V @@ -705,11 +738,132 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$S public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Companion; + public fun ()V + public fun (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests;)V + public synthetic fun (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lkotlinx/serialization/json/JsonObject; + public final fun component2 ()Lkotlinx/serialization/json/JsonObject; + public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests; + public final fun copy (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks; + public fun equals (Ljava/lang/Object;)Z + public final fun getCancel ()Lkotlinx/serialization/json/JsonObject; + public final fun getList ()Lkotlinx/serialization/json/JsonObject; + public final fun getRequests ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks; + 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/ClientCapabilities$Tasks;)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/ClientCapabilities$Tasks$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Companion; + public fun ()V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Sampling;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Elicitation;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Sampling;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Elicitation;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Sampling; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Elicitation; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Sampling;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Elicitation;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Sampling;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Elicitation;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests; + public fun equals (Ljava/lang/Object;)Z + public final fun getElicitation ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Elicitation; + public final fun getSampling ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Sampling; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests; + 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/ClientCapabilities$Tasks$Requests;)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/ClientCapabilities$Tasks$Requests$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Elicitation { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Elicitation$Companion; + public fun ()V + public fun (Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Elicitation; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Elicitation;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Elicitation; + public fun equals (Ljava/lang/Object;)Z + public final fun getCreate ()Lkotlinx/serialization/json/JsonObject; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Elicitation$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Elicitation$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Elicitation; + 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/ClientCapabilities$Tasks$Requests$Elicitation;)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/ClientCapabilities$Tasks$Requests$Elicitation$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Sampling { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Sampling$Companion; + public fun ()V + public fun (Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Sampling; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Sampling;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Sampling; + public fun equals (Ljava/lang/Object;)Z + public final fun getCreateMessage ()Lkotlinx/serialization/json/JsonObject; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Sampling$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Sampling$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Tasks$Requests$Sampling; + 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/ClientCapabilities$Tasks$Requests$Sampling;)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/ClientCapabilities$Tasks$Requests$Sampling$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilitiesBuilder { public fun ()V public final fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities; - public final fun elicitation (Lkotlin/jvm/functions/Function1;)V - public final fun elicitation (Lkotlinx/serialization/json/JsonObject;)V + public final fun elicitation (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation;)V public final fun experimental (Lkotlin/jvm/functions/Function1;)V public final fun experimental (Lkotlinx/serialization/json/JsonObject;)V public final fun extensions (Ljava/util/Map;)V @@ -4303,17 +4457,18 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/Sampling_dslKt { public final class io/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Companion; public fun ()V - public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools; public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources; public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts; public final fun component4 ()Lkotlinx/serialization/json/JsonObject; public final fun component5 ()Lkotlinx/serialization/json/JsonObject; - public final fun component6 ()Lkotlinx/serialization/json/JsonObject; - public final fun component7 ()Ljava/util/Map; - public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities; + public final fun component6 ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks; + public final fun component7 ()Lkotlinx/serialization/json/JsonObject; + public final fun component8 ()Ljava/util/Map; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities; public fun equals (Ljava/lang/Object;)Z public final fun getCompletions ()Lkotlinx/serialization/json/JsonObject; public final fun getExperimental ()Lkotlinx/serialization/json/JsonObject; @@ -4321,6 +4476,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities { public final fun getLogging ()Lkotlinx/serialization/json/JsonObject; public final fun getPrompts ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts; public final fun getResources ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources; + public final fun getTasks ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks; public final fun getTools ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools; public fun hashCode ()I public fun toString ()Ljava/lang/String; @@ -4403,6 +4559,97 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$R public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Companion; + public fun ()V + public fun (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests;)V + public synthetic fun (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lkotlinx/serialization/json/JsonObject; + public final fun component2 ()Lkotlinx/serialization/json/JsonObject; + public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests; + public final fun copy (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks; + public fun equals (Ljava/lang/Object;)Z + public final fun getCancel ()Lkotlinx/serialization/json/JsonObject; + public final fun getList ()Lkotlinx/serialization/json/JsonObject; + public final fun getRequests ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks; + 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/ServerCapabilities$Tasks;)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/ServerCapabilities$Tasks$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests$Companion; + public fun ()V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests$Tools;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests$Tools;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests$Tools; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests$Tools;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests$Tools;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests; + public fun equals (Ljava/lang/Object;)Z + public final fun getTools ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests$Tools; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests; + 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/ServerCapabilities$Tasks$Requests;)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/ServerCapabilities$Tasks$Requests$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests$Tools { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests$Tools$Companion; + public fun ()V + public fun (Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests$Tools; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests$Tools;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests$Tools; + public fun equals (Ljava/lang/Object;)Z + public final fun getCall ()Lkotlinx/serialization/json/JsonObject; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests$Tools$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests$Tools$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tasks$Requests$Tools; + 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/ServerCapabilities$Tasks$Requests$Tools;)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/ServerCapabilities$Tasks$Requests$Tools$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools$Companion; public fun ()V diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.dsl.kt index 75e923b..0d8b2b7 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.dsl.kt @@ -38,7 +38,7 @@ import kotlinx.serialization.json.buildJsonObject public class ClientCapabilitiesBuilder @PublishedApi internal constructor() { private var sampling: ClientCapabilities.Sampling? = null private var roots: ClientCapabilities.Roots? = null - private var elicitation: JsonObject? = null + private var elicitation: ClientCapabilities.Elicitation? = null private var extensions: Map? = null private var experimental: JsonObject? = null @@ -47,7 +47,7 @@ public class ClientCapabilitiesBuilder @PublishedApi internal constructor() { * * Pass `ClientCapabilities.Sampling()` to enable base sampling with no sub-capabilities. * Construct `ClientCapabilities.Sampling(tools = EmptyJsonObject, context = EmptyJsonObject)` - * directly to enable SEP-1577 sub-capabilities. + * directly to enable tools/context sub-capabilities. * * Example: * ```kotlin @@ -99,26 +99,10 @@ public class ClientCapabilitiesBuilder @PublishedApi internal constructor() { * * @param value The elicitation capability configuration */ - public fun elicitation(value: JsonObject) { + public fun elicitation(value: ClientCapabilities.Elicitation) { this.elicitation = value } - /** - * Indicates that the client supports elicitation from the server with custom configuration. - * - * Example: - * ```kotlin - * capabilities { - * elicitation { - * put("mode", JsonPrimitive("interactive")) - * } - * } - * ``` - * - * @param block Lambda for building the elicitation configuration - */ - public fun elicitation(block: JsonObjectBuilder.() -> Unit): Unit = elicitation(buildJsonObject(block)) - /** * Defines extensions that the client supports. * diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.kt index e13a750..cd78b58 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.kt @@ -41,9 +41,11 @@ public data class Implementation( * supports that capability. * * @property sampling Present if the client supports sampling from an LLM. - * Use [ClientCapabilities.Sampling] to configure SEP-1577 sub-capabilities (tools, context). + * Use [ClientCapabilities.Sampling] to configure tools/context sub-capabilities. * @property roots Present if the client supports listing roots. * @property elicitation Present if the client supports elicitation from the server. + * Use [ClientCapabilities.Elicitation] to configure form/url mode sub-capabilities. + * @property tasks Present if the client supports task-augmented requests, listing, or cancellation. * @property experimental Experimental, non-standard capabilities that the client supports. * Keys are capability names, values are capability-specific configuration objects. * @property extensions Optional extensions that the client supports. @@ -54,23 +56,33 @@ public data class Implementation( public data class ClientCapabilities( public val sampling: Sampling? = null, public val roots: Roots? = null, - public val elicitation: JsonObject? = null, + public val elicitation: Elicitation? = null, + public val tasks: Tasks? = null, public val experimental: JsonObject? = null, public val extensions: Map? = null, ) { /** - * Source-compatibility constructor retaining the pre-SEP-1577 `sampling: JsonObject?` - * shape. Any non-null [sampling] is converted to an empty [Sampling] (sub-capabilities - * cannot be recovered from the old opaque `JsonObject`). + * Source-compatibility constructor retaining the older `sampling: JsonObject?` / + * `elicitation: JsonObject?` shapes. Any non-null [sampling] is converted to an + * empty [Sampling], and any non-null [elicitation] is converted to an empty + * [Elicitation] (sub-capabilities cannot be recovered from the old opaque + * `JsonObject`). */ @Deprecated( - "ClientCapabilities.sampling is now typed. Pass a ClientCapabilities.Sampling? " + + "ClientCapabilities.sampling and ClientCapabilities.elicitation are now typed. " + + "Pass typed ClientCapabilities.Sampling? / ClientCapabilities.Elicitation? " + "instead of JsonObject?.", ReplaceWith( - "ClientCapabilities(sampling?.let { ClientCapabilities.Sampling() }, " + - "roots, elicitation, experimental, extensions)", + "ClientCapabilities(" + + "sampling?.let { ClientCapabilities.Sampling() }, " + + "roots, " + + "elicitation?.let { ClientCapabilities.Elicitation() }, " + + "tasks = null, " + + "experimental = experimental, " + + "extensions = extensions)", ), + level = DeprecationLevel.WARNING, ) public constructor( sampling: JsonObject?, @@ -81,7 +93,8 @@ public data class ClientCapabilities( ) : this( sampling = sampling?.let { Sampling() }, roots = roots, - elicitation = elicitation, + elicitation = elicitation?.let { Elicitation() }, + tasks = null, experimental = experimental, extensions = extensions, ) @@ -101,11 +114,11 @@ public data class ClientCapabilities( /** * @property sampling convenience value to enable the base sampling capability with no sub-capabilities - * @property elicitation convenience value to enable the elicitation capability + * @property elicitation convenience value to enable the elicitation capability with default form mode */ public companion object { public val sampling: Sampling = Sampling() - public val elicitation: JsonObject = EmptyJsonObject + public val elicitation: Elicitation = Elicitation() } /** @@ -120,6 +133,60 @@ public data class ClientCapabilities( */ @Serializable public data class Roots(val listChanged: Boolean? = null) + + /** + * Sub-capabilities for elicitation. + * + * An empty [Elicitation] (both fields null) is equivalent to declaring `form` mode only. + * + * @property form Present if the client supports form-mode elicitation + * (in-band structured data collection). + * @property url Present if the client supports url-mode elicitation + * (out-of-band interaction via URL navigation). + */ + @Serializable + public data class Elicitation(public val form: JsonObject? = null, public val url: JsonObject? = null) + + /** + * Sub-capabilities for tasks (client side). + * + * Declares which client-side requests can be augmented with task execution, + * and whether the client supports listing and cancelling tasks. + * + * @property list Present if the client supports the `tasks/list` operation. + * @property cancel Present if the client supports the `tasks/cancel` operation. + * @property requests Present if the client supports task-augmented sampling and/or elicitation requests. + */ + @Serializable + public data class Tasks( + public val list: JsonObject? = null, + public val cancel: JsonObject? = null, + public val requests: Requests? = null, + ) { + /** + * Task-augmentable client-side request categories. + * + * @property sampling Present if the client supports task-augmented `sampling/createMessage` requests. + * @property elicitation Present if the client supports task-augmented `elicitation/create` requests. + */ + @Serializable + public data class Requests( + public val sampling: Sampling? = null, + public val elicitation: Elicitation? = null, + ) { + /** + * @property createMessage Present if the client supports task-augmented `sampling/createMessage` requests. + */ + @Serializable + public data class Sampling(public val createMessage: JsonObject? = null) + + /** + * @property create Present if the client supports task-augmented `elicitation/create` requests. + */ + @Serializable + public data class Elicitation(public val create: JsonObject? = null) + } + } } /** @@ -137,6 +204,7 @@ public data class ClientCapabilities( * @property logging Present if the server supports sending log messages to the client. * @property completions Present if the server supports argument autocompletion suggestions. * Keys are capability names, values are capability-specific configuration objects. + * @property tasks Present if the server supports task-augmented requests, listing, or cancellation. * @property experimental Experimental, non-standard capabilities that the server supports. * @property extensions Optional extensions that the server supports. * Keys are extension identifiers (e.g., `"io.modelcontextprotocol/ui"`), @@ -149,6 +217,7 @@ public data class ServerCapabilities( val prompts: Prompts? = null, val logging: JsonObject? = null, val completions: JsonObject? = null, + val tasks: Tasks? = null, val experimental: JsonObject? = null, val extensions: Map? = null, ) { @@ -199,4 +268,35 @@ public data class ServerCapabilities( */ @Serializable public data class Prompts(val listChanged: Boolean? = null) + + /** + * Sub-capabilities for tasks (server side). + * + * Declares which server-side requests can be augmented with task execution, + * and whether the server supports listing and cancelling tasks. + * + * @property list Present if the server supports the `tasks/list` operation. + * @property cancel Present if the server supports the `tasks/cancel` operation. + * @property requests Present if the server supports task-augmented tool call requests. + */ + @Serializable + public data class Tasks( + public val list: JsonObject? = null, + public val cancel: JsonObject? = null, + public val requests: Requests? = null, + ) { + /** + * Task-augmentable server-side request categories. + * + * @property tools Present if the server supports task-augmented `tools/call` requests. + */ + @Serializable + public data class Requests(public val tools: Tools? = null) { + /** + * @property call Present if the server supports task-augmented `tools/call` requests. + */ + @Serializable + public data class Tools(public val call: JsonObject? = null) + } + } } diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CapabilitiesTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CapabilitiesTest.kt index 8b2e4f8..06949a6 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CapabilitiesTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CapabilitiesTest.kt @@ -313,7 +313,7 @@ class CapabilitiesTest { assertEquals(ClientCapabilities.Sampling(), capabilities.sampling) assertEquals(true, capabilities.roots?.listChanged) - assertEquals(EmptyJsonObject, capabilities.elicitation) + assertEquals(ClientCapabilities.Elicitation(), capabilities.elicitation) } @Test @@ -804,4 +804,123 @@ class CapabilitiesTest { val caps = ClientCapabilities() caps.sampling shouldBe null } + + @Test + fun `should serialize ServerCapabilities with tasks`() { + val capabilities = ServerCapabilities( + tasks = ServerCapabilities.Tasks( + list = EmptyJsonObject, + cancel = EmptyJsonObject, + requests = ServerCapabilities.Tasks.Requests( + tools = ServerCapabilities.Tasks.Requests.Tools(call = EmptyJsonObject), + ), + ), + ) + verifySerialization( + capabilities, + McpJson, + """ + { + "tasks": { + "list": {}, + "cancel": {}, + "requests": { + "tools": { + "call": {} + } + } + } + } + """.trimIndent(), + ) + } + + @Test + fun `should deserialize ServerCapabilities with tasks`() { + val json = """ + { + "tasks": { + "list": {}, + "requests": {"tools": {"call": {}}} + } + } + """.trimIndent() + val capabilities = verifyDeserialization(McpJson, json) + assertEquals(EmptyJsonObject, capabilities.tasks?.list) + assertNull(capabilities.tasks?.cancel) + assertEquals(EmptyJsonObject, capabilities.tasks?.requests?.tools?.call) + } + + @Test + fun `should serialize ClientCapabilities with tasks`() { + val capabilities = ClientCapabilities( + tasks = ClientCapabilities.Tasks( + list = EmptyJsonObject, + cancel = EmptyJsonObject, + requests = ClientCapabilities.Tasks.Requests( + sampling = ClientCapabilities.Tasks.Requests.Sampling(createMessage = EmptyJsonObject), + elicitation = ClientCapabilities.Tasks.Requests.Elicitation(create = EmptyJsonObject), + ), + ), + ) + verifySerialization( + capabilities, + McpJson, + """ + { + "tasks": { + "list": {}, + "cancel": {}, + "requests": { + "sampling": {"createMessage": {}}, + "elicitation": {"create": {}} + } + } + } + """.trimIndent(), + ) + } + + @Test + fun `should serialize ClientCapabilities with structured elicitation form and url`() { + val capabilities = ClientCapabilities( + elicitation = ClientCapabilities.Elicitation(form = EmptyJsonObject, url = EmptyJsonObject), + ) + verifySerialization( + capabilities, + McpJson, + """ + { + "elicitation": { + "form": {}, + "url": {} + } + } + """.trimIndent(), + ) + } + + @Test + fun `should deserialize ClientCapabilities with elicitation and tasks`() { + val json = """ + { + "elicitation": {"form": {}, "url": {}}, + "tasks": { + "list": {}, + "cancel": {}, + "requests": { + "sampling": {"createMessage": {}}, + "elicitation": {"create": {}} + } + } + } + """.trimIndent() + val capabilities = verifyDeserialization(McpJson, json) + assertEquals(EmptyJsonObject, capabilities.elicitation?.form) + assertEquals(EmptyJsonObject, capabilities.elicitation?.url) + assertEquals(EmptyJsonObject, capabilities.tasks?.list) + assertEquals(EmptyJsonObject, capabilities.tasks?.cancel) + assertEquals(EmptyJsonObject, capabilities.tasks?.requests?.sampling?.createMessage) + assertEquals(EmptyJsonObject, capabilities.tasks?.requests?.elicitation?.create) + } } diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CapabilitiesDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CapabilitiesDslTest.kt index ee2fe43..25d9ee6 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CapabilitiesDslTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CapabilitiesDslTest.kt @@ -44,11 +44,7 @@ class CapabilitiesDslTest { capabilities { sampling(ClientCapabilities.Sampling(tools = EmptyJsonObject)) roots(listChanged = true) - elicitation { - put("mode", "interactive") - put("timeout", 30) - put("retries", 3) - } + elicitation(ClientCapabilities.Elicitation(form = EmptyJsonObject, url = EmptyJsonObject)) experimental { put("customFeature", true) put("version", "1.0") @@ -74,9 +70,8 @@ class CapabilitiesDslTest { listChanged shouldBe true } elicitation shouldNotBeNull { - get("mode")?.jsonPrimitive?.content shouldBe "interactive" - get("timeout")?.jsonPrimitive?.content shouldBe "30" - get("retries")?.jsonPrimitive?.content shouldBe "3" + form shouldBe EmptyJsonObject + url shouldBe EmptyJsonObject } experimental shouldNotBeNull { get("customFeature")?.jsonPrimitive?.content shouldBe "true" diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/InitializeDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/InitializeDslTest.kt index 17e5ae6..da2a4cb 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/InitializeDslTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/InitializeDslTest.kt @@ -22,9 +22,7 @@ class InitializeDslTest { capabilities { sampling(ClientCapabilities.Sampling()) roots(listChanged = true) - elicitation { - put("mode", "interactive") - } + elicitation(ClientCapabilities.Elicitation(form = EmptyJsonObject)) experimental { put("custom", true) } @@ -46,7 +44,7 @@ class InitializeDslTest { request.params.capabilities.shouldNotBeNull { sampling shouldNotBeNull { } roots?.listChanged shouldBe true - elicitation?.get("mode")?.jsonPrimitive?.content shouldBe "interactive" + elicitation?.form shouldBe EmptyJsonObject experimental?.get("custom")?.jsonPrimitive?.content shouldBe "true" extensions shouldNotBeNull { get("io.modelcontextprotocol/ui") shouldBe EmptyJsonObject @@ -78,7 +76,7 @@ class InitializeDslTest { @Test fun `capabilities DSL should support direct typed values`() { val samplingValue = ClientCapabilities.Sampling(tools = EmptyJsonObject) - val elicitationObj = buildJsonObject { put("key", "value") } + val elicitationValue = ClientCapabilities.Elicitation(form = EmptyJsonObject, url = EmptyJsonObject) val experimentalObj = buildJsonObject { put("key", "value") } val extensionsMap = mapOf( "io.modelcontextprotocol/ui" to buildJsonObject { put("key", "value") }, @@ -88,7 +86,7 @@ class InitializeDslTest { protocolVersion = "1.0" capabilities { sampling(samplingValue) - elicitation(elicitationObj) + elicitation(elicitationValue) experimental(experimentalObj) extensions(extensionsMap) } @@ -97,7 +95,7 @@ class InitializeDslTest { request.params.capabilities.shouldNotBeNull { sampling shouldBe samplingValue - elicitation shouldBe elicitationObj + elicitation shouldBe elicitationValue experimental shouldBe experimentalObj extensions shouldBe extensionsMap } diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt index e7da26f..d215bd5 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt @@ -172,6 +172,12 @@ public open class ServerSession( } } + Defined.TasksGet, + Defined.TasksResult, + Defined.TasksList, + Defined.TasksCancel, + -> assertTasksCapabilityForMethod(method) + Defined.Ping -> { // No specific capability required } @@ -182,6 +188,28 @@ public open class ServerSession( } } + private fun assertTasksCapabilityForMethod(method: Method) { + val tasks = clientCapabilities?.tasks + ?: error("Client does not support tasks (required for ${method.value})") + when (method) { + Defined.TasksList -> { + if (tasks.list == null) { + error("Client does not support listing tasks (required for ${method.value})") + } + } + + Defined.TasksCancel -> { + if (tasks.cancel == null) { + error("Client does not support cancelling tasks (required for ${method.value})") + } + } + + else -> { + // TasksGet, TasksResult: base tasks capability suffices. + } + } + } + /** * Asserts that the server can handle the specified notification method. * @@ -225,6 +253,12 @@ public open class ServerSession( } } + Defined.NotificationsTasksStatus -> { + if (serverCapabilities.tasks == null) { + error("Server does not support tasks (required for ${method.value})") + } + } + Defined.NotificationsCancelled, Defined.NotificationsProgress, -> { diff --git a/kotlin-sdk-server/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSessionAssertCapabilityTest.kt b/kotlin-sdk-server/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSessionAssertCapabilityTest.kt new file mode 100644 index 0000000..a172e96 --- /dev/null +++ b/kotlin-sdk-server/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSessionAssertCapabilityTest.kt @@ -0,0 +1,147 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.kotest.matchers.string.shouldContain +import io.modelcontextprotocol.kotlin.sdk.shared.Transport +import io.modelcontextprotocol.kotlin.sdk.shared.TransportSendOptions +import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest +import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION +import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.RequestId +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.toJSON +import kotlinx.coroutines.test.runTest +import kotlin.test.Test +import kotlin.test.assertFailsWith + +/** + * Tests for the protected helpers [ServerSession.assertCapabilityForMethod] and + * [ServerSession.assertNotificationCapability]. + * + * Both helpers are `protected`, so this suite uses a small subclass [TestServerSession] + * that re-exports them via [TestServerSession.exposedAssertCapabilityForMethod] and + * [TestServerSession.exposedAssertNotificationCapability]. Client capabilities are + * seeded by replaying an [InitializeRequest] through [InitializeReplayTransport]: + * the transport drives the inbound side of the handshake on `start()`, which causes + * the session's internal [Method.Defined.Initialize] handler to populate + * [ServerSession.clientCapabilities]. + */ +class ServerSessionAssertCapabilityTest { + + @Test + fun `server TasksGet throws when client has no tasks capability`() = runTest { + val session = newTestServerSession(clientCapabilities = ClientCapabilities()) + val ex = assertFailsWith { + session.exposedAssertCapabilityForMethod(Method.Defined.TasksGet) + } + ex.message.orEmpty() shouldContain "Client does not support tasks" + } + + @Test + fun `server TasksGet succeeds when client declared tasks`() = runTest { + val session = newTestServerSession( + clientCapabilities = ClientCapabilities(tasks = ClientCapabilities.Tasks()), + ) + session.exposedAssertCapabilityForMethod(Method.Defined.TasksGet) + } + + @Test + fun `server TasksList throws when client tasks list is null`() = runTest { + val session = newTestServerSession( + clientCapabilities = ClientCapabilities(tasks = ClientCapabilities.Tasks()), + ) + val ex = assertFailsWith { + session.exposedAssertCapabilityForMethod(Method.Defined.TasksList) + } + ex.message.orEmpty() shouldContain "Client does not support listing tasks" + } + + @Test + fun `server TasksCancel throws when client tasks cancel is null`() = runTest { + val session = newTestServerSession( + clientCapabilities = ClientCapabilities(tasks = ClientCapabilities.Tasks()), + ) + val ex = assertFailsWith { + session.exposedAssertCapabilityForMethod(Method.Defined.TasksCancel) + } + ex.message.orEmpty() shouldContain "Client does not support cancelling tasks" + } + + @Test + fun `server NotificationsTasksStatus throws when server has no tasks capability`() = runTest { + val session = newTestServerSession(serverCapabilities = ServerCapabilities()) + val ex = assertFailsWith { + session.exposedAssertNotificationCapability(Method.Defined.NotificationsTasksStatus) + } + ex.message.orEmpty() shouldContain "Server does not support tasks" + } + + private suspend fun newTestServerSession( + clientCapabilities: ClientCapabilities = ClientCapabilities(), + serverCapabilities: ServerCapabilities = ServerCapabilities(), + ): TestServerSession { + val session = TestServerSession( + serverInfo = Implementation("test-server", "1.0.0"), + options = ServerOptions(capabilities = serverCapabilities), + instructions = null, + ) + val transport = InitializeReplayTransport(clientCapabilities) + session.connect(transport) + return session + } + + /** + * Test-only [ServerSession] subclass that exposes the protected + * [ServerSession.assertCapabilityForMethod] and + * [ServerSession.assertNotificationCapability] helpers to the test code. + */ + private class TestServerSession(serverInfo: Implementation, options: ServerOptions, instructions: String?) : + ServerSession(serverInfo, options, instructions) { + fun exposedAssertCapabilityForMethod(method: Method): Unit = assertCapabilityForMethod(method) + fun exposedAssertNotificationCapability(method: Method): Unit = assertNotificationCapability(method) + } + + /** + * Minimal in-memory [Transport] that drives the server-side initialize handshake. + * + * On [start], it dispatches an [InitializeRequest] carrying the configured + * [clientCapabilities] to the session via [onMessage]. The session's internal + * `initialize` handler runs synchronously and populates + * [ServerSession.clientCapabilities] before [start] returns. Other JSON-RPC + * traffic the session subsequently emits is silently consumed. + */ + private class InitializeReplayTransport(private val clientCapabilities: ClientCapabilities) : Transport { + private var onMessageBlock: (suspend (JSONRPCMessage) -> Unit)? = null + private var onCloseBlock: (() -> Unit)? = null + + override suspend fun start() { + val initializeRequest = InitializeRequest( + params = InitializeRequestParams( + protocolVersion = LATEST_PROTOCOL_VERSION, + capabilities = clientCapabilities, + clientInfo = Implementation("mock-client", "1.0.0"), + ), + ).toJSON().copy(id = RequestId.NumberId(1)) + onMessageBlock?.invoke(initializeRequest) + } + + override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) = Unit + + override suspend fun close() { + onCloseBlock?.invoke() + } + + override fun onMessage(block: suspend (JSONRPCMessage) -> Unit) { + onMessageBlock = block + } + + override fun onClose(block: () -> Unit) { + onCloseBlock = block + } + + override fun onError(block: (Throwable) -> Unit) = Unit + } +} From 97a429f42488fcd99e627f64d95d81a405add39f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 13:52:17 +0200 Subject: [PATCH 294/449] chore(deps): bump org.junit.jupiter:junit-jupiter-params from 5.14.3 to 5.14.4 in the testing group across 1 directory (#768) Bumps the testing group with 1 update in the / directory: [org.junit.jupiter:junit-jupiter-params](https://github.com/junit-team/junit-framework). Updates `org.junit.jupiter:junit-jupiter-params` from 5.14.3 to 5.14.4
    Release notes

    Sourced from org.junit.jupiter:junit-jupiter-params's releases.

    JUnit 5.14.4 = Platform 1.14.4 + Jupiter 5.14.4 + Vintage 5.14.4

    See Release Notes.

    Full Changelog: https://github.com/junit-team/junit-framework/compare/r5.14.3...r5.14.4

    Commits
    • 096fd69 Release 5.14.4
    • 11fd02b Remove JDK 24 (EOL)
    • 5b7f023 Fix integration tests on JDK 27
    • 18f842a Test against JDK 26 (GA) and JDK 27 (EA)
    • a5bf336 Polish 5.14.4 release notes
    • 7667c14 Fix race condition in NodeTestTask (#5427)
    • ac01f2d Include full display name in legacy XML reports (#5524)
    • fbd4a8e Include class template invocation index in legacy reporting names
    • 7d6f0c0 Only check PDF for GA releases
    • 14f534d Test 5.14.x against JDK 25 ga (#5623)
    • Additional commits viewable in compare view

    Most Recent Ignore Conditions Applied to This Pull Request | Dependency Name | Ignore Conditions | | --- | --- | | org.junit.jupiter:junit-jupiter-params | [>= 6.a, < 7] |
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5b04ca4..2480fb0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,7 +23,7 @@ mockk = "1.14.9" serialization = "1.11.0" slf4j = "2.0.17" logback = "1.5.32" -junit = "5.14.3" +junit = "5.14.4" [libraries] # Plugins From 9764036ec51f4f1bbeb8e01649c99653fb4bb8b6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 14:13:35 +0200 Subject: [PATCH 295/449] chore(deps): bump org.slf4j:slf4j-simple from 2.0.17 to 2.0.18 in the infrastructure group across 1 directory (#769) Bumps the infrastructure group with 1 update in the / directory: org.slf4j:slf4j-simple. Updates `org.slf4j:slf4j-simple` from 2.0.17 to 2.0.18 Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2480fb0..bf36c70 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,7 +21,7 @@ ktor = "3.3.3" logging = "8.0.02" mockk = "1.14.9" serialization = "1.11.0" -slf4j = "2.0.17" +slf4j = "2.0.18" logback = "1.5.32" junit = "5.14.4" From af7c5e13174465ef75fef90be46b250f3e1992db Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 15:18:30 +0200 Subject: [PATCH 296/449] chore(deps): bump the other-dependencies group across 5 directories with 3 updates (#770) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the other-dependencies group with 3 updates in the /samples/kotlin-mcp-client directory: [gradle-wrapper](https://github.com/gradle/gradle), [com.anthropic:anthropic-java](https://github.com/anthropics/anthropic-sdk-java) and org.slf4j:slf4j-simple. Bumps the other-dependencies group with 2 updates in the /samples/kotlin-mcp-server directory: [gradle-wrapper](https://github.com/gradle/gradle) and org.slf4j:slf4j-simple. Bumps the other-dependencies group with 1 update in the /samples/kotlinlang-mcp-server directory: [gradle-wrapper](https://github.com/gradle/gradle). Bumps the other-dependencies group with 2 updates in the /samples/simple-streamable-server directory: [gradle-wrapper](https://github.com/gradle/gradle) and org.slf4j:slf4j-simple. Bumps the other-dependencies group with 2 updates in the /samples/weather-stdio-server directory: [gradle-wrapper](https://github.com/gradle/gradle) and org.slf4j:slf4j-simple. Updates `gradle-wrapper` from 9.5.0 to 9.5.1
    Release notes

    Sourced from gradle-wrapper's releases.

    9.5.1

    The Gradle team is excited to announce Gradle 9.5.1.

    Here are the highlights of this release:

    • Task provenance in reports and failure messages
    • Type-safe accessors for precompiled Kotlin Settings plugins

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: atm1020, mataha, Adam, Attila Kelemen, Benedikt Ritter, Björn Kautler, Caro Silva Rode, CHANHAN, Dmitry Nezavitin, Eng Zer Jun, KugelLibelle, Madalin Valceleanu, Markus Gaisbauer, Oliver Kopp, Philip Wedemann, ploober, Roberto Perez Alcolea, Rohit Anand, Suvrat Acharya, Ujwal Suresh Vanjare, Victor Merkulov

    Upgrade instructions

    Switch your build to use Gradle 9.5.1 by updating your wrapper:

    ./gradlew wrapper --gradle-version=9.5.1 && ./gradlew
    wrapper
    

    See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

    For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

    Reporting problems

    If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

    We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

    Commits
    • fd78213 Update Documentation Infrastructure: Fix scrolling issue in user manual (#37861)
    • 7758437 fix scroll
    • 2fd605f Only try to run as worker thread in DefaultBuildOperationQueue (#37845)
    • af69849 Release notes for Gradle 9.5.1 (#37853)
    • f4d9d03 Release notes for Gradle 9.5.1
    • 01eda3a Address review feedback on worker-lease retry changes
    • 7024e15 Revert enrich file visitor with size info on release branch (#37848)
    • d51476f Fix tryRunAsWorkerThread null-return test to match contract
    • 090ebab Revert "Add getLength() to FilePropertyVisitor.VisitState"
    • bceab24 Revert "Fix annotation"
    • Additional commits viewable in compare view

    Updates `com.anthropic:anthropic-java` from 2.31.0 to 2.32.0
    Release notes

    Sourced from com.anthropic:anthropic-java's releases.

    v2.32.0

    2.32.0 (2026-05-13)

    Full Changelog: v2.31.0...v2.32.0

    Features

    • api: Add BetaManagedAgentsSearchResultBlock types (253feaa)
    • api: Add support for cache diagnostics beta (7a32231)
    • mcp: add mcp tool helpers (#1161) (5d58812)

    Chores

    Changelog

    Sourced from com.anthropic:anthropic-java's changelog.

    2.32.0 (2026-05-13)

    Full Changelog: v2.31.0...v2.32.0

    Features

    • api: Add BetaManagedAgentsSearchResultBlock types (253feaa)
    • api: Add support for cache diagnostics beta (7a32231)
    • mcp: add mcp tool helpers (#1161) (5d58812)

    Chores

    Commits
    • 72d9622 release: 2.32.0
    • b99fa17 feat(api): Add support for cache diagnostics beta
    • 54fefae test: re-enable beta skill/version create tests (#1243)
    • 5224fff chore(api): spec updates
    • dd0d0da codegen metadata
    • 3f9458f feat(api): Add BetaManagedAgentsSearchResultBlock types
    • c43e2f1 ci: pin GitHub Actions to commit SHAs
    • 5228802 feat(mcp): add mcp tool helpers (#1161)
    • 1bc3d65 ci(release-please): don't split out aws release versioning for now (#332)
    • See full diff in compare view

    Updates `org.slf4j:slf4j-simple` from 2.0.17 to 2.0.18 Updates `gradle-wrapper` from 9.5.0 to 9.5.1
    Release notes

    Sourced from gradle-wrapper's releases.

    9.5.1

    The Gradle team is excited to announce Gradle 9.5.1.

    Here are the highlights of this release:

    • Task provenance in reports and failure messages
    • Type-safe accessors for precompiled Kotlin Settings plugins

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: atm1020, mataha, Adam, Attila Kelemen, Benedikt Ritter, Björn Kautler, Caro Silva Rode, CHANHAN, Dmitry Nezavitin, Eng Zer Jun, KugelLibelle, Madalin Valceleanu, Markus Gaisbauer, Oliver Kopp, Philip Wedemann, ploober, Roberto Perez Alcolea, Rohit Anand, Suvrat Acharya, Ujwal Suresh Vanjare, Victor Merkulov

    Upgrade instructions

    Switch your build to use Gradle 9.5.1 by updating your wrapper:

    ./gradlew wrapper --gradle-version=9.5.1 && ./gradlew
    wrapper
    

    See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

    For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

    Reporting problems

    If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

    We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

    Commits
    • fd78213 Update Documentation Infrastructure: Fix scrolling issue in user manual (#37861)
    • 7758437 fix scroll
    • 2fd605f Only try to run as worker thread in DefaultBuildOperationQueue (#37845)
    • af69849 Release notes for Gradle 9.5.1 (#37853)
    • f4d9d03 Release notes for Gradle 9.5.1
    • 01eda3a Address review feedback on worker-lease retry changes
    • 7024e15 Revert enrich file visitor with size info on release branch (#37848)
    • d51476f Fix tryRunAsWorkerThread null-return test to match contract
    • 090ebab Revert "Add getLength() to FilePropertyVisitor.VisitState"
    • bceab24 Revert "Fix annotation"
    • Additional commits viewable in compare view

    Updates `org.slf4j:slf4j-simple` from 2.0.17 to 2.0.18 Updates `gradle-wrapper` from 9.5.0 to 9.5.1
    Release notes

    Sourced from gradle-wrapper's releases.

    9.5.1

    The Gradle team is excited to announce Gradle 9.5.1.

    Here are the highlights of this release:

    • Task provenance in reports and failure messages
    • Type-safe accessors for precompiled Kotlin Settings plugins

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: atm1020, mataha, Adam, Attila Kelemen, Benedikt Ritter, Björn Kautler, Caro Silva Rode, CHANHAN, Dmitry Nezavitin, Eng Zer Jun, KugelLibelle, Madalin Valceleanu, Markus Gaisbauer, Oliver Kopp, Philip Wedemann, ploober, Roberto Perez Alcolea, Rohit Anand, Suvrat Acharya, Ujwal Suresh Vanjare, Victor Merkulov

    Upgrade instructions

    Switch your build to use Gradle 9.5.1 by updating your wrapper:

    ./gradlew wrapper --gradle-version=9.5.1 && ./gradlew
    wrapper
    

    See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

    For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

    Reporting problems

    If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

    We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

    Commits
    • fd78213 Update Documentation Infrastructure: Fix scrolling issue in user manual (#37861)
    • 7758437 fix scroll
    • 2fd605f Only try to run as worker thread in DefaultBuildOperationQueue (#37845)
    • af69849 Release notes for Gradle 9.5.1 (#37853)
    • f4d9d03 Release notes for Gradle 9.5.1
    • 01eda3a Address review feedback on worker-lease retry changes
    • 7024e15 Revert enrich file visitor with size info on release branch (#37848)
    • d51476f Fix tryRunAsWorkerThread null-return test to match contract
    • 090ebab Revert "Add getLength() to FilePropertyVisitor.VisitState"
    • bceab24 Revert "Fix annotation"
    • Additional commits viewable in compare view

    Updates `gradle-wrapper` from 9.5.0 to 9.5.1
    Release notes

    Sourced from gradle-wrapper's releases.

    9.5.1

    The Gradle team is excited to announce Gradle 9.5.1.

    Here are the highlights of this release:

    • Task provenance in reports and failure messages
    • Type-safe accessors for precompiled Kotlin Settings plugins

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: atm1020, mataha, Adam, Attila Kelemen, Benedikt Ritter, Björn Kautler, Caro Silva Rode, CHANHAN, Dmitry Nezavitin, Eng Zer Jun, KugelLibelle, Madalin Valceleanu, Markus Gaisbauer, Oliver Kopp, Philip Wedemann, ploober, Roberto Perez Alcolea, Rohit Anand, Suvrat Acharya, Ujwal Suresh Vanjare, Victor Merkulov

    Upgrade instructions

    Switch your build to use Gradle 9.5.1 by updating your wrapper:

    ./gradlew wrapper --gradle-version=9.5.1 && ./gradlew
    wrapper
    

    See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

    For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

    Reporting problems

    If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

    We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

    Commits
    • fd78213 Update Documentation Infrastructure: Fix scrolling issue in user manual (#37861)
    • 7758437 fix scroll
    • 2fd605f Only try to run as worker thread in DefaultBuildOperationQueue (#37845)
    • af69849 Release notes for Gradle 9.5.1 (#37853)
    • f4d9d03 Release notes for Gradle 9.5.1
    • 01eda3a Address review feedback on worker-lease retry changes
    • 7024e15 Revert enrich file visitor with size info on release branch (#37848)
    • d51476f Fix tryRunAsWorkerThread null-return test to match contract
    • 090ebab Revert "Add getLength() to FilePropertyVisitor.VisitState"
    • bceab24 Revert "Fix annotation"
    • Additional commits viewable in compare view

    Updates `org.slf4j:slf4j-simple` from 2.0.17 to 2.0.18 Updates `gradle-wrapper` from 9.5.0 to 9.5.1
    Release notes

    Sourced from gradle-wrapper's releases.

    9.5.1

    The Gradle team is excited to announce Gradle 9.5.1.

    Here are the highlights of this release:

    • Task provenance in reports and failure messages
    • Type-safe accessors for precompiled Kotlin Settings plugins

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: atm1020, mataha, Adam, Attila Kelemen, Benedikt Ritter, Björn Kautler, Caro Silva Rode, CHANHAN, Dmitry Nezavitin, Eng Zer Jun, KugelLibelle, Madalin Valceleanu, Markus Gaisbauer, Oliver Kopp, Philip Wedemann, ploober, Roberto Perez Alcolea, Rohit Anand, Suvrat Acharya, Ujwal Suresh Vanjare, Victor Merkulov

    Upgrade instructions

    Switch your build to use Gradle 9.5.1 by updating your wrapper:

    ./gradlew wrapper --gradle-version=9.5.1 && ./gradlew
    wrapper
    

    See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

    For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

    Reporting problems

    If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

    We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

    Commits
    • fd78213 Update Documentation Infrastructure: Fix scrolling issue in user manual (#37861)
    • 7758437 fix scroll
    • 2fd605f Only try to run as worker thread in DefaultBuildOperationQueue (#37845)
    • af69849 Release notes for Gradle 9.5.1 (#37853)
    • f4d9d03 Release notes for Gradle 9.5.1
    • 01eda3a Address review feedback on worker-lease retry changes
    • 7024e15 Revert enrich file visitor with size info on release branch (#37848)
    • d51476f Fix tryRunAsWorkerThread null-return test to match contract
    • 090ebab Revert "Add getLength() to FilePropertyVisitor.VisitState"
    • bceab24 Revert "Fix annotation"
    • Additional commits viewable in compare view

    Updates `org.slf4j:slf4j-simple` from 2.0.17 to 2.0.18 Updates `gradle-wrapper` from 9.5.0 to 9.5.1
    Release notes

    Sourced from gradle-wrapper's releases.

    9.5.1

    The Gradle team is excited to announce Gradle 9.5.1.

    Here are the highlights of this release:

    • Task provenance in reports and failure messages
    • Type-safe accessors for precompiled Kotlin Settings plugins

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: atm1020, mataha, Adam, Attila Kelemen, Benedikt Ritter, Björn Kautler, Caro Silva Rode, CHANHAN, Dmitry Nezavitin, Eng Zer Jun, KugelLibelle, Madalin Valceleanu, Markus Gaisbauer, Oliver Kopp, Philip Wedemann, ploober, Roberto Perez Alcolea, Rohit Anand, Suvrat Acharya, Ujwal Suresh Vanjare, Victor Merkulov

    Upgrade instructions

    Switch your build to use Gradle 9.5.1 by updating your wrapper:

    ./gradlew wrapper --gradle-version=9.5.1 && ./gradlew
    wrapper
    

    See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

    For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

    Reporting problems

    If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

    We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

    Commits
    • fd78213 Update Documentation Infrastructure: Fix scrolling issue in user manual (#37861)
    • 7758437 fix scroll
    • 2fd605f Only try to run as worker thread in DefaultBuildOperationQueue (#37845)
    • af69849 Release notes for Gradle 9.5.1 (#37853)
    • f4d9d03 Release notes for Gradle 9.5.1
    • 01eda3a Address review feedback on worker-lease retry changes
    • 7024e15 Revert enrich file visitor with size info on release branch (#37848)
    • d51476f Fix tryRunAsWorkerThread null-return test to match contract
    • 090ebab Revert "Add getLength() to FilePropertyVisitor.VisitState"
    • bceab24 Revert "Fix annotation"
    • Additional commits viewable in compare view

    Updates `org.slf4j:slf4j-simple` from 2.0.17 to 2.0.18 Updates `gradle-wrapper` from 9.5.0 to 9.5.1
    Release notes

    Sourced from gradle-wrapper's releases.

    9.5.1

    The Gradle team is excited to announce Gradle 9.5.1.

    Here are the highlights of this release:

    • Task provenance in reports and failure messages
    • Type-safe accessors for precompiled Kotlin Settings plugins

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: atm1020, mataha, Adam, Attila Kelemen, Benedikt Ritter, Björn Kautler, Caro Silva Rode, CHANHAN, Dmitry Nezavitin, Eng Zer Jun, KugelLibelle, Madalin Valceleanu, Markus Gaisbauer, Oliver Kopp, Philip Wedemann, ploober, Roberto Perez Alcolea, Rohit Anand, Suvrat Acharya, Ujwal Suresh Vanjare, Victor Merkulov

    Upgrade instructions

    Switch your build to use Gradle 9.5.1 by updating your wrapper:

    ./gradlew wrapper --gradle-version=9.5.1 && ./gradlew
    wrapper
    

    See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

    For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

    Reporting problems

    If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

    We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

    Commits
    • fd78213 Update Documentation Infrastructure: Fix scrolling issue in user manual (#37861)
    • 7758437 fix scroll
    • 2fd605f Only try to run as worker thread in DefaultBuildOperationQueue (#37845)
    • af69849 Release notes for Gradle 9.5.1 (#37853)
    • f4d9d03 Release notes for Gradle 9.5.1
    • 01eda3a Address review feedback on worker-lease retry changes
    • 7024e15 Revert enrich file visitor with size info on release branch (#37848)
    • d51476f Fix tryRunAsWorkerThread null-return test to match contract
    • 090ebab Revert "Add getLength() to FilePropertyVisitor.VisitState"
    • bceab24 Revert "Fix annotation"
    • Additional commits viewable in compare view

    Updates `org.slf4j:slf4j-simple` from 2.0.17 to 2.0.18 Updates `gradle-wrapper` from 9.5.0 to 9.5.1
    Release notes

    Sourced from gradle-wrapper's releases.

    9.5.1

    The Gradle team is excited to announce Gradle 9.5.1.

    Here are the highlights of this release:

    • Task provenance in reports and failure messages
    • Type-safe accessors for precompiled Kotlin Settings plugins

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: atm1020, mataha, Adam, Attila Kelemen, Benedikt Ritter, Björn Kautler, Caro Silva Rode, CHANHAN, Dmitry Nezavitin, Eng Zer Jun, KugelLibelle, Madalin Valceleanu, Markus Gaisbauer, Oliver Kopp, Philip Wedemann, ploober, Roberto Perez Alcolea, Rohit Anand, Suvrat Acharya, Ujwal Suresh Vanjare, Victor Merkulov

    Upgrade instructions

    Switch your build to use Gradle 9.5.1 by updating your wrapper:

    ./gradlew wrapper --gradle-version=9.5.1 && ./gradlew
    wrapper
    

    See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

    For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

    Reporting problems

    If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

    We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

    Commits
    • fd78213 Update Documentation Infrastructure: Fix scrolling issue in user manual (#37861)
    • 7758437 fix scroll
    • 2fd605f Only try to run as worker thread in DefaultBuildOperationQueue (#37845)
    • af69849 Release notes for Gradle 9.5.1 (#37853)
    • f4d9d03 Release notes for Gradle 9.5.1
    • 01eda3a Address review feedback on worker-lease retry changes
    • 7024e15 Revert enrich file visitor with size info on release branch (#37848)
    • d51476f Fix tryRunAsWorkerThread null-return test to match contract
    • 090ebab Revert "Add getLength() to FilePropertyVisitor.VisitState"
    • bceab24 Revert "Fix annotation"
    • Additional commits viewable in compare view

    Updates `gradle-wrapper` from 9.5.0 to 9.5.1
    Release notes

    Sourced from gradle-wrapper's releases.

    9.5.1

    The Gradle team is excited to announce Gradle 9.5.1.

    Here are the highlights of this release:

    • Task provenance in reports and failure messages
    • Type-safe accessors for precompiled Kotlin Settings plugins

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: atm1020, mataha, Adam, Attila Kelemen, Benedikt Ritter, Björn Kautler, Caro Silva Rode, CHANHAN, Dmitry Nezavitin, Eng Zer Jun, KugelLibelle, Madalin Valceleanu, Markus Gaisbauer, Oliver Kopp, Philip Wedemann, ploober, Roberto Perez Alcolea, Rohit Anand, Suvrat Acharya, Ujwal Suresh Vanjare, Victor Merkulov

    Upgrade instructions

    Switch your build to use Gradle 9.5.1 by updating your wrapper:

    ./gradlew wrapper --gradle-version=9.5.1 && ./gradlew
    wrapper
    

    See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

    For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

    Reporting problems

    If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

    We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

    Commits
    • fd78213 Update Documentation Infrastructure: Fix scrolling issue in user manual (#37861)
    • 7758437 fix scroll
    • 2fd605f Only try to run as worker thread in DefaultBuildOperationQueue (#37845)
    • af69849 Release notes for Gradle 9.5.1 (#37853)
    • f4d9d03 Release notes for Gradle 9.5.1
    • 01eda3a Address review feedback on worker-lease retry changes
    • 7024e15 Revert enrich file visitor with size info on release branch (#37848)
    • d51476f Fix tryRunAsWorkerThread null-return test to match contract
    • 090ebab Revert "Add getLength() to FilePropertyVisitor.VisitState"
    • bceab24 Revert "Fix annotation"
    • Additional commits viewable in compare view

    Updates `org.slf4j:slf4j-simple` from 2.0.17 to 2.0.18 Updates `gradle-wrapper` from 9.5.0 to 9.5.1
    Release notes

    Sourced from gradle-wrapper's releases.

    9.5.1

    The Gradle team is excited to announce Gradle 9.5.1.

    Here are the highlights of this release:

    • Task provenance in reports and failure messages
    • Type-safe accessors for precompiled Kotlin Settings plugins

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: atm1020, mataha, Adam, Attila Kelemen, Benedikt Ritter, Björn Kautler, Caro Silva Rode, CHANHAN, Dmitry Nezavitin, Eng Zer Jun, KugelLibelle, Madalin Valceleanu, Markus Gaisbauer, Oliver Kopp, Philip Wedemann, ploober, Roberto Perez Alcolea, Rohit Anand, Suvrat Acharya, Ujwal Suresh Vanjare, Victor Merkulov

    Upgrade instructions

    Switch your build to use Gradle 9.5.1 by updating your wrapper:

    ./gradlew wrapper --gradle-version=9.5.1 && ./gradlew
    wrapper
    

    See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

    For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

    Reporting problems

    If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

    We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

    Commits
    • fd78213 Update Documentation Infrastructure: Fix scrolling issue in user manual (#37861)
    • 7758437 fix scroll
    • 2fd605f Only try to run as worker thread in DefaultBuildOperationQueue (#37845)
    • af69849 Release notes for Gradle 9.5.1 (#37853)
    • f4d9d03 Release notes for Gradle 9.5.1
    • 01eda3a Address review feedback on worker-lease retry changes
    • 7024e15 Revert enrich file visitor with size info on release branch (#37848)
    • d51476f Fix tryRunAsWorkerThread null-return test to match contract
    • 090ebab Revert "Add getLength() to FilePropertyVisitor.VisitState"
    • bceab24 Revert "Fix annotation"
    • Additional commits viewable in compare view

    Updates `org.slf4j:slf4j-simple` from 2.0.17 to 2.0.18 Updates `gradle-wrapper` from 9.5.0 to 9.5.1
    Release notes

    Sourced from gradle-wrapper's releases.

    9.5.1

    The Gradle team is excited to announce Gradle 9.5.1.

    Here are the highlights of this release:

    • Task provenance in reports and failure messages
    • Type-safe accessors for precompiled Kotlin Settings plugins

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: atm1020, mataha, Adam, Attila Kelemen, Benedikt Ritter, Björn Kautler, Caro Silva Rode, CHANHAN, Dmitry Nezavitin, Eng Zer Jun, KugelLibelle, Madalin Valceleanu, Markus Gaisbauer, Oliver Kopp, Philip Wedemann, ploober, Roberto Perez Alcolea, Rohit Anand, Suvrat Acharya, Ujwal Suresh Vanjare, Victor Merkulov

    Upgrade instructions

    Switch your build to use Gradle 9.5.1 by updating your wrapper:

    ./gradlew wrapper --gradle-version=9.5.1 && ./gradlew
    wrapper
    

    See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

    For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

    Reporting problems

    If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

    We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

    Commits
    • fd78213 Update Documentation Infrastructure: Fix scrolling issue in user manual (#37861)
    • 7758437 fix scroll
    • 2fd605f Only try to run as worker thread in DefaultBuildOperationQueue (#37845)
    • af69849 Release notes for Gradle 9.5.1 (#37853)
    • f4d9d03 Release notes for Gradle 9.5.1
    • 01eda3a Address review feedback on worker-lease retry changes
    • 7024e15 Revert enrich file visitor with size info on release branch (#37848)
    • d51476f Fix tryRunAsWorkerThread null-return test to match contract
    • 090ebab Revert "Add getLength() to FilePropertyVisitor.VisitState"
    • bceab24 Revert "Fix annotation"
    • Additional commits viewable in compare view

    Updates `gradle-wrapper` from 9.5.0 to 9.5.1
    Release notes

    Sourced from gradle-wrapper's releases.

    9.5.1

    The Gradle team is excited to announce Gradle 9.5.1.

    Here are the highlights of this release:

    • Task provenance in reports and failure messages
    • Type-safe accessors for precompiled Kotlin Settings plugins

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: atm1020, mataha, Adam, Attila Kelemen, Benedikt Ritter, Björn Kautler, Caro Silva Rode, CHANHAN, Dmitry Nezavitin, Eng Zer Jun, KugelLibelle, Madalin Valceleanu, Markus Gaisbauer, Oliver Kopp, Philip Wedemann, ploober, Roberto Perez Alcolea, Rohit Anand, Suvrat Acharya, Ujwal Suresh Vanjare, Victor Merkulov

    Upgrade instructions

    Switch your build to use Gradle 9.5.1 by upd... _Description has been truncated_ Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-client/gradle/libs.versions.toml | 4 ++-- .../gradle/wrapper/gradle-wrapper.properties | 2 +- samples/kotlin-mcp-server/gradle/libs.versions.toml | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- samples/simple-streamable-server/gradle/libs.versions.toml | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- samples/weather-stdio-server/gradle/libs.versions.toml | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index 6d4a979..f425a56 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -1,10 +1,10 @@ [versions] -anthropic = "2.31.0" +anthropic = "2.32.0" kotlin = "2.3.21" ktor = "3.3.3" mcp-kotlin = "0.12.0" shadow = "9.4.1" -slf4j = "2.0.17" +slf4j = "2.0.18" [libraries] anthropic-java = { group = "com.anthropic", name = "anthropic-java", version.ref = "anthropic" } diff --git a/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties b/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties index b52fb7e..df6a6ad 100644 --- a/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties +++ b/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 diff --git a/samples/kotlin-mcp-server/gradle/libs.versions.toml b/samples/kotlin-mcp-server/gradle/libs.versions.toml index 2eccae2..91d7878 100644 --- a/samples/kotlin-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-server/gradle/libs.versions.toml @@ -2,7 +2,7 @@ kotlin = "2.3.21" ktor = "3.3.3" mcp-kotlin = "0.12.0" -slf4j = "2.0.17" +slf4j = "2.0.18" shadow = "9.4.1" [libraries] diff --git a/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties b/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties index b52fb7e..df6a6ad 100644 --- a/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties +++ b/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 diff --git a/samples/kotlinlang-mcp-server/gradle/wrapper/gradle-wrapper.properties b/samples/kotlinlang-mcp-server/gradle/wrapper/gradle-wrapper.properties index b52fb7e..df6a6ad 100644 --- a/samples/kotlinlang-mcp-server/gradle/wrapper/gradle-wrapper.properties +++ b/samples/kotlinlang-mcp-server/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 diff --git a/samples/simple-streamable-server/gradle/libs.versions.toml b/samples/simple-streamable-server/gradle/libs.versions.toml index 3296f05..70a87ca 100644 --- a/samples/simple-streamable-server/gradle/libs.versions.toml +++ b/samples/simple-streamable-server/gradle/libs.versions.toml @@ -3,7 +3,7 @@ kotlin = "2.3.21" ktor = "3.3.3" junit = "6.0.3" mcp-kotlin = "0.12.0" -slf4j = "2.0.17" +slf4j = "2.0.18" shadow = "9.4.1" [libraries] diff --git a/samples/simple-streamable-server/gradle/wrapper/gradle-wrapper.properties b/samples/simple-streamable-server/gradle/wrapper/gradle-wrapper.properties index b52fb7e..df6a6ad 100644 --- a/samples/simple-streamable-server/gradle/wrapper/gradle-wrapper.properties +++ b/samples/simple-streamable-server/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 diff --git a/samples/weather-stdio-server/gradle/libs.versions.toml b/samples/weather-stdio-server/gradle/libs.versions.toml index 8eee298..ac62c36 100644 --- a/samples/weather-stdio-server/gradle/libs.versions.toml +++ b/samples/weather-stdio-server/gradle/libs.versions.toml @@ -5,7 +5,7 @@ ktor = "3.3.3" logging = "8.0.02" mcp-kotlin = "0.12.0" shadow = "9.4.1" -slf4j = "2.0.17" +slf4j = "2.0.18" [libraries] kotlin-logging = { group = "io.github.oshai", name = "kotlin-logging-jvm", version.ref = "logging" } diff --git a/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties b/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties index b52fb7e..df6a6ad 100644 --- a/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties +++ b/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 From c8545606bb87faf7595dafa7269f31f5a5f75703 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 19:59:31 +0200 Subject: [PATCH 297/449] chore(deps): bump the all-dependencies group across 1 directory with 2 updates (#777) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the all-dependencies group with 2 updates in the /integration-test/src/jvmTest/typescript directory: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) and [tsx](https://github.com/privatenumber/tsx). Updates `@types/node` from 25.6.2 to 25.9.0

    Commits

    Updates `tsx` from 4.21.0 to 4.22.1
    Release notes

    Sourced from tsx's releases.

    v4.22.1

    4.22.1 (2026-05-17)

    Bug Fixes

    • resolve tsconfig path aliases containing a colon (#780) (6979f28)

    This release is also available on:

    v4.22.0

    4.22.0 (2026-05-14)

    Features


    This release is also available on:

    v4.21.1

    4.21.1 (2026-05-14)

    Bug Fixes

    • support Node 20.11/21.2 import.meta paths (acf3d8f)
    • support Node.js 24.15.0 (c1d2d45)
    • support Node.js 26.1.0 and 25.9.0 (1d7e528)

    This release is also available on:

    Commits
    • 6979f28 fix: resolve tsconfig path aliases containing a colon (#780)
    • b29f6ee feat: upgrade esbuild to 0.28 (#789)
    • 0dd17e9 test: cover registerHooks loader composition
    • acf3d8f fix: support Node 20.11/21.2 import.meta paths
    • 4bbef80 test: cover configDir paths without baseUrl
    • dddc5ce test: cover sync-hook watch reruns and cleanup retries
    • 09e8f8c test: assert CLI runs without warnings
    • 1d7e528 fix: support Node.js 26.1.0 and 25.9.0
    • c1d2d45 fix: support Node.js 24.15.0
    • d04672d test: update node version feature gates
    • Additional commits viewable in compare view
    Maintainer changes

    This version was pushed to npm by GitHub Actions, a new releaser for tsx since your current version.


    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../src/jvmTest/typescript/package-lock.json | 264 ++++++++---------- .../src/jvmTest/typescript/package.json | 4 +- 2 files changed, 122 insertions(+), 146 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index 6aa468d..ac2f59d 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -15,16 +15,16 @@ "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^25.6.2", + "@types/node": "^25.9.0", "cors": "^2.8.6", - "tsx": "^4.21.0", + "tsx": "^4.22.1", "typescript": "^6.0.3" } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", - "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", "cpu": [ "ppc64" ], @@ -39,9 +39,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", - "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", "cpu": [ "arm" ], @@ -56,9 +56,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", - "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", "cpu": [ "arm64" ], @@ -73,9 +73,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", - "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", "cpu": [ "x64" ], @@ -90,9 +90,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", - "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", "cpu": [ "arm64" ], @@ -107,9 +107,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", - "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", "cpu": [ "x64" ], @@ -124,9 +124,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", - "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", "cpu": [ "arm64" ], @@ -141,9 +141,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", - "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", "cpu": [ "x64" ], @@ -158,9 +158,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", - "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", "cpu": [ "arm" ], @@ -175,9 +175,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", - "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", "cpu": [ "arm64" ], @@ -192,9 +192,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", - "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", "cpu": [ "ia32" ], @@ -209,9 +209,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", - "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", "cpu": [ "loong64" ], @@ -226,9 +226,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", - "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", "cpu": [ "mips64el" ], @@ -243,9 +243,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", - "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", "cpu": [ "ppc64" ], @@ -260,9 +260,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", - "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", "cpu": [ "riscv64" ], @@ -277,9 +277,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", - "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", "cpu": [ "s390x" ], @@ -294,9 +294,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", - "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", "cpu": [ "x64" ], @@ -311,9 +311,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", - "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", "cpu": [ "arm64" ], @@ -328,9 +328,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", - "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", "cpu": [ "x64" ], @@ -345,9 +345,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", - "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", "cpu": [ "arm64" ], @@ -362,9 +362,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", - "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", "cpu": [ "x64" ], @@ -379,9 +379,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", - "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", "cpu": [ "arm64" ], @@ -396,9 +396,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", - "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", "cpu": [ "x64" ], @@ -413,9 +413,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", - "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", "cpu": [ "arm64" ], @@ -430,9 +430,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", - "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", "cpu": [ "ia32" ], @@ -447,9 +447,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", - "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", "cpu": [ "x64" ], @@ -579,13 +579,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.6.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.2.tgz", - "integrity": "sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw==", + "version": "25.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.0.tgz", + "integrity": "sha512-AOQwYUNolgy3VosiRqXrACUXTN8nJUtPl7FJXMqZVyxiiCLhQuG3jXKvCS1ALr+Y2OmZhzzLVlYPEqJaiqkaJQ==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.19.0" + "undici-types": ">=7.24.0 <7.24.7" } }, "node_modules/@types/qs": { @@ -888,9 +888,9 @@ } }, "node_modules/esbuild": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", - "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -901,32 +901,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.2", - "@esbuild/android-arm": "0.27.2", - "@esbuild/android-arm64": "0.27.2", - "@esbuild/android-x64": "0.27.2", - "@esbuild/darwin-arm64": "0.27.2", - "@esbuild/darwin-x64": "0.27.2", - "@esbuild/freebsd-arm64": "0.27.2", - "@esbuild/freebsd-x64": "0.27.2", - "@esbuild/linux-arm": "0.27.2", - "@esbuild/linux-arm64": "0.27.2", - "@esbuild/linux-ia32": "0.27.2", - "@esbuild/linux-loong64": "0.27.2", - "@esbuild/linux-mips64el": "0.27.2", - "@esbuild/linux-ppc64": "0.27.2", - "@esbuild/linux-riscv64": "0.27.2", - "@esbuild/linux-s390x": "0.27.2", - "@esbuild/linux-x64": "0.27.2", - "@esbuild/netbsd-arm64": "0.27.2", - "@esbuild/netbsd-x64": "0.27.2", - "@esbuild/openbsd-arm64": "0.27.2", - "@esbuild/openbsd-x64": "0.27.2", - "@esbuild/openharmony-arm64": "0.27.2", - "@esbuild/sunos-x64": "0.27.2", - "@esbuild/win32-arm64": "0.27.2", - "@esbuild/win32-ia32": "0.27.2", - "@esbuild/win32-x64": "0.27.2" + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" } }, "node_modules/escape-html": { @@ -1147,19 +1147,6 @@ "node": ">= 0.4" } }, - "node_modules/get-tsconfig": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", - "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -1508,16 +1495,6 @@ "node": ">=0.10.0" } }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -1703,14 +1680,13 @@ } }, "node_modules/tsx": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", - "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.1.tgz", + "integrity": "sha512-TvncJykhxAzFCk0VQZKBTClall4Pm7qXDSodb6uxi8QFa8X8mT6ABjxxsQ2opDRYxG7AzcRWXaFtruz5HJKuWg==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "~0.27.0", - "get-tsconfig": "^4.7.5" + "esbuild": "~0.28.0" }, "bin": { "tsx": "dist/cli.mjs" @@ -1751,9 +1727,9 @@ } }, "node_modules/undici-types": { - "version": "7.19.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", - "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", "dev": true, "license": "MIT" }, diff --git a/integration-test/src/jvmTest/typescript/package.json b/integration-test/src/jvmTest/typescript/package.json index 6ec358a..4e776e9 100644 --- a/integration-test/src/jvmTest/typescript/package.json +++ b/integration-test/src/jvmTest/typescript/package.json @@ -16,9 +16,9 @@ "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^25.6.2", + "@types/node": "^25.9.0", "cors": "^2.8.6", - "tsx": "^4.21.0", + "tsx": "^4.22.1", "typescript": "^6.0.3" } } From 6d5bac1e6792fc42ca476527df19c56f51666885 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 21:09:59 +0200 Subject: [PATCH 298/449] chore(deps): bump gradle-wrapper from 9.5.0 to 9.5.1 (#776) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [gradle-wrapper](https://github.com/gradle/gradle) from 9.5.0 to 9.5.1.
    Release notes

    Sourced from gradle-wrapper's releases.

    9.5.1

    The Gradle team is excited to announce Gradle 9.5.1.

    Here are the highlights of this release:

    • Task provenance in reports and failure messages
    • Type-safe accessors for precompiled Kotlin Settings plugins

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: atm1020, mataha, Adam, Attila Kelemen, Benedikt Ritter, Björn Kautler, Caro Silva Rode, CHANHAN, Dmitry Nezavitin, Eng Zer Jun, KugelLibelle, Madalin Valceleanu, Markus Gaisbauer, Oliver Kopp, Philip Wedemann, ploober, Roberto Perez Alcolea, Rohit Anand, Suvrat Acharya, Ujwal Suresh Vanjare, Victor Merkulov

    Upgrade instructions

    Switch your build to use Gradle 9.5.1 by updating your wrapper:

    ./gradlew wrapper --gradle-version=9.5.1 && ./gradlew
    wrapper
    

    See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

    For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

    Reporting problems

    If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

    We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

    Commits
    • fd78213 Update Documentation Infrastructure: Fix scrolling issue in user manual (#37861)
    • 7758437 fix scroll
    • 2fd605f Only try to run as worker thread in DefaultBuildOperationQueue (#37845)
    • af69849 Release notes for Gradle 9.5.1 (#37853)
    • f4d9d03 Release notes for Gradle 9.5.1
    • 01eda3a Address review feedback on worker-lease retry changes
    • 7024e15 Revert enrich file visitor with size info on release branch (#37848)
    • d51476f Fix tryRunAsWorkerThread null-return test to match contract
    • 090ebab Revert "Add getLength() to FilePropertyVisitor.VisitState"
    • bceab24 Revert "Fix annotation"
    • Additional commits viewable in compare view

    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b52fb7e..df6a6ad 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 From 1ff5f003b657efef39e41f00591e693afa1757e0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 May 2026 14:53:06 +0200 Subject: [PATCH 299/449] chore(deps): bump qs from 6.14.2 to 6.15.2 in /integration-test/src/jvmTest/typescript (#779) Bumps [qs](https://github.com/ljharb/qs) from 6.14.2 to 6.15.2.
    Changelog

    Sourced from qs's changelog.

    6.15.2

    • [Fix] stringify: skip null/undefined entries in arrayFormat: 'comma' + encodeValuesOnly instead of crashing in encoder
    • [Fix] stringify: use configured delimiter after charsetSentinel (#555)
    • [Fix] stringify: apply formatter to encoded key under strictNullHandling (#554)
    • [Fix] stringify: skip null/undefined filter-array entries instead of crashing in encoder (#551)
    • [Fix] parse: handle nested bracket groups and add regression tests (#530)
    • [readme] fix grammar (#550)
    • [Dev Deps] update @ljharb/eslint-config
    • [Tests] add regression tests for keys containing percent-encoded bracket text

    6.15.1

    • [Fix] parse: parameterLimit: Infinity with throwOnLimitExceeded: true silently drops all parameters
    • [Deps] update @ljharb/eslint-config
    • [Dev Deps] update @ljharb/eslint-config, iconv-lite
    • [Tests] increase coverage

    6.15.0

    • [New] parse: add strictMerge option to wrap object/primitive conflicts in an array (#425, #122)
    • [Fix] duplicates option should not apply to bracket notation keys (#514)
    Commits
    • 9aca407 v6.15.2
    • 5e33d33 [Dev Deps] update @ljharb/eslint-config
    • 21f80b3 [Fix] stringify: skip null/undefined entries in arrayFormat: 'comma' + `e...
    • a0a81ea [Fix] stringify: use configured delimiter after charsetSentinel
    • e3062f7 [Fix] stringify: apply formatter to encoded key under strictNullHandling
    • 0c180a4 [Fix] stringify: skip null/undefined filter-array entries instead of crashi...
    • 3a8b94a [Tests] add regression tests for keys containing percent-encoded bracket text
    • 96755ab [readme] fix grammar
    • a419ce5 [Fix] parse: handle nested bracket groups and add regression tests
    • 3f5e1c5 v6.15.1
    • Additional commits viewable in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=qs&package-manager=npm_and_yarn&previous-version=6.14.2&new-version=6.15.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/modelcontextprotocol/kotlin-sdk/network/alerts).
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- integration-test/src/jvmTest/typescript/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index ac2f59d..6dbdf45 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -1448,9 +1448,9 @@ } }, "node_modules/qs": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" From 78066a9cf6970618ef72a8c9b48c8c2fca03c387 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 May 2026 13:10:59 +0000 Subject: [PATCH 300/449] chore(deps): bump the all-dependencies group across 1 directory with 2 updates (#784) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the all-dependencies group with 2 updates in the /integration-test/src/jvmTest/typescript directory: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) and [tsx](https://github.com/privatenumber/tsx). Updates `@types/node` from 25.9.0 to 25.9.1
    Commits

    Updates `tsx` from 4.22.1 to 4.22.3
    Release notes

    Sourced from tsx's releases.

    v4.22.3

    4.22.3 (2026-05-19)

    Bug Fixes

    • decode typed loader source (dce02fc)
    • preserve entrypoint with TypeScript preload hooks (68f72f3)

    This release is also available on:

    v4.22.2

    4.22.2 (2026-05-18)

    Bug Fixes

    • preserve CJS JSON require in ESM hooks (35b700b)
    • preserve named exports from CommonJS TypeScript (11de737)
    • support module.exports require(esm) interop (cf8f199)

    This release is also available on:

    Commits
    • dce02fc fix: decode typed loader source
    • 68f72f3 fix: preserve entrypoint with TypeScript preload hooks
    • 69455cf test: cover package exports for ambiguous ESM reexports
    • 35b700b fix: preserve CJS JSON require in ESM hooks
    • ef807db chore: update testing dependencies
    • 3917090 test: document compatibility test taxonomy
    • de8113f refactor: centralize Node capability facts
    • c1f62db test: consolidate tsconfig path edge coverage
    • 4e08174 test: consolidate loader hook coverage
    • 674bb30 test: consolidate tsImport commonjs mts coverage
    • Additional commits viewable in compare view

    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../src/jvmTest/typescript/package-lock.json | 16 ++++++++-------- .../src/jvmTest/typescript/package.json | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index 6dbdf45..9e757a5 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -15,9 +15,9 @@ "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^25.9.0", + "@types/node": "^25.9.1", "cors": "^2.8.6", - "tsx": "^4.22.1", + "tsx": "^4.22.3", "typescript": "^6.0.3" } }, @@ -579,9 +579,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.9.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.0.tgz", - "integrity": "sha512-AOQwYUNolgy3VosiRqXrACUXTN8nJUtPl7FJXMqZVyxiiCLhQuG3jXKvCS1ALr+Y2OmZhzzLVlYPEqJaiqkaJQ==", + "version": "25.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", + "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", "dev": true, "license": "MIT", "dependencies": { @@ -1680,9 +1680,9 @@ } }, "node_modules/tsx": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.1.tgz", - "integrity": "sha512-TvncJykhxAzFCk0VQZKBTClall4Pm7qXDSodb6uxi8QFa8X8mT6ABjxxsQ2opDRYxG7AzcRWXaFtruz5HJKuWg==", + "version": "4.22.3", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.3.tgz", + "integrity": "sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/integration-test/src/jvmTest/typescript/package.json b/integration-test/src/jvmTest/typescript/package.json index 4e776e9..40c58b3 100644 --- a/integration-test/src/jvmTest/typescript/package.json +++ b/integration-test/src/jvmTest/typescript/package.json @@ -16,9 +16,9 @@ "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^25.9.0", + "@types/node": "^25.9.1", "cors": "^2.8.6", - "tsx": "^4.22.1", + "tsx": "^4.22.3", "typescript": "^6.0.3" } } From ff4b6232ba1a107058f6243463c9f7298b52dd76 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 May 2026 18:27:57 +0200 Subject: [PATCH 301/449] chore(deps): bump the other-dependencies group across 3 directories with 3 updates (#781) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the other-dependencies group with 1 update in the /samples/kotlin-mcp-client directory: [com.anthropic:anthropic-java](https://github.com/anthropics/anthropic-sdk-java). Bumps the other-dependencies group with 1 update in the /samples/simple-streamable-server directory: [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit-framework). Bumps the other-dependencies group with 1 update in the /samples/weather-stdio-server directory: [io.github.oshai:kotlin-logging-jvm](https://github.com/oshai/kotlin-logging). Updates `com.anthropic:anthropic-java` from 2.32.0 to 2.34.0
    Release notes

    Sourced from com.anthropic:anthropic-java's releases.

    v2.34.0

    2.34.0 (2026-05-21)

    Full Changelog: v2.33.0...v2.34.0

    Features

    • api: Add support for thinking-token-count beta for estimated tokens in thinking block deltas when streaming (c814c7a)

    Bug Fixes

    • test: set required estimatedTokens on BetaThinkingDelta in BetaMessageAccumulatorTest (04aedf3)

    v2.33.0

    2.33.0 (2026-05-19)

    Full Changelog: v2.32.0...v2.33.0

    Features

    • client: Add support for self-hosted sandboxes in CMA with sandbox helpers (d519362)
    Changelog

    Sourced from com.anthropic:anthropic-java's changelog.

    2.34.0 (2026-05-21)

    Full Changelog: v2.33.0...v2.34.0

    Features

    • api: Add support for thinking-token-count beta for estimated tokens in thinking block deltas when streaming (c814c7a)

    Bug Fixes

    • test: set required estimatedTokens on BetaThinkingDelta in BetaMessageAccumulatorTest (04aedf3)

    2.33.0 (2026-05-19)

    Full Changelog: v2.32.0...v2.33.0

    Features

    • client: Add support for self-hosted sandboxes in CMA with sandbox helpers (d519362)
    Commits
    • 765ec0e release: 2.34.0
    • 56c637f fix(test): set required estimatedTokens on BetaThinkingDelta in BetaMessageAc...
    • 5d636b2 feat(api): Add support for thinking-token-count beta for estimated tokens in ...
    • ba2d183 release: 2.33.0
    • 56312db feat(client): Add support for self-hosted sandboxes in CMA with sandbox helpers
    • See full diff in compare view

    Updates `org.junit.jupiter:junit-jupiter` from 6.0.3 to 6.1.0
    Release notes

    Sourced from org.junit.jupiter:junit-jupiter's releases.

    JUnit 6.1.0 = Platform 6.1.0 + Jupiter 6.1.0 + Vintage 6.1.0

    See Release Notes.

    New Contributors

    Full Changelog: https://github.com/junit-team/junit-framework/compare/r6.0.3...r6.1.0

    JUnit 6.1.0-RC1 = Platform 6.1.0-RC1 + Jupiter 6.1.0-RC1 + Vintage 6.1.0-RC1

    See Release Notes.

    New Contributors

    Full Changelog: https://github.com/junit-team/junit-framework/compare/r6.1.0-M1...r6.1.0-RC1

    JUnit 6.1.0-M1 = Platform 6.1.0-M1 + Jupiter 6.1.0-M1 + Vintage 6.1.0-M1

    See Release Notes.

    New Contributors

    Full Changelog: https://github.com/junit-team/junit-framework/compare/r6.0.0...r6.1.0-M1

    Commits

    Updates `io.github.oshai:kotlin-logging-jvm` from 8.0.02 to 8.0.03
    Release notes

    Sourced from io.github.oshai:kotlin-logging-jvm's releases.

    8.0.03

    What's Changed

    Full Changelog: https://github.com/oshai/kotlin-logging/compare/8.0.02...8.0.03

    Commits
    • b641856 bump version to 8.0.04
    • 3e6096d Fix #598: Allow external configuration of logStartupMessage via Properties an...
    • See full diff in compare view

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-client/gradle/libs.versions.toml | 2 +- samples/simple-streamable-server/gradle/libs.versions.toml | 2 +- samples/weather-stdio-server/gradle/libs.versions.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index f425a56..c263c59 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -anthropic = "2.32.0" +anthropic = "2.34.0" kotlin = "2.3.21" ktor = "3.3.3" mcp-kotlin = "0.12.0" diff --git a/samples/simple-streamable-server/gradle/libs.versions.toml b/samples/simple-streamable-server/gradle/libs.versions.toml index 70a87ca..b607a1c 100644 --- a/samples/simple-streamable-server/gradle/libs.versions.toml +++ b/samples/simple-streamable-server/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] kotlin = "2.3.21" ktor = "3.3.3" -junit = "6.0.3" +junit = "6.1.0" mcp-kotlin = "0.12.0" slf4j = "2.0.18" shadow = "9.4.1" diff --git a/samples/weather-stdio-server/gradle/libs.versions.toml b/samples/weather-stdio-server/gradle/libs.versions.toml index ac62c36..daf5fc1 100644 --- a/samples/weather-stdio-server/gradle/libs.versions.toml +++ b/samples/weather-stdio-server/gradle/libs.versions.toml @@ -2,7 +2,7 @@ coroutines = "1.11.0" kotlin = "2.3.21" ktor = "3.3.3" -logging = "8.0.02" +logging = "8.0.03" mcp-kotlin = "0.12.0" shadow = "9.4.1" slf4j = "2.0.18" From 37ee423308078a5daa57bb76953c79f1bcc6c7dc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 May 2026 16:45:06 +0000 Subject: [PATCH 302/449] chore(deps): bump the infrastructure group with 2 updates (#783) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the infrastructure group with 2 updates: [io.github.oshai:kotlin-logging](https://github.com/oshai/kotlin-logging) and [io.netty:netty-bom](https://github.com/netty/netty). Updates `io.github.oshai:kotlin-logging` from 8.0.02 to 8.0.03
    Release notes

    Sourced from io.github.oshai:kotlin-logging's releases.

    8.0.03

    What's Changed

    Full Changelog: https://github.com/oshai/kotlin-logging/compare/8.0.02...8.0.03

    Commits
    • b641856 bump version to 8.0.04
    • 3e6096d Fix #598: Allow external configuration of logStartupMessage via Properties an...
    • See full diff in compare view

    Updates `io.netty:netty-bom` from 4.2.13.Final to 4.2.14.Final
    Release notes

    Sourced from io.netty:netty-bom's releases.

    netty-4.2.14.Final

    What's Changed

    New Contributors

    Full Changelog: https://github.com/netty/netty/compare/netty-4.2.13.Final...netty-4.2.14.Final

    Commits
    • 0a60b75 [maven-release-plugin] prepare release netty-4.2.14.Final
    • 72df658 Fix MQTT decoder size check after variable header replay (#16787)
    • 7125dba MQTT: Allow MQTT 5 CONNECT with password only (#16833)
    • 9e19320 IoUring: Stop generic FileRegion drain loop when transferred() reaches count(...
    • 4ce9f17 Route synchronous onLookupComplete exceptions via fireExceptionCaught (#16794)
    • f7b1b7d Fix memoryAddress() for direct ByteBuffers wrapped by Unpooled without Unsafe...
    • 0ccb265 IpFilter: Fix ClassCastException caused by IpSubnetFilter if only ipv6 rules ...
    • a6aeb6d Resolve all localhost addresses without querying DNS servers (#16749)
    • c328ba2 Fix ResumptionController wrapping (#16815)
    • bc5862b HTTP2: Use 100 as default max concurrent streams setting (#16804)
    • Additional commits viewable in compare view

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bf36c70..54c27c0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,7 +9,7 @@ kover = "0.9.8" ktlint = "14.2.0" knit = "0.5.1" mavenPublish = "0.36.0" -netty = "4.2.13.Final" +netty = "4.2.14.Final" # libraries version awaitility = "4.3.0" @@ -18,7 +18,7 @@ coroutines = "1.11.0" kotest = "6.1.11" kotlinx-io = "0.9.0" ktor = "3.3.3" -logging = "8.0.02" +logging = "8.0.03" mockk = "1.14.9" serialization = "1.11.0" slf4j = "2.0.18" From 0adfb3bcdb06843a9da6617e0a50c604a6747339 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Mon, 1 Jun 2026 12:10:01 +0200 Subject: [PATCH 303/449] fix!: harden lifecycle, dispatchers, back-pressure (#778) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixes #573 fixes #574 fixes #708 - Handlers ran on `Dispatchers.IO` → now `Dispatchers.Default`, configurable via `handlerDispatcher`. - Reader/writer dispatcher exposed as `ioDispatcher` (default `IODispatcher.limitedParallelism(2)`). - Optional caller-supplied `CoroutineScope` so the pipeline isn't detached from the caller's tree. - `CancellationException` consistently rethrown across all pumps. - Unbounded channels → bounded with graceful drain on close (in-flight outbound flushed before `onClose`). - Input `Source` now closed on natural EOF / reader error (was leaking for non-`System.in` sources). - Race between `start()` and `close()` fixed via 3-state CAS + `setupComplete` barrier replacing `AtomicBoolean`. ## How Has This Been Tested? unit and integration tests ## Breaking Changes - Handlers default to `Dispatchers.Default` (was `Dispatchers.IO`). Override with `handlerDispatcher = Dispatchers.IO` if your handlers perform blocking I/O. - `send()` suspends under back-pressure instead of buffering unbounded. - `StdioServerTransport(input, output)` ctor is `@Deprecated` (WARNING) in favor of `StdioServerTransport(input, output) { ... }`. Still binary-compatible. ## Types of changes - [x] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- .gitignore | 2 +- kotlin-sdk-server/api/kotlin-sdk-server.api | 11 + .../kotlin/sdk/server/StdioServerTransport.kt | 452 ++++++++++++------ .../sdk/server/StdioServerTransportTest.kt | 342 ++++++++++++- 4 files changed, 651 insertions(+), 156 deletions(-) diff --git a/.gitignore b/.gitignore index d9f0fde..805579c 100644 --- a/.gitignore +++ b/.gitignore @@ -56,7 +56,7 @@ node_modules dist ### SWE agents ### -.claude/settings.local.json +.claude/ .junie/ ### Conformance test results ### diff --git a/kotlin-sdk-server/api/kotlin-sdk-server.api b/kotlin-sdk-server/api/kotlin-sdk-server.api index f400e80..3acaa64 100644 --- a/kotlin-sdk-server/api/kotlin-sdk-server.api +++ b/kotlin-sdk-server/api/kotlin-sdk-server.api @@ -219,11 +219,22 @@ public final class io/modelcontextprotocol/kotlin/sdk/server/SseServerTransport public final class io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport : io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport { public fun (Lkotlinx/io/Source;Lkotlinx/io/Sink;)V + public fun (Lkotlinx/io/Source;Lkotlinx/io/Sink;Lkotlin/jvm/functions/Function1;)V + public synthetic fun (Lkotlinx/io/Source;Lkotlinx/io/Sink;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun close (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/server/StdioServerTransport$Builder { + public final fun getHandlerDispatcher ()Lkotlinx/coroutines/CoroutineDispatcher; + public final fun getIoDispatcher ()Lkotlinx/coroutines/CoroutineDispatcher; + public final fun getScope ()Lkotlinx/coroutines/CoroutineScope; + public final fun setHandlerDispatcher (Lkotlinx/coroutines/CoroutineDispatcher;)V + public final fun setIoDispatcher (Lkotlinx/coroutines/CoroutineDispatcher;)V + public final fun setScope (Lkotlinx/coroutines/CoroutineScope;)V +} + public final class io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport : io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport { public static final field STANDALONE_SSE_STREAM_ID Ljava/lang/String; public fun ()V diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport.kt index a4d588c..2b76089 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport.kt @@ -7,13 +7,23 @@ 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 io.modelcontextprotocol.kotlin.sdk.types.McpDsl +import io.modelcontextprotocol.kotlin.sdk.types.McpException +import io.modelcontextprotocol.kotlin.sdk.types.RPCError import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.ClosedSendChannelException +import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -23,191 +33,345 @@ import kotlinx.io.Source import kotlinx.io.buffered import kotlinx.io.readByteArray import kotlinx.io.writeString -import kotlin.concurrent.atomics.AtomicBoolean +import kotlin.concurrent.atomics.AtomicReference import kotlin.concurrent.atomics.ExperimentalAtomicApi -import kotlin.coroutines.CoroutineContext private const val READ_BUFFER_SIZE = 8192L /** - * A server transport that communicates with a client via standard I/O. + * Server-side MCP transport that exchanges JSON-RPC messages over arbitrary byte streams. * - * Reads from input [Source] and writes to output [Sink]. + * Reads framed messages from the supplied [Source] and writes framed messages to the supplied + * [Sink]. Three internal coroutines drive the pipeline: * - * @constructor Creates a new instance of [StdioServerTransport]. - * @param inputStream The input [Source] used to receive data. - * @param outputStream The output [Sink] used to send data. + * - **reader** — pulls bytes from the input source into the parsing buffer; runs on + * [Builder.ioDispatcher]. + * - **processor** — parses messages out of the buffer and invokes the registered message handler; + * runs on [Builder.handlerDispatcher] (defaults to [Dispatchers.Default]) so blocking handler + * code does not starve the I/O pool. + * - **writer** — serialises outbound messages and flushes them to the output sink; runs on + * [Builder.ioDispatcher]. + * + * Both internal channels are bounded, so a slow handler or slow output naturally back-pressures + * the upstream producer — [send] suspends when the outbound channel is full. + * + * Both explicit [close] and a natural EOF from the input perform a graceful drain: in-flight + * outbound messages are flushed before `onClose` fires, and the input source and output sink are + * released. + * + * Example: + * ```kotlin + * val transport = StdioServerTransport( + * input = System.`in`.asSource().buffered(), + * output = System.out.asSink().buffered(), + * ) { + * scope = myScope + * } + * transport.start() + * ``` */ @OptIn(ExperimentalAtomicApi::class) -public class StdioServerTransport(private val inputStream: Source, outputStream: Sink) : AbstractTransport() { +public class StdioServerTransport private constructor( + private val input: Source, + output: Sink, + private val scope: CoroutineScope?, + private val handlerDispatcher: CoroutineDispatcher, + private val ioDispatcher: CoroutineDispatcher, +) : AbstractTransport() { + + /** + * Creates a [StdioServerTransport] reading from [input] and writing to [output], with + * optional configuration applied through a [Builder] block. + * + * @param input source the transport reads JSON-RPC messages from + * @param output sink the transport writes JSON-RPC messages to + * @param block configuration applied to the underlying [Builder] + */ + public constructor( + input: Source, + output: Sink, + block: Builder.() -> Unit = {}, + ) : this(Builder(input, output).apply(block)) + + private constructor(builder: Builder) : this( + input = builder.input, + output = builder.output, + scope = builder.scope, + handlerDispatcher = builder.handlerDispatcher, + ioDispatcher = builder.ioDispatcher, + ) + + /** + * Creates a [StdioServerTransport] from the given input source and output sink with default + * configuration. Retained for binary compatibility; prefer the [Builder]-based constructor. + * + * @param inputStream source the transport reads JSON-RPC messages from + * @param outputStream sink the transport writes JSON-RPC messages to + */ + @Deprecated( + message = "Use StdioServerTransport(input, output) { ... } instead.", + replaceWith = ReplaceWith("StdioServerTransport(input = inputStream, output = outputStream)"), + level = DeprecationLevel.WARNING, + ) + public constructor(inputStream: Source, outputStream: Sink) : this( + input = inputStream, + output = outputStream, + scope = null, + handlerDispatcher = Dispatchers.Default, + ioDispatcher = IODispatcher.limitedParallelism(2), + ) private val logger = KotlinLogging.logger {} private val readBuffer = ReadBuffer() - private val initialized: AtomicBoolean = AtomicBoolean(false) - private var readingJob: Job? = null - private var sendingJob: Job? = null - private var processingJob: Job? = null + private val readChannel = Channel(Channel.BUFFERED) + private val writeChannel = Channel(Channel.BUFFERED) + private val outputSink = output.buffered() - private val coroutineContext: CoroutineContext = IODispatcher + SupervisorJob() - private val scope = CoroutineScope(coroutineContext) - private val readChannel = Channel(Channel.UNLIMITED) - private val writeChannel = Channel(Channel.UNLIMITED) - private val outputSink = outputStream.buffered() + private enum class State { New, Operational, Stopped } + private val state: AtomicReference = AtomicReference(State.New) + + private var readerJob: Job? = null + private var processorJob: Job? = null + private var writerJob: Job? = null + + private var effectiveScope: CoroutineScope? = null + private val ownsScope: Boolean = (scope == null) + + private val setupComplete = CompletableDeferred() + + /** + * Starts the reader, processor, and writer coroutines. Must be called exactly once before + * messages can be exchanged; subsequent calls throw. + */ override suspend fun start() { - if (!initialized.compareAndSet(expectedValue = false, newValue = true)) { - error("StdioServerTransport already started!") + if (!state.compareAndSet(State.New, State.Operational)) { + when (state.load()) { + State.Stopped -> error("StdioServerTransport is already closed!") + else -> error("StdioServerTransport already started!") + } } - // Launch a coroutine to read from stdin - readingJob = launchReadingJob() + try { + val resolvedScope = scope ?: CoroutineScope( + currentCoroutineContext() + IODispatcher + SupervisorJob(), + ) + effectiveScope = resolvedScope - // Launch a coroutine to process messages from readChannel - processingJob = launchProcessingJob() - - // Launch a coroutine to handle message sending - sendingJob = launchSendingJob() + readerJob = resolvedScope.launch( + ioDispatcher + CoroutineName("StdioServerTransport.reader"), + ) { readerPump() } + processorJob = resolvedScope.launch( + handlerDispatcher + CoroutineName("StdioServerTransport.processor"), + start = CoroutineStart.UNDISPATCHED, + ) { processorPump() } + writerJob = resolvedScope.launch( + ioDispatcher + CoroutineName("StdioServerTransport.writer"), + start = CoroutineStart.UNDISPATCHED, + ) { writerPump() } + } finally { + setupComplete.complete(Unit) + } } - private fun launchReadingJob(): Job { - val job = scope.launch { - val buf = Buffer() - try { - while (isActive) { - val bytesRead = inputStream.readAtMostTo(buf, READ_BUFFER_SIZE) - if (bytesRead == -1L) { - // EOF reached - break - } - if (bytesRead > 0) { - val chunk = buf.readByteArray() - readChannel.send(chunk) + private suspend fun readerPump() { + val buf = Buffer() + try { + while (currentCoroutineContext().isActive) { + val bytesRead = input.readAtMostTo(buf, READ_BUFFER_SIZE) + if (bytesRead == -1L) break + if (bytesRead > 0) { + val chunk = buf.readByteArray() + readChannel.send(chunk) + } + } + } catch (e: CancellationException) { + throw e + } catch (e: Throwable) { + if (state.load() == State.Stopped) { + logger.debug(e) { "Reader interrupted by close()" } + } else { + logger.error(e) { "Error reading from input source" } + _onError(e) + } + } finally { + readChannel.close() + } + } + + private suspend fun processorPump() { + try { + for (chunk in readChannel) { + readBuffer.append(chunk) + while (true) { + val message = try { + readBuffer.readMessage() + } catch (e: CancellationException) { + throw e + } catch (e: Throwable) { + _onError(e) + null + } ?: break + try { + _onMessage(message) + } catch (e: CancellationException) { + throw e + } catch (e: Throwable) { + logger.error(e) { "Error processing message" } + _onError(e) } } - } catch (e: CancellationException) { - throw e - } catch (e: Throwable) { - logger.error(e) { "Error reading from stdin" } - _onError.invoke(e) - } finally { - // Reached EOF or error, close connection - close() - } - } - job.invokeOnCompletion { cause -> - logJobCompletion("Message reading", cause) - } - return job - } - - private fun launchProcessingJob(): Job { - val job = scope.launch { - try { - for (chunk in readChannel) { - readBuffer.append(chunk) - processReadBuffer() - } - } catch (e: CancellationException) { - throw e - } catch (e: Throwable) { - _onError.invoke(e) - } - } - job.invokeOnCompletion { cause -> - logJobCompletion("Processing", cause) - } - return job - } - - private fun launchSendingJob(): Job { - val job = scope.launch { - try { - for (message in writeChannel) { - val json = serializeMessage(message) - outputSink.writeString(json) - outputSink.flush() - } - } catch (e: CancellationException) { - throw e - } catch (e: Throwable) { - logger.error(e) { "Error writing to stdout" } - _onError.invoke(e) - } - } - job.invokeOnCompletion { cause -> - logJobCompletion("Message sending", cause) - if (cause is CancellationException) { - readingJob?.cancel(cause) - } - } - return job - } - - private suspend fun processReadBuffer() { - while (true) { - val message = try { - readBuffer.readMessage() - } catch (e: Throwable) { - _onError.invoke(e) - null - } - - if (message == null) break - // Async invocation broke delivery order - try { - _onMessage.invoke(message) - } catch (e: CancellationException) { - throw e - } catch (e: Throwable) { - logger.error(e) { "Error processing message" } - _onError.invoke(e) } + } catch (e: CancellationException) { + throw e + } catch (e: Throwable) { + _onError(e) + } finally { + writeChannel.close() } } - private fun logJobCompletion(jobName: String, cause: Throwable?) { - when (cause) { - is CancellationException -> { - } - - null -> { - logger.debug { "$jobName job completed" } - } - - else -> { - logger.debug(cause) { "$jobName job completed exceptionally" } + private suspend fun writerPump() { + try { + for (message in writeChannel) { + val json = serializeMessage(message) + outputSink.writeString(json) + outputSink.flush() } + } catch (e: CancellationException) { + throw e + } catch (e: Throwable) { + logger.error(e) { "Error writing to output sink" } + _onError(e) + } finally { + transitionToStoppedNaturally() } } + /** + * Closes the transport. When called after [start], waits for in-flight outbound messages to be + * flushed, releases the input source and output sink, cancels the internal scope when the + * transport owns it, and invokes `onClose`. When called before [start], transitions directly + * to the closed state without releasing the input/output or invoking `onClose` — the caller + * remains responsible for resources that were never handed off to a running transport. Safe to + * call multiple times and safe to race with [start]. + */ override suspend fun close() { - if (!initialized.compareAndSet(expectedValue = true, newValue = false)) return + var previous: State + do { + previous = state.load() + } while (previous != State.Stopped && !state.compareAndSet(previous, State.Stopped)) + + if (previous == State.New) { + setupComplete.complete(Unit) + return + } withContext(NonCancellable) { - writeChannel.close() - sendingJob?.cancelAndJoin() + setupComplete.await() - runCatching { - inputStream.close() - }.onFailure { logger.warn(it) { "Failed to close stdin" } } + if (previous == State.Stopped) { + writerJob?.join() + return@withContext + } - readingJob?.cancel() + runCatching { input.close() } + .onFailure { logger.warn(it) { "Failed to close input source" } } + readerJob?.cancel() readChannel.close() - - processingJob?.cancelAndJoin() - + processorJob?.cancelAndJoin() + writeChannel.close() + writerJob?.join() + runCatching { outputSink.close() } + .onFailure { logger.warn(it) { "Failed to close output sink" } } readBuffer.clear() - - runCatching { - outputSink.flush() - outputSink.close() - }.onFailure { logger.warn(it) { "Failed to close stdout" } } - + if (ownsScope) { + effectiveScope?.coroutineContext?.get(Job)?.cancel() + } invokeOnCloseCallback() } } + /** + * Queues [message] for the writer coroutine. Suspends when the outbound channel is full, + * applying back-pressure to the caller. Throws [McpException] with + * [RPCError.ErrorCode.CONNECTION_CLOSED] if the transport has not been started or has + * already closed. + * + * @param message JSON-RPC message to send + * @param options transport-specific send options; ignored by this transport + */ override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { - writeChannel.send(message) + when (state.load()) { + State.New -> throw McpException( + code = RPCError.ErrorCode.CONNECTION_CLOSED, + message = "Transport is not started", + ) + + State.Stopped -> throw McpException( + code = RPCError.ErrorCode.CONNECTION_CLOSED, + message = "Transport is closed", + ) + + State.Operational -> Unit + } + try { + writeChannel.send(message) + } catch (e: CancellationException) { + throw e + } catch (e: ClosedSendChannelException) { + throw McpException( + code = RPCError.ErrorCode.CONNECTION_CLOSED, + message = "Transport is closed", + cause = e, + ) + } + } + + private fun transitionToStoppedNaturally() { + if (!state.compareAndSet(State.Operational, State.Stopped)) return + runCatching { input.close() } + .onFailure { logger.warn(it) { "Failed to close input source" } } + runCatching { outputSink.close() } + .onFailure { logger.warn(it) { "Failed to close output sink" } } + readBuffer.clear() + if (ownsScope) { + // Non-null in practice: writer launches after effectiveScope is published. + effectiveScope?.coroutineContext?.get(Job)?.cancel() + } + invokeOnCloseCallback() + } + + /** + * Configuration builder for [StdioServerTransport]. Used via the + * `StdioServerTransport(input, output) { ... }` factory; the I/O endpoints are supplied + * positionally, while [scope], [handlerDispatcher], and [ioDispatcher] are configurable + * inside the block. + * + * Example: + * ```kotlin + * StdioServerTransport(input, output) { + * scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) + * handlerDispatcher = myHandlerDispatcher + * ioDispatcher = Dispatchers.IO + * } + * ``` + * + * @property scope Optional caller-supplied [CoroutineScope]. When non-null, the transport's + * pipeline coroutines run as children of this scope and the transport does not tear it + * down on [close]. When `null`, the transport creates and owns an internal scope. + * @property handlerDispatcher Dispatcher for invoking the registered message handler. + * Defaults to [Dispatchers.Default]. + * @property ioDispatcher Dispatcher for the reader and writer coroutines. Must allow at + * least two threads to run concurrently so the reader and writer don't block each other. + * The default is a two-thread view of the platform I/O dispatcher + * (`IODispatcher.limitedParallelism(2)`); pass a different value to share or isolate I/O + * threads with the rest of your application. + */ + @McpDsl + public class Builder internal constructor(internal val input: Source, internal val output: Sink) { + public var scope: CoroutineScope? = null + public var handlerDispatcher: CoroutineDispatcher = Dispatchers.Default + public var ioDispatcher: CoroutineDispatcher = IODispatcher.limitedParallelism(2) } } diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransportTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransportTest.kt index 32123a6..c6370a1 100644 --- a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransportTest.kt +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransportTest.kt @@ -5,17 +5,29 @@ import io.kotest.assertions.throwables.shouldThrow import io.kotest.assertions.withClue import io.kotest.matchers.collections.shouldContain import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain import io.modelcontextprotocol.kotlin.sdk.shared.ReadBuffer import io.modelcontextprotocol.kotlin.sdk.shared.serializeMessage import io.modelcontextprotocol.kotlin.sdk.types.InitializedNotification import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.McpException import io.modelcontextprotocol.kotlin.sdk.types.PingRequest +import io.modelcontextprotocol.kotlin.sdk.types.RPCError import io.modelcontextprotocol.kotlin.sdk.types.toJSON import io.modelcontextprotocol.kotlin.test.utils.runIntegrationTest import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.TimeoutCancellationException +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.cancel +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import kotlinx.coroutines.withTimeout +import kotlinx.coroutines.withTimeoutOrNull import kotlinx.io.Buffer import kotlinx.io.RawSink import kotlinx.io.RawSource @@ -33,9 +45,12 @@ import java.io.ByteArrayOutputStream import java.io.IOException import java.io.PipedInputStream import java.io.PipedOutputStream +import java.util.concurrent.CountDownLatch +import java.util.concurrent.Executors import kotlin.test.assertFalse import kotlin.test.assertTrue import kotlin.test.fail +import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds @TestInstance(TestInstance.Lifecycle.PER_CLASS) @@ -71,13 +86,13 @@ class StdioServerTransportTest { @Test fun `should be safe to close before start`() = runIntegrationTest { - val server = StdioServerTransport(bufferedInput, printOutput) + val server = StdioServerTransport(input = bufferedInput, output = printOutput) server.close() // initialized guard makes this a no-op; must not throw } @Test fun `should start then close cleanly`() = runIntegrationTest { - val server = StdioServerTransport(bufferedInput, printOutput) + val server = StdioServerTransport(input = bufferedInput, output = printOutput) server.onError { error -> throw error } @@ -96,7 +111,7 @@ class StdioServerTransportTest { @Test fun `should not read until started`() = runIntegrationTest { - val server = StdioServerTransport(bufferedInput, printOutput) + val server = StdioServerTransport(input = bufferedInput, output = printOutput) server.onError { error -> throw error } @@ -125,7 +140,7 @@ class StdioServerTransportTest { @Test fun `should read multiple messages`() = runIntegrationTest { - val server = StdioServerTransport(bufferedInput, printOutput) + val server = StdioServerTransport(input = bufferedInput, output = printOutput) server.onError { error -> throw error } @@ -162,7 +177,7 @@ class StdioServerTransportTest { @ParameterizedTest(name = "[{index}] input throws {0}") @MethodSource("inputErrors") fun `should invoke onError when input stream throws`(throwable: Throwable): Unit = runIntegrationTest { - val server = StdioServerTransport(FaultyRawSource(throwable).buffered(), printOutput) + val server = StdioServerTransport(input = FaultyRawSource(throwable).buffered(), output = printOutput) val capturedError = CompletableDeferred() server.onError { capturedError.complete(it) } server.onMessage {} @@ -176,7 +191,7 @@ class StdioServerTransportTest { @ParameterizedTest(name = "[{index}] output throws {0}") @MethodSource("outputErrors") fun `should invoke onError when output sink throws`(throwable: Throwable): Unit = runIntegrationTest { - val server = StdioServerTransport(bufferedInput, FaultyRawSink(throwable).buffered()) + val server = StdioServerTransport(input = bufferedInput, output = FaultyRawSink(throwable).buffered()) val capturedError = CompletableDeferred() server.onError { capturedError.complete(it) } server.onMessage {} @@ -190,7 +205,7 @@ class StdioServerTransportTest { @Test fun `should call onClose when input EOF is reached`(): Unit = runIntegrationTest { - val server = StdioServerTransport(bufferedInput, printOutput) + val server = StdioServerTransport(input = bufferedInput, output = printOutput) val didClose = CompletableDeferred() server.onError { throw it } server.onClose { didClose.complete(Unit) } @@ -206,7 +221,7 @@ class StdioServerTransportTest { @Test fun `should throw when starting twice`(): Unit = runIntegrationTest { - val server = StdioServerTransport(bufferedInput, printOutput) + val server = StdioServerTransport(input = bufferedInput, output = printOutput) server.onMessage {} server.start() withClue("Server should not start twice") { @@ -217,10 +232,24 @@ class StdioServerTransportTest { server.close() } + @Test + fun `should report 'closed' error message when starting after close`(): Unit = runIntegrationTest { + val server = StdioServerTransport(input = bufferedInput, output = printOutput) + server.onError {} + server.onMessage {} + server.start() + server.close() + + val ex = shouldThrow { + server.start() + } + ex.message shouldContain "closed" + } + @ParameterizedTest(name = "[{index}] handler throws {0}") @MethodSource("handlerErrors") fun `should continue processing messages after handler throws`(throwable: Throwable) = runIntegrationTest { - val server = StdioServerTransport(bufferedInput, printOutput) + val server = StdioServerTransport(input = bufferedInput, output = printOutput) val capturedErrors = mutableListOf() val receivedMessages = mutableListOf() val secondMessageProcessed = CompletableDeferred() @@ -253,7 +282,7 @@ class StdioServerTransportTest { @Test fun `should not invoke onError for CancellationException in handler`() = runIntegrationTest { - val server = StdioServerTransport(bufferedInput, printOutput) + val server = StdioServerTransport(input = bufferedInput, output = printOutput) val capturedError = CompletableDeferred() server.onError { capturedError.complete(it) } @@ -281,7 +310,7 @@ class StdioServerTransportTest { @Test fun `should continue receiving valid messages after malformed JSON is skipped`() = runIntegrationTest { - val server = StdioServerTransport(bufferedInput, printOutput) + val server = StdioServerTransport(input = bufferedInput, output = printOutput) val received = CompletableDeferred() // ReadBuffer silently skips unparseable lines — no onError callback expected server.onError {} @@ -299,6 +328,297 @@ class StdioServerTransportTest { server.close() } + @Test + fun `should throw McpException when send is called before start`() = runIntegrationTest { + val server = StdioServerTransport(input = bufferedInput, output = printOutput) + val ex = shouldThrow { + server.send(PingRequest().toJSON()) + } + ex.code shouldBe RPCError.ErrorCode.CONNECTION_CLOSED + } + + @Test + fun `should throw McpException when send is called after close`() = runIntegrationTest { + val server = StdioServerTransport(input = bufferedInput, output = printOutput) + server.onError {} + server.onMessage {} + server.start() + server.close() + + val ex = shouldThrow { + server.send(PingRequest().toJSON()) + } + ex.code shouldBe RPCError.ErrorCode.CONNECTION_CLOSED + } + + @Test + fun `should drain in-flight outbound messages on graceful close`() = runIntegrationTest { + val server = StdioServerTransport(input = bufferedInput, output = printOutput) + server.onError {} + server.onMessage {} + server.start() + + val numMessages = 50 + repeat(numMessages) { + server.send(PingRequest().toJSON()) + } + server.close() + + val outputLines = String(output.toByteArray()).lines().count { it.isNotBlank() } + outputLines shouldBe numMessages + } + + @Test + fun `should fail-fast when reader throws non-EOF IOException`() = runIntegrationTest { + val ioError = IOException("transient stream failure") + val server = StdioServerTransport(input = FaultyRawSource(ioError).buffered(), output = printOutput) + val errorCaptured = CompletableDeferred() + val closeCaptured = CompletableDeferred() + server.onError { errorCaptured.complete(it) } + server.onClose { closeCaptured.complete(Unit) } + server.onMessage {} + + server.start() + + errorCaptured.await() shouldBe ioError + closeCaptured.await() + } + + @Test + fun `should close input source on natural EOF`() = runIntegrationTest { + val inputClosed = CompletableDeferred() + val eofSource = object : RawSource { + override fun readAtMostTo(sink: Buffer, byteCount: Long): Long = -1L + override fun close() { + inputClosed.complete(Unit) + } + } + val server = StdioServerTransport(input = eofSource.buffered(), output = printOutput) + val onCloseFired = CompletableDeferred() + server.onError {} + server.onMessage {} + server.onClose { onCloseFired.complete(Unit) } + + server.start() + + inputClosed.await() + onCloseFired.await() + } + + // region: concurrency + + @Test + fun `should be safe under concurrent start and close`() = runIntegrationTest(timeout = 30.seconds) { + val iterations = 2000 + repeat(iterations) { iteration -> + val server = StdioServerTransport(input = Buffer(), output = Buffer()) + server.onError {} + server.onMessage {} + + coroutineScope { + launch(Dispatchers.Default) { + try { + server.start() + } catch (_: IllegalStateException) { + } + } + launch(Dispatchers.Default) { + server.close() + } + } + + withClue("iteration $iteration: send() after race should fail with CONNECTION_CLOSED") { + val ex = shouldThrow { + server.send(PingRequest().toJSON()) + } + ex.code shouldBe RPCError.ErrorCode.CONNECTION_CLOSED + } + } + } + + @Test + fun `should suspend send under back-pressure`() = runIntegrationTest(timeout = 30.seconds) { + val unblock = CountDownLatch(1) + val blockingSink = object : RawSink { + override fun write(source: Buffer, byteCount: Long) { + source.clear() + unblock.await() + } + + override fun flush() { + // noop + } + + override fun close() { + unblock.countDown() + } + } + val server = StdioServerTransport(input = bufferedInput, output = blockingSink.buffered()) + server.onError {} + server.onMessage {} + server.start() + + val maxAttempts = 256 + var suspendedAt = -1 + for (i in 0 until maxAttempts) { + val ok = withTimeoutOrNull(100.milliseconds) { server.send(PingRequest().toJSON()) } + if (ok == null) { + suspendedAt = i + break + } + } + assertTrue( + suspendedAt in 0 until maxAttempts, + "send() should have suspended within $maxAttempts attempts, got suspendedAt=$suspendedAt", + ) + // Unblock writer so close() can drain. + unblock.countDown() + server.close() + } + + @Test + fun `should not hang close when handler suspends indefinitely`() = runIntegrationTest { + val server = StdioServerTransport(input = bufferedInput, output = printOutput) + val handlerEntered = CompletableDeferred() + val neverComplete = CompletableDeferred() + server.onError {} + server.onMessage { + handlerEntered.complete(Unit) + neverComplete.await() + } + server.start() + + inputWriter.write(serializeMessage(PingRequest().toJSON())) + inputWriter.flush() + + handlerEntered.await() + server.close() + } + + @Test + fun `should not hang close when onMessage was never registered`() = runIntegrationTest { + val server = StdioServerTransport(input = bufferedInput, output = printOutput) + server.onError {} + server.start() + + inputWriter.write(serializeMessage(PingRequest().toJSON())) + inputWriter.flush() + + delay(200.milliseconds) + + server.close() + } + + // endregion + + // region: scope and dispatcher knobs + + @Test + fun `should honor externally provided CoroutineScope`() = runIntegrationTest { + val externalScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) + val server = StdioServerTransport(input = bufferedInput, output = printOutput) { + scope = externalScope + } + val closeCaptured = CompletableDeferred() + server.onError {} + server.onClose { closeCaptured.complete(Unit) } + server.onMessage {} + + server.start() + externalScope.cancel() + + closeCaptured.await() + + val ex = shouldThrow { + server.send(PingRequest().toJSON()) + } + ex.code shouldBe RPCError.ErrorCode.CONNECTION_CLOSED + } + + @Test + fun `should use ioDispatcher for reader and writer`() = runIntegrationTest { + val threadName = "stdio-test-io-thread" + val executor = Executors.newFixedThreadPool(2) { r -> Thread(r, threadName) } + try { + val readerThread = CompletableDeferred() + val writerThread = CompletableDeferred() + val unblockReader = CountDownLatch(1) + val recordingInput = object : RawSource { + override fun readAtMostTo(sink: Buffer, byteCount: Long): Long { + if (!readerThread.isCompleted) { + readerThread.complete(Thread.currentThread().name) + } + unblockReader.await() + return -1L + } + + override fun close() { + unblockReader.countDown() + } + } + val recordingOutput = object : RawSink { + override fun write(source: Buffer, byteCount: Long) { + if (!writerThread.isCompleted) { + writerThread.complete(Thread.currentThread().name) + } + source.clear() + } + + override fun flush() { + // noop + } + + override fun close() { + // noop + } + } + val server = StdioServerTransport( + input = recordingInput.buffered(), + output = recordingOutput.buffered(), + ) { + ioDispatcher = executor.asCoroutineDispatcher() + } + server.onError {} + server.onMessage {} + server.start() + + readerThread.await() shouldContain threadName + server.send(PingRequest().toJSON()) + writerThread.await() shouldContain threadName + + server.close() + } finally { + executor.shutdownNow() + } + } + + @Test + fun `should use handlerDispatcher for message handling`() = runIntegrationTest { + val threadName = "stdio-test-handler-thread" + val executor = Executors.newSingleThreadExecutor { r -> Thread(r, threadName) } + try { + val server = StdioServerTransport(input = bufferedInput, output = printOutput) { + handlerDispatcher = executor.asCoroutineDispatcher() + } + val observedThreadName = CompletableDeferred() + server.onError {} + server.onMessage { + observedThreadName.complete(Thread.currentThread().name) + } + server.start() + + inputWriter.write(serializeMessage(PingRequest().toJSON())) + inputWriter.flush() + + observedThreadName.await() shouldContain threadName + server.close() + } finally { + executor.shutdownNow() + } + } + + // endregion + private fun inputErrors() = listOf( IOException("simulated read failure"), RuntimeException("unexpected read exception"), From 0eb18228538c5410957e73933509fca2c00902c1 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Mon, 1 Jun 2026 12:33:39 +0200 Subject: [PATCH 304/449] Update Ktor to version 3.4.3 (#785) ktor 3.3.3 -> 3.4.3 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 54c27c0..4982b6d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,7 +17,7 @@ collections-immutable = "0.4.0" coroutines = "1.11.0" kotest = "6.1.11" kotlinx-io = "0.9.0" -ktor = "3.3.3" +ktor = "3.4.3" logging = "8.0.03" mockk = "1.14.9" serialization = "1.11.0" From 6e6f80512fb8fcc9f3c031cfd693ccbcf9c4aaab Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Tue, 2 Jun 2026 12:31:28 +0200 Subject: [PATCH 305/449] Merge commit from fork --- kotlin-sdk-core/api/kotlin-sdk-core.api | 6 +++ .../kotlin/sdk/shared/ReadBuffer.kt | 40 ++++++++++++++++++- .../kotlin/sdk/shared/ReadBufferTest.kt | 33 +++++++++++++++ .../kotlin/sdk/server/StdioServerTransport.kt | 3 ++ 4 files changed, 80 insertions(+), 2 deletions(-) diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index b588eef..0895722 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -125,6 +125,12 @@ public final class io/modelcontextprotocol/kotlin/sdk/shared/RequestOptions : io public fun toString ()Ljava/lang/String; } +public final class io/modelcontextprotocol/kotlin/sdk/shared/TooLongFrameException : java/io/IOException { + public fun (JI)V + public final fun getFrameSize ()J + public final fun getMaxFrameSize ()I +} + public abstract interface class io/modelcontextprotocol/kotlin/sdk/shared/Transport { public abstract fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun onClose (Lkotlin/jvm/functions/Function0;)V diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBuffer.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBuffer.kt index c0c853f..8102cf6 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBuffer.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBuffer.kt @@ -4,13 +4,24 @@ import io.github.oshai.kotlinlogging.KotlinLogging import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage import io.modelcontextprotocol.kotlin.sdk.types.McpJson import kotlinx.io.Buffer +import kotlinx.io.IOException import kotlinx.io.indexOf import kotlinx.io.readString /** * Buffers a continuous stdio stream into discrete JSON-RPC messages. + * + * A single newline-terminated frame may not exceed an internal size cap (16 MiB): a peer that streams + * bytes without ever sending a newline triggers a [TooLongFrameException] from [readMessage] instead + * of growing the buffer without bound. A non-positive cap disables the check. */ -public class ReadBuffer { +public class ReadBuffer internal constructor(private val maxFrameSize: Int) { + + public constructor() : this(DEFAULT_MAX_FRAME_SIZE) + + internal companion object { + internal const val DEFAULT_MAX_FRAME_SIZE: Int = 16 * 1024 * 1024 + } private val logger = KotlinLogging.logger { } @@ -53,7 +64,11 @@ public class ReadBuffer { private fun readNextLine(): String? { val lfIndex = if (buffer.exhausted()) -1L else buffer.indexOf('\n'.code.toByte()) - if (lfIndex == -1L) return null + if (lfIndex == -1L) { + failIfFrameTooLong(buffer.size) + return null + } + failIfFrameTooLong(lfIndex) return if (lfIndex == 0L) { buffer.skip(1) @@ -71,6 +86,13 @@ public class ReadBuffer { } } + private fun failIfFrameTooLong(frameSize: Long) { + if (maxFrameSize in 1..(line) /** Serializes a [JSONRPCMessage] to its JSON string representation with a trailing newline. */ diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBufferTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBufferTest.kt index 7b4f053..e5e50f8 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBufferTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBufferTest.kt @@ -6,7 +6,9 @@ import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification import kotlinx.serialization.json.Json import kotlin.test.Test +import kotlin.test.assertContains import kotlin.test.assertEquals +import kotlin.test.assertFailsWith import kotlin.test.assertNull class ReadBufferTest { @@ -75,4 +77,35 @@ class ReadBufferTest { val message = readBuffer.readMessage() assertEquals(testMessage, message) } + + @Test + fun `should fail when an unframed blob exceeds the cap`() { + val readBuffer = ReadBuffer(maxFrameSize = 64) + // No newline ever arrives: the memory-exhaustion vector. + readBuffer.append(ByteArray(100) { 'a'.code.toByte() }) + val ex = assertFailsWith { readBuffer.readMessage() } + assertContains(ex.message.orEmpty(), "maximum size") + } + + @Test + fun `should fail when a completed line exceeds the cap`() { + val readBuffer = ReadBuffer(maxFrameSize = 64) + readBuffer.append(ByteArray(100) { 'a'.code.toByte() } + '\n'.code.toByte()) + assertFailsWith { readBuffer.readMessage() } + } + + @Test + fun `should not enforce a cap when maxFrameSize is non-positive`() { + val readBuffer = ReadBuffer(maxFrameSize = 0) + // Well beyond any small cap and still no newline — must not throw when disabled. + readBuffer.append(ByteArray(8192) { 'a'.code.toByte() }) + assertNull(readBuffer.readMessage()) + } + + @Test + fun `should parse a message that fits under the cap`() { + val readBuffer = ReadBuffer(maxFrameSize = 1024) + readBuffer.append(serializeMessage(testMessage).encodeToByteArray()) + assertEquals(testMessage, readBuffer.readMessage()) + } } diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport.kt index 2b76089..9261be2 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport.kt @@ -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.TooLongFrameException import io.modelcontextprotocol.kotlin.sdk.shared.TransportSendOptions import io.modelcontextprotocol.kotlin.sdk.shared.serializeMessage import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage @@ -208,6 +209,8 @@ public class StdioServerTransport private constructor( readBuffer.readMessage() } catch (e: CancellationException) { throw e + } catch (e: TooLongFrameException) { + throw e } catch (e: Throwable) { _onError(e) null From b7eeeb12058099f980fa98d1468506489a6b5f32 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Tue, 2 Jun 2026 13:30:40 +0200 Subject: [PATCH 306/449] release: 0.13.0 (#796) bump version to 0.13.0 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index e145506..e5294e8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,4 +8,4 @@ kotlin.daemon.jvmargs=-Xmx4G kotlin.native.ignoreDisabledTargets=true group=io.modelcontextprotocol -version=0.12.0 +version=0.13.0 From 59b44a30e4ab816bc4658cfab3be69b90bf76063 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 13:59:56 +0200 Subject: [PATCH 307/449] chore(deps): bump the other-dependencies group across 5 directories with 4 updates (#789) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the other-dependencies group with 2 updates in the /samples/kotlin-mcp-client directory: [com.anthropic:anthropic-java](https://github.com/anthropics/anthropic-sdk-java) and [com.gradleup.shadow](https://github.com/GradleUp/shadow). Bumps the other-dependencies group with 1 update in the /samples/kotlin-mcp-server directory: [com.gradleup.shadow](https://github.com/GradleUp/shadow). Bumps the other-dependencies group with 2 updates in the /samples/kotlinlang-mcp-server directory: [com.gradleup.shadow](https://github.com/GradleUp/shadow) and [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback). Bumps the other-dependencies group with 1 update in the /samples/simple-streamable-server directory: [com.gradleup.shadow](https://github.com/GradleUp/shadow). Bumps the other-dependencies group with 2 updates in the /samples/weather-stdio-server directory: [com.gradleup.shadow](https://github.com/GradleUp/shadow) and [io.github.oshai:kotlin-logging-jvm](https://github.com/oshai/kotlin-logging). Updates `com.anthropic:anthropic-java` from 2.34.0 to 2.35.0
    Release notes

    Sourced from com.anthropic:anthropic-java's releases.

    v2.35.0

    2.35.0 (2026-05-28)

    Full Changelog: v2.34.1...v2.35.0

    Features

    • api: Add support for claude-opus-4-8, mid-conversation system blocks, and usage.output_tokens_details (3e4621e)

    v2.34.1

    2.34.1 (2026-05-27)

    Full Changelog: v2.34.0...v2.34.1

    Bug Fixes

    • structured-outputs: support all nullability annotations (#1254) (95e4391)

    Documentation

    • replace literal newlines (c5573d9)
    Changelog

    Sourced from com.anthropic:anthropic-java's changelog.

    2.35.0 (2026-05-28)

    Full Changelog: v2.34.1...v2.35.0

    Features

    • api: Add support for claude-opus-4-8, mid-conversation system blocks, and usage.output_tokens_details (3e4621e)

    2.34.1 (2026-05-27)

    Full Changelog: v2.34.0...v2.34.1

    Bug Fixes

    • structured-outputs: support all nullability annotations (#1254) (95e4391)

    Documentation

    • replace literal newlines (c5573d9)
    Commits
    • 65d26cc release: 2.35.0
    • f901010 feat(api): Add support for claude-opus-4-8, mid-conversation system blocks, a...
    • bc1bdd9 release: 2.34.1
    • f27e209 fix(structured-outputs): support all nullability annotations (#1254)
    • 62ef906 docs: replace literal newlines
    • dc19d08 ci: run breaking change detection on every branch (#1224)
    • 1382286 codegen metadata
    • See full diff in compare view

    Updates `com.gradleup.shadow` from 9.4.1 to 9.4.2
    Release notes

    Sourced from com.gradleup.shadow's releases.

    9.4.2

    Changed

    • Update jdependency to support Java 27. (#2033)
    Commits

    Updates `com.gradleup.shadow` from 9.4.1 to 9.4.2
    Release notes

    Sourced from com.gradleup.shadow's releases.

    9.4.2

    Changed

    • Update jdependency to support Java 27. (#2033)
    Commits

    Updates `com.gradleup.shadow` from 9.4.1 to 9.4.2
    Release notes

    Sourced from com.gradleup.shadow's releases.

    9.4.2

    Changed

    • Update jdependency to support Java 27. (#2033)
    Commits

    Updates `com.gradleup.shadow` from 9.4.1 to 9.4.2
    Release notes

    Sourced from com.gradleup.shadow's releases.

    9.4.2

    Changed

    • Update jdependency to support Java 27. (#2033)
    Commits

    Updates `com.gradleup.shadow` from 9.4.1 to 9.4.2
    Release notes

    Sourced from com.gradleup.shadow's releases.

    9.4.2

    Changed

    • Update jdependency to support Java 27. (#2033)
    Commits

    Updates `com.gradleup.shadow` from 9.4.1 to 9.4.2
    Release notes

    Sourced from com.gradleup.shadow's releases.

    9.4.2

    Changed

    • Update jdependency to support Java 27. (#2033)
    Commits

    Updates `com.gradleup.shadow` from 9.4.1 to 9.4.2
    Release notes

    Sourced from com.gradleup.shadow's releases.

    9.4.2

    Changed

    • Update jdependency to support Java 27. (#2033)
    Commits

    Updates `com.gradleup.shadow` from 9.4.1 to 9.4.2
    Release notes

    Sourced from com.gradleup.shadow's releases.

    9.4.2

    Changed

    • Update jdependency to support Java 27. (#2033)
    Commits

    Updates `com.gradleup.shadow` from 9.4.1 to 9.4.2
    Release notes

    Sourced from com.gradleup.shadow's releases.

    9.4.2

    Changed

    • Update jdependency to support Java 27. (#2033)
    Commits

    Updates `com.gradleup.shadow` from 9.4.1 to 9.4.2
    Release notes

    Sourced from com.gradleup.shadow's releases.

    9.4.2

    Changed

    • Update jdependency to support Java 27. (#2033)
    Commits

    Updates `com.gradleup.shadow` from 9.4.1 to 9.4.2
    Release notes

    Sourced from com.gradleup.shadow's releases.

    9.4.2

    Changed

    • Update jdependency to support Java 27. (#2033)
    Commits

    Updates `com.gradleup.shadow` from 9.4.1 to 9.4.2
    Release notes

    Sourced from com.gradleup.shadow's releases.

    9.4.2

    Changed

    • Update jdependency to support Java 27. (#2033)
    Commits

    Updates `ch.qos.logback:logback-classic` from 1.5.32 to 1.5.34
    Release notes

    Sourced from ch.qos.logback:logback-classic's releases.

    Logback 1.5.34

    2026-06-01 Release of logback version 1.5.34

    • In case certain StackTraceElement values returned by the Throwable.getStackTrace method are null, StackTraceElementProxy substitutes a dummy instance instead of throwing an IllegalArgumentException. This resolves [issues #1040](qos-ch/logback#1040), reported by Naotsugu Kobayashi.

    • HardenedObjectInputStream will now throw an InvalidClassException during deserialization attempts of Proxy classes. This change addresses potential deserialization whitelist bypass vulnerability reported by York Shen and registered as CVE-2026-10532.

    • A bitwise identical binary of this version can be reproduced by building from source code at commit e62272ac152469aec1ede056c3c7d0d7314e7bfe associated with the tag v_1.5.34. This release was built using Java "21" 2023-10-17 LTS build 21.0.1.+12-LTS-29 under Linux Debian 11.6.

    Logback 1.5.33

    2026-05-27 Release of logback version 1.5.33

    • PropertiesConfiguratorModelHandler now registers properties file URLs to the ConfigurationWatchList when scan is enabled (via local scan="true" attribute or top-level configuration scan), ensuring changes are detected and reconfiguration occurs. This problem was reported in issues/1034.

    • When processing <conversionRule> elements and both class and converterClass attributes are specified, silently use the class attribute without issuing a warning. However, if the attribute values differ, a warning will be issued. This change was requested in issues/1031.

    • HardenedModelInputStream will no longer accept to deserialize all classes located under the "java.lang" and "java.util" packages but a limited number of explicitly authorized classes in those packages. This potential deserialization whitelist bypass vulnerability was reported by York Shen and registered as CVE-2026-9828.

    • SSL parameters for SSLSocketAppender now enable hostname verification by default. Moreover, the default protocol is now "TLSv1.2". This potential vulnerability was reported by York Shen.

    • When printing the status message field, ViewStatusMessagesServletBase now escapes special characters such as "&" as character entities. This potential vulnerability was reported by York Shen.

    • A bit-wise identical binary of this version can be reproduced by building from source code at commit 124e8b49b55ac34d08743a0646bd463410192647 associated with the tag v_1.5.33. Release built using Java "21" 2023-10-17 LTS build 21.0.1.+12-LTS-29 under Linux Debian 11.6.

    Commits
    • e62272a prepare release 1.5.34
    • 1e9e926 add resolveProxyClassRejectsDynamicProxies unit test
    • 2de5cbe added StackTraceElementProxyTest, minor edits to AGENTS.md
    • 0e9b927 in case StackTraceElement is null use a substitute, fixing issues/1040
    • f7a0654 prevent resolveProxyClass bypass
    • 249b81f docs are no longer distributed
    • 1c3b26a start work on 1.5.34-SNAPSHOT
    • 124e8b4 prepare release 1.5.33
    • d8fd6f2 escapeTags in message field when printing status messages
    • 95edbeb hostnameVerification default to true in SSLParametersConfiguration, SSL.DEFAU...
    • Additional commits viewable in compare view

    Updates `com.gradleup.shadow` from 9.4.1 to 9.4.2
    Release notes

    Sourced from com.gradleup.shadow's releases.

    9.4.2

    Changed

    • Update jdependency to support Java 27. (#2033)
    Commits

    Updates `com.gradleup.shadow` from 9.4.1 to 9.4.2
    Release notes

    Sourced from com.gradleup.shadow's releases.

    9.4.2

    Changed

    • Update jdependency to support Java 27. (#2033)
    Commits

    Updates `com.gradleup.shadow` from 9.4.1 to 9.4.2
    Release notes

    Sourced from com.gradleup.shadow's releases.

    9.4.2

    Changed

    • Update jdependency to support Java 27. (#2033)
    Commits

    Updates `com.gradleup.shadow` from 9.4.1 to 9.4.2
    Release notes

    Sourced from com.gradleup.shadow's releases.

    9.4.2

    Changed

    • Update jdependency to support Java 27. (#2033)
    Commits

    Updates `com.gradleup.shadow` from 9.4.1 to 9.4.2
    Release notes

    Sourced from com.gradleup.shadow's releases.

    9.4.2

    Changed

    • Update jdependency to support Java 27. (#2033)
    Commits

    Updates `com.gradleup.shadow` from 9.4.1 to 9.4.2
    Release notes

    Sourced from com.gradleup.shadow's releases.

    9.4.2

    Changed

    • Update jdependency to support Java 27. (#2033)
    Commits

    Updates `com.gradleup.shadow` from 9.4.1 to 9.4.2
    Release notes

    Sourced from com.gradleup.shadow's releases.

    9.4.2

    Changed

    • Update jdependency to support Java 27. (#2033)
    Commits

    Updates `com.gradleup.shadow` from 9.4.1 to 9.4.2
    Release notes

    Sourced from com.gradleup.shadow's releases.

    9.4.2

    Changed

    • Update jdependency to support Java 27. (#2033)
    Commits

    Updates `com.gradleup.shadow` from 9.4.1 to 9.4.2
    Release notes

    Sourced from com.gradleup.shadow's releases.

    9.4.2

    Changed

    • Update jdependency to support Java 27. (#2033)
    Commits

    Updates `com.gradleup.shadow` from 9.4.1 to 9.4.2
    Release notes

    Sourced from com.gradleup.shadow's releases.

    9.4.2

    Changed

    • Update jdependency to support Java 27. (#2033)
    Commits

    Updates `com.gradleup.shadow` from 9.4.1 to 9.4.2
    Release notes

    Sourced from com.gradleup.shadow's releases.

    9.4.2

    Changed

    • Update jdependency to support Java 27. (#2033)
    Commits

    Updates `com.gradleup.shadow` from 9.4.1 to 9.4.2
    Release notes

    Sourced from com.gradleup.shadow's releases.

    9.4.2

    Changed

    • Update jdependency to support Java 27. (#2033)
    Commits

    Updates `io.github.oshai:kotlin-logging-jvm` from 8.0.03 to 8.0.4
    Release notes

    Sourced from io.github.oshai:kotlin-logging-jvm's releases.

    8.0.4

    Align to semver.

    Full Changelog: https://github.com/oshai/kotlin-logging/compare/8.0.03...8.0.4

    Commits

    Updates `com.gradleup.shadow` from 9.4.1 to 9.4.2
    Release notes

    Sourced from com.gradleup.shadow's releases.

    9.4.2

    Changed

    • Update jdependency to support Java 27. (#2033)
    Commits

    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-client/gradle/libs.versions.toml | 4 ++-- samples/kotlin-mcp-server/gradle/libs.versions.toml | 2 +- samples/kotlinlang-mcp-server/gradle/libs.versions.toml | 4 ++-- samples/simple-streamable-server/gradle/libs.versions.toml | 2 +- samples/weather-stdio-server/gradle/libs.versions.toml | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index c263c59..42d700e 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -1,9 +1,9 @@ [versions] -anthropic = "2.34.0" +anthropic = "2.35.0" kotlin = "2.3.21" ktor = "3.3.3" mcp-kotlin = "0.12.0" -shadow = "9.4.1" +shadow = "9.4.2" slf4j = "2.0.18" [libraries] diff --git a/samples/kotlin-mcp-server/gradle/libs.versions.toml b/samples/kotlin-mcp-server/gradle/libs.versions.toml index 91d7878..165d096 100644 --- a/samples/kotlin-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-server/gradle/libs.versions.toml @@ -3,7 +3,7 @@ kotlin = "2.3.21" ktor = "3.3.3" mcp-kotlin = "0.12.0" slf4j = "2.0.18" -shadow = "9.4.1" +shadow = "9.4.2" [libraries] ktor-bom = { group = "io.ktor", name = "ktor-bom", version.ref = "ktor" } diff --git a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml index 5b7988e..9a68e31 100644 --- a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml @@ -4,8 +4,8 @@ ktor = "3.3.3" mcp-kotlin = "0.12.0" serialization = "1.11.0" coroutines = "1.11.0" -logback = "1.5.32" -shadow = "9.4.1" +logback = "1.5.34" +shadow = "9.4.2" [libraries] ktor-bom = { group = "io.ktor", name = "ktor-bom", version.ref = "ktor" } diff --git a/samples/simple-streamable-server/gradle/libs.versions.toml b/samples/simple-streamable-server/gradle/libs.versions.toml index b607a1c..07bdac1 100644 --- a/samples/simple-streamable-server/gradle/libs.versions.toml +++ b/samples/simple-streamable-server/gradle/libs.versions.toml @@ -4,7 +4,7 @@ ktor = "3.3.3" junit = "6.1.0" mcp-kotlin = "0.12.0" slf4j = "2.0.18" -shadow = "9.4.1" +shadow = "9.4.2" [libraries] ktor-bom = { group = "io.ktor", name = "ktor-bom", version.ref = "ktor" } diff --git a/samples/weather-stdio-server/gradle/libs.versions.toml b/samples/weather-stdio-server/gradle/libs.versions.toml index daf5fc1..d7a6fcb 100644 --- a/samples/weather-stdio-server/gradle/libs.versions.toml +++ b/samples/weather-stdio-server/gradle/libs.versions.toml @@ -2,9 +2,9 @@ coroutines = "1.11.0" kotlin = "2.3.21" ktor = "3.3.3" -logging = "8.0.03" +logging = "8.0.4" mcp-kotlin = "0.12.0" -shadow = "9.4.1" +shadow = "9.4.2" slf4j = "2.0.18" [libraries] From 32cc44115d601ae0dea1dde6e6dd487ce6a7d355 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 14:48:50 +0200 Subject: [PATCH 308/449] chore(deps): bump org.jetbrains.kotlinx:atomicfu-gradle-plugin from 0.32.1 to 0.33.0 in the kotlinx group across 1 directory (#791) Bumps the kotlinx group with 1 update in the / directory: [org.jetbrains.kotlinx:atomicfu-gradle-plugin](https://github.com/Kotlin/kotlinx.atomicfu). Updates `org.jetbrains.kotlinx:atomicfu-gradle-plugin` from 0.32.1 to 0.33.0
    Release notes

    Sourced from org.jetbrains.kotlinx:atomicfu-gradle-plugin's releases.

    0.33.0

    • Deprecated atomic array types provided by the atomicfu. Use types provided by the Kotlin standard library instead. (#591)
    • Published jar files now include Implementation-* attributes in their manifests (#592)
    • Updated ASM to 9.10.1 (#595)
    • Various build infra updates
    Changelog

    Sourced from org.jetbrains.kotlinx:atomicfu-gradle-plugin's changelog.

    Versoin 0.33.0

    • Deprecated atomic array types provided by the atomicfu. Use types provided by the Kotlin standard library instead. (#591)
    • Published jar files now include Implementation-* attributes in their manifests (#592)
    • Updated ASM to 9.10.1 (#595)
    • Various build infra updates
    Commits
    • fc175fc Version 0.33.0
    • dc6187a Update dependencies (#595)
    • 8e23c32 Deprecate AFU array types (#591)
    • 0d27eb1 Do not use deprecated js(compiler: KotlinJsCompilerType, ...) DSL (#594)
    • 6c6b2d6 Fill Implementation-* attributes in JAR's manifests (#592)
    • a658e7d KT-84955: add a suppressor for gradle plugin error checker for native target ...
    • 1dd25ee Add logic to handle changed .kotlin_module file name in test (#581)
    • See full diff in compare view

    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4982b6d..e8fbe8d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] # plugins version -atomicfu = "0.32.1" +atomicfu = "0.33.0" binaryCompatibilityValidatorPlugin = "0.18.1" detekt = "2.0.0-alpha.3" dokka = "2.2.0" From 4b726c8f5f250735ead11052ff89dbf63d82a7c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 15:49:40 +0200 Subject: [PATCH 309/449] chore(deps): bump io.mockk:mockk from 1.14.9 to 1.14.11 in the testing group across 1 directory (#792) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the testing group with 1 update in the / directory: [io.mockk:mockk](https://github.com/mockk/mockk). Updates `io.mockk:mockk` from 1.14.9 to 1.14.11
    Release notes

    Sourced from io.mockk:mockk's releases.

    v1.14.11

    What's Changed

    New Contributors

    Full Changelog: https://github.com/mockk/mockk/compare/1.14.9...v1.14.11

    Commits
    • d5617fc Version bump for v1.14.11
    • d4e6a00 Merge pull request #1530 from hspedro/fix/726-openj9-classcast-message-format
    • 98e91bc fix: handle OpenJ9 CCE message in JvmAutoHinter
    • 945f746 Merge pull request #1529 from snowykte0426/fix/1273-jvmtiagent-agp85-native-l...
    • 35cc073 [ 1273-jvmtiagent ] chore: stop tracking .claude/scheduled_tasks.lock
    • d6aa82b [ 1273-jvmtiagent ] chore: drop useLegacyPackaging workaround
    • 85a907c [ 1273-jvmtiagent ] chore: apply spotless formatting
    • 7d9aec1 [ 1273-jvmtiagent ] fix: extract .so when findLibrary returns a path with '='
    • 1d5d30e [ 1273-jvmtiagent ] test: verify findLibrary resolves mockkjvmtiagent to a va...
    • 21a6a3a [ 1273-jvmtiagent]fix: pass findLibrary path directly to attach JvmtiAgent
    • Additional commits viewable in compare view

    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e8fbe8d..83e7a6f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,7 +19,7 @@ kotest = "6.1.11" kotlinx-io = "0.9.0" ktor = "3.4.3" logging = "8.0.03" -mockk = "1.14.9" +mockk = "1.14.11" serialization = "1.11.0" slf4j = "2.0.18" logback = "1.5.32" From cf88353b35216998a6d379b2ef10daeccb47de82 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 16:39:32 +0200 Subject: [PATCH 310/449] chore(deps): bump ch.qos.logback:logback-classic from 1.5.32 to 1.5.34 (#794) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.32 to 1.5.34.
    Release notes

    Sourced from ch.qos.logback:logback-classic's releases.

    Logback 1.5.34

    2026-06-01 Release of logback version 1.5.34

    • In case certain StackTraceElement values returned by the Throwable.getStackTrace method are null, StackTraceElementProxy substitutes a dummy instance instead of throwing an IllegalArgumentException. This resolves [issues #1040](qos-ch/logback#1040), reported by Naotsugu Kobayashi.

    • HardenedObjectInputStream will now throw an InvalidClassException during deserialization attempts of Proxy classes. This change addresses potential deserialization whitelist bypass vulnerability reported by York Shen and registered as CVE-2026-10532.

    • A bitwise identical binary of this version can be reproduced by building from source code at commit e62272ac152469aec1ede056c3c7d0d7314e7bfe associated with the tag v_1.5.34. This release was built using Java "21" 2023-10-17 LTS build 21.0.1.+12-LTS-29 under Linux Debian 11.6.

    Logback 1.5.33

    2026-05-27 Release of logback version 1.5.33

    • PropertiesConfiguratorModelHandler now registers properties file URLs to the ConfigurationWatchList when scan is enabled (via local scan="true" attribute or top-level configuration scan), ensuring changes are detected and reconfiguration occurs. This problem was reported in issues/1034.

    • When processing <conversionRule> elements and both class and converterClass attributes are specified, silently use the class attribute without issuing a warning. However, if the attribute values differ, a warning will be issued. This change was requested in issues/1031.

    • HardenedModelInputStream will no longer accept to deserialize all classes located under the "java.lang" and "java.util" packages but a limited number of explicitly authorized classes in those packages. This potential deserialization whitelist bypass vulnerability was reported by York Shen and registered as CVE-2026-9828.

    • SSL parameters for SSLSocketAppender now enable hostname verification by default. Moreover, the default protocol is now "TLSv1.2". This potential vulnerability was reported by York Shen.

    • When printing the status message field, ViewStatusMessagesServletBase now escapes special characters such as "&" as character entities. This potential vulnerability was reported by York Shen.

    • A bit-wise identical binary of this version can be reproduced by building from source code at commit 124e8b49b55ac34d08743a0646bd463410192647 associated with the tag v_1.5.33. Release built using Java "21" 2023-10-17 LTS build 21.0.1.+12-LTS-29 under Linux Debian 11.6.

    Commits
    • e62272a prepare release 1.5.34
    • 1e9e926 add resolveProxyClassRejectsDynamicProxies unit test
    • 2de5cbe added StackTraceElementProxyTest, minor edits to AGENTS.md
    • 0e9b927 in case StackTraceElement is null use a substitute, fixing issues/1040
    • f7a0654 prevent resolveProxyClass bypass
    • 249b81f docs are no longer distributed
    • 1c3b26a start work on 1.5.34-SNAPSHOT
    • 124e8b4 prepare release 1.5.33
    • d8fd6f2 escapeTags in message field when printing status messages
    • 95edbeb hostnameVerification default to true in SSLParametersConfiguration, SSL.DEFAU...
    • Additional commits viewable in compare view

    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 83e7a6f..8474680 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,7 +22,7 @@ logging = "8.0.03" mockk = "1.14.11" serialization = "1.11.0" slf4j = "2.0.18" -logback = "1.5.32" +logback = "1.5.34" junit = "5.14.4" [libraries] From 2f71156483845752f4678287e466810eff468ea6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 18:07:54 +0200 Subject: [PATCH 311/449] chore(deps): bump tsx from 4.22.3 to 4.22.4 in /integration-test/src/jvmTest/typescript in the all-dependencies group across 1 directory (#795) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the all-dependencies group with 1 update in the /integration-test/src/jvmTest/typescript directory: [tsx](https://github.com/privatenumber/tsx). Updates `tsx` from 4.22.3 to 4.22.4
    Release notes

    Sourced from tsx's releases.

    v4.22.4

    4.22.4 (2026-05-31)

    Bug Fixes

    • resolve CommonJS directory requires inside dependencies (#803) (1ce8463)

    This release is also available on:

    Commits

    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- integration-test/src/jvmTest/typescript/package-lock.json | 8 ++++---- integration-test/src/jvmTest/typescript/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index 9e757a5..6e5fb5f 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -17,7 +17,7 @@ "@types/express": "^5.0.6", "@types/node": "^25.9.1", "cors": "^2.8.6", - "tsx": "^4.22.3", + "tsx": "^4.22.4", "typescript": "^6.0.3" } }, @@ -1680,9 +1680,9 @@ } }, "node_modules/tsx": { - "version": "4.22.3", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.3.tgz", - "integrity": "sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", + "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/integration-test/src/jvmTest/typescript/package.json b/integration-test/src/jvmTest/typescript/package.json index 40c58b3..0fafdb9 100644 --- a/integration-test/src/jvmTest/typescript/package.json +++ b/integration-test/src/jvmTest/typescript/package.json @@ -18,7 +18,7 @@ "@types/express": "^5.0.6", "@types/node": "^25.9.1", "cors": "^2.8.6", - "tsx": "^4.22.3", + "tsx": "^4.22.4", "typescript": "^6.0.3" } } From 52f8b0fa919c59dcae068e85b8e337f532704128 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Jun 2026 01:30:10 +0200 Subject: [PATCH 312/449] chore(deps): bump the infrastructure group across 1 directory with 2 updates (#797) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the infrastructure group with 2 updates in the / directory: [io.github.oshai:kotlin-logging](https://github.com/oshai/kotlin-logging) and [io.netty:netty-bom](https://github.com/netty/netty). Updates `io.github.oshai:kotlin-logging` from 8.0.03 to 8.0.4
    Release notes

    Sourced from io.github.oshai:kotlin-logging's releases.

    8.0.4

    Align to semver.

    Full Changelog: https://github.com/oshai/kotlin-logging/compare/8.0.03...8.0.4

    Commits

    Updates `io.netty:netty-bom` from 4.2.14.Final to 4.2.15.Final
    Release notes

    Sourced from io.netty:netty-bom's releases.

    netty-4.2.15.Final

    Security fixes

    • CVE-2026-48059: memory exhaustion in io.netty:netty-codec-haproxy (high).
    • CVE-2026-47691: DNS cache poisoning in io.netty:netty-resolver-dns (high).
    • CVE-2026-XXXXX: DDoS in io.netty:netty-codec-http2.
    • CVE-2026-XXXXX: memory exhaustion in io.netty:netty-codec-redis (high).
    • CVE-2026-44250: memory exhaustion in io.netty:netty-codec-redis (high).
    • CVE-2026-44890: memory exhaustion in io.netty:netty-codec-redis (high).
    • CVE-2026-XXXXX: information disclosure and denial of service in io.netty:netty-codec-classes-quic.
    • CVE-2026-44249: IPv6 subnet filter bypass in io.netty:netty-handler (high).
    • CVE-2026-XXXXX: request smuggling in io.netty:netty-codec-http.
    • CVE-2026-44892: memory exhaustion in io.netty:netty-codec-http3 (high).
    • CVE-2026-44893: memory leak in io.netty:netty-codec-haproxy (high).
    • CVE-2026-44894: traffic amplification in io.netty:netty-codec-classes-quic (high).
    • CVE-2026-XXXXX: TLS hostname verification accidentally disabled in io.netty:netty-handler (high).
    • CVE-2026-45673: DNS cache poisoning in io.netty:netty-resolver-dns.
    • CVE-2026-45416: excessive memory usage from SNIHandler in io.netty:netty-handler (high).
    • CVE-2026-45536: file descriptor leak in io.netty:netty-transport-native-epoll and io.netty:netty-transport-native-kqueue.
    • CVE-2026-45674: DNS cache poisoning in io.netty:netty-resolver-dns (high).
    • CVE-2026-46340: memory exhaustion in io.netty:netty-transport-sctp (high).
    • CVE-2026-47244: denial of service in io.netty:netty-codec-http2.
    • CVE-2026-48006: memory exhaustion in io.netty:netty-codec-redis (high).
    • CVE-2026-48748: memory exhaustion in io.netty:netty-codec-http3 (high).
    • CVE-2026-48043: memory exhaustion in io.netty:netty-codec-http2.

    What's Changed

    New Contributors

    Full Changelog: https://github.com/netty/netty/compare/netty-4.2.14.Final...netty-4.2.15.Final

    Commits
    • a41f7b2 [maven-release-plugin] prepare release netty-4.2.15.Final
    • 2394530 Auto-port 4.2: MQTT: Reject malformed no-payload packets with non-zero Remain...
    • 0bd1657 Add maxWindowLog parameter to ZstdDecoder to bound memory allocation (#16850)
    • 76291f5 Fix SCTP and Redis tests (#16893)
    • e067b6e Fix revapi warnings (#16885)
    • 5a52600 Pass maxAllocation to Brotli and Zstd decoders (#16844)
    • 541add0 Merge commit from fork
    • 270800e Merge commit from fork
    • 3d45a1e Merge commit from fork
    • 75127ca Merge commit from fork
    • Additional commits viewable in compare view

    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8474680..4904ea4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,7 +9,7 @@ kover = "0.9.8" ktlint = "14.2.0" knit = "0.5.1" mavenPublish = "0.36.0" -netty = "4.2.14.Final" +netty = "4.2.15.Final" # libraries version awaitility = "4.3.0" @@ -18,7 +18,7 @@ coroutines = "1.11.0" kotest = "6.1.11" kotlinx-io = "0.9.0" ktor = "3.4.3" -logging = "8.0.03" +logging = "8.0.4" mockk = "1.14.11" serialization = "1.11.0" slf4j = "2.0.18" From a1f11fae2e8a5150360fec4dccbd34b6686fcaec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Jun 2026 05:31:56 +0200 Subject: [PATCH 313/449] chore(deps): bump hono from 4.12.18 to 4.12.23 in /integration-test/src/jvmTest/typescript (#799) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [hono](https://github.com/honojs/hono) from 4.12.18 to 4.12.23.
    Release notes

    Sourced from hono's releases.

    v4.12.23

    What's Changed

    Full Changelog: https://github.com/honojs/hono/compare/v4.12.22...v4.12.23

    v4.12.22

    What's Changed

    New Contributors

    Full Changelog: https://github.com/honojs/hono/compare/v4.12.21...v4.12.22

    v4.12.21

    Security fixes

    This release includes fixes for the following security issues:

    app.mount() strips mount prefix using undecoded path, causing incorrect routing for percent-encoded paths

    Affects: app.mount(). Fixes prefix stripping using the raw URL pathname instead of the decoded path, where percent-encoded characters in the mount prefix or path could cause the prefix to be removed at the wrong position, resulting in the sub-application receiving an incorrect path. GHSA-2gcr-mfcq-wcc3

    IP Restriction bypasses static deny rules for non-canonical IPv6

    Affects: hono/ip-restriction. Fixes IP address comparison using string equality, where non-canonical IPv6 representations of a denied address — such as compressed forms or hex-notation IPv4-mapped addresses — could bypass static deny rules. GHSA-xrhx-7g5j-rcj5

    Cookie helper does not sanitize sameSite and priority, allowing Set-Cookie injection

    Affects: hono/cookie. Fixes missing validation of sameSite and priority options against injection characters (;, \r, \n), where user-controlled input passed to either option could inject additional attributes into the Set-Cookie response header. GHSA-3hrh-pfw6-9m5x

    JWT middleware accepts any Authorization scheme, not only Bearer

    Affects: hono/jwt, hono/jwk. Fixes missing scheme validation in the Authorization header, where any two-part header value was accepted regardless of the scheme name, allowing non-Bearer schemes to pass JWT authentication. GHSA-f577-qrjj-4474


    Users who use app.mount(), hono/ip-restriction, hono/cookie, or hono/jwt/hono/jwk are encouraged to upgrade to this version.

    ... (truncated)

    Commits
    • 83bfb3b 4.12.23
    • bcd290a fix(utils/ipaddr): do not compress a single 0 group to :: (#4971)
    • c968177 feat(compress): add contentTypeFilter option and `COMPRESSIBLE_CONTENT_TYPE_R...
    • 0265a54 docs(contribution): add AI Usage Policy (#4970)
    • c84c5d2 feat(context): export the Context class publicly (#4543)
    • 82dad62 fix(serve-static): normalize all backslashes in file paths, not just the firs...
    • 2f01b77 4.12.22
    • 6bc0dff feat: add msgpack as a compressible content type (#4957)
    • 7e0555d fix(deno): echo negotiated WebSocket subprotocol in upgrade response (#4955)
    • f0ed246 fix(compress): respect Accept-Encoding when encoding option is set (#4951)
    • Additional commits viewable in compare view

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=hono&package-manager=npm_and_yarn&previous-version=4.12.18&new-version=4.12.23)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/modelcontextprotocol/kotlin-sdk/network/alerts).
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- integration-test/src/jvmTest/typescript/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index 6e5fb5f..ed07e4e 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -1184,9 +1184,9 @@ } }, "node_modules/hono": { - "version": "4.12.18", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.18.tgz", - "integrity": "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==", + "version": "4.12.23", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.23.tgz", + "integrity": "sha512-eIaZ9qDgu7XV0pxOCrg7/WhnQ6Ivm22UcxhXx/A3dcbqbbYgBEkc6e/J/s7j2tS96zoB0S9VBdLwQNCWwUo4LA==", "license": "MIT", "engines": { "node": ">=16.9.0" From 75920e1eb789ad183ea51c8dc338db638207a59a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Jun 2026 11:39:50 +0200 Subject: [PATCH 314/449] chore(deps): bump mcp-kotlin from 0.12.0 to 0.13.0 in /samples/kotlinlang-mcp-server (#824) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `mcp-kotlin` from 0.12.0 to 0.13.0. Updates `io.modelcontextprotocol:kotlin-sdk-server` from 0.12.0 to 0.13.0
    Release notes

    Sourced from io.modelcontextprotocol:kotlin-sdk-server's releases.

    0.13.0

    Description

    Adds a tasks capability and a typed elicitation capability, hardens transport lifecycle, dispatchers, and back-pressure, and enables DNS rebinding protection by default.

    Breaking Changes

    Typed elicitation capability and new tasks capability by @​devcrocod in #732

    ClientCapabilities.elicitation is now a typed ClientCapabilities.Elicitation (form, url) instead of a raw JsonObject?, and both ClientCapabilities and ServerCapabilities gained a typed tasks capability. The constructors of both types changed and are binary-incompatible; recompilation is required.

    Transport handlers now run on Dispatchers.Default by @​devcrocod in #778

    Handlers moved from Dispatchers.IO to Dispatchers.Default (override via handlerDispatcher), send() now suspends under back-pressure instead of buffering unbounded, and the two-argument StdioServerTransport(input, output) constructor is deprecated in favor of the builder-lambda form. Also fixes a start()/close() race, consistent CancellationException propagation, and an input-Source leak (closes #573, #574, #708).

    DNS rebinding protection enabled by default by @​devcrocod in #659

    enableDnsRebindingProtection now defaults to true on mcpStreamableHttp/mcpStatelessStreamableHttp, and the Route.mcp() / Application.mcp() overloads gained enableDnsRebindingProtection, allowedHosts, and allowedOrigins parameters (binary-incompatible on JVM). Only localhost, 127.0.0.1, and [::1] are allowed by default; opt out with enableDnsRebindingProtection = false. The Configuration.enableDnsRebindingProtection, .allowedHosts, and .allowedOrigins properties are deprecated in favor of the new DnsRebindingProtection plugin.

    Features

    • Add a tasks capability (list, cancel, and per-request augmentation for sampling and elicitation) on ClientCapabilities and ServerCapabilities, with assertions for tasks/* and notifications/tasks/status enforced by Client and ServerSession by @​devcrocod in #732
    • Configurable handlerDispatcher and ioDispatcher, plus an optional caller-supplied CoroutineScope, on transports by @​devcrocod in #778

    Fixed

    • Fire onClose callbacks on SseClientTransport when the SSE session ends — whether closed gracefully or terminated by an error — matching the other client transports by @​jskjw157 in #738

    Security

    • Enable DNS rebinding protection by default across all HTTP transports, including the SSE endpoints that previously had none (see Breaking Changes for migration) by @​devcrocod in #659
    • Cap a single stdio JSON-RPC frame at 16 MiB, throwing the new TooLongFrameException instead of buffering unbounded when a peer never sends a newline terminator by @​devcrocod in 6e6f805

    Maintenance

    • Remove the test-only dev.mokksy:mokksy dependency in favor of MockEngine and embeddedServer by @​devcrocod in #754

    Dependencies

    • Ktor to v3.4.3 in #785
    • kotlinx.coroutines to v1.11.0 in #760
    • kotlin-logging to v8.0.03 in #752, #783
    • 24 further Dependabot updates across sample projects, TypeScript conformance-test fixtures, and build/test tooling (JUnit, slf4j-simple, Gradle wrapper, anthropic-java)

    Full Changelog: https://github.com/modelcontextprotocol/kotlin-sdk/compare/0.12.0...0.13.0

    Commits
    • b7eeeb1 release: 0.13.0 (#796)
    • 6e6f805 Merge commit from fork
    • 0eb1822 Update Ktor to version 3.4.3 (#785)
    • 0adfb3b fix!: harden lifecycle, dispatchers, back-pressure (#778)
    • 37ee423 chore(deps): bump the infrastructure group with 2 updates (#783)
    • ff4b623 chore(deps): bump the other-dependencies group across 3 directories with 3 up...
    • 78066a9 chore(deps): bump the all-dependencies group across 1 directory with 2 update...
    • 1ff5f00 chore(deps): bump qs from 6.14.2 to 6.15.2 in /integration-test/src/jvmTest/t...
    • 6d5bac1 chore(deps): bump gradle-wrapper from 9.5.0 to 9.5.1 (#776)
    • c854560 chore(deps): bump the all-dependencies group across 1 directory with 2 update...
    • Additional commits viewable in compare view

    Updates `io.modelcontextprotocol:kotlin-sdk-client` from 0.12.0 to 0.13.0
    Release notes

    Sourced from io.modelcontextprotocol:kotlin-sdk-client's releases.

    0.13.0

    Description

    Adds a tasks capability and a typed elicitation capability, hardens transport lifecycle, dispatchers, and back-pressure, and enables DNS rebinding protection by default.

    Breaking Changes

    Typed elicitation capability and new tasks capability by @​devcrocod in #732

    ClientCapabilities.elicitation is now a typed ClientCapabilities.Elicitation (form, url) instead of a raw JsonObject?, and both ClientCapabilities and ServerCapabilities gained a typed tasks capability. The constructors of both types changed and are binary-incompatible; recompilation is required.

    Transport handlers now run on Dispatchers.Default by @​devcrocod in #778

    Handlers moved from Dispatchers.IO to Dispatchers.Default (override via handlerDispatcher), send() now suspends under back-pressure instead of buffering unbounded, and the two-argument StdioServerTransport(input, output) constructor is deprecated in favor of the builder-lambda form. Also fixes a start()/close() race, consistent CancellationException propagation, and an input-Source leak (closes #573, #574, #708).

    DNS rebinding protection enabled by default by @​devcrocod in #659

    enableDnsRebindingProtection now defaults to true on mcpStreamableHttp/mcpStatelessStreamableHttp, and the Route.mcp() / Application.mcp() overloads gained enableDnsRebindingProtection, allowedHosts, and allowedOrigins parameters (binary-incompatible on JVM). Only localhost, 127.0.0.1, and [::1] are allowed by default; opt out with enableDnsRebindingProtection = false. The Configuration.enableDnsRebindingProtection, .allowedHosts, and .allowedOrigins properties are deprecated in favor of the new DnsRebindingProtection plugin.

    Features

    • Add a tasks capability (list, cancel, and per-request augmentation for sampling and elicitation) on ClientCapabilities and ServerCapabilities, with assertions for tasks/* and notifications/tasks/status enforced by Client and ServerSession by @​devcrocod in #732
    • Configurable handlerDispatcher and ioDispatcher, plus an optional caller-supplied CoroutineScope, on transports by @​devcrocod in #778

    Fixed

    • Fire onClose callbacks on SseClientTransport when the SSE session ends — whether closed gracefully or terminated by an error — matching the other client transports by @​jskjw157 in #738

    Security

    • Enable DNS rebinding protection by default across all HTTP transports, including the SSE endpoints that previously had none (see Breaking Changes for migration) by @​devcrocod in #659
    • Cap a single stdio JSON-RPC frame at 16 MiB, throwing the new TooLongFrameException instead of buffering unbounded when a peer never sends a newline terminator by @​devcrocod in 6e6f805

    Maintenance

    • Remove the test-only dev.mokksy:mokksy dependency in favor of MockEngine and embeddedServer by @​devcrocod in #754

    Dependencies

    • Ktor to v3.4.3 in #785
    • kotlinx.coroutines to v1.11.0 in #760
    • kotlin-logging to v8.0.03 in #752, #783
    • 24 further Dependabot updates across sample projects, TypeScript conformance-test fixtures, and build/test tooling (JUnit, slf4j-simple, Gradle wrapper, anthropic-java)

    Full Changelog: https://github.com/modelcontextprotocol/kotlin-sdk/compare/0.12.0...0.13.0

    Commits
    • b7eeeb1 release: 0.13.0 (#796)
    • 6e6f805 Merge commit from fork
    • 0eb1822 Update Ktor to version 3.4.3 (#785)
    • 0adfb3b fix!: harden lifecycle, dispatchers, back-pressure (#778)
    • 37ee423 chore(deps): bump the infrastructure group with 2 updates (#783)
    • ff4b623 chore(deps): bump the other-dependencies group across 3 directories with 3 up...
    • 78066a9 chore(deps): bump the all-dependencies group across 1 directory with 2 update...
    • 1ff5f00 chore(deps): bump qs from 6.14.2 to 6.15.2 in /integration-test/src/jvmTest/t...
    • 6d5bac1 chore(deps): bump gradle-wrapper from 9.5.0 to 9.5.1 (#776)
    • c854560 chore(deps): bump the all-dependencies group across 1 directory with 2 update...
    • Additional commits viewable in compare view

    Updates `io.modelcontextprotocol:kotlin-sdk-testing` from 0.12.0 to 0.13.0
    Release notes

    Sourced from io.modelcontextprotocol:kotlin-sdk-testing's releases.

    0.13.0

    Description

    Adds a tasks capability and a typed elicitation capability, hardens transport lifecycle, dispatchers, and back-pressure, and enables DNS rebinding protection by default.

    Breaking Changes

    Typed elicitation capability and new tasks capability by @​devcrocod in #732

    ClientCapabilities.elicitation is now a typed ClientCapabilities.Elicitation (form, url) instead of a raw JsonObject?, and both ClientCapabilities and ServerCapabilities gained a typed tasks capability. The constructors of both types changed and are binary-incompatible; recompilation is required.

    Transport handlers now run on Dispatchers.Default by @​devcrocod in #778

    Handlers moved from Dispatchers.IO to Dispatchers.Default (override via handlerDispatcher), send() now suspends under back-pressure instead of buffering unbounded, and the two-argument StdioServerTransport(input, output) constructor is deprecated in favor of the builder-lambda form. Also fixes a start()/close() race, consistent CancellationException propagation, and an input-Source leak (closes #573, #574, #708).

    DNS rebinding protection enabled by default by @​devcrocod in #659

    enableDnsRebindingProtection now defaults to true on mcpStreamableHttp/mcpStatelessStreamableHttp, and the Route.mcp() / Application.mcp() overloads gained enableDnsRebindingProtection, allowedHosts, and allowedOrigins parameters (binary-incompatible on JVM). Only localhost, 127.0.0.1, and [::1] are allowed by default; opt out with enableDnsRebindingProtection = false. The Configuration.enableDnsRebindingProtection, .allowedHosts, and .allowedOrigins properties are deprecated in favor of the new DnsRebindingProtection plugin.

    Features

    • Add a tasks capability (list, cancel, and per-request augmentation for sampling and elicitation) on ClientCapabilities and ServerCapabilities, with assertions for tasks/* and notifications/tasks/status enforced by Client and ServerSession by @​devcrocod in #732
    • Configurable handlerDispatcher and ioDispatcher, plus an optional caller-supplied CoroutineScope, on transports by @​devcrocod in #778

    Fixed

    • Fire onClose callbacks on SseClientTransport when the SSE session ends — whether closed gracefully or terminated by an error — matching the other client transports by @​jskjw157 in #738

    Security

    • Enable DNS rebinding protection by default across all HTTP transports, including the SSE endpoints that previously had none (see Breaking Changes for migration) by @​devcrocod in #659
    • Cap a single stdio JSON-RPC frame at 16 MiB, throwing the new TooLongFrameException instead of buffering unbounded when a peer never sends a newline terminator by @​devcrocod in 6e6f805

    Maintenance

    • Remove the test-only dev.mokksy:mokksy dependency in favor of MockEngine and embeddedServer by @​devcrocod in #754

    Dependencies

    • Ktor to v3.4.3 in #785
    • kotlinx.coroutines to v1.11.0 in #760
    • kotlin-logging to v8.0.03 in #752, #783
    • 24 further Dependabot updates across sample projects, TypeScript conformance-test fixtures, and build/test tooling (JUnit, slf4j-simple, Gradle wrapper, anthropic-java)

    Full Changelog: https://github.com/modelcontextprotocol/kotlin-sdk/compare/0.12.0...0.13.0

    Commits
    • b7eeeb1 release: 0.13.0 (#796)
    • 6e6f805 Merge commit from fork
    • 0eb1822 Update Ktor to version 3.4.3 (#785)
    • 0adfb3b fix!: harden lifecycle, dispatchers, back-pressure (#778)
    • 37ee423 chore(deps): bump the infrastructure group with 2 updates (#783)
    • ff4b623 chore(deps): bump the other-dependencies group across 3 directories with 3 up...
    • 78066a9 chore(deps): bump the all-dependencies group across 1 directory with 2 update...
    • 1ff5f00 chore(deps): bump qs from 6.14.2 to 6.15.2 in /integration-test/src/jvmTest/t...
    • 6d5bac1 chore(deps): bump gradle-wrapper from 9.5.0 to 9.5.1 (#776)
    • c854560 chore(deps): bump the all-dependencies group across 1 directory with 2 update...
    • Additional commits viewable in compare view

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlinlang-mcp-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml index 9a68e31..fdef6d3 100644 --- a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] kotlin = "2.3.21" ktor = "3.3.3" -mcp-kotlin = "0.12.0" +mcp-kotlin = "0.13.0" serialization = "1.11.0" coroutines = "1.11.0" logback = "1.5.34" From fd8afcf27fbebb26daef87fdf0328b0aa0159185 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Jun 2026 12:14:31 +0200 Subject: [PATCH 315/449] chore(deps): bump com.anthropic:anthropic-java from 2.35.0 to 2.40.1 in /samples/kotlin-mcp-client in the other-dependencies group across 1 directory (#818) Bumps the other-dependencies group with 1 update in the /samples/kotlin-mcp-client directory: [com.anthropic:anthropic-java](https://github.com/anthropics/anthropic-sdk-java). Updates `com.anthropic:anthropic-java` from 2.35.0 to 2.40.1
    Release notes

    Sourced from com.anthropic:anthropic-java's releases.

    v2.40.1

    2.40.1 (2026-06-09)

    Full Changelog: v2.40.0...v2.40.1

    Bug Fixes

    • api: add frontier_llm refusal category (1627fd3)

    v2.40.0

    2.40.0 (2026-06-09)

    Full Changelog: v2.39.0...v2.40.0

    Features

    • api: add support for Managed Agents deployments and environment variable credentials (628720b)

    v2.39.0

    2.39.0 (2026-06-09)

    Full Changelog: v2.38.0...v2.39.0

    Features

    • api: add support for claude-mythos-5 and claude-fable-5, with support for server-side fallbacks on refusal (835fb42)
    • client: adds client-side fallbacks middleware for API providers that do not support server-side fallbacks (835fb42)

    Bug Fixes

    • 3p middleware ordering (#34) (d01da3a)
    • streaming: merge message_delta usage without dropping or rejecting iterations (#39) (e3af118)

    v2.38.0

    2.38.0 (2026-06-06)

    Full Changelog: v2.37.0...v2.38.0

    Features

    • api: small updates to Managed Agents types (c7b08d8)

    v2.37.0

    2.37.0 (2026-06-05)

    Full Changelog: v2.36.0...v2.37.0

    Features

    ... (truncated)

    Changelog

    Sourced from com.anthropic:anthropic-java's changelog.

    2.40.1 (2026-06-09)

    Full Changelog: v2.40.0...v2.40.1

    Bug Fixes

    • api: add frontier_llm refusal category (1627fd3)

    2.40.0 (2026-06-09)

    Full Changelog: v2.39.0...v2.40.0

    Features

    • api: add support for Managed Agents deployments and environment variable credentials (628720b)

    2.39.0 (2026-06-09)

    Full Changelog: v2.38.0...v2.39.0

    Features

    • api: add support for claude-mythos-5 and claude-fable-5, with support for server-side fallbacks on refusal (835fb42)
    • client: adds client-side fallbacks middleware for API providers that do not support server-side fallbacks (835fb42)

    Bug Fixes

    • 3p middleware ordering (#34) (d01da3a)
    • streaming: merge message_delta usage without dropping or rejecting iterations (#39) (e3af118)

    2.38.0 (2026-06-06)

    Full Changelog: v2.37.0...v2.38.0

    Features

    • api: small updates to Managed Agents types (c7b08d8)

    2.37.0 (2026-06-05)

    Full Changelog: v2.36.0...v2.37.0

    Features

    • api: mark Claude Opus 4.1 as deprecated (693cbb4)

    2.36.0 (2026-06-04)

    Full Changelog: v2.35.0...v2.36.0

    ... (truncated)

    Commits
    • 43b8020 release: 2.40.1 (#350)
    • 8f3e69b release: 2.40.0
    • 1540132 feat(api): add support for Managed Agents deployments and environment variabl...
    • a214f31 release: 2.39.0
    • 194e2f4 feat(api): add support for claude-mythos-5 and claude-fable-5, with support f...
    • 56a2a49 fix(streaming): merge message_delta usage without dropping or rejecting itera...
    • a8c6d0f fix: 3p middleware ordering (#34)
    • 79e8568 release: 2.38.0
    • e7c8e6e feat(api): small updates to Managed Agents types
    • edc1ae0 release: 2.37.0
    • Additional commits viewable in compare view

    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-client/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index 42d700e..2b5eb49 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -anthropic = "2.35.0" +anthropic = "2.40.1" kotlin = "2.3.21" ktor = "3.3.3" mcp-kotlin = "0.12.0" From 0851f367972699fc3c588c0c8cabe7023cc03485 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Jun 2026 12:37:03 +0200 Subject: [PATCH 316/449] chore(deps): bump io.modelcontextprotocol:kotlin-sdk-client from 0.12.0 to 0.13.0 in /samples/kotlin-mcp-client (#820) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [io.modelcontextprotocol:kotlin-sdk-client](https://github.com/modelcontextprotocol/kotlin-sdk) from 0.12.0 to 0.13.0.
    Release notes

    Sourced from io.modelcontextprotocol:kotlin-sdk-client's releases.

    0.13.0

    Description

    Adds a tasks capability and a typed elicitation capability, hardens transport lifecycle, dispatchers, and back-pressure, and enables DNS rebinding protection by default.

    Breaking Changes

    Typed elicitation capability and new tasks capability by @​devcrocod in #732

    ClientCapabilities.elicitation is now a typed ClientCapabilities.Elicitation (form, url) instead of a raw JsonObject?, and both ClientCapabilities and ServerCapabilities gained a typed tasks capability. The constructors of both types changed and are binary-incompatible; recompilation is required.

    Transport handlers now run on Dispatchers.Default by @​devcrocod in #778

    Handlers moved from Dispatchers.IO to Dispatchers.Default (override via handlerDispatcher), send() now suspends under back-pressure instead of buffering unbounded, and the two-argument StdioServerTransport(input, output) constructor is deprecated in favor of the builder-lambda form. Also fixes a start()/close() race, consistent CancellationException propagation, and an input-Source leak (closes #573, #574, #708).

    DNS rebinding protection enabled by default by @​devcrocod in #659

    enableDnsRebindingProtection now defaults to true on mcpStreamableHttp/mcpStatelessStreamableHttp, and the Route.mcp() / Application.mcp() overloads gained enableDnsRebindingProtection, allowedHosts, and allowedOrigins parameters (binary-incompatible on JVM). Only localhost, 127.0.0.1, and [::1] are allowed by default; opt out with enableDnsRebindingProtection = false. The Configuration.enableDnsRebindingProtection, .allowedHosts, and .allowedOrigins properties are deprecated in favor of the new DnsRebindingProtection plugin.

    Features

    • Add a tasks capability (list, cancel, and per-request augmentation for sampling and elicitation) on ClientCapabilities and ServerCapabilities, with assertions for tasks/* and notifications/tasks/status enforced by Client and ServerSession by @​devcrocod in #732
    • Configurable handlerDispatcher and ioDispatcher, plus an optional caller-supplied CoroutineScope, on transports by @​devcrocod in #778

    Fixed

    • Fire onClose callbacks on SseClientTransport when the SSE session ends — whether closed gracefully or terminated by an error — matching the other client transports by @​jskjw157 in #738

    Security

    • Enable DNS rebinding protection by default across all HTTP transports, including the SSE endpoints that previously had none (see Breaking Changes for migration) by @​devcrocod in #659
    • Cap a single stdio JSON-RPC frame at 16 MiB, throwing the new TooLongFrameException instead of buffering unbounded when a peer never sends a newline terminator by @​devcrocod in 6e6f805

    Maintenance

    • Remove the test-only dev.mokksy:mokksy dependency in favor of MockEngine and embeddedServer by @​devcrocod in #754

    Dependencies

    • Ktor to v3.4.3 in #785
    • kotlinx.coroutines to v1.11.0 in #760
    • kotlin-logging to v8.0.03 in #752, #783
    • 24 further Dependabot updates across sample projects, TypeScript conformance-test fixtures, and build/test tooling (JUnit, slf4j-simple, Gradle wrapper, anthropic-java)

    Full Changelog: https://github.com/modelcontextprotocol/kotlin-sdk/compare/0.12.0...0.13.0

    Commits
    • b7eeeb1 release: 0.13.0 (#796)
    • 6e6f805 Merge commit from fork
    • 0eb1822 Update Ktor to version 3.4.3 (#785)
    • 0adfb3b fix!: harden lifecycle, dispatchers, back-pressure (#778)
    • 37ee423 chore(deps): bump the infrastructure group with 2 updates (#783)
    • ff4b623 chore(deps): bump the other-dependencies group across 3 directories with 3 up...
    • 78066a9 chore(deps): bump the all-dependencies group across 1 directory with 2 update...
    • 1ff5f00 chore(deps): bump qs from 6.14.2 to 6.15.2 in /integration-test/src/jvmTest/t...
    • 6d5bac1 chore(deps): bump gradle-wrapper from 9.5.0 to 9.5.1 (#776)
    • c854560 chore(deps): bump the all-dependencies group across 1 directory with 2 update...
    • Additional commits viewable in compare view

    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-client/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index 2b5eb49..033fbdc 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -2,7 +2,7 @@ anthropic = "2.40.1" kotlin = "2.3.21" ktor = "3.3.3" -mcp-kotlin = "0.12.0" +mcp-kotlin = "0.13.0" shadow = "9.4.2" slf4j = "2.0.18" From b71df8be126deceab628c701a79b03fb3177a697 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Jun 2026 10:54:57 +0000 Subject: [PATCH 317/449] chore(deps): bump @types/node from 25.9.1 to 25.9.3 in /integration-test/src/jvmTest/typescript in the all-dependencies group across 1 directory (#821) Bumps the all-dependencies group with 1 update in the /integration-test/src/jvmTest/typescript directory: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node). Updates `@types/node` from 25.9.1 to 25.9.3
    Commits

    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- integration-test/src/jvmTest/typescript/package-lock.json | 8 ++++---- integration-test/src/jvmTest/typescript/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index ed07e4e..3dab623 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -15,7 +15,7 @@ "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^25.9.1", + "@types/node": "^25.9.3", "cors": "^2.8.6", "tsx": "^4.22.4", "typescript": "^6.0.3" @@ -579,9 +579,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.9.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", - "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", + "version": "25.9.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.3.tgz", + "integrity": "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/integration-test/src/jvmTest/typescript/package.json b/integration-test/src/jvmTest/typescript/package.json index 0fafdb9..629b3bb 100644 --- a/integration-test/src/jvmTest/typescript/package.json +++ b/integration-test/src/jvmTest/typescript/package.json @@ -16,7 +16,7 @@ "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^25.9.1", + "@types/node": "^25.9.3", "cors": "^2.8.6", "tsx": "^4.22.4", "typescript": "^6.0.3" From 2b9bc78165f85aff0c1eec99c483af3e87dbe312 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Jun 2026 11:13:14 +0000 Subject: [PATCH 318/449] chore(deps): bump mcp-kotlin from 0.12.0 to 0.13.0 in /samples/kotlin-mcp-server (#823) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `mcp-kotlin` from 0.12.0 to 0.13.0. Updates `io.modelcontextprotocol:kotlin-sdk-client` from 0.12.0 to 0.13.0
    Release notes

    Sourced from io.modelcontextprotocol:kotlin-sdk-client's releases.

    0.13.0

    Description

    Adds a tasks capability and a typed elicitation capability, hardens transport lifecycle, dispatchers, and back-pressure, and enables DNS rebinding protection by default.

    Breaking Changes

    Typed elicitation capability and new tasks capability by @​devcrocod in #732

    ClientCapabilities.elicitation is now a typed ClientCapabilities.Elicitation (form, url) instead of a raw JsonObject?, and both ClientCapabilities and ServerCapabilities gained a typed tasks capability. The constructors of both types changed and are binary-incompatible; recompilation is required.

    Transport handlers now run on Dispatchers.Default by @​devcrocod in #778

    Handlers moved from Dispatchers.IO to Dispatchers.Default (override via handlerDispatcher), send() now suspends under back-pressure instead of buffering unbounded, and the two-argument StdioServerTransport(input, output) constructor is deprecated in favor of the builder-lambda form. Also fixes a start()/close() race, consistent CancellationException propagation, and an input-Source leak (closes #573, #574, #708).

    DNS rebinding protection enabled by default by @​devcrocod in #659

    enableDnsRebindingProtection now defaults to true on mcpStreamableHttp/mcpStatelessStreamableHttp, and the Route.mcp() / Application.mcp() overloads gained enableDnsRebindingProtection, allowedHosts, and allowedOrigins parameters (binary-incompatible on JVM). Only localhost, 127.0.0.1, and [::1] are allowed by default; opt out with enableDnsRebindingProtection = false. The Configuration.enableDnsRebindingProtection, .allowedHosts, and .allowedOrigins properties are deprecated in favor of the new DnsRebindingProtection plugin.

    Features

    • Add a tasks capability (list, cancel, and per-request augmentation for sampling and elicitation) on ClientCapabilities and ServerCapabilities, with assertions for tasks/* and notifications/tasks/status enforced by Client and ServerSession by @​devcrocod in #732
    • Configurable handlerDispatcher and ioDispatcher, plus an optional caller-supplied CoroutineScope, on transports by @​devcrocod in #778

    Fixed

    • Fire onClose callbacks on SseClientTransport when the SSE session ends — whether closed gracefully or terminated by an error — matching the other client transports by @​jskjw157 in #738

    Security

    • Enable DNS rebinding protection by default across all HTTP transports, including the SSE endpoints that previously had none (see Breaking Changes for migration) by @​devcrocod in #659
    • Cap a single stdio JSON-RPC frame at 16 MiB, throwing the new TooLongFrameException instead of buffering unbounded when a peer never sends a newline terminator by @​devcrocod in 6e6f805

    Maintenance

    • Remove the test-only dev.mokksy:mokksy dependency in favor of MockEngine and embeddedServer by @​devcrocod in #754

    Dependencies

    • Ktor to v3.4.3 in #785
    • kotlinx.coroutines to v1.11.0 in #760
    • kotlin-logging to v8.0.03 in #752, #783
    • 24 further Dependabot updates across sample projects, TypeScript conformance-test fixtures, and build/test tooling (JUnit, slf4j-simple, Gradle wrapper, anthropic-java)

    Full Changelog: https://github.com/modelcontextprotocol/kotlin-sdk/compare/0.12.0...0.13.0

    Commits
    • b7eeeb1 release: 0.13.0 (#796)
    • 6e6f805 Merge commit from fork
    • 0eb1822 Update Ktor to version 3.4.3 (#785)
    • 0adfb3b fix!: harden lifecycle, dispatchers, back-pressure (#778)
    • 37ee423 chore(deps): bump the infrastructure group with 2 updates (#783)
    • ff4b623 chore(deps): bump the other-dependencies group across 3 directories with 3 up...
    • 78066a9 chore(deps): bump the all-dependencies group across 1 directory with 2 update...
    • 1ff5f00 chore(deps): bump qs from 6.14.2 to 6.15.2 in /integration-test/src/jvmTest/t...
    • 6d5bac1 chore(deps): bump gradle-wrapper from 9.5.0 to 9.5.1 (#776)
    • c854560 chore(deps): bump the all-dependencies group across 1 directory with 2 update...
    • Additional commits viewable in compare view

    Updates `io.modelcontextprotocol:kotlin-sdk-server` from 0.12.0 to 0.13.0
    Release notes

    Sourced from io.modelcontextprotocol:kotlin-sdk-server's releases.

    0.13.0

    Description

    Adds a tasks capability and a typed elicitation capability, hardens transport lifecycle, dispatchers, and back-pressure, and enables DNS rebinding protection by default.

    Breaking Changes

    Typed elicitation capability and new tasks capability by @​devcrocod in #732

    ClientCapabilities.elicitation is now a typed ClientCapabilities.Elicitation (form, url) instead of a raw JsonObject?, and both ClientCapabilities and ServerCapabilities gained a typed tasks capability. The constructors of both types changed and are binary-incompatible; recompilation is required.

    Transport handlers now run on Dispatchers.Default by @​devcrocod in #778

    Handlers moved from Dispatchers.IO to Dispatchers.Default (override via handlerDispatcher), send() now suspends under back-pressure instead of buffering unbounded, and the two-argument StdioServerTransport(input, output) constructor is deprecated in favor of the builder-lambda form. Also fixes a start()/close() race, consistent CancellationException propagation, and an input-Source leak (closes #573, #574, #708).

    DNS rebinding protection enabled by default by @​devcrocod in #659

    enableDnsRebindingProtection now defaults to true on mcpStreamableHttp/mcpStatelessStreamableHttp, and the Route.mcp() / Application.mcp() overloads gained enableDnsRebindingProtection, allowedHosts, and allowedOrigins parameters (binary-incompatible on JVM). Only localhost, 127.0.0.1, and [::1] are allowed by default; opt out with enableDnsRebindingProtection = false. The Configuration.enableDnsRebindingProtection, .allowedHosts, and .allowedOrigins properties are deprecated in favor of the new DnsRebindingProtection plugin.

    Features

    • Add a tasks capability (list, cancel, and per-request augmentation for sampling and elicitation) on ClientCapabilities and ServerCapabilities, with assertions for tasks/* and notifications/tasks/status enforced by Client and ServerSession by @​devcrocod in #732
    • Configurable handlerDispatcher and ioDispatcher, plus an optional caller-supplied CoroutineScope, on transports by @​devcrocod in #778

    Fixed

    • Fire onClose callbacks on SseClientTransport when the SSE session ends — whether closed gracefully or terminated by an error — matching the other client transports by @​jskjw157 in #738

    Security

    • Enable DNS rebinding protection by default across all HTTP transports, including the SSE endpoints that previously had none (see Breaking Changes for migration) by @​devcrocod in #659
    • Cap a single stdio JSON-RPC frame at 16 MiB, throwing the new TooLongFrameException instead of buffering unbounded when a peer never sends a newline terminator by @​devcrocod in 6e6f805

    Maintenance

    • Remove the test-only dev.mokksy:mokksy dependency in favor of MockEngine and embeddedServer by @​devcrocod in #754

    Dependencies

    • Ktor to v3.4.3 in #785
    • kotlinx.coroutines to v1.11.0 in #760
    • kotlin-logging to v8.0.03 in #752, #783
    • 24 further Dependabot updates across sample projects, TypeScript conformance-test fixtures, and build/test tooling (JUnit, slf4j-simple, Gradle wrapper, anthropic-java)

    Full Changelog: https://github.com/modelcontextprotocol/kotlin-sdk/compare/0.12.0...0.13.0

    Commits
    • b7eeeb1 release: 0.13.0 (#796)
    • 6e6f805 Merge commit from fork
    • 0eb1822 Update Ktor to version 3.4.3 (#785)
    • 0adfb3b fix!: harden lifecycle, dispatchers, back-pressure (#778)
    • 37ee423 chore(deps): bump the infrastructure group with 2 updates (#783)
    • ff4b623 chore(deps): bump the other-dependencies group across 3 directories with 3 up...
    • 78066a9 chore(deps): bump the all-dependencies group across 1 directory with 2 update...
    • 1ff5f00 chore(deps): bump qs from 6.14.2 to 6.15.2 in /integration-test/src/jvmTest/t...
    • 6d5bac1 chore(deps): bump gradle-wrapper from 9.5.0 to 9.5.1 (#776)
    • c854560 chore(deps): bump the all-dependencies group across 1 directory with 2 update...
    • Additional commits viewable in compare view

    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-server/gradle/libs.versions.toml b/samples/kotlin-mcp-server/gradle/libs.versions.toml index 165d096..bd87373 100644 --- a/samples/kotlin-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-server/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] kotlin = "2.3.21" ktor = "3.3.3" -mcp-kotlin = "0.12.0" +mcp-kotlin = "0.13.0" slf4j = "2.0.18" shadow = "9.4.2" From 251bacdc447d21a0c0031aaaca8bfd6c27f5c3aa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Jun 2026 11:27:40 +0000 Subject: [PATCH 319/449] chore(deps): bump mcp-kotlin from 0.12.0 to 0.13.0 in /samples/simple-streamable-server (#826) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `mcp-kotlin` from 0.12.0 to 0.13.0. Updates `io.modelcontextprotocol:kotlin-sdk-client` from 0.12.0 to 0.13.0
    Release notes

    Sourced from io.modelcontextprotocol:kotlin-sdk-client's releases.

    0.13.0

    Description

    Adds a tasks capability and a typed elicitation capability, hardens transport lifecycle, dispatchers, and back-pressure, and enables DNS rebinding protection by default.

    Breaking Changes

    Typed elicitation capability and new tasks capability by @​devcrocod in #732

    ClientCapabilities.elicitation is now a typed ClientCapabilities.Elicitation (form, url) instead of a raw JsonObject?, and both ClientCapabilities and ServerCapabilities gained a typed tasks capability. The constructors of both types changed and are binary-incompatible; recompilation is required.

    Transport handlers now run on Dispatchers.Default by @​devcrocod in #778

    Handlers moved from Dispatchers.IO to Dispatchers.Default (override via handlerDispatcher), send() now suspends under back-pressure instead of buffering unbounded, and the two-argument StdioServerTransport(input, output) constructor is deprecated in favor of the builder-lambda form. Also fixes a start()/close() race, consistent CancellationException propagation, and an input-Source leak (closes #573, #574, #708).

    DNS rebinding protection enabled by default by @​devcrocod in #659

    enableDnsRebindingProtection now defaults to true on mcpStreamableHttp/mcpStatelessStreamableHttp, and the Route.mcp() / Application.mcp() overloads gained enableDnsRebindingProtection, allowedHosts, and allowedOrigins parameters (binary-incompatible on JVM). Only localhost, 127.0.0.1, and [::1] are allowed by default; opt out with enableDnsRebindingProtection = false. The Configuration.enableDnsRebindingProtection, .allowedHosts, and .allowedOrigins properties are deprecated in favor of the new DnsRebindingProtection plugin.

    Features

    • Add a tasks capability (list, cancel, and per-request augmentation for sampling and elicitation) on ClientCapabilities and ServerCapabilities, with assertions for tasks/* and notifications/tasks/status enforced by Client and ServerSession by @​devcrocod in #732
    • Configurable handlerDispatcher and ioDispatcher, plus an optional caller-supplied CoroutineScope, on transports by @​devcrocod in #778

    Fixed

    • Fire onClose callbacks on SseClientTransport when the SSE session ends — whether closed gracefully or terminated by an error — matching the other client transports by @​jskjw157 in #738

    Security

    • Enable DNS rebinding protection by default across all HTTP transports, including the SSE endpoints that previously had none (see Breaking Changes for migration) by @​devcrocod in #659
    • Cap a single stdio JSON-RPC frame at 16 MiB, throwing the new TooLongFrameException instead of buffering unbounded when a peer never sends a newline terminator by @​devcrocod in 6e6f805

    Maintenance

    • Remove the test-only dev.mokksy:mokksy dependency in favor of MockEngine and embeddedServer by @​devcrocod in #754

    Dependencies

    • Ktor to v3.4.3 in #785
    • kotlinx.coroutines to v1.11.0 in #760
    • kotlin-logging to v8.0.03 in #752, #783
    • 24 further Dependabot updates across sample projects, TypeScript conformance-test fixtures, and build/test tooling (JUnit, slf4j-simple, Gradle wrapper, anthropic-java)

    Full Changelog: https://github.com/modelcontextprotocol/kotlin-sdk/compare/0.12.0...0.13.0

    Commits
    • b7eeeb1 release: 0.13.0 (#796)
    • 6e6f805 Merge commit from fork
    • 0eb1822 Update Ktor to version 3.4.3 (#785)
    • 0adfb3b fix!: harden lifecycle, dispatchers, back-pressure (#778)
    • 37ee423 chore(deps): bump the infrastructure group with 2 updates (#783)
    • ff4b623 chore(deps): bump the other-dependencies group across 3 directories with 3 up...
    • 78066a9 chore(deps): bump the all-dependencies group across 1 directory with 2 update...
    • 1ff5f00 chore(deps): bump qs from 6.14.2 to 6.15.2 in /integration-test/src/jvmTest/t...
    • 6d5bac1 chore(deps): bump gradle-wrapper from 9.5.0 to 9.5.1 (#776)
    • c854560 chore(deps): bump the all-dependencies group across 1 directory with 2 update...
    • Additional commits viewable in compare view

    Updates `io.modelcontextprotocol:kotlin-sdk-server` from 0.12.0 to 0.13.0
    Release notes

    Sourced from io.modelcontextprotocol:kotlin-sdk-server's releases.

    0.13.0

    Description

    Adds a tasks capability and a typed elicitation capability, hardens transport lifecycle, dispatchers, and back-pressure, and enables DNS rebinding protection by default.

    Breaking Changes

    Typed elicitation capability and new tasks capability by @​devcrocod in #732

    ClientCapabilities.elicitation is now a typed ClientCapabilities.Elicitation (form, url) instead of a raw JsonObject?, and both ClientCapabilities and ServerCapabilities gained a typed tasks capability. The constructors of both types changed and are binary-incompatible; recompilation is required.

    Transport handlers now run on Dispatchers.Default by @​devcrocod in #778

    Handlers moved from Dispatchers.IO to Dispatchers.Default (override via handlerDispatcher), send() now suspends under back-pressure instead of buffering unbounded, and the two-argument StdioServerTransport(input, output) constructor is deprecated in favor of the builder-lambda form. Also fixes a start()/close() race, consistent CancellationException propagation, and an input-Source leak (closes #573, #574, #708).

    DNS rebinding protection enabled by default by @​devcrocod in #659

    enableDnsRebindingProtection now defaults to true on mcpStreamableHttp/mcpStatelessStreamableHttp, and the Route.mcp() / Application.mcp() overloads gained enableDnsRebindingProtection, allowedHosts, and allowedOrigins parameters (binary-incompatible on JVM). Only localhost, 127.0.0.1, and [::1] are allowed by default; opt out with enableDnsRebindingProtection = false. The Configuration.enableDnsRebindingProtection, .allowedHosts, and .allowedOrigins properties are deprecated in favor of the new DnsRebindingProtection plugin.

    Features

    • Add a tasks capability (list, cancel, and per-request augmentation for sampling and elicitation) on ClientCapabilities and ServerCapabilities, with assertions for tasks/* and notifications/tasks/status enforced by Client and ServerSession by @​devcrocod in #732
    • Configurable handlerDispatcher and ioDispatcher, plus an optional caller-supplied CoroutineScope, on transports by @​devcrocod in #778

    Fixed

    • Fire onClose callbacks on SseClientTransport when the SSE session ends — whether closed gracefully or terminated by an error — matching the other client transports by @​jskjw157 in #738

    Security

    • Enable DNS rebinding protection by default across all HTTP transports, including the SSE endpoints that previously had none (see Breaking Changes for migration) by @​devcrocod in #659
    • Cap a single stdio JSON-RPC frame at 16 MiB, throwing the new TooLongFrameException instead of buffering unbounded when a peer never sends a newline terminator by @​devcrocod in 6e6f805

    Maintenance

    • Remove the test-only dev.mokksy:mokksy dependency in favor of MockEngine and embeddedServer by @​devcrocod in #754

    Dependencies

    • Ktor to v3.4.3 in #785
    • kotlinx.coroutines to v1.11.0 in #760
    • kotlin-logging to v8.0.03 in #752, #783
    • 24 further Dependabot updates across sample projects, TypeScript conformance-test fixtures, and build/test tooling (JUnit, slf4j-simple, Gradle wrapper, anthropic-java)

    Full Changelog: https://github.com/modelcontextprotocol/kotlin-sdk/compare/0.12.0...0.13.0

    Commits
    • b7eeeb1 release: 0.13.0 (#796)
    • 6e6f805 Merge commit from fork
    • 0eb1822 Update Ktor to version 3.4.3 (#785)
    • 0adfb3b fix!: harden lifecycle, dispatchers, back-pressure (#778)
    • 37ee423 chore(deps): bump the infrastructure group with 2 updates (#783)
    • ff4b623 chore(deps): bump the other-dependencies group across 3 directories with 3 up...
    • 78066a9 chore(deps): bump the all-dependencies group across 1 directory with 2 update...
    • 1ff5f00 chore(deps): bump qs from 6.14.2 to 6.15.2 in /integration-test/src/jvmTest/t...
    • 6d5bac1 chore(deps): bump gradle-wrapper from 9.5.0 to 9.5.1 (#776)
    • c854560 chore(deps): bump the all-dependencies group across 1 directory with 2 update...
    • Additional commits viewable in compare view

    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/simple-streamable-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/simple-streamable-server/gradle/libs.versions.toml b/samples/simple-streamable-server/gradle/libs.versions.toml index 07bdac1..ba5b0e7 100644 --- a/samples/simple-streamable-server/gradle/libs.versions.toml +++ b/samples/simple-streamable-server/gradle/libs.versions.toml @@ -2,7 +2,7 @@ kotlin = "2.3.21" ktor = "3.3.3" junit = "6.1.0" -mcp-kotlin = "0.12.0" +mcp-kotlin = "0.13.0" slf4j = "2.0.18" shadow = "9.4.2" From 479527c7bb2274c50c3ce112801c3ee242d3fb69 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Jun 2026 14:18:19 +0200 Subject: [PATCH 320/449] chore(deps): bump mcp-kotlin from 0.12.0 to 0.13.0 in /samples/weather-stdio-server (#828) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `mcp-kotlin` from 0.12.0 to 0.13.0. Updates `io.modelcontextprotocol:kotlin-sdk-client` from 0.12.0 to 0.13.0
    Release notes

    Sourced from io.modelcontextprotocol:kotlin-sdk-client's releases.

    0.13.0

    Description

    Adds a tasks capability and a typed elicitation capability, hardens transport lifecycle, dispatchers, and back-pressure, and enables DNS rebinding protection by default.

    Breaking Changes

    Typed elicitation capability and new tasks capability by @​devcrocod in #732

    ClientCapabilities.elicitation is now a typed ClientCapabilities.Elicitation (form, url) instead of a raw JsonObject?, and both ClientCapabilities and ServerCapabilities gained a typed tasks capability. The constructors of both types changed and are binary-incompatible; recompilation is required.

    Transport handlers now run on Dispatchers.Default by @​devcrocod in #778

    Handlers moved from Dispatchers.IO to Dispatchers.Default (override via handlerDispatcher), send() now suspends under back-pressure instead of buffering unbounded, and the two-argument StdioServerTransport(input, output) constructor is deprecated in favor of the builder-lambda form. Also fixes a start()/close() race, consistent CancellationException propagation, and an input-Source leak (closes #573, #574, #708).

    DNS rebinding protection enabled by default by @​devcrocod in #659

    enableDnsRebindingProtection now defaults to true on mcpStreamableHttp/mcpStatelessStreamableHttp, and the Route.mcp() / Application.mcp() overloads gained enableDnsRebindingProtection, allowedHosts, and allowedOrigins parameters (binary-incompatible on JVM). Only localhost, 127.0.0.1, and [::1] are allowed by default; opt out with enableDnsRebindingProtection = false. The Configuration.enableDnsRebindingProtection, .allowedHosts, and .allowedOrigins properties are deprecated in favor of the new DnsRebindingProtection plugin.

    Features

    • Add a tasks capability (list, cancel, and per-request augmentation for sampling and elicitation) on ClientCapabilities and ServerCapabilities, with assertions for tasks/* and notifications/tasks/status enforced by Client and ServerSession by @​devcrocod in #732
    • Configurable handlerDispatcher and ioDispatcher, plus an optional caller-supplied CoroutineScope, on transports by @​devcrocod in #778

    Fixed

    • Fire onClose callbacks on SseClientTransport when the SSE session ends — whether closed gracefully or terminated by an error — matching the other client transports by @​jskjw157 in #738

    Security

    • Enable DNS rebinding protection by default across all HTTP transports, including the SSE endpoints that previously had none (see Breaking Changes for migration) by @​devcrocod in #659
    • Cap a single stdio JSON-RPC frame at 16 MiB, throwing the new TooLongFrameException instead of buffering unbounded when a peer never sends a newline terminator by @​devcrocod in 6e6f805

    Maintenance

    • Remove the test-only dev.mokksy:mokksy dependency in favor of MockEngine and embeddedServer by @​devcrocod in #754

    Dependencies

    • Ktor to v3.4.3 in #785
    • kotlinx.coroutines to v1.11.0 in #760
    • kotlin-logging to v8.0.03 in #752, #783
    • 24 further Dependabot updates across sample projects, TypeScript conformance-test fixtures, and build/test tooling (JUnit, slf4j-simple, Gradle wrapper, anthropic-java)

    Full Changelog: https://github.com/modelcontextprotocol/kotlin-sdk/compare/0.12.0...0.13.0

    Commits
    • b7eeeb1 release: 0.13.0 (#796)
    • 6e6f805 Merge commit from fork
    • 0eb1822 Update Ktor to version 3.4.3 (#785)
    • 0adfb3b fix!: harden lifecycle, dispatchers, back-pressure (#778)
    • 37ee423 chore(deps): bump the infrastructure group with 2 updates (#783)
    • ff4b623 chore(deps): bump the other-dependencies group across 3 directories with 3 up...
    • 78066a9 chore(deps): bump the all-dependencies group across 1 directory with 2 update...
    • 1ff5f00 chore(deps): bump qs from 6.14.2 to 6.15.2 in /integration-test/src/jvmTest/t...
    • 6d5bac1 chore(deps): bump gradle-wrapper from 9.5.0 to 9.5.1 (#776)
    • c854560 chore(deps): bump the all-dependencies group across 1 directory with 2 update...
    • Additional commits viewable in compare view

    Updates `io.modelcontextprotocol:kotlin-sdk-server` from 0.12.0 to 0.13.0
    Release notes

    Sourced from io.modelcontextprotocol:kotlin-sdk-server's releases.

    0.13.0

    Description

    Adds a tasks capability and a typed elicitation capability, hardens transport lifecycle, dispatchers, and back-pressure, and enables DNS rebinding protection by default.

    Breaking Changes

    Typed elicitation capability and new tasks capability by @​devcrocod in #732

    ClientCapabilities.elicitation is now a typed ClientCapabilities.Elicitation (form, url) instead of a raw JsonObject?, and both ClientCapabilities and ServerCapabilities gained a typed tasks capability. The constructors of both types changed and are binary-incompatible; recompilation is required.

    Transport handlers now run on Dispatchers.Default by @​devcrocod in #778

    Handlers moved from Dispatchers.IO to Dispatchers.Default (override via handlerDispatcher), send() now suspends under back-pressure instead of buffering unbounded, and the two-argument StdioServerTransport(input, output) constructor is deprecated in favor of the builder-lambda form. Also fixes a start()/close() race, consistent CancellationException propagation, and an input-Source leak (closes #573, #574, #708).

    DNS rebinding protection enabled by default by @​devcrocod in #659

    enableDnsRebindingProtection now defaults to true on mcpStreamableHttp/mcpStatelessStreamableHttp, and the Route.mcp() / Application.mcp() overloads gained enableDnsRebindingProtection, allowedHosts, and allowedOrigins parameters (binary-incompatible on JVM). Only localhost, 127.0.0.1, and [::1] are allowed by default; opt out with enableDnsRebindingProtection = false. The Configuration.enableDnsRebindingProtection, .allowedHosts, and .allowedOrigins properties are deprecated in favor of the new DnsRebindingProtection plugin.

    Features

    • Add a tasks capability (list, cancel, and per-request augmentation for sampling and elicitation) on ClientCapabilities and ServerCapabilities, with assertions for tasks/* and notifications/tasks/status enforced by Client and ServerSession by @​devcrocod in #732
    • Configurable handlerDispatcher and ioDispatcher, plus an optional caller-supplied CoroutineScope, on transports by @​devcrocod in #778

    Fixed

    • Fire onClose callbacks on SseClientTransport when the SSE session ends — whether closed gracefully or terminated by an error — matching the other client transports by @​jskjw157 in #738

    Security

    • Enable DNS rebinding protection by default across all HTTP transports, including the SSE endpoints that previously had none (see Breaking Changes for migration) by @​devcrocod in #659
    • Cap a single stdio JSON-RPC frame at 16 MiB, throwing the new TooLongFrameException instead of buffering unbounded when a peer never sends a newline terminator by @​devcrocod in 6e6f805

    Maintenance

    • Remove the test-only dev.mokksy:mokksy dependency in favor of MockEngine and embeddedServer by @​devcrocod in #754

    Dependencies

    • Ktor to v3.4.3 in #785
    • kotlinx.coroutines to v1.11.0 in #760
    • kotlin-logging to v8.0.03 in #752, #783
    • 24 further Dependabot updates across sample projects, TypeScript conformance-test fixtures, and build/test tooling (JUnit, slf4j-simple, Gradle wrapper, anthropic-java)

    Full Changelog: https://github.com/modelcontextprotocol/kotlin-sdk/compare/0.12.0...0.13.0

    Commits
    • b7eeeb1 release: 0.13.0 (#796)
    • 6e6f805 Merge commit from fork
    • 0eb1822 Update Ktor to version 3.4.3 (#785)
    • 0adfb3b fix!: harden lifecycle, dispatchers, back-pressure (#778)
    • 37ee423 chore(deps): bump the infrastructure group with 2 updates (#783)
    • ff4b623 chore(deps): bump the other-dependencies group across 3 directories with 3 up...
    • 78066a9 chore(deps): bump the all-dependencies group across 1 directory with 2 update...
    • 1ff5f00 chore(deps): bump qs from 6.14.2 to 6.15.2 in /integration-test/src/jvmTest/t...
    • 6d5bac1 chore(deps): bump gradle-wrapper from 9.5.0 to 9.5.1 (#776)
    • c854560 chore(deps): bump the all-dependencies group across 1 directory with 2 update...
    • Additional commits viewable in compare view

    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/weather-stdio-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/weather-stdio-server/gradle/libs.versions.toml b/samples/weather-stdio-server/gradle/libs.versions.toml index d7a6fcb..3e99ead 100644 --- a/samples/weather-stdio-server/gradle/libs.versions.toml +++ b/samples/weather-stdio-server/gradle/libs.versions.toml @@ -3,7 +3,7 @@ coroutines = "1.11.0" kotlin = "2.3.21" ktor = "3.3.3" logging = "8.0.4" -mcp-kotlin = "0.12.0" +mcp-kotlin = "0.13.0" shadow = "9.4.2" slf4j = "2.0.18" From d65c50735b6b8753a6db2fa3fc489ed24ed1f5c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Jun 2026 14:36:48 +0200 Subject: [PATCH 321/449] chore(deps): bump esbuild from 0.28.0 to 0.28.1 in /integration-test/src/jvmTest/typescript (#831) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [esbuild](https://github.com/evanw/esbuild) from 0.28.0 to 0.28.1.
    Release notes

    Sourced from esbuild's releases.

    v0.28.1

    • Disallow \ in local development server HTTP requests (GHSA-g7r4-m6w7-qqqr)

      This release fixes a security issue where HTTP requests to esbuild's local development server could traverse outside of the serve directory on Windows using a \ backslash character. It happened due to the use of Go's path.Clean() function, which only handles Unix-style / characters. HTTP requests with paths containing \ are no longer allowed.

      Thanks to @​dellalibera for reporting this issue.

    • Add integrity checks to the Deno API (GHSA-gv7w-rqvm-qjhr)

      The previous release of esbuild added integrity checks to esbuild's npm install script. This release also adds integrity checks to esbuild's Deno install script. Now esbuild's Deno API will also fail with an error if the downloaded esbuild binary contains something other than the expected content.

      Note that esbuild's Deno API installs from registry.npmjs.org by default, but allows the NPM_CONFIG_REGISTRY environment variable to override this with a custom package registry. This change means that the esbuild executable served by NPM_CONFIG_REGISTRY must now match the expected content.

      Thanks to @​sondt99 for reporting this issue.

    • Avoid inlining using and await using declarations (#4482)

      Previously esbuild's minifier sometimes incorrectly inlined using and await using declarations into subsequent uses of that declaration, which then fails to dispose of the resource correctly. This bug happened because inlining was done for let and const declarations by avoiding doing it for var declarations, which no longer worked when more declaration types were added. Here's an example:

      // Original code
      {
        using x = new Resource()
        x.activate()
      }
      

      // Old output (with --minify)
      new Resource().activate();

      // New output (with --minify)
      {using e=new Resource;e.activate()}

    • Fix module evaluation when an error is thrown (#4461, #4467)

      If an error is thrown during module evaluation, esbuild previously didn't preserve the state of the module for subsequent module references. This was observable if import() or require() is used to import a module multiple times. The thrown error is supposed to be thrown by every call to import() or require(), not just the first. With this release, esbuild will now throw the same error every time you call import() or require() on a module that throws during its evaluation.

    • Fix some edge cases around the new operator (#4477)

      Previously esbuild incorrectly printed certain edge cases involving complex expressions inside the target of a new expression (specifically an optional chain and/or a tagged template literal). The generated code for the new target was not correctly wrapped with parentheses, and either contained a syntax error or had different semantics. These edge cases have been fixed so that they now correctly wrap the new target in parentheses. Here is an example of some affected code:

      // Original code
      new (foo()`bar`)()
      new (foo()?.bar)()
      

      // Old output
      new foo()bar();
      new (foo())?.bar();

    ... (truncated)

    Changelog

    Sourced from esbuild's changelog.

    0.28.1

    • Disallow \ in local development server HTTP requests (GHSA-g7r4-m6w7-qqqr)

      This release fixes a security issue where HTTP requests to esbuild's local development server could traverse outside of the serve directory on Windows using a \ backslash character. It happened due to the use of Go's path.Clean() function, which only handles Unix-style / characters. HTTP requests with paths containing \ are no longer allowed.

      Thanks to @​dellalibera for reporting this issue.

    • Add integrity checks to the Deno API (GHSA-gv7w-rqvm-qjhr)

      The previous release of esbuild added integrity checks to esbuild's npm install script. This release also adds integrity checks to esbuild's Deno install script. Now esbuild's Deno API will also fail with an error if the downloaded esbuild binary contains something other than the expected content.

      Note that esbuild's Deno API installs from registry.npmjs.org by default, but allows the NPM_CONFIG_REGISTRY environment variable to override this with a custom package registry. This change means that the esbuild executable served by NPM_CONFIG_REGISTRY must now match the expected content.

      Thanks to @​sondt99 for reporting this issue.

    • Avoid inlining using and await using declarations (#4482)

      Previously esbuild's minifier sometimes incorrectly inlined using and await using declarations into subsequent uses of that declaration, which then fails to dispose of the resource correctly. This bug happened because inlining was done for let and const declarations by avoiding doing it for var declarations, which no longer worked when more declaration types were added. Here's an example:

      // Original code
      {
        using x = new Resource()
        x.activate()
      }
      

      // Old output (with --minify)
      new Resource().activate();

      // New output (with --minify)
      {using e=new Resource;e.activate()}

    • Fix module evaluation when an error is thrown (#4461, #4467)

      If an error is thrown during module evaluation, esbuild previously didn't preserve the state of the module for subsequent module references. This was observable if import() or require() is used to import a module multiple times. The thrown error is supposed to be thrown by every call to import() or require(), not just the first. With this release, esbuild will now throw the same error every time you call import() or require() on a module that throws during its evaluation.

    • Fix some edge cases around the new operator (#4477)

      Previously esbuild incorrectly printed certain edge cases involving complex expressions inside the target of a new expression (specifically an optional chain and/or a tagged template literal). The generated code for the new target was not correctly wrapped with parentheses, and either contained a syntax error or had different semantics. These edge cases have been fixed so that they now correctly wrap the new target in parentheses. Here is an example of some affected code:

      // Original code
      new (foo()`bar`)()
      new (foo()?.bar)()
      

      // Old output
      new foo()bar();
      new (foo())?.bar();

    ... (truncated)

    Commits

    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../src/jvmTest/typescript/package-lock.json | 214 +++++++++--------- 1 file changed, 107 insertions(+), 107 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index 3dab623..de65fb2 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -22,9 +22,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", - "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", "cpu": [ "ppc64" ], @@ -39,9 +39,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", - "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", "cpu": [ "arm" ], @@ -56,9 +56,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", - "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", "cpu": [ "arm64" ], @@ -73,9 +73,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", - "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", "cpu": [ "x64" ], @@ -90,9 +90,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", - "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", "cpu": [ "arm64" ], @@ -107,9 +107,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", - "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", "cpu": [ "x64" ], @@ -124,9 +124,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", - "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", "cpu": [ "arm64" ], @@ -141,9 +141,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", - "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", "cpu": [ "x64" ], @@ -158,9 +158,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", - "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", "cpu": [ "arm" ], @@ -175,9 +175,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", - "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", "cpu": [ "arm64" ], @@ -192,9 +192,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", - "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", "cpu": [ "ia32" ], @@ -209,9 +209,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", - "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", "cpu": [ "loong64" ], @@ -226,9 +226,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", - "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", "cpu": [ "mips64el" ], @@ -243,9 +243,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", - "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", "cpu": [ "ppc64" ], @@ -260,9 +260,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", - "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", "cpu": [ "riscv64" ], @@ -277,9 +277,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", - "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", "cpu": [ "s390x" ], @@ -294,9 +294,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", - "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", "cpu": [ "x64" ], @@ -311,9 +311,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", - "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", "cpu": [ "arm64" ], @@ -328,9 +328,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", - "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", "cpu": [ "x64" ], @@ -345,9 +345,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", - "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", "cpu": [ "arm64" ], @@ -362,9 +362,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", - "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", "cpu": [ "x64" ], @@ -379,9 +379,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", - "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", "cpu": [ "arm64" ], @@ -396,9 +396,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", - "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", "cpu": [ "x64" ], @@ -413,9 +413,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", - "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", "cpu": [ "arm64" ], @@ -430,9 +430,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", - "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", "cpu": [ "ia32" ], @@ -447,9 +447,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", - "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", "cpu": [ "x64" ], @@ -888,9 +888,9 @@ } }, "node_modules/esbuild": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", - "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -901,32 +901,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.0", - "@esbuild/android-arm": "0.28.0", - "@esbuild/android-arm64": "0.28.0", - "@esbuild/android-x64": "0.28.0", - "@esbuild/darwin-arm64": "0.28.0", - "@esbuild/darwin-x64": "0.28.0", - "@esbuild/freebsd-arm64": "0.28.0", - "@esbuild/freebsd-x64": "0.28.0", - "@esbuild/linux-arm": "0.28.0", - "@esbuild/linux-arm64": "0.28.0", - "@esbuild/linux-ia32": "0.28.0", - "@esbuild/linux-loong64": "0.28.0", - "@esbuild/linux-mips64el": "0.28.0", - "@esbuild/linux-ppc64": "0.28.0", - "@esbuild/linux-riscv64": "0.28.0", - "@esbuild/linux-s390x": "0.28.0", - "@esbuild/linux-x64": "0.28.0", - "@esbuild/netbsd-arm64": "0.28.0", - "@esbuild/netbsd-x64": "0.28.0", - "@esbuild/openbsd-arm64": "0.28.0", - "@esbuild/openbsd-x64": "0.28.0", - "@esbuild/openharmony-arm64": "0.28.0", - "@esbuild/sunos-x64": "0.28.0", - "@esbuild/win32-arm64": "0.28.0", - "@esbuild/win32-ia32": "0.28.0", - "@esbuild/win32-x64": "0.28.0" + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" } }, "node_modules/escape-html": { From 75a944fb8ce249eb36a6800eabeddca7cf8b40e2 Mon Sep 17 00:00:00 2001 From: Mukunda Rao Katta Date: Wed, 17 Jun 2026 10:11:23 -0700 Subject: [PATCH 322/449] fix: polish SEP-1577 sampling handling (#765) ## Summary - reject empty sampling content in SamplingMessage and CreateMessageResult constructors - clarify sampling validation docs and emit an explicit error when tool_use is not followed by tool_result - return sampled text from the conformance test_sampling tool instead of content object strings Fixes #730 ## Testing - ./gradlew -Dorg.gradle.java.installations.paths="/Users/ubl/Documents/New project/oss-contributions/.jdks/jdk-21.0.11+10/Contents/Home" :kotlin-sdk-core:jvmTest :kotlin-sdk-server:jvmTest :conformance-test:compileKotlin Co-authored-by: Pavel Gorgulov --- .../kotlin/sdk/conformance/ConformanceTools.kt | 8 +++++++- .../kotlin/sdk/types/sampling.kt | 8 ++++++++ .../kotlin/sdk/types/SamplingTest.kt | 18 ++++++++++++++++++ .../kotlin/sdk/server/SamplingValidation.kt | 7 +++++-- .../kotlin/sdk/server/SamplingTest.kt | 18 ++++++++++++++++++ 5 files changed, 56 insertions(+), 3 deletions(-) diff --git a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTools.kt b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTools.kt index e288272..c81c98c 100644 --- a/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTools.kt +++ b/conformance-test/src/main/kotlin/io/modelcontextprotocol/kotlin/sdk/conformance/ConformanceTools.kt @@ -179,7 +179,13 @@ fun Server.registerConformanceTools() { ), ), ) - CallToolResult(listOf(TextContent(result.content.joinToString("\n") { it.toString() }))) + val sampledText = result.content.joinToString("\n") { content -> + when (content) { + is TextContent -> content.text + else -> "Non-text sampling content: ${content::class.simpleName}" + } + } + CallToolResult(listOf(TextContent(sampledText))) } // 9. Elicitation diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.kt index 211fdc7..4526405 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.kt @@ -91,6 +91,10 @@ public data class SamplingMessage( @SerialName("_meta") val meta: JsonObject? = null, ) { + init { + require(content.isNotEmpty()) { "content must contain at least one block" } + } + /** * Convenience constructor for a single-block message. Wraps [content] in a * singleton list so call sites can write `SamplingMessage(Role.User, TextContent("hi"))` @@ -273,6 +277,10 @@ public data class CreateMessageResult( @SerialName("_meta") override val meta: JsonObject? = null, ) : ClientResult { + init { + require(content.isNotEmpty()) { "content must contain at least one block" } + } + /** * Convenience constructor for a single-block response. Wraps [content] in a * singleton list so call sites can write diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/SamplingTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/SamplingTest.kt index 62a82cd..b2c8cf0 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/SamplingTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/SamplingTest.kt @@ -302,6 +302,13 @@ class SamplingTest { (m.content[0] as TextContent).text shouldBe "hi" } + @Test + fun `SamplingMessage rejects empty content`() { + assertFailsWith { + SamplingMessage(role = Role.User, content = emptyList()) + } + } + @Test fun `SamplingMessage single-element content serialises as single object`() { val m = SamplingMessage(role = Role.User, content = listOf(TextContent("hi"))) @@ -408,6 +415,17 @@ class SamplingTest { (decoded.content[0] as TextContent).text shouldBe "hi" } + @Test + fun `CreateMessageResult rejects empty content`() { + assertFailsWith { + CreateMessageResult( + role = Role.Assistant, + content = emptyList(), + model = "test-model", + ) + } + } + // ============================================================================ // SamplingContentSerializer (single-or-array wire heuristic) // ============================================================================ diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SamplingValidation.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SamplingValidation.kt index ef40808..8243171 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SamplingValidation.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SamplingValidation.kt @@ -20,8 +20,8 @@ import io.modelcontextprotocol.kotlin.sdk.types.ToolUseContent * 3. If the previous message contains `tool_use` blocks, the last message's * `tool_result` ids MUST form exactly the same set. * - * On the first violation throws [IllegalArgumentException]. No-op when there are fewer - * than two messages or no tool_use / tool_result blocks are involved. + * On the first violation throws [IllegalArgumentException]. No-op when no + * tool_use / tool_result blocks are involved. */ internal fun validateSamplingMessages(messages: List) { if (messages.isEmpty()) return @@ -44,6 +44,9 @@ internal fun validateSamplingMessages(messages: List) { if (hasPreviousToolUse) { val toolUseIds = previous.filterIsInstance().map { it.id }.toSet() val toolResultIds = last.filterIsInstance().map { it.toolUseId }.toSet() + require(toolResultIds.isNotEmpty()) { + "tool_use blocks from previous message must be followed by matching tool_result blocks" + } require(toolUseIds == toolResultIds) { "ids of tool_result blocks and tool_use blocks from previous message do not match" } diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SamplingTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SamplingTest.kt index 2b5446b..cfcbb62 100644 --- a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SamplingTest.kt +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SamplingTest.kt @@ -8,6 +8,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.ToolUseContent import kotlinx.serialization.json.JsonObject import org.junit.jupiter.api.assertDoesNotThrow import kotlin.test.Test +import kotlin.test.assertEquals import kotlin.test.assertFailsWith /** @@ -83,4 +84,21 @@ class SamplingTest { ) } } + + @Test + fun `validate tool_use requires explicit tool_result in last message`() { + val error = assertFailsWith { + validateSamplingMessages( + listOf( + SamplingMessage(Role.Assistant, toolUse("c1")), + SamplingMessage(Role.User, TextContent("missing result")), + ), + ) + } + + assertEquals( + "tool_use blocks from previous message must be followed by matching tool_result blocks", + error.message, + ) + } } From d21728eccab4b59a2a850eb417695c624d95bab4 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Thu, 18 Jun 2026 08:54:54 +0200 Subject: [PATCH 323/449] fix: bound inline SSE event size in Streamable HTTP client (#841) Bound the size of a single inline SSE event parsed from a POST response in `StreamableHttpClientTransport`, so a non-conforming or unbounded server response cannot grow the client's buffer without limit. ## How Has This Been Tested? New unit tests ## Breaking Changes none ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- kotlin-sdk-client/api/kotlin-sdk-client.api | 4 +- .../client/StreamableHttpClientTransport.kt | 42 ++++++- .../http/StreamableHttpClientTransportTest.kt | 106 +++++++++++++++++- 3 files changed, 144 insertions(+), 8 deletions(-) diff --git a/kotlin-sdk-client/api/kotlin-sdk-client.api b/kotlin-sdk-client/api/kotlin-sdk-client.api index 8b6fb79..0a544f5 100644 --- a/kotlin-sdk-client/api/kotlin-sdk-client.api +++ b/kotlin-sdk-client/api/kotlin-sdk-client.api @@ -101,8 +101,8 @@ public final class io/modelcontextprotocol/kotlin/sdk/client/StdioClientTranspor } public final class io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport : io/modelcontextprotocol/kotlin/sdk/shared/AbstractClientTransport { - public fun (Lio/ktor/client/HttpClient;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/client/ReconnectionOptions;Lkotlin/jvm/functions/Function1;)V - public synthetic fun (Lio/ktor/client/HttpClient;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/client/ReconnectionOptions;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lio/ktor/client/HttpClient;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/client/ReconnectionOptions;ILkotlin/jvm/functions/Function1;)V + public synthetic fun (Lio/ktor/client/HttpClient;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/client/ReconnectionOptions;ILkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun (Lio/ktor/client/HttpClient;Ljava/lang/String;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun (Lio/ktor/client/HttpClient;Ljava/lang/String;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getProtocolVersion ()Ljava/lang/String; diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt index c27ed79..a63c1d2 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt @@ -21,8 +21,10 @@ import io.ktor.http.HttpMethod import io.ktor.http.HttpStatusCode import io.ktor.http.contentType import io.ktor.http.isSuccess +import io.ktor.utils.io.charsets.TooLongLineException import io.ktor.utils.io.readUTF8Line import io.modelcontextprotocol.kotlin.sdk.shared.AbstractClientTransport +import io.modelcontextprotocol.kotlin.sdk.shared.TooLongFrameException import io.modelcontextprotocol.kotlin.sdk.shared.TransportSendOptions import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification @@ -50,6 +52,14 @@ private const val MCP_SESSION_ID_HEADER = "mcp-session-id" private const val MCP_PROTOCOL_VERSION_HEADER = "mcp-protocol-version" private const val MCP_RESUMPTION_TOKEN_HEADER = "Last-Event-ID" +/** + * Default maximum size, in characters, of a single inline SSE event assembled from a POST response. + * + * Mirrors the stdio transport's 16 MiB frame cap: a server that streams `data:` lines without ever + * terminating the event cannot grow the client's buffer without bound. + */ +private const val DEFAULT_MAX_INLINE_SSE_EVENT_SIZE: Int = 16 * 1024 * 1024 + /** * Represents an error from the Streamable HTTP transport. * @@ -75,15 +85,23 @@ private sealed interface ConnectResult { * @param client Ktor HTTP client used for all requests * @param url MCP endpoint URL * @param reconnectionOptions reconnection backoff and retry-limit settings for the SSE stream + * @param maxInlineSseEventSize maximum size, in characters, of a single inline SSE event parsed from a + * POST response; a server that exceeds it (including by never terminating an event) fails the send + * with [io.modelcontextprotocol.kotlin.sdk.shared.TooLongFrameException]. Defaults to 16 MiB. * @param requestBuilder builder applied to every outgoing HTTP request, e.g. for adding auth headers */ public class StreamableHttpClientTransport( private val client: HttpClient, private val url: String, private val reconnectionOptions: ReconnectionOptions = ReconnectionOptions(), + private val maxInlineSseEventSize: Int = DEFAULT_MAX_INLINE_SSE_EVENT_SIZE, private val requestBuilder: HttpRequestBuilder.() -> Unit = {}, ) : AbstractClientTransport() { + init { + require(maxInlineSseEventSize > 0) { "maxInlineSseEventSize must be greater than 0" } + } + @Deprecated( "Use constructor with ReconnectionOptions", replaceWith = ReplaceWith( @@ -98,7 +116,12 @@ public class StreamableHttpClientTransport( url: String, reconnectionTime: Duration?, requestBuilder: HttpRequestBuilder.() -> Unit = {}, - ) : this(client, url, ReconnectionOptions(initialReconnectionDelay = reconnectionTime ?: 1.seconds), requestBuilder) + ) : this( + client, + url, + ReconnectionOptions(initialReconnectionDelay = reconnectionTime ?: 1.seconds), + requestBuilder = requestBuilder, + ) override val logger: KLogger = KotlinLogging.logger {} @@ -458,7 +481,14 @@ public class StreamableHttpClientTransport( } while (!channel.isClosedForRead) { - val line = channel.readUTF8Line() ?: break + // Bound each line so a server that streams a line without ever terminating it cannot + // exhaust client memory; readUTF8Line returns null at the end of the stream. + val line = try { + channel.readUTF8Line(maxInlineSseEventSize) + } catch (_: TooLongLineException) { + throw TooLongFrameException(maxInlineSseEventSize.toLong() + 1, maxInlineSseEventSize) + } + if (line == null) break if (line.isEmpty()) { dispatch(id = id, eventName = eventName, data = sb.toString()) // reset @@ -472,7 +502,13 @@ public class StreamableHttpClientTransport( line.startsWith("event:") -> eventName = line.substringAfter("event:").trim() - line.startsWith("data:") -> sb.append(line.substringAfter("data:").trim()) + line.startsWith("data:") -> { + sb.append(line.substringAfter("data:").trim()) + // Cap an event assembled from many data: lines that never sees a terminating blank line. + if (sb.length > maxInlineSseEventSize) { + throw TooLongFrameException(sb.length.toLong(), maxInlineSseEventSize) + } + } line.startsWith("retry:") -> line.substringAfter("retry:").trim().toLongOrNull()?.let { localServerRetryDelay = it.milliseconds diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt index b380f64..ef55867 100644 --- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt @@ -19,6 +19,7 @@ import io.ktor.http.headersOf import io.ktor.utils.io.ByteReadChannel import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.client.StreamableHttpClientTransport +import io.modelcontextprotocol.kotlin.sdk.shared.TooLongFrameException import io.modelcontextprotocol.kotlin.sdk.types.Implementation import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification @@ -43,11 +44,15 @@ import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertNull import kotlin.test.assertTrue +import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds class StreamableHttpClientTransportTest { - private fun createTransport(handler: MockRequestHandler): StreamableHttpClientTransport { + private fun createTransport( + maxInlineSseEventSize: Int = 16 * 1024 * 1024, + handler: MockRequestHandler, + ): StreamableHttpClientTransport { val mockEngine = MockEngine(handler) val httpClient = HttpClient(mockEngine) { install(SSE) { @@ -55,7 +60,11 @@ class StreamableHttpClientTransportTest { } } - return StreamableHttpClientTransport(httpClient, url = "http://localhost:8080/mcp") + return StreamableHttpClientTransport( + httpClient, + url = "http://localhost:8080/mcp", + maxInlineSseEventSize = maxInlineSseEventSize, + ) } private fun buildSseMessage(id: String, method: String, params: String): String = buildString { @@ -551,6 +560,97 @@ class StreamableHttpClientTransportTest { transport.close() } + @Test + fun testInlineSseRejectsEventExceedingMaxSize() = runTest { + // A malicious server streams an endless single event: many `data:` lines that accumulate + // past the cap and never send the blank-line terminator that would flush the buffer. + val maxInlineSseEventSize = 64 + val transport = createTransport(maxInlineSseEventSize) { request -> + if (request.method == HttpMethod.Post) { + val sseContent = buildString { + appendLine("event: message") + // 20 lines × 16 chars = 320 chars of accumulated data, no terminating blank line. + repeat(20) { appendLine("data: ${"A".repeat(16)}") } + } + respond( + content = ByteReadChannel(sseContent), + status = HttpStatusCode.OK, + headers = headersOf(HttpHeaders.ContentType, ContentType.Text.EventStream.toString()), + ) + } else { + respond("", HttpStatusCode.OK) + } + } + + val receivedMessages = mutableListOf() + val receivedErrors = mutableListOf() + transport.onMessage { receivedMessages.add(it) } + transport.onError { receivedErrors.add(it) } + transport.start() + + val error = assertFailsWith { + transport.send(JSONRPCRequest(id = "req-1", method = "test", params = buildJsonObject { })) + } + + error.cause.shouldBeInstanceOf() + receivedErrors.filterIsInstance() shouldHaveSize 1 + receivedMessages shouldHaveSize 0 + transport.close() + } + + @Test + fun testInlineSseEventExactlyAtMaxSizeIsAccepted() = runTest { + // An event whose assembled data length equals the cap must still be accepted and dispatched: + // the guard rejects only sizes strictly greater than the cap (parity with ReadBuffer). + val part1 = """{"jsonrpc":"2.0",""" + val part2 = """"method":"notifications/tools/list_changed"}""" + val maxInlineSseEventSize = (part1 + part2).length + + val transport = createTransport(maxInlineSseEventSize) { request -> + if (request.method == HttpMethod.Post) { + val sseContent = buildString { + appendLine("event: message") + appendLine("data: $part1") + appendLine("data: $part2") + appendLine() + } + respond( + content = ByteReadChannel(sseContent), + status = HttpStatusCode.OK, + headers = headersOf(HttpHeaders.ContentType, ContentType.Text.EventStream.toString()), + ) + } else { + respond("", HttpStatusCode.OK) + } + } + + val receivedMessages = mutableListOf() + val receivedErrors = mutableListOf() + val messageReceived = CompletableDeferred() + transport.onMessage { + receivedMessages.add(it) + if (!messageReceived.isCompleted) messageReceived.complete(Unit) + } + transport.onError { receivedErrors.add(it) } + transport.start() + + transport.send(JSONRPCRequest(id = "req-1", method = "test", params = buildJsonObject { })) + + eventually { messageReceived.await() } + + receivedMessages shouldHaveSize 1 + (receivedMessages[0] as JSONRPCNotification).method shouldBe "notifications/tools/list_changed" + receivedErrors shouldHaveSize 0 + transport.close() + } + + @Test + fun testNonPositiveMaxInlineSseEventSizeThrows() { + assertFailsWith { + createTransport(maxInlineSseEventSize = 0) { respond("", HttpStatusCode.OK) } + } + } + @Test fun testInlineSSEInResponse() = runTest { val transport = createTransport { request -> @@ -716,7 +816,7 @@ class StreamableHttpClientTransportTest { eventually { while (receivedMessages.isEmpty()) { - delay(10) + delay(10.milliseconds) } } From c8a1103b163509e6f58900ec3bd99307031faeef Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Tue, 23 Jun 2026 22:14:59 +0200 Subject: [PATCH 324/449] feat: sep-1036 add URL Mode Elicitation (#843) closes #410 ## How Has This Been Tested? integration and unit tests ## 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- .../kotlin/sdk/client/ClientTest.kt | 187 ++++++++++++++++++ .../kotlin/sdk/server/ClientConnectionTest.kt | 2 + kotlin-sdk-client/api/kotlin-sdk-client.api | 1 + .../kotlin/sdk/client/Client.kt | 45 ++++- kotlin-sdk-core/api/kotlin-sdk-core.api | 40 +++- .../kotlin/sdk/shared/Protocol.kt | 2 +- .../kotlin/sdk/types/McpException.kt | 21 +- .../types/UrlElicitationRequiredException.kt | 65 ++++++ .../kotlin/sdk/types/capabilities.kt | 9 + .../kotlin/sdk/types/elicitation.kt | 5 + .../kotlin/sdk/types/jsonRpc.kt | 7 + .../kotlin/sdk/types/CapabilitiesTest.kt | 20 ++ .../kotlin/sdk/types/ElicitationTest.kt | 83 ++++++++ .../kotlin/sdk/server/ClientConnection.kt | 9 + .../kotlin/sdk/server/Server.kt | 4 + 15 files changed, 494 insertions(+), 6 deletions(-) create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/UrlElicitationRequiredException.kt diff --git a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt index 5c00b23..5dfcf61 100644 --- a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt +++ b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt @@ -14,7 +14,10 @@ import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageResult import io.modelcontextprotocol.kotlin.sdk.types.DoubleSchema 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.ElicitationCompleteNotificationParams import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject import io.modelcontextprotocol.kotlin.sdk.types.Implementation import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest @@ -46,6 +49,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.Tool import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema import io.modelcontextprotocol.kotlin.sdk.types.UntitledMultiSelectEnumSchema import io.modelcontextprotocol.kotlin.sdk.types.UntitledSingleSelectEnumSchema +import io.modelcontextprotocol.kotlin.sdk.types.UrlElicitationRequiredException import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.cancel @@ -1251,6 +1255,189 @@ class ClientTest { client.close() } + // ── URL-mode elicitation (SEP-1036) ───────────────────────────────── + + @Test + fun `should handle URL mode elicitation end-to-end`() = runTest { + val client = Client( + Implementation(name = "test client", version = "1.0"), + ClientOptions( + capabilities = ClientCapabilities( + elicitation = ClientCapabilities.Elicitation(url = EmptyJsonObject), + ), + ), + ) + + val elicitationId = "550e8400-e29b-41d4-a716-446655440000" + val url = "https://oauth.example.com/authorize" + + client.setElicitationHandler { request -> + val params = assertIs(request.params) + assertEquals(elicitationId, params.elicitationId) + assertEquals(url, params.url) + ElicitResult(action = ElicitResult.Action.Accept) + } + + val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() + val server = Server( + serverInfo = Implementation(name = "test server", version = "1.0"), + options = ServerOptions(capabilities = ServerCapabilities()), + ) + + val serverSessionResult = CompletableDeferred() + listOf( + launch { client.connect(clientTransport) }, + launch { serverSessionResult.complete(server.createSession(serverTransport)) }, + ).joinAll() + val serverSession = serverSessionResult.await() + + val result = serverSession.createElicitation( + message = "Authorize access to continue", + elicitationId = elicitationId, + url = url, + ) + + assertEquals(ElicitResult.Action.Accept, result.action) + assertNull(result.content) + + client.close() + } + + @Test + fun `should reject URL mode elicitation when client supports only form mode`() = runTest { + val (client, serverSession) = setupElicitationPair { + ElicitResult(action = ElicitResult.Action.Accept) + } + + val exception = assertFailsWith { + serverSession.createElicitation( + message = "Authorize", + elicitationId = "id-1", + url = "https://example.com/auth", + ) + } + assertTrue(exception.message!!.contains("elicitation.url")) + + client.close() + } + + @Test + fun `should deliver elicitation complete notification to client`() = runTest { + val received = CompletableDeferred() + val client = Client( + Implementation(name = "test client", version = "1.0"), + ClientOptions( + capabilities = ClientCapabilities( + elicitation = ClientCapabilities.Elicitation(url = EmptyJsonObject), + ), + ), + ) + client.setElicitationCompleteHandler { received.complete(it) } + + val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() + val server = Server( + serverInfo = Implementation(name = "test server", version = "1.0"), + options = ServerOptions(capabilities = ServerCapabilities()), + ) + + val serverSessionResult = CompletableDeferred() + listOf( + launch { client.connect(clientTransport) }, + launch { serverSessionResult.complete(server.createSession(serverTransport)) }, + ).joinAll() + val serverSession = serverSessionResult.await() + + val elicitationId = "complete-id-1" + serverSession.sendElicitationComplete( + ElicitationCompleteNotification(ElicitationCompleteNotificationParams(elicitationId = elicitationId)), + ) + + val notification = received.await() + assertEquals(elicitationId, notification.params.elicitationId) + + client.close() + } + + @Test + fun `should reject elicitation complete when client supports only form mode`() = runTest { + val (client, serverSession) = setupElicitationPair { + ElicitResult(action = ElicitResult.Action.Accept) + } + + val exception = assertFailsWith { + serverSession.sendElicitationComplete( + ElicitationCompleteNotification(ElicitationCompleteNotificationParams(elicitationId = "id-1")), + ) + } + assertTrue(exception.message!!.contains("elicitation.url")) + + client.close() + } + + @Test + fun `setElicitationCompleteHandler should require url capability`() = runTest { + val client = Client( + Implementation(name = "test client", version = "1.0"), + ClientOptions( + capabilities = ClientCapabilities( + elicitation = ClientCapabilities.Elicitation(), + ), + ), + ) + + assertFailsWith { + client.setElicitationCompleteHandler { } + } + } + + @Test + fun `should surface URL elicitation required error to client as typed exception`() = runTest { + val client = Client( + Implementation(name = "test client", version = "1.0"), + ClientOptions( + capabilities = ClientCapabilities( + elicitation = ClientCapabilities.Elicitation(url = EmptyJsonObject), + ), + ), + ) + + val elicitationId = "auth-required-1" + val url = "https://oauth.example.com/authorize" + + val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() + val server = Server( + serverInfo = Implementation(name = "test server", version = "1.0"), + options = ServerOptions(capabilities = ServerCapabilities(tools = ServerCapabilities.Tools(true))), + ) + server.addTool("needs-auth", "Requires URL elicitation") { + throw UrlElicitationRequiredException( + listOf( + ElicitRequestURLParams( + message = "Authorize to continue", + elicitationId = elicitationId, + url = url, + ), + ), + ) + } + + val serverSessionResult = CompletableDeferred() + listOf( + launch { client.connect(clientTransport) }, + launch { serverSessionResult.complete(server.createSession(serverTransport)) }, + ).joinAll() + serverSessionResult.await() + + val exception = assertFailsWith { + client.callTool(name = "needs-auth", arguments = emptyMap()) + } + val elicitation = exception.elicitations.single() + assertEquals(elicitationId, elicitation.elicitationId) + assertEquals(url, elicitation.url) + + client.close() + } + private fun defaultsTestSchema(): ElicitRequestParams.RequestedSchema = ElicitRequestParams.RequestedSchema( properties = mapOf( "name" to StringSchema(description = "User name", default = "John Doe"), diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnectionTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnectionTest.kt index ee8953a..581e5ae 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnectionTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnectionTest.kt @@ -9,6 +9,7 @@ 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.EmptyJsonObject import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequestParams import io.modelcontextprotocol.kotlin.sdk.types.ListRootsRequest @@ -61,6 +62,7 @@ class ClientConnectionTest : AbstractServerFeaturesTest() { override fun getClientCapabilities(): ClientCapabilities = ClientCapabilities( roots = ClientCapabilities.Roots(listChanged = true), + elicitation = ClientCapabilities.Elicitation(url = EmptyJsonObject), ) private val sampleRoots = listOf(Root("file:///project", "Project Root")) diff --git a/kotlin-sdk-client/api/kotlin-sdk-client.api b/kotlin-sdk-client/api/kotlin-sdk-client.api index 0a544f5..1493a30 100644 --- a/kotlin-sdk-client/api/kotlin-sdk-client.api +++ b/kotlin-sdk-client/api/kotlin-sdk-client.api @@ -34,6 +34,7 @@ public class io/modelcontextprotocol/kotlin/sdk/client/Client : io/modelcontextp public final fun removeRoot (Ljava/lang/String;)Z public final fun removeRoots (Ljava/util/List;)I public final fun sendRootsListChanged (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun setElicitationCompleteHandler (Lkotlin/jvm/functions/Function1;)V public final fun setElicitationHandler (Lkotlin/jvm/functions/Function1;)V public final fun setLoggingLevel (Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun setLoggingLevel$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt index ee865af..b88cb1b 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt @@ -19,6 +19,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.DoubleSchema import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequest import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestFormParams 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.GetPromptRequest import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult @@ -63,6 +64,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.TitledSingleSelectEnumSchema import io.modelcontextprotocol.kotlin.sdk.types.UnsubscribeRequest import io.modelcontextprotocol.kotlin.sdk.types.UntitledMultiSelectEnumSchema import io.modelcontextprotocol.kotlin.sdk.types.UntitledSingleSelectEnumSchema +import io.modelcontextprotocol.kotlin.sdk.types.supportsUrl import io.modelcontextprotocol.kotlin.sdk.types.toJson import kotlinx.atomicfu.atomic import kotlinx.atomicfu.getAndUpdate @@ -70,6 +72,7 @@ import kotlinx.atomicfu.update import kotlinx.collections.immutable.minus import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.toPersistentSet +import kotlinx.coroutines.CompletableDeferred import kotlinx.serialization.SerializationException import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject @@ -643,9 +646,18 @@ public open class Client(private val clientInfo: Implementation, options: Client /** * Sets the elicitation handler. * - * When the handler returns [ElicitResult.Action.Accept], any properties missing from + * The handler receives both form-mode ([ElicitRequestFormParams]) and URL-mode + * ([io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestURLParams]) requests; + * branch on `request.params` to tell them apart. For URL mode, + * the host application must obtain explicit user consent and display the target domain before + * navigating — the SDK never opens or validates the URL — and should return + * [ElicitResult.Action.Decline] or [ElicitResult.Action.Cancel] when it cannot or will not proceed. + * A URL-mode [ElicitResult.Action.Accept] only signals consent; the outcome arrives out-of-band via + * [setElicitationCompleteHandler]. + * + * When a form-mode handler returns [ElicitResult.Action.Accept], any properties missing from * [ElicitResult.content] are automatically populated with default values defined in the - * elicitation schema. + * elicitation schema. URL-mode responses carry no content. * * @param handler The elicitation handler. * @throws IllegalStateException if the client does not support elicitation. @@ -663,6 +675,35 @@ public open class Client(private val clientInfo: Implementation, options: Client } } + /** + * Sets the handler invoked when the server reports that a URL-mode elicitation has completed. + * + * The handler is called for every `notifications/elicitation/complete` notification. Because the + * server only sends this for an out-of-band (URL-mode) interaction, the client must support url-mode + * elicitation. The client is responsible for correlating the notification's `elicitationId` with a + * pending elicitation, ignoring unknown or already-completed identifiers, and providing a manual way + * to continue if a notification never arrives. + * + * @param handler Invoked with each completion notification. + * @throws IllegalStateException if the client does not support url-mode elicitation. + */ + public fun setElicitationCompleteHandler(handler: (ElicitationCompleteNotification) -> Unit) { + check(capabilities.elicitation.supportsUrl) { + logger.error { + "Failed to set elicitation-complete handler: client does not support url-mode elicitation" + } + "Client does not support url-mode elicitation." + } + logger.info { "Setting the elicitation-complete handler" } + + setNotificationHandler( + Method.Defined.NotificationsElicitationComplete, + ) { notification -> + handler(notification) + CompletableDeferred(Unit) + } + } + // --- Internal Handlers --- private fun applyElicitationDefaults(request: ElicitRequest, result: ElicitResult): ElicitResult { diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index 0895722..1904dc7 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -610,6 +610,10 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CancelledNotificatio public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/CapabilitiesKt { + public static final fun getSupportsUrl (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Elicitation;)Z +} + public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Companion; public fun ()V @@ -3128,7 +3132,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/Logging_dslKt { public abstract interface annotation class io/modelcontextprotocol/kotlin/sdk/types/McpDsl : java/lang/annotation/Annotation { } -public final class io/modelcontextprotocol/kotlin/sdk/types/McpException : java/lang/Exception { +public class io/modelcontextprotocol/kotlin/sdk/types/McpException : java/lang/Exception { public fun (I)V public fun (ILjava/lang/String;)V public fun (ILjava/lang/String;Lkotlinx/serialization/json/JsonElement;)V @@ -3744,6 +3748,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/RPCError$ErrorCode { public static final field PARSE_ERROR I public static final field REQUEST_TIMEOUT I public static final field RESOURCE_NOT_FOUND I + public static final field URL_ELICITATION_REQUIRED I } public final class io/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest { @@ -5814,6 +5819,39 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/UntitledSingleSelect public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/UrlElicitationRequiredData { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/UrlElicitationRequiredData$Companion; + public fun (Ljava/util/List;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lio/modelcontextprotocol/kotlin/sdk/types/UrlElicitationRequiredData; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/UrlElicitationRequiredData;Ljava/util/List;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/UrlElicitationRequiredData; + public fun equals (Ljava/lang/Object;)Z + public final fun getElicitations ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/UrlElicitationRequiredData$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/UrlElicitationRequiredData$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/UrlElicitationRequiredData; + 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/UrlElicitationRequiredData;)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/UrlElicitationRequiredData$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/UrlElicitationRequiredException : io/modelcontextprotocol/kotlin/sdk/types/McpException { + public fun (Ljava/util/List;Ljava/lang/String;)V + public synthetic fun (Ljava/util/List;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getElicitations ()Ljava/util/List; +} + public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/WithMeta { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/WithMeta$Companion; public abstract fun getMeta ()Lkotlinx/serialization/json/JsonObject; diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt index 8f86c03..50dc356 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt @@ -393,7 +393,7 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio handler(response, null) } else { checkNotNull(error) - val mcpException = McpException( + val mcpException = McpException.fromError( code = error.error.code, message = error.error.message, data = error.error.data, diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/McpException.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/McpException.kt index 56c851b..6a9015b 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/McpException.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/McpException.kt @@ -12,9 +12,26 @@ import kotlin.jvm.JvmOverloads * @property data optional additional error payload as a JSON element * @param cause the original cause */ -public class McpException @JvmOverloads public constructor( +public open class McpException @JvmOverloads public constructor( public val code: Int, message: String = "MCP error $code", public val data: JsonElement? = null, cause: Throwable? = null, -) : Exception(message, cause) +) : Exception(message, cause) { + internal companion object { + /** + * Reconstructs the most specific [McpException] subtype for a JSON-RPC error. + * + * Recognizes [RPCError.ErrorCode.URL_ELICITATION_REQUIRED] errors and returns a + * [UrlElicitationRequiredException] when [data] carries valid URL-mode elicitations; + * otherwise returns a plain [McpException]. Never throws — a malformed payload simply + * degrades to a plain [McpException]. + */ + fun fromError(code: Int, message: String, data: JsonElement?): McpException { + if (code == RPCError.ErrorCode.URL_ELICITATION_REQUIRED && data != null) { + UrlElicitationRequiredException.fromDataOrNull(message, data)?.let { return it } + } + return McpException(code = code, message = message, data = data) + } + } +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/UrlElicitationRequiredException.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/UrlElicitationRequiredException.kt new file mode 100644 index 0000000..f3da768 --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/UrlElicitationRequiredException.kt @@ -0,0 +1,65 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.decodeFromJsonElement +import kotlinx.serialization.json.encodeToJsonElement + +/** + * Payload carried by the [data][RPCError.data] of a [RPCError.ErrorCode.URL_ELICITATION_REQUIRED] error. + * + * @property elicitations The URL-mode elicitations that must be completed before the original request + * can succeed. Each entry is equivalent to an `elicitation/create` request. + */ +@Serializable +public data class UrlElicitationRequiredData(val elicitations: List) + +/** + * Signals that one or more URL-mode elicitations must be completed before a request can be processed. + * + * Serialized over the wire as a JSON-RPC error with code [RPCError.ErrorCode.URL_ELICITATION_REQUIRED] + * and [data][RPCError.data] holding the [elicitations]. A server throws this from a request handler to + * tell the client that a URL-mode elicitation is required; the client receives it from any request as a + * typed exception, equivalent to receiving an `elicitation/create` request. + * + * The client is responsible for obtaining explicit user consent and surfacing each [ElicitRequestURLParams] + * before navigation — the SDK never opens or validates URLs. + * + * Example client-side handling: + * ```kotlin + * try { + * client.callTool(name = "create_repo", arguments = args) + * } catch (e: UrlElicitationRequiredException) { + * for (elicitation in e.elicitations) { + * // Show elicitation.message and the target domain, then ask the user for consent. + * if (userConsents(elicitation.url)) openInBrowser(elicitation.url) + * // Await completion via Client.setElicitationCompleteHandler, then optionally retry the call. + * } + * } + * ``` + * + * @property elicitations The required URL-mode elicitations (always non-empty). + */ +public class UrlElicitationRequiredException( + public val elicitations: List, + message: String = defaultMessage(elicitations), +) : McpException( + code = RPCError.ErrorCode.URL_ELICITATION_REQUIRED, + message = message, + data = McpJson.encodeToJsonElement(UrlElicitationRequiredData(elicitations)), +) { + internal companion object { + private fun defaultMessage(elicitations: List): String = + if (elicitations.size > 1) "URL elicitations are required" else "URL elicitation is required" + + /** + * Reconstructs a [UrlElicitationRequiredException] from JSON-RPC error [data], or `null` when the + * payload is absent, malformed, or carries no elicitations. Never throws. + */ + fun fromDataOrNull(message: String, data: JsonElement): UrlElicitationRequiredException? = + runCatching { McpJson.decodeFromJsonElement(data).elicitations } + .getOrNull() + ?.takeIf { it.isNotEmpty() } + ?.let { UrlElicitationRequiredException(it, message) } + } +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.kt index cd78b58..ecb8c48 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.kt @@ -189,6 +189,15 @@ public data class ClientCapabilities( } } +/** + * Whether the client supports url-mode elicitation (out-of-band interaction via URL navigation). + * + * `false` when the client declared no elicitation capability at all, or only form mode (an empty + * [ClientCapabilities.Elicitation] is treated as form mode only). + */ +public val ClientCapabilities.Elicitation?.supportsUrl: Boolean + get() = this?.url != null + /** * Capabilities that a server may support. * diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.kt index a0ee74c..1bb73e6 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.kt @@ -133,6 +133,11 @@ public data class ElicitRequestFormParams( * Directs the user to an external URL for out-of-band interactions (e.g., OAuth flows, * payment processing, or entering sensitive credentials) that must not pass through the MCP client. * + * Handling the [url] is the responsibility of the host application: it should require HTTPS, clearly + * display the target domain, obtain explicit user consent, and open the URL in a secure browser context. + * The SDK neither opens, fetches, nor validates the URL. URLs must only appear in URL-mode requests — + * never render a URL from a form-mode request as a clickable link. + * * @property message The message explaining why the interaction is needed. * @property elicitationId A unique identifier for this elicitation. The client MUST treat * this ID as an opaque value. diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt index 7eefdb5..a9d465f 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt @@ -269,6 +269,13 @@ public data class RPCError(val code: Int, val message: String, val data: JsonEle /** Resource not found */ public const val RESOURCE_NOT_FOUND: Int = -32002 + /** + * A URL-mode elicitation must be completed before the request can be processed. + * The error [data][RPCError.data] carries the required URL-mode elicitations + * (see [UrlElicitationRequiredException]). + */ + public const val URL_ELICITATION_REQUIRED: Int = -32042 + // Standard JSON-RPC 2.0 error codes /** Invalid JSON was received */ diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CapabilitiesTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CapabilitiesTest.kt index 06949a6..3468aae 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CapabilitiesTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CapabilitiesTest.kt @@ -923,4 +923,24 @@ class CapabilitiesTest { assertEquals(EmptyJsonObject, capabilities.tasks?.requests?.sampling?.createMessage) assertEquals(EmptyJsonObject, capabilities.tasks?.requests?.elicitation?.create) } + + // ── Elicitation supportsUrl predicate ─────────────────────────────── + + @Test + fun `supportsUrl is false when no elicitation capability is declared`() { + val elicitation: ClientCapabilities.Elicitation? = null + elicitation.supportsUrl shouldBe false + } + + @Test + fun `supportsUrl is false for an empty form-only capability`() { + ClientCapabilities.Elicitation().supportsUrl shouldBe false + ClientCapabilities.Elicitation(form = EmptyJsonObject).supportsUrl shouldBe false + } + + @Test + fun `supportsUrl is true when url mode is declared`() { + ClientCapabilities.Elicitation(url = EmptyJsonObject).supportsUrl shouldBe true + ClientCapabilities.Elicitation(form = EmptyJsonObject, url = EmptyJsonObject).supportsUrl shouldBe true + } } diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ElicitationTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ElicitationTest.kt index 1a41863..bd5ef06 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ElicitationTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ElicitationTest.kt @@ -10,6 +10,7 @@ import io.modelcontextprotocol.kotlin.test.utils.verifyDeserialization import io.modelcontextprotocol.kotlin.test.utils.verifySerialization import kotlinx.serialization.json.boolean import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.encodeToJsonElement import kotlinx.serialization.json.int import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.put @@ -273,4 +274,86 @@ class ElicitationTest { ) } } + + // ── URLElicitationRequired error (-32042) ─────────────────────────── + + @Test + fun `UrlElicitationRequiredException carries code and serialized elicitations`() { + val elicitation = ElicitRequestURLParams( + message = "Authorize access to continue", + elicitationId = "550e8400-e29b-41d4-a716-446655440000", + url = "https://oauth.example.com/authorize", + ) + val exception = UrlElicitationRequiredException(listOf(elicitation)) + + exception.code shouldBe RPCError.ErrorCode.URL_ELICITATION_REQUIRED + exception.elicitations shouldBe listOf(elicitation) + + val data = exception.data.shouldNotBeNull() + McpJson.encodeToString(data) shouldEqualJson """ + { + "elicitations": [ + { + "message": "Authorize access to continue", + "elicitationId": "550e8400-e29b-41d4-a716-446655440000", + "url": "https://oauth.example.com/authorize", + "mode": "url" + } + ] + } + """.trimIndent() + } + + @Test + fun `fromError reconstructs typed exception for -32042 with valid data`() { + val data = McpJson.encodeToJsonElement( + UrlElicitationRequiredData( + listOf(ElicitRequestURLParams(message = "m", elicitationId = "id-1", url = "https://example.com/a")), + ), + ) + + val exception = McpException.fromError( + code = RPCError.ErrorCode.URL_ELICITATION_REQUIRED, + message = "needs auth", + data = data, + ) + + val typed = exception.shouldBeInstanceOf() + typed.message shouldBe "needs auth" + typed.elicitations.single().elicitationId shouldBe "id-1" + } + + @Test + fun `fromError returns plain McpException for unrelated code`() { + val exception = McpException.fromError( + code = RPCError.ErrorCode.INVALID_PARAMS, + message = "bad params", + data = null, + ) + (exception is UrlElicitationRequiredException) shouldBe false + exception.code shouldBe RPCError.ErrorCode.INVALID_PARAMS + } + + @Test + fun `fromError degrades to plain McpException for -32042 with malformed data`() { + val malformed = buildJsonObject { put("unexpected", "shape") } + + val exception = McpException.fromError( + code = RPCError.ErrorCode.URL_ELICITATION_REQUIRED, + message = "x", + data = malformed, + ) + + (exception is UrlElicitationRequiredException) shouldBe false + exception.code shouldBe RPCError.ErrorCode.URL_ELICITATION_REQUIRED + } + + @Test + fun `fromError degrades to plain McpException when elicitations empty`() { + val data = McpJson.encodeToJsonElement(UrlElicitationRequiredData(emptyList())) + + val exception = McpException.fromError(RPCError.ErrorCode.URL_ELICITATION_REQUIRED, "x", data) + + (exception is UrlElicitationRequiredException) shouldBe false + } } diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnection.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnection.kt index 9b4b2f3..343774b 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnection.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnection.kt @@ -25,6 +25,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.ResourceListChangedNotification import io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotification import io.modelcontextprotocol.kotlin.sdk.types.ServerNotification import io.modelcontextprotocol.kotlin.sdk.types.ToolListChangedNotification +import io.modelcontextprotocol.kotlin.sdk.types.supportsUrl private val logger = KotlinLogging.logger {} @@ -238,6 +239,11 @@ internal class ClientConnectionImpl(private val session: ServerSession) : Client override suspend fun createElicitation(request: ElicitRequest, options: RequestOptions?): ElicitResult { val params = request.params + if (params is ElicitRequestURLParams) { + require(session.clientCapabilities?.elicitation.supportsUrl) { + "Client did not advertise elicitation.url capability; cannot send a URL-mode elicitation." + } + } logger.debug { when (params) { is ElicitRequestFormParams -> @@ -299,6 +305,9 @@ internal class ClientConnectionImpl(private val session: ServerSession) : Client } override suspend fun sendElicitationComplete(notification: ElicitationCompleteNotification) { + require(session.clientCapabilities?.elicitation.supportsUrl) { + "Client did not advertise elicitation.url capability; cannot send an elicitation completion notification." + } logger.debug { "Sending elicitation complete notification for: ${notification.params.elicitationId}" } notification(notification) } diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt index 1b7bfe7..c0d3e84 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt @@ -46,6 +46,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.ToolAnnotations import io.modelcontextprotocol.kotlin.sdk.types.ToolExecution import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema import io.modelcontextprotocol.kotlin.sdk.types.UnsubscribeRequest +import io.modelcontextprotocol.kotlin.sdk.types.UrlElicitationRequiredException import io.modelcontextprotocol.kotlin.sdk.utils.MatchResult import io.modelcontextprotocol.kotlin.sdk.utils.PathSegmentTemplateMatcher import io.modelcontextprotocol.kotlin.sdk.utils.ResourceTemplateMatcher @@ -651,6 +652,9 @@ public open class Server( } } catch (e: CancellationException) { throw e + } catch (e: UrlElicitationRequiredException) { + // Surface a required URL-mode elicitation as a JSON-RPC error (-32042) + throw e } catch (e: Exception) { logger.error(e) { "Error executing tool ${requestParams.name}" } CallToolResult( From c5abfe493a1967c7d09c8c7c238d6f6fab2ba691 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Jun 2026 20:31:48 +0000 Subject: [PATCH 325/449] chore(deps): bump @types/node from 25.9.3 to 26.0.0 in /integration-test/src/jvmTest/typescript in the all-dependencies group across 1 directory (#853) Bumps the all-dependencies group with 1 update in the /integration-test/src/jvmTest/typescript directory: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node). Updates `@types/node` from 25.9.3 to 26.0.0
    Commits

    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../src/jvmTest/typescript/package-lock.json | 16 ++++++++-------- .../src/jvmTest/typescript/package.json | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index de65fb2..7e3293c 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -15,7 +15,7 @@ "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^25.9.3", + "@types/node": "^26.0.0", "cors": "^2.8.6", "tsx": "^4.22.4", "typescript": "^6.0.3" @@ -579,13 +579,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.9.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.3.tgz", - "integrity": "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==", + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.0.tgz", + "integrity": "sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": ">=7.24.0 <7.24.7" + "undici-types": "~8.3.0" } }, "node_modules/@types/qs": { @@ -1727,9 +1727,9 @@ } }, "node_modules/undici-types": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", - "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", "dev": true, "license": "MIT" }, diff --git a/integration-test/src/jvmTest/typescript/package.json b/integration-test/src/jvmTest/typescript/package.json index 629b3bb..00f0961 100644 --- a/integration-test/src/jvmTest/typescript/package.json +++ b/integration-test/src/jvmTest/typescript/package.json @@ -16,7 +16,7 @@ "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^25.9.3", + "@types/node": "^26.0.0", "cors": "^2.8.6", "tsx": "^4.22.4", "typescript": "^6.0.3" From bb265cc6e5df7d0ebf48dbdbc16a914046179da7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Jun 2026 23:09:37 +0200 Subject: [PATCH 326/449] chore(deps): bump hono from 4.12.23 to 4.12.27 in /integration-test/src/jvmTest/typescript (#837) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [hono](https://github.com/honojs/hono) from 4.12.23 to 4.12.27.
    Release notes

    Sourced from hono's releases.

    v4.12.27

    Security fixes

    This release includes fixes for the following security issues:

    hono/jsx does not isolate context per request

    Affects: hono/jsx, hono/jsx-renderer. During SSR, context was stored process-wide instead of per request, so useContext()/useRequestContext() read after an await in an async component could return another concurrent request's value — leading to cross-request data disclosure or authorization checks against the wrong request. GHSA-hvrm-45r6-mjfj

    Server-Side XSS via JSX escaping bypass in cx()

    Affects: hono/css. cx() marked its composed class name as already-escaped without escaping the input, so untrusted input passed as a class name could break out of the JSX class attribute during SSR and inject markup (XSS). GHSA-w62v-xxxg-mg59

    API Gateway v1 adapter can drop a repeated request header value

    Affects: hono/aws-lambda. The API Gateway v1 (and VPC Lattice) adapter de-duplicated repeated header values by substring instead of exact match, dropping a value that is a substring of another (e.g. 203.0.113.1 dropped when 203.0.113.10 is present) — affecting logic such as X-Forwarded-For-based IP restriction. GHSA-xgm2-5f3f-mvvc


    Users of hono/jsx/hono/jsx-renderer, hono/css (cx()), or the hono/aws-lambda API Gateway v1 / VPC Lattice adapters are encouraged to upgrade.

    v4.12.26

    What's Changed

    Full Changelog: https://github.com/honojs/hono/compare/v4.12.25...v4.12.26

    v4.12.25

    Security fixes

    This release includes fixes for the following security issues:

    CORS Middleware reflects any Origin with credentials when origin defaults to the wildcard

    Affects: hono/cors. Fixes the wildcard origin reflecting the request Origin and sending Access-Control-Allow-Credentials: true when credentials: true is set without an explicit origin, where any site a logged-in user visited could make credentialed cross-origin requests and read responses from cookie-authenticated endpoints. GHSA-88fw-hqm2-52qc

    Body Limit Middleware can be bypassed on AWS Lambda by understating Content-Length

    Affects: hono/body-limit on AWS Lambda (hono/aws-lambda, hono/lambda-edge). Fixes the request being built with the client-declared Content-Length while the body is delivered fully buffered, where a client could declare a small Content-Length with a much larger body and slip past the configured size limit. GHSA-rv63-4mwf-qqc2

    Path traversal in serve-static on Windows via encoded backslash (%5C)

    Affects: serveStatic on Windows (Node, Bun, Deno adapters). Fixes the path guard allowing a lone backslash, where an encoded backslash (%5C) decoded to \ was treated as a separator by the Windows path resolver, letting a single URL segment escape into a middleware-guarded subtree. GHSA-wwfh-h76j-fc44

    AWS Lambda adapter merges multiple Set-Cookie headers into one value, dropping cookies on ALB single-header and Lattice

    ... (truncated)

    Commits
    • 97c6fe1 4.12.27
    • aa92177 Merge commit from fork
    • cd3f6f7 Merge commit from fork
    • d4853a8 fix(jsx): make merged context-isolation tests pass tsc type check (#5037)
    • 6735fea fix(jsx): cast awaitedFallback through unknown to fix Deno type check (#5036)
    • fab3b13 Merge commit from fork
    • 9f0dadf ci: use npm Staged publishing (#5035)
    • 27b7992 4.12.26
    • d29982c chore: replace arg and glob with Bun native APIs in build script
    • 16215d5 chore: remove unused devcontainer and gitpod configs (#5029)
    • Additional commits viewable in compare view
    Maintainer changes

    This version was pushed to npm by GitHub Actions, a new releaser for hono since your current version.


    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- integration-test/src/jvmTest/typescript/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index 7e3293c..0fa51a9 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -1184,9 +1184,9 @@ } }, "node_modules/hono": { - "version": "4.12.23", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.23.tgz", - "integrity": "sha512-eIaZ9qDgu7XV0pxOCrg7/WhnQ6Ivm22UcxhXx/A3dcbqbbYgBEkc6e/J/s7j2tS96zoB0S9VBdLwQNCWwUo4LA==", + "version": "4.12.27", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.27.tgz", + "integrity": "sha512-1yrb/+w6HWQJrUCLkJ2IF5jNIPvvFkblV5RNOYl6bV+OA6p9GLcMpHFFGTosSvHvcAUibuUukRqhlYI4z32C7Q==", "license": "MIT", "engines": { "node": ">=16.9.0" From 2edcb51d799f9c2a98082485dc189d457a35c27f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Jun 2026 15:31:46 +0200 Subject: [PATCH 327/449] chore(deps): bump the other-dependencies group across 5 directories with 3 updates (#854) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the other-dependencies group with 2 updates in the /samples/kotlin-mcp-client directory: [gradle-wrapper](https://github.com/gradle/gradle) and [com.anthropic:anthropic-java](https://github.com/anthropics/anthropic-sdk-java). Bumps the other-dependencies group with 1 update in the /samples/kotlin-mcp-server directory: [gradle-wrapper](https://github.com/gradle/gradle). Bumps the other-dependencies group with 2 updates in the /samples/kotlinlang-mcp-server directory: [gradle-wrapper](https://github.com/gradle/gradle) and [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback). Bumps the other-dependencies group with 1 update in the /samples/simple-streamable-server directory: [gradle-wrapper](https://github.com/gradle/gradle). Bumps the other-dependencies group with 1 update in the /samples/weather-stdio-server directory: [gradle-wrapper](https://github.com/gradle/gradle). Updates `gradle-wrapper` from 9.5.1 to 9.6.0
    Release notes

    Sourced from gradle-wrapper's releases.

    9.6.0

    The Gradle team is excited to announce Gradle 9.6.0.

    Here are the highlights of this release:

    • Improved Configuration Cache hit rates
    • Additional CLI rendering options
    • Important project hierarchy lookup deprecations

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: Aharnish Solanki, Benedikt Johannes, Devendra Reddy Pennabadi, Dmytro Rodionov, Dreeam, Elías Hernández Rodríguez, Eng Zer Jun, FinlayRJW, Kamal Kansal, Marcono1234, Nelson Osacky, Philip Wedemann, Ravi, Roberto Perez Alcolea, Ryan Schmitt, Sebastian Schuberth, seunghun.ham, sk-reddy17, Suvrat Acharya, Vedant Madane.

    Upgrade instructions

    Switch your build to use Gradle 9.6.0 by updating your wrapper:

    ./gradlew :wrapper --gradle-version=9.6.0 &&
    ./gradlew :wrapper
    

    See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

    For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

    Reporting problems

    If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

    We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

    9.6.0 RC3

    ... (truncated)

    Commits
    • 3f750f0 Update distro size for Gradle 9.6.0 release (#38243)
    • ae93cfa update distro size
    • f7e22b5 Update Gradle wrapper to version 9.6.0-rc-3 (#38227)
    • 71a8eb9 Update Gradle wrapper to version 9.6.0-rc-3
    • 70a8745 Prepare release notes for Gradle 9.6.0RC3 (#38220)
    • 9706522 some final polishing for release notes
    • af308eb Restore GradleInternal.getRootProject overload for binary compatibility (#38214)
    • 239361b Restore GradleInternal.getRootProject overload for binary compatibility
    • 896dc44 Update Gradle wrapper to version 9.6.0-rc-2 (#38187)
    • 2d4ec25 Update Gradle wrapper to version 9.6.0-rc-2
    • Additional commits viewable in compare view

    Updates `com.anthropic:anthropic-java` from 2.40.1 to 2.43.0
    Release notes

    Sourced from com.anthropic:anthropic-java's releases.

    v2.43.0

    2.43.0 (2026-06-18)

    Full Changelog: v2.42.0...v2.43.0

    Features

    • helpers: introduce x-stainless-helper telemetry + tag the refusal-fallback interceptor (#91) (a14925b)

    v2.42.0

    2.42.0 (2026-06-18)

    Full Changelog: v2.41.1...v2.42.0

    Features

    • api: add support for new code_execution_20260120 tool (1a1a6b9)

    v2.41.1

    2.41.1 (2026-06-18)

    Full Changelog: v2.41.0...v2.41.1

    Chores

    Build System

    v2.41.0

    2.41.0 (2026-06-17)

    Full Changelog: v2.40.1...v2.41.0

    Bug Fixes

    • client: bad model definitions (96787c6)
    • foundry: align baseUrl contract with the other SDKs (/anthropic in baseUrl, no path rewrite) (#84) (e651d93)

    Chores

    • api: remove retired models from API and SDKs (da1c93d)
    • upgrade all deps (#73) (e57f1b5)

    Styles

    ... (truncated)

    Changelog

    Sourced from com.anthropic:anthropic-java's changelog.

    2.43.0 (2026-06-18)

    Full Changelog: v2.42.0...v2.43.0

    Features

    • helpers: introduce x-stainless-helper telemetry + tag the refusal-fallback interceptor (#91) (a14925b)

    2.42.0 (2026-06-18)

    Full Changelog: v2.41.1...v2.42.0

    Features

    • api: add support for new code_execution_20260120 tool (1a1a6b9)

    2.41.1 (2026-06-18)

    Full Changelog: v2.41.0...v2.41.1

    Chores

    Build System

    2.41.0 (2026-06-17)

    Full Changelog: v2.40.1...v2.41.0

    Bug Fixes

    • client: bad model definitions (96787c6)
    • foundry: align baseUrl contract with the other SDKs (/anthropic in baseUrl, no path rewrite) (#84) (e651d93)

    Chores

    • api: remove retired models from API and SDKs (da1c93d)
    • upgrade all deps (#73) (e57f1b5)

    Styles

    ... (truncated)

    Commits
    • 8e217ec release: 2.43.0
    • 8f4d5be feat(helpers): introduce x-stainless-helper telemetry + tag the refusal-fallb...
    • 60e15dd release: 2.42.0
    • e430bfb feat(api): add support for new code_execution_20260120 tool
    • 3c27033 release: 2.41.1
    • 1d77d61 build: fix signing for publishing (#90)
    • 78e23a5 chore: fix release runs-on (#354)
    • 946fe2a release: 2.41.0
    • 536e177 style: apply formatting (#88)
    • f719e12 fix(client): bad model definitions
    • Additional commits viewable in compare view

    Updates `gradle-wrapper` from 9.5.1 to 9.6.0
    Release notes

    Sourced from gradle-wrapper's releases.

    9.6.0

    The Gradle team is excited to announce Gradle 9.6.0.

    Here are the highlights of this release:

    • Improved Configuration Cache hit rates
    • Additional CLI rendering options
    • Important project hierarchy lookup deprecations

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: Aharnish Solanki, Benedikt Johannes, Devendra Reddy Pennabadi, Dmytro Rodionov, Dreeam, Elías Hernández Rodríguez, Eng Zer Jun, FinlayRJW, Kamal Kansal, Marcono1234, Nelson Osacky, Philip Wedemann, Ravi, Roberto Perez Alcolea, Ryan Schmitt, Sebastian Schuberth, seunghun.ham, sk-reddy17, Suvrat Acharya, Vedant Madane.

    Upgrade instructions

    Switch your build to use Gradle 9.6.0 by updating your wrapper:

    ./gradlew :wrapper --gradle-version=9.6.0 &&
    ./gradlew :wrapper
    

    See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

    For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

    Reporting problems

    If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

    We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

    9.6.0 RC3

    ... (truncated)

    Commits
    • 3f750f0 Update distro size for Gradle 9.6.0 release (#38243)
    • ae93cfa update distro size
    • f7e22b5 Update Gradle wrapper to version 9.6.0-rc-3 (#38227)
    • 71a8eb9 Update Gradle wrapper to version 9.6.0-rc-3
    • 70a8745 Prepare release notes for Gradle 9.6.0RC3 (#38220)
    • 9706522 some final polishing for release notes
    • af308eb Restore GradleInternal.getRootProject overload for binary compatibility (#38214)
    • 239361b Restore GradleInternal.getRootProject overload for binary compatibility
    • 896dc44 Update Gradle wrapper to version 9.6.0-rc-2 (#38187)
    • 2d4ec25 Update Gradle wrapper to version 9.6.0-rc-2
    • Additional commits viewable in compare view

    Updates `gradle-wrapper` from 9.5.1 to 9.6.0
    Release notes

    Sourced from gradle-wrapper's releases.

    9.6.0

    The Gradle team is excited to announce Gradle 9.6.0.

    Here are the highlights of this release:

    • Improved Configuration Cache hit rates
    • Additional CLI rendering options
    • Important project hierarchy lookup deprecations

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: Aharnish Solanki, Benedikt Johannes, Devendra Reddy Pennabadi, Dmytro Rodionov, Dreeam, Elías Hernández Rodríguez, Eng Zer Jun, FinlayRJW, Kamal Kansal, Marcono1234, Nelson Osacky, Philip Wedemann, Ravi, Roberto Perez Alcolea, Ryan Schmitt, Sebastian Schuberth, seunghun.ham, sk-reddy17, Suvrat Acharya, Vedant Madane.

    Upgrade instructions

    Switch your build to use Gradle 9.6.0 by updating your wrapper:

    ./gradlew :wrapper --gradle-version=9.6.0 &&
    ./gradlew :wrapper
    

    See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

    For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

    Reporting problems

    If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

    We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

    9.6.0 RC3

    ... (truncated)

    Commits
    • 3f750f0 Update distro size for Gradle 9.6.0 release (#38243)
    • ae93cfa update distro size
    • f7e22b5 Update Gradle wrapper to version 9.6.0-rc-3 (#38227)
    • 71a8eb9 Update Gradle wrapper to version 9.6.0-rc-3
    • 70a8745 Prepare release notes for Gradle 9.6.0RC3 (#38220)
    • 9706522 some final polishing for release notes
    • af308eb Restore GradleInternal.getRootProject overload for binary compatibility (#38214)
    • 239361b Restore GradleInternal.getRootProject overload for binary compatibility
    • 896dc44 Update Gradle wrapper to version 9.6.0-rc-2 (#38187)
    • 2d4ec25 Update Gradle wrapper to version 9.6.0-rc-2
    • Additional commits viewable in compare view

    Updates `gradle-wrapper` from 9.5.1 to 9.6.0
    Release notes

    Sourced from gradle-wrapper's releases.

    9.6.0

    The Gradle team is excited to announce Gradle 9.6.0.

    Here are the highlights of this release:

    • Improved Configuration Cache hit rates
    • Additional CLI rendering options
    • Important project hierarchy lookup deprecations

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: Aharnish Solanki, Benedikt Johannes, Devendra Reddy Pennabadi, Dmytro Rodionov, Dreeam, Elías Hernández Rodríguez, Eng Zer Jun, FinlayRJW, Kamal Kansal, Marcono1234, Nelson Osacky, Philip Wedemann, Ravi, Roberto Perez Alcolea, Ryan Schmitt, Sebastian Schuberth, seunghun.ham, sk-reddy17, Suvrat Acharya, Vedant Madane.

    Upgrade instructions

    Switch your build to use Gradle 9.6.0 by updating your wrapper:

    ./gradlew :wrapper --gradle-version=9.6.0 &&
    ./gradlew :wrapper
    

    See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

    For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

    Reporting problems

    If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

    We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

    9.6.0 RC3

    ... (truncated)

    Commits
    • 3f750f0 Update distro size for Gradle 9.6.0 release (#38243)
    • ae93cfa update distro size
    • f7e22b5 Update Gradle wrapper to version 9.6.0-rc-3 (#38227)
    • 71a8eb9 Update Gradle wrapper to version 9.6.0-rc-3
    • 70a8745 Prepare release notes for Gradle 9.6.0RC3 (#38220)
    • 9706522 some final polishing for release notes
    • af308eb Restore GradleInternal.getRootProject overload for binary compatibility (#38214)
    • 239361b Restore GradleInternal.getRootProject overload for binary compatibility
    • 896dc44 Update Gradle wrapper to version 9.6.0-rc-2 (#38187)
    • 2d4ec25 Update Gradle wrapper to version 9.6.0-rc-2
    • Additional commits viewable in compare view

    Updates `gradle-wrapper` from 9.5.1 to 9.6.0
    Release notes

    Sourced from gradle-wrapper's releases.

    9.6.0

    The Gradle team is excited to announce Gradle 9.6.0.

    Here are the highlights of this release:

    • Improved Configuration Cache hit rates
    • Additional CLI rendering options
    • Important project hierarchy lookup deprecations

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: Aharnish Solanki, Benedikt Johannes, Devendra Reddy Pennabadi, Dmytro Rodionov, Dreeam, Elías Hernández Rodríguez, Eng Zer Jun, FinlayRJW, Kamal Kansal, Marcono1234, Nelson Osacky, Philip Wedemann, Ravi, Roberto Perez Alcolea, Ryan Schmitt, Sebastian Schuberth, seunghun.ham, sk-reddy17, Suvrat Acharya, Vedant Madane.

    Upgrade instructions

    Switch your build to use Gradle 9.6.0 by updating your wrapper:

    ./gradlew :wrapper --gradle-version=9.6.0 &&
    ./gradlew :wrapper
    

    See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

    For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

    Reporting problems

    If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

    We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

    9.6.0 RC3

    ... (truncated)

    Commits
    • 3f750f0 Update distro size for Gradle 9.6.0 release (#38243)
    • ae93cfa update distro size
    • f7e22b5 Update Gradle wrapper to version 9.6.0-rc-3 (#38227)
    • 71a8eb9 Update Gradle wrapper to version 9.6.0-rc-3
    • 70a8745 Prepare release notes for Gradle 9.6.0RC3 (#38220)
    • 9706522 some final polishing for release notes
    • af308eb Restore GradleInternal.getRootProject overload for binary compatibility (#38214)
    • 239361b Restore GradleInternal.getRootProject overload for binary compatibility
    • 896dc44 Update Gradle wrapper to version 9.6.0-rc-2 (#38187)
    • 2d4ec25 Update Gradle wrapper to version 9.6.0-rc-2
    • Additional commits viewable in compare view

    Updates `gradle-wrapper` from 9.5.1 to 9.6.0
    Release notes

    Sourced from gradle-wrapper's releases.

    9.6.0

    The Gradle team is excited to announce Gradle 9.6.0.

    Here are the highlights of this release:

    • Improved Configuration Cache hit rates
    • Additional CLI rendering options
    • Important project hierarchy lookup deprecations

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: Aharnish Solanki, Benedikt Johannes, Devendra Reddy Pennabadi, Dmytro Rodionov, Dreeam, Elías Hernández Rodríguez, Eng Zer Jun, FinlayRJW, Kamal Kansal, Marcono1234, Nelson Osacky, Philip Wedemann, Ravi, Roberto Perez Alcolea, Ryan Schmitt, Sebastian Schuberth, seunghun.ham, sk-reddy17, Suvrat Acharya, Vedant Madane.

    Upgrade instructions

    Switch your build to use Gradle 9.6.0 by updating your wrapper:

    ./gradlew :wrapper --gradle-version=9.6.0 &&
    ./gradlew :wrapper
    

    See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

    For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

    Reporting problems

    If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

    We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

    9.6.0 RC3

    ... (truncated)

    Commits
    • 3f750f0 Update distro size for Gradle 9.6.0 release (#38243)
    • ae93cfa update distro size
    • f7e22b5 Update Gradle wrapper to version 9.6.0-rc-3 (#38227)
    • 71a8eb9 Update Gradle wrapper to version 9.6.0-rc-3
    • 70a8745 Prepare release notes for Gradle 9.6.0RC3 (#38220)
    • 9706522 some final polishing for release notes
    • af308eb Restore GradleInternal.getRootProject overload for binary compatibility (#38214)
    • 239361b Restore GradleInternal.getRootProject overload for binary compatibility
    • 896dc44 Update Gradle wrapper to version 9.6.0-rc-2 (#38187)
    • 2d4ec25 Update Gradle wrapper to version 9.6.0-rc-2
    • Additional commits viewable in compare view

    Updates `gradle-wrapper` from 9.5.1 to 9.6.0
    Release notes

    Sourced from gradle-wrapper's releases.

    9.6.0

    The Gradle team is excited to announce Gradle 9.6.0.

    Here are the highlights of this release:

    • Improved Configuration Cache hit rates
    • Additional CLI rendering options
    • Important project hierarchy lookup deprecations

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: Aharnish Solanki, Benedikt Johannes, Devendra Reddy Pennabadi, Dmytro Rodionov, Dreeam, Elías Hernández Rodríguez, Eng Zer Jun, FinlayRJW, Kamal Kansal, Marcono1234, Nelson Osacky, Philip Wedemann, Ravi, Roberto Perez Alcolea, Ryan Schmitt, Sebastian Schuberth, seunghun.ham, sk-reddy17, Suvrat Acharya, Vedant Madane.

    Upgrade instructions

    Switch your build to use Gradle 9.6.0 by updating your wrapper:

    ./gradlew :wrapper --gradle-version=9.6.0 &&
    ./gradlew :wrapper
    

    See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

    For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

    Reporting problems

    If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

    We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

    9.6.0 RC3

    ... (truncated)

    Commits
    • 3f750f0 Update distro size for Gradle 9.6.0 release (#38243)
    • ae93cfa update distro size
    • f7e22b5 Update Gradle wrapper to version 9.6.0-rc-3 (#38227)
    • 71a8eb9 Update Gradle wrapper to version 9.6.0-rc-3
    • 70a8745 Prepare release notes for Gradle 9.6.0RC3 (#38220)
    • 9706522 some final polishing for release notes
    • af308eb Restore GradleInternal.getRootProject overload for binary compatibility (#38214)
    • 239361b Restore GradleInternal.getRootProject overload for binary compatibility
    • 896dc44 Update Gradle wrapper to version 9.6.0-rc-2 (#38187)
    • 2d4ec25 Update Gradle wrapper to version 9.6.0-rc-2
    • Additional commits viewable in compare view

    Updates `gradle-wrapper` from 9.5.1 to 9.6.0
    Release notes

    Sourced from gradle-wrapper's releases.

    9.6.0

    The Gradle team is excited to announce Gradle 9.6.0.

    Here are the highlights of this release:

    • Improved Configuration Cache hit rates
    • Additional CLI rendering options
    • Important project hierarchy lookup deprecations

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: Aharnish Solanki, Benedikt Johannes, Devendra Reddy Pennabadi, Dmytro Rodionov, Dreeam, Elías Hernández Rodríguez, Eng Zer Jun, FinlayRJW, Kamal Kansal, Marcono1234, Nelson Osacky, Philip Wedemann, Ravi, Roberto Perez Alcolea, Ryan Schmitt, Sebastian Schuberth, seunghun.ham, sk-reddy17, Suvrat Acharya, Vedant Madane.

    Upgrade instructions

    Switch your build to use Gradle 9.6.0 by updating your wrapper:

    ./gradlew :wrapper --gradle-version=9.6.0 &&
    ./gradlew :wrapper
    

    See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

    For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

    Reporting problems

    If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

    We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

    9.6.0 RC3

    ... (truncated)

    Commits
    • 3f750f0 Update distro size for Gradle 9.6.0 release (#38243)
    • ae93cfa update distro size
    • f7e22b5 Update Gradle wrapper to version 9.6.0-rc-3 (#38227)
    • 71a8eb9 Update Gradle wrapper to version 9.6.0-rc-3
    • 70a8745 Prepare release notes for Gradle 9.6.0RC3 (#38220)
    • 9706522 some final polishing for release notes
    • af308eb Restore GradleInternal.getRootProject overload for binary compatibility (#38214)
    • 239361b Restore GradleInternal.getRootProject overload for binary compatibility
    • 896dc44 Update Gradle wrapper to version 9.6.0-rc-2 (#38187)
    • 2d4ec25 Update Gradle wrapper to version 9.6.0-rc-2
    • Additional commits viewable in compare view

    Updates `gradle-wrapper` from 9.5.1 to 9.6.0
    Release notes

    Sourced from gradle-wrapper's releases.

    9.6.0

    The Gradle team is excited to announce Gradle 9.6.0.

    Here are the highlights of this release:

    • Improved Configuration Cache hit rates
    • Additional CLI rendering options
    • Important project hierarchy lookup deprecations

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: Aharnish Solanki, Benedikt Johannes, Devendra Reddy Pennabadi, Dmytro Rodionov, Dreeam, Elías Hernández Rodríguez, Eng Zer Jun, FinlayRJW, Kamal Kansal, Marcono1234, Nelson Osacky, Philip Wedemann, Ravi, Roberto Perez Alcolea, Ryan Schmitt, Sebastian Schuberth, seunghun.ham, sk-reddy17, Suvrat Acharya, Vedant Madane.

    Upgrade instructions

    Switch your build to use Gradle 9.6.0 by updating your wrapper:

    ./gradlew :wrapper --gradle-version=9.6.0 &&
    ./gradlew :wrapper
    

    See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

    For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

    Reporting problems

    If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

    We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

    9.6.0 RC3

    ... (truncated)

    Commits
    • 3f750f0 Update distro size for Gradle 9.6.0 release (#38243)
    • ae93cfa update distro size
    • f7e22b5 Update Gradle wrapper to version 9.6.0-rc-3 (#38227)
    • 71a8eb9 Update Gradle wrapper to version 9.6.0-rc-3
    • 70a8745 Prepare release notes for Gradle 9.6.0RC3 (#38220)
    • 9706522 some final polishing for release notes
    • af308eb Restore GradleInternal.getRootProject overload for binary compatibility (#38214)
    • 239361b Restore GradleInternal.getRootProject overload for binary compatibility
    • 896dc44 Update Gradle wrapper to version 9.6.0-rc-2 (#38187)
    • 2d4ec25 Update Gradle wrapper to version 9.6.0-rc-2
    • Additional commits viewable in compare view

    Updates `gradle-wrapper` from 9.5.1 to 9.6.0
    Release notes

    Sourced from gradle-wrapper's releases.

    9.6.0

    The Gradle team is excited to announce Gradle 9.6.0.

    Here are the highlights of this release:

    • Improved Configuration Cache hit rates
    • Additional CLI rendering options
    • Important project hierarchy lookup deprecations

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: Aharnish Solanki, Benedikt Johannes, Devendra Reddy Pennabadi, Dmytro Rodionov, Dreeam, Elías Hernández Rodríguez, Eng Zer Jun, FinlayRJW, Kamal Kansal, Marcono1234, Nelson Osacky, Philip Wedemann, Ravi, Roberto Perez Alcolea, Ryan Schmitt, Sebastian Schuberth, seunghun.ham, sk-reddy17, Suvrat Acharya, Vedant Madane.

    Upgrade instructions

    Switch your build to use Gradle 9.6.0 by updating your wrapper:

    ./gradlew :wrapper --gradle-version=9.6.0 &&
    ./gradlew :wrapper
    

    See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

    For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

    Reporting problems

    If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

    We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

    9.6.0 RC3

    ... (truncated)

    Commits
    • 3f750f0 Update distro size for Gradle 9.6.0 release (#38243)
    • ae93cfa update distro size
    • f7e22b5 Update Gradle wrapper to version 9.6.0-rc-3 (#38227)
    • 71a8eb9 Update Gradle wrapper to version 9.6.0-rc-3
    • 70a8745 Prepare release notes for Gradle 9.6.0RC3 (#38220)
    • 9706522 some final polishing for release notes
    • af308eb Restore GradleInternal.getRootProject overload for binary compatibility (#38214)
    • 239361b Restore GradleInternal.getRootProject overload for binary compatibility
    • 896dc44 Update Gradle wrapper to version 9.6.0-rc-2 (#38187)
    • 2d4ec25 Update Gradle wrapper to version 9.6.0-rc-2
    • Additional commits viewable in compare view

    Updates `gradle-wrapper` from 9.5.1 to 9.6.0
    Release notes

    Sourced from gradle-wrapper's releases.

    9.6.0

    The Gradle team is excited to announce Gradle 9.6.0.

    Here are the highlights of this release:

    • Improved Configuration Cache hit rates
    • Additional CLI rendering options
    • Important project hierarchy lookup deprecations

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: Aharnish Solanki, Benedikt Johannes, Devendra Reddy Pennabadi, Dmytro Rodionov, Dreeam, Elías Hernández Rodríguez, Eng Zer Jun, FinlayRJW, Kamal Kansal, Marcono1234, Nelson Osacky, Philip Wedemann, Ravi, Roberto Perez Alcolea, Ryan Schmitt, Sebastian Schuberth, seunghun.ham, sk-reddy17, Suvrat Acharya, Vedant Madane.

    Upgrade instructions

    Switch your build to use Gradle 9.6.0 by updating your wrapper:

    ./gradlew :wrapper --gradle-version=9.6.0 &&
    ./gradlew :wrapper
    

    See the Gradle 9.x upgrade guide to learn about deprecations, breaking ... _Description has been truncated_ Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-client/gradle/libs.versions.toml | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- samples/kotlin-mcp-client/gradlew | 4 ++-- samples/kotlin-mcp-client/gradlew.bat | 4 ++-- .../gradle/wrapper/gradle-wrapper.properties | 2 +- samples/kotlin-mcp-server/gradlew | 4 ++-- samples/kotlin-mcp-server/gradlew.bat | 4 ++-- samples/kotlinlang-mcp-server/gradle/libs.versions.toml | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- samples/kotlinlang-mcp-server/gradlew | 4 ++-- samples/kotlinlang-mcp-server/gradlew.bat | 4 ++-- .../gradle/wrapper/gradle-wrapper.properties | 2 +- samples/simple-streamable-server/gradlew | 4 ++-- samples/simple-streamable-server/gradlew.bat | 4 ++-- .../gradle/wrapper/gradle-wrapper.properties | 2 +- samples/weather-stdio-server/gradlew | 4 ++-- samples/weather-stdio-server/gradlew.bat | 4 ++-- 17 files changed, 27 insertions(+), 27 deletions(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index 033fbdc..7479186 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -anthropic = "2.40.1" +anthropic = "2.43.0" kotlin = "2.3.21" ktor = "3.3.3" mcp-kotlin = "0.13.0" diff --git a/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties b/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties index df6a6ad..eb84db6 100644 --- a/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties +++ b/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.0-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 diff --git a/samples/kotlin-mcp-client/gradlew b/samples/kotlin-mcp-client/gradlew index b9bb139..249efbb 100755 --- a/samples/kotlin-mcp-client/gradlew +++ b/samples/kotlin-mcp-client/gradlew @@ -20,7 +20,7 @@ ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. # # Important for running: # @@ -29,7 +29,7 @@ # bash, then to run this script, type that shell name before the whole # command line, like: # -# ksh Gradle +# ksh gradlew # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: diff --git a/samples/kotlin-mcp-client/gradlew.bat b/samples/kotlin-mcp-client/gradlew.bat index aa5f10b..8508ef6 100644 --- a/samples/kotlin-mcp-client/gradlew.bat +++ b/samples/kotlin-mcp-client/gradlew.bat @@ -19,7 +19,7 @@ @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## @@ -72,7 +72,7 @@ echo location of your Java installation. 1>&2 -@rem Execute Gradle +@rem Execute gradlew @rem endlocal doesn't take effect until after the line is parsed and variables are expanded @rem which allows us to clear the local environment before executing the java command endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel diff --git a/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties b/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties index df6a6ad..eb84db6 100644 --- a/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties +++ b/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.0-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 diff --git a/samples/kotlin-mcp-server/gradlew b/samples/kotlin-mcp-server/gradlew index b9bb139..249efbb 100755 --- a/samples/kotlin-mcp-server/gradlew +++ b/samples/kotlin-mcp-server/gradlew @@ -20,7 +20,7 @@ ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. # # Important for running: # @@ -29,7 +29,7 @@ # bash, then to run this script, type that shell name before the whole # command line, like: # -# ksh Gradle +# ksh gradlew # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: diff --git a/samples/kotlin-mcp-server/gradlew.bat b/samples/kotlin-mcp-server/gradlew.bat index aa5f10b..8508ef6 100644 --- a/samples/kotlin-mcp-server/gradlew.bat +++ b/samples/kotlin-mcp-server/gradlew.bat @@ -19,7 +19,7 @@ @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## @@ -72,7 +72,7 @@ echo location of your Java installation. 1>&2 -@rem Execute Gradle +@rem Execute gradlew @rem endlocal doesn't take effect until after the line is parsed and variables are expanded @rem which allows us to clear the local environment before executing the java command endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel diff --git a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml index fdef6d3..69dfe7e 100644 --- a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml @@ -4,7 +4,7 @@ ktor = "3.3.3" mcp-kotlin = "0.13.0" serialization = "1.11.0" coroutines = "1.11.0" -logback = "1.5.34" +logback = "1.5.35" shadow = "9.4.2" [libraries] diff --git a/samples/kotlinlang-mcp-server/gradle/wrapper/gradle-wrapper.properties b/samples/kotlinlang-mcp-server/gradle/wrapper/gradle-wrapper.properties index df6a6ad..eb84db6 100644 --- a/samples/kotlinlang-mcp-server/gradle/wrapper/gradle-wrapper.properties +++ b/samples/kotlinlang-mcp-server/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.0-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 diff --git a/samples/kotlinlang-mcp-server/gradlew b/samples/kotlinlang-mcp-server/gradlew index b9bb139..249efbb 100755 --- a/samples/kotlinlang-mcp-server/gradlew +++ b/samples/kotlinlang-mcp-server/gradlew @@ -20,7 +20,7 @@ ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. # # Important for running: # @@ -29,7 +29,7 @@ # bash, then to run this script, type that shell name before the whole # command line, like: # -# ksh Gradle +# ksh gradlew # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: diff --git a/samples/kotlinlang-mcp-server/gradlew.bat b/samples/kotlinlang-mcp-server/gradlew.bat index aa5f10b..8508ef6 100644 --- a/samples/kotlinlang-mcp-server/gradlew.bat +++ b/samples/kotlinlang-mcp-server/gradlew.bat @@ -19,7 +19,7 @@ @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## @@ -72,7 +72,7 @@ echo location of your Java installation. 1>&2 -@rem Execute Gradle +@rem Execute gradlew @rem endlocal doesn't take effect until after the line is parsed and variables are expanded @rem which allows us to clear the local environment before executing the java command endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel diff --git a/samples/simple-streamable-server/gradle/wrapper/gradle-wrapper.properties b/samples/simple-streamable-server/gradle/wrapper/gradle-wrapper.properties index df6a6ad..eb84db6 100644 --- a/samples/simple-streamable-server/gradle/wrapper/gradle-wrapper.properties +++ b/samples/simple-streamable-server/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.0-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 diff --git a/samples/simple-streamable-server/gradlew b/samples/simple-streamable-server/gradlew index b9bb139..249efbb 100755 --- a/samples/simple-streamable-server/gradlew +++ b/samples/simple-streamable-server/gradlew @@ -20,7 +20,7 @@ ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. # # Important for running: # @@ -29,7 +29,7 @@ # bash, then to run this script, type that shell name before the whole # command line, like: # -# ksh Gradle +# ksh gradlew # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: diff --git a/samples/simple-streamable-server/gradlew.bat b/samples/simple-streamable-server/gradlew.bat index aa5f10b..8508ef6 100644 --- a/samples/simple-streamable-server/gradlew.bat +++ b/samples/simple-streamable-server/gradlew.bat @@ -19,7 +19,7 @@ @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## @@ -72,7 +72,7 @@ echo location of your Java installation. 1>&2 -@rem Execute Gradle +@rem Execute gradlew @rem endlocal doesn't take effect until after the line is parsed and variables are expanded @rem which allows us to clear the local environment before executing the java command endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel diff --git a/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties b/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties index df6a6ad..eb84db6 100644 --- a/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties +++ b/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.0-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 diff --git a/samples/weather-stdio-server/gradlew b/samples/weather-stdio-server/gradlew index b9bb139..249efbb 100755 --- a/samples/weather-stdio-server/gradlew +++ b/samples/weather-stdio-server/gradlew @@ -20,7 +20,7 @@ ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. # # Important for running: # @@ -29,7 +29,7 @@ # bash, then to run this script, type that shell name before the whole # command line, like: # -# ksh Gradle +# ksh gradlew # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: diff --git a/samples/weather-stdio-server/gradlew.bat b/samples/weather-stdio-server/gradlew.bat index aa5f10b..8508ef6 100644 --- a/samples/weather-stdio-server/gradlew.bat +++ b/samples/weather-stdio-server/gradlew.bat @@ -19,7 +19,7 @@ @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## @@ -72,7 +72,7 @@ echo location of your Java installation. 1>&2 -@rem Execute Gradle +@rem Execute gradlew @rem endlocal doesn't take effect until after the line is parsed and variables are expanded @rem which allows us to clear the local environment before executing the java command endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel From a84ff59b53c26feec583cbfffa2e61d50e92c8a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Jun 2026 13:51:56 +0000 Subject: [PATCH 328/449] chore(deps): bump actions/checkout from 6 to 7 in the all-actions group across 1 directory (#852) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the all-actions group with 1 update in the / directory: [actions/checkout](https://github.com/actions/checkout). Updates `actions/checkout` from 6 to 7

    Release notes

    Sourced from actions/checkout's releases.

    v7.0.0

    What's Changed

    New Contributors

    Full Changelog: https://github.com/actions/checkout/compare/v6.0.3...v7.0.0

    v6.0.3

    What's Changed

    New Contributors

    Full Changelog: https://github.com/actions/checkout/compare/v6...v6.0.3

    v6.0.2

    What's Changed

    Full Changelog: https://github.com/actions/checkout/compare/v6.0.1...v6.0.2

    v6.0.1

    What's Changed

    Full Changelog: https://github.com/actions/checkout/compare/v6...v6.0.1

    Changelog

    Sourced from actions/checkout's changelog.

    Changelog

    v7.0.0

    v6.0.3

    v6.0.2

    v6.0.1

    v6.0.0

    v5.0.1

    v5.0.0

    v4.3.1

    v4.3.0

    v4.2.2

    v4.2.1

    ... (truncated)

    Commits

    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/apidocs.yaml | 2 +- .github/workflows/build.yml | 6 +++--- .github/workflows/codeql.yml | 2 +- .github/workflows/conformance.yml | 8 ++++---- .github/workflows/gradle-publish.yml | 2 +- .github/workflows/samples.yml | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/apidocs.yaml b/.github/workflows/apidocs.yaml index 995f7d8..510af19 100644 --- a/.github/workflows/apidocs.yaml +++ b/.github/workflows/apidocs.yaml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Set up JDK ${{ env.JAVA_VERSION }} uses: actions/setup-java@v5 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ad4cc7a..2b6e415 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -43,7 +43,7 @@ jobs: max-workers: 2 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Set up JDK ${{ env.JAVA_VERSION }} uses: actions/setup-java@v5 @@ -126,7 +126,7 @@ jobs: contents: read steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Set up JDK ${{ env.JAVA_VERSION }} uses: actions/setup-java@v5 @@ -168,7 +168,7 @@ jobs: name: "Build Sample: ${{ matrix.sample }}" timeout-minutes: 10 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Set up JDK ${{ env.JAVA_VERSION }} uses: actions/setup-java@v5 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 5697b61..f25ec64 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -25,7 +25,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Set up JDK ${{ env.JAVA_VERSION }} uses: actions/setup-java@v5 diff --git a/.github/workflows/conformance.yml b/.github/workflows/conformance.yml index 4058d02..2511286 100644 --- a/.github/workflows/conformance.yml +++ b/.github/workflows/conformance.yml @@ -25,7 +25,7 @@ jobs: timeout-minutes: 20 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Set up JDK uses: actions/setup-java@v5 @@ -76,7 +76,7 @@ jobs: - sse-retry steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Set up JDK uses: actions/setup-java@v5 @@ -107,7 +107,7 @@ jobs: timeout-minutes: 20 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Set up JDK uses: actions/setup-java@v5 @@ -146,7 +146,7 @@ jobs: - auth/cross-app-access-complete-flow steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Set up JDK uses: actions/setup-java@v5 diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml index 6ed0756..f9df053 100644 --- a/.github/workflows/gradle-publish.yml +++ b/.github/workflows/gradle-publish.yml @@ -17,7 +17,7 @@ jobs: packages: write steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Set up JDK 21 uses: actions/setup-java@v5 with: diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index 2c910fd..89de699 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -36,7 +36,7 @@ jobs: name: Build Sample (${{ matrix.sample }}) timeout-minutes: 10 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Set up JDK ${{ env.JAVA_VERSION }} uses: actions/setup-java@v5 From 3274244f7d018d4108d8611c95eb10cc7dbc18ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Jun 2026 21:27:25 +0000 Subject: [PATCH 329/449] chore(deps): bump gradle-wrapper from 9.5.1 to 9.6.0 (#851) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [gradle-wrapper](https://github.com/gradle/gradle) from 9.5.1 to 9.6.0.
    Release notes

    Sourced from gradle-wrapper's releases.

    9.6.0

    The Gradle team is excited to announce Gradle 9.6.0.

    Here are the highlights of this release:

    • Improved Configuration Cache hit rates
    • Additional CLI rendering options
    • Important project hierarchy lookup deprecations

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: Aharnish Solanki, Benedikt Johannes, Devendra Reddy Pennabadi, Dmytro Rodionov, Dreeam, Elías Hernández Rodríguez, Eng Zer Jun, FinlayRJW, Kamal Kansal, Marcono1234, Nelson Osacky, Philip Wedemann, Ravi, Roberto Perez Alcolea, Ryan Schmitt, Sebastian Schuberth, seunghun.ham, sk-reddy17, Suvrat Acharya, Vedant Madane.

    Upgrade instructions

    Switch your build to use Gradle 9.6.0 by updating your wrapper:

    ./gradlew :wrapper --gradle-version=9.6.0 &&
    ./gradlew :wrapper
    

    See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

    For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

    Reporting problems

    If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

    We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

    9.6.0 RC3

    ... (truncated)

    Commits
    • 3f750f0 Update distro size for Gradle 9.6.0 release (#38243)
    • ae93cfa update distro size
    • f7e22b5 Update Gradle wrapper to version 9.6.0-rc-3 (#38227)
    • 71a8eb9 Update Gradle wrapper to version 9.6.0-rc-3
    • 70a8745 Prepare release notes for Gradle 9.6.0RC3 (#38220)
    • 9706522 some final polishing for release notes
    • af308eb Restore GradleInternal.getRootProject overload for binary compatibility (#38214)
    • 239361b Restore GradleInternal.getRootProject overload for binary compatibility
    • 896dc44 Update Gradle wrapper to version 9.6.0-rc-2 (#38187)
    • 2d4ec25 Update Gradle wrapper to version 9.6.0-rc-2
    • Additional commits viewable in compare view

    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 4 ++-- gradlew.bat | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index df6a6ad..eb84db6 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.0-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 diff --git a/gradlew b/gradlew index b9bb139..249efbb 100755 --- a/gradlew +++ b/gradlew @@ -20,7 +20,7 @@ ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. # # Important for running: # @@ -29,7 +29,7 @@ # bash, then to run this script, type that shell name before the whole # command line, like: # -# ksh Gradle +# ksh gradlew # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: diff --git a/gradlew.bat b/gradlew.bat index aa5f10b..8508ef6 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -19,7 +19,7 @@ @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## @@ -72,7 +72,7 @@ echo location of your Java installation. 1>&2 -@rem Execute Gradle +@rem Execute gradlew @rem endlocal doesn't take effect until after the line is parsed and variables are expanded @rem which allows us to clear the local environment before executing the java command endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel From 2967696e6ca32dcaddb88fe890a2127010e07edb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Jun 2026 21:49:27 +0000 Subject: [PATCH 330/449] chore(deps): bump com.vanniktech:gradle-maven-publish-plugin from 0.36.0 to 0.37.0 in the gradle-plugins group across 1 directory (#848) Bumps the gradle-plugins group with 1 update in the / directory: [com.vanniktech:gradle-maven-publish-plugin](https://github.com/vanniktech/gradle-maven-publish-plugin). Updates `com.vanniktech:gradle-maven-publish-plugin` from 0.36.0 to 0.37.0
    Release notes

    Sourced from com.vanniktech:gradle-maven-publish-plugin's releases.

    0.37.0

    • When publishing to Maven Central, redundant checksum files are now excluded by default: checksums of .asc signature files (gradle/gradle#20232) and the sha256/sha512 checksums, which are never read by Gradle or Maven Central. The published checksums can be configured through checksums(...) in the DSL or the mavenCentralChecksums Gradle property (default md5,sha1). Signature checksum exclusion can be controlled through excludeSignatureChecksums() or the mavenCentralExcludeSignatureChecksums Gradle property.
    • Maven Central deployment id is being logged after upload.

    Minimum supported versions

    • JDK 17
    • Gradle 9.0.0
    • Android Gradle Plugin 8.13.0
    • Kotlin Gradle Plugin 2.2.0

    Compatibility tested up to

    • JDK 26
    • Gradle 9.6.0
    • Gradle 9.7.0-milestone-1
    • Android Gradle Plugin 9.2.1
    • Android Gradle Plugin 9.3.0-rc01
    • Android Gradle Plugin 9.4.0-alpha01
    • Kotlin Gradle Plugin 2.4.0

    0.37.0-rc1

    • When publishing to Maven Central, redundant checksum files are now excluded by default: checksums of .asc signature files (gradle/gradle#20232) and the sha256/sha512 checksums, which are never read by Gradle or Maven Central. The published checksums can be configured through checksums(...) in the DSL or the mavenCentralChecksums Gradle property (default md5,sha1). Signature checksum exclusion can be controlled through excludeSignatureChecksums() or the mavenCentralExcludeSignatureChecksums Gradle property.
    • Maven Central deployment id is being logged after upload.

    Minimum supported versions

    • JDK 17
    • Gradle 9.0.0
    • Android Gradle Plugin 8.13.0
    • Kotlin Gradle Plugin 2.2.0

    Compatibility tested up to

    • JDK 26
    • Gradle 9.6.0
    • Gradle 9.7.0-milestone-1
    • Android Gradle Plugin 9.2.1
    • Android Gradle Plugin 9.3.0-rc01
    • Android Gradle Plugin 9.4.0-alpha01
    • Kotlin Gradle Plugin 2.4.0
    Changelog

    Sourced from com.vanniktech:gradle-maven-publish-plugin's changelog.

    0.37.0 (2026-06-21)

    • When publishing to Maven Central, redundant checksum files are now excluded by default: checksums of .asc signature files (gradle/gradle#20232) and the sha256/sha512 checksums, which are never read by Gradle or Maven Central. The published checksums can be configured through checksums(...) in the DSL or the mavenCentralChecksums Gradle property (default md5,sha1). Signature checksum exclusion can be controlled through excludeSignatureChecksums() or the mavenCentralExcludeSignatureChecksums Gradle property.
    • Maven Central deployment id is being logged after upload.

    Minimum supported versions

    • JDK 17
    • Gradle 9.0.0
    • Android Gradle Plugin 8.13.0
    • Kotlin Gradle Plugin 2.2.0

    Compatibility tested up to

    • JDK 26
    • Gradle 9.6.0
    • Gradle 9.7.0-milestone-1
    • Android Gradle Plugin 9.2.1
    • Android Gradle Plugin 9.3.0-rc01
    • Android Gradle Plugin 9.4.0-alpha01
    • Kotlin Gradle Plugin 2.4.0
    Commits

    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4904ea4..0b2929d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ kotlin = "2.3.21" kover = "0.9.8" ktlint = "14.2.0" knit = "0.5.1" -mavenPublish = "0.36.0" +mavenPublish = "0.37.0" netty = "4.2.15.Final" # libraries version From cca883aa3ff9c4c700287585c8e5db13d599d14d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 Jun 2026 00:43:56 +0200 Subject: [PATCH 331/449] chore(deps): bump the kotest group across 1 directory with 3 updates (#847) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the kotest group with 3 updates in the / directory: [io.kotest:kotest-assertions-core](https://github.com/kotest/kotest), [io.kotest:kotest-assertions-json](https://github.com/kotest/kotest) and [io.kotest:kotest-assertions-ktor](https://github.com/kotest/kotest). Updates `io.kotest:kotest-assertions-core` from 6.1.11 to 6.2.1
    Release notes

    Sourced from io.kotest:kotest-assertions-core's releases.

    v6.2.1

    What's Changed

    Full Changelog: https://github.com/kotest/kotest/compare/v6.2.0...v6.2.1

    v6.2.0

    ✨ Features

    New matchers / assertions

    comparables.shouldBeAtLeast — @​AlexCue987 (#5831) MultipleMatcherResult for combining multiple matcher results — @​sksamuel (#5723) shouldContainExactCopies for lists — @​AlexCue987 (#5913) Character matchers — @​JordanLongstaff (#5921) shouldBeSingle matcher — @​sksamuel (#6028) shouldContainRepeats for strings — @​AlexCue987 (#6004) Infix form for inspectors — @​alfonsoristorato (#5920) Block-asserting overload for shouldBeFailure — @​alfonsoristorato (#5927) Per-call Eq overrides via a withEqs DSL — @​PreAgile (#6010) Per-element data class diffs in collection comparisons — @​PreAgile (#5835)

    Framework / API

    Run data tests singularly — @​alfonsoristorato (#5574) New JVM test-suites module (deprecates JunitXmlReporter) — @​sksamuel (#5801) Support isolation modes on all KMP platforms — @​sksamuel (#5806) Breadcrumbs support for Kotest spec files — @​sksamuel (#5793) Type-safe test metadata API — @​PreAgile (#5905) Public API for creating custom styles — @​sksamuel (#5814) Support multiple --test args joined by semicolon in KOTEST_INCLUDE_PATTERN — @​sksamuel (#5922) Extend life of deprecated Test containers and add onStart — @​sksamuel (#5896) Make Arb.stringPattern multiplatform — @​wilmveel (#6008) Complete the permutations module — @​sksamuel (#6016) Add Wasm WASI target to kotest-property and kotest-property-permutations — @​jsoizo (#6129) Make fibonacci interval functions public — @​mvanhorn (#6135) Add Android instrumented test module — @​sksamuel (#5900)

    IntelliJ plugin

    N-times setter for test invocation — @​alfonsoristorato (#5815) Invocation count on the Kotest producer — @​alfonsoristorato (#5837) Singular data-test run on the Kotest producer — @​alfonsoristorato (#5843) Amper-aware run-configuration producer — @​sksamuel (#5999)

    ... (truncated)

    Commits
    • 9ae8a1d Make flaky "throws FileNotFoundException for some time" eventually test deter...
    • a16146f Fix data objects being considered equal in DataClassEq (#6144) (#6145)
    • 47fe8d4 remove KotestInternal in TestFactory (#6146)
    • b39700c fix docs (#6147)
    • 8006dbd Remove usages of K1's KtUltraLightClass from kotest plugin sources (#6143)
    • 07b21d7 Add support for deprecated Kotlin targets in Android and Native conve… (#6141)
    • 96f71e0 docs: add KDoc to ArrayEq (#6140)
    • 7f2b14a Cut docs for 6.2
    • ead01c0 Override additional Kotlin version properties for IC-261 in release workflow
    • f3134ea align compile toolchain with minimum supported Kotlin version for KLIB ABI co...
    • Additional commits viewable in compare view

    Updates `io.kotest:kotest-assertions-json` from 6.1.11 to 6.2.1
    Release notes

    Sourced from io.kotest:kotest-assertions-json's releases.

    v6.2.1

    What's Changed

    Full Changelog: https://github.com/kotest/kotest/compare/v6.2.0...v6.2.1

    v6.2.0

    ✨ Features

    New matchers / assertions

    comparables.shouldBeAtLeast — @​AlexCue987 (#5831) MultipleMatcherResult for combining multiple matcher results — @​sksamuel (#5723) shouldContainExactCopies for lists — @​AlexCue987 (#5913) Character matchers — @​JordanLongstaff (#5921) shouldBeSingle matcher — @​sksamuel (#6028) shouldContainRepeats for strings — @​AlexCue987 (#6004) Infix form for inspectors — @​alfonsoristorato (#5920) Block-asserting overload for shouldBeFailure — @​alfonsoristorato (#5927) Per-call Eq overrides via a withEqs DSL — @​PreAgile (#6010) Per-element data class diffs in collection comparisons — @​PreAgile (#5835)

    Framework / API

    Run data tests singularly — @​alfonsoristorato (#5574) New JVM test-suites module (deprecates JunitXmlReporter) — @​sksamuel (#5801) Support isolation modes on all KMP platforms — @​sksamuel (#5806) Breadcrumbs support for Kotest spec files — @​sksamuel (#5793) Type-safe test metadata API — @​PreAgile (#5905) Public API for creating custom styles — @​sksamuel (#5814) Support multiple --test args joined by semicolon in KOTEST_INCLUDE_PATTERN — @​sksamuel (#5922) Extend life of deprecated Test containers and add onStart — @​sksamuel (#5896) Make Arb.stringPattern multiplatform — @​wilmveel (#6008) Complete the permutations module — @​sksamuel (#6016) Add Wasm WASI target to kotest-property and kotest-property-permutations — @​jsoizo (#6129) Make fibonacci interval functions public — @​mvanhorn (#6135) Add Android instrumented test module — @​sksamuel (#5900)

    IntelliJ plugin

    N-times setter for test invocation — @​alfonsoristorato (#5815) Invocation count on the Kotest producer — @​alfonsoristorato (#5837) Singular data-test run on the Kotest producer — @​alfonsoristorato (#5843) Amper-aware run-configuration producer — @​sksamuel (#5999)

    ... (truncated)

    Commits
    • 9ae8a1d Make flaky "throws FileNotFoundException for some time" eventually test deter...
    • a16146f Fix data objects being considered equal in DataClassEq (#6144) (#6145)
    • 47fe8d4 remove KotestInternal in TestFactory (#6146)
    • b39700c fix docs (#6147)
    • 8006dbd Remove usages of K1's KtUltraLightClass from kotest plugin sources (#6143)
    • 07b21d7 Add support for deprecated Kotlin targets in Android and Native conve… (#6141)
    • 96f71e0 docs: add KDoc to ArrayEq (#6140)
    • 7f2b14a Cut docs for 6.2
    • ead01c0 Override additional Kotlin version properties for IC-261 in release workflow
    • f3134ea align compile toolchain with minimum supported Kotlin version for KLIB ABI co...
    • Additional commits viewable in compare view

    Updates `io.kotest:kotest-assertions-ktor` from 6.1.11 to 6.2.1
    Release notes

    Sourced from io.kotest:kotest-assertions-ktor's releases.

    v6.2.1

    What's Changed

    Full Changelog: https://github.com/kotest/kotest/compare/v6.2.0...v6.2.1

    v6.2.0

    ✨ Features

    New matchers / assertions

    comparables.shouldBeAtLeast — @​AlexCue987 (#5831) MultipleMatcherResult for combining multiple matcher results — @​sksamuel (#5723) shouldContainExactCopies for lists — @​AlexCue987 (#5913) Character matchers — @​JordanLongstaff (#5921) shouldBeSingle matcher — @​sksamuel (#6028) shouldContainRepeats for strings — @​AlexCue987 (#6004) Infix form for inspectors — @​alfonsoristorato (#5920) Block-asserting overload for shouldBeFailure — @​alfonsoristorato (#5927) Per-call Eq overrides via a withEqs DSL — @​PreAgile (#6010) Per-element data class diffs in collection comparisons — @​PreAgile (#5835)

    Framework / API

    Run data tests singularly — @​alfonsoristorato (#5574) New JVM test-suites module (deprecates JunitXmlReporter) — @​sksamuel (#5801) Support isolation modes on all KMP platforms — @​sksamuel (#5806) Breadcrumbs support for Kotest spec files — @​sksamuel (#5793) Type-safe test metadata API — @​PreAgile (#5905) Public API for creating custom styles — @​sksamuel (#5814) Support multiple --test args joined by semicolon in KOTEST_INCLUDE_PATTERN — @​sksamuel (#5922) Extend life of deprecated Test containers and add onStart — @​sksamuel (#5896) Make Arb.stringPattern multiplatform — @​wilmveel (#6008) Complete the permutations module — @​sksamuel (#6016) Add Wasm WASI target to kotest-property and kotest-property-permutations — @​jsoizo (#6129) Make fibonacci interval functions public — @​mvanhorn (#6135) Add Android instrumented test module — @​sksamuel (#5900)

    IntelliJ plugin

    N-times setter for test invocation — @​alfonsoristorato (#5815) Invocation count on the Kotest producer — @​alfonsoristorato (#5837) Singular data-test run on the Kotest producer — @​alfonsoristorato (#5843) Amper-aware run-configuration producer — @​sksamuel (#5999)

    ... (truncated)

    Commits
    • 9ae8a1d Make flaky "throws FileNotFoundException for some time" eventually test deter...
    • a16146f Fix data objects being considered equal in DataClassEq (#6144) (#6145)
    • 47fe8d4 remove KotestInternal in TestFactory (#6146)
    • b39700c fix docs (#6147)
    • 8006dbd Remove usages of K1's KtUltraLightClass from kotest plugin sources (#6143)
    • 07b21d7 Add support for deprecated Kotlin targets in Android and Native conve… (#6141)
    • 96f71e0 docs: add KDoc to ArrayEq (#6140)
    • 7f2b14a Cut docs for 6.2
    • ead01c0 Override additional Kotlin version properties for IC-261 in release workflow
    • f3134ea align compile toolchain with minimum supported Kotlin version for KLIB ABI co...
    • Additional commits viewable in compare view

    Updates `io.kotest:kotest-assertions-json` from 6.1.11 to 6.2.1
    Release notes

    Sourced from io.kotest:kotest-assertions-json's releases.

    v6.2.1

    What's Changed

    Full Changelog: https://github.com/kotest/kotest/compare/v6.2.0...v6.2.1

    v6.2.0

    ✨ Features

    New matchers / assertions

    comparables.shouldBeAtLeast — @​AlexCue987 (#5831) MultipleMatcherResult for combining multiple matcher results — @​sksamuel (#5723) shouldContainExactCopies for lists — @​AlexCue987 (#5913) Character matchers — @​JordanLongstaff (#5921) shouldBeSingle matcher — @​sksamuel (#6028) shouldContainRepeats for strings — @​AlexCue987 (#6004) Infix form for inspectors — @​alfonsoristorato (#5920) Block-asserting overload for shouldBeFailure — @​alfonsoristorato (#5927) Per-call Eq overrides via a withEqs DSL — @​PreAgile (#6010) Per-element data class diffs in collection comparisons — @​PreAgile (#5835)

    Framework / API

    Run data tests singularly — @​alfonsoristorato (#5574) New JVM test-suites module (deprecates JunitXmlReporter) — @​sksamuel (#5801) Support isolation modes on all KMP platforms — @​sksamuel (#5806) Breadcrumbs support for Kotest spec files — @​sksamuel (#5793) Type-safe test metadata API — @​PreAgile (#5905) Public API for creating custom styles — @​sksamuel (#5814) Support multiple --test args joined by semicolon in KOTEST_INCLUDE_PATTERN — @​sksamuel (#5922) Extend life of deprecated Test containers and add onStart — @​sksamuel (#5896) Make Arb.stringPattern multiplatform — @​wilmveel (#6008) Complete the permutations module — @​sksamuel (#6016) Add Wasm WASI target to kotest-property and kotest-property-permutations — @​jsoizo (#6129) Make fibonacci interval functions public — @​mvanhorn (#6135) Add Android instrumented test module — @​sksamuel (#5900)

    IntelliJ plugin

    N-times setter for test invocation — @​alfonsoristorato (#5815) Invocation count on the Kotest producer — @​alfonsoristorato (#5837) Singular data-test run on the Kotest producer — @​alfonsoristorato (#5843) Amper-aware run-configuration producer — @​sksamuel (#5999)

    ... (truncated)

    Commits
    • 9ae8a1d Make flaky "throws FileNotFoundException for some time" eventually test deter...
    • a16146f Fix data objects being considered equal in DataClassEq (#6144) (#6145)
    • 47fe8d4 remove KotestInternal in TestFactory (#6146)
    • b39700c fix docs (#6147)
    • 8006dbd Remove usages of K1's KtUltraLightClass from kotest plugin sources (#6143)
    • 07b21d7 Add support for deprecated Kotlin targets in Android and Native conve… (#6141)
    • 96f71e0 docs: add KDoc to ArrayEq (#6140)
    • 7f2b14a Cut docs for 6.2
    • ead01c0 Override additional Kotlin version properties for IC-261 in release workflow
    • f3134ea align compile toolchain with minimum supported Kotlin version for KLIB ABI co...
    • Additional commits viewable in compare view

    Updates `io.kotest:kotest-assertions-ktor` from 6.1.11 to 6.2.1
    Release notes

    Sourced from io.kotest:kotest-assertions-ktor's releases.

    v6.2.1

    What's Changed

    Full Changelog: https://github.com/kotest/kotest/compare/v6.2.0...v6.2.1

    v6.2.0

    ✨ Features

    New matchers / assertions

    comparables.shouldBeAtLeast — @​AlexCue987 (#5831) MultipleMatcherResult for combining multiple matcher results — @​sksamuel (#5723) shouldContainExactCopies for lists — @​AlexCue987 (#5913) Character matchers — @​JordanLongstaff (#5921) shouldBeSingle matcher — @​sksamuel (#6028) shouldContainRepeats for strings — @​AlexCue987 (#6004) Infix form for inspectors — @​alfonsoristorato (#5920) Block-asserting overload for shouldBeFailure — @​alfonsoristorato (#5927) Per-call Eq overrides via a withEqs DSL — @​PreAgile (#6010) Per-element data class diffs in collection comparisons — @​PreAgile (#5835)

    Framework / API

    Run data tests singularly — @​alfonsoristorato (#5574) New JVM test-suites module (deprecates JunitXmlReporter) — @​sksamuel (#5801) Support isolation modes on all KMP platforms — @​sksamuel (#5806) Breadcrumbs support for Kotest spec files — @​sksamuel (#5793) Type-safe test metadata API — @​PreAgile (#5905) Public API for creating custom styles — @​sksamuel (#5814) Support multiple --test args joined by semicolon in KOTEST_INCLUDE_PATTERN — @​sksamuel (#5922) Extend life of deprecated Test containers and add onStart — @​sksamuel (#5896) Make Arb.stringPattern multiplatform — @​wilmveel (#6008) Complete the permutations module — @​sksamuel (#6016) Add Wasm WASI target to kotest-property and kotest-property-permutations — @​jsoizo (#6129) Make fibonacci interval functions public — @​mvanhorn (#6135) Add Android instrumented test module — @​sksamuel (#5900)

    IntelliJ plugin

    N-times setter for test invocation — @​alfonsoristorato (#5815) Invocation count on the Kotest producer — @​alfonsoristorato (#5837) Singular data-test run on the Kotest producer — @​alfonsoristorato (#5843) Amper-aware run-configuration producer — @​sksamuel (#5999)

    ... (truncated)

    Commits
    • 9ae8a1d Make flaky "throws FileNotFoundException for some time" eventually test deter...
    • a16146f Fix data objects being considered equal in DataClassEq (#6144) (#6145)
    • 47fe8d4 remove KotestInternal in TestFactory (#6146)
    • b39700c fix docs (#6147)
    • 8006dbd Remove usages of K1's KtUltraLightClass from kotest plugin sources (#6143)
    • 07b21d7 Add support for deprecated Kotlin targets in Android and Native conve… (#6141)
    • 96f71e0 docs: add KDoc to ArrayEq (#6140)
    • 7f2b14a Cut docs for 6.2
    • ead01c0 Override additional Kotlin version properties for IC-261 in release workflow
    • f3134ea align compile toolchain with minimum supported Kotlin version for KLIB ABI co...
    • Additional commits viewable in compare view

    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0b2929d..b35612e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,7 +15,7 @@ netty = "4.2.15.Final" awaitility = "4.3.0" collections-immutable = "0.4.0" coroutines = "1.11.0" -kotest = "6.1.11" +kotest = "6.2.1" kotlinx-io = "0.9.0" ktor = "3.4.3" logging = "8.0.4" From b6f4ad5a9fe52ef4b3937e224854e325771a4207 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 Jun 2026 21:12:13 +0200 Subject: [PATCH 332/449] chore(deps): bump org.jetbrains.kotlinx:kotlinx-collections-immutable from 0.4.0 to 0.5.0 in the kotlinx group across 1 directory (#833) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the kotlinx group with 1 update in the / directory: [org.jetbrains.kotlinx:kotlinx-collections-immutable](https://github.com/Kotlin/kotlinx.collections.immutable). Updates `org.jetbrains.kotlinx:kotlinx-collections-immutable` from 0.4.0 to 0.5.0
    Release notes

    Sourced from org.jetbrains.kotlinx:kotlinx-collections-immutable's releases.

    v0.5.0

    This release renames the copy-returning methods on PersistentCollection, PersistentList, and PersistentMap to participial forms (KEEP-0459) — add→adding, put→putting, set→replacingAt, clear→cleared, and the rest. The old imperative names continue to compile in 0.5.x as @Deprecated(WARNING), become a compile error in 0.6.0, and are removed in 0.7.0. First shipped in 0.5.0-beta01; 0.5.0 is the stable release, with only internal fixes since.

    To migrate, apply the IDE quick-fixes, follow the migration guide, or use the 0.5.x migration skill with an AI assistant — each applies the rename the deprecation warnings name.

    Changes since 0.4.0:

    • Renamed copy-returning methods to participial forms; deprecated the old names #233
    • Enabled the Kotlin return-value checker (-Xreturn-value-checker=full) for the core module #243
    • Updated Kotlin to 2.3.0 #242
    • Configured JDK release 8 for the Kotlin compiler #239
    • Populated Implementation-* attributes in published JAR manifests #237
    • Enabled Dokka documentation generation, upgraded to Dokka 2.2.0 #225, #245, #248

    Full changelog: https://github.com/Kotlin/kotlinx.collections.immutable/compare/v0.4.0...v0.5.0

    v0.5.0-beta01

    This release renames copy-returning methods on PersistentCollection, PersistentList, and PersistentMap to participial forms — the old imperative names continue to compile in 0.5.x as @Deprecated(WARNING), become a compile error in 0.6.0, and are removed in 0.7.0. Concretely, add → adding, put → putting, clear → cleared, set → replacingAt, removeAt → removingAt, and the rest of the family (see the migration guide for the full table). The rename follows KEEP-0459 — Naming Conventions for Copy-Returning Operations.

    If your code uses any of these methods, please read the migration guide for the full rename table, the deprecation timeline, and the recommended IDE quick-fix workflow.

    This is a beta — 0.5.0 is planned to drop the -beta suffix soon, and once it ships stable the participial names are committed (any further rename would need its own deprecation cycle). If any of the renames feels awkward or you've hit a migration problem, please share it on [issue #232](Kotlin/kotlinx.collections.immutable#232) before then.

    What's changed

    • Renamed PersistentCollection mutating-copy methods to participial forms (add/remove/set/put/clear → adding/removing/setting/putting/clearing and *ed variants) and deprecated the original names #233
    • Enabled the Kotlin return-value checker (-Xreturn-value-checker=full) for the core module and annotated select internal helpers with @IgnorableReturnValue where discarding the result is intentional #243
    • Updated Kotlin to version 2.3.0 #242
    • Configured JDK release to 8 for the Kotlin compiler #239
    • Populated Implementation-Title/Implementation-Version/Implementation-Vendor in published JAR manifests #237
    • Enabled Dokka documentation generation and upgraded Dokka to 2.2.0 #225, #245, #248
    Changelog

    Sourced from org.jetbrains.kotlinx:kotlinx-collections-immutable's changelog.

    0.5.0

    • Promoted 0.5.0-beta01 to stable; no API or behavior changes

    0.5.0-beta01

    • Renamed copy-returning methods on PersistentCollection, PersistentList, and PersistentMap from imperative to participial forms (add/remove/put/clear/set/removeAt/… → adding/removing/putting/cleared/replacingAt/removingAt/…) per KEEP-0459 and deprecated the original names #233
    • Enabled the Kotlin return-value checker (-Xreturn-value-checker=full) for the core module and annotated select internal helpers with @IgnorableReturnValue where discarding the result is intentional #243
    • Updated Kotlin to version 2.3.0 #242
    • Configured JDK release to 8 for the Kotlin compiler #239
    • Populated Implementation-Title/Implementation-Version/Implementation-Vendor in published JAR manifests #237
    • Enabled Dokka documentation generation and upgraded Dokka to 2.2.0 #225, #245, #248
    Commits
    • 0727896 Release v0.5.0
    • 0e1a24c Document return values in the KDoc summary instead of @return (#256)
    • 2069a62 Fix equals/hashCode contract violation in random-operations stress tests (#262)
    • 6f7abb9 Updated team infra plugin (#260)
    • 15a77d7 Fix the package-list location (#258)
    • 1ab038c Add .kotlin to .gitignore to exclude build metadata produced by KGP (#259)
    • dc6ba40 Add top-level README for kotlinx-collections-immutable module (#255)
    • a6ad2e1 Release v0.5.0-beta01
    • 54f4815 Rename PersistentCollection methods to participial forms and deprecate origin...
    • 753924c Added missing reference to deployment id parameter (#250)
    • Additional commits viewable in compare view

    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b35612e..cf6d374 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,7 +13,7 @@ netty = "4.2.15.Final" # libraries version awaitility = "4.3.0" -collections-immutable = "0.4.0" +collections-immutable = "0.5.0" coroutines = "1.11.0" kotest = "6.2.1" kotlinx-io = "0.9.0" From d93f47f1e57a2c1fbdd835679bae7e701cc15096 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jun 2026 13:14:48 +0200 Subject: [PATCH 333/449] chore(deps): bump ch.qos.logback:logback-classic from 1.5.34 to 1.5.37 (#857) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.34 to 1.5.37.
    Release notes

    Sourced from ch.qos.logback:logback-classic's releases.

    Logback 1.5.37

    2026-06-26 Release of logback version 1.5.37

    • Given the numerous vulnerabilities related to conditional configuration processing based on the evaluation of Java expressions using the Janino library, support for such expressions has been removed. Users are offered the an online migration service or the element introduced in version 1.5.20. See the relevant documentation for more details.

    • A bitwise identical binary of this version can be reproduced by building from source code at commit c1df7f522e648eec7b4ef6a12c8758fec0f00048 associated with the tag v_1.5.37. Release built using Java "21" 2023-10-17 LTS build 21.0.1.+12-LTS-29 under Linux Debian 11.6.

    Logback 1.5.36

    2026-06-25 Release of logback version 1.5.36

    • The 'condition' attribute in <if> elements now reject certain references that are associated with ACE attacks. This issue was reported by "yulate" (yulate531@gmail.com.com) and registered as CVE-2026-13006.

    • A bitwise identical binary of this version can be reproduced by building from source code at commit 9b94c37562bf25a6a944146701d42ee6c4eee888 associated with the tag v_1.5.36. Release built using Java "21" 2023-10-17 LTS build 21.0.1.+12-LTS-29 under Linux Debian 11.6.

    Logback 1.5.35

    026-06-23 Release of logback version 1.5.35

    • The 'condition' attribute in <if> elements now rejects unicode escape sequences (\u and \U). This closes a bypass of the existing prohibition on the new operator in Janino-evaluated conditions. This issue was reported by IcySun (icysun@qq.com) and registered as CVE-2026-13006.

    • Added ConfiguratorRank.AUTHENTICATING (rank 100), the highest configurator rank, for certified/authenticating configurators discovered via the ServiceLoader mechanism. ContextInitializer now requires that at most one such configurator exist on the classpath; if more than one is found, initialization aborts with an error.

    • ConsoleCharsetPropertyDefiner is no longer shipped. The Java 21 multi-release compilation of logback-core has been disabled, which removes this class from the published artifact. Configurations that referenced ch.qos.logback.core.property.ConsoleCharsetPropertyDefiner will need an alternative approach for console charset detection.

    • The logback-examples module is now included in artifacts published to Maven Central.

    • JoranConfigurator.makeAnotherInstance() and DefaultJoranConfigurator.performMultiStepConfigurationFileSearch() are now protected, allowing derived configurators to override these methods.

    • A bitwise identical binary of this version can be reproduced by building from source code at commit 08bd1598d565d83444f72983935e7da4746783b7 associated with the tag v_1.5.35. Release built using Java "21" 2023-10-17 LTS build 21.0.1.+12-LTS-29 under Linux Debian 11.6.

    Commits

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=ch.qos.logback:logback-classic&package-manager=gradle&previous-version=1.5.34&new-version=1.5.37)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cf6d374..6faaf2a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,7 +22,7 @@ logging = "8.0.4" mockk = "1.14.11" serialization = "1.11.0" slf4j = "2.0.18" -logback = "1.5.34" +logback = "1.5.37" junit = "5.14.4" [libraries] From 4103b87d6952f7e72b0fc9ddfdb2f59a0a39d0ee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jun 2026 14:17:43 +0200 Subject: [PATCH 334/449] chore(deps): bump org.jetbrains.kotlinx:kotlinx-io-core from 0.9.0 to 0.9.1 in the kotlinx group across 1 directory (#855) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the kotlinx group with 1 update in the / directory: [org.jetbrains.kotlinx:kotlinx-io-core](https://github.com/Kotlin/kotlinx-io). Updates `org.jetbrains.kotlinx:kotlinx-io-core` from 0.9.0 to 0.9.1
    Release notes

    Sourced from org.jetbrains.kotlinx:kotlinx-io-core's releases.

    v0.9.1

    Features

    • Dropped an old workaround for unsupported Android version #508
    • Reimplemented module loading for WasmJs #506

    Thanks to @​ilgonmic and @​MohammedKHC for their contributions!

    Changelog

    Sourced from org.jetbrains.kotlinx:kotlinx-io-core's changelog.

    0.9.1

    Published 26 June 2026

    Features

    • Dropped an old workaround for unsupported Android version #508
    • Reimplemented module loading for WasmJs #506
    Commits
    • 362bdc3 Release 0.9.1
    • e5ae2f0 Merge branch 'develop'
    • 2997caf Drop workaround for Android 4.2.2 getsockname bug (#508)
    • a1d6bd9 Use IIFE to asynchronously load a module instead of globalThis (#506)
    • d53d449 Update feedback channels in contributing guidelines
    • 34f1579 Merge branch 'develop'
    • 82caeb2 Document segment pool configuration (#494)
    • See full diff in compare view

    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6faaf2a..ae38db6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,7 +16,7 @@ awaitility = "4.3.0" collections-immutable = "0.5.0" coroutines = "1.11.0" kotest = "6.2.1" -kotlinx-io = "0.9.0" +kotlinx-io = "0.9.1" ktor = "3.4.3" logging = "8.0.4" mockk = "1.14.11" From cd91652be115064c0b9fc43ba1c517893131ba92 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Mon, 29 Jun 2026 14:38:44 +0200 Subject: [PATCH 335/449] fix: validate Origin against localhost by default in DNS rebinding protection (#840) Default the `Origin` allowlist to localhost when `DnsRebindingProtection` relies on the localhost host defaults, so a request with a valid `Host` but a hostile (or `null`) `Origin` is no longer accepted. ## How Has This Been Tested? unit tests ## Breaking Changes Behavioral change only ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- .../kotlin/sdk/server/HostValidation.kt | 10 ++++ .../kotlin/sdk/server/KtorServer.kt | 21 +++++++-- .../sdk/server/DnsRebindingProtectionTest.kt | 46 +++++++++++++++++++ 3 files changed, 72 insertions(+), 5 deletions(-) diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/HostValidation.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/HostValidation.kt index 88b56bc..ef010fe 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/HostValidation.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/HostValidation.kt @@ -19,6 +19,16 @@ import io.modelcontextprotocol.kotlin.sdk.types.RPCError */ internal val LOCALHOST_ALLOWED_HOSTS: List = listOf("localhost", "127.0.0.1", "[::1]") +/** + * Default list of `Origin` values allowed for localhost DNS rebinding protection. + * + * Mirrors [LOCALHOST_ALLOWED_HOSTS] but carries a scheme so the values parse as URLs: + * [extractOriginHost] rejects schemeless input. Comparison is hostname-only and + * scheme-agnostic, so these entries also match `https://` origins on the same host. + */ +internal val LOCALHOST_ALLOWED_ORIGINS: List = + listOf("http://localhost", "http://127.0.0.1", "http://[::1]") + /** * Characters that are valid in a URL but must not appear in an HTTP `Host` header. * Rejecting them prevents the parser from accepting malformed values diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt index d6191df..7e47d76 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt @@ -41,7 +41,9 @@ private val logger = KotlinLogging.logger {} * @param allowedHosts hostnames allowed in the `Host` header. Defaults to `localhost`, `127.0.0.1`, `[::1]`. * @param allowedOrigins origins allowed in the `Origin` header, compared by hostname only * (scheme and port are ignored). Requests without an `Origin` header are allowed. - * Pass `null` to skip origin validation. + * When `null` while the localhost host defaults are in effect (no custom `allowedHosts`), + * the `Origin` header is validated against `localhost`, `127.0.0.1`, `[::1]`. + * With custom `allowedHosts`, `null` skips origin validation. * @param block factory block with access to the [ServerSSESession] * that creates and returns the [Server] to handle the connection. * @throws IllegalStateException if the [SSE] plugin is not installed. @@ -71,7 +73,9 @@ public fun Route.mcp( * @param allowedHosts hostnames allowed in the `Host` header. Defaults to `localhost`, `127.0.0.1`, `[::1]`. * @param allowedOrigins origins allowed in the `Origin` header, compared by hostname only * (scheme and port are ignored). Requests without an `Origin` header are allowed. - * Pass `null` to skip origin validation. + * When `null` while the localhost host defaults are in effect (no custom `allowedHosts`), + * the `Origin` header is validated against `localhost`, `127.0.0.1`, `[::1]`. + * With custom `allowedHosts`, `null` skips origin validation. * @param block factory block with access to the [ServerSSESession] * that creates and returns the [Server] to handle the connection. * @throws IllegalStateException if the [SSE] plugin is not installed. @@ -119,7 +123,9 @@ public fun Route.mcp( * @param allowedHosts hostnames allowed in the `Host` header. Defaults to `localhost`, `127.0.0.1`, `[::1]`. * @param allowedOrigins origins allowed in the `Origin` header, compared by hostname only * (scheme and port are ignored). Requests without an `Origin` header are allowed. - * Pass `null` to skip origin validation. + * When `null` while the localhost host defaults are in effect (no custom `allowedHosts`), + * the `Origin` header is validated against `localhost`, `127.0.0.1`, `[::1]`. + * With custom `allowedHosts`, `null` skips origin validation. * @param block factory block with access to the [ServerSSESession] * that creates and returns the [Server] to handle the connection. */ @@ -205,7 +211,9 @@ private fun Application.mcpStreamableHttp( * If `null` and DNS rebinding protection is enabled, defaults to `localhost`, `127.0.0.1`, `[::1]`. * @param allowedOrigins A list of allowed `Origin` header values, compared by hostname only * (scheme and port are ignored). Requests without an `Origin` header are allowed. - * If `null`, origin validation is disabled. + * When `null` while the localhost host defaults are in effect (no custom `allowedHosts`), + * the `Origin` header is validated against `localhost`, `127.0.0.1`, `[::1]`. + * With custom `allowedHosts`, `null` skips origin validation. * @param eventStore An optional [EventStore] instance to enable resumable event stream functionality. * Allows storing and replaying events. * @param block factory block with access to the [RoutingContext] (for reading request headers) @@ -451,6 +459,9 @@ private fun Route.installDnsRebindingProtection(enabled: Boolean, hosts: List Date: Mon, 29 Jun 2026 15:07:06 +0200 Subject: [PATCH 336/449] chore(deps): bump gradle-wrapper from 9.6.0 to 9.6.1 (#856) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [gradle-wrapper](https://github.com/gradle/gradle) from 9.6.0 to 9.6.1.
    Release notes

    Sourced from gradle-wrapper's releases.

    9.6.1

    The Gradle team is excited to announce Gradle 9.6.1.

    Here are the highlights of this release:

    • Improved Configuration Cache hit rates
    • Additional CLI rendering options
    • Important project hierarchy lookup deprecations

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: Aharnish Solanki, Benedikt Johannes, Devendra Reddy Pennabadi, Dmytro Rodionov, Dreeam, Elías Hernández Rodríguez, Eng Zer Jun, FinlayRJW, Kamal Kansal, Marcono1234, Nelson Osacky, Philip Wedemann, Ravi, Roberto Perez Alcolea, Ryan Schmitt, Sebastian Schuberth, seunghun.ham, sk-reddy17, Suvrat Acharya, Vedant Madane.

    Upgrade instructions

    Switch your build to use Gradle 9.6.1 by updating your wrapper:

    ./gradlew :wrapper --gradle-version=9.6.1 &&
    ./gradlew :wrapper
    

    See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

    For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

    Reporting problems

    If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

    We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

    Commits
    • 309d128 Update fixed issues in release notes for 9.6.1 (#38328)
    • 040a978 Update fixed issues in release notes for 9.6.1
    • e0b8325 Restore --non-interactive flag instead of --interactive/--no-interactive (#38...
    • 946f3e6 Limit explicit temp file permission setting to intended use case (#38300)
    • 65f8224 Restore --non-interactive flag instead of --interactive/--no-interactive
    • e346a5e Adjust CLI flag to configure non-interactive console (#38301)
    • 9b53be9 Adjust CLI flag to configure non-interactive console
    • 0dd3b53 Limit explicit temp file permission setting to intended use case
    • 48e5ac2 Add reproducers
    • 25598fd Prepare 9.6.1 patch release (#38293)
    • Additional commits viewable in compare view

    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index eb84db6..a9db115 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 From 19db4b04e3d578635d4a60e190bc5f9f23bc53f4 Mon Sep 17 00:00:00 2001 From: Takuya Akamatsu Date: Tue, 30 Jun 2026 00:18:53 +0900 Subject: [PATCH 337/449] fix(client): gate completion/complete on the completions capability (#846) Gate `completion/complete` on the server's `completions` capability instead of `prompts`. closes #845 ## Motivation and Context A server that declares `completions` but not `prompts` is currently rejected for completion. Details in #845. ## How Has This Been Tested? New `ClientAssertCapabilityTest` cases; they fail on `main` and pass with this change. `apiCheck` / `ktlintCheck` / `detekt` pass. ## Breaking Changes None. ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed Co-authored-by: Pavel Gorgulov --- .../kotlin/sdk/client/Client.kt | 9 +++++++-- .../sdk/client/ClientAssertCapabilityTest.kt | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt index b88cb1b..59f2b7e 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt @@ -242,13 +242,18 @@ public open class Client(private val clientInfo: Implementation, options: Client Method.Defined.PromptsGet, Method.Defined.PromptsList, - Method.Defined.CompletionComplete, -> { checkNotNull(serverCapabilities?.prompts) { "Server does not support prompts (required for $method)" } } + Method.Defined.CompletionComplete -> { + checkNotNull(serverCapabilities?.completions) { + "Server does not support completions (required for $method)" + } + } + Method.Defined.ResourcesList, Method.Defined.ResourcesTemplatesList, Method.Defined.ResourcesRead, @@ -394,7 +399,7 @@ public open class Client(private val clientInfo: Implementation, options: Client * @param params The completion request parameters. * @param options Optional request options. * @return The completion result returned by the server, or `null` if none. - * @throws IllegalStateException If the server does not support prompts or completion. + * @throws IllegalStateException If the server does not support completions. */ public suspend fun complete(params: CompleteRequest, options: RequestOptions? = null): CompleteResult = request(params, options) diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientAssertCapabilityTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientAssertCapabilityTest.kt index 2f322ec..e9379a7 100644 --- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientAssertCapabilityTest.kt +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientAssertCapabilityTest.kt @@ -84,6 +84,25 @@ class ClientAssertCapabilityTest { ex.message.orEmpty() shouldContain "Client does not support tasks" } + @Test + fun `CompletionComplete does not throw when server declared completions`() = runTest { + val client = newTestClient( + serverCapabilities = ServerCapabilities(completions = ServerCapabilities.Completions), + ) + client.exposedAssertCapabilityForMethod(Method.Defined.CompletionComplete) + } + + @Test + fun `CompletionComplete throws when server has no completions capability`() = runTest { + val client = newTestClient( + serverCapabilities = ServerCapabilities(prompts = ServerCapabilities.Prompts()), + ) + val ex = assertFailsWith { + client.exposedAssertCapabilityForMethod(Method.Defined.CompletionComplete) + } + ex.message.orEmpty() shouldContain "Server does not support completions" + } + private suspend fun newTestClient( serverCapabilities: ServerCapabilities = ServerCapabilities(), clientCapabilities: ClientCapabilities = ClientCapabilities(), From 0221b8db3d596a8add753786048126564ad70b78 Mon Sep 17 00:00:00 2001 From: King Star Date: Tue, 30 Jun 2026 15:46:43 +0800 Subject: [PATCH 338/449] fix(client): close stdio read sources during shutdown (#798) ## Summary Fixes `StdioClientTransport.close()` so shutdown does not hang when the stdin read loop is blocked waiting for input. ## Root Cause / Context `StdioClientTransport.closeResources()` stopped the transport coroutine scope and then waited for the scope job to finish. On the JVM, a `Source.readAtMostTo()` call backed by a blocking Java stream does not necessarily return when the coroutine is cancelled. If the read loop stayed blocked, `close()` could wait indefinitely. ## Changes - Close the stdin source during stdio client shutdown before waiting for transport coroutines to finish. - Also close the optional stderr source during shutdown. - Keep source-close failures non-fatal and log them at debug level. - Add a JVM regression test with a `RawSource` that blocks in `readAtMostTo()` until `close()` is called. - Assert the close path completes, emits `onClose`, and does not invoke `onError`. ## Scope / Risk This is limited to `StdioClientTransport` shutdown behavior. It does not change the public API or server transports. The regression coverage is JVM-focused because the reported hang is caused by a blocking Java stream read. ## Verification ```bash ./gradlew :integration-test:jvmTest --tests 'io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransportTest' ./gradlew :kotlin-sdk-client:jvmTest ./gradlew :kotlin-sdk-client:ktlintCheck :integration-test:ktlintCheck :kotlin-sdk-client:detekt :integration-test:detekt :kotlin-sdk-client:apiCheck ``` Closes #514 --- .../sdk/client/StdioClientTransportTest.kt | 61 +++++++++++++++++++ .../kotlin/sdk/client/StdioClientTransport.kt | 10 +++ 2 files changed, 71 insertions(+) diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt index 585af3f..fc3c1d3 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt @@ -7,9 +7,13 @@ import io.modelcontextprotocol.kotlin.sdk.types.McpException import io.modelcontextprotocol.kotlin.test.utils.createSleepyProcessBuilder import io.modelcontextprotocol.kotlin.test.utils.createTeeProcessBuilder import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import kotlinx.coroutines.withTimeout +import kotlinx.coroutines.withTimeoutOrNull +import kotlinx.io.Buffer +import kotlinx.io.RawSource import kotlinx.io.asSink import kotlinx.io.asSource import kotlinx.io.buffered @@ -18,10 +22,12 @@ import org.junit.jupiter.api.Timeout import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.condition.DisabledOnOs import org.junit.jupiter.api.condition.OS +import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import kotlin.concurrent.atomics.AtomicBoolean import kotlin.concurrent.atomics.ExperimentalAtomicApi import kotlin.test.assertFalse +import kotlin.test.assertNotNull import kotlin.test.assertTrue import kotlin.test.fail import kotlin.time.Duration.Companion.seconds @@ -109,6 +115,43 @@ class StdioClientTransportTest : BaseTransportTest() { } } + @OptIn(ExperimentalAtomicApi::class) + @Test + fun `should close cleanly while stdin read is blocked`(): Unit = runBlocking(Dispatchers.IO) { + val input = BlockingRawSource() + + val transport = StdioClientTransport( + input = input.buffered(), + output = Buffer(), + ) + + val didClose = AtomicBoolean(false) + transport.onClose { didClose.store(true) } + transport.onError { error -> + fail("Unexpected error while closing transport: $error") + } + + transport.start() + + eventually(2.seconds) { + assertTrue(input.readStarted, "Transport should start reading stdin before close") + } + + val closeJob = async { + transport.close() + } + + val closed = withTimeoutOrNull(1.seconds) { + closeJob.await() + } + + input.close() + closeJob.await() + + assertNotNull(closed, "Transport.close() should not wait for stdin to produce data or EOF") + assertTrue(didClose.load(), "Transport should be closed after close() call") + } + @Test fun `should read messages`() = runTest { val processBuilder = createTeeProcessBuilder() @@ -147,4 +190,22 @@ class StdioClientTransportTest : BaseTransportTest() { process.waitFor() process.destroyForcibly() } + + private class BlockingRawSource : RawSource { + private val closed = CountDownLatch(1) + + @Volatile + var readStarted: Boolean = false + private set + + override fun readAtMostTo(sink: Buffer, byteCount: Long): Long { + readStarted = true + closed.await() + return -1L + } + + override fun close() { + closed.countDown() + } + } } diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt index 63f681c..5e990ee 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt @@ -244,10 +244,20 @@ public class StdioClientTransport @JvmOverloads public constructor( override suspend fun closeResources() { withContext(NonCancellable) { scope.stopProcessing("Closed") + closeReadSources() scope.coroutineContext[Job]?.join() // Wait for all coroutines to complete } } + private fun closeReadSources() { + runCatching { input.close() } + .onFailure { logger.debug(it) { "Error closing stdin source" } } + error?.let { source -> + runCatching { source.close() } + .onFailure { logger.debug(it) { "Error closing stderr source" } } + } + } + private fun sendOutboundMessage(message: JSONRPCMessage, sink: Sink, mainScope: CoroutineScope) { try { val json = serializeMessage(message) From d2b1509147722305da481e3c939940253b066ecd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Jun 2026 11:44:40 +0200 Subject: [PATCH 339/449] chore(deps): bump the other-dependencies group across 5 directories with 5 updates (#865) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the other-dependencies group with 3 updates in the /samples/kotlin-mcp-client directory: [gradle-wrapper](https://github.com/gradle/gradle), [com.anthropic:anthropic-java](https://github.com/anthropics/anthropic-sdk-java) and [com.gradleup.shadow](https://github.com/GradleUp/shadow). Bumps the other-dependencies group with 2 updates in the /samples/kotlin-mcp-server directory: [gradle-wrapper](https://github.com/gradle/gradle) and [com.gradleup.shadow](https://github.com/GradleUp/shadow). Bumps the other-dependencies group with 3 updates in the /samples/kotlinlang-mcp-server directory: [gradle-wrapper](https://github.com/gradle/gradle), [com.gradleup.shadow](https://github.com/GradleUp/shadow) and [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback). Bumps the other-dependencies group with 3 updates in the /samples/simple-streamable-server directory: [gradle-wrapper](https://github.com/gradle/gradle), [com.gradleup.shadow](https://github.com/GradleUp/shadow) and [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit-framework). Bumps the other-dependencies group with 2 updates in the /samples/weather-stdio-server directory: [gradle-wrapper](https://github.com/gradle/gradle) and [com.gradleup.shadow](https://github.com/GradleUp/shadow). Updates `gradle-wrapper` from 9.6.0 to 9.6.1
    Release notes

    Sourced from gradle-wrapper's releases.

    9.6.1

    The Gradle team is excited to announce Gradle 9.6.1.

    Here are the highlights of this release:

    • Improved Configuration Cache hit rates
    • Additional CLI rendering options
    • Important project hierarchy lookup deprecations

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: Aharnish Solanki, Benedikt Johannes, Devendra Reddy Pennabadi, Dmytro Rodionov, Dreeam, Elías Hernández Rodríguez, Eng Zer Jun, FinlayRJW, Kamal Kansal, Marcono1234, Nelson Osacky, Philip Wedemann, Ravi, Roberto Perez Alcolea, Ryan Schmitt, Sebastian Schuberth, seunghun.ham, sk-reddy17, Suvrat Acharya, Vedant Madane.

    Upgrade instructions

    Switch your build to use Gradle 9.6.1 by updating your wrapper:

    ./gradlew :wrapper --gradle-version=9.6.1 &&
    ./gradlew :wrapper
    

    See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

    For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

    Reporting problems

    If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

    We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

    Commits
    • 309d128 Update fixed issues in release notes for 9.6.1 (#38328)
    • 040a978 Update fixed issues in release notes for 9.6.1
    • e0b8325 Restore --non-interactive flag instead of --interactive/--no-interactive (#38...
    • 946f3e6 Limit explicit temp file permission setting to intended use case (#38300)
    • 65f8224 Restore --non-interactive flag instead of --interactive/--no-interactive
    • e346a5e Adjust CLI flag to configure non-interactive console (#38301)
    • 9b53be9 Adjust CLI flag to configure non-interactive console
    • 0dd3b53 Limit explicit temp file permission setting to intended use case
    • 48e5ac2 Add reproducers
    • 25598fd Prepare 9.6.1 patch release (#38293)
    • Additional commits viewable in compare view

    Updates `com.anthropic:anthropic-java` from 2.43.0 to 2.45.0
    Release notes

    Sourced from com.anthropic:anthropic-java's releases.

    v2.45.0

    2.45.0 (2026-06-29)

    Full Changelog: v2.44.1...v2.45.0

    Features

    • api: add support for 20260318 web fetch and support tools (f38d0e6)

    Bug Fixes

    • add web tools 20260318 delegate overloads to StructuredMessageCreateParams (#120) (a66f7a3)

    v2.44.1

    2.44.1 (2026-06-26)

    Full Changelog: v2.44.0...v2.44.1

    Bug Fixes

    • bedrock: use daemon threads for the SSE transcoding pool (#97) (0678c4d)
    • skill creation (#119) (5dadc03)

    Chores

    • api: accept user profile ID's when counting tokens (ee8b72d)
    • delete unnecessary dir (#108) (35a9662)
    • docs: updates to descriptions and example values (6686e6b)

    Build System

    • add detekt rules (#109) (86b8f8d)
    • derive umbrella Dokka aggregation from re-exported modules (#110) (82c00c7)

    v2.44.0

    2.44.0 (2026-06-24)

    Full Changelog: v2.43.0...v2.44.0

    Features

    • client: add support for system.message streaming events (e27e961)

    Chores

    • api: add support for new refusal category (8c1bd29)

    ... (truncated)

    Changelog

    Sourced from com.anthropic:anthropic-java's changelog.

    2.45.0 (2026-06-29)

    Full Changelog: v2.44.1...v2.45.0

    Features

    • api: add support for 20260318 web fetch and support tools (f38d0e6)

    Bug Fixes

    • add web tools 20260318 delegate overloads to StructuredMessageCreateParams (#120) (a66f7a3)

    2.44.1 (2026-06-26)

    Full Changelog: v2.44.0...v2.44.1

    Bug Fixes

    • bedrock: use daemon threads for the SSE transcoding pool (#97) (0678c4d)
    • skill creation (#119) (5dadc03)

    Chores

    • api: accept user profile ID's when counting tokens (ee8b72d)
    • delete unnecessary dir (#108) (35a9662)
    • docs: updates to descriptions and example values (6686e6b)

    Build System

    • add detekt rules (#109) (86b8f8d)
    • derive umbrella Dokka aggregation from re-exported modules (#110) (82c00c7)

    2.44.0 (2026-06-24)

    Full Changelog: v2.43.0...v2.44.0

    Features

    • client: add support for system.message streaming events (e27e961)

    Chores

    • api: add support for new refusal category (8c1bd29)
    • api: add support for sending User Profile ID in request headers (8d69cbe)

    ... (truncated)

    Commits
    • 66da1ba release: 2.45.0
    • ae4a759 fix: add web tools 20260318 delegate overloads to StructuredMessageCreatePara...
    • 89455ee feat(api): add support for 20260318 web fetch and support tools
    • ca57c4e release: 2.44.1
    • 2e9d127 fix: skill creation (#119)
    • 4ee08d6 fix(bedrock): use daemon threads for the SSE transcoding pool (#97)
    • b6bf663 chore(docs): updates to descriptions and example values
    • fea6ca0 build: add detekt rules (#109)
    • 35963af chore(api): accept user profile ID's when counting tokens
    • 0b00c21 chore: delete unnecessary dir (#108)
    • Additional commits viewable in compare view

    Updates `com.gradleup.shadow` from 9.4.2 to 9.4.3
    Release notes

    Sourced from com.gradleup.shadow's releases.

    9.4.3

    Changed

    • Update dependencies for resolving CVEs. (#2069)
    Commits

    Updates `gradle-wrapper` from 9.6.0 to 9.6.1
    Release notes

    Sourced from gradle-wrapper's releases.

    9.6.1

    The Gradle team is excited to announce Gradle 9.6.1.

    Here are the highlights of this release:

    • Improved Configuration Cache hit rates
    • Additional CLI rendering options
    • Important project hierarchy lookup deprecations

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: Aharnish Solanki, Benedikt Johannes, Devendra Reddy Pennabadi, Dmytro Rodionov, Dreeam, Elías Hernández Rodríguez, Eng Zer Jun, FinlayRJW, Kamal Kansal, Marcono1234, Nelson Osacky, Philip Wedemann, Ravi, Roberto Perez Alcolea, Ryan Schmitt, Sebastian Schuberth, seunghun.ham, sk-reddy17, Suvrat Acharya, Vedant Madane.

    Upgrade instructions

    Switch your build to use Gradle 9.6.1 by updating your wrapper:

    ./gradlew :wrapper --gradle-version=9.6.1 &&
    ./gradlew :wrapper
    

    See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

    For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

    Reporting problems

    If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

    We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

    Commits
    • 309d128 Update fixed issues in release notes for 9.6.1 (#38328)
    • 040a978 Update fixed issues in release notes for 9.6.1
    • e0b8325 Restore --non-interactive flag instead of --interactive/--no-interactive (#38...
    • 946f3e6 Limit explicit temp file permission setting to intended use case (#38300)
    • 65f8224 Restore --non-interactive flag instead of --interactive/--no-interactive
    • e346a5e Adjust CLI flag to configure non-interactive console (#38301)
    • 9b53be9 Adjust CLI flag to configure non-interactive console
    • 0dd3b53 Limit explicit temp file permission setting to intended use case
    • 48e5ac2 Add reproducers
    • 25598fd Prepare 9.6.1 patch release (#38293)
    • Additional commits viewable in compare view

    Updates `com.gradleup.shadow` from 9.4.2 to 9.4.3
    Release notes

    Sourced from com.gradleup.shadow's releases.

    9.4.3

    Changed

    • Update dependencies for resolving CVEs. (#2069)
    Commits

    Updates `gradle-wrapper` from 9.6.0 to 9.6.1
    Release notes

    Sourced from gradle-wrapper's releases.

    9.6.1

    The Gradle team is excited to announce Gradle 9.6.1.

    Here are the highlights of this release:

    • Improved Configuration Cache hit rates
    • Additional CLI rendering options
    • Important project hierarchy lookup deprecations

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: Aharnish Solanki, Benedikt Johannes, Devendra Reddy Pennabadi, Dmytro Rodionov, Dreeam, Elías Hernández Rodríguez, Eng Zer Jun, FinlayRJW, Kamal Kansal, Marcono1234, Nelson Osacky, Philip Wedemann, Ravi, Roberto Perez Alcolea, Ryan Schmitt, Sebastian Schuberth, seunghun.ham, sk-reddy17, Suvrat Acharya, Vedant Madane.

    Upgrade instructions

    Switch your build to use Gradle 9.6.1 by updating your wrapper:

    ./gradlew :wrapper --gradle-version=9.6.1 &&
    ./gradlew :wrapper
    

    See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

    For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

    Reporting problems

    If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

    We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

    Commits
    • 309d128 Update fixed issues in release notes for 9.6.1 (#38328)
    • 040a978 Update fixed issues in release notes for 9.6.1
    • e0b8325 Restore --non-interactive flag instead of --interactive/--no-interactive (#38...
    • 946f3e6 Limit explicit temp file permission setting to intended use case (#38300)
    • 65f8224 Restore --non-interactive flag instead of --interactive/--no-interactive
    • e346a5e Adjust CLI flag to configure non-interactive console (#38301)
    • 9b53be9 Adjust CLI flag to configure non-interactive console
    • 0dd3b53 Limit explicit temp file permission setting to intended use case
    • 48e5ac2 Add reproducers
    • 25598fd Prepare 9.6.1 patch release (#38293)
    • Additional commits viewable in compare view

    Updates `com.gradleup.shadow` from 9.4.2 to 9.4.3
    Release notes

    Sourced from com.gradleup.shadow's releases.

    9.4.3

    Changed

    • Update dependencies for resolving CVEs. (#2069)
    Commits

    Updates `gradle-wrapper` from 9.6.0 to 9.6.1
    Release notes

    Sourced from gradle-wrapper's releases.

    9.6.1

    The Gradle team is excited to announce Gradle 9.6.1.

    Here are the highlights of this release:

    • Improved Configuration Cache hit rates
    • Additional CLI rendering options
    • Important project hierarchy lookup deprecations

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: Aharnish Solanki, Benedikt Johannes, Devendra Reddy Pennabadi, Dmytro Rodionov, Dreeam, Elías Hernández Rodríguez, Eng Zer Jun, FinlayRJW, Kamal Kansal, Marcono1234, Nelson Osacky, Philip Wedemann, Ravi, Roberto Perez Alcolea, Ryan Schmitt, Sebastian Schuberth, seunghun.ham, sk-reddy17, Suvrat Acharya, Vedant Madane.

    Upgrade instructions

    Switch your build to use Gradle 9.6.1 by updating your wrapper:

    ./gradlew :wrapper --gradle-version=9.6.1 &&
    ./gradlew :wrapper
    

    See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

    For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

    Reporting problems

    If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

    We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

    Commits
    • 309d128 Update fixed issues in release notes for 9.6.1 (#38328)
    • 040a978 Update fixed issues in release notes for 9.6.1
    • e0b8325 Restore --non-interactive flag instead of --interactive/--no-interactive (#38...
    • 946f3e6 Limit explicit temp file permission setting to intended use case (#38300)
    • 65f8224 Restore --non-interactive flag instead of --interactive/--no-interactive
    • e346a5e Adjust CLI flag to configure non-interactive console (#38301)
    • 9b53be9 Adjust CLI flag to configure non-interactive console
    • 0dd3b53 Limit explicit temp file permission setting to intended use case
    • 48e5ac2 Add reproducers
    • 25598fd Prepare 9.6.1 patch release (#38293)
    • Additional commits viewable in compare view

    Updates `com.gradleup.shadow` from 9.4.2 to 9.4.3
    Release notes

    Sourced from com.gradleup.shadow's releases.

    9.4.3

    Changed

    • Update dependencies for resolving CVEs. (#2069)
    Commits

    Updates `gradle-wrapper` from 9.6.0 to 9.6.1
    Release notes

    Sourced from gradle-wrapper's releases.

    9.6.1

    The Gradle team is excited to announce Gradle 9.6.1.

    Here are the highlights of this release:

    • Improved Configuration Cache hit rates
    • Additional CLI rendering options
    • Important project hierarchy lookup deprecations

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: Aharnish Solanki, Benedikt Johannes, Devendra Reddy Pennabadi, Dmytro Rodionov, Dreeam, Elías Hernández Rodríguez, Eng Zer Jun, FinlayRJW, Kamal Kansal, Marcono1234, Nelson Osacky, Philip Wedemann, Ravi, Roberto Perez Alcolea, Ryan Schmitt, Sebastian Schuberth, seunghun.ham, sk-reddy17, Suvrat Acharya, Vedant Madane.

    Upgrade instructions

    Switch your build to use Gradle 9.6.1 by updating your wrapper:

    ./gradlew :wrapper --gradle-version=9.6.1 &&
    ./gradlew :wrapper
    

    See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

    For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

    Reporting problems

    If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

    We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

    Commits
    • 309d128 Update fixed issues in release notes for 9.6.1 (#38328)
    • 040a978 Update fixed issues in release notes for 9.6.1
    • e0b8325 Restore --non-interactive flag instead of --interactive/--no-interactive (#38...
    • 946f3e6 Limit explicit temp file permission setting to intended use case (#38300)
    • 65f8224 Restore --non-interactive flag instead of --interactive/--no-interactive
    • e346a5e Adjust CLI flag to configure non-interactive console (#38301)
    • 9b53be9 Adjust CLI flag to configure non-interactive console
    • 0dd3b53 Limit explicit temp file permission setting to intended use case
    • 48e5ac2 Add reproducers
    • 25598fd Prepare 9.6.1 patch release (#38293)
    • Additional commits viewable in compare view

    Updates `com.gradleup.shadow` from 9.4.2 to 9.4.3
    Release notes

    Sourced from com.gradleup.shadow's releases.

    9.4.3

    Changed

    • Update dependencies for resolving CVEs. (#2069)
    Commits

    Updates `gradle-wrapper` from 9.6.0 to 9.6.1
    Release notes

    Sourced from gradle-wrapper's releases.

    9.6.1

    The Gradle team is excited to announce Gradle 9.6.1.

    Here are the highlights of this release:

    • Improved Configuration Cache hit rates
    • Additional CLI rendering options
    • Important project hierarchy lookup deprecations

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: Aharnish Solanki, Benedikt Johannes, Devendra Reddy Pennabadi, Dmytro Rodionov, Dreeam, Elías Hernández Rodríguez, Eng Zer Jun, FinlayRJW, Kamal Kansal, Marcono1234, Nelson Osacky, Philip Wedemann, Ravi, Roberto Perez Alcolea, Ryan Schmitt, Sebastian Schuberth, seunghun.ham, sk-reddy17, Suvrat Acharya, Vedant Madane.

    Upgrade instructions

    Switch your build to use Gradle 9.6.1 by updating your wrapper:

    ./gradlew :wrapper --gradle-version=9.6.1 &&
    ./gradlew :wrapper
    

    See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

    For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

    Reporting problems

    If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

    We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

    Commits
    • 309d128 Update fixed issues in release notes for 9.6.1 (#38328)
    • 040a978 Update fixed issues in release notes for 9.6.1
    • e0b8325 Restore --non-interactive flag instead of --interactive/--no-interactive (#38...
    • 946f3e6 Limit explicit temp file permission setting to intended use case (#38300)
    • 65f8224 Restore --non-interactive flag instead of --interactive/--no-interactive
    • e346a5e Adjust CLI flag to configure non-interactive console (#38301)
    • 9b53be9 Adjust CLI flag to configure non-interactive console
    • 0dd3b53 Limit explicit temp file permission setting to intended use case
    • 48e5ac2 Add reproducers
    • 25598fd Prepare 9.6.1 patch release (#38293)
    • Additional commits viewable in compare view

    Updates `com.gradleup.shadow` from 9.4.2 to 9.4.3
    Release notes

    Sourced from com.gradleup.shadow's releases.

    9.4.3

    Changed

    • Update dependencies for resolving CVEs. (#2069)
    Commits

    Updates `gradle-wrapper` from 9.6.0 to 9.6.1
    Release notes

    Sourced from gradle-wrapper's releases.

    9.6.1

    The Gradle team is excited to announce Gradle 9.6.1.

    Here are the highlights of this release:

    • Improved Configuration Cache hit rates
    • Additional CLI rendering options
    • Important project hierarchy lookup deprecations

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: Aharnish Solanki, Benedikt Johannes, Devendra Reddy Pennabadi, Dmytro Rodionov, Dreeam, Elías Hernández Rodríguez, Eng Zer Jun, FinlayRJW, Kamal Kansal, Marcono1234, Nelson Osacky, Philip Wedemann, Ravi, Roberto Perez Alcolea, Ryan Schmitt, Sebastian Schuberth, seunghun.ham, sk-reddy17, Suvrat Acharya, Vedant Madane.

    Upgrade instructions

    Switch your build to use Gradle 9.6.1 by updating your wrapper:

    ./gradlew :wrapper --gradle-version=9.6.1 &&
    ./gradlew :wrapper
    

    See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

    For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

    Reporting problems

    If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

    We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

    Commits
    • 309d128 Update fixed issues in release notes for 9.6.1 (#38328)
    • 040a978 Update fixed issues in release notes for 9.6.1
    • e0b8325 Restore --non-interactive flag instead of --interactive/--no-interactive (#38...
    • 946f3e6 Limit explicit temp file permission setting to intended use case (#38300)
    • 65f8224 Restore --non-interactive flag instead of --interactive/--no-interactive
    • e346a5e Adjust CLI flag to configure non-interactive console (#38301)
    • 9b53be9 Adjust CLI flag to configure non-interactive console
    • 0dd3b53 Limit explicit temp file permission setting to intended use case
    • 48e5ac2 Add reproducers
    • 25598fd Prepare 9.6.1 patch release (#38293)
    • Additional commits viewable in compare view

    Updates `com.gradleup.shadow` from 9.4.2 to 9.4.3
    Release notes

    Sourced from com.gradleup.shadow's releases.

    9.4.3

    Changed

    • Update dependencies for resolving CVEs. (#2069)
    Commits

    Updates `gradle-wrapper` from 9.6.0 to 9.6.1
    Release notes

    Sourced from gradle-wrapper's releases.

    9.6.1

    The Gradle team is excited to announce Gradle 9.6.1.

    Here are the highlights of this release:

    • Improved Configuration Cache hit rates
    • Additional CLI rendering options
    • Important project hierarchy lookup deprecations

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: Aharnish Solanki, Benedikt Johannes, Devendra Reddy Pennabadi, Dmytro Rodionov, Dreeam, Elías Hernández Rodríguez, Eng Zer Jun, FinlayRJW, Kamal Kansal, Marcono1234, Nelson Osacky, Philip Wedemann, Ravi, Roberto Perez Alcolea, Ryan Schmitt, Sebastian Schuberth, seunghun.ham, sk-reddy17, Suvrat Acharya, Vedant Madane.

    Upgrade instructions

    Switch your build to use Gradle 9.6.1 by updating your wrapper:

    ./gradlew :wrapper --gradle-version=9.6.1 &&
    ./gradlew :wrapper
    

    See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

    For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

    Reporting problems

    If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

    We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

    Commits
    • 309d128 Update fixed issues in release notes for 9.6.1 (#38328)
    • 040a978 Update fixed issues in release notes for 9.6.1
    • e0b8325 Restore --non-interactive flag instead of --interactive/--no-interactive (#38...
    • 946f3e6 Limit explicit temp file permission setting to intended use case (#38300)
    • 65f8224 Restore --non-interactive flag instead of --interactive/--no-interactive
    • e346a5e Adjust CLI flag to configure non-interactive console (#38301)
    • 9b53be9 Adjust CLI flag to configure non-interactive console
    • 0dd3b53 Limit explicit temp file permission setting to intended use case
    • 48e5ac2 Add reproducers
    • 25598fd Prepare 9.6.1 patch release (#38293)
    • Additional commits viewable in compare view

    Updates `com.gradleup.shadow` from 9.4.2 to 9.4.3
    Release notes

    Sourced from com.gradleup.shadow's releases.

    9.4.3

    Changed

    • Update dependencies for resolving CVEs. (#2069)
    Commits

    Updates `gradle-wrapper` from 9.6.0 to 9.6.1
    Release notes

    Sourced from gradle-wrapper's releases.

    9.6.1

    The Gradle team is excited to announce Gradle 9.6.1.

    Here are the highlights of this release:

    • Improved Configuration Cache hit rates
    • Additional CLI rendering options
    • Important project hierarchy lookup deprecations

    Read the Release Notes

    We would like to thank the following community members for their contributions to this release of Gradle: Aharnish Solanki, Benedikt Johannes, Devendra Reddy Pennabadi, Dmytro Rodionov, Dreeam, Elías Hernández Rodríguez, Eng Zer Jun, FinlayRJW, Kamal Kansal, Marcono1234, Nelson Osacky, Philip Wedemann, Ravi, Roberto Perez Alcolea, Ryan Schmitt, Sebastian Schuberth, seunghun.ham, sk-reddy17, Suvrat Acharya, Vedant Madane.

    Upgrade instructions

    Switch your build to use Gradle 9.6.1 by updating your wrapper:

    ./gradlew :wrapper --gradle-version=9.6.1 &&
    ./gradlew :wrapper
    

    See the Gradle 9.x upgrade guide to l... _Description has been truncated_ Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-client/gradle/libs.versions.toml | 4 ++-- .../gradle/wrapper/gradle-wrapper.properties | 2 +- samples/kotlin-mcp-server/gradle/libs.versions.toml | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- samples/kotlinlang-mcp-server/gradle/libs.versions.toml | 4 ++-- .../gradle/wrapper/gradle-wrapper.properties | 2 +- samples/simple-streamable-server/gradle/libs.versions.toml | 4 ++-- .../gradle/wrapper/gradle-wrapper.properties | 2 +- samples/weather-stdio-server/gradle/libs.versions.toml | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- 10 files changed, 13 insertions(+), 13 deletions(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index 7479186..0b9018d 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -1,9 +1,9 @@ [versions] -anthropic = "2.43.0" +anthropic = "2.45.0" kotlin = "2.3.21" ktor = "3.3.3" mcp-kotlin = "0.13.0" -shadow = "9.4.2" +shadow = "9.4.3" slf4j = "2.0.18" [libraries] diff --git a/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties b/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties index eb84db6..a9db115 100644 --- a/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties +++ b/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 diff --git a/samples/kotlin-mcp-server/gradle/libs.versions.toml b/samples/kotlin-mcp-server/gradle/libs.versions.toml index bd87373..56bf5f1 100644 --- a/samples/kotlin-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-server/gradle/libs.versions.toml @@ -3,7 +3,7 @@ kotlin = "2.3.21" ktor = "3.3.3" mcp-kotlin = "0.13.0" slf4j = "2.0.18" -shadow = "9.4.2" +shadow = "9.4.3" [libraries] ktor-bom = { group = "io.ktor", name = "ktor-bom", version.ref = "ktor" } diff --git a/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties b/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties index eb84db6..a9db115 100644 --- a/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties +++ b/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 diff --git a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml index 69dfe7e..26e89b2 100644 --- a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml @@ -4,8 +4,8 @@ ktor = "3.3.3" mcp-kotlin = "0.13.0" serialization = "1.11.0" coroutines = "1.11.0" -logback = "1.5.35" -shadow = "9.4.2" +logback = "1.5.37" +shadow = "9.4.3" [libraries] ktor-bom = { group = "io.ktor", name = "ktor-bom", version.ref = "ktor" } diff --git a/samples/kotlinlang-mcp-server/gradle/wrapper/gradle-wrapper.properties b/samples/kotlinlang-mcp-server/gradle/wrapper/gradle-wrapper.properties index eb84db6..a9db115 100644 --- a/samples/kotlinlang-mcp-server/gradle/wrapper/gradle-wrapper.properties +++ b/samples/kotlinlang-mcp-server/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 diff --git a/samples/simple-streamable-server/gradle/libs.versions.toml b/samples/simple-streamable-server/gradle/libs.versions.toml index ba5b0e7..723994b 100644 --- a/samples/simple-streamable-server/gradle/libs.versions.toml +++ b/samples/simple-streamable-server/gradle/libs.versions.toml @@ -1,10 +1,10 @@ [versions] kotlin = "2.3.21" ktor = "3.3.3" -junit = "6.1.0" +junit = "6.1.1" mcp-kotlin = "0.13.0" slf4j = "2.0.18" -shadow = "9.4.2" +shadow = "9.4.3" [libraries] ktor-bom = { group = "io.ktor", name = "ktor-bom", version.ref = "ktor" } diff --git a/samples/simple-streamable-server/gradle/wrapper/gradle-wrapper.properties b/samples/simple-streamable-server/gradle/wrapper/gradle-wrapper.properties index eb84db6..a9db115 100644 --- a/samples/simple-streamable-server/gradle/wrapper/gradle-wrapper.properties +++ b/samples/simple-streamable-server/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 diff --git a/samples/weather-stdio-server/gradle/libs.versions.toml b/samples/weather-stdio-server/gradle/libs.versions.toml index 3e99ead..e805236 100644 --- a/samples/weather-stdio-server/gradle/libs.versions.toml +++ b/samples/weather-stdio-server/gradle/libs.versions.toml @@ -4,7 +4,7 @@ kotlin = "2.3.21" ktor = "3.3.3" logging = "8.0.4" mcp-kotlin = "0.13.0" -shadow = "9.4.2" +shadow = "9.4.3" slf4j = "2.0.18" [libraries] diff --git a/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties b/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties index eb84db6..a9db115 100644 --- a/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties +++ b/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 From bbf77bae93970d382ef97bf1569d57ed6d434ded Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Jun 2026 12:18:27 +0200 Subject: [PATCH 340/449] chore(deps): bump @types/node from 26.0.0 to 26.0.1 in /integration-test/src/jvmTest/typescript in the all-dependencies group across 1 directory (#864) Bumps the all-dependencies group with 1 update in the /integration-test/src/jvmTest/typescript directory: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node). Updates `@types/node` from 26.0.0 to 26.0.1

    Commits

    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- integration-test/src/jvmTest/typescript/package-lock.json | 8 ++++---- integration-test/src/jvmTest/typescript/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index 0fa51a9..befd50e 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -15,7 +15,7 @@ "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^26.0.0", + "@types/node": "^26.0.1", "cors": "^2.8.6", "tsx": "^4.22.4", "typescript": "^6.0.3" @@ -579,9 +579,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.0.tgz", - "integrity": "sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==", + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.1.tgz", + "integrity": "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/integration-test/src/jvmTest/typescript/package.json b/integration-test/src/jvmTest/typescript/package.json index 00f0961..aa8d9db 100644 --- a/integration-test/src/jvmTest/typescript/package.json +++ b/integration-test/src/jvmTest/typescript/package.json @@ -16,7 +16,7 @@ "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^26.0.0", + "@types/node": "^26.0.1", "cors": "^2.8.6", "tsx": "^4.22.4", "typescript": "^6.0.3" From 35da063c23e0e89571939d8cb0cba6621d997ebe Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Tue, 30 Jun 2026 20:43:45 +0200 Subject: [PATCH 341/449] fix: bound request body size in SSE and Streamable HTTP transports (#839) Caps the size of incoming HTTP POST bodies in both server transports so an oversized request can no longer exhaust server memory ## How Has This Been Tested? new tests ## Breaking Changes binary incompatible for SseServerTransport constuctors ## Types of changes - [x] Bug fix (non-breaking change which fixes a - [x] New feature (non-breaking change which adds functionality) - [x] Breaking change (fix or feature that would to change) - [ ] Documentation update ## Checklist - [x] I have read the [MCP Documentation](https: - [x] My code follows the repository's style guidelines - [x] New and existing tests pass locally - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- kotlin-sdk-server/api/kotlin-sdk-server.api | 15 +-- .../kotlin/sdk/server/KtorServer.kt | 23 +++- .../kotlin/sdk/server/RequestBody.kt | 34 ++++++ .../kotlin/sdk/server/SSEServerTransport.kt | 21 +++- .../server/StreamableHttpServerTransport.kt | 20 +--- .../kotlin/sdk/server/RequestBodyTest.kt | 103 ++++++++++++++++++ .../sdk/server/SseServerTransportTest.kt | 90 +++++++++++++++ .../StreamableHttpServerTransportTest.kt | 38 ++++++- 8 files changed, 308 insertions(+), 36 deletions(-) create mode 100644 kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/RequestBody.kt create mode 100644 kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/RequestBodyTest.kt create mode 100644 kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SseServerTransportTest.kt diff --git a/kotlin-sdk-server/api/kotlin-sdk-server.api b/kotlin-sdk-server/api/kotlin-sdk-server.api index 3acaa64..6094b7c 100644 --- a/kotlin-sdk-server/api/kotlin-sdk-server.api +++ b/kotlin-sdk-server/api/kotlin-sdk-server.api @@ -51,12 +51,12 @@ public final class io/modelcontextprotocol/kotlin/sdk/server/HostValidationKt { } public final class io/modelcontextprotocol/kotlin/sdk/server/KtorServerKt { - public static final fun mcp (Lio/ktor/server/application/Application;ZLjava/util/List;Ljava/util/List;Lkotlin/jvm/functions/Function1;)V - public static final fun mcp (Lio/ktor/server/routing/Route;Ljava/lang/String;ZLjava/util/List;Ljava/util/List;Lkotlin/jvm/functions/Function1;)V - public static final fun mcp (Lio/ktor/server/routing/Route;ZLjava/util/List;Ljava/util/List;Lkotlin/jvm/functions/Function1;)V - public static synthetic fun mcp$default (Lio/ktor/server/application/Application;ZLjava/util/List;Ljava/util/List;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V - public static synthetic fun mcp$default (Lio/ktor/server/routing/Route;Ljava/lang/String;ZLjava/util/List;Ljava/util/List;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V - public static synthetic fun mcp$default (Lio/ktor/server/routing/Route;ZLjava/util/List;Ljava/util/List;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V + public static final fun mcp (Lio/ktor/server/application/Application;ZLjava/util/List;Ljava/util/List;JLkotlin/jvm/functions/Function1;)V + public static final fun mcp (Lio/ktor/server/routing/Route;Ljava/lang/String;ZLjava/util/List;Ljava/util/List;JLkotlin/jvm/functions/Function1;)V + public static final fun mcp (Lio/ktor/server/routing/Route;ZLjava/util/List;Ljava/util/List;JLkotlin/jvm/functions/Function1;)V + public static synthetic fun mcp$default (Lio/ktor/server/application/Application;ZLjava/util/List;Ljava/util/List;JLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V + public static synthetic fun mcp$default (Lio/ktor/server/routing/Route;Ljava/lang/String;ZLjava/util/List;Ljava/util/List;JLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V + public static synthetic fun mcp$default (Lio/ktor/server/routing/Route;ZLjava/util/List;Ljava/util/List;JLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V public static final fun mcpStatelessStreamableHttp (Lio/ktor/server/application/Application;Ljava/lang/String;ZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/jvm/functions/Function1;)V public static synthetic fun mcpStatelessStreamableHttp$default (Lio/ktor/server/application/Application;Ljava/lang/String;ZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V public static final fun mcpStreamableHttp (Lio/ktor/server/application/Application;Ljava/lang/String;ZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/jvm/functions/Function1;)V @@ -208,7 +208,8 @@ public class io/modelcontextprotocol/kotlin/sdk/server/ServerSession : io/modelc } public final class io/modelcontextprotocol/kotlin/sdk/server/SseServerTransport : io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport { - public fun (Ljava/lang/String;Lio/ktor/server/sse/ServerSSESession;)V + public fun (Ljava/lang/String;Lio/ktor/server/sse/ServerSSESession;J)V + public synthetic fun (Ljava/lang/String;Lio/ktor/server/sse/ServerSSESession;JILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun getSessionId ()Ljava/lang/String; public final fun handleMessage (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt index 7e47d76..ec0652b 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt @@ -44,6 +44,8 @@ private val logger = KotlinLogging.logger {} * When `null` while the localhost host defaults are in effect (no custom `allowedHosts`), * the `Origin` header is validated against `localhost`, `127.0.0.1`, `[::1]`. * With custom `allowedHosts`, `null` skips origin validation. + * @param maxRequestBodySize maximum allowed size, in bytes, of an incoming POST body; larger requests are + * rejected with `413 Payload Too Large`. Defaults to 4 MiB. * @param block factory block with access to the [ServerSSESession] * that creates and returns the [Server] to handle the connection. * @throws IllegalStateException if the [SSE] plugin is not installed. @@ -55,10 +57,11 @@ public fun Route.mcp( enableDnsRebindingProtection: Boolean = true, allowedHosts: List? = null, allowedOrigins: List? = null, + maxRequestBodySize: Long = DEFAULT_MAX_REQUEST_BODY_SIZE, block: ServerSSESession.() -> Server, ) { route(path) { - mcp(enableDnsRebindingProtection, allowedHosts, allowedOrigins, block) + mcp(enableDnsRebindingProtection, allowedHosts, allowedOrigins, maxRequestBodySize, block) } } @@ -76,15 +79,19 @@ public fun Route.mcp( * When `null` while the localhost host defaults are in effect (no custom `allowedHosts`), * the `Origin` header is validated against `localhost`, `127.0.0.1`, `[::1]`. * With custom `allowedHosts`, `null` skips origin validation. + * @param maxRequestBodySize maximum allowed size, in bytes, of an incoming POST body; larger requests are + * rejected with `413 Payload Too Large`. Defaults to 4 MiB. * @param block factory block with access to the [ServerSSESession] * that creates and returns the [Server] to handle the connection. * @throws IllegalStateException if the [SSE] plugin is not installed. */ @KtorDsl +@Suppress("LongParameterList") public fun Route.mcp( enableDnsRebindingProtection: Boolean = true, allowedHosts: List? = null, allowedOrigins: List? = null, + maxRequestBodySize: Long = DEFAULT_MAX_REQUEST_BODY_SIZE, block: ServerSSESession.() -> Server, ) { try { @@ -103,7 +110,7 @@ public fun Route.mcp( val transportManager = TransportManager() sse { - mcpSseEndpoint("", transportManager, block) + mcpSseEndpoint("", transportManager, maxRequestBodySize, block) } post { @@ -126,21 +133,25 @@ public fun Route.mcp( * When `null` while the localhost host defaults are in effect (no custom `allowedHosts`), * the `Origin` header is validated against `localhost`, `127.0.0.1`, `[::1]`. * With custom `allowedHosts`, `null` skips origin validation. + * @param maxRequestBodySize maximum allowed size, in bytes, of an incoming POST body; larger requests are + * rejected with `413 Payload Too Large`. Defaults to 4 MiB. * @param block factory block with access to the [ServerSSESession] * that creates and returns the [Server] to handle the connection. */ @KtorDsl +@Suppress("LongParameterList") public fun Application.mcp( enableDnsRebindingProtection: Boolean = true, allowedHosts: List? = null, allowedOrigins: List? = null, + maxRequestBodySize: Long = DEFAULT_MAX_REQUEST_BODY_SIZE, block: ServerSSESession.() -> Server, ) { installMcpContentNegotiation() install(SSE) routing { - mcp(enableDnsRebindingProtection, allowedHosts, allowedOrigins, block) + mcp(enableDnsRebindingProtection, allowedHosts, allowedOrigins, maxRequestBodySize, block) } } @@ -327,9 +338,10 @@ public fun Application.mcpStatelessStreamableHttp( private suspend fun ServerSSESession.mcpSseEndpoint( postEndpoint: String, transportManager: TransportManager, + maxRequestBodySize: Long, block: ServerSSESession.() -> Server, ) { - val transport = mcpSseTransport(postEndpoint, transportManager) + val transport = mcpSseTransport(postEndpoint, transportManager, maxRequestBodySize) val server = block() @@ -348,8 +360,9 @@ private suspend fun ServerSSESession.mcpSseEndpoint( private fun ServerSSESession.mcpSseTransport( postEndpoint: String, transportManager: TransportManager, + maxRequestBodySize: Long, ): SseServerTransport { - val transport = SseServerTransport(postEndpoint, this) + val transport = SseServerTransport(postEndpoint, this, maxRequestBodySize) transportManager.addTransport(transport.sessionId, transport) logger.info { "New SSE connection established and stored with sessionId: ${transport.sessionId}" } diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/RequestBody.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/RequestBody.kt new file mode 100644 index 0000000..0cc4143 --- /dev/null +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/RequestBody.kt @@ -0,0 +1,34 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.ktor.server.application.ApplicationCall +import io.ktor.server.request.receiveChannel +import io.ktor.utils.io.readRemaining +import kotlinx.io.Buffer +import kotlinx.io.IOException +import kotlinx.io.readString + +/** Default maximum size (in bytes) for an incoming HTTP request body: 4 MiB. */ +internal const val DEFAULT_MAX_REQUEST_BODY_SIZE: Long = 4L * 1024 * 1024 + +/** Signals that an incoming request body exceeded the maximum size a transport is willing to read. */ +internal class RequestBodyTooLargeException(val maxBodySize: Long) : + IOException("Request body exceeds the maximum of $maxBodySize bytes.") + +/** + * Reads the request body as a UTF-8 string, rejecting bodies larger than [maxBytes]. + * + * At most `maxBytes + 1` bytes are ever pulled from the request channel, so an oversized or unframed + * body cannot exhaust memory regardless of the (untrusted) `Content-Length` header. + * + * @throws RequestBodyTooLargeException if the body exceeds [maxBytes] + */ +internal suspend fun ApplicationCall.receiveTextWithLimit(maxBytes: Long): String { + val buffer = Buffer() + val readLimit = if (maxBytes == Long.MAX_VALUE) maxBytes else maxBytes + 1 + val read = receiveChannel().readRemaining(readLimit).transferTo(buffer) + if (read > maxBytes) { + buffer.clear() + throw RequestBodyTooLargeException(maxBytes) + } + return buffer.readString() +} diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt index a6d4618..f525ec2 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt @@ -5,7 +5,6 @@ import io.ktor.http.HttpStatusCode import io.ktor.http.encodeURLPath import io.ktor.server.application.ApplicationCall import io.ktor.server.request.contentType -import io.ktor.server.request.receiveText import io.ktor.server.response.respondText import io.ktor.server.sse.ServerSSESession import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport @@ -29,10 +28,19 @@ internal const val SESSION_ID_PARAM = "sessionId" * * @param endpoint relative or absolute URL the client will POST messages to * @param session active SSE session used to deliver server-to-client events + * @param maxRequestBodySize maximum allowed size, in bytes, of an incoming POST body; larger requests are + * rejected with `413 Payload Too Large` without being buffered in full. Defaults to 4 MiB. */ @OptIn(ExperimentalAtomicApi::class) -public class SseServerTransport(private val endpoint: String, private val session: ServerSSESession) : - AbstractTransport() { +public class SseServerTransport( + private val endpoint: String, + private val session: ServerSSESession, + private val maxRequestBodySize: Long = DEFAULT_MAX_REQUEST_BODY_SIZE, +) : AbstractTransport() { + init { + require(maxRequestBodySize > 0) { "maxRequestBodySize must be greater than 0" } + } + private val initialized: AtomicBoolean = AtomicBoolean(false) /** Unique identifier for this transport session, generated randomly on creation. */ @@ -77,6 +85,7 @@ public class SseServerTransport(private val endpoint: String, private val sessio val message = "SSE connection not established" call.respondText(message, status = HttpStatusCode.InternalServerError) _onError.invoke(IllegalStateException(message)) + return } val body = try { @@ -85,9 +94,13 @@ public class SseServerTransport(private val endpoint: String, private val sessio error("Unsupported content-type: $ct") } - call.receiveText() + call.receiveTextWithLimit(maxRequestBodySize) } catch (e: CancellationException) { throw e + } catch (e: RequestBodyTooLargeException) { + call.respondText(e.message ?: "Request body too large", status = HttpStatusCode.PayloadTooLarge) + _onError.invoke(e) + return } catch (e: Exception) { call.respondText("Invalid message: ${e.message}", status = HttpStatusCode.BadRequest) _onError.invoke(e) diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt index ae8a39d..c213350 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt @@ -8,7 +8,6 @@ import io.ktor.server.application.ApplicationCall import io.ktor.server.request.contentType import io.ktor.server.request.header import io.ktor.server.request.httpMethod -import io.ktor.server.request.receiveText import io.ktor.server.response.header import io.ktor.server.response.respond import io.ktor.server.response.respondNullable @@ -48,7 +47,6 @@ import kotlin.uuid.Uuid internal const val MCP_SESSION_ID_HEADER = "mcp-session-id" private const val MCP_PROTOCOL_VERSION_HEADER = "mcp-protocol-version" private const val MCP_RESUMPTION_TOKEN_HEADER = "Last-Event-ID" -private const val DEFAULT_MAX_REQUEST_BODY_SIZE: Long = 4L * 1024 * 1024 // 4 MB private const val MIN_PRIMING_EVENT_PROTOCOL_VERSION = "2025-11-25" /** @@ -703,23 +701,13 @@ public class StreamableHttpServerTransport(private val configuration: Configurat } private suspend fun parseBody(call: ApplicationCall): List? { - val maxSize = configuration.maxRequestBodySize - val contentLength = call.request.header(HttpHeaders.ContentLength)?.toLongOrNull() ?: 0L - if (contentLength > maxSize) { + val body = try { + call.receiveTextWithLimit(configuration.maxRequestBodySize) + } catch (e: RequestBodyTooLargeException) { call.reject( HttpStatusCode.PayloadTooLarge, RPCError.ErrorCode.INVALID_REQUEST, - "Invalid Request: message size exceeds maximum of $maxSize bytes", - ) - return null - } - - val body = call.receiveText() - if (body.length.toLong() > maxSize) { - call.reject( - HttpStatusCode.PayloadTooLarge, - RPCError.ErrorCode.INVALID_REQUEST, - "Invalid Request: message size exceeds maximum of $maxSize bytes", + "Invalid Request: message size exceeds maximum of ${e.maxBodySize} bytes", ) return null } diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/RequestBodyTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/RequestBodyTest.kt new file mode 100644 index 0000000..9dc30cf --- /dev/null +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/RequestBodyTest.kt @@ -0,0 +1,103 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.kotest.matchers.shouldBe +import io.ktor.client.request.post +import io.ktor.client.request.setBody +import io.ktor.client.statement.bodyAsText +import io.ktor.http.HttpStatusCode +import io.ktor.server.response.respondText +import io.ktor.server.routing.post +import io.ktor.server.routing.routing +import io.ktor.server.testing.ApplicationTestBuilder +import io.ktor.server.testing.testApplication +import kotlin.test.Test + +class RequestBodyTest { + + /** Wires a POST endpoint that echoes the body when it fits, or replies 413 when it is too large. */ + private fun ApplicationTestBuilder.installEchoEndpoint(maxBytes: Long) { + install(io.ktor.server.sse.SSE) + application { + routing { + post("/echo") { + val text = try { + call.receiveTextWithLimit(maxBytes) + } catch (e: RequestBodyTooLargeException) { + call.respondText(e.message ?: "too large", status = HttpStatusCode.PayloadTooLarge) + return@post + } + call.respondText(text) + } + } + } + } + + @Test + fun `body under the limit is returned intact`() = testApplication { + installEchoEndpoint(maxBytes = 1024) + val payload = "hello world" + + val response = client.post("/echo") { setBody(payload) } + + response.status shouldBe HttpStatusCode.OK + response.bodyAsText() shouldBe payload + } + + @Test + fun `body exactly at the limit is accepted`() = testApplication { + val payload = "x".repeat(64) + installEchoEndpoint(maxBytes = payload.length.toLong()) + + val response = client.post("/echo") { setBody(payload) } + + response.status shouldBe HttpStatusCode.OK + response.bodyAsText() shouldBe payload + } + + @Test + fun `body exceeding the limit is rejected with 413`() = testApplication { + val payload = "x".repeat(65) + installEchoEndpoint(maxBytes = 64) + + val response = client.post("/echo") { setBody(payload) } + + response.status shouldBe HttpStatusCode.PayloadTooLarge + } + + @Test + fun `large body exceeding the limit is rejected without buffering it whole`() = testApplication { + // 8 MB body against a 4 MB limit: must be rejected. + installEchoEndpoint(maxBytes = 4L * 1024 * 1024) + val payload = "x".repeat(8 * 1024 * 1024) + + val response = client.post("/echo") { setBody(payload) } + + response.status shouldBe HttpStatusCode.PayloadTooLarge + } + + @Test + fun `multibyte UTF-8 body larger than a read chunk decodes intact`() = testApplication { + // Emoji is 4 bytes in UTF-8; a payload larger than the internal read chunk forces the + // accumulated bytes to span multiple chunks. Decoding once at the end must not corrupt + // characters that straddle a chunk boundary. + val unit = "a😀b" // mixes 1- and 4-byte code units so boundaries land mid-character + val payload = unit.repeat(40_000) // well over a 64 KiB read chunk + installEchoEndpoint(maxBytes = payload.encodeToByteArray().size.toLong()) + + val response = client.post("/echo") { setBody(payload) } + + response.status shouldBe HttpStatusCode.OK + response.bodyAsText() shouldBe payload + } + + @Test + fun `body is returned intact when the limit is Long MAX_VALUE`() = testApplication { + installEchoEndpoint(maxBytes = Long.MAX_VALUE) + val payload = "hello world" + + val response = client.post("/echo") { setBody(payload) } + + response.status shouldBe HttpStatusCode.OK + response.bodyAsText() shouldBe payload + } +} diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SseServerTransportTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SseServerTransportTest.kt new file mode 100644 index 0000000..a0756f3 --- /dev/null +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SseServerTransportTest.kt @@ -0,0 +1,90 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.kotest.matchers.shouldBe +import io.ktor.client.request.post +import io.ktor.client.request.prepareGet +import io.ktor.client.request.setBody +import io.ktor.client.statement.HttpResponse +import io.ktor.client.statement.bodyAsChannel +import io.ktor.http.ContentType +import io.ktor.http.HttpStatusCode +import io.ktor.http.contentType +import io.ktor.server.application.install +import io.ktor.server.routing.post +import io.ktor.server.routing.routing +import io.ktor.server.sse.SSE +import io.ktor.server.sse.ServerSSESession +import io.ktor.server.testing.testApplication +import io.ktor.utils.io.readLine +import io.mockk.mockk +import java.util.concurrent.atomic.AtomicBoolean +import kotlin.test.Test + +class SseServerTransportTest : AbstractKtorExtensionsTest() { + + @Test + fun `handlePostMessage on a not-started transport does not deliver the message`() = testApplication { + // A registered onMessage callback opens the delivery gate, so if the not-initialized branch + // falls through it would wrongly hand the message to the application. It must return instead. + val transport = SseServerTransport("/messages", mockk(relaxed = true)) + val delivered = AtomicBoolean(false) + transport.onMessage { delivered.set(true) } + + application { + routing { + post("/messages") { transport.handlePostMessage(call) } + } + } + + val response = client.post("/messages") { + contentType(ContentType.Application.Json) + setBody("""{"jsonrpc":"2.0","id":1,"method":"ping"}""") + } + + response.status shouldBe HttpStatusCode.InternalServerError + delivered.get() shouldBe false + } + + @Test + fun `SSE POST exceeding maxRequestBodySize is rejected with 413`() = testApplication { + application { + install(SSE) + routing { + mcp(enableDnsRebindingProtection = false, maxRequestBodySize = MAX_BODY) { testServer() } + } + } + + client.prepareGet("/").execute { response -> + val sessionId = response.readSessionId() + requireNotNull(sessionId) { "sessionId not found in SSE endpoint event" } + + // A body one byte over the configured limit must be rejected before processing. + val oversized = "x".repeat((MAX_BODY + 1).toInt()) + val postResponse = client.post("/?sessionId=$sessionId") { + contentType(ContentType.Application.Json) + setBody(oversized) + } + postResponse.status shouldBe HttpStatusCode.PayloadTooLarge + } + } + + private suspend fun HttpResponse.readSessionId(): String? { + val channel = bodyAsChannel() + var eventName: String? = null + while (!channel.isClosedForRead) { + val line = channel.readLine() ?: break + when { + line.startsWith("event:") -> eventName = line.substringAfter("event:").trim() + + line.startsWith("data:") && eventName == "endpoint" -> { + return line.substringAfter("data:").trim().substringAfter("sessionId=").ifEmpty { null } + } + } + } + return null + } + + private companion object { + const val MAX_BODY = 1024L + } +} diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt index f6dda15..d7dec4e 100644 --- a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt @@ -24,6 +24,8 @@ import io.ktor.server.routing.post import io.ktor.server.routing.routing import io.ktor.server.testing.ApplicationTestBuilder import io.ktor.server.testing.testApplication +import io.ktor.utils.io.ByteReadChannel +import io.ktor.utils.io.readLine import io.ktor.utils.io.readUTF8Line import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities import io.modelcontextprotocol.kotlin.sdk.types.EmptyResult @@ -421,6 +423,34 @@ class StreamableHttpServerTransportTest { response.status shouldBe HttpStatusCode.PayloadTooLarge } + @Test + fun `POST with oversized chunked body and no Content-Length returns 413`() = testApplication { + configTestServer() + + val client = createTestClient() + + val transport = StreamableHttpServerTransport( + StreamableHttpServerTransport.Configuration(enableJsonResponse = true, maxRequestBodySize = 1024), + ) + transport.onMessage { message -> + if (message is JSONRPCRequest) { + transport.send(JSONRPCResponse(message.id, EmptyResult())) + } + } + + configureTransportEndpoint(transport) + + // Stream the body as a channel so the client uses chunked transfer-encoding with no + // Content-Length header: the size limit must hold without trusting the (absent) header. + val oversized = ByteReadChannel("x".repeat(4096).encodeToByteArray()) + val response = client.post(path) { + addStreamableHeaders() + setBody(oversized) + } + + response.status shouldBe HttpStatusCode.PayloadTooLarge + } + @ParameterizedTest @MethodSource("maxBodySizeTestCases") fun `POST with custom max request body size validates payload size`( @@ -507,7 +537,7 @@ class StreamableHttpServerTransportTest { // New stream is alive val secondChannel = secondResponse.bodyAsChannel() - val firstLine = secondChannel.readUTF8Line() + val firstLine = secondChannel.readLine() firstLine.shouldNotBeNull() secondChannel.isClosedForRead shouldBe false } @@ -546,7 +576,7 @@ class StreamableHttpServerTransportTest { header("mcp-protocol-version", LATEST_PROTOCOL_VERSION) }.execute { response -> response.status shouldBe HttpStatusCode.OK - response.bodyAsChannel().readUTF8Line() + response.bodyAsChannel().readLine() } // Step 3: Immediately reconnect — the transport should close the stale @@ -561,7 +591,7 @@ class StreamableHttpServerTransportTest { response.headers[MCP_SESSION_ID_HEADER] shouldBe sessionId val channel = response.bodyAsChannel() - val firstLine = channel.readUTF8Line() + val firstLine = channel.readLine() firstLine.shouldNotBeNull() channel.isClosedForRead shouldBe false } @@ -604,7 +634,7 @@ class StreamableHttpServerTransportTest { // Verify the stream is alive by reading at least one line (flush event) val channel = response.bodyAsChannel() - val firstLine = channel.readUTF8Line() + val firstLine = channel.readLine() firstLine.shouldNotBeNull() channel.isClosedForRead shouldBe false } From 57e546d378b8fb7251d7aeecc3452cb9fdf7c9cf Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Tue, 30 Jun 2026 22:04:15 +0200 Subject: [PATCH 342/449] feat: SEP-1686 Tasks (#844) finished SEP-1686 closes #421 ## 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- kotlin-sdk-core/api/kotlin-sdk-core.api | 26 ++++++---- .../kotlin/sdk/types/sampling.kt | 9 ++++ .../kotlin/sdk/types/tools.kt | 9 ++++ .../kotlin/sdk/types/SamplingTest.kt | 20 ++++++++ .../kotlin/sdk/types/ToolsTest.kt | 51 +++++++++++++++++++ 5 files changed, 105 insertions(+), 10 deletions(-) diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index 1904dc7..cdee342 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -390,6 +390,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CallToolRequest : io public final fun getName ()Ljava/lang/String; public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams; public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; + public final fun getTask ()Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -421,17 +422,19 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CallToolRequestBuild public final class io/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams$Companion; - public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Lkotlinx/serialization/json/JsonObject; - public final fun component3-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; - public final fun copy-EDEVuBg (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams; - public static synthetic fun copy-EDEVuBg$default (Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams; + public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata; + public final fun component4-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun copy-6R815Ik (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams; + public static synthetic fun copy-6R815Ik$default (Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams; public fun equals (Ljava/lang/Object;)Z public final fun getArguments ()Lkotlinx/serialization/json/JsonObject; public fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; public final fun getName ()Ljava/lang/String; + public final fun getTask ()Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -1161,6 +1164,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; public final fun getStopSequences ()Ljava/util/List; public final fun getSystemPrompt ()Ljava/lang/String; + public final fun getTask ()Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata; public final fun getTemperature ()Ljava/lang/Double; public fun hashCode ()I public fun toString ()Ljava/lang/String; @@ -1205,11 +1209,12 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest public final class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams$Companion; - public synthetic fun (ILjava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext;Ljava/lang/Double;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun (ILjava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext;Ljava/lang/Double;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (ILjava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext;Ljava/lang/Double;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice;Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (ILjava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext;Ljava/lang/Double;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice;Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()I public final fun component10 ()Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice; - public final fun component11-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public final fun component11 ()Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata; + public final fun component12-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; public final fun component2 ()Ljava/util/List; public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences; public final fun component4 ()Ljava/lang/String; @@ -1218,8 +1223,8 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest public final fun component7 ()Ljava/util/List; public final fun component8 ()Lkotlinx/serialization/json/JsonObject; public final fun component9 ()Ljava/util/List; - public final fun copy-APmi5RE (ILjava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext;Ljava/lang/Double;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams; - public static synthetic fun copy-APmi5RE$default (Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams;ILjava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext;Ljava/lang/Double;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams; + public final fun copy-7OuPiQU (ILjava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext;Ljava/lang/Double;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice;Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams; + public static synthetic fun copy-7OuPiQU$default (Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams;ILjava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext;Ljava/lang/Double;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice;Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestParams; public fun equals (Ljava/lang/Object;)Z public final fun getIncludeContext ()Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext; public final fun getMaxTokens ()I @@ -1229,6 +1234,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest public final fun getModelPreferences ()Lio/modelcontextprotocol/kotlin/sdk/types/ModelPreferences; public final fun getStopSequences ()Ljava/util/List; public final fun getSystemPrompt ()Ljava/lang/String; + public final fun getTask ()Lio/modelcontextprotocol/kotlin/sdk/types/TaskMetadata; public final fun getTemperature ()Ljava/lang/Double; public final fun getToolChoice ()Lio/modelcontextprotocol/kotlin/sdk/types/ToolChoice; public final fun getTools ()Ljava/util/List; diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.kt index 4526405..7cc2685 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.kt @@ -182,6 +182,12 @@ public data class CreateMessageRequest(override val params: CreateMessageRequest public val metadata: JsonObject? get() = params.metadata + /** + * Task metadata, if the caller is requesting task-augmented execution. + */ + public val task: TaskMetadata? + get() = params.task + /** * Metadata for this request. May include a progressToken for out-of-band progress notifications. */ @@ -216,6 +222,8 @@ public data class CreateMessageRequest(override val params: CreateMessageRequest * @property toolChoice Optional policy controlling how the model uses the provided [tools]. * The client MUST return an error if this field is present but the client did not advertise * [ClientCapabilities.Sampling.tools]. + * @property task If set, requests task-augmented execution. A receiver that supports it returns + * a [CreateTaskResult] (result via `tasks/result`); otherwise it returns a [CreateMessageResult] directly. * @property meta Optional metadata for this request. May include a progressToken for * out-of-band progress notifications. */ @@ -231,6 +239,7 @@ public data class CreateMessageRequestParams( val metadata: JsonObject? = null, val tools: List? = null, val toolChoice: ToolChoice? = null, + val task: TaskMetadata? = null, @SerialName("_meta") override val meta: RequestMeta? = null, ) : RequestParams diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt index 2ff890a..1f0f54e 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt @@ -203,6 +203,12 @@ public data class CallToolRequest(override val params: CallToolRequestParams) : public val arguments: JsonObject? get() = params.arguments + /** + * Task metadata, if the caller is requesting task-augmented execution. + */ + public val task: TaskMetadata? + get() = params.task + /** * Metadata for this request. May include a progressToken for out-of-band progress notifications. */ @@ -216,6 +222,8 @@ public data class CallToolRequest(override val params: CallToolRequestParams) : * @property name The name of the tool to invoke. * @property arguments Arguments to pass to the tool. Keys are argument names, values are the argument values. * The structure must match the tool's input schema. + * @property task If set, requests task-augmented execution. A receiver that supports it returns + * a [CreateTaskResult] (result via `tasks/result`); otherwise it returns a [CallToolResult] directly. * @property meta Optional metadata for this request. May include a progressToken for * out-of-band progress notifications. */ @@ -223,6 +231,7 @@ public data class CallToolRequest(override val params: CallToolRequestParams) : public data class CallToolRequestParams( val name: String, val arguments: JsonObject? = null, + val task: TaskMetadata? = null, @SerialName("_meta") override val meta: RequestMeta? = null, ) : RequestParams diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/SamplingTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/SamplingTest.kt index b2c8cf0..b7bc5cf 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/SamplingTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/SamplingTest.kt @@ -367,6 +367,26 @@ class SamplingTest { decoded.toolChoice shouldBe ToolChoice(mode = ToolChoice.Mode.Required) } + @Test + fun `CreateMessageRequestParams task defaults to null and is omitted when encoding`() { + val params = CreateMessageRequestParams(maxTokens = 100, messages = emptyList()) + params.task shouldBe null + val encoded = McpJson.encodeToString(CreateMessageRequestParams.serializer(), params) + assertEquals(false, "\"task\"" in encoded) + } + + @Test + fun `CreateMessageRequestParams round-trips task`() { + val original = CreateMessageRequestParams( + maxTokens = 100, + messages = emptyList(), + task = TaskMetadata(ttl = 60_000L), + ) + val encoded = McpJson.encodeToString(CreateMessageRequestParams.serializer(), original) + val decoded = McpJson.decodeFromString(CreateMessageRequestParams.serializer(), encoded) + decoded.task shouldBe TaskMetadata(ttl = 60_000L) + } + @Test fun `StopReason ToolUse serialises as toolUse`() { StopReason.ToolUse.value shouldBe "toolUse" diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ToolsTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ToolsTest.kt index d5e1d5a..358866a 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ToolsTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ToolsTest.kt @@ -319,6 +319,57 @@ class ToolsTest { assertEquals(ProgressToken(77), params.meta?.progressToken) } + @Test + fun `should serialize CallToolRequest with task augmentation`() { + val request = CallToolRequest( + CallToolRequestParams( + name = "long-running", + arguments = buildJsonObject { put("query", "MCP protocol") }, + task = TaskMetadata(ttl = 60_000L), + ), + ) + + verifySerialization( + request, + McpJson, + """ + { + "method": "tools/call", + "params": { + "name": "long-running", + "arguments": { + "query": "MCP protocol" + }, + "task": { + "ttl": 60000 + } + } + } + """.trimIndent(), + ) + + assertEquals(TaskMetadata(ttl = 60_000L), request.task) + } + + @Test + fun `should omit task from CallToolRequest when null`() { + val request = CallToolRequest(CallToolRequestParams(name = "plain")) + + assertEquals(null, request.task) + verifySerialization( + request, + McpJson, + """ + { + "method": "tools/call", + "params": { + "name": "plain" + } + } + """.trimIndent(), + ) + } + @Test fun `should serialize CallToolResult with structured content`() { val result = CallToolResult( From fc7916fdc7f47590b7c33e9c311185727a40d083 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Tue, 30 Jun 2026 22:39:53 +0200 Subject: [PATCH 343/449] release: 0.14.0 (#867) Bump version to 0.14.0 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index e5294e8..434144d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,4 +8,4 @@ kotlin.daemon.jvmargs=-Xmx4G kotlin.native.ignoreDisabledTargets=true group=io.modelcontextprotocol -version=0.13.0 +version=0.14.0 From 25678bdded92dd9fc30ef572bcf63814ab8aaf35 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Jul 2026 15:56:44 +0200 Subject: [PATCH 344/449] chore(deps): bump org.jetbrains.kotlin.jvm from 2.3.21 to 2.4.0 in /samples/kotlin-mcp-client (#819) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [org.jetbrains.kotlin.jvm](https://github.com/JetBrains/kotlin) from 2.3.21 to 2.4.0.
    Release notes

    Sourced from org.jetbrains.kotlin.jvm's releases.

    Kotlin 2.4.0

    Changelog

    Analysis API

    • KT-83867 OVERLOAD_RESOLUTION_AMBIGUITY false positive with assertEquals in IJ repo
    • KT-83723 [Analysis API] Enable experimental KDoc resolver by default
    • KT-83388 Analysis API: properly support KMP in KotlinPackageProvider
    • KT-65683 Analysis API: Dangling file session creation causes a computeIfAbsent contract violation

    Analysis API. Code Compilation

    • KT-78946 Evaluation of variable with local class in type parameter leads to InventNamesForLocalClasses exception

    Analysis API. FIR

    • KT-68260 K2 AA: InvalidFirElementTypeException “For CALLABLE_REFERENCE_EXPRESSION with text ::lam1, unexpected element of type: no element found” with illegal callable reference call
    • KT-70896 AA: False positive deprecation warning with override of built-in method in JDK mapped class
    • KT-84711 K2 IDE sometimes loses FIR plugin-generated declarations after file changes
    • KT-84625 Analysis API: collectDesignationPath fails for nested classes inside plugin-generated top-level classes
    • KT-84596 Improve K2 Jooq completion performance
    • KT-84525 KaValueParameterSymbol#getHasSynthesizedName returns false for FirDeclarationOrigin.SubstitutionOverride.DeclarationSite
    • KT-83546 Kotlin analysis reach ClsCustomNavigationPolicy
    • KT-84259 Move CommonDefaultImportsProvider to the frontend independent module
    • KT-82945 Analysis API: KotlinIllegalArgumentExceptionWithAttachments: Expected FirResolvedTypeRef with ConeKotlinType but was FirUserTypeRefImpl
    • KT-71135 AA: exception from sealed inheritors checker when analyzeCopy

    Analysis API. Infrastructure

    Fixes

    • KT-83905 Analysis API: Improve UX with test data
    • KT-84913 Extract compiler classes used by the PSI & Analysis API to a separate module
    • KT-64986 Analysis API: Implement Analysis API tests for different KMP Platforms
    • KT-84776 The test data manager misses the redundancy check in the update mode
    • KT-84962 The test data manager misses -ea flag
    • KT-84388 Preserve the EOF status in the test data manager to avoid extra changes
    • KT-84362 Analysis API tests produce many warnings due to "not yet loaded registry"
    • KT-84279 Test Data Manager fails on a clean build
    • KT-83913 Exclude compiler-based Analysis API tests from Git tracking
    • KT-80379 Extract per-module test generators for AA tests
    • KT-84120 Move CLI modules out of kotlin-compiler-fe10-for-ide
    • KT-83200 Track external dependencies of the Analysis API modules

    Analysis API. Light Classes

    • KT-82434 Light classes should prefer enum entries to properties
    • KT-84200 SLC: return type is not boxed for delegated methods with generic original method
    • KT-72451 "CCE: class PsiPrimitiveType cannot be cast to class PsiClassType" with same-named enum class and typealias

    ... (truncated)

    Changelog

    Sourced from org.jetbrains.kotlin.jvm's changelog.

    2.4.0

    Analysis API

    • KT-83867 OVERLOAD_RESOLUTION_AMBIGUITY false positive with assertEquals in IJ repo
    • KT-83723 [Analysis API] Enable experimental KDoc resolver by default
    • KT-83388 Analysis API: properly support KMP in KotlinPackageProvider
    • KT-65683 Analysis API: Dangling file session creation causes a computeIfAbsent contract violation

    Analysis API. Code Compilation

    • KT-78946 Evaluation of variable with local class in type parameter leads to InventNamesForLocalClasses exception

    Analysis API. FIR

    • KT-68260 K2 AA: InvalidFirElementTypeException “For CALLABLE_REFERENCE_EXPRESSION with text ::lam1, unexpected element of type: no element found” with illegal callable reference call
    • KT-70896 AA: False positive deprecation warning with override of built-in method in JDK mapped class
    • KT-84711 K2 IDE sometimes loses FIR plugin-generated declarations after file changes
    • KT-84625 Analysis API: collectDesignationPath fails for nested classes inside plugin-generated top-level classes
    • KT-84596 Improve K2 Jooq completion performance
    • KT-84525 KaValueParameterSymbol#getHasSynthesizedName returns false for FirDeclarationOrigin.SubstitutionOverride.DeclarationSite
    • KT-83546 Kotlin analysis reach ClsCustomNavigationPolicy
    • KT-84259 Move CommonDefaultImportsProvider to the frontend independent module
    • KT-82945 Analysis API: KotlinIllegalArgumentExceptionWithAttachments: Expected FirResolvedTypeRef with ConeKotlinType but was FirUserTypeRefImpl
    • KT-71135 AA: exception from sealed inheritors checker when analyzeCopy

    Analysis API. Infrastructure

    Fixes

    • KT-83905 Analysis API: Improve UX with test data
    • KT-84913 Extract compiler classes used by the PSI & Analysis API to a separate module
    • KT-64986 Analysis API: Implement Analysis API tests for different KMP Platforms
    • KT-84776 The test data manager misses the redundancy check in the update mode
    • KT-84962 The test data manager misses -ea flag
    • KT-84388 Preserve the EOF status in the test data manager to avoid extra changes
    • KT-84362 Analysis API tests produce many warnings due to "not yet loaded registry"
    • KT-84279 Test Data Manager fails on a clean build
    • KT-83913 Exclude compiler-based Analysis API tests from Git tracking
    • KT-80379 Extract per-module test generators for AA tests
    • KT-84120 Move CLI modules out of kotlin-compiler-fe10-for-ide
    • KT-83200 Track external dependencies of the Analysis API modules

    Analysis API. Light Classes

    • KT-82434 Light classes should prefer enum entries to properties
    • KT-84200 SLC: return type is not boxed for delegated methods with generic original method
    • KT-72451 "CCE: class PsiPrimitiveType cannot be cast to class PsiClassType" with same-named enum class and typealias

    Analysis API. PSI

    ... (truncated)

    Commits
    • add726c Add ChangeLog for 2.4.0-RC2
    • 69a4a81 [Wasm] Add binaryen per-file arguments
    • 8907c63 [KGP] Suppress duplicate compiler warning output when --warning-mode=all is a...
    • f9bef12 [KGP] Add failing test for duplicate compiler warning output via Problems API
    • 7fe49c2 KT-86268: Use 2.4.0 toolchain for abiValidation with older CVs
    • 13d25e2 KT-86268: Regression test added
    • f7d1a76 [JKlib] Fix JAR deserializer configuration.
    • 5cec663 [JKlib] Wire up JvmBuiltInClassDescriptorFactory
    • ef14611 Make the default stability of non-final classes Unknown instead of Stable
    • d76473a [Gradle] Fix GeneralNativeIT.shouldAllowToOverrideDownloadUrl test failures
    • Additional commits viewable in compare view

    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-client/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index 0b9018d..b84f095 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] anthropic = "2.45.0" -kotlin = "2.3.21" +kotlin = "2.4.0" ktor = "3.3.3" mcp-kotlin = "0.13.0" shadow = "9.4.3" From bb6c1ec2e12d2577f1d045829202ed3308837c99 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Jul 2026 11:45:09 +0200 Subject: [PATCH 345/449] chore(deps): bump kotlin from 2.3.21 to 2.4.0 in /samples/kotlinlang-mcp-server (#825) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `kotlin` from 2.3.21 to 2.4.0. Updates `org.jetbrains.kotlin.jvm` from 2.3.21 to 2.4.0
    Release notes

    Sourced from org.jetbrains.kotlin.jvm's releases.

    Kotlin 2.4.0

    Changelog

    Analysis API

    • KT-83867 OVERLOAD_RESOLUTION_AMBIGUITY false positive with assertEquals in IJ repo
    • KT-83723 [Analysis API] Enable experimental KDoc resolver by default
    • KT-83388 Analysis API: properly support KMP in KotlinPackageProvider
    • KT-65683 Analysis API: Dangling file session creation causes a computeIfAbsent contract violation

    Analysis API. Code Compilation

    • KT-78946 Evaluation of variable with local class in type parameter leads to InventNamesForLocalClasses exception

    Analysis API. FIR

    • KT-68260 K2 AA: InvalidFirElementTypeException “For CALLABLE_REFERENCE_EXPRESSION with text ::lam1, unexpected element of type: no element found” with illegal callable reference call
    • KT-70896 AA: False positive deprecation warning with override of built-in method in JDK mapped class
    • KT-84711 K2 IDE sometimes loses FIR plugin-generated declarations after file changes
    • KT-84625 Analysis API: collectDesignationPath fails for nested classes inside plugin-generated top-level classes
    • KT-84596 Improve K2 Jooq completion performance
    • KT-84525 KaValueParameterSymbol#getHasSynthesizedName returns false for FirDeclarationOrigin.SubstitutionOverride.DeclarationSite
    • KT-83546 Kotlin analysis reach ClsCustomNavigationPolicy
    • KT-84259 Move CommonDefaultImportsProvider to the frontend independent module
    • KT-82945 Analysis API: KotlinIllegalArgumentExceptionWithAttachments: Expected FirResolvedTypeRef with ConeKotlinType but was FirUserTypeRefImpl
    • KT-71135 AA: exception from sealed inheritors checker when analyzeCopy

    Analysis API. Infrastructure

    Fixes

    • KT-83905 Analysis API: Improve UX with test data
    • KT-84913 Extract compiler classes used by the PSI & Analysis API to a separate module
    • KT-64986 Analysis API: Implement Analysis API tests for different KMP Platforms
    • KT-84776 The test data manager misses the redundancy check in the update mode
    • KT-84962 The test data manager misses -ea flag
    • KT-84388 Preserve the EOF status in the test data manager to avoid extra changes
    • KT-84362 Analysis API tests produce many warnings due to "not yet loaded registry"
    • KT-84279 Test Data Manager fails on a clean build
    • KT-83913 Exclude compiler-based Analysis API tests from Git tracking
    • KT-80379 Extract per-module test generators for AA tests
    • KT-84120 Move CLI modules out of kotlin-compiler-fe10-for-ide
    • KT-83200 Track external dependencies of the Analysis API modules

    Analysis API. Light Classes

    • KT-82434 Light classes should prefer enum entries to properties
    • KT-84200 SLC: return type is not boxed for delegated methods with generic original method
    • KT-72451 "CCE: class PsiPrimitiveType cannot be cast to class PsiClassType" with same-named enum class and typealias

    ... (truncated)

    Changelog

    Sourced from org.jetbrains.kotlin.jvm's changelog.

    2.4.0

    Analysis API

    • KT-83867 OVERLOAD_RESOLUTION_AMBIGUITY false positive with assertEquals in IJ repo
    • KT-83723 [Analysis API] Enable experimental KDoc resolver by default
    • KT-83388 Analysis API: properly support KMP in KotlinPackageProvider
    • KT-65683 Analysis API: Dangling file session creation causes a computeIfAbsent contract violation

    Analysis API. Code Compilation

    • KT-78946 Evaluation of variable with local class in type parameter leads to InventNamesForLocalClasses exception

    Analysis API. FIR

    • KT-68260 K2 AA: InvalidFirElementTypeException “For CALLABLE_REFERENCE_EXPRESSION with text ::lam1, unexpected element of type: no element found” with illegal callable reference call
    • KT-70896 AA: False positive deprecation warning with override of built-in method in JDK mapped class
    • KT-84711 K2 IDE sometimes loses FIR plugin-generated declarations after file changes
    • KT-84625 Analysis API: collectDesignationPath fails for nested classes inside plugin-generated top-level classes
    • KT-84596 Improve K2 Jooq completion performance
    • KT-84525 KaValueParameterSymbol#getHasSynthesizedName returns false for FirDeclarationOrigin.SubstitutionOverride.DeclarationSite
    • KT-83546 Kotlin analysis reach ClsCustomNavigationPolicy
    • KT-84259 Move CommonDefaultImportsProvider to the frontend independent module
    • KT-82945 Analysis API: KotlinIllegalArgumentExceptionWithAttachments: Expected FirResolvedTypeRef with ConeKotlinType but was FirUserTypeRefImpl
    • KT-71135 AA: exception from sealed inheritors checker when analyzeCopy

    Analysis API. Infrastructure

    Fixes

    • KT-83905 Analysis API: Improve UX with test data
    • KT-84913 Extract compiler classes used by the PSI & Analysis API to a separate module
    • KT-64986 Analysis API: Implement Analysis API tests for different KMP Platforms
    • KT-84776 The test data manager misses the redundancy check in the update mode
    • KT-84962 The test data manager misses -ea flag
    • KT-84388 Preserve the EOF status in the test data manager to avoid extra changes
    • KT-84362 Analysis API tests produce many warnings due to "not yet loaded registry"
    • KT-84279 Test Data Manager fails on a clean build
    • KT-83913 Exclude compiler-based Analysis API tests from Git tracking
    • KT-80379 Extract per-module test generators for AA tests
    • KT-84120 Move CLI modules out of kotlin-compiler-fe10-for-ide
    • KT-83200 Track external dependencies of the Analysis API modules

    Analysis API. Light Classes

    • KT-82434 Light classes should prefer enum entries to properties
    • KT-84200 SLC: return type is not boxed for delegated methods with generic original method
    • KT-72451 "CCE: class PsiPrimitiveType cannot be cast to class PsiClassType" with same-named enum class and typealias

    Analysis API. PSI

    ... (truncated)

    Commits
    • add726c Add ChangeLog for 2.4.0-RC2
    • 69a4a81 [Wasm] Add binaryen per-file arguments
    • 8907c63 [KGP] Suppress duplicate compiler warning output when --warning-mode=all is a...
    • f9bef12 [KGP] Add failing test for duplicate compiler warning output via Problems API
    • 7fe49c2 KT-86268: Use 2.4.0 toolchain for abiValidation with older CVs
    • 13d25e2 KT-86268: Regression test added
    • f7d1a76 [JKlib] Fix JAR deserializer configuration.
    • 5cec663 [JKlib] Wire up JvmBuiltInClassDescriptorFactory
    • ef14611 Make the default stability of non-final classes Unknown instead of Stable
    • d76473a [Gradle] Fix GeneralNativeIT.shouldAllowToOverrideDownloadUrl test failures
    • Additional commits viewable in compare view

    Updates `org.jetbrains.kotlin.plugin.serialization` from 2.3.21 to 2.4.0
    Release notes

    Sourced from org.jetbrains.kotlin.plugin.serialization's releases.

    Kotlin 2.4.0

    Changelog

    Analysis API

    • KT-83867 OVERLOAD_RESOLUTION_AMBIGUITY false positive with assertEquals in IJ repo
    • KT-83723 [Analysis API] Enable experimental KDoc resolver by default
    • KT-83388 Analysis API: properly support KMP in KotlinPackageProvider
    • KT-65683 Analysis API: Dangling file session creation causes a computeIfAbsent contract violation

    Analysis API. Code Compilation

    • KT-78946 Evaluation of variable with local class in type parameter leads to InventNamesForLocalClasses exception

    Analysis API. FIR

    • KT-68260 K2 AA: InvalidFirElementTypeException “For CALLABLE_REFERENCE_EXPRESSION with text ::lam1, unexpected element of type: no element found” with illegal callable reference call
    • KT-70896 AA: False positive deprecation warning with override of built-in method in JDK mapped class
    • KT-84711 K2 IDE sometimes loses FIR plugin-generated declarations after file changes
    • KT-84625 Analysis API: collectDesignationPath fails for nested classes inside plugin-generated top-level classes
    • KT-84596 Improve K2 Jooq completion performance
    • KT-84525 KaValueParameterSymbol#getHasSynthesizedName returns false for FirDeclarationOrigin.SubstitutionOverride.DeclarationSite
    • KT-83546 Kotlin analysis reach ClsCustomNavigationPolicy
    • KT-84259 Move CommonDefaultImportsProvider to the frontend independent module
    • KT-82945 Analysis API: KotlinIllegalArgumentExceptionWithAttachments: Expected FirResolvedTypeRef with ConeKotlinType but was FirUserTypeRefImpl
    • KT-71135 AA: exception from sealed inheritors checker when analyzeCopy

    Analysis API. Infrastructure

    Fixes

    • KT-83905 Analysis API: Improve UX with test data
    • KT-84913 Extract compiler classes used by the PSI & Analysis API to a separate module
    • KT-64986 Analysis API: Implement Analysis API tests for different KMP Platforms
    • KT-84776 The test data manager misses the redundancy check in the update mode
    • KT-84962 The test data manager misses -ea flag
    • KT-84388 Preserve the EOF status in the test data manager to avoid extra changes
    • KT-84362 Analysis API tests produce many warnings due to "not yet loaded registry"
    • KT-84279 Test Data Manager fails on a clean build
    • KT-83913 Exclude compiler-based Analysis API tests from Git tracking
    • KT-80379 Extract per-module test generators for AA tests
    • KT-84120 Move CLI modules out of kotlin-compiler-fe10-for-ide
    • KT-83200 Track external dependencies of the Analysis API modules

    Analysis API. Light Classes

    • KT-82434 Light classes should prefer enum entries to properties
    • KT-84200 SLC: return type is not boxed for delegated methods with generic original method
    • KT-72451 "CCE: class PsiPrimitiveType cannot be cast to class PsiClassType" with same-named enum class and typealias

    ... (truncated)

    Changelog

    Sourced from org.jetbrains.kotlin.plugin.serialization's changelog.

    2.4.0

    Analysis API

    • KT-83867 OVERLOAD_RESOLUTION_AMBIGUITY false positive with assertEquals in IJ repo
    • KT-83723 [Analysis API] Enable experimental KDoc resolver by default
    • KT-83388 Analysis API: properly support KMP in KotlinPackageProvider
    • KT-65683 Analysis API: Dangling file session creation causes a computeIfAbsent contract violation

    Analysis API. Code Compilation

    • KT-78946 Evaluation of variable with local class in type parameter leads to InventNamesForLocalClasses exception

    Analysis API. FIR

    • KT-68260 K2 AA: InvalidFirElementTypeException “For CALLABLE_REFERENCE_EXPRESSION with text ::lam1, unexpected element of type: no element found” with illegal callable reference call
    • KT-70896 AA: False positive deprecation warning with override of built-in method in JDK mapped class
    • KT-84711 K2 IDE sometimes loses FIR plugin-generated declarations after file changes
    • KT-84625 Analysis API: collectDesignationPath fails for nested classes inside plugin-generated top-level classes
    • KT-84596 Improve K2 Jooq completion performance
    • KT-84525 KaValueParameterSymbol#getHasSynthesizedName returns false for FirDeclarationOrigin.SubstitutionOverride.DeclarationSite
    • KT-83546 Kotlin analysis reach ClsCustomNavigationPolicy
    • KT-84259 Move CommonDefaultImportsProvider to the frontend independent module
    • KT-82945 Analysis API: KotlinIllegalArgumentExceptionWithAttachments: Expected FirResolvedTypeRef with ConeKotlinType but was FirUserTypeRefImpl
    • KT-71135 AA: exception from sealed inheritors checker when analyzeCopy

    Analysis API. Infrastructure

    Fixes

    • KT-83905 Analysis API: Improve UX with test data
    • KT-84913 Extract compiler classes used by the PSI & Analysis API to a separate module
    • KT-64986 Analysis API: Implement Analysis API tests for different KMP Platforms
    • KT-84776 The test data manager misses the redundancy check in the update mode
    • KT-84962 The test data manager misses -ea flag
    • KT-84388 Preserve the EOF status in the test data manager to avoid extra changes
    • KT-84362 Analysis API tests produce many warnings due to "not yet loaded registry"
    • KT-84279 Test Data Manager fails on a clean build
    • KT-83913 Exclude compiler-based Analysis API tests from Git tracking
    • KT-80379 Extract per-module test generators for AA tests
    • KT-84120 Move CLI modules out of kotlin-compiler-fe10-for-ide
    • KT-83200 Track external dependencies of the Analysis API modules

    Analysis API. Light Classes

    • KT-82434 Light classes should prefer enum entries to properties
    • KT-84200 SLC: return type is not boxed for delegated methods with generic original method
    • KT-72451 "CCE: class PsiPrimitiveType cannot be cast to class PsiClassType" with same-named enum class and typealias

    Analysis API. PSI

    ... (truncated)

    Commits
    • add726c Add ChangeLog for 2.4.0-RC2
    • 69a4a81 [Wasm] Add binaryen per-file arguments
    • 8907c63 [KGP] Suppress duplicate compiler warning output when --warning-mode=all is a...
    • f9bef12 [KGP] Add failing test for duplicate compiler warning output via Problems API
    • 7fe49c2 KT-86268: Use 2.4.0 toolchain for abiValidation with older CVs
    • 13d25e2 KT-86268: Regression test added
    • f7d1a76 [JKlib] Fix JAR deserializer configuration.
    • 5cec663 [JKlib] Wire up JvmBuiltInClassDescriptorFactory
    • ef14611 Make the default stability of non-final classes Unknown instead of Stable
    • d76473a [Gradle] Fix GeneralNativeIT.shouldAllowToOverrideDownloadUrl test failures
    • Additional commits viewable in compare view

    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlinlang-mcp-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml index 26e89b2..39caf34 100644 --- a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin = "2.3.21" +kotlin = "2.4.0" ktor = "3.3.3" mcp-kotlin = "0.13.0" serialization = "1.11.0" From 7937e9cba6e27626da7f747b924f87696b81c223 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Jul 2026 12:11:36 +0200 Subject: [PATCH 346/449] chore(deps): bump io.ktor:ktor-bom from 3.3.3 to 3.5.1 in /samples/kotlin-mcp-server (#860) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [io.ktor:ktor-bom](https://github.com/ktorio/ktor) from 3.3.3 to 3.5.1.
    Release notes

    Sourced from io.ktor:ktor-bom's releases.

    3.5.1

    Published 25 June 2026

    Improvements

    • KTOR-9492 OpenAPI plugin: handle requireXxx functions in compiler plugin
    • KTOR-9569 Add more details for KDoc of BearerAuthConfig.sendWithoutRequest
    • KTOR-9571 Add KDoc for BearerAuthConfig.realm
    • KTOR-9570 Add Kdoc for BearerAuthConfig.refreshTokens

    Bugfixes

    • KTOR-9646 Fix Kotlin 2.4.0 compiler plugin breaking changes
    • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
    • KTOR-9591 OpenAPI: @JsonClassDiscriminator on sealed types is ignored when generating OpenAPI discriminator schema
    • KTOR-9597 OpenAPI: Schema name collisions because@SerialName of sealed subtypes is used as components.schemas key
    • KTOR-9601 OpenAPI: nullable @​JvmInline value class loses nullability in generated schema
    • KTOR-9620 Some issues related to digest authentication in 3.5.0
    • KTOR-9623 Digest Auth: server must respond with one WWW-Authenticate header for each supported algorithm
    • KTOR-9624 Digest Auth: "Unsupported charset in digest authentication header" for a charset name in lowercase
    • KTOR-9630 Digest Auth: The plugin sends incorrect nonce when server responds with multiple WWW-Authenticate headers
    • KTOR-9565 WebRTC: Peer stops receiving messages after a while on iOS
    • KTOR-9610 WebRtcPeerConnection::close causes ClosedReceiveChannelException for other peer
    • KTOR-9633 WebRTC statistics fetching race condition
    • KTOR-9603 Structured concurrency is violated in WebRTC tests
    • KTOR-9671 Darwin: Semicolons in URL path are sanitized
    • KTOR-9639 Darwin: WebSocket client crashes the process when a PONG arrives after the session is closed
    • KTOR-9575 Darwin: findCharset("UTF-16") maps to NSUTF16LittleEndianStringEncoding causing decoding failure for UTF-16 content with BOM
    • KTOR-9661 HttpCache: InvalidCacheStateException is thrown on 304 when no cached entry matches varyKeys
    • KTOR-9673 Caching always missing with Content Negotiation
    • KTOR-9596 HttpTimeout: HttpRequestTimeoutException on any request with runTest and request timeout defined since 3.5.0
    • KTOR-9632 HttpRequestBuilder.timeout call removes capabilities set in DefaultRequest
    • KTOR-9252 ContentEncoding: Incomplete gzip response causes client/server to hang indefinitely
    • KTOR-9670 Compression: The plugin ignores Accept-Encoding q=0 and most-specific matching
    • KTOR-9614 Zstd Compression: "Destination buffer is too small" exception for a particular sequence of bytes
    • KTOR-9636 CORS plugin drops OPTIONS preflight requests when allowSameOrigin is on
    • KTOR-9659 CORS is skipped when the Origin header contains an IPv6 address
    • KTOR-9574 ContentNegotiation: The charset of Accept-Charset header is used for response deserialization
    • KTOR-9606 KotlinxSerializationConverter: it fails to deserialize an empty channel closed with a delay
    • KTOR-9617 OutputStreamContent and WriterContent can exhaust Dispatchers.IO
    • KTOR-9629 RawSourceChannel: coroutine cancellation is not propagated to the RawSource
    • KTOR-8870 File.readChannel: do not close a non-opened file and ignore closing exceptions
    • KTOR-9460 Curl: Can't build shared library with Ktor 3.4.2
    • KTOR-9607 Crash on Android 7: NoSuchMethodError getInstanceStrong() since 3.5.0
    • KTOR-9595 CIO: The engine imports node:net statically and breaks wasmJsBrowser webpack build
    • KTOR-9615 Apache5: The underlying request is not aborted when coroutine Job is cancelled
    • KTOR-8443 RoutingBuilder.contentType does not use parameters when matching
    • KTOR-9425 SessionTransportTransformerEncrypt init block uses wrong IV size for AES-256 (regression of KTOR-661)
    • KTOR-9621 RateLimit plugin is bypassed when the nested authenticate block rejects the request
    • KTOR-8766 DI: The close method is called twice on cleanup of AutoCloseable
    • KTOR-8695 GMTDate() being offset by 369 years on Windows
    • KTOR-9589 Duplicate PROPERTY_SETTER target in @InternalAPI annotation

    ... (truncated)

    Changelog

    Sourced from io.ktor:ktor-bom's changelog.

    3.5.1

    Published 25 June 2026

    Improvements

    • KTOR-9492 OpenAPI plugin: handle requireXxx functions in compiler plugin
    • KTOR-9569 Add more details for KDoc of BearerAuthConfig.sendWithoutRequest
    • KTOR-9571 Add KDoc for BearerAuthConfig.realm
    • KTOR-9570 Add Kdoc for BearerAuthConfig.refreshTokens

    Bugfixes

    • KTOR-9646 Fix Kotlin 2.4.0 compiler plugin breaking changes
    • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
    • KTOR-9591 OpenAPI: @JsonClassDiscriminator on sealed types is ignored when generating OpenAPI discriminator schema
    • KTOR-9597 OpenAPI: Schema name collisions because@SerialName of sealed subtypes is used as components.schemas key
    • KTOR-9601 OpenAPI: nullable @​JvmInline value class loses nullability in generated schema
    • KTOR-9620 Some issues related to digest authentication in 3.5.0
    • KTOR-9623 Digest Auth: server must respond with one WWW-Authenticate header for each supported algorithm
    • KTOR-9624 Digest Auth: "Unsupported charset in digest authentication header" for a charset name in lowercase
    • KTOR-9630 Digest Auth: The plugin sends incorrect nonce when server responds with multiple WWW-Authenticate headers
    • KTOR-9565 WebRTC: Peer stops receiving messages after a while on iOS
    • KTOR-9610 WebRtcPeerConnection::close causes ClosedReceiveChannelException for other peer
    • KTOR-9633 WebRTC statistics fetching race condition
    • KTOR-9603 Structured concurrency is violated in WebRTC tests
    • KTOR-9671 Darwin: Semicolons in URL path are sanitized
    • KTOR-9639 Darwin: WebSocket client crashes the process when a PONG arrives after the session is closed
    • KTOR-9575 Darwin: findCharset("UTF-16") maps to NSUTF16LittleEndianStringEncoding causing decoding failure for UTF-16 content with BOM
    • KTOR-9661 HttpCache: InvalidCacheStateException is thrown on 304 when no cached entry matches varyKeys
    • KTOR-9673 Caching always missing with Content Negotiation
    • KTOR-9596 HttpTimeout: HttpRequestTimeoutException on any request with runTest and request timeout defined since 3.5.0
    • KTOR-9632 HttpRequestBuilder.timeout call removes capabilities set in DefaultRequest
    • KTOR-9252 ContentEncoding: Incomplete gzip response causes client/server to hang indefinitely
    • KTOR-9670 Compression: The plugin ignores Accept-Encoding q=0 and most-specific matching
    • KTOR-9614 Zstd Compression: "Destination buffer is too small" exception for a particular sequence of bytes
    • KTOR-9636 CORS plugin drops OPTIONS preflight requests when allowSameOrigin is on
    • KTOR-9659 CORS is skipped when the Origin header contains an IPv6 address
    • KTOR-9574 ContentNegotiation: The charset of Accept-Charset header is used for response deserialization
    • KTOR-9606 KotlinxSerializationConverter: it fails to deserialize an empty channel closed with a delay
    • KTOR-9617 OutputStreamContent and WriterContent can exhaust Dispatchers.IO
    • KTOR-9629 RawSourceChannel: coroutine cancellation is not propagated to the RawSource
    • KTOR-8870 File.readChannel: do not close a non-opened file and ignore closing exceptions
    • KTOR-9460 Curl: Can't build shared library with Ktor 3.4.2
    • KTOR-9607 Crash on Android 7: NoSuchMethodError getInstanceStrong() since 3.5.0
    • KTOR-9595 CIO: The engine imports node:net statically and breaks wasmJsBrowser webpack build
    • KTOR-9615 Apache5: The underlying request is not aborted when coroutine Job is cancelled
    • KTOR-8443 RoutingBuilder.contentType does not use parameters when matching
    • KTOR-9425 SessionTransportTransformerEncrypt init block uses wrong IV size for AES-256 (regression of KTOR-661)
    • KTOR-9621 RateLimit plugin is bypassed when the nested authenticate block rejects the request
    • KTOR-8766 DI: The close method is called twice on cleanup of AutoCloseable
    • KTOR-8695 GMTDate() being offset by 369 years on Windows
    • KTOR-9589 Duplicate PROPERTY_SETTER target in @​InternalAPI annotation

    ... (truncated)

    Commits
    • 5ba9d6f Release 3.5.1 (#5719)
    • a4f145d KTOR-9661 HttpCache: InvalidCacheStateException is thrown on 304 when… (#5701)
    • 2caf278 Fix failing :ktor-io:compileDarwinMainKotlinMetadata (#5716)
    • 257de90 KTOR-8766 Fix multiple invocations of close from DI
    • e77a168 Fix cache misses with content negotiation 3x (#5714)
    • ed22950 Make huge post test less huge
    • f329069 Update dependency com.github.luben:zstd-jni to v1.5.7-11
    • 6ecd072 Update dependency io.swagger.parser.v3:swagger-parser to v2.1.44
    • a0534cd Update dependency io.pebbletemplates:pebble to v4.1.2
    • 8647e53 Update dependency com.typesafe:config to v1.4.9
    • Additional commits viewable in compare view

    Most Recent Ignore Conditions Applied to This Pull Request | Dependency Name | Ignore Conditions | | --- | --- | | io.ktor:ktor-bom | [>= 3.4.a, < 3.5] |
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-server/gradle/libs.versions.toml b/samples/kotlin-mcp-server/gradle/libs.versions.toml index 56bf5f1..91c9513 100644 --- a/samples/kotlin-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-server/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] kotlin = "2.3.21" -ktor = "3.3.3" +ktor = "3.5.1" mcp-kotlin = "0.13.0" slf4j = "2.0.18" shadow = "9.4.3" From c59b87f273480460c292df3c0ec715f74fd1db0d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Jul 2026 12:34:29 +0200 Subject: [PATCH 347/449] chore(deps): bump io.ktor:ktor-bom from 3.3.3 to 3.5.1 in /samples/kotlin-mcp-client (#859) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [io.ktor:ktor-bom](https://github.com/ktorio/ktor) from 3.3.3 to 3.5.1.
    Release notes

    Sourced from io.ktor:ktor-bom's releases.

    3.5.1

    Published 25 June 2026

    Improvements

    • KTOR-9492 OpenAPI plugin: handle requireXxx functions in compiler plugin
    • KTOR-9569 Add more details for KDoc of BearerAuthConfig.sendWithoutRequest
    • KTOR-9571 Add KDoc for BearerAuthConfig.realm
    • KTOR-9570 Add Kdoc for BearerAuthConfig.refreshTokens

    Bugfixes

    • KTOR-9646 Fix Kotlin 2.4.0 compiler plugin breaking changes
    • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
    • KTOR-9591 OpenAPI: @JsonClassDiscriminator on sealed types is ignored when generating OpenAPI discriminator schema
    • KTOR-9597 OpenAPI: Schema name collisions because@SerialName of sealed subtypes is used as components.schemas key
    • KTOR-9601 OpenAPI: nullable @​JvmInline value class loses nullability in generated schema
    • KTOR-9620 Some issues related to digest authentication in 3.5.0
    • KTOR-9623 Digest Auth: server must respond with one WWW-Authenticate header for each supported algorithm
    • KTOR-9624 Digest Auth: "Unsupported charset in digest authentication header" for a charset name in lowercase
    • KTOR-9630 Digest Auth: The plugin sends incorrect nonce when server responds with multiple WWW-Authenticate headers
    • KTOR-9565 WebRTC: Peer stops receiving messages after a while on iOS
    • KTOR-9610 WebRtcPeerConnection::close causes ClosedReceiveChannelException for other peer
    • KTOR-9633 WebRTC statistics fetching race condition
    • KTOR-9603 Structured concurrency is violated in WebRTC tests
    • KTOR-9671 Darwin: Semicolons in URL path are sanitized
    • KTOR-9639 Darwin: WebSocket client crashes the process when a PONG arrives after the session is closed
    • KTOR-9575 Darwin: findCharset("UTF-16") maps to NSUTF16LittleEndianStringEncoding causing decoding failure for UTF-16 content with BOM
    • KTOR-9661 HttpCache: InvalidCacheStateException is thrown on 304 when no cached entry matches varyKeys
    • KTOR-9673 Caching always missing with Content Negotiation
    • KTOR-9596 HttpTimeout: HttpRequestTimeoutException on any request with runTest and request timeout defined since 3.5.0
    • KTOR-9632 HttpRequestBuilder.timeout call removes capabilities set in DefaultRequest
    • KTOR-9252 ContentEncoding: Incomplete gzip response causes client/server to hang indefinitely
    • KTOR-9670 Compression: The plugin ignores Accept-Encoding q=0 and most-specific matching
    • KTOR-9614 Zstd Compression: "Destination buffer is too small" exception for a particular sequence of bytes
    • KTOR-9636 CORS plugin drops OPTIONS preflight requests when allowSameOrigin is on
    • KTOR-9659 CORS is skipped when the Origin header contains an IPv6 address
    • KTOR-9574 ContentNegotiation: The charset of Accept-Charset header is used for response deserialization
    • KTOR-9606 KotlinxSerializationConverter: it fails to deserialize an empty channel closed with a delay
    • KTOR-9617 OutputStreamContent and WriterContent can exhaust Dispatchers.IO
    • KTOR-9629 RawSourceChannel: coroutine cancellation is not propagated to the RawSource
    • KTOR-8870 File.readChannel: do not close a non-opened file and ignore closing exceptions
    • KTOR-9460 Curl: Can't build shared library with Ktor 3.4.2
    • KTOR-9607 Crash on Android 7: NoSuchMethodError getInstanceStrong() since 3.5.0
    • KTOR-9595 CIO: The engine imports node:net statically and breaks wasmJsBrowser webpack build
    • KTOR-9615 Apache5: The underlying request is not aborted when coroutine Job is cancelled
    • KTOR-8443 RoutingBuilder.contentType does not use parameters when matching
    • KTOR-9425 SessionTransportTransformerEncrypt init block uses wrong IV size for AES-256 (regression of KTOR-661)
    • KTOR-9621 RateLimit plugin is bypassed when the nested authenticate block rejects the request
    • KTOR-8766 DI: The close method is called twice on cleanup of AutoCloseable
    • KTOR-8695 GMTDate() being offset by 369 years on Windows
    • KTOR-9589 Duplicate PROPERTY_SETTER target in @InternalAPI annotation

    ... (truncated)

    Changelog

    Sourced from io.ktor:ktor-bom's changelog.

    3.5.1

    Published 25 June 2026

    Improvements

    • KTOR-9492 OpenAPI plugin: handle requireXxx functions in compiler plugin
    • KTOR-9569 Add more details for KDoc of BearerAuthConfig.sendWithoutRequest
    • KTOR-9571 Add KDoc for BearerAuthConfig.realm
    • KTOR-9570 Add Kdoc for BearerAuthConfig.refreshTokens

    Bugfixes

    • KTOR-9646 Fix Kotlin 2.4.0 compiler plugin breaking changes
    • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
    • KTOR-9591 OpenAPI: @JsonClassDiscriminator on sealed types is ignored when generating OpenAPI discriminator schema
    • KTOR-9597 OpenAPI: Schema name collisions because@SerialName of sealed subtypes is used as components.schemas key
    • KTOR-9601 OpenAPI: nullable @​JvmInline value class loses nullability in generated schema
    • KTOR-9620 Some issues related to digest authentication in 3.5.0
    • KTOR-9623 Digest Auth: server must respond with one WWW-Authenticate header for each supported algorithm
    • KTOR-9624 Digest Auth: "Unsupported charset in digest authentication header" for a charset name in lowercase
    • KTOR-9630 Digest Auth: The plugin sends incorrect nonce when server responds with multiple WWW-Authenticate headers
    • KTOR-9565 WebRTC: Peer stops receiving messages after a while on iOS
    • KTOR-9610 WebRtcPeerConnection::close causes ClosedReceiveChannelException for other peer
    • KTOR-9633 WebRTC statistics fetching race condition
    • KTOR-9603 Structured concurrency is violated in WebRTC tests
    • KTOR-9671 Darwin: Semicolons in URL path are sanitized
    • KTOR-9639 Darwin: WebSocket client crashes the process when a PONG arrives after the session is closed
    • KTOR-9575 Darwin: findCharset("UTF-16") maps to NSUTF16LittleEndianStringEncoding causing decoding failure for UTF-16 content with BOM
    • KTOR-9661 HttpCache: InvalidCacheStateException is thrown on 304 when no cached entry matches varyKeys
    • KTOR-9673 Caching always missing with Content Negotiation
    • KTOR-9596 HttpTimeout: HttpRequestTimeoutException on any request with runTest and request timeout defined since 3.5.0
    • KTOR-9632 HttpRequestBuilder.timeout call removes capabilities set in DefaultRequest
    • KTOR-9252 ContentEncoding: Incomplete gzip response causes client/server to hang indefinitely
    • KTOR-9670 Compression: The plugin ignores Accept-Encoding q=0 and most-specific matching
    • KTOR-9614 Zstd Compression: "Destination buffer is too small" exception for a particular sequence of bytes
    • KTOR-9636 CORS plugin drops OPTIONS preflight requests when allowSameOrigin is on
    • KTOR-9659 CORS is skipped when the Origin header contains an IPv6 address
    • KTOR-9574 ContentNegotiation: The charset of Accept-Charset header is used for response deserialization
    • KTOR-9606 KotlinxSerializationConverter: it fails to deserialize an empty channel closed with a delay
    • KTOR-9617 OutputStreamContent and WriterContent can exhaust Dispatchers.IO
    • KTOR-9629 RawSourceChannel: coroutine cancellation is not propagated to the RawSource
    • KTOR-8870 File.readChannel: do not close a non-opened file and ignore closing exceptions
    • KTOR-9460 Curl: Can't build shared library with Ktor 3.4.2
    • KTOR-9607 Crash on Android 7: NoSuchMethodError getInstanceStrong() since 3.5.0
    • KTOR-9595 CIO: The engine imports node:net statically and breaks wasmJsBrowser webpack build
    • KTOR-9615 Apache5: The underlying request is not aborted when coroutine Job is cancelled
    • KTOR-8443 RoutingBuilder.contentType does not use parameters when matching
    • KTOR-9425 SessionTransportTransformerEncrypt init block uses wrong IV size for AES-256 (regression of KTOR-661)
    • KTOR-9621 RateLimit plugin is bypassed when the nested authenticate block rejects the request
    • KTOR-8766 DI: The close method is called twice on cleanup of AutoCloseable
    • KTOR-8695 GMTDate() being offset by 369 years on Windows
    • KTOR-9589 Duplicate PROPERTY_SETTER target in @​InternalAPI annotation

    ... (truncated)

    Commits
    • 5ba9d6f Release 3.5.1 (#5719)
    • a4f145d KTOR-9661 HttpCache: InvalidCacheStateException is thrown on 304 when… (#5701)
    • 2caf278 Fix failing :ktor-io:compileDarwinMainKotlinMetadata (#5716)
    • 257de90 KTOR-8766 Fix multiple invocations of close from DI
    • e77a168 Fix cache misses with content negotiation 3x (#5714)
    • ed22950 Make huge post test less huge
    • f329069 Update dependency com.github.luben:zstd-jni to v1.5.7-11
    • 6ecd072 Update dependency io.swagger.parser.v3:swagger-parser to v2.1.44
    • a0534cd Update dependency io.pebbletemplates:pebble to v4.1.2
    • 8647e53 Update dependency com.typesafe:config to v1.4.9
    • Additional commits viewable in compare view

    Most Recent Ignore Conditions Applied to This Pull Request | Dependency Name | Ignore Conditions | | --- | --- | | io.ktor:ktor-bom | [>= 3.4.a, < 3.5] |
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-client/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index b84f095..b5167d9 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] anthropic = "2.45.0" kotlin = "2.4.0" -ktor = "3.3.3" +ktor = "3.5.1" mcp-kotlin = "0.13.0" shadow = "9.4.3" slf4j = "2.0.18" From d47429dd35767fe1b08c50b7fbbff5292881b2dd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Jul 2026 13:35:57 +0200 Subject: [PATCH 348/449] chore(deps): bump io.ktor:ktor-bom from 3.3.3 to 3.5.1 in /samples/simple-streamable-server (#862) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [io.ktor:ktor-bom](https://github.com/ktorio/ktor) from 3.3.3 to 3.5.1.
    Release notes

    Sourced from io.ktor:ktor-bom's releases.

    3.5.1

    Published 25 June 2026

    Improvements

    • KTOR-9492 OpenAPI plugin: handle requireXxx functions in compiler plugin
    • KTOR-9569 Add more details for KDoc of BearerAuthConfig.sendWithoutRequest
    • KTOR-9571 Add KDoc for BearerAuthConfig.realm
    • KTOR-9570 Add Kdoc for BearerAuthConfig.refreshTokens

    Bugfixes

    • KTOR-9646 Fix Kotlin 2.4.0 compiler plugin breaking changes
    • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
    • KTOR-9591 OpenAPI: @JsonClassDiscriminator on sealed types is ignored when generating OpenAPI discriminator schema
    • KTOR-9597 OpenAPI: Schema name collisions because@SerialName of sealed subtypes is used as components.schemas key
    • KTOR-9601 OpenAPI: nullable @​JvmInline value class loses nullability in generated schema
    • KTOR-9620 Some issues related to digest authentication in 3.5.0
    • KTOR-9623 Digest Auth: server must respond with one WWW-Authenticate header for each supported algorithm
    • KTOR-9624 Digest Auth: "Unsupported charset in digest authentication header" for a charset name in lowercase
    • KTOR-9630 Digest Auth: The plugin sends incorrect nonce when server responds with multiple WWW-Authenticate headers
    • KTOR-9565 WebRTC: Peer stops receiving messages after a while on iOS
    • KTOR-9610 WebRtcPeerConnection::close causes ClosedReceiveChannelException for other peer
    • KTOR-9633 WebRTC statistics fetching race condition
    • KTOR-9603 Structured concurrency is violated in WebRTC tests
    • KTOR-9671 Darwin: Semicolons in URL path are sanitized
    • KTOR-9639 Darwin: WebSocket client crashes the process when a PONG arrives after the session is closed
    • KTOR-9575 Darwin: findCharset("UTF-16") maps to NSUTF16LittleEndianStringEncoding causing decoding failure for UTF-16 content with BOM
    • KTOR-9661 HttpCache: InvalidCacheStateException is thrown on 304 when no cached entry matches varyKeys
    • KTOR-9673 Caching always missing with Content Negotiation
    • KTOR-9596 HttpTimeout: HttpRequestTimeoutException on any request with runTest and request timeout defined since 3.5.0
    • KTOR-9632 HttpRequestBuilder.timeout call removes capabilities set in DefaultRequest
    • KTOR-9252 ContentEncoding: Incomplete gzip response causes client/server to hang indefinitely
    • KTOR-9670 Compression: The plugin ignores Accept-Encoding q=0 and most-specific matching
    • KTOR-9614 Zstd Compression: "Destination buffer is too small" exception for a particular sequence of bytes
    • KTOR-9636 CORS plugin drops OPTIONS preflight requests when allowSameOrigin is on
    • KTOR-9659 CORS is skipped when the Origin header contains an IPv6 address
    • KTOR-9574 ContentNegotiation: The charset of Accept-Charset header is used for response deserialization
    • KTOR-9606 KotlinxSerializationConverter: it fails to deserialize an empty channel closed with a delay
    • KTOR-9617 OutputStreamContent and WriterContent can exhaust Dispatchers.IO
    • KTOR-9629 RawSourceChannel: coroutine cancellation is not propagated to the RawSource
    • KTOR-8870 File.readChannel: do not close a non-opened file and ignore closing exceptions
    • KTOR-9460 Curl: Can't build shared library with Ktor 3.4.2
    • KTOR-9607 Crash on Android 7: NoSuchMethodError getInstanceStrong() since 3.5.0
    • KTOR-9595 CIO: The engine imports node:net statically and breaks wasmJsBrowser webpack build
    • KTOR-9615 Apache5: The underlying request is not aborted when coroutine Job is cancelled
    • KTOR-8443 RoutingBuilder.contentType does not use parameters when matching
    • KTOR-9425 SessionTransportTransformerEncrypt init block uses wrong IV size for AES-256 (regression of KTOR-661)
    • KTOR-9621 RateLimit plugin is bypassed when the nested authenticate block rejects the request
    • KTOR-8766 DI: The close method is called twice on cleanup of AutoCloseable
    • KTOR-8695 GMTDate() being offset by 369 years on Windows
    • KTOR-9589 Duplicate PROPERTY_SETTER target in @InternalAPI annotation

    ... (truncated)

    Changelog

    Sourced from io.ktor:ktor-bom's changelog.

    3.5.1

    Published 25 June 2026

    Improvements

    • KTOR-9492 OpenAPI plugin: handle requireXxx functions in compiler plugin
    • KTOR-9569 Add more details for KDoc of BearerAuthConfig.sendWithoutRequest
    • KTOR-9571 Add KDoc for BearerAuthConfig.realm
    • KTOR-9570 Add Kdoc for BearerAuthConfig.refreshTokens

    Bugfixes

    • KTOR-9646 Fix Kotlin 2.4.0 compiler plugin breaking changes
    • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
    • KTOR-9591 OpenAPI: @JsonClassDiscriminator on sealed types is ignored when generating OpenAPI discriminator schema
    • KTOR-9597 OpenAPI: Schema name collisions because@SerialName of sealed subtypes is used as components.schemas key
    • KTOR-9601 OpenAPI: nullable @​JvmInline value class loses nullability in generated schema
    • KTOR-9620 Some issues related to digest authentication in 3.5.0
    • KTOR-9623 Digest Auth: server must respond with one WWW-Authenticate header for each supported algorithm
    • KTOR-9624 Digest Auth: "Unsupported charset in digest authentication header" for a charset name in lowercase
    • KTOR-9630 Digest Auth: The plugin sends incorrect nonce when server responds with multiple WWW-Authenticate headers
    • KTOR-9565 WebRTC: Peer stops receiving messages after a while on iOS
    • KTOR-9610 WebRtcPeerConnection::close causes ClosedReceiveChannelException for other peer
    • KTOR-9633 WebRTC statistics fetching race condition
    • KTOR-9603 Structured concurrency is violated in WebRTC tests
    • KTOR-9671 Darwin: Semicolons in URL path are sanitized
    • KTOR-9639 Darwin: WebSocket client crashes the process when a PONG arrives after the session is closed
    • KTOR-9575 Darwin: findCharset("UTF-16") maps to NSUTF16LittleEndianStringEncoding causing decoding failure for UTF-16 content with BOM
    • KTOR-9661 HttpCache: InvalidCacheStateException is thrown on 304 when no cached entry matches varyKeys
    • KTOR-9673 Caching always missing with Content Negotiation
    • KTOR-9596 HttpTimeout: HttpRequestTimeoutException on any request with runTest and request timeout defined since 3.5.0
    • KTOR-9632 HttpRequestBuilder.timeout call removes capabilities set in DefaultRequest
    • KTOR-9252 ContentEncoding: Incomplete gzip response causes client/server to hang indefinitely
    • KTOR-9670 Compression: The plugin ignores Accept-Encoding q=0 and most-specific matching
    • KTOR-9614 Zstd Compression: "Destination buffer is too small" exception for a particular sequence of bytes
    • KTOR-9636 CORS plugin drops OPTIONS preflight requests when allowSameOrigin is on
    • KTOR-9659 CORS is skipped when the Origin header contains an IPv6 address
    • KTOR-9574 ContentNegotiation: The charset of Accept-Charset header is used for response deserialization
    • KTOR-9606 KotlinxSerializationConverter: it fails to deserialize an empty channel closed with a delay
    • KTOR-9617 OutputStreamContent and WriterContent can exhaust Dispatchers.IO
    • KTOR-9629 RawSourceChannel: coroutine cancellation is not propagated to the RawSource
    • KTOR-8870 File.readChannel: do not close a non-opened file and ignore closing exceptions
    • KTOR-9460 Curl: Can't build shared library with Ktor 3.4.2
    • KTOR-9607 Crash on Android 7: NoSuchMethodError getInstanceStrong() since 3.5.0
    • KTOR-9595 CIO: The engine imports node:net statically and breaks wasmJsBrowser webpack build
    • KTOR-9615 Apache5: The underlying request is not aborted when coroutine Job is cancelled
    • KTOR-8443 RoutingBuilder.contentType does not use parameters when matching
    • KTOR-9425 SessionTransportTransformerEncrypt init block uses wrong IV size for AES-256 (regression of KTOR-661)
    • KTOR-9621 RateLimit plugin is bypassed when the nested authenticate block rejects the request
    • KTOR-8766 DI: The close method is called twice on cleanup of AutoCloseable
    • KTOR-8695 GMTDate() being offset by 369 years on Windows
    • KTOR-9589 Duplicate PROPERTY_SETTER target in @​InternalAPI annotation

    ... (truncated)

    Commits
    • 5ba9d6f Release 3.5.1 (#5719)
    • a4f145d KTOR-9661 HttpCache: InvalidCacheStateException is thrown on 304 when… (#5701)
    • 2caf278 Fix failing :ktor-io:compileDarwinMainKotlinMetadata (#5716)
    • 257de90 KTOR-8766 Fix multiple invocations of close from DI
    • e77a168 Fix cache misses with content negotiation 3x (#5714)
    • ed22950 Make huge post test less huge
    • f329069 Update dependency com.github.luben:zstd-jni to v1.5.7-11
    • 6ecd072 Update dependency io.swagger.parser.v3:swagger-parser to v2.1.44
    • a0534cd Update dependency io.pebbletemplates:pebble to v4.1.2
    • 8647e53 Update dependency com.typesafe:config to v1.4.9
    • Additional commits viewable in compare view

    Most Recent Ignore Conditions Applied to This Pull Request | Dependency Name | Ignore Conditions | | --- | --- | | io.ktor:ktor-bom | [>= 3.4.a, < 3.5] |
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/simple-streamable-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/simple-streamable-server/gradle/libs.versions.toml b/samples/simple-streamable-server/gradle/libs.versions.toml index 723994b..dc7393d 100644 --- a/samples/simple-streamable-server/gradle/libs.versions.toml +++ b/samples/simple-streamable-server/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] kotlin = "2.3.21" -ktor = "3.3.3" +ktor = "3.5.1" junit = "6.1.1" mcp-kotlin = "0.13.0" slf4j = "2.0.18" From a19ec03aa2bd098598f907d9a5639cbdf008f109 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Jul 2026 13:59:49 +0200 Subject: [PATCH 349/449] chore(deps): bump io.ktor:ktor-bom from 3.3.3 to 3.5.1 in /samples/weather-stdio-server (#863) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [io.ktor:ktor-bom](https://github.com/ktorio/ktor) from 3.3.3 to 3.5.1.
    Release notes

    Sourced from io.ktor:ktor-bom's releases.

    3.5.1

    Published 25 June 2026

    Improvements

    • KTOR-9492 OpenAPI plugin: handle requireXxx functions in compiler plugin
    • KTOR-9569 Add more details for KDoc of BearerAuthConfig.sendWithoutRequest
    • KTOR-9571 Add KDoc for BearerAuthConfig.realm
    • KTOR-9570 Add Kdoc for BearerAuthConfig.refreshTokens

    Bugfixes

    • KTOR-9646 Fix Kotlin 2.4.0 compiler plugin breaking changes
    • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
    • KTOR-9591 OpenAPI: @JsonClassDiscriminator on sealed types is ignored when generating OpenAPI discriminator schema
    • KTOR-9597 OpenAPI: Schema name collisions because@SerialName of sealed subtypes is used as components.schemas key
    • KTOR-9601 OpenAPI: nullable @​JvmInline value class loses nullability in generated schema
    • KTOR-9620 Some issues related to digest authentication in 3.5.0
    • KTOR-9623 Digest Auth: server must respond with one WWW-Authenticate header for each supported algorithm
    • KTOR-9624 Digest Auth: "Unsupported charset in digest authentication header" for a charset name in lowercase
    • KTOR-9630 Digest Auth: The plugin sends incorrect nonce when server responds with multiple WWW-Authenticate headers
    • KTOR-9565 WebRTC: Peer stops receiving messages after a while on iOS
    • KTOR-9610 WebRtcPeerConnection::close causes ClosedReceiveChannelException for other peer
    • KTOR-9633 WebRTC statistics fetching race condition
    • KTOR-9603 Structured concurrency is violated in WebRTC tests
    • KTOR-9671 Darwin: Semicolons in URL path are sanitized
    • KTOR-9639 Darwin: WebSocket client crashes the process when a PONG arrives after the session is closed
    • KTOR-9575 Darwin: findCharset("UTF-16") maps to NSUTF16LittleEndianStringEncoding causing decoding failure for UTF-16 content with BOM
    • KTOR-9661 HttpCache: InvalidCacheStateException is thrown on 304 when no cached entry matches varyKeys
    • KTOR-9673 Caching always missing with Content Negotiation
    • KTOR-9596 HttpTimeout: HttpRequestTimeoutException on any request with runTest and request timeout defined since 3.5.0
    • KTOR-9632 HttpRequestBuilder.timeout call removes capabilities set in DefaultRequest
    • KTOR-9252 ContentEncoding: Incomplete gzip response causes client/server to hang indefinitely
    • KTOR-9670 Compression: The plugin ignores Accept-Encoding q=0 and most-specific matching
    • KTOR-9614 Zstd Compression: "Destination buffer is too small" exception for a particular sequence of bytes
    • KTOR-9636 CORS plugin drops OPTIONS preflight requests when allowSameOrigin is on
    • KTOR-9659 CORS is skipped when the Origin header contains an IPv6 address
    • KTOR-9574 ContentNegotiation: The charset of Accept-Charset header is used for response deserialization
    • KTOR-9606 KotlinxSerializationConverter: it fails to deserialize an empty channel closed with a delay
    • KTOR-9617 OutputStreamContent and WriterContent can exhaust Dispatchers.IO
    • KTOR-9629 RawSourceChannel: coroutine cancellation is not propagated to the RawSource
    • KTOR-8870 File.readChannel: do not close a non-opened file and ignore closing exceptions
    • KTOR-9460 Curl: Can't build shared library with Ktor 3.4.2
    • KTOR-9607 Crash on Android 7: NoSuchMethodError getInstanceStrong() since 3.5.0
    • KTOR-9595 CIO: The engine imports node:net statically and breaks wasmJsBrowser webpack build
    • KTOR-9615 Apache5: The underlying request is not aborted when coroutine Job is cancelled
    • KTOR-8443 RoutingBuilder.contentType does not use parameters when matching
    • KTOR-9425 SessionTransportTransformerEncrypt init block uses wrong IV size for AES-256 (regression of KTOR-661)
    • KTOR-9621 RateLimit plugin is bypassed when the nested authenticate block rejects the request
    • KTOR-8766 DI: The close method is called twice on cleanup of AutoCloseable
    • KTOR-8695 GMTDate() being offset by 369 years on Windows
    • KTOR-9589 Duplicate PROPERTY_SETTER target in @InternalAPI annotation

    ... (truncated)

    Changelog

    Sourced from io.ktor:ktor-bom's changelog.

    3.5.1

    Published 25 June 2026

    Improvements

    • KTOR-9492 OpenAPI plugin: handle requireXxx functions in compiler plugin
    • KTOR-9569 Add more details for KDoc of BearerAuthConfig.sendWithoutRequest
    • KTOR-9571 Add KDoc for BearerAuthConfig.realm
    • KTOR-9570 Add Kdoc for BearerAuthConfig.refreshTokens

    Bugfixes

    • KTOR-9646 Fix Kotlin 2.4.0 compiler plugin breaking changes
    • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
    • KTOR-9591 OpenAPI: @JsonClassDiscriminator on sealed types is ignored when generating OpenAPI discriminator schema
    • KTOR-9597 OpenAPI: Schema name collisions because@SerialName of sealed subtypes is used as components.schemas key
    • KTOR-9601 OpenAPI: nullable @​JvmInline value class loses nullability in generated schema
    • KTOR-9620 Some issues related to digest authentication in 3.5.0
    • KTOR-9623 Digest Auth: server must respond with one WWW-Authenticate header for each supported algorithm
    • KTOR-9624 Digest Auth: "Unsupported charset in digest authentication header" for a charset name in lowercase
    • KTOR-9630 Digest Auth: The plugin sends incorrect nonce when server responds with multiple WWW-Authenticate headers
    • KTOR-9565 WebRTC: Peer stops receiving messages after a while on iOS
    • KTOR-9610 WebRtcPeerConnection::close causes ClosedReceiveChannelException for other peer
    • KTOR-9633 WebRTC statistics fetching race condition
    • KTOR-9603 Structured concurrency is violated in WebRTC tests
    • KTOR-9671 Darwin: Semicolons in URL path are sanitized
    • KTOR-9639 Darwin: WebSocket client crashes the process when a PONG arrives after the session is closed
    • KTOR-9575 Darwin: findCharset("UTF-16") maps to NSUTF16LittleEndianStringEncoding causing decoding failure for UTF-16 content with BOM
    • KTOR-9661 HttpCache: InvalidCacheStateException is thrown on 304 when no cached entry matches varyKeys
    • KTOR-9673 Caching always missing with Content Negotiation
    • KTOR-9596 HttpTimeout: HttpRequestTimeoutException on any request with runTest and request timeout defined since 3.5.0
    • KTOR-9632 HttpRequestBuilder.timeout call removes capabilities set in DefaultRequest
    • KTOR-9252 ContentEncoding: Incomplete gzip response causes client/server to hang indefinitely
    • KTOR-9670 Compression: The plugin ignores Accept-Encoding q=0 and most-specific matching
    • KTOR-9614 Zstd Compression: "Destination buffer is too small" exception for a particular sequence of bytes
    • KTOR-9636 CORS plugin drops OPTIONS preflight requests when allowSameOrigin is on
    • KTOR-9659 CORS is skipped when the Origin header contains an IPv6 address
    • KTOR-9574 ContentNegotiation: The charset of Accept-Charset header is used for response deserialization
    • KTOR-9606 KotlinxSerializationConverter: it fails to deserialize an empty channel closed with a delay
    • KTOR-9617 OutputStreamContent and WriterContent can exhaust Dispatchers.IO
    • KTOR-9629 RawSourceChannel: coroutine cancellation is not propagated to the RawSource
    • KTOR-8870 File.readChannel: do not close a non-opened file and ignore closing exceptions
    • KTOR-9460 Curl: Can't build shared library with Ktor 3.4.2
    • KTOR-9607 Crash on Android 7: NoSuchMethodError getInstanceStrong() since 3.5.0
    • KTOR-9595 CIO: The engine imports node:net statically and breaks wasmJsBrowser webpack build
    • KTOR-9615 Apache5: The underlying request is not aborted when coroutine Job is cancelled
    • KTOR-8443 RoutingBuilder.contentType does not use parameters when matching
    • KTOR-9425 SessionTransportTransformerEncrypt init block uses wrong IV size for AES-256 (regression of KTOR-661)
    • KTOR-9621 RateLimit plugin is bypassed when the nested authenticate block rejects the request
    • KTOR-8766 DI: The close method is called twice on cleanup of AutoCloseable
    • KTOR-8695 GMTDate() being offset by 369 years on Windows
    • KTOR-9589 Duplicate PROPERTY_SETTER target in @​InternalAPI annotation

    ... (truncated)

    Commits
    • 5ba9d6f Release 3.5.1 (#5719)
    • a4f145d KTOR-9661 HttpCache: InvalidCacheStateException is thrown on 304 when… (#5701)
    • 2caf278 Fix failing :ktor-io:compileDarwinMainKotlinMetadata (#5716)
    • 257de90 KTOR-8766 Fix multiple invocations of close from DI
    • e77a168 Fix cache misses with content negotiation 3x (#5714)
    • ed22950 Make huge post test less huge
    • f329069 Update dependency com.github.luben:zstd-jni to v1.5.7-11
    • 6ecd072 Update dependency io.swagger.parser.v3:swagger-parser to v2.1.44
    • a0534cd Update dependency io.pebbletemplates:pebble to v4.1.2
    • 8647e53 Update dependency com.typesafe:config to v1.4.9
    • Additional commits viewable in compare view

    Most Recent Ignore Conditions Applied to This Pull Request | Dependency Name | Ignore Conditions | | --- | --- | | io.ktor:ktor-bom | [>= 3.4.a, < 3.5] |
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/weather-stdio-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/weather-stdio-server/gradle/libs.versions.toml b/samples/weather-stdio-server/gradle/libs.versions.toml index e805236..4b74090 100644 --- a/samples/weather-stdio-server/gradle/libs.versions.toml +++ b/samples/weather-stdio-server/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] coroutines = "1.11.0" kotlin = "2.3.21" -ktor = "3.3.3" +ktor = "3.5.1" logging = "8.0.4" mcp-kotlin = "0.13.0" shadow = "9.4.3" From 65ce490454a08fee0bb3b522c1653da508f78cb8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Jul 2026 14:22:52 +0200 Subject: [PATCH 350/449] chore(deps): bump io.ktor:ktor-bom from 3.3.3 to 3.5.1 in /samples/kotlinlang-mcp-server (#861) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [io.ktor:ktor-bom](https://github.com/ktorio/ktor) from 3.3.3 to 3.5.1.
    Release notes

    Sourced from io.ktor:ktor-bom's releases.

    3.5.1

    Published 25 June 2026

    Improvements

    • KTOR-9492 OpenAPI plugin: handle requireXxx functions in compiler plugin
    • KTOR-9569 Add more details for KDoc of BearerAuthConfig.sendWithoutRequest
    • KTOR-9571 Add KDoc for BearerAuthConfig.realm
    • KTOR-9570 Add Kdoc for BearerAuthConfig.refreshTokens

    Bugfixes

    • KTOR-9646 Fix Kotlin 2.4.0 compiler plugin breaking changes
    • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
    • KTOR-9591 OpenAPI: @JsonClassDiscriminator on sealed types is ignored when generating OpenAPI discriminator schema
    • KTOR-9597 OpenAPI: Schema name collisions because@SerialName of sealed subtypes is used as components.schemas key
    • KTOR-9601 OpenAPI: nullable @​JvmInline value class loses nullability in generated schema
    • KTOR-9620 Some issues related to digest authentication in 3.5.0
    • KTOR-9623 Digest Auth: server must respond with one WWW-Authenticate header for each supported algorithm
    • KTOR-9624 Digest Auth: "Unsupported charset in digest authentication header" for a charset name in lowercase
    • KTOR-9630 Digest Auth: The plugin sends incorrect nonce when server responds with multiple WWW-Authenticate headers
    • KTOR-9565 WebRTC: Peer stops receiving messages after a while on iOS
    • KTOR-9610 WebRtcPeerConnection::close causes ClosedReceiveChannelException for other peer
    • KTOR-9633 WebRTC statistics fetching race condition
    • KTOR-9603 Structured concurrency is violated in WebRTC tests
    • KTOR-9671 Darwin: Semicolons in URL path are sanitized
    • KTOR-9639 Darwin: WebSocket client crashes the process when a PONG arrives after the session is closed
    • KTOR-9575 Darwin: findCharset("UTF-16") maps to NSUTF16LittleEndianStringEncoding causing decoding failure for UTF-16 content with BOM
    • KTOR-9661 HttpCache: InvalidCacheStateException is thrown on 304 when no cached entry matches varyKeys
    • KTOR-9673 Caching always missing with Content Negotiation
    • KTOR-9596 HttpTimeout: HttpRequestTimeoutException on any request with runTest and request timeout defined since 3.5.0
    • KTOR-9632 HttpRequestBuilder.timeout call removes capabilities set in DefaultRequest
    • KTOR-9252 ContentEncoding: Incomplete gzip response causes client/server to hang indefinitely
    • KTOR-9670 Compression: The plugin ignores Accept-Encoding q=0 and most-specific matching
    • KTOR-9614 Zstd Compression: "Destination buffer is too small" exception for a particular sequence of bytes
    • KTOR-9636 CORS plugin drops OPTIONS preflight requests when allowSameOrigin is on
    • KTOR-9659 CORS is skipped when the Origin header contains an IPv6 address
    • KTOR-9574 ContentNegotiation: The charset of Accept-Charset header is used for response deserialization
    • KTOR-9606 KotlinxSerializationConverter: it fails to deserialize an empty channel closed with a delay
    • KTOR-9617 OutputStreamContent and WriterContent can exhaust Dispatchers.IO
    • KTOR-9629 RawSourceChannel: coroutine cancellation is not propagated to the RawSource
    • KTOR-8870 File.readChannel: do not close a non-opened file and ignore closing exceptions
    • KTOR-9460 Curl: Can't build shared library with Ktor 3.4.2
    • KTOR-9607 Crash on Android 7: NoSuchMethodError getInstanceStrong() since 3.5.0
    • KTOR-9595 CIO: The engine imports node:net statically and breaks wasmJsBrowser webpack build
    • KTOR-9615 Apache5: The underlying request is not aborted when coroutine Job is cancelled
    • KTOR-8443 RoutingBuilder.contentType does not use parameters when matching
    • KTOR-9425 SessionTransportTransformerEncrypt init block uses wrong IV size for AES-256 (regression of KTOR-661)
    • KTOR-9621 RateLimit plugin is bypassed when the nested authenticate block rejects the request
    • KTOR-8766 DI: The close method is called twice on cleanup of AutoCloseable
    • KTOR-8695 GMTDate() being offset by 369 years on Windows
    • KTOR-9589 Duplicate PROPERTY_SETTER target in @InternalAPI annotation

    ... (truncated)

    Changelog

    Sourced from io.ktor:ktor-bom's changelog.

    3.5.1

    Published 25 June 2026

    Improvements

    • KTOR-9492 OpenAPI plugin: handle requireXxx functions in compiler plugin
    • KTOR-9569 Add more details for KDoc of BearerAuthConfig.sendWithoutRequest
    • KTOR-9571 Add KDoc for BearerAuthConfig.realm
    • KTOR-9570 Add Kdoc for BearerAuthConfig.refreshTokens

    Bugfixes

    • KTOR-9646 Fix Kotlin 2.4.0 compiler plugin breaking changes
    • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
    • KTOR-9591 OpenAPI: @JsonClassDiscriminator on sealed types is ignored when generating OpenAPI discriminator schema
    • KTOR-9597 OpenAPI: Schema name collisions because@SerialName of sealed subtypes is used as components.schemas key
    • KTOR-9601 OpenAPI: nullable @​JvmInline value class loses nullability in generated schema
    • KTOR-9620 Some issues related to digest authentication in 3.5.0
    • KTOR-9623 Digest Auth: server must respond with one WWW-Authenticate header for each supported algorithm
    • KTOR-9624 Digest Auth: "Unsupported charset in digest authentication header" for a charset name in lowercase
    • KTOR-9630 Digest Auth: The plugin sends incorrect nonce when server responds with multiple WWW-Authenticate headers
    • KTOR-9565 WebRTC: Peer stops receiving messages after a while on iOS
    • KTOR-9610 WebRtcPeerConnection::close causes ClosedReceiveChannelException for other peer
    • KTOR-9633 WebRTC statistics fetching race condition
    • KTOR-9603 Structured concurrency is violated in WebRTC tests
    • KTOR-9671 Darwin: Semicolons in URL path are sanitized
    • KTOR-9639 Darwin: WebSocket client crashes the process when a PONG arrives after the session is closed
    • KTOR-9575 Darwin: findCharset("UTF-16") maps to NSUTF16LittleEndianStringEncoding causing decoding failure for UTF-16 content with BOM
    • KTOR-9661 HttpCache: InvalidCacheStateException is thrown on 304 when no cached entry matches varyKeys
    • KTOR-9673 Caching always missing with Content Negotiation
    • KTOR-9596 HttpTimeout: HttpRequestTimeoutException on any request with runTest and request timeout defined since 3.5.0
    • KTOR-9632 HttpRequestBuilder.timeout call removes capabilities set in DefaultRequest
    • KTOR-9252 ContentEncoding: Incomplete gzip response causes client/server to hang indefinitely
    • KTOR-9670 Compression: The plugin ignores Accept-Encoding q=0 and most-specific matching
    • KTOR-9614 Zstd Compression: "Destination buffer is too small" exception for a particular sequence of bytes
    • KTOR-9636 CORS plugin drops OPTIONS preflight requests when allowSameOrigin is on
    • KTOR-9659 CORS is skipped when the Origin header contains an IPv6 address
    • KTOR-9574 ContentNegotiation: The charset of Accept-Charset header is used for response deserialization
    • KTOR-9606 KotlinxSerializationConverter: it fails to deserialize an empty channel closed with a delay
    • KTOR-9617 OutputStreamContent and WriterContent can exhaust Dispatchers.IO
    • KTOR-9629 RawSourceChannel: coroutine cancellation is not propagated to the RawSource
    • KTOR-8870 File.readChannel: do not close a non-opened file and ignore closing exceptions
    • KTOR-9460 Curl: Can't build shared library with Ktor 3.4.2
    • KTOR-9607 Crash on Android 7: NoSuchMethodError getInstanceStrong() since 3.5.0
    • KTOR-9595 CIO: The engine imports node:net statically and breaks wasmJsBrowser webpack build
    • KTOR-9615 Apache5: The underlying request is not aborted when coroutine Job is cancelled
    • KTOR-8443 RoutingBuilder.contentType does not use parameters when matching
    • KTOR-9425 SessionTransportTransformerEncrypt init block uses wrong IV size for AES-256 (regression of KTOR-661)
    • KTOR-9621 RateLimit plugin is bypassed when the nested authenticate block rejects the request
    • KTOR-8766 DI: The close method is called twice on cleanup of AutoCloseable
    • KTOR-8695 GMTDate() being offset by 369 years on Windows
    • KTOR-9589 Duplicate PROPERTY_SETTER target in @​InternalAPI annotation

    ... (truncated)

    Commits
    • 5ba9d6f Release 3.5.1 (#5719)
    • a4f145d KTOR-9661 HttpCache: InvalidCacheStateException is thrown on 304 when… (#5701)
    • 2caf278 Fix failing :ktor-io:compileDarwinMainKotlinMetadata (#5716)
    • 257de90 KTOR-8766 Fix multiple invocations of close from DI
    • e77a168 Fix cache misses with content negotiation 3x (#5714)
    • ed22950 Make huge post test less huge
    • f329069 Update dependency com.github.luben:zstd-jni to v1.5.7-11
    • 6ecd072 Update dependency io.swagger.parser.v3:swagger-parser to v2.1.44
    • a0534cd Update dependency io.pebbletemplates:pebble to v4.1.2
    • 8647e53 Update dependency com.typesafe:config to v1.4.9
    • Additional commits viewable in compare view

    Most Recent Ignore Conditions Applied to This Pull Request | Dependency Name | Ignore Conditions | | --- | --- | | io.ktor:ktor-bom | [>= 3.4.a, < 3.5] |
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlinlang-mcp-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml index 39caf34..c521eba 100644 --- a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] kotlin = "2.4.0" -ktor = "3.3.3" +ktor = "3.5.1" mcp-kotlin = "0.13.0" serialization = "1.11.0" coroutines = "1.11.0" From 365f2625e171aa47c7f6e57f5c485047a4937b8c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Jul 2026 14:54:36 +0200 Subject: [PATCH 351/449] chore(deps): bump kotlin from 2.3.21 to 2.4.0 in /samples/weather-stdio-server (#829) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `kotlin` from 2.3.21 to 2.4.0. Updates `org.jetbrains.kotlin.jvm` from 2.3.21 to 2.4.0
    Release notes

    Sourced from org.jetbrains.kotlin.jvm's releases.

    Kotlin 2.4.0

    Changelog

    Analysis API

    • KT-83867 OVERLOAD_RESOLUTION_AMBIGUITY false positive with assertEquals in IJ repo
    • KT-83723 [Analysis API] Enable experimental KDoc resolver by default
    • KT-83388 Analysis API: properly support KMP in KotlinPackageProvider
    • KT-65683 Analysis API: Dangling file session creation causes a computeIfAbsent contract violation

    Analysis API. Code Compilation

    • KT-78946 Evaluation of variable with local class in type parameter leads to InventNamesForLocalClasses exception

    Analysis API. FIR

    • KT-68260 K2 AA: InvalidFirElementTypeException “For CALLABLE_REFERENCE_EXPRESSION with text ::lam1, unexpected element of type: no element found” with illegal callable reference call
    • KT-70896 AA: False positive deprecation warning with override of built-in method in JDK mapped class
    • KT-84711 K2 IDE sometimes loses FIR plugin-generated declarations after file changes
    • KT-84625 Analysis API: collectDesignationPath fails for nested classes inside plugin-generated top-level classes
    • KT-84596 Improve K2 Jooq completion performance
    • KT-84525 KaValueParameterSymbol#getHasSynthesizedName returns false for FirDeclarationOrigin.SubstitutionOverride.DeclarationSite
    • KT-83546 Kotlin analysis reach ClsCustomNavigationPolicy
    • KT-84259 Move CommonDefaultImportsProvider to the frontend independent module
    • KT-82945 Analysis API: KotlinIllegalArgumentExceptionWithAttachments: Expected FirResolvedTypeRef with ConeKotlinType but was FirUserTypeRefImpl
    • KT-71135 AA: exception from sealed inheritors checker when analyzeCopy

    Analysis API. Infrastructure

    Fixes

    • KT-83905 Analysis API: Improve UX with test data
    • KT-84913 Extract compiler classes used by the PSI & Analysis API to a separate module
    • KT-64986 Analysis API: Implement Analysis API tests for different KMP Platforms
    • KT-84776 The test data manager misses the redundancy check in the update mode
    • KT-84962 The test data manager misses -ea flag
    • KT-84388 Preserve the EOF status in the test data manager to avoid extra changes
    • KT-84362 Analysis API tests produce many warnings due to "not yet loaded registry"
    • KT-84279 Test Data Manager fails on a clean build
    • KT-83913 Exclude compiler-based Analysis API tests from Git tracking
    • KT-80379 Extract per-module test generators for AA tests
    • KT-84120 Move CLI modules out of kotlin-compiler-fe10-for-ide
    • KT-83200 Track external dependencies of the Analysis API modules

    Analysis API. Light Classes

    • KT-82434 Light classes should prefer enum entries to properties
    • KT-84200 SLC: return type is not boxed for delegated methods with generic original method
    • KT-72451 "CCE: class PsiPrimitiveType cannot be cast to class PsiClassType" with same-named enum class and typealias

    ... (truncated)

    Changelog

    Sourced from org.jetbrains.kotlin.jvm's changelog.

    2.4.0

    Analysis API

    • KT-83867 OVERLOAD_RESOLUTION_AMBIGUITY false positive with assertEquals in IJ repo
    • KT-83723 [Analysis API] Enable experimental KDoc resolver by default
    • KT-83388 Analysis API: properly support KMP in KotlinPackageProvider
    • KT-65683 Analysis API: Dangling file session creation causes a computeIfAbsent contract violation

    Analysis API. Code Compilation

    • KT-78946 Evaluation of variable with local class in type parameter leads to InventNamesForLocalClasses exception

    Analysis API. FIR

    • KT-68260 K2 AA: InvalidFirElementTypeException “For CALLABLE_REFERENCE_EXPRESSION with text ::lam1, unexpected element of type: no element found” with illegal callable reference call
    • KT-70896 AA: False positive deprecation warning with override of built-in method in JDK mapped class
    • KT-84711 K2 IDE sometimes loses FIR plugin-generated declarations after file changes
    • KT-84625 Analysis API: collectDesignationPath fails for nested classes inside plugin-generated top-level classes
    • KT-84596 Improve K2 Jooq completion performance
    • KT-84525 KaValueParameterSymbol#getHasSynthesizedName returns false for FirDeclarationOrigin.SubstitutionOverride.DeclarationSite
    • KT-83546 Kotlin analysis reach ClsCustomNavigationPolicy
    • KT-84259 Move CommonDefaultImportsProvider to the frontend independent module
    • KT-82945 Analysis API: KotlinIllegalArgumentExceptionWithAttachments: Expected FirResolvedTypeRef with ConeKotlinType but was FirUserTypeRefImpl
    • KT-71135 AA: exception from sealed inheritors checker when analyzeCopy

    Analysis API. Infrastructure

    Fixes

    • KT-83905 Analysis API: Improve UX with test data
    • KT-84913 Extract compiler classes used by the PSI & Analysis API to a separate module
    • KT-64986 Analysis API: Implement Analysis API tests for different KMP Platforms
    • KT-84776 The test data manager misses the redundancy check in the update mode
    • KT-84962 The test data manager misses -ea flag
    • KT-84388 Preserve the EOF status in the test data manager to avoid extra changes
    • KT-84362 Analysis API tests produce many warnings due to "not yet loaded registry"
    • KT-84279 Test Data Manager fails on a clean build
    • KT-83913 Exclude compiler-based Analysis API tests from Git tracking
    • KT-80379 Extract per-module test generators for AA tests
    • KT-84120 Move CLI modules out of kotlin-compiler-fe10-for-ide
    • KT-83200 Track external dependencies of the Analysis API modules

    Analysis API. Light Classes

    • KT-82434 Light classes should prefer enum entries to properties
    • KT-84200 SLC: return type is not boxed for delegated methods with generic original method
    • KT-72451 "CCE: class PsiPrimitiveType cannot be cast to class PsiClassType" with same-named enum class and typealias

    Analysis API. PSI

    ... (truncated)

    Commits
    • add726c Add ChangeLog for 2.4.0-RC2
    • 69a4a81 [Wasm] Add binaryen per-file arguments
    • 8907c63 [KGP] Suppress duplicate compiler warning output when --warning-mode=all is a...
    • f9bef12 [KGP] Add failing test for duplicate compiler warning output via Problems API
    • 7fe49c2 KT-86268: Use 2.4.0 toolchain for abiValidation with older CVs
    • 13d25e2 KT-86268: Regression test added
    • f7d1a76 [JKlib] Fix JAR deserializer configuration.
    • 5cec663 [JKlib] Wire up JvmBuiltInClassDescriptorFactory
    • ef14611 Make the default stability of non-final classes Unknown instead of Stable
    • d76473a [Gradle] Fix GeneralNativeIT.shouldAllowToOverrideDownloadUrl test failures
    • Additional commits viewable in compare view

    Updates `org.jetbrains.kotlin.plugin.serialization` from 2.3.21 to 2.4.0
    Release notes

    Sourced from org.jetbrains.kotlin.plugin.serialization's releases.

    Kotlin 2.4.0

    Changelog

    Analysis API

    • KT-83867 OVERLOAD_RESOLUTION_AMBIGUITY false positive with assertEquals in IJ repo
    • KT-83723 [Analysis API] Enable experimental KDoc resolver by default
    • KT-83388 Analysis API: properly support KMP in KotlinPackageProvider
    • KT-65683 Analysis API: Dangling file session creation causes a computeIfAbsent contract violation

    Analysis API. Code Compilation

    • KT-78946 Evaluation of variable with local class in type parameter leads to InventNamesForLocalClasses exception

    Analysis API. FIR

    • KT-68260 K2 AA: InvalidFirElementTypeException “For CALLABLE_REFERENCE_EXPRESSION with text ::lam1, unexpected element of type: no element found” with illegal callable reference call
    • KT-70896 AA: False positive deprecation warning with override of built-in method in JDK mapped class
    • KT-84711 K2 IDE sometimes loses FIR plugin-generated declarations after file changes
    • KT-84625 Analysis API: collectDesignationPath fails for nested classes inside plugin-generated top-level classes
    • KT-84596 Improve K2 Jooq completion performance
    • KT-84525 KaValueParameterSymbol#getHasSynthesizedName returns false for FirDeclarationOrigin.SubstitutionOverride.DeclarationSite
    • KT-83546 Kotlin analysis reach ClsCustomNavigationPolicy
    • KT-84259 Move CommonDefaultImportsProvider to the frontend independent module
    • KT-82945 Analysis API: KotlinIllegalArgumentExceptionWithAttachments: Expected FirResolvedTypeRef with ConeKotlinType but was FirUserTypeRefImpl
    • KT-71135 AA: exception from sealed inheritors checker when analyzeCopy

    Analysis API. Infrastructure

    Fixes

    • KT-83905 Analysis API: Improve UX with test data
    • KT-84913 Extract compiler classes used by the PSI & Analysis API to a separate module
    • KT-64986 Analysis API: Implement Analysis API tests for different KMP Platforms
    • KT-84776 The test data manager misses the redundancy check in the update mode
    • KT-84962 The test data manager misses -ea flag
    • KT-84388 Preserve the EOF status in the test data manager to avoid extra changes
    • KT-84362 Analysis API tests produce many warnings due to "not yet loaded registry"
    • KT-84279 Test Data Manager fails on a clean build
    • KT-83913 Exclude compiler-based Analysis API tests from Git tracking
    • KT-80379 Extract per-module test generators for AA tests
    • KT-84120 Move CLI modules out of kotlin-compiler-fe10-for-ide
    • KT-83200 Track external dependencies of the Analysis API modules

    Analysis API. Light Classes

    • KT-82434 Light classes should prefer enum entries to properties
    • KT-84200 SLC: return type is not boxed for delegated methods with generic original method
    • KT-72451 "CCE: class PsiPrimitiveType cannot be cast to class PsiClassType" with same-named enum class and typealias

    ... (truncated)

    Changelog

    Sourced from org.jetbrains.kotlin.plugin.serialization's changelog.

    2.4.0

    Analysis API

    • KT-83867 OVERLOAD_RESOLUTION_AMBIGUITY false positive with assertEquals in IJ repo
    • KT-83723 [Analysis API] Enable experimental KDoc resolver by default
    • KT-83388 Analysis API: properly support KMP in KotlinPackageProvider
    • KT-65683 Analysis API: Dangling file session creation causes a computeIfAbsent contract violation

    Analysis API. Code Compilation

    • KT-78946 Evaluation of variable with local class in type parameter leads to InventNamesForLocalClasses exception

    Analysis API. FIR

    • KT-68260 K2 AA: InvalidFirElementTypeException “For CALLABLE_REFERENCE_EXPRESSION with text ::lam1, unexpected element of type: no element found” with illegal callable reference call
    • KT-70896 AA: False positive deprecation warning with override of built-in method in JDK mapped class
    • KT-84711 K2 IDE sometimes loses FIR plugin-generated declarations after file changes
    • KT-84625 Analysis API: collectDesignationPath fails for nested classes inside plugin-generated top-level classes
    • KT-84596 Improve K2 Jooq completion performance
    • KT-84525 KaValueParameterSymbol#getHasSynthesizedName returns false for FirDeclarationOrigin.SubstitutionOverride.DeclarationSite
    • KT-83546 Kotlin analysis reach ClsCustomNavigationPolicy
    • KT-84259 Move CommonDefaultImportsProvider to the frontend independent module
    • KT-82945 Analysis API: KotlinIllegalArgumentExceptionWithAttachments: Expected FirResolvedTypeRef with ConeKotlinType but was FirUserTypeRefImpl
    • KT-71135 AA: exception from sealed inheritors checker when analyzeCopy

    Analysis API. Infrastructure

    Fixes

    • KT-83905 Analysis API: Improve UX with test data
    • KT-84913 Extract compiler classes used by the PSI & Analysis API to a separate module
    • KT-64986 Analysis API: Implement Analysis API tests for different KMP Platforms
    • KT-84776 The test data manager misses the redundancy check in the update mode
    • KT-84962 The test data manager misses -ea flag
    • KT-84388 Preserve the EOF status in the test data manager to avoid extra changes
    • KT-84362 Analysis API tests produce many warnings due to "not yet loaded registry"
    • KT-84279 Test Data Manager fails on a clean build
    • KT-83913 Exclude compiler-based Analysis API tests from Git tracking
    • KT-80379 Extract per-module test generators for AA tests
    • KT-84120 Move CLI modules out of kotlin-compiler-fe10-for-ide
    • KT-83200 Track external dependencies of the Analysis API modules

    Analysis API. Light Classes

    • KT-82434 Light classes should prefer enum entries to properties
    • KT-84200 SLC: return type is not boxed for delegated methods with generic original method
    • KT-72451 "CCE: class PsiPrimitiveType cannot be cast to class PsiClassType" with same-named enum class and typealias

    Analysis API. PSI

    ... (truncated)

    Commits
    • add726c Add ChangeLog for 2.4.0-RC2
    • 69a4a81 [Wasm] Add binaryen per-file arguments
    • 8907c63 [KGP] Suppress duplicate compiler warning output when --warning-mode=all is a...
    • f9bef12 [KGP] Add failing test for duplicate compiler warning output via Problems API
    • 7fe49c2 KT-86268: Use 2.4.0 toolchain for abiValidation with older CVs
    • 13d25e2 KT-86268: Regression test added
    • f7d1a76 [JKlib] Fix JAR deserializer configuration.
    • 5cec663 [JKlib] Wire up JvmBuiltInClassDescriptorFactory
    • ef14611 Make the default stability of non-final classes Unknown instead of Stable
    • d76473a [Gradle] Fix GeneralNativeIT.shouldAllowToOverrideDownloadUrl test failures
    • Additional commits viewable in compare view

    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/weather-stdio-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/weather-stdio-server/gradle/libs.versions.toml b/samples/weather-stdio-server/gradle/libs.versions.toml index 4b74090..2acaf66 100644 --- a/samples/weather-stdio-server/gradle/libs.versions.toml +++ b/samples/weather-stdio-server/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] coroutines = "1.11.0" -kotlin = "2.3.21" +kotlin = "2.4.0" ktor = "3.5.1" logging = "8.0.4" mcp-kotlin = "0.13.0" From e8ebf19a32830c3e1838e8f588e4969171c6996a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Jul 2026 20:22:33 +0200 Subject: [PATCH 352/449] chore(deps): bump kotlin from 2.3.21 to 2.4.0 in /samples/simple-streamable-server (#827) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `kotlin` from 2.3.21 to 2.4.0. Updates `org.jetbrains.kotlin.jvm` from 2.3.21 to 2.4.0
    Release notes

    Sourced from org.jetbrains.kotlin.jvm's releases.

    Kotlin 2.4.0

    Changelog

    Analysis API

    • KT-83867 OVERLOAD_RESOLUTION_AMBIGUITY false positive with assertEquals in IJ repo
    • KT-83723 [Analysis API] Enable experimental KDoc resolver by default
    • KT-83388 Analysis API: properly support KMP in KotlinPackageProvider
    • KT-65683 Analysis API: Dangling file session creation causes a computeIfAbsent contract violation

    Analysis API. Code Compilation

    • KT-78946 Evaluation of variable with local class in type parameter leads to InventNamesForLocalClasses exception

    Analysis API. FIR

    • KT-68260 K2 AA: InvalidFirElementTypeException “For CALLABLE_REFERENCE_EXPRESSION with text ::lam1, unexpected element of type: no element found” with illegal callable reference call
    • KT-70896 AA: False positive deprecation warning with override of built-in method in JDK mapped class
    • KT-84711 K2 IDE sometimes loses FIR plugin-generated declarations after file changes
    • KT-84625 Analysis API: collectDesignationPath fails for nested classes inside plugin-generated top-level classes
    • KT-84596 Improve K2 Jooq completion performance
    • KT-84525 KaValueParameterSymbol#getHasSynthesizedName returns false for FirDeclarationOrigin.SubstitutionOverride.DeclarationSite
    • KT-83546 Kotlin analysis reach ClsCustomNavigationPolicy
    • KT-84259 Move CommonDefaultImportsProvider to the frontend independent module
    • KT-82945 Analysis API: KotlinIllegalArgumentExceptionWithAttachments: Expected FirResolvedTypeRef with ConeKotlinType but was FirUserTypeRefImpl
    • KT-71135 AA: exception from sealed inheritors checker when analyzeCopy

    Analysis API. Infrastructure

    Fixes

    • KT-83905 Analysis API: Improve UX with test data
    • KT-84913 Extract compiler classes used by the PSI & Analysis API to a separate module
    • KT-64986 Analysis API: Implement Analysis API tests for different KMP Platforms
    • KT-84776 The test data manager misses the redundancy check in the update mode
    • KT-84962 The test data manager misses -ea flag
    • KT-84388 Preserve the EOF status in the test data manager to avoid extra changes
    • KT-84362 Analysis API tests produce many warnings due to "not yet loaded registry"
    • KT-84279 Test Data Manager fails on a clean build
    • KT-83913 Exclude compiler-based Analysis API tests from Git tracking
    • KT-80379 Extract per-module test generators for AA tests
    • KT-84120 Move CLI modules out of kotlin-compiler-fe10-for-ide
    • KT-83200 Track external dependencies of the Analysis API modules

    Analysis API. Light Classes

    • KT-82434 Light classes should prefer enum entries to properties
    • KT-84200 SLC: return type is not boxed for delegated methods with generic original method
    • KT-72451 "CCE: class PsiPrimitiveType cannot be cast to class PsiClassType" with same-named enum class and typealias

    ... (truncated)

    Changelog

    Sourced from org.jetbrains.kotlin.jvm's changelog.

    2.4.0

    Analysis API

    • KT-83867 OVERLOAD_RESOLUTION_AMBIGUITY false positive with assertEquals in IJ repo
    • KT-83723 [Analysis API] Enable experimental KDoc resolver by default
    • KT-83388 Analysis API: properly support KMP in KotlinPackageProvider
    • KT-65683 Analysis API: Dangling file session creation causes a computeIfAbsent contract violation

    Analysis API. Code Compilation

    • KT-78946 Evaluation of variable with local class in type parameter leads to InventNamesForLocalClasses exception

    Analysis API. FIR

    • KT-68260 K2 AA: InvalidFirElementTypeException “For CALLABLE_REFERENCE_EXPRESSION with text ::lam1, unexpected element of type: no element found” with illegal callable reference call
    • KT-70896 AA: False positive deprecation warning with override of built-in method in JDK mapped class
    • KT-84711 K2 IDE sometimes loses FIR plugin-generated declarations after file changes
    • KT-84625 Analysis API: collectDesignationPath fails for nested classes inside plugin-generated top-level classes
    • KT-84596 Improve K2 Jooq completion performance
    • KT-84525 KaValueParameterSymbol#getHasSynthesizedName returns false for FirDeclarationOrigin.SubstitutionOverride.DeclarationSite
    • KT-83546 Kotlin analysis reach ClsCustomNavigationPolicy
    • KT-84259 Move CommonDefaultImportsProvider to the frontend independent module
    • KT-82945 Analysis API: KotlinIllegalArgumentExceptionWithAttachments: Expected FirResolvedTypeRef with ConeKotlinType but was FirUserTypeRefImpl
    • KT-71135 AA: exception from sealed inheritors checker when analyzeCopy

    Analysis API. Infrastructure

    Fixes

    • KT-83905 Analysis API: Improve UX with test data
    • KT-84913 Extract compiler classes used by the PSI & Analysis API to a separate module
    • KT-64986 Analysis API: Implement Analysis API tests for different KMP Platforms
    • KT-84776 The test data manager misses the redundancy check in the update mode
    • KT-84962 The test data manager misses -ea flag
    • KT-84388 Preserve the EOF status in the test data manager to avoid extra changes
    • KT-84362 Analysis API tests produce many warnings due to "not yet loaded registry"
    • KT-84279 Test Data Manager fails on a clean build
    • KT-83913 Exclude compiler-based Analysis API tests from Git tracking
    • KT-80379 Extract per-module test generators for AA tests
    • KT-84120 Move CLI modules out of kotlin-compiler-fe10-for-ide
    • KT-83200 Track external dependencies of the Analysis API modules

    Analysis API. Light Classes

    • KT-82434 Light classes should prefer enum entries to properties
    • KT-84200 SLC: return type is not boxed for delegated methods with generic original method
    • KT-72451 "CCE: class PsiPrimitiveType cannot be cast to class PsiClassType" with same-named enum class and typealias

    Analysis API. PSI

    ... (truncated)

    Commits
    • add726c Add ChangeLog for 2.4.0-RC2
    • 69a4a81 [Wasm] Add binaryen per-file arguments
    • 8907c63 [KGP] Suppress duplicate compiler warning output when --warning-mode=all is a...
    • f9bef12 [KGP] Add failing test for duplicate compiler warning output via Problems API
    • 7fe49c2 KT-86268: Use 2.4.0 toolchain for abiValidation with older CVs
    • 13d25e2 KT-86268: Regression test added
    • f7d1a76 [JKlib] Fix JAR deserializer configuration.
    • 5cec663 [JKlib] Wire up JvmBuiltInClassDescriptorFactory
    • ef14611 Make the default stability of non-final classes Unknown instead of Stable
    • d76473a [Gradle] Fix GeneralNativeIT.shouldAllowToOverrideDownloadUrl test failures
    • Additional commits viewable in compare view

    Updates `org.jetbrains.kotlin.plugin.serialization` from 2.3.21 to 2.4.0
    Release notes

    Sourced from org.jetbrains.kotlin.plugin.serialization's releases.

    Kotlin 2.4.0

    Changelog

    Analysis API

    • KT-83867 OVERLOAD_RESOLUTION_AMBIGUITY false positive with assertEquals in IJ repo
    • KT-83723 [Analysis API] Enable experimental KDoc resolver by default
    • KT-83388 Analysis API: properly support KMP in KotlinPackageProvider
    • KT-65683 Analysis API: Dangling file session creation causes a computeIfAbsent contract violation

    Analysis API. Code Compilation

    • KT-78946 Evaluation of variable with local class in type parameter leads to InventNamesForLocalClasses exception

    Analysis API. FIR

    • KT-68260 K2 AA: InvalidFirElementTypeException “For CALLABLE_REFERENCE_EXPRESSION with text ::lam1, unexpected element of type: no element found” with illegal callable reference call
    • KT-70896 AA: False positive deprecation warning with override of built-in method in JDK mapped class
    • KT-84711 K2 IDE sometimes loses FIR plugin-generated declarations after file changes
    • KT-84625 Analysis API: collectDesignationPath fails for nested classes inside plugin-generated top-level classes
    • KT-84596 Improve K2 Jooq completion performance
    • KT-84525 KaValueParameterSymbol#getHasSynthesizedName returns false for FirDeclarationOrigin.SubstitutionOverride.DeclarationSite
    • KT-83546 Kotlin analysis reach ClsCustomNavigationPolicy
    • KT-84259 Move CommonDefaultImportsProvider to the frontend independent module
    • KT-82945 Analysis API: KotlinIllegalArgumentExceptionWithAttachments: Expected FirResolvedTypeRef with ConeKotlinType but was FirUserTypeRefImpl
    • KT-71135 AA: exception from sealed inheritors checker when analyzeCopy

    Analysis API. Infrastructure

    Fixes

    • KT-83905 Analysis API: Improve UX with test data
    • KT-84913 Extract compiler classes used by the PSI & Analysis API to a separate module
    • KT-64986 Analysis API: Implement Analysis API tests for different KMP Platforms
    • KT-84776 The test data manager misses the redundancy check in the update mode
    • KT-84962 The test data manager misses -ea flag
    • KT-84388 Preserve the EOF status in the test data manager to avoid extra changes
    • KT-84362 Analysis API tests produce many warnings due to "not yet loaded registry"
    • KT-84279 Test Data Manager fails on a clean build
    • KT-83913 Exclude compiler-based Analysis API tests from Git tracking
    • KT-80379 Extract per-module test generators for AA tests
    • KT-84120 Move CLI modules out of kotlin-compiler-fe10-for-ide
    • KT-83200 Track external dependencies of the Analysis API modules

    Analysis API. Light Classes

    • KT-82434 Light classes should prefer enum entries to properties
    • KT-84200 SLC: return type is not boxed for delegated methods with generic original method
    • KT-72451 "CCE: class PsiPrimitiveType cannot be cast to class PsiClassType" with same-named enum class and typealias

    ... (truncated)

    Changelog

    Sourced from org.jetbrains.kotlin.plugin.serialization's changelog.

    2.4.0

    Analysis API

    • KT-83867 OVERLOAD_RESOLUTION_AMBIGUITY false positive with assertEquals in IJ repo
    • KT-83723 [Analysis API] Enable experimental KDoc resolver by default
    • KT-83388 Analysis API: properly support KMP in KotlinPackageProvider
    • KT-65683 Analysis API: Dangling file session creation causes a computeIfAbsent contract violation

    Analysis API. Code Compilation

    • KT-78946 Evaluation of variable with local class in type parameter leads to InventNamesForLocalClasses exception

    Analysis API. FIR

    • KT-68260 K2 AA: InvalidFirElementTypeException “For CALLABLE_REFERENCE_EXPRESSION with text ::lam1, unexpected element of type: no element found” with illegal callable reference call
    • KT-70896 AA: False positive deprecation warning with override of built-in method in JDK mapped class
    • KT-84711 K2 IDE sometimes loses FIR plugin-generated declarations after file changes
    • KT-84625 Analysis API: collectDesignationPath fails for nested classes inside plugin-generated top-level classes
    • KT-84596 Improve K2 Jooq completion performance
    • KT-84525 KaValueParameterSymbol#getHasSynthesizedName returns false for FirDeclarationOrigin.SubstitutionOverride.DeclarationSite
    • KT-83546 Kotlin analysis reach ClsCustomNavigationPolicy
    • KT-84259 Move CommonDefaultImportsProvider to the frontend independent module
    • KT-82945 Analysis API: KotlinIllegalArgumentExceptionWithAttachments: Expected FirResolvedTypeRef with ConeKotlinType but was FirUserTypeRefImpl
    • KT-71135 AA: exception from sealed inheritors checker when analyzeCopy

    Analysis API. Infrastructure

    Fixes

    • KT-83905 Analysis API: Improve UX with test data
    • KT-84913 Extract compiler classes used by the PSI & Analysis API to a separate module
    • KT-64986 Analysis API: Implement Analysis API tests for different KMP Platforms
    • KT-84776 The test data manager misses the redundancy check in the update mode
    • KT-84962 The test data manager misses -ea flag
    • KT-84388 Preserve the EOF status in the test data manager to avoid extra changes
    • KT-84362 Analysis API tests produce many warnings due to "not yet loaded registry"
    • KT-84279 Test Data Manager fails on a clean build
    • KT-83913 Exclude compiler-based Analysis API tests from Git tracking
    • KT-80379 Extract per-module test generators for AA tests
    • KT-84120 Move CLI modules out of kotlin-compiler-fe10-for-ide
    • KT-83200 Track external dependencies of the Analysis API modules

    Analysis API. Light Classes

    • KT-82434 Light classes should prefer enum entries to properties
    • KT-84200 SLC: return type is not boxed for delegated methods with generic original method
    • KT-72451 "CCE: class PsiPrimitiveType cannot be cast to class PsiClassType" with same-named enum class and typealias

    Analysis API. PSI

    ... (truncated)

    Commits
    • add726c Add ChangeLog for 2.4.0-RC2
    • 69a4a81 [Wasm] Add binaryen per-file arguments
    • 8907c63 [KGP] Suppress duplicate compiler warning output when --warning-mode=all is a...
    • f9bef12 [KGP] Add failing test for duplicate compiler warning output via Problems API
    • 7fe49c2 KT-86268: Use 2.4.0 toolchain for abiValidation with older CVs
    • 13d25e2 KT-86268: Regression test added
    • f7d1a76 [JKlib] Fix JAR deserializer configuration.
    • 5cec663 [JKlib] Wire up JvmBuiltInClassDescriptorFactory
    • ef14611 Make the default stability of non-final classes Unknown instead of Stable
    • d76473a [Gradle] Fix GeneralNativeIT.shouldAllowToOverrideDownloadUrl test failures
    • Additional commits viewable in compare view

    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/simple-streamable-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/simple-streamable-server/gradle/libs.versions.toml b/samples/simple-streamable-server/gradle/libs.versions.toml index dc7393d..e3f8f6a 100644 --- a/samples/simple-streamable-server/gradle/libs.versions.toml +++ b/samples/simple-streamable-server/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin = "2.3.21" +kotlin = "2.4.0" ktor = "3.5.1" junit = "6.1.1" mcp-kotlin = "0.13.0" From 594ed4aa86ccc9f4777af735035ab7d4f1f4e0fd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Jul 2026 18:43:12 +0000 Subject: [PATCH 353/449] chore(deps): bump kotlin from 2.3.21 to 2.4.0 in /samples/kotlin-mcp-server (#822) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `kotlin` from 2.3.21 to 2.4.0. Updates `org.jetbrains.kotlin.jvm` from 2.3.21 to 2.4.0
    Release notes

    Sourced from org.jetbrains.kotlin.jvm's releases.

    Kotlin 2.4.0

    Changelog

    Analysis API

    • KT-83867 OVERLOAD_RESOLUTION_AMBIGUITY false positive with assertEquals in IJ repo
    • KT-83723 [Analysis API] Enable experimental KDoc resolver by default
    • KT-83388 Analysis API: properly support KMP in KotlinPackageProvider
    • KT-65683 Analysis API: Dangling file session creation causes a computeIfAbsent contract violation

    Analysis API. Code Compilation

    • KT-78946 Evaluation of variable with local class in type parameter leads to InventNamesForLocalClasses exception

    Analysis API. FIR

    • KT-68260 K2 AA: InvalidFirElementTypeException “For CALLABLE_REFERENCE_EXPRESSION with text ::lam1, unexpected element of type: no element found” with illegal callable reference call
    • KT-70896 AA: False positive deprecation warning with override of built-in method in JDK mapped class
    • KT-84711 K2 IDE sometimes loses FIR plugin-generated declarations after file changes
    • KT-84625 Analysis API: collectDesignationPath fails for nested classes inside plugin-generated top-level classes
    • KT-84596 Improve K2 Jooq completion performance
    • KT-84525 KaValueParameterSymbol#getHasSynthesizedName returns false for FirDeclarationOrigin.SubstitutionOverride.DeclarationSite
    • KT-83546 Kotlin analysis reach ClsCustomNavigationPolicy
    • KT-84259 Move CommonDefaultImportsProvider to the frontend independent module
    • KT-82945 Analysis API: KotlinIllegalArgumentExceptionWithAttachments: Expected FirResolvedTypeRef with ConeKotlinType but was FirUserTypeRefImpl
    • KT-71135 AA: exception from sealed inheritors checker when analyzeCopy

    Analysis API. Infrastructure

    Fixes

    • KT-83905 Analysis API: Improve UX with test data
    • KT-84913 Extract compiler classes used by the PSI & Analysis API to a separate module
    • KT-64986 Analysis API: Implement Analysis API tests for different KMP Platforms
    • KT-84776 The test data manager misses the redundancy check in the update mode
    • KT-84962 The test data manager misses -ea flag
    • KT-84388 Preserve the EOF status in the test data manager to avoid extra changes
    • KT-84362 Analysis API tests produce many warnings due to "not yet loaded registry"
    • KT-84279 Test Data Manager fails on a clean build
    • KT-83913 Exclude compiler-based Analysis API tests from Git tracking
    • KT-80379 Extract per-module test generators for AA tests
    • KT-84120 Move CLI modules out of kotlin-compiler-fe10-for-ide
    • KT-83200 Track external dependencies of the Analysis API modules

    Analysis API. Light Classes

    • KT-82434 Light classes should prefer enum entries to properties
    • KT-84200 SLC: return type is not boxed for delegated methods with generic original method
    • KT-72451 "CCE: class PsiPrimitiveType cannot be cast to class PsiClassType" with same-named enum class and typealias

    ... (truncated)

    Changelog

    Sourced from org.jetbrains.kotlin.jvm's changelog.

    2.4.0

    Analysis API

    • KT-83867 OVERLOAD_RESOLUTION_AMBIGUITY false positive with assertEquals in IJ repo
    • KT-83723 [Analysis API] Enable experimental KDoc resolver by default
    • KT-83388 Analysis API: properly support KMP in KotlinPackageProvider
    • KT-65683 Analysis API: Dangling file session creation causes a computeIfAbsent contract violation

    Analysis API. Code Compilation

    • KT-78946 Evaluation of variable with local class in type parameter leads to InventNamesForLocalClasses exception

    Analysis API. FIR

    • KT-68260 K2 AA: InvalidFirElementTypeException “For CALLABLE_REFERENCE_EXPRESSION with text ::lam1, unexpected element of type: no element found” with illegal callable reference call
    • KT-70896 AA: False positive deprecation warning with override of built-in method in JDK mapped class
    • KT-84711 K2 IDE sometimes loses FIR plugin-generated declarations after file changes
    • KT-84625 Analysis API: collectDesignationPath fails for nested classes inside plugin-generated top-level classes
    • KT-84596 Improve K2 Jooq completion performance
    • KT-84525 KaValueParameterSymbol#getHasSynthesizedName returns false for FirDeclarationOrigin.SubstitutionOverride.DeclarationSite
    • KT-83546 Kotlin analysis reach ClsCustomNavigationPolicy
    • KT-84259 Move CommonDefaultImportsProvider to the frontend independent module
    • KT-82945 Analysis API: KotlinIllegalArgumentExceptionWithAttachments: Expected FirResolvedTypeRef with ConeKotlinType but was FirUserTypeRefImpl
    • KT-71135 AA: exception from sealed inheritors checker when analyzeCopy

    Analysis API. Infrastructure

    Fixes

    • KT-83905 Analysis API: Improve UX with test data
    • KT-84913 Extract compiler classes used by the PSI & Analysis API to a separate module
    • KT-64986 Analysis API: Implement Analysis API tests for different KMP Platforms
    • KT-84776 The test data manager misses the redundancy check in the update mode
    • KT-84962 The test data manager misses -ea flag
    • KT-84388 Preserve the EOF status in the test data manager to avoid extra changes
    • KT-84362 Analysis API tests produce many warnings due to "not yet loaded registry"
    • KT-84279 Test Data Manager fails on a clean build
    • KT-83913 Exclude compiler-based Analysis API tests from Git tracking
    • KT-80379 Extract per-module test generators for AA tests
    • KT-84120 Move CLI modules out of kotlin-compiler-fe10-for-ide
    • KT-83200 Track external dependencies of the Analysis API modules

    Analysis API. Light Classes

    • KT-82434 Light classes should prefer enum entries to properties
    • KT-84200 SLC: return type is not boxed for delegated methods with generic original method
    • KT-72451 "CCE: class PsiPrimitiveType cannot be cast to class PsiClassType" with same-named enum class and typealias

    Analysis API. PSI

    ... (truncated)

    Commits
    • add726c Add ChangeLog for 2.4.0-RC2
    • 69a4a81 [Wasm] Add binaryen per-file arguments
    • 8907c63 [KGP] Suppress duplicate compiler warning output when --warning-mode=all is a...
    • f9bef12 [KGP] Add failing test for duplicate compiler warning output via Problems API
    • 7fe49c2 KT-86268: Use 2.4.0 toolchain for abiValidation with older CVs
    • 13d25e2 KT-86268: Regression test added
    • f7d1a76 [JKlib] Fix JAR deserializer configuration.
    • 5cec663 [JKlib] Wire up JvmBuiltInClassDescriptorFactory
    • ef14611 Make the default stability of non-final classes Unknown instead of Stable
    • d76473a [Gradle] Fix GeneralNativeIT.shouldAllowToOverrideDownloadUrl test failures
    • Additional commits viewable in compare view

    Updates `org.jetbrains.kotlin.plugin.serialization` from 2.3.21 to 2.4.0
    Release notes

    Sourced from org.jetbrains.kotlin.plugin.serialization's releases.

    Kotlin 2.4.0

    Changelog

    Analysis API

    • KT-83867 OVERLOAD_RESOLUTION_AMBIGUITY false positive with assertEquals in IJ repo
    • KT-83723 [Analysis API] Enable experimental KDoc resolver by default
    • KT-83388 Analysis API: properly support KMP in KotlinPackageProvider
    • KT-65683 Analysis API: Dangling file session creation causes a computeIfAbsent contract violation

    Analysis API. Code Compilation

    • KT-78946 Evaluation of variable with local class in type parameter leads to InventNamesForLocalClasses exception

    Analysis API. FIR

    • KT-68260 K2 AA: InvalidFirElementTypeException “For CALLABLE_REFERENCE_EXPRESSION with text ::lam1, unexpected element of type: no element found” with illegal callable reference call
    • KT-70896 AA: False positive deprecation warning with override of built-in method in JDK mapped class
    • KT-84711 K2 IDE sometimes loses FIR plugin-generated declarations after file changes
    • KT-84625 Analysis API: collectDesignationPath fails for nested classes inside plugin-generated top-level classes
    • KT-84596 Improve K2 Jooq completion performance
    • KT-84525 KaValueParameterSymbol#getHasSynthesizedName returns false for FirDeclarationOrigin.SubstitutionOverride.DeclarationSite
    • KT-83546 Kotlin analysis reach ClsCustomNavigationPolicy
    • KT-84259 Move CommonDefaultImportsProvider to the frontend independent module
    • KT-82945 Analysis API: KotlinIllegalArgumentExceptionWithAttachments: Expected FirResolvedTypeRef with ConeKotlinType but was FirUserTypeRefImpl
    • KT-71135 AA: exception from sealed inheritors checker when analyzeCopy

    Analysis API. Infrastructure

    Fixes

    • KT-83905 Analysis API: Improve UX with test data
    • KT-84913 Extract compiler classes used by the PSI & Analysis API to a separate module
    • KT-64986 Analysis API: Implement Analysis API tests for different KMP Platforms
    • KT-84776 The test data manager misses the redundancy check in the update mode
    • KT-84962 The test data manager misses -ea flag
    • KT-84388 Preserve the EOF status in the test data manager to avoid extra changes
    • KT-84362 Analysis API tests produce many warnings due to "not yet loaded registry"
    • KT-84279 Test Data Manager fails on a clean build
    • KT-83913 Exclude compiler-based Analysis API tests from Git tracking
    • KT-80379 Extract per-module test generators for AA tests
    • KT-84120 Move CLI modules out of kotlin-compiler-fe10-for-ide
    • KT-83200 Track external dependencies of the Analysis API modules

    Analysis API. Light Classes

    • KT-82434 Light classes should prefer enum entries to properties
    • KT-84200 SLC: return type is not boxed for delegated methods with generic original method
    • KT-72451 "CCE: class PsiPrimitiveType cannot be cast to class PsiClassType" with same-named enum class and typealias

    ... (truncated)

    Changelog

    Sourced from org.jetbrains.kotlin.plugin.serialization's changelog.

    2.4.0

    Analysis API

    • KT-83867 OVERLOAD_RESOLUTION_AMBIGUITY false positive with assertEquals in IJ repo
    • KT-83723 [Analysis API] Enable experimental KDoc resolver by default
    • KT-83388 Analysis API: properly support KMP in KotlinPackageProvider
    • KT-65683 Analysis API: Dangling file session creation causes a computeIfAbsent contract violation

    Analysis API. Code Compilation

    • KT-78946 Evaluation of variable with local class in type parameter leads to InventNamesForLocalClasses exception

    Analysis API. FIR

    • KT-68260 K2 AA: InvalidFirElementTypeException “For CALLABLE_REFERENCE_EXPRESSION with text ::lam1, unexpected element of type: no element found” with illegal callable reference call
    • KT-70896 AA: False positive deprecation warning with override of built-in method in JDK mapped class
    • KT-84711 K2 IDE sometimes loses FIR plugin-generated declarations after file changes
    • KT-84625 Analysis API: collectDesignationPath fails for nested classes inside plugin-generated top-level classes
    • KT-84596 Improve K2 Jooq completion performance
    • KT-84525 KaValueParameterSymbol#getHasSynthesizedName returns false for FirDeclarationOrigin.SubstitutionOverride.DeclarationSite
    • KT-83546 Kotlin analysis reach ClsCustomNavigationPolicy
    • KT-84259 Move CommonDefaultImportsProvider to the frontend independent module
    • KT-82945 Analysis API: KotlinIllegalArgumentExceptionWithAttachments: Expected FirResolvedTypeRef with ConeKotlinType but was FirUserTypeRefImpl
    • KT-71135 AA: exception from sealed inheritors checker when analyzeCopy

    Analysis API. Infrastructure

    Fixes

    • KT-83905 Analysis API: Improve UX with test data
    • KT-84913 Extract compiler classes used by the PSI & Analysis API to a separate module
    • KT-64986 Analysis API: Implement Analysis API tests for different KMP Platforms
    • KT-84776 The test data manager misses the redundancy check in the update mode
    • KT-84962 The test data manager misses -ea flag
    • KT-84388 Preserve the EOF status in the test data manager to avoid extra changes
    • KT-84362 Analysis API tests produce many warnings due to "not yet loaded registry"
    • KT-84279 Test Data Manager fails on a clean build
    • KT-83913 Exclude compiler-based Analysis API tests from Git tracking
    • KT-80379 Extract per-module test generators for AA tests
    • KT-84120 Move CLI modules out of kotlin-compiler-fe10-for-ide
    • KT-83200 Track external dependencies of the Analysis API modules

    Analysis API. Light Classes

    • KT-82434 Light classes should prefer enum entries to properties
    • KT-84200 SLC: return type is not boxed for delegated methods with generic original method
    • KT-72451 "CCE: class PsiPrimitiveType cannot be cast to class PsiClassType" with same-named enum class and typealias

    Analysis API. PSI

    ... (truncated)

    Commits
    • add726c Add ChangeLog for 2.4.0-RC2
    • 69a4a81 [Wasm] Add binaryen per-file arguments
    • 8907c63 [KGP] Suppress duplicate compiler warning output when --warning-mode=all is a...
    • f9bef12 [KGP] Add failing test for duplicate compiler warning output via Problems API
    • 7fe49c2 KT-86268: Use 2.4.0 toolchain for abiValidation with older CVs
    • 13d25e2 KT-86268: Regression test added
    • f7d1a76 [JKlib] Fix JAR deserializer configuration.
    • 5cec663 [JKlib] Wire up JvmBuiltInClassDescriptorFactory
    • ef14611 Make the default stability of non-final classes Unknown instead of Stable
    • d76473a [Gradle] Fix GeneralNativeIT.shouldAllowToOverrideDownloadUrl test failures
    • Additional commits viewable in compare view

    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-server/gradle/libs.versions.toml b/samples/kotlin-mcp-server/gradle/libs.versions.toml index 91c9513..5a6217e 100644 --- a/samples/kotlin-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-server/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin = "2.3.21" +kotlin = "2.4.0" ktor = "3.5.1" mcp-kotlin = "0.13.0" slf4j = "2.0.18" From 1866effd07705bece73f882eb7b210bf04377af8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Jul 2026 22:04:38 +0200 Subject: [PATCH 354/449] chore(deps): bump the ktor group across 1 directory with 15 updates (#832) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the ktor group with 15 updates in the / directory: | Package | From | To | | --- | --- | --- | | [io.ktor:ktor-client-apache5](https://github.com/ktorio/ktor) | `3.4.3` | `3.5.1` | | [io.ktor:ktor-client-auth](https://github.com/ktorio/ktor) | `3.4.3` | `3.5.1` | | [io.ktor:ktor-client-core](https://github.com/ktorio/ktor) | `3.4.3` | `3.5.1` | | [io.ktor:ktor-client-logging](https://github.com/ktorio/ktor) | `3.4.3` | `3.5.1` | | [io.ktor:ktor-server-content-negotiation](https://github.com/ktorio/ktor) | `3.4.3` | `3.5.1` | | [io.ktor:ktor-client-content-negotiation](https://github.com/ktorio/ktor) | `3.4.3` | `3.5.1` | | [io.ktor:ktor-serialization-kotlinx-json](https://github.com/ktorio/ktor) | `3.4.3` | `3.5.1` | | [io.ktor:ktor-server-auth](https://github.com/ktorio/ktor) | `3.4.3` | `3.5.1` | | [io.ktor:ktor-server-core](https://github.com/ktorio/ktor) | `3.4.3` | `3.5.1` | | [io.ktor:ktor-server-sse](https://github.com/ktorio/ktor) | `3.4.3` | `3.5.1` | | [io.ktor:ktor-server-websockets](https://github.com/ktorio/ktor) | `3.4.3` | `3.5.1` | | [io.ktor:ktor-client-mock](https://github.com/ktorio/ktor) | `3.4.3` | `3.5.1` | | [io.ktor:ktor-server-test-host](https://github.com/ktorio/ktor) | `3.4.3` | `3.5.1` | | [io.ktor:ktor-client-cio](https://github.com/ktorio/ktor) | `3.4.3` | `3.5.1` | | [io.ktor:ktor-server-cio](https://github.com/ktorio/ktor) | `3.4.3` | `3.5.1` | Updates `io.ktor:ktor-client-apache5` from 3.4.3 to 3.5.1
    Release notes

    Sourced from io.ktor:ktor-client-apache5's releases.

    3.5.1

    Published 25 June 2026

    Improvements

    • KTOR-9492 OpenAPI plugin: handle requireXxx functions in compiler plugin
    • KTOR-9569 Add more details for KDoc of BearerAuthConfig.sendWithoutRequest
    • KTOR-9571 Add KDoc for BearerAuthConfig.realm
    • KTOR-9570 Add Kdoc for BearerAuthConfig.refreshTokens

    Bugfixes

    • KTOR-9646 Fix Kotlin 2.4.0 compiler plugin breaking changes
    • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
    • KTOR-9591 OpenAPI: @JsonClassDiscriminator on sealed types is ignored when generating OpenAPI discriminator schema
    • KTOR-9597 OpenAPI: Schema name collisions because@SerialName of sealed subtypes is used as components.schemas key
    • KTOR-9601 OpenAPI: nullable @​JvmInline value class loses nullability in generated schema
    • KTOR-9620 Some issues related to digest authentication in 3.5.0
    • KTOR-9623 Digest Auth: server must respond with one WWW-Authenticate header for each supported algorithm
    • KTOR-9624 Digest Auth: "Unsupported charset in digest authentication header" for a charset name in lowercase
    • KTOR-9630 Digest Auth: The plugin sends incorrect nonce when server responds with multiple WWW-Authenticate headers
    • KTOR-9565 WebRTC: Peer stops receiving messages after a while on iOS
    • KTOR-9610 WebRtcPeerConnection::close causes ClosedReceiveChannelException for other peer
    • KTOR-9633 WebRTC statistics fetching race condition
    • KTOR-9603 Structured concurrency is violated in WebRTC tests
    • KTOR-9671 Darwin: Semicolons in URL path are sanitized
    • KTOR-9639 Darwin: WebSocket client crashes the process when a PONG arrives after the session is closed
    • KTOR-9575 Darwin: findCharset("UTF-16") maps to NSUTF16LittleEndianStringEncoding causing decoding failure for UTF-16 content with BOM
    • KTOR-9661 HttpCache: InvalidCacheStateException is thrown on 304 when no cached entry matches varyKeys
    • KTOR-9673 Caching always missing with Content Negotiation
    • KTOR-9596 HttpTimeout: HttpRequestTimeoutException on any request with runTest and request timeout defined since 3.5.0
    • KTOR-9632 HttpRequestBuilder.timeout call removes capabilities set in DefaultRequest
    • KTOR-9252 ContentEncoding: Incomplete gzip response causes client/server to hang indefinitely
    • KTOR-9670 Compression: The plugin ignores Accept-Encoding q=0 and most-specific matching
    • KTOR-9614 Zstd Compression: "Destination buffer is too small" exception for a particular sequence of bytes
    • KTOR-9636 CORS plugin drops OPTIONS preflight requests when allowSameOrigin is on
    • KTOR-9659 CORS is skipped when the Origin header contains an IPv6 address
    • KTOR-9574 ContentNegotiation: The charset of Accept-Charset header is used for response deserialization
    • KTOR-9606 KotlinxSerializationConverter: it fails to deserialize an empty channel closed with a delay
    • KTOR-9617 OutputStreamContent and WriterContent can exhaust Dispatchers.IO
    • KTOR-9629 RawSourceChannel: coroutine cancellation is not propagated to the RawSource
    • KTOR-8870 File.readChannel: do not close a non-opened file and ignore closing exceptions
    • KTOR-9460 Curl: Can't build shared library with Ktor 3.4.2
    • KTOR-9607 Crash on Android 7: NoSuchMethodError getInstanceStrong() since 3.5.0
    • KTOR-9595 CIO: The engine imports node:net statically and breaks wasmJsBrowser webpack build
    • KTOR-9615 Apache5: The underlying request is not aborted when coroutine Job is cancelled
    • KTOR-8443 RoutingBuilder.contentType does not use parameters when matching
    • KTOR-9425 SessionTransportTransformerEncrypt init block uses wrong IV size for AES-256 (regression of KTOR-661)
    • KTOR-9621 RateLimit plugin is bypassed when the nested authenticate block rejects the request
    • KTOR-8766 DI: The close method is called twice on cleanup of AutoCloseable
    • KTOR-8695 GMTDate() being offset by 369 years on Windows
    • KTOR-9589 Duplicate PROPERTY_SETTER target in @InternalAPI annotation

    ... (truncated)

    Changelog

    Sourced from io.ktor:ktor-client-apache5's changelog.

    3.5.1

    Published 25 June 2026

    Improvements

    • KTOR-9492 OpenAPI plugin: handle requireXxx functions in compiler plugin
    • KTOR-9569 Add more details for KDoc of BearerAuthConfig.sendWithoutRequest
    • KTOR-9571 Add KDoc for BearerAuthConfig.realm
    • KTOR-9570 Add Kdoc for BearerAuthConfig.refreshTokens

    Bugfixes

    • KTOR-9646 Fix Kotlin 2.4.0 compiler plugin breaking changes
    • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
    • KTOR-9591 OpenAPI: @JsonClassDiscriminator on sealed types is ignored when generating OpenAPI discriminator schema
    • KTOR-9597 OpenAPI: Schema name collisions because@SerialName of sealed subtypes is used as components.schemas key
    • KTOR-9601 OpenAPI: nullable @​JvmInline value class loses nullability in generated schema
    • KTOR-9620 Some issues related to digest authentication in 3.5.0
    • KTOR-9623 Digest Auth: server must respond with one WWW-Authenticate header for each supported algorithm
    • KTOR-9624 Digest Auth: "Unsupported charset in digest authentication header" for a charset name in lowercase
    • KTOR-9630 Digest Auth: The plugin sends incorrect nonce when server responds with multiple WWW-Authenticate headers
    • KTOR-9565 WebRTC: Peer stops receiving messages after a while on iOS
    • KTOR-9610 WebRtcPeerConnection::close causes ClosedReceiveChannelException for other peer
    • KTOR-9633 WebRTC statistics fetching race condition
    • KTOR-9603 Structured concurrency is violated in WebRTC tests
    • KTOR-9671 Darwin: Semicolons in URL path are sanitized
    • KTOR-9639 Darwin: WebSocket client crashes the process when a PONG arrives after the session is closed
    • KTOR-9575 Darwin: findCharset("UTF-16") maps to NSUTF16LittleEndianStringEncoding causing decoding failure for UTF-16 content with BOM
    • KTOR-9661 HttpCache: InvalidCacheStateException is thrown on 304 when no cached entry matches varyKeys
    • KTOR-9673 Caching always missing with Content Negotiation
    • KTOR-9596 HttpTimeout: HttpRequestTimeoutException on any request with runTest and request timeout defined since 3.5.0
    • KTOR-9632 HttpRequestBuilder.timeout call removes capabilities set in DefaultRequest
    • KTOR-9252 ContentEncoding: Incomplete gzip response causes client/server to hang indefinitely
    • KTOR-9670 Compression: The plugin ignores Accept-Encoding q=0 and most-specific matching
    • KTOR-9614 Zstd Compression: "Destination buffer is too small" exception for a particular sequence of bytes
    • KTOR-9636 CORS plugin drops OPTIONS preflight requests when allowSameOrigin is on
    • KTOR-9659 CORS is skipped when the Origin header contains an IPv6 address
    • KTOR-9574 ContentNegotiation: The charset of Accept-Charset header is used for response deserialization
    • KTOR-9606 KotlinxSerializationConverter: it fails to deserialize an empty channel closed with a delay
    • KTOR-9617 OutputStreamContent and WriterContent can exhaust Dispatchers.IO
    • KTOR-9629 RawSourceChannel: coroutine cancellation is not propagated to the RawSource
    • KTOR-8870 File.readChannel: do not close a non-opened file and ignore closing exceptions
    • KTOR-9460 Curl: Can't build shared library with Ktor 3.4.2
    • KTOR-9607 Crash on Android 7: NoSuchMethodError getInstanceStrong() since 3.5.0
    • KTOR-9595 CIO: The engine imports node:net statically and breaks wasmJsBrowser webpack build
    • KTOR-9615 Apache5: The underlying request is not aborted when coroutine Job is cancelled
    • KTOR-8443 RoutingBuilder.contentType does not use parameters when matching
    • KTOR-9425 SessionTransportTransformerEncrypt init block uses wrong IV size for AES-256 (regression of KTOR-661)
    • KTOR-9621 RateLimit plugin is bypassed when the nested authenticate block rejects the request
    • KTOR-8766 DI: The close method is called twice on cleanup of AutoCloseable
    • KTOR-8695 GMTDate() being offset by 369 years on Windows
    • KTOR-9589 Duplicate PROPERTY_SETTER target in @​InternalAPI annotation

    ... (truncated)

    Commits
    • 5ba9d6f Release 3.5.1 (#5719)
    • a4f145d KTOR-9661 HttpCache: InvalidCacheStateException is thrown on 304 when… (#5701)
    • 2caf278 Fix failing :ktor-io:compileDarwinMainKotlinMetadata (#5716)
    • 257de90 KTOR-8766 Fix multiple invocations of close from DI
    • e77a168 Fix cache misses with content negotiation 3x (#5714)
    • ed22950 Make huge post test less huge
    • f329069 Update dependency com.github.luben:zstd-jni to v1.5.7-11
    • 6ecd072 Update dependency io.swagger.parser.v3:swagger-parser to v2.1.44
    • a0534cd Update dependency io.pebbletemplates:pebble to v4.1.2
    • 8647e53 Update dependency com.typesafe:config to v1.4.9
    • Additional commits viewable in compare view

    Updates `io.ktor:ktor-client-auth` from 3.4.3 to 3.5.1
    Release notes

    Sourced from io.ktor:ktor-client-auth's releases.

    3.5.1

    Published 25 June 2026

    Improvements

    • KTOR-9492 OpenAPI plugin: handle requireXxx functions in compiler plugin
    • KTOR-9569 Add more details for KDoc of BearerAuthConfig.sendWithoutRequest
    • KTOR-9571 Add KDoc for BearerAuthConfig.realm
    • KTOR-9570 Add Kdoc for BearerAuthConfig.refreshTokens

    Bugfixes

    • KTOR-9646 Fix Kotlin 2.4.0 compiler plugin breaking changes
    • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
    • KTOR-9591 OpenAPI: @JsonClassDiscriminator on sealed types is ignored when generating OpenAPI discriminator schema
    • KTOR-9597 OpenAPI: Schema name collisions because@SerialName of sealed subtypes is used as components.schemas key
    • KTOR-9601 OpenAPI: nullable @​JvmInline value class loses nullability in generated schema
    • KTOR-9620 Some issues related to digest authentication in 3.5.0
    • KTOR-9623 Digest Auth: server must respond with one WWW-Authenticate header for each supported algorithm
    • KTOR-9624 Digest Auth: "Unsupported charset in digest authentication header" for a charset name in lowercase
    • KTOR-9630 Digest Auth: The plugin sends incorrect nonce when server responds with multiple WWW-Authenticate headers
    • KTOR-9565 WebRTC: Peer stops receiving messages after a while on iOS
    • KTOR-9610 WebRtcPeerConnection::close causes ClosedReceiveChannelException for other peer
    • KTOR-9633 WebRTC statistics fetching race condition
    • KTOR-9603 Structured concurrency is violated in WebRTC tests
    • KTOR-9671 Darwin: Semicolons in URL path are sanitized
    • KTOR-9639 Darwin: WebSocket client crashes the process when a PONG arrives after the session is closed
    • KTOR-9575 Darwin: findCharset("UTF-16") maps to NSUTF16LittleEndianStringEncoding causing decoding failure for UTF-16 content with BOM
    • KTOR-9661 HttpCache: InvalidCacheStateException is thrown on 304 when no cached entry matches varyKeys
    • KTOR-9673 Caching always missing with Content Negotiation
    • KTOR-9596 HttpTimeout: HttpRequestTimeoutException on any request with runTest and request timeout defined since 3.5.0
    • KTOR-9632 HttpRequestBuilder.timeout call removes capabilities set in DefaultRequest
    • KTOR-9252 ContentEncoding: Incomplete gzip response causes client/server to hang indefinitely
    • KTOR-9670 Compression: The plugin ignores Accept-Encoding q=0 and most-specific matching
    • KTOR-9614 Zstd Compression: "Destination buffer is too small" exception for a particular sequence of bytes
    • KTOR-9636 CORS plugin drops OPTIONS preflight requests when allowSameOrigin is on
    • KTOR-9659 CORS is skipped when the Origin header contains an IPv6 address
    • KTOR-9574 ContentNegotiation: The charset of Accept-Charset header is used for response deserialization
    • KTOR-9606 KotlinxSerializationConverter: it fails to deserialize an empty channel closed with a delay
    • KTOR-9617 OutputStreamContent and WriterContent can exhaust Dispatchers.IO
    • KTOR-9629 RawSourceChannel: coroutine cancellation is not propagated to the RawSource
    • KTOR-8870 File.readChannel: do not close a non-opened file and ignore closing exceptions
    • KTOR-9460 Curl: Can't build shared library with Ktor 3.4.2
    • KTOR-9607 Crash on Android 7: NoSuchMethodError getInstanceStrong() since 3.5.0
    • KTOR-9595 CIO: The engine imports node:net statically and breaks wasmJsBrowser webpack build
    • KTOR-9615 Apache5: The underlying request is not aborted when coroutine Job is cancelled
    • KTOR-8443 RoutingBuilder.contentType does not use parameters when matching
    • KTOR-9425 SessionTransportTransformerEncrypt init block uses wrong IV size for AES-256 (regression of KTOR-661)
    • KTOR-9621 RateLimit plugin is bypassed when the nested authenticate block rejects the request
    • KTOR-8766 DI: The close method is called twice on cleanup of AutoCloseable
    • KTOR-8695 GMTDate() being offset by 369 years on Windows
    • KTOR-9589 Duplicate PROPERTY_SETTER target in @InternalAPI annotation

    ... (truncated)

    Changelog

    Sourced from io.ktor:ktor-client-auth's changelog.

    3.5.1

    Published 25 June 2026

    Improvements

    • KTOR-9492 OpenAPI plugin: handle requireXxx functions in compiler plugin
    • KTOR-9569 Add more details for KDoc of BearerAuthConfig.sendWithoutRequest
    • KTOR-9571 Add KDoc for BearerAuthConfig.realm
    • KTOR-9570 Add Kdoc for BearerAuthConfig.refreshTokens

    Bugfixes

    • KTOR-9646 Fix Kotlin 2.4.0 compiler plugin breaking changes
    • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
    • KTOR-9591 OpenAPI: @JsonClassDiscriminator on sealed types is ignored when generating OpenAPI discriminator schema
    • KTOR-9597 OpenAPI: Schema name collisions because@SerialName of sealed subtypes is used as components.schemas key
    • KTOR-9601 OpenAPI: nullable @​JvmInline value class loses nullability in generated schema
    • KTOR-9620 Some issues related to digest authentication in 3.5.0
    • KTOR-9623 Digest Auth: server must respond with one WWW-Authenticate header for each supported algorithm
    • KTOR-9624 Digest Auth: "Unsupported charset in digest authentication header" for a charset name in lowercase
    • KTOR-9630 Digest Auth: The plugin sends incorrect nonce when server responds with multiple WWW-Authenticate headers
    • KTOR-9565 WebRTC: Peer stops receiving messages after a while on iOS
    • KTOR-9610 WebRtcPeerConnection::close causes ClosedReceiveChannelException for other peer
    • KTOR-9633 WebRTC statistics fetching race condition
    • KTOR-9603 Structured concurrency is violated in WebRTC tests
    • KTOR-9671 Darwin: Semicolons in URL path are sanitized
    • KTOR-9639 Darwin: WebSocket client crashes the process when a PONG arrives after the session is closed
    • KTOR-9575 Darwin: findCharset("UTF-16") maps to NSUTF16LittleEndianStringEncoding causing decoding failure for UTF-16 content with BOM
    • KTOR-9661 HttpCache: InvalidCacheStateException is thrown on 304 when no cached entry matches varyKeys
    • KTOR-9673 Caching always missing with Content Negotiation
    • KTOR-9596 HttpTimeout: HttpRequestTimeoutException on any request with runTest and request timeout defined since 3.5.0
    • KTOR-9632 HttpRequestBuilder.timeout call removes capabilities set in DefaultRequest
    • KTOR-9252 ContentEncoding: Incomplete gzip response causes client/server to hang indefinitely
    • KTOR-9670 Compression: The plugin ignores Accept-Encoding q=0 and most-specific matching
    • KTOR-9614 Zstd Compression: "Destination buffer is too small" exception for a particular sequence of bytes
    • KTOR-9636 CORS plugin drops OPTIONS preflight requests when allowSameOrigin is on
    • KTOR-9659 CORS is skipped when the Origin header contains an IPv6 address
    • KTOR-9574 ContentNegotiation: The charset of Accept-Charset header is used for response deserialization
    • KTOR-9606 KotlinxSerializationConverter: it fails to deserialize an empty channel closed with a delay
    • KTOR-9617 OutputStreamContent and WriterContent can exhaust Dispatchers.IO
    • KTOR-9629 RawSourceChannel: coroutine cancellation is not propagated to the RawSource
    • KTOR-8870 File.readChannel: do not close a non-opened file and ignore closing exceptions
    • KTOR-9460 Curl: Can't build shared library with Ktor 3.4.2
    • KTOR-9607 Crash on Android 7: NoSuchMethodError getInstanceStrong() since 3.5.0
    • KTOR-9595 CIO: The engine imports node:net statically and breaks wasmJsBrowser webpack build
    • KTOR-9615 Apache5: The underlying request is not aborted when coroutine Job is cancelled
    • KTOR-8443 RoutingBuilder.contentType does not use parameters when matching
    • KTOR-9425 SessionTransportTransformerEncrypt init block uses wrong IV size for AES-256 (regression of KTOR-661)
    • KTOR-9621 RateLimit plugin is bypassed when the nested authenticate block rejects the request
    • KTOR-8766 DI: The close method is called twice on cleanup of AutoCloseable
    • KTOR-8695 GMTDate() being offset by 369 years on Windows
    • KTOR-9589 Duplicate PROPERTY_SETTER target in @​InternalAPI annotation

    ... (truncated)

    Commits
    • 5ba9d6f Release 3.5.1 (#5719)
    • a4f145d KTOR-9661 HttpCache: InvalidCacheStateException is thrown on 304 when… (#5701)
    • 2caf278 Fix failing :ktor-io:compileDarwinMainKotlinMetadata (#5716)
    • 257de90 KTOR-8766 Fix multiple invocations of close from DI
    • e77a168 Fix cache misses with content negotiation 3x (#5714)
    • ed22950 Make huge post test less huge
    • f329069 Update dependency com.github.luben:zstd-jni to v1.5.7-11
    • 6ecd072 Update dependency io.swagger.parser.v3:swagger-parser to v2.1.44
    • a0534cd Update dependency io.pebbletemplates:pebble to v4.1.2
    • 8647e53 Update dependency com.typesafe:config to v1.4.9
    • Additional commits viewable in compare view

    Updates `io.ktor:ktor-client-core` from 3.4.3 to 3.5.1
    Release notes

    Sourced from io.ktor:ktor-client-core's releases.

    3.5.1

    Published 25 June 2026

    Improvements

    • KTOR-9492 OpenAPI plugin: handle requireXxx functions in compiler plugin
    • KTOR-9569 Add more details for KDoc of BearerAuthConfig.sendWithoutRequest
    • KTOR-9571 Add KDoc for BearerAuthConfig.realm
    • KTOR-9570 Add Kdoc for BearerAuthConfig.refreshTokens

    Bugfixes

    • KTOR-9646 Fix Kotlin 2.4.0 compiler plugin breaking changes
    • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
    • KTOR-9591 OpenAPI: @JsonClassDiscriminator on sealed types is ignored when generating OpenAPI discriminator schema
    • KTOR-9597 OpenAPI: Schema name collisions because@SerialName of sealed subtypes is used as components.schemas key
    • KTOR-9601 OpenAPI: nullable @​JvmInline value class loses nullability in generated schema
    • KTOR-9620 Some issues related to digest authentication in 3.5.0
    • KTOR-9623 Digest Auth: server must respond with one WWW-Authenticate header for each supported algorithm
    • KTOR-9624 Digest Auth: "Unsupported charset in digest authentication header" for a charset name in lowercase
    • KTOR-9630 Digest Auth: The plugin sends incorrect nonce when server responds with multiple WWW-Authenticate headers
    • KTOR-9565 WebRTC: Peer stops receiving messages after a while on iOS
    • KTOR-9610 WebRtcPeerConnection::close causes ClosedReceiveChannelException for other peer
    • KTOR-9633 WebRTC statistics fetching race condition
    • KTOR-9603 Structured concurrency is violated in WebRTC tests
    • KTOR-9671 Darwin: Semicolons in URL path are sanitized
    • KTOR-9639 Darwin: WebSocket client crashes the process when a PONG arrives after the session is closed
    • KTOR-9575 Darwin: findCharset("UTF-16") maps to NSUTF16LittleEndianStringEncoding causing decoding failure for UTF-16 content with BOM
    • KTOR-9661 HttpCache: InvalidCacheStateException is thrown on 304 when no cached entry matches varyKeys
    • KTOR-9673 Caching always missing with Content Negotiation
    • KTOR-9596 HttpTimeout: HttpRequestTimeoutException on any request with runTest and request timeout defined since 3.5.0
    • KTOR-9632 HttpRequestBuilder.timeout call removes capabilities set in DefaultRequest
    • KTOR-9252 ContentEncoding: Incomplete gzip response causes client/server to hang indefinitely
    • KTOR-9670 Compression: The plugin ignores Accept-Encoding q=0 and most-specific matching
    • KTOR-9614 Zstd Compression: "Destination buffer is too small" exception for a particular sequence of bytes
    • KTOR-9636 CORS plugin drops OPTIONS preflight requests when allowSameOrigin is on
    • KTOR-9659 CORS is skipped when the Origin header contains an IPv6 address
    • KTOR-9574 ContentNegotiation: The charset of Accept-Charset header is used for response deserialization
    • KTOR-9606 KotlinxSerializationConverter: it fails to deserialize an empty channel closed with a delay
    • KTOR-9617 OutputStreamContent and WriterContent can exhaust Dispatchers.IO
    • KTOR-9629 RawSourceChannel: coroutine cancellation is not propagated to the RawSource
    • KTOR-8870 File.readChannel: do not close a non-opened file and ignore closing exceptions
    • KTOR-9460 Curl: Can't build shared library with Ktor 3.4.2
    • KTOR-9607 Crash on Android 7: NoSuchMethodError getInstanceStrong() since 3.5.0
    • KTOR-9595 CIO: The engine imports node:net statically and breaks wasmJsBrowser webpack build
    • KTOR-9615 Apache5: The underlying request is not aborted when coroutine Job is cancelled
    • KTOR-8443 RoutingBuilder.contentType does not use parameters when matching
    • KTOR-9425 SessionTransportTransformerEncrypt init block uses wrong IV size for AES-256 (regression of KTOR-661)
    • KTOR-9621 RateLimit plugin is bypassed when the nested authenticate block rejects the request
    • KTOR-8766 DI: The close method is called twice on cleanup of AutoCloseable
    • KTOR-8695 GMTDate() being offset by 369 years on Windows
    • KTOR-9589 Duplicate PROPERTY_SETTER target in @InternalAPI annotation

    ... (truncated)

    Changelog

    Sourced from io.ktor:ktor-client-core's changelog.

    3.5.1

    Published 25 June 2026

    Improvements

    • KTOR-9492 OpenAPI plugin: handle requireXxx functions in compiler plugin
    • KTOR-9569 Add more details for KDoc of BearerAuthConfig.sendWithoutRequest
    • KTOR-9571 Add KDoc for BearerAuthConfig.realm
    • KTOR-9570 Add Kdoc for BearerAuthConfig.refreshTokens

    Bugfixes

    • KTOR-9646 Fix Kotlin 2.4.0 compiler plugin breaking changes
    • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
    • KTOR-9591 OpenAPI: @JsonClassDiscriminator on sealed types is ignored when generating OpenAPI discriminator schema
    • KTOR-9597 OpenAPI: Schema name collisions because@SerialName of sealed subtypes is used as components.schemas key
    • KTOR-9601 OpenAPI: nullable @​JvmInline value class loses nullability in generated schema
    • KTOR-9620 Some issues related to digest authentication in 3.5.0
    • KTOR-9623 Digest Auth: server must respond with one WWW-Authenticate header for each supported algorithm
    • KTOR-9624 Digest Auth: "Unsupported charset in digest authentication header" for a charset name in lowercase
    • KTOR-9630 Digest Auth: The plugin sends incorrect nonce when server responds with multiple WWW-Authenticate headers
    • KTOR-9565 WebRTC: Peer stops receiving messages after a while on iOS
    • KTOR-9610 WebRtcPeerConnection::close causes ClosedReceiveChannelException for other peer
    • KTOR-9633 WebRTC statistics fetching race condition
    • KTOR-9603 Structured concurrency is violated in WebRTC tests
    • KTOR-9671 Darwin: Semicolons in URL path are sanitized
    • KTOR-9639 Darwin: WebSocket client crashes the process when a PONG arrives after the session is closed
    • KTOR-9575 Darwin: findCharset("UTF-16") maps to NSUTF16LittleEndianStringEncoding causing decoding failure for UTF-16 content with BOM
    • KTOR-9661 HttpCache: InvalidCacheStateException is thrown on 304 when no cached entry matches varyKeys
    • KTOR-9673 Caching always missing with Content Negotiation
    • KTOR-9596 HttpTimeout: HttpRequestTimeoutException on any request with runTest and request timeout defined since 3.5.0
    • KTOR-9632 HttpRequestBuilder.timeout call removes capabilities set in DefaultRequest
    • KTOR-9252 ContentEncoding: Incomplete gzip response causes client/server to hang indefinitely
    • KTOR-9670 Compression: The plugin ignores Accept-Encoding q=0 and most-specific matching
    • KTOR-9614 Zstd Compression: "Destination buffer is too small" exception for a particular sequence of bytes
    • KTOR-9636 CORS plugin drops OPTIONS preflight requests when allowSameOrigin is on
    • KTOR-9659 CORS is skipped when the Origin header contains an IPv6 address
    • KTOR-9574 ContentNegotiation: The charset of Accept-Charset header is used for response deserialization
    • KTOR-9606 KotlinxSerializationConverter: it fails to deserialize an empty channel closed with a delay
    • KTOR-9617 OutputStreamContent and WriterContent can exhaust Dispatchers.IO
    • KTOR-9629 RawSourceChannel: coroutine cancellation is not propagated to the RawSource
    • KTOR-8870 File.readChannel: do not close a non-opened file and ignore closing exceptions
    • KTOR-9460 Curl: Can't build shared library with Ktor 3.4.2
    • KTOR-9607 Crash on Android 7: NoSuchMethodError getInstanceStrong() since 3.5.0
    • KTOR-9595 CIO: The engine imports node:net statically and breaks wasmJsBrowser webpack build
    • KTOR-9615 Apache5: The underlying request is not aborted when coroutine Job is cancelled
    • KTOR-8443 RoutingBuilder.contentType does not use parameters when matching
    • KTOR-9425 SessionTransportTransformerEncrypt init block uses wrong IV size for AES-256 (regression of KTOR-661)
    • KTOR-9621 RateLimit plugin is bypassed when the nested authenticate block rejects the request
    • KTOR-8766 DI: The close method is called twice on cleanup of AutoCloseable
    • KTOR-8695 GMTDate() being offset by 369 years on Windows
    • KTOR-9589 Duplicate PROPERTY_SETTER target in @​InternalAPI annotation

    ... (truncated)

    Commits
    • 5ba9d6f Release 3.5.1 (#5719)
    • a4f145d KTOR-9661 HttpCache: InvalidCacheStateException is thrown on 304 when… (#5701)
    • 2caf278 Fix failing :ktor-io:compileDarwinMainKotlinMetadata (#5716)
    • 257de90 KTOR-8766 Fix multiple invocations of close from DI
    • e77a168 Fix cache misses with content negotiation 3x (#5714)
    • ed22950 Make huge post test less huge
    • f329069 Update dependency com.github.luben:zstd-jni to v1.5.7-11
    • 6ecd072 Update dependency io.swagger.parser.v3:swagger-parser to v2.1.44
    • a0534cd Update dependency io.pebbletemplates:pebble to v4.1.2
    • 8647e53 Update dependency com.typesafe:config to v1.4.9
    • Additional commits viewable in compare view

    Updates `io.ktor:ktor-client-logging` from 3.4.3 to 3.5.1
    Release notes

    Sourced from io.ktor:ktor-client-logging's releases.

    3.5.1

    Published 25 June 2026

    Improvements

    • KTOR-9492 OpenAPI plugin: handle requireXxx functions in compiler plugin
    • KTOR-9569 Add more details for KDoc of BearerAuthConfig.sendWithoutRequest
    • KTOR-9571 Add KDoc for BearerAuthConfig.realm
    • KTOR-9570 Add Kdoc for BearerAuthConfig.refreshTokens

    Bugfixes

    • KTOR-9646 Fix Kotlin 2.4.0 compiler plugin breaking changes
    • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
    • KTOR-9591 OpenAPI: @JsonClassDiscriminator on sealed types is ignored when generating OpenAPI discriminator schema
    • KTOR-9597 OpenAPI: Schema name collisions because@SerialName of sealed subtypes is used as components.schemas key
    • KTOR-9601 OpenAPI: nullable @​JvmInline value class loses nullability in generated schema
    • KTOR-9620 Some issues related to digest authentication in 3.5.0
    • KTOR-9623 Digest Auth: server must respond with one WWW-Authenticate header for each supported algorithm
    • KTOR-9624 Digest Auth: "Unsupported charset in digest authentication header" for a charset name in lowercase
    • KTOR-9630 Digest Auth: The plugin sends incorrect nonce when server responds with multiple WWW-Authenticate headers
    • KTOR-9565 WebRTC: Peer stops receiving messages after a while on iOS
    • KTOR-9610 WebRtcPeerConnection::close causes ClosedReceiveChannelException for other peer
    • KTOR-9633 WebRTC statistics fetching race condition
    • KTOR-9603 Structured concurrency is violated in WebRTC tests
    • KTOR-9671 Darwin: Semicolons in URL path are sanitized
    • KTOR-9639 Darwin: WebSocket client crashes the process when a PONG arrives after the session is closed
    • KTOR-9575 Darwin: findCharset("UTF-16") maps to NSUTF16LittleEndianStringEncoding causing decoding failure for UTF-16 content with BOM
    • KTOR-9661 HttpCache: InvalidCacheStateException is thrown on 304 when no cached entry matches varyKeys
    • KTOR-9673 Caching always missing with Content Negotiation
    • KTOR-9596 HttpTimeout: HttpRequestTimeoutException on any request with runTest and request timeout defined since 3.5.0
    • KTOR-9632 HttpRequestBuilder.timeout call removes capabilities set in DefaultRequest
    • KTOR-9252 ContentEncoding: Incomplete gzip response causes client/server to hang indefinitely
    • KTOR-9670 Compression: The plugin ignores Accept-Encoding q=0 and most-specific matching
    • KTOR-9614 Zstd Compression: "Destination buffer is too small" exception for a particular sequence of bytes
    • KTOR-9636 CORS plugin drops OPTIONS preflight requests when allowSameOrigin is on
    • KTOR-9659 CORS is skipped when the Origin header contains an IPv6 address
    • KTOR-9574 ContentNegotiation: The charset of Accept-Charset header is used for response deserialization
    • KTOR-9606 KotlinxSerializationConverter: it fails to deserialize an empty channel closed with a delay
    • KTOR-9617 OutputStreamContent and WriterContent can exhaust Dispatchers.IO
    • KTOR-9629 RawSourceChannel: coroutine cancellation is not propagated to the RawSource
    • KTOR-8870 File.readChannel: do not close a non-opened file and ignore closing exceptions
    • KTOR-9460 Curl: Can't build shared library with Ktor 3.4.2
    • KTOR-9607 Crash on Android 7: NoSuchMethodError getInstanceStrong() since 3.5.0
    • KTOR-9595 CIO: The engine imports node:net statically and breaks wasmJsBrowser webpack build
    • KTOR-9615 Apache5: The underlying request is not aborted when coroutine Job is cancelled
    • KTOR-8443 RoutingBuilder.contentType does not use parameters when matching
    • KTOR-9425 SessionTransportTransformerEncrypt init block uses wrong IV size for AES-256 (regression of KTOR-661)
    • KTOR-9621 RateLimit plugin is bypassed when the nested authenticate block rejects the request
    • KTOR-8766 DI: The close method is called twice on cleanup of AutoCloseable
    • KTOR-8695 GMTDate() being offset by 369 years on Windows
    • KTOR-9589 Duplicate PROPERTY_SETTER target in @InternalAPI annotation

    ... (truncated)

    Changelog

    Sourced from io.ktor:ktor-client-logging's changelog.

    3.5.1

    Published 25 June 2026

    Improvements

    • KTOR-9492 OpenAPI plugin: handle requireXxx functions in compiler plugin
    • KTOR-9569 Add more details for KDoc of BearerAuthConfig.sendWithoutRequest
    • KTOR-9571 Add KDoc for BearerAuthConfig.realm
    • KTOR-9570 Add Kdoc for BearerAuthConfig.refreshTokens

    Bugfixes

    • KTOR-9646 Fix Kotlin 2.4.0 compiler plugin breaking changes
    • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
    • KTOR-9591 OpenAPI: @JsonClassDiscriminator on sealed types is ignored when generating OpenAPI discriminator schema
    • KTOR-9597 OpenAPI: Schema name collisions because@SerialName of sealed subtypes is used as components.schemas key
    • KTOR-9601 OpenAPI: nullable @​JvmInline value class loses nullability in generated schema
    • KTOR-9620 Some issues related to digest authentication in 3.5.0
    • KTOR-9623 Digest Auth: server must respond with one WWW-Authenticate header for each supported algorithm
    • KTOR-9624 Digest Auth: "Unsupported charset in digest authentication header" for a charset name in lowercase
    • KTOR-9630 Digest Auth: The plugin sends incorrect nonce when server responds with multiple WWW-Authenticate headers
    • KTOR-9565 WebRTC: Peer stops receiving messages after a while on iOS
    • KTOR-9610 WebRtcPeerConnection::close causes ClosedReceiveChannelException for other peer
    • KTOR-9633 WebRTC statistics fetching race condition
    • KTOR-9603 Structured concurrency is violated in WebRTC tests
    • KTOR-9671 Darwin: Semicolons in URL path are sanitized
    • KTOR-9639 Darwin: WebSocket client crashes the process when a PONG arrives after the session is closed
    • KTOR-9575 Darwin: findCharset("UTF-16") maps to NSUTF16LittleEndianStringEncoding causing decoding failure for UTF-16 content with BOM
    • KTOR-9661 HttpCache: InvalidCacheStateException is thrown on 304 when no cached entry matches varyKeys
    • KTOR-9673 Caching always missing with Content Negotiation
    • KTOR-9596 HttpTimeout: HttpRequestTimeoutException on any request with runTest and request timeout defined since 3.5.0
    • KTOR-9632 HttpRequestBuilder.timeout call removes capabilities set in DefaultRequest
    • KTOR-9252 ContentEncoding: Incomplete gzip response causes client/server to hang indefinitely
    • KTOR-9670 Compression: The plugin ignores Accept-Encoding q=0 and most-specific matching
    • KTOR-9614 Zstd Compression: "Destination buffer is too small" exception for a particular sequence of bytes
    • KTOR-9636 CORS plugin drops OPTIONS preflight requests when allowSameOrigin is on
    • KTOR-9659 CORS is skipped when the Origin header contains an IPv6 address
    • KTOR-9574 ContentNegotiation: The charset of Accept-Charset header is used for response deserialization
    • KTOR-9606 KotlinxSerializationConverter: it fails to deserialize an empty channel closed with a delay
    • KTOR-9617 OutputStreamContent and WriterContent can exhaust Dispatchers.IO
    • KTOR-9629 RawSourceChannel: coroutine cancellation is not propagated ... _Description has been truncated_ Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ae38db6..9e864e8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,7 +17,7 @@ collections-immutable = "0.5.0" coroutines = "1.11.0" kotest = "6.2.1" kotlinx-io = "0.9.1" -ktor = "3.4.3" +ktor = "3.5.1" logging = "8.0.4" mockk = "1.14.11" serialization = "1.11.0" From 4531a4c846ef54a5c9352888b4ab6a6541d33d9d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Jul 2026 22:37:39 +0200 Subject: [PATCH 355/449] chore(deps): bump dev.detekt:detekt-gradle-plugin from 2.0.0-alpha.3 to 2.0.0-alpha.5 (#849) Bumps [dev.detekt:detekt-gradle-plugin](https://github.com/detekt/detekt) from 2.0.0-alpha.3 to 2.0.0-alpha.5.
      Release notes

      Sourced from dev.detekt:detekt-gradle-plugin's releases.

      v2.0.0-alpha.5

      2.0.0-alpha.5 - 2026-06-17

      This is a hotfix release for 2.0.0-alpha.4 where we fixed 2 bugs that were preventing detekt from running correctly.

      Changelog

      • fix: stop leaking ktlint-repackage as a transitive dependency - #9391
      • fix: exclude gradle-public-api from published POM - #9408
      • Stop accepting unsupported API or language versions passed to CLI - #9410

      v2.0.0-alpha.4

      2.0.0-alpha.4 - 2026-06-13

      This is an alpha release of Detekt 2.0.0. Is built against Kotlin 2.4.0, Gradle 9.5.1, AGP 9.2.1 and is tested against JDK 25.

      Notable Changes

      • Support Kotlin 2.4.0 - #9218
      • Add new rule LeakingSessionBoundType - #9242
      • Apply the indent_style configuration to all the ktlint wrapper rules (previsouly this was applied only to the Indentation rule) - #9315

      Breaking Changes

      • Expose Glob-to-Regex functions in psi-utils - #9340
      • Make Detektion immutable - #8583

      Changelog

      • fix: prevent ArrayPrimitive crash on unresolved types - #9364
      • Update ktlint documentation links - #9354
      • Fix PrintStackTrace nested call detection - #9352
      • Fix unused import false positive for companion object member extension properties - #9349
      • Accept punctuation after a link - #9341
      • Fixes UnnecessaryFullyQualifiedName false positive with explicit import shadowing - #9329
      • docs: fix KMP type resolution task names - #9316
      • Stop publishing ktlint-repackage - #9314
      • Stop exposing ktlint's StandardRule to detekt-rules-ktlint-wrapper consumers - #9313
      • Include ktlint-repackage in ktlint-wrapper JAR - #9312
      • Declare DGP compatibility with Gradle's configuration cache - #9311
      • Forward languageVersionSettings to KotlinAnalysisApiEngine.compile - #9305
      • docs: add ForbiddenImport configuration example - #9302
      • Fix UnnecessaryFullyQualifiedName clashes with kotlin package name - #9289
      • Remove excludeImportStatements from Deprecation rule - #9287
      • Improve repackaging of Analysis API dependencies - #9285
      • MaxChainedCallsOnSameLine - Fix AA type leaks - #9261
      • SuspendFunSwallowedCancellation - Fix AA type leaks - #9256
      • feat: Add exhaustive option to OutdatedDocumentation rule, also improve error messages - #9255
      • Excluding generated sources by default - #9251

      ... (truncated)

      Commits
      • 9c6ced6 Stop accepting unsupported API or language versions passed to CLI (#9410)
      • 44526f4 fix: exclude gradle-public-api from published POM (#9408)
      • 6bad2e8 fix: stop leaking ktlint-repackage as a transitive dependency (#9391)
      • fd5683e Update dependency path-to-regexp to v0.2.5 (#9397)
      • 7865b27 fix: resolve npm security vulnerabilities in website and bots dependencies (#...
      • bb582d5 Unbreak snapshot publishing (#9394)
      • 69866dd Update caniuse browser definitions (#9393)
      • bfd3b5d Add Docs for 2.0.0-alpha.4 (#9388)
      • 60300b2 Switch to vanniktech/gradle-maven-publish-plugin for publishing (#9357)
      • 63e0777 Update gradle/actions digest to 3f131e8 (#9386)
      • Additional commits viewable in compare view

      --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: devcrocod --- config/detekt/detekt.yml | 1 - gradle/libs.versions.toml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml index 98fce0e..a82b404 100644 --- a/config/detekt/detekt.yml +++ b/config/detekt/detekt.yml @@ -416,7 +416,6 @@ potential-bugs: Deprecation: active: false aliases: ['DEPRECATION'] - excludeImportStatements: false DontDowncastCollectionTypes: active: false DoubleMutabilityForCollection: diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9e864e8..592fa25 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ # plugins version atomicfu = "0.33.0" binaryCompatibilityValidatorPlugin = "0.18.1" -detekt = "2.0.0-alpha.3" +detekt = "2.0.0-alpha.5" dokka = "2.2.0" kotlin = "2.3.21" kover = "0.9.8" From 420b64d1bb2d35e86adf544499e6748ee314ec72 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Jul 2026 13:16:58 +0200 Subject: [PATCH 356/449] chore(deps): bump the kotlin group across 1 directory with 2 updates (#834) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the kotlin group with 2 updates in the / directory: [org.jetbrains.kotlin:kotlin-gradle-plugin](https://github.com/JetBrains/kotlin) and [org.jetbrains.kotlin:kotlin-serialization](https://github.com/JetBrains/kotlin). Updates `org.jetbrains.kotlin:kotlin-gradle-plugin` from 2.3.21 to 2.4.0
      Release notes

      Sourced from org.jetbrains.kotlin:kotlin-gradle-plugin's releases.

      Kotlin 2.4.0

      Changelog

      Analysis API

      • KT-83867 OVERLOAD_RESOLUTION_AMBIGUITY false positive with assertEquals in IJ repo
      • KT-83723 [Analysis API] Enable experimental KDoc resolver by default
      • KT-83388 Analysis API: properly support KMP in KotlinPackageProvider
      • KT-65683 Analysis API: Dangling file session creation causes a computeIfAbsent contract violation

      Analysis API. Code Compilation

      • KT-78946 Evaluation of variable with local class in type parameter leads to InventNamesForLocalClasses exception

      Analysis API. FIR

      • KT-68260 K2 AA: InvalidFirElementTypeException “For CALLABLE_REFERENCE_EXPRESSION with text ::lam1, unexpected element of type: no element found” with illegal callable reference call
      • KT-70896 AA: False positive deprecation warning with override of built-in method in JDK mapped class
      • KT-84711 K2 IDE sometimes loses FIR plugin-generated declarations after file changes
      • KT-84625 Analysis API: collectDesignationPath fails for nested classes inside plugin-generated top-level classes
      • KT-84596 Improve K2 Jooq completion performance
      • KT-84525 KaValueParameterSymbol#getHasSynthesizedName returns false for FirDeclarationOrigin.SubstitutionOverride.DeclarationSite
      • KT-83546 Kotlin analysis reach ClsCustomNavigationPolicy
      • KT-84259 Move CommonDefaultImportsProvider to the frontend independent module
      • KT-82945 Analysis API: KotlinIllegalArgumentExceptionWithAttachments: Expected FirResolvedTypeRef with ConeKotlinType but was FirUserTypeRefImpl
      • KT-71135 AA: exception from sealed inheritors checker when analyzeCopy

      Analysis API. Infrastructure

      Fixes

      • KT-83905 Analysis API: Improve UX with test data
      • KT-84913 Extract compiler classes used by the PSI & Analysis API to a separate module
      • KT-64986 Analysis API: Implement Analysis API tests for different KMP Platforms
      • KT-84776 The test data manager misses the redundancy check in the update mode
      • KT-84962 The test data manager misses -ea flag
      • KT-84388 Preserve the EOF status in the test data manager to avoid extra changes
      • KT-84362 Analysis API tests produce many warnings due to "not yet loaded registry"
      • KT-84279 Test Data Manager fails on a clean build
      • KT-83913 Exclude compiler-based Analysis API tests from Git tracking
      • KT-80379 Extract per-module test generators for AA tests
      • KT-84120 Move CLI modules out of kotlin-compiler-fe10-for-ide
      • KT-83200 Track external dependencies of the Analysis API modules

      Analysis API. Light Classes

      • KT-82434 Light classes should prefer enum entries to properties
      • KT-84200 SLC: return type is not boxed for delegated methods with generic original method
      • KT-72451 "CCE: class PsiPrimitiveType cannot be cast to class PsiClassType" with same-named enum class and typealias

      ... (truncated)

      Changelog

      Sourced from org.jetbrains.kotlin:kotlin-gradle-plugin's changelog.

      2.4.0

      Analysis API

      • KT-83867 OVERLOAD_RESOLUTION_AMBIGUITY false positive with assertEquals in IJ repo
      • KT-83723 [Analysis API] Enable experimental KDoc resolver by default
      • KT-83388 Analysis API: properly support KMP in KotlinPackageProvider
      • KT-65683 Analysis API: Dangling file session creation causes a computeIfAbsent contract violation

      Analysis API. Code Compilation

      • KT-78946 Evaluation of variable with local class in type parameter leads to InventNamesForLocalClasses exception

      Analysis API. FIR

      • KT-68260 K2 AA: InvalidFirElementTypeException “For CALLABLE_REFERENCE_EXPRESSION with text ::lam1, unexpected element of type: no element found” with illegal callable reference call
      • KT-70896 AA: False positive deprecation warning with override of built-in method in JDK mapped class
      • KT-84711 K2 IDE sometimes loses FIR plugin-generated declarations after file changes
      • KT-84625 Analysis API: collectDesignationPath fails for nested classes inside plugin-generated top-level classes
      • KT-84596 Improve K2 Jooq completion performance
      • KT-84525 KaValueParameterSymbol#getHasSynthesizedName returns false for FirDeclarationOrigin.SubstitutionOverride.DeclarationSite
      • KT-83546 Kotlin analysis reach ClsCustomNavigationPolicy
      • KT-84259 Move CommonDefaultImportsProvider to the frontend independent module
      • KT-82945 Analysis API: KotlinIllegalArgumentExceptionWithAttachments: Expected FirResolvedTypeRef with ConeKotlinType but was FirUserTypeRefImpl
      • KT-71135 AA: exception from sealed inheritors checker when analyzeCopy

      Analysis API. Infrastructure

      Fixes

      • KT-83905 Analysis API: Improve UX with test data
      • KT-84913 Extract compiler classes used by the PSI & Analysis API to a separate module
      • KT-64986 Analysis API: Implement Analysis API tests for different KMP Platforms
      • KT-84776 The test data manager misses the redundancy check in the update mode
      • KT-84962 The test data manager misses -ea flag
      • KT-84388 Preserve the EOF status in the test data manager to avoid extra changes
      • KT-84362 Analysis API tests produce many warnings due to "not yet loaded registry"
      • KT-84279 Test Data Manager fails on a clean build
      • KT-83913 Exclude compiler-based Analysis API tests from Git tracking
      • KT-80379 Extract per-module test generators for AA tests
      • KT-84120 Move CLI modules out of kotlin-compiler-fe10-for-ide
      • KT-83200 Track external dependencies of the Analysis API modules

      Analysis API. Light Classes

      • KT-82434 Light classes should prefer enum entries to properties
      • KT-84200 SLC: return type is not boxed for delegated methods with generic original method
      • KT-72451 "CCE: class PsiPrimitiveType cannot be cast to class PsiClassType" with same-named enum class and typealias

      Analysis API. PSI

      ... (truncated)

      Commits
      • add726c Add ChangeLog for 2.4.0-RC2
      • 69a4a81 [Wasm] Add binaryen per-file arguments
      • 8907c63 [KGP] Suppress duplicate compiler warning output when --warning-mode=all is a...
      • f9bef12 [KGP] Add failing test for duplicate compiler warning output via Problems API
      • 7fe49c2 KT-86268: Use 2.4.0 toolchain for abiValidation with older CVs
      • 13d25e2 KT-86268: Regression test added
      • f7d1a76 [JKlib] Fix JAR deserializer configuration.
      • 5cec663 [JKlib] Wire up JvmBuiltInClassDescriptorFactory
      • ef14611 Make the default stability of non-final classes Unknown instead of Stable
      • d76473a [Gradle] Fix GeneralNativeIT.shouldAllowToOverrideDownloadUrl test failures
      • Additional commits viewable in compare view

      Updates `org.jetbrains.kotlin:kotlin-serialization` from 2.3.21 to 2.4.0
      Release notes

      Sourced from org.jetbrains.kotlin:kotlin-serialization's releases.

      Kotlin 2.4.0

      Changelog

      Analysis API

      • KT-83867 OVERLOAD_RESOLUTION_AMBIGUITY false positive with assertEquals in IJ repo
      • KT-83723 [Analysis API] Enable experimental KDoc resolver by default
      • KT-83388 Analysis API: properly support KMP in KotlinPackageProvider
      • KT-65683 Analysis API: Dangling file session creation causes a computeIfAbsent contract violation

      Analysis API. Code Compilation

      • KT-78946 Evaluation of variable with local class in type parameter leads to InventNamesForLocalClasses exception

      Analysis API. FIR

      • KT-68260 K2 AA: InvalidFirElementTypeException “For CALLABLE_REFERENCE_EXPRESSION with text ::lam1, unexpected element of type: no element found” with illegal callable reference call
      • KT-70896 AA: False positive deprecation warning with override of built-in method in JDK mapped class
      • KT-84711 K2 IDE sometimes loses FIR plugin-generated declarations after file changes
      • KT-84625 Analysis API: collectDesignationPath fails for nested classes inside plugin-generated top-level classes
      • KT-84596 Improve K2 Jooq completion performance
      • KT-84525 KaValueParameterSymbol#getHasSynthesizedName returns false for FirDeclarationOrigin.SubstitutionOverride.DeclarationSite
      • KT-83546 Kotlin analysis reach ClsCustomNavigationPolicy
      • KT-84259 Move CommonDefaultImportsProvider to the frontend independent module
      • KT-82945 Analysis API: KotlinIllegalArgumentExceptionWithAttachments: Expected FirResolvedTypeRef with ConeKotlinType but was FirUserTypeRefImpl
      • KT-71135 AA: exception from sealed inheritors checker when analyzeCopy

      Analysis API. Infrastructure

      Fixes

      • KT-83905 Analysis API: Improve UX with test data
      • KT-84913 Extract compiler classes used by the PSI & Analysis API to a separate module
      • KT-64986 Analysis API: Implement Analysis API tests for different KMP Platforms
      • KT-84776 The test data manager misses the redundancy check in the update mode
      • KT-84962 The test data manager misses -ea flag
      • KT-84388 Preserve the EOF status in the test data manager to avoid extra changes
      • KT-84362 Analysis API tests produce many warnings due to "not yet loaded registry"
      • KT-84279 Test Data Manager fails on a clean build
      • KT-83913 Exclude compiler-based Analysis API tests from Git tracking
      • KT-80379 Extract per-module test generators for AA tests
      • KT-84120 Move CLI modules out of kotlin-compiler-fe10-for-ide
      • KT-83200 Track external dependencies of the Analysis API modules

      Analysis API. Light Classes

      • KT-82434 Light classes should prefer enum entries to properties
      • KT-84200 SLC: return type is not boxed for delegated methods with generic original method
      • KT-72451 "CCE: class PsiPrimitiveType cannot be cast to class PsiClassType" with same-named enum class and typealias

      ... (truncated)

      Changelog

      Sourced from org.jetbrains.kotlin:kotlin-serialization's changelog.

      2.4.0

      Analysis API

      • KT-83867 OVERLOAD_RESOLUTION_AMBIGUITY false positive with assertEquals in IJ repo
      • KT-83723 [Analysis API] Enable experimental KDoc resolver by default
      • KT-83388 Analysis API: properly support KMP in KotlinPackageProvider
      • KT-65683 Analysis API: Dangling file session creation causes a computeIfAbsent contract violation

      Analysis API. Code Compilation

      • KT-78946 Evaluation of variable with local class in type parameter leads to InventNamesForLocalClasses exception

      Analysis API. FIR

      • KT-68260 K2 AA: InvalidFirElementTypeException “For CALLABLE_REFERENCE_EXPRESSION with text ::lam1, unexpected element of type: no element found” with illegal callable reference call
      • KT-70896 AA: False positive deprecation warning with override of built-in method in JDK mapped class
      • KT-84711 K2 IDE sometimes loses FIR plugin-generated declarations after file changes
      • KT-84625 Analysis API: collectDesignationPath fails for nested classes inside plugin-generated top-level classes
      • KT-84596 Improve K2 Jooq completion performance
      • KT-84525 KaValueParameterSymbol#getHasSynthesizedName returns false for FirDeclarationOrigin.SubstitutionOverride.DeclarationSite
      • KT-83546 Kotlin analysis reach ClsCustomNavigationPolicy
      • KT-84259 Move CommonDefaultImportsProvider to the frontend independent module
      • KT-82945 Analysis API: KotlinIllegalArgumentExceptionWithAttachments: Expected FirResolvedTypeRef with ConeKotlinType but was FirUserTypeRefImpl
      • KT-71135 AA: exception from sealed inheritors checker when analyzeCopy

      Analysis API. Infrastructure

      Fixes

      • KT-83905 Analysis API: Improve UX with test data
      • KT-84913 Extract compiler classes used by the PSI & Analysis API to a separate module
      • KT-64986 Analysis API: Implement Analysis API tests for different KMP Platforms
      • KT-84776 The test data manager misses the redundancy check in the update mode
      • KT-84962 The test data manager misses -ea flag
      • KT-84388 Preserve the EOF status in the test data manager to avoid extra changes
      • KT-84362 Analysis API tests produce many warnings due to "not yet loaded registry"
      • KT-84279 Test Data Manager fails on a clean build
      • KT-83913 Exclude compiler-based Analysis API tests from Git tracking
      • KT-80379 Extract per-module test generators for AA tests
      • KT-84120 Move CLI modules out of kotlin-compiler-fe10-for-ide
      • KT-83200 Track external dependencies of the Analysis API modules

      Analysis API. Light Classes

      • KT-82434 Light classes should prefer enum entries to properties
      • KT-84200 SLC: return type is not boxed for delegated methods with generic original method
      • KT-72451 "CCE: class PsiPrimitiveType cannot be cast to class PsiClassType" with same-named enum class and typealias

      Analysis API. PSI

      ... (truncated)

      Commits
      • add726c Add ChangeLog for 2.4.0-RC2
      • 69a4a81 [Wasm] Add binaryen per-file arguments
      • 8907c63 [KGP] Suppress duplicate compiler warning output when --warning-mode=all is a...
      • f9bef12 [KGP] Add failing test for duplicate compiler warning output via Problems API
      • 7fe49c2 KT-86268: Use 2.4.0 toolchain for abiValidation with older CVs
      • 13d25e2 KT-86268: Regression test added
      • f7d1a76 [JKlib] Fix JAR deserializer configuration.
      • 5cec663 [JKlib] Wire up JvmBuiltInClassDescriptorFactory
      • ef14611 Make the default stability of non-final classes Unknown instead of Stable
      • d76473a [Gradle] Fix GeneralNativeIT.shouldAllowToOverrideDownloadUrl test failures
      • Additional commits viewable in compare view

      Updates `org.jetbrains.kotlin:kotlin-serialization` from 2.3.21 to 2.4.0
      Release notes

      Sourced from org.jetbrains.kotlin:kotlin-serialization's releases.

      Kotlin 2.4.0

      Changelog

      Analysis API

      • KT-83867 OVERLOAD_RESOLUTION_AMBIGUITY false positive with assertEquals in IJ repo
      • KT-83723 [Analysis API] Enable experimental KDoc resolver by default
      • KT-83388 Analysis API: properly support KMP in KotlinPackageProvider
      • KT-65683 Analysis API: Dangling file session creation causes a computeIfAbsent contract violation

      Analysis API. Code Compilation

      • KT-78946 Evaluation of variable with local class in type parameter leads to InventNamesForLocalClasses exception

      Analysis API. FIR

      • KT-68260 K2 AA: InvalidFirElementTypeException “For CALLABLE_REFERENCE_EXPRESSION with text ::lam1, unexpected element of type: no element found” with illegal callable reference call
      • KT-70896 AA: False positive deprecation warning with override of built-in method in JDK mapped class
      • KT-84711 K2 IDE sometimes loses FIR plugin-generated declarations after file changes
      • KT-84625 Analysis API: collectDesignationPath fails for nested classes inside plugin-generated top-level classes
      • KT-84596 Improve K2 Jooq completion performance
      • KT-84525 KaValueParameterSymbol#getHasSynthesizedName returns false for FirDeclarationOrigin.SubstitutionOverride.DeclarationSite
      • KT-83546 Kotlin analysis reach ClsCustomNavigationPolicy
      • KT-84259 Move CommonDefaultImportsProvider to the frontend independent module
      • KT-82945 Analysis API: KotlinIllegalArgumentExceptionWithAttachments: Expected FirResolvedTypeRef with ConeKotlinType but was FirUserTypeRefImpl
      • KT-71135 AA: exception from sealed inheritors checker when analyzeCopy

      Analysis API. Infrastructure

      Fixes

      • KT-83905 Analysis API: Improve UX with test data
      • KT-84913 Extract compiler classes used by the PSI & Analysis API to a separate module
      • KT-64986 Analysis API: Implement Analysis API tests for different KMP Platforms
      • KT-84776 The test data manager misses the redundancy check in the update mode
      • KT-84962 The test data manager misses -ea flag
      • KT-84388 Preserve the EOF status in the test data manager to avoid extra changes
      • KT-84362 Analysis API tests produce many warnings due to "not yet loaded registry"
      • KT-84279 Test Data Manager fails on a clean build
      • KT-83913 Exclude compiler-based Analysis API tests from Git tracking
      • KT-80379 Extract per-module test generators for AA tests
      • KT-84120 Move CLI modules out of kotlin-compiler-fe10-for-ide
      • KT-83200 Track external dependencies of the Analysis API modules

      Analysis API. Light Classes

      • KT-82434 Light classes should prefer enum entries to properties
      • KT-84200 SLC: return type is not boxed for delegated methods with generic original method
      • KT-72451 "CCE: class PsiPrimitiveType cannot be cast to class PsiClassType" with same-named enum class and typealias

      ... (truncated)

      Changelog

      Sourced from org.jetbrains.kotlin:kotlin-serialization's changelog.

      2.4.0

      Analysis API

      • KT-83867 OVERLOAD_RESOLUTION_AMBIGUITY false positive with assertEquals in IJ repo
      • KT-83723 [Analysis API] Enable experimental KDoc resolver by default
      • KT-83388 Analysis API: properly support KMP in KotlinPackageProvider
      • KT-65683 Analysis API: Dangling file session creation causes a computeIfAbsent contract violation

      Analysis API. Code Compilation

      • KT-78946 Evaluation of variable with local class in type parameter leads to InventNamesForLocalClasses exception

      Analysis API. FIR

      • KT-68260 K2 AA: InvalidFirElementTypeException “For CALLABLE_REFERENCE_EXPRESSION with text ::lam1, unexpected element of type: no element found” with illegal callable reference call
      • KT-70896 AA: False positive deprecation warning with override of built-in method in JDK mapped class
      • KT-84711 K2 IDE sometimes loses FIR plugin-generated declarations after file changes
      • KT-84625 Analysis API: collectDesignationPath fails for nested classes inside plugin-generated top-level classes
      • KT-84596 Improve K2 Jooq completion performance
      • KT-84525 KaValueParameterSymbol#getHasSynthesizedName returns false for FirDeclarationOrigin.SubstitutionOverride.DeclarationSite
      • KT-83546 Kotlin analysis reach ClsCustomNavigationPolicy
      • KT-84259 Move CommonDefaultImportsProvider to the frontend independent module
      • KT-82945 Analysis API: KotlinIllegalArgumentExceptionWithAttachments: Expected FirResolvedTypeRef with ConeKotlinType but was FirUserTypeRefImpl
      • KT-71135 AA: exception from sealed inheritors checker when analyzeCopy

      Analysis API. Infrastructure

      Fixes

      • KT-83905 Analysis API: Improve UX with test data
      • KT-84913 Extract compiler classes used by the PSI & Analysis API to a separate module
      • KT-64986 Analysis API: Implement Analysis API tests for different KMP Platforms
      • KT-84776 The test data manager misses the redundancy check in the update mode
      • KT-84962 The test data manager misses -ea flag
      • KT-84388 Preserve the EOF status in the test data manager to avoid extra changes
      • KT-84362 Analysis API tests produce many warnings due to "not yet loaded registry"
      • KT-84279 Test Data Manager fails on a clean build
      • KT-83913 Exclude compiler-based Analysis API tests from Git tracking
      • KT-80379 Extract per-module test generators for AA tests
      • KT-84120 Move CLI modules out of kotlin-compiler-fe10-for-ide
      • KT-83200 Track external dependencies of the Analysis API modules

      Analysis API. Light Classes

      • KT-82434 Light classes should prefer enum entries to properties
      • KT-84200 SLC: return type is not boxed for delegated methods with generic original method
      • KT-72451 "CCE: class PsiPrimitiveType cannot be cast to class PsiClassType" with same-named enum class and typealias

      Analysis API. PSI

      ... (truncated)

      Commits
      • add726c Add ChangeLog for 2.4.0-RC2
      • 69a4a81 [Wasm] Add binaryen per-file arguments
      • 8907c63 [KGP] Suppress duplicate compiler warning output when --warning-mode=all is a...
      • f9bef12 [KGP] Add failing test for duplicate compiler warning output via Problems API
      • 7fe49c2 KT-86268: Use 2.4.0 toolchain for abiValidation with older CVs
      • 13d25e2 KT-86268: Regression test added
      • f7d1a76 [JKlib] Fix JAR deserializer configuration.
      • 5cec663 [JKlib] Wire up JvmBuiltInClassDescriptorFactory
      • ef14611 Make the default stability of non-final classes Unknown instead of Stable
      • d76473a [Gradle] Fix GeneralNativeIT.shouldAllowToOverrideDownloadUrl test failures
      • Additional commits viewable in compare view

      --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: devcrocod --- README.md | 2 +- gradle/libs.versions.toml | 2 +- kotlin-sdk-core/api/kotlin-sdk-core.api | 32 ++++++++++++------------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 58a2cad..aefadcf 100644 --- a/README.md +++ b/README.md @@ -729,7 +729,7 @@ val client = Client( ) client.setRequestHandler(Method.Defined.SamplingCreateMessage) { request, _ -> - val content = request.messages.lastOrNull()?.content + val content = request.messages.lastOrNull()?.content?.firstOrNull() val prompt = if (content is TextContent) content.text else "your topic" CreateMessageResult( model = "gpt-4o-mini", diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 592fa25..e7f4683 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ atomicfu = "0.33.0" binaryCompatibilityValidatorPlugin = "0.18.1" detekt = "2.0.0-alpha.5" dokka = "2.2.0" -kotlin = "2.3.21" +kotlin = "2.4.0" kover = "0.9.8" ktlint = "14.2.0" knit = "0.5.1" diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index cdee342..1935e9a 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -415,7 +415,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CallToolRequestBuild public final fun arguments (Lkotlin/jvm/functions/Function1;)V public final fun arguments (Lkotlinx/serialization/json/JsonObject;)V public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequest; - public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; + public synthetic fun build$io_modelcontextprotocol_kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; public final fun getName ()Ljava/lang/String; public final fun setName (Ljava/lang/String;)V } @@ -954,7 +954,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CompleteRequestBuild public fun ()V public final fun argument (Ljava/lang/String;Ljava/lang/String;)V public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequest; - public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; + public synthetic fun build$io_modelcontextprotocol_kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; public final fun context (Ljava/util/Map;)V public final fun context (Lkotlin/jvm/functions/Function1;)V public final fun ref (Lio/modelcontextprotocol/kotlin/sdk/types/Reference;)V @@ -1188,7 +1188,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest public final class io/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/RequestBuilder { public fun ()V public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest; - public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; + public synthetic fun build$io_modelcontextprotocol_kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; public final fun getContext ()Lio/modelcontextprotocol/kotlin/sdk/types/IncludeContext; public final fun getMaxTokens ()Ljava/lang/Integer; public final fun getStopSequences ()Ljava/util/List; @@ -1455,7 +1455,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequest$Compan public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/RequestBuilder { public fun ()V public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequest; - public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; + public synthetic fun build$io_modelcontextprotocol_kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; public final fun getMessage ()Ljava/lang/String; public final fun requestedSchema (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;)V public final fun requestedSchema (Lkotlin/jvm/functions/Function1;)V @@ -1833,7 +1833,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest$Com public final class io/modelcontextprotocol/kotlin/sdk/types/GetPromptRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/RequestBuilder { public fun ()V public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest; - public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; + public synthetic fun build$io_modelcontextprotocol_kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; public final fun getArguments ()Ljava/util/Map; public final fun getName ()Ljava/lang/String; public final fun setArguments (Ljava/util/Map;)V @@ -2278,7 +2278,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/InitializeRequest$Co public final class io/modelcontextprotocol/kotlin/sdk/types/InitializeRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/RequestBuilder { public fun ()V public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequest; - public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; + public synthetic fun build$io_modelcontextprotocol_kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; public final fun capabilities (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;)V public final fun capabilities (Lkotlin/jvm/functions/Function1;)V public final fun getProtocolVersion ()Ljava/lang/String; @@ -2668,7 +2668,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest$C public final class io/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestBuilder { public fun ()V public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest; - public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; + public synthetic fun build$io_modelcontextprotocol_kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; } public final class io/modelcontextprotocol/kotlin/sdk/types/ListPromptsResult : io/modelcontextprotocol/kotlin/sdk/types/PaginatedResult, io/modelcontextprotocol/kotlin/sdk/types/ServerResult { @@ -2739,7 +2739,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplate public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestBuilder { public fun ()V public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesRequest; - public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; + public synthetic fun build$io_modelcontextprotocol_kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; } public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesResult : io/modelcontextprotocol/kotlin/sdk/types/PaginatedResult, io/modelcontextprotocol/kotlin/sdk/types/ServerResult { @@ -2810,7 +2810,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestBuilder { public fun ()V public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest; - public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; + public synthetic fun build$io_modelcontextprotocol_kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; } public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourcesResult : io/modelcontextprotocol/kotlin/sdk/types/PaginatedResult, io/modelcontextprotocol/kotlin/sdk/types/ServerResult { @@ -2880,7 +2880,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest$Com public final class io/modelcontextprotocol/kotlin/sdk/types/ListRootsRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/RequestBuilder { public fun ()V public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest; - public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; + public synthetic fun build$io_modelcontextprotocol_kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; } public final class io/modelcontextprotocol/kotlin/sdk/types/ListRootsResult : io/modelcontextprotocol/kotlin/sdk/types/ClientResult { @@ -3014,7 +3014,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest$Com public final class io/modelcontextprotocol/kotlin/sdk/types/ListToolsRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequestBuilder { public fun ()V public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest; - public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; + public synthetic fun build$io_modelcontextprotocol_kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; } public final class io/modelcontextprotocol/kotlin/sdk/types/ListToolsResult : io/modelcontextprotocol/kotlin/sdk/types/PaginatedResult, io/modelcontextprotocol/kotlin/sdk/types/ServerResult { @@ -3440,7 +3440,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/PingRequest$Companio public final class io/modelcontextprotocol/kotlin/sdk/types/PingRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/RequestBuilder { public fun ()V public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/PingRequest; - public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; + public synthetic fun build$io_modelcontextprotocol_kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; } public final class io/modelcontextprotocol/kotlin/sdk/types/PingRequest_dslKt { @@ -3791,7 +3791,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest$ public final class io/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/RequestBuilder { public fun ()V public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest; - public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; + public synthetic fun build$io_modelcontextprotocol_kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; public final fun getUri ()Ljava/lang/String; public final fun setUri (Ljava/lang/String;)V } @@ -4754,7 +4754,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/SetLevelRequest$Comp public final class io/modelcontextprotocol/kotlin/sdk/types/SetLevelRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/RequestBuilder { public fun ()V public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequest; - public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; + public synthetic fun build$io_modelcontextprotocol_kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; public final fun getLoggingLevel ()Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; public final fun setLoggingLevel (Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel;)V } @@ -4920,7 +4920,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest$Com public final class io/modelcontextprotocol/kotlin/sdk/types/SubscribeRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/RequestBuilder { public fun ()V public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest; - public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; + public synthetic fun build$io_modelcontextprotocol_kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; public final fun getUri ()Ljava/lang/String; public final fun setUri (Ljava/lang/String;)V } @@ -5688,7 +5688,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest$C public final class io/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequestBuilder : io/modelcontextprotocol/kotlin/sdk/types/RequestBuilder { public fun ()V public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest; - public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; + public synthetic fun build$io_modelcontextprotocol_kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request; public final fun getUri ()Ljava/lang/String; public final fun setUri (Ljava/lang/String;)V } From 19091d35284316f69a0f7505ddb27e5cabf57186 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 12:13:30 +0200 Subject: [PATCH 357/449] chore(deps): bump org.jetbrains.kotlinx:kotlinx-collections-immutable from 0.5.0 to 0.5.1 in the kotlinx group (#873) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the kotlinx group with 1 update: [org.jetbrains.kotlinx:kotlinx-collections-immutable](https://github.com/Kotlin/kotlinx.collections.immutable). Updates `org.jetbrains.kotlinx:kotlinx-collections-immutable` from 0.5.0 to 0.5.1
      Release notes

      Sourced from org.jetbrains.kotlinx:kotlinx-collections-immutable's releases.

      v0.5.1

      • Fixed PersistentOrderedMapBuilder returning a previously built map from build() after updating an entry value through MutableMap.MutableEntry.setValue #253, #274
      • Fixed an assertion failure in PersistentOrderedSetBuilder.build() after interleaved build and remove/removeAll calls #251
      • Provided a JPMS module descriptor for the JVM artifact — module kotlinx.collections.immutable compiled into META-INF/versions/9, the artifact stays Java 8-compatible #268

      Full changelog: https://github.com/Kotlin/kotlinx.collections.immutable/compare/v0.5.0...v0.5.1

      Changelog

      Sourced from org.jetbrains.kotlinx:kotlinx-collections-immutable's changelog.

      0.5.1

      • Fixed PersistentOrderedMapBuilder returning a previously built map from build() after updating an entry value through MutableMap.MutableEntry.setValue #253, #274
      • Fixed an assertion failure in PersistentOrderedSetBuilder.build() after interleaved build and remove/removeAll calls #251
      • Provided a JPMS module descriptor for the JVM artifact — module kotlinx.collections.immutable compiled into META-INF/versions/9, the artifact stays Java 8-compatible #268
      Commits
      • ad741d1 Release v0.5.1
      • cb4cf67 Applied artifacts validator plugin (#271)
      • e9ca796 Restore the direct links write in the ordered map builder entry (#274)
      • 6822eed Fix the pull request exclusion in the VCS trigger branch filter (#276)
      • 8201f28 Surface pull request branches on CI for manually started builds (#273)
      • f3dec4f Fix ordered set builder cache after removeAll (#251)
      • 96bf012 Provide a JPMS module descriptor for the JVM artifact (#268)
      • 81382a1 Migrate ABI validation to the Kotlin Gradle plugin (#264)
      • 694d34b Invalidate ordered map builder after entry value updates (#253)
      • 1d6fda3 Fix the method-rename examples in the 0.5.0 changelog entry (#266)
      • See full diff in compare view

      [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.jetbrains.kotlinx:kotlinx-collections-immutable&package-manager=gradle&previous-version=0.5.0&new-version=0.5.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
      Dependabot commands and options
      You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
      Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e7f4683..0d9b5de 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,7 +13,7 @@ netty = "4.2.15.Final" # libraries version awaitility = "4.3.0" -collections-immutable = "0.5.0" +collections-immutable = "0.5.1" coroutines = "1.11.0" kotest = "6.2.1" kotlinx-io = "0.9.1" From ea3940ce477c9672b43447b9aedaa9fd98612df5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 12:39:14 +0200 Subject: [PATCH 358/449] chore(deps): bump io.modelcontextprotocol:kotlin-sdk-client from 0.13.0 to 0.14.0 in /samples/kotlin-mcp-client (#875) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [io.modelcontextprotocol:kotlin-sdk-client](https://github.com/modelcontextprotocol/kotlin-sdk) from 0.13.0 to 0.14.0.
      Commits
      • fc7916f release: 0.14.0 (#867)
      • 57e546d feat: SEP-1686 Tasks (#844)
      • 35da063 fix: bound request body size in SSE and Streamable HTTP transports (#839)
      • bbf77ba chore(deps): bump @​types/node from 26.0.0 to 26.0.1 in /integration-test/src/...
      • d2b1509 chore(deps): bump the other-dependencies group across 5 directories with 5 up...
      • 0221b8d fix(client): close stdio read sources during shutdown (#798)
      • 19db4b0 fix(client): gate completion/complete on the completions capability (#846)
      • 64cf29f chore(deps): bump gradle-wrapper from 9.6.0 to 9.6.1 (#856)
      • cd91652 fix: validate Origin against localhost by default in DNS rebinding protection...
      • 4103b87 chore(deps): bump org.jetbrains.kotlinx:kotlinx-io-core from 0.9.0 to 0.9.1 i...
      • Additional commits viewable in compare view

      Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-client/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index b5167d9..fab3459 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -2,7 +2,7 @@ anthropic = "2.45.0" kotlin = "2.4.0" ktor = "3.5.1" -mcp-kotlin = "0.13.0" +mcp-kotlin = "0.14.0" shadow = "9.4.3" slf4j = "2.0.18" From 04e6d56fd6c5df0dea86cea303b121f3dd3d7ef0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 13:28:48 +0200 Subject: [PATCH 359/449] chore(deps): bump mcp-kotlin from 0.13.0 to 0.14.0 in /samples/kotlin-mcp-server (#876) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `mcp-kotlin` from 0.13.0 to 0.14.0. Updates `io.modelcontextprotocol:kotlin-sdk-client` from 0.13.0 to 0.14.0
      Commits
      • fc7916f release: 0.14.0 (#867)
      • 57e546d feat: SEP-1686 Tasks (#844)
      • 35da063 fix: bound request body size in SSE and Streamable HTTP transports (#839)
      • bbf77ba chore(deps): bump @​types/node from 26.0.0 to 26.0.1 in /integration-test/src/...
      • d2b1509 chore(deps): bump the other-dependencies group across 5 directories with 5 up...
      • 0221b8d fix(client): close stdio read sources during shutdown (#798)
      • 19db4b0 fix(client): gate completion/complete on the completions capability (#846)
      • 64cf29f chore(deps): bump gradle-wrapper from 9.6.0 to 9.6.1 (#856)
      • cd91652 fix: validate Origin against localhost by default in DNS rebinding protection...
      • 4103b87 chore(deps): bump org.jetbrains.kotlinx:kotlinx-io-core from 0.9.0 to 0.9.1 i...
      • Additional commits viewable in compare view

      Updates `io.modelcontextprotocol:kotlin-sdk-server` from 0.13.0 to 0.14.0
      Commits
      • fc7916f release: 0.14.0 (#867)
      • 57e546d feat: SEP-1686 Tasks (#844)
      • 35da063 fix: bound request body size in SSE and Streamable HTTP transports (#839)
      • bbf77ba chore(deps): bump @​types/node from 26.0.0 to 26.0.1 in /integration-test/src/...
      • d2b1509 chore(deps): bump the other-dependencies group across 5 directories with 5 up...
      • 0221b8d fix(client): close stdio read sources during shutdown (#798)
      • 19db4b0 fix(client): gate completion/complete on the completions capability (#846)
      • 64cf29f chore(deps): bump gradle-wrapper from 9.6.0 to 9.6.1 (#856)
      • cd91652 fix: validate Origin against localhost by default in DNS rebinding protection...
      • 4103b87 chore(deps): bump org.jetbrains.kotlinx:kotlinx-io-core from 0.9.0 to 0.9.1 i...
      • Additional commits viewable in compare view

      Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-server/gradle/libs.versions.toml b/samples/kotlin-mcp-server/gradle/libs.versions.toml index 5a6217e..6c6ff89 100644 --- a/samples/kotlin-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-server/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] kotlin = "2.4.0" ktor = "3.5.1" -mcp-kotlin = "0.13.0" +mcp-kotlin = "0.14.0" slf4j = "2.0.18" shadow = "9.4.3" From 14c848db417b4c884990af2ce69d1a8bac6e40a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 13:29:15 +0200 Subject: [PATCH 360/449] chore(deps): bump mcp-kotlin from 0.13.0 to 0.14.0 in /samples/kotlinlang-mcp-server (#877) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `mcp-kotlin` from 0.13.0 to 0.14.0. Updates `io.modelcontextprotocol:kotlin-sdk-server` from 0.13.0 to 0.14.0
      Commits
      • fc7916f release: 0.14.0 (#867)
      • 57e546d feat: SEP-1686 Tasks (#844)
      • 35da063 fix: bound request body size in SSE and Streamable HTTP transports (#839)
      • bbf77ba chore(deps): bump @​types/node from 26.0.0 to 26.0.1 in /integration-test/src/...
      • d2b1509 chore(deps): bump the other-dependencies group across 5 directories with 5 up...
      • 0221b8d fix(client): close stdio read sources during shutdown (#798)
      • 19db4b0 fix(client): gate completion/complete on the completions capability (#846)
      • 64cf29f chore(deps): bump gradle-wrapper from 9.6.0 to 9.6.1 (#856)
      • cd91652 fix: validate Origin against localhost by default in DNS rebinding protection...
      • 4103b87 chore(deps): bump org.jetbrains.kotlinx:kotlinx-io-core from 0.9.0 to 0.9.1 i...
      • Additional commits viewable in compare view

      Updates `io.modelcontextprotocol:kotlin-sdk-client` from 0.13.0 to 0.14.0
      Commits
      • fc7916f release: 0.14.0 (#867)
      • 57e546d feat: SEP-1686 Tasks (#844)
      • 35da063 fix: bound request body size in SSE and Streamable HTTP transports (#839)
      • bbf77ba chore(deps): bump @​types/node from 26.0.0 to 26.0.1 in /integration-test/src/...
      • d2b1509 chore(deps): bump the other-dependencies group across 5 directories with 5 up...
      • 0221b8d fix(client): close stdio read sources during shutdown (#798)
      • 19db4b0 fix(client): gate completion/complete on the completions capability (#846)
      • 64cf29f chore(deps): bump gradle-wrapper from 9.6.0 to 9.6.1 (#856)
      • cd91652 fix: validate Origin against localhost by default in DNS rebinding protection...
      • 4103b87 chore(deps): bump org.jetbrains.kotlinx:kotlinx-io-core from 0.9.0 to 0.9.1 i...
      • Additional commits viewable in compare view

      Updates `io.modelcontextprotocol:kotlin-sdk-testing` from 0.13.0 to 0.14.0
      Commits
      • fc7916f release: 0.14.0 (#867)
      • 57e546d feat: SEP-1686 Tasks (#844)
      • 35da063 fix: bound request body size in SSE and Streamable HTTP transports (#839)
      • bbf77ba chore(deps): bump @​types/node from 26.0.0 to 26.0.1 in /integration-test/src/...
      • d2b1509 chore(deps): bump the other-dependencies group across 5 directories with 5 up...
      • 0221b8d fix(client): close stdio read sources during shutdown (#798)
      • 19db4b0 fix(client): gate completion/complete on the completions capability (#846)
      • 64cf29f chore(deps): bump gradle-wrapper from 9.6.0 to 9.6.1 (#856)
      • cd91652 fix: validate Origin against localhost by default in DNS rebinding protection...
      • 4103b87 chore(deps): bump org.jetbrains.kotlinx:kotlinx-io-core from 0.9.0 to 0.9.1 i...
      • Additional commits viewable in compare view

      Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlinlang-mcp-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml index c521eba..766b5ab 100644 --- a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] kotlin = "2.4.0" ktor = "3.5.1" -mcp-kotlin = "0.13.0" +mcp-kotlin = "0.14.0" serialization = "1.11.0" coroutines = "1.11.0" logback = "1.5.37" From 7ea602a5bf259379c027c315e08b94269db0340e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 13:29:38 +0200 Subject: [PATCH 361/449] chore(deps): bump mcp-kotlin from 0.13.0 to 0.14.0 in /samples/simple-streamable-server (#878) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `mcp-kotlin` from 0.13.0 to 0.14.0. Updates `io.modelcontextprotocol:kotlin-sdk-client` from 0.13.0 to 0.14.0
      Commits
      • fc7916f release: 0.14.0 (#867)
      • 57e546d feat: SEP-1686 Tasks (#844)
      • 35da063 fix: bound request body size in SSE and Streamable HTTP transports (#839)
      • bbf77ba chore(deps): bump @​types/node from 26.0.0 to 26.0.1 in /integration-test/src/...
      • d2b1509 chore(deps): bump the other-dependencies group across 5 directories with 5 up...
      • 0221b8d fix(client): close stdio read sources during shutdown (#798)
      • 19db4b0 fix(client): gate completion/complete on the completions capability (#846)
      • 64cf29f chore(deps): bump gradle-wrapper from 9.6.0 to 9.6.1 (#856)
      • cd91652 fix: validate Origin against localhost by default in DNS rebinding protection...
      • 4103b87 chore(deps): bump org.jetbrains.kotlinx:kotlinx-io-core from 0.9.0 to 0.9.1 i...
      • Additional commits viewable in compare view

      Updates `io.modelcontextprotocol:kotlin-sdk-server` from 0.13.0 to 0.14.0
      Commits
      • fc7916f release: 0.14.0 (#867)
      • 57e546d feat: SEP-1686 Tasks (#844)
      • 35da063 fix: bound request body size in SSE and Streamable HTTP transports (#839)
      • bbf77ba chore(deps): bump @​types/node from 26.0.0 to 26.0.1 in /integration-test/src/...
      • d2b1509 chore(deps): bump the other-dependencies group across 5 directories with 5 up...
      • 0221b8d fix(client): close stdio read sources during shutdown (#798)
      • 19db4b0 fix(client): gate completion/complete on the completions capability (#846)
      • 64cf29f chore(deps): bump gradle-wrapper from 9.6.0 to 9.6.1 (#856)
      • cd91652 fix: validate Origin against localhost by default in DNS rebinding protection...
      • 4103b87 chore(deps): bump org.jetbrains.kotlinx:kotlinx-io-core from 0.9.0 to 0.9.1 i...
      • Additional commits viewable in compare view

      Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/simple-streamable-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/simple-streamable-server/gradle/libs.versions.toml b/samples/simple-streamable-server/gradle/libs.versions.toml index e3f8f6a..21db581 100644 --- a/samples/simple-streamable-server/gradle/libs.versions.toml +++ b/samples/simple-streamable-server/gradle/libs.versions.toml @@ -2,7 +2,7 @@ kotlin = "2.4.0" ktor = "3.5.1" junit = "6.1.1" -mcp-kotlin = "0.13.0" +mcp-kotlin = "0.14.0" slf4j = "2.0.18" shadow = "9.4.3" From 91f7390a95dc7265b1202ad5226aaa8fd1d348a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 14:09:53 +0200 Subject: [PATCH 362/449] chore(deps): bump the other-dependencies group across 5 directories with 2 updates (#882) Bumps the other-dependencies group with 2 updates in the /samples/kotlin-mcp-client directory: [com.anthropic:anthropic-java](https://github.com/anthropics/anthropic-sdk-java) and [com.gradleup.shadow](https://github.com/GradleUp/shadow). Bumps the other-dependencies group with 1 update in the /samples/kotlin-mcp-server directory: [com.gradleup.shadow](https://github.com/GradleUp/shadow). Bumps the other-dependencies group with 1 update in the /samples/kotlinlang-mcp-server directory: [com.gradleup.shadow](https://github.com/GradleUp/shadow). Bumps the other-dependencies group with 1 update in the /samples/simple-streamable-server directory: [com.gradleup.shadow](https://github.com/GradleUp/shadow). Bumps the other-dependencies group with 1 update in the /samples/weather-stdio-server directory: [com.gradleup.shadow](https://github.com/GradleUp/shadow). Updates `com.anthropic:anthropic-java` from 2.45.0 to 2.48.0
      Release notes

      Sourced from com.anthropic:anthropic-java's releases.

      v2.48.0

      2.48.0 (2026-07-02)

      Full Changelog: v2.47.1...v2.48.0

      Features

      • api: add agent-memory-2026-07-22 beta header (6603879)

      v2.47.1

      2.47.1 (2026-07-01)

      Full Changelog: v2.47.0...v2.47.1

      Chores

      • api: remove some nonfunctional types from the SDKs (2f92a48)

      v2.47.0

      2.47.0 (2026-06-30)

      Full Changelog: v2.46.0...v2.47.0

      Features

      • api: add support for Managed Agents event delta streaming, agent overrides, reverse pagination, vault credential injection scoping, and agent and deployment webhook events (5eed04b)

      v2.46.0

      2.46.0 (2026-06-30)

      Full Changelog: v2.45.0...v2.46.0

      Features

      • api: add support for claude-sonnet-5 (444f054)
      Changelog

      Sourced from com.anthropic:anthropic-java's changelog.

      2.48.0 (2026-07-02)

      Full Changelog: v2.47.1...v2.48.0

      Features

      • api: add agent-memory-2026-07-22 beta header (6603879)

      2.47.1 (2026-07-01)

      Full Changelog: v2.47.0...v2.47.1

      Chores

      • api: remove some nonfunctional types from the SDKs (2f92a48)

      2.47.0 (2026-06-30)

      Full Changelog: v2.46.0...v2.47.0

      Features

      • api: add support for Managed Agents event delta streaming, agent overrides, reverse pagination, vault credential injection scoping, and agent and deployment webhook events (5eed04b)

      2.46.0 (2026-06-30)

      Full Changelog: v2.45.0...v2.46.0

      Features

      • api: add support for claude-sonnet-5 (444f054)
      Commits
      • db3e617 release: 2.48.0
      • a462333 feat(api): add agent-memory-2026-07-22 beta header
      • 3c6c77b release: 2.47.1
      • 0d315e6 chore(api): remove some nonfunctional types from the SDKs
      • 2093447 release: 2.47.0
      • 4bf9078 feat(api): add support for Managed Agents event delta streaming, agent overri...
      • 1c9486b codegen metadata
      • fab7574 release: 2.46.0
      • f0b5297 feat(api): add support for claude-sonnet-5
      • See full diff in compare view

      Updates `com.gradleup.shadow` from 9.4.3 to 9.5.1
      Release notes

      Sourced from com.gradleup.shadow's releases.

      9.5.1

      Fixed

      • Fix eager calls for toolchainSpec in Kotlin DSL.
        This fixes the regression when using toolchain without targetCompatibility in Kotlin DSL.

      9.5.0

      Added

      • Check DuplicatesStrategy for merging transformers. (#2026)
        This will log warnings when an incompatible DuplicatesStrategy (e.g., EXCLUDE) is applied in Gradle configuration for built-in ResourceTransformers.
      • Add KotlinModuleMetadataTransformer. (#2073)
      • Add R8 as an opt-in minimize { r8 { ... } } tool for shrinking the final shadowed JAR. (#2077)

      Changed

      • Bump min Gradle requirement to 9.2.0. (#2057)
      • Remove afterEvaluate when adding variants. (#2056)
      • Deprecate enableKotlinModuleRemapping for ShadowJar. (#2073)
        Apply KotlinModuleMetadataTransformer explicitly to support relocating inside Kotlin module metadata files.
      • Deprecate everything under ShadowCopyAction. (#2083)

      Fixed

      • Fix the conflicts when using afterEvaluate with other plugins. (#2055)
      Commits
      • c33612d Prepare version 9.5.1
      • 485e412 Fix eager calls for toolchainSpec in Kotlin DSL (#2087)
      • d062fa2 Fix DuplicatesStrategyCheckerTest resource loading on Windows (#2085)
      • e71a2c1 Add description for shadow configuration (#2084)
      • 7ba43b0 Prepare next development version
      • 6e00cb7 Prepare version 9.5.0
      • ca03909 Deprecate everything under ShadowCopyAction (#2083)
      • 7d7da27 Revert "Mark CONSTANT_TIME_FOR_ZIP_ENTRIES as const (#2074)"
      • da18cc0 Deprecate enableKotlinModuleRemapping for ShadowJar (#2073)
      • ec6e55a Replace system properties with buildConfig consts (#2082)
      • Additional commits viewable in compare view

      Updates `com.gradleup.shadow` from 9.4.3 to 9.5.1
      Release notes

      Sourced from com.gradleup.shadow's releases.

      9.5.1

      Fixed

      • Fix eager calls for toolchainSpec in Kotlin DSL.
        This fixes the regression when using toolchain without targetCompatibility in Kotlin DSL.

      9.5.0

      Added

      • Check DuplicatesStrategy for merging transformers. (#2026)
        This will log warnings when an incompatible DuplicatesStrategy (e.g., EXCLUDE) is applied in Gradle configuration for built-in ResourceTransformers.
      • Add KotlinModuleMetadataTransformer. (#2073)
      • Add R8 as an opt-in minimize { r8 { ... } } tool for shrinking the final shadowed JAR. (#2077)

      Changed

      • Bump min Gradle requirement to 9.2.0. (#2057)
      • Remove afterEvaluate when adding variants. (#2056)
      • Deprecate enableKotlinModuleRemapping for ShadowJar. (#2073)
        Apply KotlinModuleMetadataTransformer explicitly to support relocating inside Kotlin module metadata files.
      • Deprecate everything under ShadowCopyAction. (#2083)

      Fixed

      • Fix the conflicts when using afterEvaluate with other plugins. (#2055)
      Commits
      • c33612d Prepare version 9.5.1
      • 485e412 Fix eager calls for toolchainSpec in Kotlin DSL (#2087)
      • d062fa2 Fix DuplicatesStrategyCheckerTest resource loading on Windows (#2085)
      • e71a2c1 Add description for shadow configuration (#2084)
      • 7ba43b0 Prepare next development version
      • 6e00cb7 Prepare version 9.5.0
      • ca03909 Deprecate everything under ShadowCopyAction (#2083)
      • 7d7da27 Revert "Mark CONSTANT_TIME_FOR_ZIP_ENTRIES as const (#2074)"
      • da18cc0 Deprecate enableKotlinModuleRemapping for ShadowJar (#2073)
      • ec6e55a Replace system properties with buildConfig consts (#2082)
      • Additional commits viewable in compare view

      Updates `com.gradleup.shadow` from 9.4.3 to 9.5.1
      Release notes

      Sourced from com.gradleup.shadow's releases.

      9.5.1

      Fixed

      • Fix eager calls for toolchainSpec in Kotlin DSL.
        This fixes the regression when using toolchain without targetCompatibility in Kotlin DSL.

      9.5.0

      Added

      • Check DuplicatesStrategy for merging transformers. (#2026)
        This will log warnings when an incompatible DuplicatesStrategy (e.g., EXCLUDE) is applied in Gradle configuration for built-in ResourceTransformers.
      • Add KotlinModuleMetadataTransformer. (#2073)
      • Add R8 as an opt-in minimize { r8 { ... } } tool for shrinking the final shadowed JAR. (#2077)

      Changed

      • Bump min Gradle requirement to 9.2.0. (#2057)
      • Remove afterEvaluate when adding variants. (#2056)
      • Deprecate enableKotlinModuleRemapping for ShadowJar. (#2073)
        Apply KotlinModuleMetadataTransformer explicitly to support relocating inside Kotlin module metadata files.
      • Deprecate everything under ShadowCopyAction. (#2083)

      Fixed

      • Fix the conflicts when using afterEvaluate with other plugins. (#2055)
      Commits
      • c33612d Prepare version 9.5.1
      • 485e412 Fix eager calls for toolchainSpec in Kotlin DSL (#2087)
      • d062fa2 Fix DuplicatesStrategyCheckerTest resource loading on Windows (#2085)
      • e71a2c1 Add description for shadow configuration (#2084)
      • 7ba43b0 Prepare next development version
      • 6e00cb7 Prepare version 9.5.0
      • ca03909 Deprecate everything under ShadowCopyAction (#2083)
      • 7d7da27 Revert "Mark CONSTANT_TIME_FOR_ZIP_ENTRIES as const (#2074)"
      • da18cc0 Deprecate enableKotlinModuleRemapping for ShadowJar (#2073)
      • ec6e55a Replace system properties with buildConfig consts (#2082)
      • Additional commits viewable in compare view

      Updates `com.gradleup.shadow` from 9.4.3 to 9.5.1
      Release notes

      Sourced from com.gradleup.shadow's releases.

      9.5.1

      Fixed

      • Fix eager calls for toolchainSpec in Kotlin DSL.
        This fixes the regression when using toolchain without targetCompatibility in Kotlin DSL.

      9.5.0

      Added

      • Check DuplicatesStrategy for merging transformers. (#2026)
        This will log warnings when an incompatible DuplicatesStrategy (e.g., EXCLUDE) is applied in Gradle configuration for built-in ResourceTransformers.
      • Add KotlinModuleMetadataTransformer. (#2073)
      • Add R8 as an opt-in minimize { r8 { ... } } tool for shrinking the final shadowed JAR. (#2077)

      Changed

      • Bump min Gradle requirement to 9.2.0. (#2057)
      • Remove afterEvaluate when adding variants. (#2056)
      • Deprecate enableKotlinModuleRemapping for ShadowJar. (#2073)
        Apply KotlinModuleMetadataTransformer explicitly to support relocating inside Kotlin module metadata files.
      • Deprecate everything under ShadowCopyAction. (#2083)

      Fixed

      • Fix the conflicts when using afterEvaluate with other plugins. (#2055)
      Commits
      • c33612d Prepare version 9.5.1
      • 485e412 Fix eager calls for toolchainSpec in Kotlin DSL (#2087)
      • d062fa2 Fix DuplicatesStrategyCheckerTest resource loading on Windows (#2085)
      • e71a2c1 Add description for shadow configuration (#2084)
      • 7ba43b0 Prepare next development version
      • 6e00cb7 Prepare version 9.5.0
      • ca03909 Deprecate everything under ShadowCopyAction (#2083)
      • 7d7da27 Revert "Mark CONSTANT_TIME_FOR_ZIP_ENTRIES as const (#2074)"
      • da18cc0 Deprecate enableKotlinModuleRemapping for ShadowJar (#2073)
      • ec6e55a Replace system properties with buildConfig consts (#2082)
      • Additional commits viewable in compare view

      Updates `com.gradleup.shadow` from 9.4.3 to 9.5.1
      Release notes

      Sourced from com.gradleup.shadow's releases.

      9.5.1

      Fixed

      • Fix eager calls for toolchainSpec in Kotlin DSL.
        This fixes the regression when using toolchain without targetCompatibility in Kotlin DSL.

      9.5.0

      Added

      • Check DuplicatesStrategy for merging transformers. (#2026)
        This will log warnings when an incompatible DuplicatesStrategy (e.g., EXCLUDE) is applied in Gradle configuration for built-in ResourceTransformers.
      • Add KotlinModuleMetadataTransformer. (#2073)
      • Add R8 as an opt-in minimize { r8 { ... } } tool for shrinking the final shadowed JAR. (#2077)

      Changed

      • Bump min Gradle requirement to 9.2.0. (#2057)
      • Remove afterEvaluate when adding variants. (#2056)
      • Deprecate enableKotlinModuleRemapping for ShadowJar. (#2073)
        Apply KotlinModuleMetadataTransformer explicitly to support relocating inside Kotlin module metadata files.
      • Deprecate everything under ShadowCopyAction. (#2083)

      Fixed

      • Fix the conflicts when using afterEvaluate with other plugins. (#2055)
      Commits
      • c33612d Prepare version 9.5.1
      • 485e412 Fix eager calls for toolchainSpec in Kotlin DSL (#2087)
      • d062fa2 Fix DuplicatesStrategyCheckerTest resource loading on Windows (#2085)
      • e71a2c1 Add description for shadow configuration (#2084)
      • 7ba43b0 Prepare next development version
      • 6e00cb7 Prepare version 9.5.0
      • ca03909 Deprecate everything under ShadowCopyAction (#2083)
      • 7d7da27 Revert "Mark CONSTANT_TIME_FOR_ZIP_ENTRIES as const (#2074)"
      • da18cc0 Deprecate enableKotlinModuleRemapping for ShadowJar (#2073)
      • ec6e55a Replace system properties with buildConfig consts (#2082)
      • Additional commits viewable in compare view

      Updates `com.gradleup.shadow` from 9.4.3 to 9.5.1
      Release notes

      Sourced from com.gradleup.shadow's releases.

      9.5.1

      Fixed

      • Fix eager calls for toolchainSpec in Kotlin DSL.
        This fixes the regression when using toolchain without targetCompatibility in Kotlin DSL.

      9.5.0

      Added

      • Check DuplicatesStrategy for merging transformers. (#2026)
        This will log warnings when an incompatible DuplicatesStrategy (e.g., EXCLUDE) is applied in Gradle configuration for built-in ResourceTransformers.
      • Add KotlinModuleMetadataTransformer. (#2073)
      • Add R8 as an opt-in minimize { r8 { ... } } tool for shrinking the final shadowed JAR. (#2077)

      Changed

      • Bump min Gradle requirement to 9.2.0. (#2057)
      • Remove afterEvaluate when adding variants. (#2056)
      • Deprecate enableKotlinModuleRemapping for ShadowJar. (#2073)
        Apply KotlinModuleMetadataTransformer explicitly to support relocating inside Kotlin module metadata files.
      • Deprecate everything under ShadowCopyAction. (#2083)

      Fixed

      • Fix the conflicts when using afterEvaluate with other plugins. (#2055)
      Commits
      • c33612d Prepare version 9.5.1
      • 485e412 Fix eager calls for toolchainSpec in Kotlin DSL (#2087)
      • d062fa2 Fix DuplicatesStrategyCheckerTest resource loading on Windows (#2085)
      • e71a2c1 Add description for shadow configuration (#2084)
      • 7ba43b0 Prepare next development version
      • 6e00cb7 Prepare version 9.5.0
      • ca03909 Deprecate everything under ShadowCopyAction (#2083)
      • 7d7da27 Revert "Mark CONSTANT_TIME_FOR_ZIP_ENTRIES as const (#2074)"
      • da18cc0 Deprecate enableKotlinModuleRemapping for ShadowJar (#2073)
      • ec6e55a Replace system properties with buildConfig consts (#2082)
      • Additional commits viewable in compare view

      Updates `com.gradleup.shadow` from 9.4.3 to 9.5.1
      Release notes

      Sourced from com.gradleup.shadow's releases.

      9.5.1

      Fixed

      • Fix eager calls for toolchainSpec in Kotlin DSL.
        This fixes the regression when using toolchain without targetCompatibility in Kotlin DSL.

      9.5.0

      Added

      • Check DuplicatesStrategy for merging transformers. (#2026)
        This will log warnings when an incompatible DuplicatesStrategy (e.g., EXCLUDE) is applied in Gradle configuration for built-in ResourceTransformers.
      • Add KotlinModuleMetadataTransformer. (#2073)
      • Add R8 as an opt-in minimize { r8 { ... } } tool for shrinking the final shadowed JAR. (#2077)

      Changed

      • Bump min Gradle requirement to 9.2.0. (#2057)
      • Remove afterEvaluate when adding variants. (#2056)
      • Deprecate enableKotlinModuleRemapping for ShadowJar. (#2073)
        Apply KotlinModuleMetadataTransformer explicitly to support relocating inside Kotlin module metadata files.
      • Deprecate everything under ShadowCopyAction. (#2083)

      Fixed

      • Fix the conflicts when using afterEvaluate with other plugins. (#2055)
      Commits
      • c33612d Prepare version 9.5.1
      • 485e412 Fix eager calls for toolchainSpec in Kotlin DSL (#2087)
      • d062fa2 Fix DuplicatesStrategyCheckerTest resource loading on Windows (#2085)
      • e71a2c1 Add description for shadow configuration (#2084)
      • 7ba43b0 Prepare next development version
      • 6e00cb7 Prepare version 9.5.0
      • ca03909 Deprecate everything under ShadowCopyAction (#2083)
      • 7d7da27 Revert "Mark CONSTANT_TIME_FOR_ZIP_ENTRIES as const (#2074)"
      • da18cc0 Deprecate enableKotlinModuleRemapping for ShadowJar (#2073)
      • ec6e55a Replace system properties with buildConfig consts (#2082)
      • Additional commits viewable in compare view

      Updates `com.gradleup.shadow` from 9.4.3 to 9.5.1
      Release notes

      Sourced from com.gradleup.shadow's releases.

      9.5.1

      Fixed

      • Fix eager calls for toolchainSpec in Kotlin DSL.
        This fixes the regression when using toolchain without targetCompatibility in Kotlin DSL.

      9.5.0

      Added

      • Check DuplicatesStrategy for merging transformers. (#2026)
        This will log warnings when an incompatible DuplicatesStrategy (e.g., EXCLUDE) is applied in Gradle configuration for built-in ResourceTransformers.
      • Add KotlinModuleMetadataTransformer. (#2073)
      • Add R8 as an opt-in minimize { r8 { ... } } tool for shrinking the final shadowed JAR. (#2077)

      Changed

      • Bump min Gradle requirement to 9.2.0. (#2057)
      • Remove afterEvaluate when adding variants. (#2056)
      • Deprecate enableKotlinModuleRemapping for ShadowJar. (#2073)
        Apply KotlinModuleMetadataTransformer explicitly to support relocating inside Kotlin module metadata files.
      • Deprecate everything under ShadowCopyAction. (#2083)

      Fixed

      • Fix the conflicts when using afterEvaluate with other plugins. (#2055)
      Commits
      • c33612d Prepare version 9.5.1
      • 485e412 Fix eager calls for toolchainSpec in Kotlin DSL (#2087)
      • d062fa2 Fix DuplicatesStrategyCheckerTest resource loading on Windows (#2085)
      • e71a2c1 Add description for shadow configuration (#2084)
      • 7ba43b0 Prepare next development version
      • 6e00cb7 Prepare version 9.5.0
      • ca03909 Deprecate everything under ShadowCopyAction (#2083)
      • 7d7da27 Revert "Mark CONSTANT_TIME_FOR_ZIP_ENTRIES as const (#2074)"
      • da18cc0 Deprecate enableKotlinModuleRemapping for ShadowJar (#2073)
      • ec6e55a Replace system properties with buildConfig consts (#2082)
      • Additional commits viewable in compare view

      Updates `com.gradleup.shadow` from 9.4.3 to 9.5.1
      Release notes

      Sourced from com.gradleup.shadow's releases.

      9.5.1

      Fixed

      • Fix eager calls for toolchainSpec in Kotlin DSL.
        This fixes the regression when using toolchain without targetCompatibility in Kotlin DSL.

      9.5.0

      Added

      • Check DuplicatesStrategy for merging transformers. (#2026)
        This will log warnings when an incompatible DuplicatesStrategy (e.g., EXCLUDE) is applied in Gradle configuration for built-in ResourceTransformers.
      • Add KotlinModuleMetadataTransformer. (#2073)
      • Add R8 as an opt-in minimize { r8 { ... } } tool for shrinking the final shadowed JAR. (#2077)

      Changed

      • Bump min Gradle requirement to 9.2.0. (#2057)
      • Remove afterEvaluate when adding variants. (#2056)
      • Deprecate enableKotlinModuleRemapping for ShadowJar. (#2073)
        Apply KotlinModuleMetadataTransformer explicitly to support relocating inside Kotlin module metadata files.
      • Deprecate everything under ShadowCopyAction. (#2083)

      Fixed

      • Fix the conflicts when using afterEvaluate with other plugins. (#2055)
      Commits
      • c33612d Prepare version 9.5.1
      • 485e412 Fix eager calls for toolchainSpec in Kotlin DSL (#2087)
      • d062fa2 Fix DuplicatesStrategyCheckerTest resource loading on Windows (#2085)
      • e71a2c1 Add description for shadow configuration (#2084)
      • 7ba43b0 Prepare next development version
      • 6e00cb7 Prepare version 9.5.0
      • ca03909 Deprecate everything under ShadowCopyAction (#2083)
      • 7d7da27 Revert "Mark CONSTANT_TIME_FOR_ZIP_ENTRIES as const (#2074)"
      • da18cc0 Deprecate enableKotlinModuleRemapping for ShadowJar (#2073)
      • ec6e55a Replace system properties with buildConfig consts (#2082)
      • Additional commits viewable in compare view

      Updates `com.gradleup.shadow` from 9.4.3 to 9.5.1
      Release notes

      Sourced from com.gradleup.shadow's releases.

      9.5.1

      Fixed

      • Fix eager calls for toolchainSpec in Kotlin DSL.
        This fixes the regression when using toolchain without targetCompatibility in Kotlin DSL.

      9.5.0

      Added

      • Check DuplicatesStrategy for merging transformers. (#2026)
        This will log warnings when an incompatible DuplicatesStrategy (e.g., EXCLUDE) is applied in Gradle configuration for built-in ResourceTransformers.
      • Add KotlinModuleMetadataTransformer. (#2073)
      • Add R8 as an opt-in minimize { r8 { ... } } tool for shrinking the final shadowed JAR. (#2077)

      Changed

      • Bump min Gradle requirement to 9.2.0. (#2057)
      • Remove afterEvaluate when adding variants. (#2056)
      • Deprecate enableKotlinModuleRemapping for ShadowJar. (#2073)
        Apply KotlinModuleMetadataTransformer explicitly to support relocating inside Kotlin module metadata files.
      • Deprecate everything under ShadowCopyAction. (#2083)

      Fixed

      • Fix the conflicts when using afterEvaluate with other plugins. (#2055)
      Commits
      • c33612d Prepare version 9.5.1
      • 485e412 Fix eager calls for toolchainSpec in Kotlin DSL (#2087)
      • d062fa2 Fix DuplicatesStrategyCheckerTest resource loading on Windows (#2085)
      • e71a2c1 Add description for shadow configuration (#2084)
      • 7ba43b0 Prepare next development version
      • 6e00cb7 Prepare version 9.5.0
      • ca03909 Deprecate everything under ShadowCopyAction (#2083)
      • 7d7da27 Revert "Mark CONSTANT_TIME_FOR_ZIP_ENTRIES as const (#2074)"
      • da18cc0 Deprecate enableKotlinModuleRemapping for ShadowJar (#2073)
      • ec6e55a Replace system properties with buildConfig consts (#2082)
      • Additional commits viewable in compare view

      Updates `com.gradleup.shadow` from 9.4.3 to 9.5.1
      Release notes

      Sourced from com.gradleup.shadow's releases.

      9.5.1

      Fixed

      • Fix eager calls for toolchainSpec in Kotlin DSL.
        This fixes the regression when using toolchain without targetCompatibility in Kotlin DSL.

      9.5.0

      Added

      • Check DuplicatesStrategy for merging transformers. (#2026)
        This will log warnings when an incompatible DuplicatesStrategy (e.g., EXCLUDE) is applied in Gradle configuration for built-in ResourceTransformers.
      • Add KotlinModuleMetadataTransformer. (#2073)
      • Add R8 as an opt-in minimize { r8 { ... } } tool for shrinking the final shadowed JAR. (#2077)

      Changed

      • Bump min Gradle requirement to 9.2.0. (#2057)
      • Remove afterEvaluate when adding variants. (#2056)
      • Deprecate enableKotlinModuleRemapping for ShadowJar. (#2073)
        Apply KotlinModuleMetadataTransformer explicitly to support relocating inside Kotlin module metadata files.
      • Deprecate everything under ShadowCopyAction. (#2083)

      Fixed

      • Fix the conflicts when using afterEvaluate with other plugins. (#2055)
      Commits
      • c33612d Prepare version 9.5.1
      • 485e412 Fix eager calls for toolchainSpec in Kotlin DSL (#2087)
      • d062fa2 Fix DuplicatesStrategyCheckerTest resource loading on Windows (#2085)
      • e71a2c1 Add description for shadow configuration (#2084)
      • 7ba43b0 Prepare next development version
      • 6e00cb7 Prepare version 9.5.0
      • ca03909 Deprecate everything under ShadowCopyAction (#2083)
      • 7d7da27 Revert "Mark CONSTANT_TIME_FOR_ZIP_ENTRIES as const (#2074)"
      • da18cc0 Deprecate enableKotlinModuleRemapping for ShadowJar (#2073)
      • ec6e55a Replace system properties with buildConfig consts (#2082)
      • Additional commits viewable in compare view

      Updates `com.gradleup.shadow` from 9.4.3 to 9.5.1
      Release notes

      Sourced from com.gradleup.shadow's releases.

      9.5.1

      Fixed

      • Fix eager calls for toolchainSpec in Kotlin DSL.
        This fixes the regression when using toolchain without targetCompatibility in Kotlin DSL.

      9.5.0

      Added

      • Check DuplicatesStrategy for merging transformers. (#2026)
        This will log warnings when an incompatible DuplicatesStrategy (e.g., EXCLUDE) is applied in Gradle configuration for built-in ResourceTransformers.
      • Add KotlinModuleMetadataTransformer. (#2073)
      • Add R8 as an opt-in minimize { r8 { ... } } tool for shrinking the final shadowed JAR. (#2077)

      Changed

      • Bump min Gradle requirement to 9.2.0. (#2057)
      • Remove afterEvaluate when adding variants. (#2056)
      • Deprecate enableKotlinModuleRemapping for ShadowJar. (#2073)
        Apply KotlinModuleMetadataTransformer explicitly to support relocating inside Kotlin module metadata files.
      • Deprecate everything under ShadowCopyAction. (#2083)

      Fixed

      • Fix the conflicts when using afterEvaluate with other plugins. (#2055)
      Commits
      • c33612d Prepare version 9.5.1
      • 485e412 Fix eager calls for toolchainSpec in Kotlin DSL (#2087)
      • d062fa2 Fix DuplicatesStrategyCheckerTest resource loading on Windows (#2085)
      • e71a2c1 Add description for shadow configuration (#2084)
      • 7ba43b0 Prepare next development version
      • 6e00cb7 Prepare version 9.5.0
      • ca03909 Deprecate everything under ShadowCopyAction (#2083)
      • 7d7da27 Revert "Mark CONSTANT_TIME_FOR_ZIP_ENTRIES as const (#2074)"
      • da18cc0 Deprecate enableKotlinModuleRemapping for ShadowJar (#2073)
      • ec6e55a Replace system properties with buildConfig consts (#2082)
      • Additional commits viewable in compare view

      Updates `com.gradleup.shadow` from 9.4.3 to 9.5.1
      Release notes

      Sourced from com.gradleup.shadow's releases.

      9.5.1

      Fixed

      • Fix eager calls for toolchainSpec in Kotlin DSL.
        This fixes the regression when using toolchain without targetCompatibility in Kotlin DSL.

      9.5.0

      Added

      • Check DuplicatesStrategy for merging transformers. (#2026)
        This will log warnings when an incompatible DuplicatesStrategy (e.g., EXCLUDE) is applied in Gradle configuration for built-in ResourceTransformers.
      • Add KotlinModuleMetadataTransformer. (#2073)
      • Add R8 as an opt-in minimize { r8 { ... } } tool for shrinking the final shadowed JAR. (#2077)

      Changed

      • Bump min Gradle requirement to 9.2.0. (#2057)
      • Remove afterEvaluate when adding variants. (#2056)
      • Deprecate enableKotlinModuleRemapping for ShadowJar. (#2073)
        Apply KotlinModuleMetadataTransformer explicitly to support relocating inside Kotlin module metadata files.
      • Deprecate everything under ShadowCopyAction. (#2083)

      Fixed

      • Fix the conflicts when using afterEvaluate with other plugins. (#2055)
      Commits
      • c33612d Prepare version 9.5.1
      • 485e412 Fix eager calls for toolchainSpec in Kotlin DSL (#2087)
      • d062fa2 Fix DuplicatesStrategyCheckerTest resource loading on Windows (#2085)
      • e71a2c1 Add description for shadow configuration (#2084)
      • 7ba43b0 Prepare next development version
      • 6e00cb7 Prepare version 9.5.0
      • ca03909 Deprecate everything under ShadowCopyAction (#2083)
      • 7d7da27 Revert "Mark CONSTANT_TIME_FOR_ZIP_ENTRIES as const (#2074)"
      • da18cc0 Deprecate enableKotlinModuleRemapping for ShadowJar (#2073)
      • ec6e55a Replace system properties with buildConfig consts (#2082)
      • Additional commits viewable in Date: Mon, 6 Jul 2026 14:39:33 +0200 Subject: [PATCH 363/449] chore(deps): bump mcp-kotlin from 0.13.0 to 0.14.0 in /samples/weather-stdio-server (#879) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `mcp-kotlin` from 0.13.0 to 0.14.0. Updates `io.modelcontextprotocol:kotlin-sdk-client` from 0.13.0 to 0.14.0
        Commits
        • fc7916f release: 0.14.0 (#867)
        • 57e546d feat: SEP-1686 Tasks (#844)
        • 35da063 fix: bound request body size in SSE and Streamable HTTP transports (#839)
        • bbf77ba chore(deps): bump @​types/node from 26.0.0 to 26.0.1 in /integration-test/src/...
        • d2b1509 chore(deps): bump the other-dependencies group across 5 directories with 5 up...
        • 0221b8d fix(client): close stdio read sources during shutdown (#798)
        • 19db4b0 fix(client): gate completion/complete on the completions capability (#846)
        • 64cf29f chore(deps): bump gradle-wrapper from 9.6.0 to 9.6.1 (#856)
        • cd91652 fix: validate Origin against localhost by default in DNS rebinding protection...
        • 4103b87 chore(deps): bump org.jetbrains.kotlinx:kotlinx-io-core from 0.9.0 to 0.9.1 i...
        • Additional commits viewable in compare view

        Updates `io.modelcontextprotocol:kotlin-sdk-server` from 0.13.0 to 0.14.0
        Commits
        • fc7916f release: 0.14.0 (#867)
        • 57e546d feat: SEP-1686 Tasks (#844)
        • 35da063 fix: bound request body size in SSE and Streamable HTTP transports (#839)
        • bbf77ba chore(deps): bump @​types/node from 26.0.0 to 26.0.1 in /integration-test/src/...
        • d2b1509 chore(deps): bump the other-dependencies group across 5 directories with 5 up...
        • 0221b8d fix(client): close stdio read sources during shutdown (#798)
        • 19db4b0 fix(client): gate completion/complete on the completions capability (#846)
        • 64cf29f chore(deps): bump gradle-wrapper from 9.6.0 to 9.6.1 (#856)
        • cd91652 fix: validate Origin against localhost by default in DNS rebinding protection...
        • 4103b87 chore(deps): bump org.jetbrains.kotlinx:kotlinx-io-core from 0.9.0 to 0.9.1 i...
        • Additional commits viewable in compare view

        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/weather-stdio-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/weather-stdio-server/gradle/libs.versions.toml b/samples/weather-stdio-server/gradle/libs.versions.toml index ee81756..67fec97 100644 --- a/samples/weather-stdio-server/gradle/libs.versions.toml +++ b/samples/weather-stdio-server/gradle/libs.versions.toml @@ -3,7 +3,7 @@ coroutines = "1.11.0" kotlin = "2.4.0" ktor = "3.5.1" logging = "8.0.4" -mcp-kotlin = "0.13.0" +mcp-kotlin = "0.14.0" shadow = "9.5.1" slf4j = "2.0.18" From 905d4f975e415a1113922465ef8013d9eedfdb21 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 13:00:40 +0000 Subject: [PATCH 364/449] chore(deps): bump the all-dependencies group across 1 directory with 2 updates (#880) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the all-dependencies group with 2 updates in the /integration-test/src/jvmTest/typescript directory: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) and [tsx](https://github.com/privatenumber/tsx). Updates `@types/node` from 26.0.1 to 26.1.0
        Commits

        Updates `tsx` from 4.22.4 to 4.23.0
        Release notes

        Sourced from tsx's releases.

        v4.23.0

        4.23.0 (2026-07-03)

        Bug Fixes

        Features


        This release is also available on:

        v4.22.5

        4.22.5 (2026-07-02)

        Bug Fixes

        • isolate hook state per async module.register() registration (a305f36)

        This release is also available on:

        Commits
        • 1dfad37 docs: cite esbuild's extension-resolution model in node notes
        • 257bbbb fix: avoid redundant filesystem probes during module resolution
        • c178197 feat: add multi-scenario startup benchmark suite
        • 51800ac docs: add Node internals knowledge base (notes/node)
        • a305f36 fix: isolate hook state per async module.register() registration
        • ca501a9 chore: upgrade skills-npm to v1.2.0
        • 596cd1f test: cover __dirname, __filename, & require.cache in CJS TS file
        • 75d9bf0 test: lock in lenient ESM for ambiguous and CJS-typed packages
        • 1472f3e test: cover ESM-syntax dependency with omitted "type" field
        • See full diff in compare view

        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../src/jvmTest/typescript/package-lock.json | 16 ++++++++-------- .../src/jvmTest/typescript/package.json | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index befd50e..4d0c500 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -15,9 +15,9 @@ "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^26.0.1", + "@types/node": "^26.1.0", "cors": "^2.8.6", - "tsx": "^4.22.4", + "tsx": "^4.23.0", "typescript": "^6.0.3" } }, @@ -579,9 +579,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.1.tgz", - "integrity": "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw==", + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.0.tgz", + "integrity": "sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw==", "dev": true, "license": "MIT", "dependencies": { @@ -1680,9 +1680,9 @@ } }, "node_modules/tsx": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", - "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.0.tgz", + "integrity": "sha512-eUdUIaCr963q2h5u3+QwvYp0+eqPvn+egeqZUm0hwERCqqx1E3kK5ehbGCvqSE5MQAULr67ww0cA3jKc3YkM1w==", "dev": true, "license": "MIT", "dependencies": { diff --git a/integration-test/src/jvmTest/typescript/package.json b/integration-test/src/jvmTest/typescript/package.json index aa8d9db..bf81140 100644 --- a/integration-test/src/jvmTest/typescript/package.json +++ b/integration-test/src/jvmTest/typescript/package.json @@ -16,9 +16,9 @@ "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^26.0.1", + "@types/node": "^26.1.0", "cors": "^2.8.6", - "tsx": "^4.22.4", + "tsx": "^4.23.0", "typescript": "^6.0.3" } } From 7937390b4787f758c82c369541374d050fbdcd9b Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Thu, 9 Jul 2026 17:03:01 +0200 Subject: [PATCH 365/449] test: create the server session before connecting the client (#885) `ChannelTransportTest` connected both sides concurrently, which is racy. `ChannelTransport` starts delivering inbound messages when its read loop launches, which happens before `start()` marks the transport `Operational`. The two run on different dispatchers, so the client's `initialize` could reach the server read loop first and the session's `InitializeResult` was then rejected by the send state gate with `Transport is not ready`. Measured ~3 failures in 600 rounds. `createSession()` does not await the handshake, so creating the session first is enough to order the two. This also matches the usage shown in `createLinkedPair()`'s KDoc. Not addressed here: the transport itself should not deliver inbound messages before it is `Operational`. Fixing that needs a post-`Operational` hook on `AbstractClientTransport` that the read loop awaits. --- .../kotlin/sdk/testing/ChannelTransportTest.kt | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/testing/ChannelTransportTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/testing/ChannelTransportTest.kt index ea246db..4d798ce 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/testing/ChannelTransportTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/testing/ChannelTransportTest.kt @@ -6,7 +6,6 @@ import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi import io.modelcontextprotocol.kotlin.sdk.client.Client import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions -import io.modelcontextprotocol.kotlin.sdk.server.ServerSession import io.modelcontextprotocol.kotlin.sdk.types.Implementation import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest import io.modelcontextprotocol.kotlin.sdk.types.InitializeResult @@ -16,9 +15,6 @@ import io.modelcontextprotocol.kotlin.sdk.types.ListResourcesResult import io.modelcontextprotocol.kotlin.sdk.types.Method import io.modelcontextprotocol.kotlin.sdk.types.Resource import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.joinAll -import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlin.test.Test @@ -43,18 +39,9 @@ class ChannelTransportTest { clientInfo = Implementation(name = "test client", version = "1.0"), ) - val serverSessionResult = CompletableDeferred() + val serverSession = server.createSession(serverTransport) + client.connect(clientTransport) - listOf( - launch { - client.connect(clientTransport) - }, - launch { - serverSessionResult.complete(server.createSession(serverTransport)) - }, - ).joinAll() - - val serverSession = serverSessionResult.await() serverSession.setRequestHandler(Method.Defined.Initialize) { _, _ -> InitializeResult( protocolVersion = LATEST_PROTOCOL_VERSION, From beb340006498a73f83205e431344c0ff70687984 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Wed, 15 Jul 2026 12:33:29 +0200 Subject: [PATCH 366/449] feat(core): concurrent inbound dispatch behind an initialization gate (#884) Make inbound message handling concurrent after the initialization handshake, so a slow or peer-awaiting handler no longer blocks other messages on the same connection fixes #176, #572 ## Motivation and Context Inbound messages were processed serially on the transport read loop. One slow or peer-awaiting request handler (sampling, elicitation, roots) blocked every later message on that connection, including the responses and `notifications/cancelled` those handlers depend on which could deadlock request/response flows. ## How Has This Been Tested? New and old tests ## Breaking Changes - after the handshake, inbound handlers may run concurrently (processing stays serial during initialization). There is no opt-out flag by design, concurrency is bounded by `maxConcurrentHandlers` / `maxInFlightHandlers`. - `ProtocolOptions` / `ClientOptions` / `ServerOptions` gain new parameters with defaults - `RequestHandlerExtra` is now a coroutine-context element and is no longer user-constructible ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [x] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- .../kotlin/sdk/client/ClientTest.kt | 38 +- .../ProtocolConcurrencyIntegrationTest.kt | 306 ++++++++++ .../sdk/server/ClientConnectionLoggingTest.kt | 22 +- .../sdk/server/ServerSessionInitializeTest.kt | 2 + kotlin-sdk-client/api/kotlin-sdk-client.api | 4 +- .../kotlin/sdk/client/Client.kt | 10 +- kotlin-sdk-core/api/kotlin-sdk-core.api | 21 +- .../kotlin/sdk/shared/Protocol.kt | 569 +++++++++++++++--- .../kotlin/sdk/types/methods.kt | 9 + .../kotlin/sdk/types/serializers.kt | 9 +- .../sdk/shared/ProtocolConcurrencyTest.kt | 524 ++++++++++++++++ .../kotlin/sdk/shared/ProtocolOptionsTest.kt | 16 + .../ProtocolOutboundCancellationTest.kt | 167 +++++ .../kotlin/sdk/shared/ProtocolTest.kt | 197 ++++-- .../kotlin/sdk/shared/ProtocolTestFakes.kt | 112 ++++ .../sdk/shared/ProtocolConcurrencyJvmTest.kt | 114 ++++ kotlin-sdk-server/api/kotlin-sdk-server.api | 5 +- .../kotlin/sdk/server/Server.kt | 9 +- .../kotlin/sdk/server/ServerSession.kt | 8 + .../server/StreamableHttpServerTransport.kt | 191 +++++- .../StreamableHttpServerTransportTest.kt | 326 ++++++++++ 21 files changed, 2465 insertions(+), 194 deletions(-) create mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/ProtocolConcurrencyIntegrationTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolConcurrencyTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolOptionsTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolOutboundCancellationTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolTestFakes.kt create mode 100644 kotlin-sdk-core/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolConcurrencyJvmTest.kt diff --git a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt index 5dfcf61..54d25ee 100644 --- a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt +++ b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt @@ -24,6 +24,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest import io.modelcontextprotocol.kotlin.sdk.types.InitializeResult import io.modelcontextprotocol.kotlin.sdk.types.IntegerSchema import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse import io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION @@ -52,6 +53,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.UntitledSingleSelectEnumSchema import io.modelcontextprotocol.kotlin.sdk.types.UrlElicitationRequiredException import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.TimeoutCancellationException +import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.joinAll @@ -71,6 +73,7 @@ import kotlin.test.assertIs import kotlin.test.assertNull import kotlin.test.assertTrue import kotlin.test.fail +import kotlin.time.Duration.Companion.seconds class ClientTest { @Test @@ -543,6 +546,15 @@ class ClientTest { val serverSession = serverSessionResult.await() + // Register the tap after createSession so it chains onto the session's subscription instead + // of pre-empting the handshake. + val cancelledOnWire = CompletableDeferred() + serverTransport.onMessage { message -> + if (message is JSONRPCNotification && message.method == Method.Defined.NotificationsCancelled.value) { + cancelledOnWire.complete(Unit) + } + } + serverSession.setRequestHandler(Method.Defined.ResourcesList) { _, _ -> // Simulate delay def.complete(Unit) @@ -568,6 +580,7 @@ class ClientTest { def.await() runCatching { job.cancel("Cancelled by test") } defCancel.await() + cancelledOnWire.await() defTimeOut.await() } @@ -606,17 +619,9 @@ class ClientTest { val serverSession = serverSessionResult.await() serverSession.setRequestHandler(Method.Defined.ResourcesList) { _, _ -> - // Simulate a delayed response - // Wait ~100ms unless canceled - try { - withTimeout(100L) { - // Just delay here, if timeout is 0 on the client side, this won't return in time - delay(100) - } - } catch (_: Exception) { - // If aborted, just rethrow or return early - } - ListResourcesResult(resources = emptyList()) + // Never respond: the handler runs on a real dispatcher while the 1 ms timeout runs on the + // virtual test clock, so a real-time delay could race it. Parking keeps the test deterministic. + awaitCancellation() } // Request with 1 msec timeout should fail immediately @@ -895,8 +900,8 @@ class ClientTest { ), ) - // Track notifications - var rootListChangedNotificationReceived = false + // The handler runs on Dispatchers.Default after concurrent dispatch is enabled; await the deferred. + val rootListChangedReceived = CompletableDeferred() val serverSessionResult = CompletableDeferred() @@ -915,16 +920,13 @@ class ClientTest { serverSession.setNotificationHandler( Method.Defined.NotificationsRootsListChanged, ) { - rootListChangedNotificationReceived = true + rootListChangedReceived.complete(Unit) CompletableDeferred(Unit) } client.sendRootsListChanged() - assertTrue( - rootListChangedNotificationReceived, - "Notification should be sent when sendRootsListChanged is called", - ) + withTimeout(5.seconds) { rootListChangedReceived.await() } } @Test diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/ProtocolConcurrencyIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/ProtocolConcurrencyIntegrationTest.kt new file mode 100644 index 0000000..d8d64ec --- /dev/null +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/ProtocolConcurrencyIntegrationTest.kt @@ -0,0 +1,306 @@ +package io.modelcontextprotocol.kotlin.sdk.integration + +import io.kotest.matchers.shouldBe +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.client.ClientOptions +import io.modelcontextprotocol.kotlin.sdk.server.Server +import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions +import io.modelcontextprotocol.kotlin.sdk.server.ServerSession +import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport +import io.modelcontextprotocol.kotlin.sdk.shared.currentRequestHandlerExtra +import io.modelcontextprotocol.kotlin.sdk.testing.ChannelTransport +import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest +import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult +import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest +import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageResult +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest +import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.InitializedNotification +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCError +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse +import io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION +import io.modelcontextprotocol.kotlin.sdk.types.ListRootsRequest +import io.modelcontextprotocol.kotlin.sdk.types.ListRootsResult +import io.modelcontextprotocol.kotlin.sdk.types.ListToolsRequest +import io.modelcontextprotocol.kotlin.sdk.types.ListToolsResult +import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.RequestId +import io.modelcontextprotocol.kotlin.sdk.types.Role +import io.modelcontextprotocol.kotlin.sdk.types.SamplingMessage +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.sdk.types.toJSON +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeout +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.CopyOnWriteArrayList +import java.util.concurrent.atomic.AtomicReference +import kotlin.coroutines.cancellation.CancellationException +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.time.Duration.Companion.seconds + +/** + * End-to-end tests for concurrent inbound dispatch enabled after the MCP handshake. + * + * Handlers run on a real [kotlinx.coroutines.Dispatchers.Default], so these use [runBlocking] with + * explicit [withTimeout] guards. A virtual test clock would false-fire timeouts against cross-thread + * real dispatchers. + */ +@OptIn(ExperimentalMcpApi::class) +class ProtocolConcurrencyIntegrationTest { + + private fun rootsClient(): Client = Client( + clientInfo = Implementation(name = "test client", version = "1.0"), + options = ClientOptions( + capabilities = ClientCapabilities(roots = ClientCapabilities.Roots(listChanged = null)), + ), + ) + + private fun toolsServer(): Server = Server( + serverInfo = Implementation(name = "test server", version = "1.0"), + options = ServerOptions( + capabilities = ServerCapabilities(tools = ServerCapabilities.Tools(listChanged = null)), + ), + ) + + // ChannelTransport delivers inbound messages as soon as its read loop starts, which is before + // start() marks it operational. Creating the session first keeps the client's initialize from + // landing on a transport that would reject the session's response. + private suspend fun connect( + client: Client, + clientTransport: ChannelTransport, + server: Server, + serverTransport: ChannelTransport, + ): ServerSession { + val serverSession = server.createSession(serverTransport) + client.connect(clientTransport) + return serverSession + } + + // The motivating deadlock over real serial read loops: a server tool handler makes a + // server-to-client request (roots/list) while still handling the client's tools/call. + @Test + fun `server tool handler can call roots list mid-handling without deadlock`() = runBlocking { + val client = rootsClient() + val server = toolsServer() + val (clientTransport, serverTransport) = ChannelTransport.createLinkedPair() + + val serverSession = connect(client, clientTransport, server, serverTransport) + + // Register the tools/call handler directly on the session so it can call back to the client + // mid-handling. Before concurrent dispatch this would deadlock: the server read loop is + // blocked by the tool handler and can never deliver the roots/list response. + serverSession.setRequestHandler(Method.Defined.ToolsCall) { _, _ -> + val roots = serverSession.request(ListRootsRequest()) + CallToolResult(content = listOf(TextContent(text = "roots=${roots.roots.size}"))) + } + + val result = withTimeout(10.seconds) { + client.callTool(CallToolRequest(CallToolRequestParams("needs-roots"))) + } + val text = (result as CallToolResult).content.filterIsInstance().single().text + text shouldBe "roots=0" + + client.close() + server.close() + } + + // Client-side mirror: a client sampling handler makes a client-to-server request (tools/list) + // while still handling the server's sampling/createMessage. + @Test + fun `client sampling handler can call the server mid-handling without deadlock`() = runBlocking { + val client = Client( + clientInfo = Implementation(name = "test client", version = "1.0"), + options = ClientOptions( + capabilities = ClientCapabilities(sampling = ClientCapabilities.Sampling()), + ), + ) + val server = toolsServer() + val (clientTransport, serverTransport) = ChannelTransport.createLinkedPair() + + client.setRequestHandler(Method.Defined.SamplingCreateMessage) { _, _ -> + val tools = client.request(ListToolsRequest()) + CreateMessageResult( + role = Role.Assistant, + content = TextContent(text = "tools=${tools.tools.size}"), + model = "test-model", + ) + } + + val serverSession = connect(client, clientTransport, server, serverTransport) + + val result = withTimeout(10.seconds) { + serverSession.request( + CreateMessageRequest( + CreateMessageRequestParams( + maxTokens = 100, + messages = listOf(SamplingMessage(role = Role.User, content = TextContent(text = "hi"))), + ), + ), + ) + } + val text = result.content.filterIsInstance().single().text + text shouldBe "tools=0" + + client.close() + server.close() + } + + // Nested-cancel cascade: cancelling the client's tools/call must reach the server tool handler + // over the wire and cascade into its nested roots/list request, cancelling the parked + // client-side roots handler. No tools/call response is ever sent. + @Test + fun `cancelling a tool call cascades into its nested roots list request`() = runBlocking { + val client = rootsClient() + val server = toolsServer() + val (clientTransport, serverTransport) = ChannelTransport.createLinkedPair() + + val capturedToolCallId = AtomicReference(null) + val toolHandlerCancelled = CompletableDeferred() + val rootsHandlerEntered = CompletableDeferred() + val rootsHandlerCancelled = CompletableDeferred() + val parkForever = CompletableDeferred() + val unexpectedToolCallResponse = CompletableDeferred() + + // Tap the server->client direction (client transport inbound) to prove no tools/call + // response is ever delivered once the call is cancelled. + clientTransport.onMessage { message -> + if (message is JSONRPCResponse && message.id == capturedToolCallId.get()) { + unexpectedToolCallResponse.complete(Unit) + } + } + + val serverSession = connect(client, clientTransport, server, serverTransport) + + // Client roots handler parks until cancelled and records the cancellation. + client.setRequestHandler(Method.Defined.RootsList) { _, _ -> + rootsHandlerEntered.complete(Unit) + try { + parkForever.await() + } catch (e: CancellationException) { + rootsHandlerCancelled.complete(e) + throw e + } + } + + // Tool handler makes a nested server->client request and records its own cancellation. + serverSession.setRequestHandler(Method.Defined.ToolsCall) { _, _ -> + val extra = currentRequestHandlerExtra()!! + capturedToolCallId.set(extra.requestId) + try { + extra.sendRequest(ListRootsRequest()) + } catch (e: CancellationException) { + toolHandlerCancelled.complete(e) + throw e + } + CallToolResult(content = listOf(TextContent(text = "unreachable"))) + } + + val call = launch { client.callTool(CallToolRequest(CallToolRequestParams("nested"))) } + + withTimeout(10.seconds) { rootsHandlerEntered.await() } + call.cancel(CancellationException("user aborted")) + + withTimeout(10.seconds) { toolHandlerCancelled.await() } + withTimeout(10.seconds) { rootsHandlerCancelled.await() } + + // The server suppresses the response for a cancelled request, so none is ever sent. + assertFalse( + unexpectedToolCallResponse.isCompleted, + "no tools/call response should be delivered after the call was cancelled", + ) + + client.close() + server.close() + } + + // Init gate plus context element in a high-level handler: initialize and tools/list are + // pipelined before notifications/initialized and answered serially in order. After the + // handshake a high-level addTool handler can observe currentRequestHandlerExtra(). + @Test + fun `pipelined initialize and tools list are processed serially then context element is available`(): Unit = + runBlocking { + val server = toolsServer() + server.addTool(name = "ctx-tool", description = "returns its request id") { + val extra = currentRequestHandlerExtra() + checkNotNull(extra) { "currentRequestHandlerExtra() must be non-null inside a tool handler" } + CallToolResult(content = listOf(TextContent(text = extra.requestId.asText()))) + } + + val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() + + val arrivalOrder = CopyOnWriteArrayList() + val pending = ConcurrentHashMap>() + clientTransport.onMessage { message -> + val id = when (message) { + is JSONRPCResponse -> message.id + is JSONRPCError -> message.id + else -> null + } + if (id != null) { + arrivalOrder.add(id) + pending[id]?.complete(message) + } + } + + // Drive the client side manually over the raw transport. + server.createSession(serverTransport) + + val initRequest = InitializeRequest( + InitializeRequestParams( + protocolVersion = LATEST_PROTOCOL_VERSION, + capabilities = ClientCapabilities(), + clientInfo = Implementation(name = "raw-client", version = "1.0"), + ), + ).toJSON() + val toolsListRequest = ListToolsRequest().toJSON() + + val initAnswered = pending.expect(initRequest.id) + val toolsListAnswered = pending.expect(toolsListRequest.id) + + // Pipelined before notifications/initialized: both handled in the serial dispatch phase. + clientTransport.send(initRequest) + clientTransport.send(toolsListRequest) + + withTimeout(10.seconds) { initAnswered.await() } + withTimeout(10.seconds) { toolsListAnswered.await() } + + // Deterministic serial ordering: initialize answered before tools/list. + assertEquals( + listOf(initRequest.id, toolsListRequest.id), + arrivalOrder.toList(), + "pipelined requests must be answered in arrival order", + ) + + // Complete the handshake, then call the high-level tool. + clientTransport.send(InitializedNotification().toJSON()) + + val toolCallRequest = CallToolRequest(CallToolRequestParams("ctx-tool")).toJSON() + val toolCallAnswered = pending.expect(toolCallRequest.id) + clientTransport.send(toolCallRequest) + + val toolCallResponse = withTimeout(10.seconds) { toolCallAnswered.await() } as JSONRPCResponse + val text = (toolCallResponse.result as CallToolResult).content.filterIsInstance().single().text + // The handler echoed its request id, proving currentRequestHandlerExtra() was available. + text shouldBe toolCallRequest.id.asText() + } + + private fun ConcurrentHashMap>.expect( + id: RequestId, + ): CompletableDeferred = CompletableDeferred().also { put(id, it) } + + private fun RequestId.asText(): String = when (this) { + is RequestId.StringId -> value + is RequestId.NumberId -> value.toString() + } +} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnectionLoggingTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnectionLoggingTest.kt index 81a3b41..a1cf797 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnectionLoggingTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnectionLoggingTest.kt @@ -1,5 +1,7 @@ package io.modelcontextprotocol.kotlin.sdk.server +import io.kotest.assertions.nondeterministic.continually +import io.kotest.assertions.nondeterministic.eventually import io.kotest.matchers.collections.shouldBeEmpty import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.shouldBe @@ -17,6 +19,9 @@ import kotlinx.serialization.json.JsonPrimitive import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.EnumSource +import java.util.concurrent.CopyOnWriteArrayList +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds class ClientConnectionLoggingTest : AbstractServerFeaturesTest() { @@ -56,7 +61,7 @@ class ClientConnectionLoggingTest : AbstractServerFeaturesTest() { @ParameterizedTest @EnumSource(LoggingLevel::class) fun `notification should filter logging messages below level`(minLevel: LoggingLevel): Unit = runBlocking { - val receivedMessages = mutableListOf() + val receivedMessages = CopyOnWriteArrayList() client.setNotificationHandler(Method.Defined.NotificationsMessage) { receivedMessages.add(it) CompletableDeferred(Unit) @@ -79,8 +84,10 @@ class ClientConnectionLoggingTest : AbstractServerFeaturesTest() { client.callTool(CallToolRequest(CallToolRequestParams("test-notification-filtered"))) + // Handlers dispatch on Dispatchers.Default after the handshake, so await the expected count + // before asserting order. val expectedLevels = LoggingLevel.entries.filter { it >= minLevel } - receivedMessages shouldHaveSize expectedLevels.size + eventually(5.seconds) { receivedMessages shouldHaveSize expectedLevels.size } receivedMessages.map { it.params.level } shouldBe expectedLevels } @@ -115,7 +122,7 @@ class ClientConnectionLoggingTest : AbstractServerFeaturesTest() { @ParameterizedTest @EnumSource(LoggingLevel::class) fun `sendLoggingMessage should filter messages below level`(minLevel: LoggingLevel): Unit = runBlocking { - val receivedMessages = mutableListOf() + val receivedMessages = CopyOnWriteArrayList() client.setNotificationHandler(Method.Defined.NotificationsMessage) { receivedMessages.add(it) CompletableDeferred(Unit) @@ -138,14 +145,16 @@ class ClientConnectionLoggingTest : AbstractServerFeaturesTest() { client.callTool(CallToolRequest(CallToolRequestParams("test-logging-level"))) + // Handlers dispatch on Dispatchers.Default after the handshake, so await the expected count + // before asserting order. val expectedLevels = LoggingLevel.entries.filter { it >= minLevel } - receivedMessages shouldHaveSize expectedLevels.size + eventually(5.seconds) { receivedMessages shouldHaveSize expectedLevels.size } receivedMessages.map { it.params.level } shouldBe expectedLevels } @Test fun `sendLoggingMessage should send no messages when level is set to highest`(): Unit = runBlocking { - val receivedMessages = mutableListOf() + val receivedMessages = CopyOnWriteArrayList() client.setNotificationHandler(Method.Defined.NotificationsMessage) { receivedMessages.add(it) CompletableDeferred(Unit) @@ -167,6 +176,7 @@ class ClientConnectionLoggingTest : AbstractServerFeaturesTest() { } client.callTool(CallToolRequest(CallToolRequestParams("test-logging-highest"))) - receivedMessages.shouldBeEmpty() + // Handlers dispatch asynchronously, so hold for a window to confirm nothing ever arrives. + continually(500.milliseconds) { receivedMessages.shouldBeEmpty() } } } diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSessionInitializeTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSessionInitializeTest.kt index 5031a9c..82cf91a 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSessionInitializeTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSessionInitializeTest.kt @@ -65,6 +65,8 @@ class ServerSessionInitializeTest { assertEquals("test-client", session.clientVersion?.name) } + // Both initialize requests arrive before notifications/initialized, in the serial dispatch + // phase, so processing and response order stay deterministic. @Test fun `should reject duplicate initialize request`() = runTest { val session = createSession() diff --git a/kotlin-sdk-client/api/kotlin-sdk-client.api b/kotlin-sdk-client/api/kotlin-sdk-client.api index 1493a30..d856f5b 100644 --- a/kotlin-sdk-client/api/kotlin-sdk-client.api +++ b/kotlin-sdk-client/api/kotlin-sdk-client.api @@ -52,8 +52,8 @@ public final class io/modelcontextprotocol/kotlin/sdk/client/ClientKt { public final class io/modelcontextprotocol/kotlin/sdk/client/ClientOptions : io/modelcontextprotocol/kotlin/sdk/shared/ProtocolOptions { public fun ()V - public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;Z)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;ZLkotlin/coroutines/CoroutineContext;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;ZLkotlin/coroutines/CoroutineContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getCapabilities ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities; } diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt index 59f2b7e..3352104 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt @@ -73,11 +73,13 @@ import kotlinx.collections.immutable.minus import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.toPersistentSet import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.Dispatchers import kotlinx.serialization.SerializationException import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.buildJsonArray +import kotlin.coroutines.CoroutineContext import kotlin.coroutines.cancellation.CancellationException private val logger = KotlinLogging.logger {} @@ -87,11 +89,16 @@ private val logger = KotlinLogging.logger {} * * @property capabilities The capabilities this client supports. * @param enforceStrictCapabilities Whether to strictly enforce capabilities when interacting with the server. + * @param handlerCoroutineContext Coroutine context for inbound handlers. See [ProtocolOptions.handlerCoroutineContext]. */ public class ClientOptions( public val capabilities: ClientCapabilities = ClientCapabilities(), enforceStrictCapabilities: Boolean = true, -) : ProtocolOptions(enforceStrictCapabilities = enforceStrictCapabilities) + handlerCoroutineContext: CoroutineContext = Dispatchers.Default, +) : ProtocolOptions( + enforceStrictCapabilities = enforceStrictCapabilities, + handlerCoroutineContext = handlerCoroutineContext, +) /** * Initializes and connects an MCP client using the provided clientInfo [Implementation], client options, @@ -216,6 +223,7 @@ public open class Client(private val clientInfo: Implementation, options: Client serverInstructions = result.instructions notification(InitializedNotification()) + enableConcurrentDispatch() } catch (error: Throwable) { logger.error(error) { "Failed to initialize client: ${error.message}" } close() diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index 1935e9a..28d6737 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -59,6 +59,7 @@ public abstract class io/modelcontextprotocol/kotlin/sdk/shared/Protocol { public abstract fun assertRequestHandlerCapability (Lio/modelcontextprotocol/kotlin/sdk/types/Method;)V public final fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun connect (Lio/modelcontextprotocol/kotlin/sdk/shared/Transport;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + protected final fun enableConcurrentDispatch ()V public final fun getFallbackNotificationHandler ()Lkotlin/jvm/functions/Function2; public final fun getFallbackRequestHandler ()Lkotlin/jvm/functions/Function3; public final fun getNotificationHandlers ()Ljava/util/Map; @@ -71,6 +72,7 @@ public abstract class io/modelcontextprotocol/kotlin/sdk/shared/Protocol { 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 + protected fun onInitializedNotification ()V public final fun removeNotificationHandler (Lio/modelcontextprotocol/kotlin/sdk/types/Method;)V public final fun removeRequestHandler (Lio/modelcontextprotocol/kotlin/sdk/types/Method;)V public final fun request (Lio/modelcontextprotocol/kotlin/sdk/types/Request;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -84,15 +86,18 @@ public abstract class io/modelcontextprotocol/kotlin/sdk/shared/Protocol { public final class io/modelcontextprotocol/kotlin/sdk/shared/ProtocolKt { public static final field IMPLEMENTATION_NAME Ljava/lang/String; + public static final fun currentRequestHandlerExtra (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun getDEFAULT_REQUEST_TIMEOUT ()J } public class io/modelcontextprotocol/kotlin/sdk/shared/ProtocolOptions { - public synthetic fun (ZJILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun (ZJLkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (ZJLkotlin/coroutines/CoroutineContext;IIILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (ZJLkotlin/coroutines/CoroutineContext;IILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getEnforceStrictCapabilities ()Z + public final fun getHandlerCoroutineContext ()Lkotlin/coroutines/CoroutineContext; public final fun getTimeout-UwyO8pc ()J public final fun setEnforceStrictCapabilities (Z)V + public final fun setHandlerCoroutineContext (Lkotlin/coroutines/CoroutineContext;)V public final fun setTimeout-LRDsOJo (J)V } @@ -107,8 +112,16 @@ public final class io/modelcontextprotocol/kotlin/sdk/shared/ReadBufferKt { public static final fun serializeMessage (Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage;)Ljava/lang/String; } -public final class io/modelcontextprotocol/kotlin/sdk/shared/RequestHandlerExtra { - public fun ()V +public final class io/modelcontextprotocol/kotlin/sdk/shared/RequestHandlerExtra : kotlin/coroutines/AbstractCoroutineContextElement { + public static final field Key Lio/modelcontextprotocol/kotlin/sdk/shared/RequestHandlerExtra$Key; + public final fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public final fun getRequestId ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestId; + public final fun sendNotification (Lio/modelcontextprotocol/kotlin/sdk/types/Notification;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun sendRequest (Lio/modelcontextprotocol/kotlin/sdk/types/Request;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun sendRequest$default (Lio/modelcontextprotocol/kotlin/sdk/shared/RequestHandlerExtra;Lio/modelcontextprotocol/kotlin/sdk/types/Request;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; +} + +public final class io/modelcontextprotocol/kotlin/sdk/shared/RequestHandlerExtra$Key : kotlin/coroutines/CoroutineContext$Key { } public final class io/modelcontextprotocol/kotlin/sdk/shared/RequestOptions : io/modelcontextprotocol/kotlin/sdk/shared/TransportSendOptions { diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt index 50dc356..3515b4a 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt @@ -27,15 +27,33 @@ import kotlinx.atomicfu.AtomicRef import kotlinx.atomicfu.atomic import kotlinx.atomicfu.getAndUpdate import kotlinx.atomicfu.update +import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.PersistentMap +import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentMapOf import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Deferred -import kotlinx.coroutines.TimeoutCancellationException -import kotlinx.coroutines.withTimeout +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.NonCancellable +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.job +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Semaphore +import kotlinx.coroutines.sync.withPermit +import kotlinx.coroutines.withContext +import kotlinx.coroutines.withTimeoutOrNull import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.encodeToJsonElement +import kotlin.coroutines.AbstractCoroutineContextElement +import kotlin.coroutines.ContinuationInterceptor +import kotlin.coroutines.CoroutineContext import kotlin.coroutines.cancellation.CancellationException import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds @@ -53,17 +71,26 @@ public typealias ProgressCallback = (Progress) -> Unit /** * Additional initialization options. * - * @property enforceStrictCapabilities whether to restrict emitted requests to only those that the - * remote side has indicated it can handle through its advertised capabilities. - * This does NOT affect checking of _local_ side capabilities, as it is considered a logic error - * to mis-specify those. - * Currently defaults to `false` for backwards compatibility with SDK versions that did not advertise - * capabilities correctly; in the future, this will default to `true`. + * [handlerCoroutineContext] is read once when [Protocol.connect] attaches a transport and stays + * fixed until the next [Protocol.connect]. + * + * @property enforceStrictCapabilities whether to restrict emitted requests to only those the remote + * side advertised support for. Local-side capability checks are unaffected. Defaults to `false` for + * backward compatibility with SDK versions that did not advertise capabilities correctly. * @property timeout default timeout for outgoing requests + * @property handlerCoroutineContext coroutine context used to run inbound request and notification + * handlers once initialization has completed. Must contain a real dispatching + * [kotlin.coroutines.ContinuationInterceptor]. `Dispatchers.Unconfined` and unconfined test + * dispatchers are unsupported, since handler resumptions would then run on the transport read loop + * and reintroduce head-of-line blocking. Any [kotlinx.coroutines.Job] in this context is ignored. */ public open class ProtocolOptions( public var enforceStrictCapabilities: Boolean = false, public var timeout: Duration = DEFAULT_REQUEST_TIMEOUT, + public var handlerCoroutineContext: CoroutineContext = Dispatchers.Default, + // Internal handler-concurrency bounds, read once at connect and not part of the public surface. + internal var maxConcurrentHandlers: Int = DEFAULT_MAX_CONCURRENT_HANDLERS, + internal var maxInFlightHandlers: Int = DEFAULT_MAX_IN_FLIGHT_HANDLERS, ) /** @@ -71,6 +98,12 @@ public open class ProtocolOptions( */ public val DEFAULT_REQUEST_TIMEOUT: Duration = 60.seconds +/** Default cap on concurrently executing inbound handlers per connection. */ +internal const val DEFAULT_MAX_CONCURRENT_HANDLERS: Int = 64 + +/** Default cap on launched-but-not-completed inbound handlers per connection. */ +internal const val DEFAULT_MAX_IN_FLIGHT_HANDLERS: Int = 256 + /** * Options that can be given per request. * @@ -121,7 +154,7 @@ public class RequestOptions( override fun hashCode(): Int { var result = super.hashCode() - result = 31 * result + (onProgress?.hashCode() ?: 0) + result = 31 * result + onProgress.hashCode() result = 31 * result + timeout.hashCode() return result } @@ -132,11 +165,70 @@ public class RequestOptions( /** * Extra data given to request handlers. + * + * One instance exists per in-flight inbound request. It is passed as the second parameter to + * low-level handlers registered via [Protocol.setRequestHandler] **and** installed into the + * handler coroutine's [CoroutineContext], so code nested anywhere under the handler can reach it + * via [currentRequestHandlerExtra] without parameter plumbing. + * + * @property requestId the JSON-RPC id of the request being handled + * @property method the request's method */ -public class RequestHandlerExtra +public class RequestHandlerExtra internal constructor( + public val requestId: RequestId, + public val method: Method, + private val protocol: Protocol, + internal val capturedTransport: Transport, +) : AbstractCoroutineContextElement(Key) { + + /** Context key for retrieving the extra from a handler coroutine's context. */ + public companion object Key : CoroutineContext.Key + + /** + * Sends a notification tagged with `relatedRequestId = `[requestId], letting transports + * associate it with the request being handled (e.g. route it onto the request's SSE stream). + */ + public suspend fun sendNotification(notification: Notification) { + protocol.notification(notification, relatedRequestId = requestId) + } + + /** + * Sends a request to the peer tagged with `relatedRequestId = `[requestId]. + * + * Mirrors [Protocol.request]; cancellation of the handler coroutine propagates into this call. + */ + public suspend fun sendRequest(request: Request, options: RequestOptions? = null): T { + val base = options ?: RequestOptions() + return protocol.request( + request, + RequestOptions( + relatedRequestId = requestId, + resumptionToken = base.resumptionToken, + onResumptionToken = base.onResumptionToken, + onProgress = base.onProgress, + timeout = base.timeout, + ), + ) + } +} + +/** The [RequestHandlerExtra] of the MCP request being handled by the current coroutine, or `null` outside a handler. */ +public suspend fun currentRequestHandlerExtra(): RequestHandlerExtra? = currentCoroutineContext()[RequestHandlerExtra] internal val COMPLETED = CompletableDeferred(Unit).also { it.complete(Unit) } +/** Cap on how many recently-cancelled outbound request ids are remembered to quiet late responses/progress. */ +private const val CANCELLED_REQUEST_IDS_REMEMBERED = 256 + +// Bypass set: exempt from both bound tiers so the connection cannot self-block. +// Responses are not listed — they are handled inline before dispatch. +private val CONTROL_METHODS: Set = setOf( + Method.Defined.Ping.value, + Method.Defined.NotificationsCancelled.value, + Method.Defined.NotificationsProgress.value, + Method.Defined.NotificationsInitialized.value, +) + /** * Implements MCP protocol framing on top of a pluggable transport, including * features like request/response linking, notifications, and progress. @@ -144,9 +236,11 @@ internal val COMPLETED = CompletableDeferred(Unit).also { it.complete(Unit) } * @property options protocol-level configuration; `null` falls back to defaults */ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptions?) { + private val connectionRef: AtomicRef = atomic(null) + /** The active transport, or `null` if not connected. */ - public var transport: Transport? = null - private set + public val transport: Transport? + get() = connectionRef.value?.transport private val _requestHandlers: AtomicRef RequestResult?>> = @@ -184,6 +278,21 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio public val progressHandlers: Map get() = _progressHandlers.value + private val recentlyCancelledRequestIds: AtomicRef> = + atomic(persistentListOf()) + + /** Remembers a locally-cancelled outbound request id so its late response/progress is quieted. */ + private fun rememberCancelledRequestId(id: RequestId) { + recentlyCancelledRequestIds.update { current -> + val appended = current.adding(id) + if (appended.size > CANCELLED_REQUEST_IDS_REMEMBERED) appended.removingAt(0) else appended + } + } + + /** Whether [id] was recently cancelled locally (see [rememberCancelledRequestId]). */ + private fun isRecentlyCancelled(id: RequestId?): Boolean = + id != null && recentlyCancelledRequestIds.value.contains(id) + /** * Callback for when the connection is closed for any reason. * @@ -213,6 +322,11 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio public var fallbackNotificationHandler: (suspend (notification: JSONRPCNotification) -> Unit)? = null init { + setNotificationHandler(Method.Defined.NotificationsCancelled) { notification -> + handleCancelledNotification(notification) + COMPLETED + } + setNotificationHandler(Method.Defined.NotificationsProgress) { notification -> onProgress(notification) COMPLETED @@ -223,15 +337,64 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio } } + /** + * Enables concurrent dispatch of inbound messages on the current connection. + * + * Called by subclasses when the MCP initialization phase completes (the client after the + * initialize exchange; the server session on receiving `notifications/initialized`). Until + * then, inbound messages are handled inline in arrival order. Reset by [connect]. + */ + protected fun enableConcurrentDispatch() { + val connection = connectionRef.value ?: return + connection.concurrentDispatchEnabled.value = true + } + + /** + * Router hook invoked whenever `notifications/initialized` is received, before handler lookup. + * + * Runs regardless of which notification handler is registered for the method, so replacing the + * handler via [setNotificationHandler] cannot disable subclass initialization logic. + */ + protected open fun onInitializedNotification() {} + /** * Attaches to the given transport, starts it, and starts listening for messages. * - * The Protocol object assumes ownership of the Transport, replacing any callbacks that have already been set, and expects that it is the only user of the Transport instance going forward. + * The Protocol object assumes ownership of the Transport, replacing any callbacks that have + * already been set, and expects that it is the only user of the Transport instance going forward. + * + * @throws IllegalStateException if this Protocol is already connected; call [close] first. */ public open suspend fun connect(transport: Transport) { - this.transport = transport + val maxConcurrent = options?.maxConcurrentHandlers ?: DEFAULT_MAX_CONCURRENT_HANDLERS + val maxInFlight = options?.maxInFlightHandlers ?: DEFAULT_MAX_IN_FLIGHT_HANDLERS + require(maxConcurrent > 0) { "maxConcurrentHandlers must be positive, but was $maxConcurrent" } + require(maxInFlight >= maxConcurrent) { + "maxInFlightHandlers ($maxInFlight) must be >= maxConcurrentHandlers ($maxConcurrent)" + } + val handlerContext = (options?.handlerCoroutineContext ?: Dispatchers.Default).minusKey(Job) + val interceptor = handlerContext[ContinuationInterceptor] + if (interceptor === Dispatchers.Unconfined || + interceptor?.let { it::class.simpleName?.contains("Unconfined") } == true + ) { + logger.warn { + "handlerCoroutineContext uses an unconfined dispatcher ($interceptor); " + + "handler resumptions will run on the transport read loop and may block unrelated messages" + } + } + val connection = Connection( + transport = transport, + handlerScope = CoroutineScope(SupervisorJob() + handlerContext + CoroutineName("McpProtocol")), + executionSemaphore = Semaphore(maxConcurrent), + maxInFlightHandlers = maxInFlight, + ) + if (!connectionRef.compareAndSet(null, connection)) { + connection.handlerScope.cancel() + error("Protocol is already connected; close() the current connection before connecting a new transport") + } + transport.onClose { - doClose() + doClose(connection) } transport.onError { @@ -239,24 +402,42 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio } transport.onMessage { message -> + if (connectionRef.value !== connection) { + logger.trace { "Dropping message received after close: ${message::class.simpleName}" } + return@onMessage + } when (message) { is JSONRPCResponse -> onResponse(message, null) - is JSONRPCRequest -> onRequest(message) - is JSONRPCNotification -> onNotification(message) + is JSONRPCRequest -> dispatchRequest(connection, message) + is JSONRPCNotification -> dispatchNotification(connection, message) is JSONRPCError -> onResponse(null, message) is JSONRPCEmptyMessage -> Unit } } logger.info { "Starting transport" } - transport.start() + try { + transport.start() + } catch (cause: Throwable) { + // Roll back so the Protocol can be reconnected after a failed start; idempotent with + // a transport-initiated doClose (both CAS on the same connection). + if (connectionRef.compareAndSet(connection, null)) { + connection.handlerScope.cancel() + } + throw cause + } } - private fun doClose() { - val handlersToNotify = _responseHandlers.value.values.toList() - _responseHandlers.getAndSet(persistentMapOf()) + private fun doClose(connection: Connection) { + // A stale onClose from a previous transport must not tear down the successor connection. + if (!connectionRef.compareAndSet(connection, null)) { + logger.trace { "Ignoring close signal from a stale transport" } + return + } + val handlersToNotify = _responseHandlers.getAndSet(persistentMapOf()).values.toList() _progressHandlers.getAndSet(persistentMapOf()) - transport = null + connection.inFlightRequestJobs.getAndSet(persistentMapOf()) + connection.handlerScope.cancel() onClose() val error = McpException(RPCError.ErrorCode.CONNECTION_CLOSED, "Connection closed") @@ -265,6 +446,110 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio } } + private suspend fun dispatchRequest(connection: Connection, request: JSONRPCRequest) { + if (!connection.concurrentDispatchEnabled.value) { + // Serial phase (before the peer completes MCP initialization): inline, in arrival + // order — the delivering coroutine awaits the handler. + onRequest(request, connection, trackCancellation = false) + return + } + val bypass = request.method in CONTROL_METHODS + if (!bypass && !tryAdmit(connection)) { + rejectOverloadedRequest(connection, request) + return + } + // UNDISPATCHED starts the handler synchronously in the delivering coroutine, preserving + // arrival order, then moves to handlerCoroutineContext at its first suspension. + connection.handlerScope.launch(start = CoroutineStart.UNDISPATCHED) { + // Register before any suspension point so a following notifications/cancelled can find + // and cancel even a handler still parked on admission. + registerInFlight(connection, request.id, coroutineContext.job) + try { + if (bypass) { + onRequest(request, connection, trackCancellation = true) + } else { + connection.executionSemaphore.withPermit { + onRequest(request, connection, trackCancellation = true) + } + } + } finally { + if (!bypass) connection.inFlightCount.decrementAndGet() + } + } + } + + private suspend fun dispatchNotification(connection: Connection, notification: JSONRPCNotification) { + if (notification.method == Method.Defined.NotificationsInitialized.value) { + onInitializedNotification() + } + if (!connection.concurrentDispatchEnabled.value) { + onNotification(notification) + return + } + val bypass = notification.method in CONTROL_METHODS + if (!bypass && !tryAdmit(connection)) { + val dropped = IllegalStateException( + "Dropped notification ${notification.method}: too many in-flight messages", + ) + logger.warn { dropped.message } + onError(dropped) + return + } + connection.handlerScope.launch(start = CoroutineStart.UNDISPATCHED) { + try { + if (bypass) { + onNotification(notification) + } else { + connection.executionSemaphore.withPermit { onNotification(notification) } + } + } finally { + if (!bypass) connection.inFlightCount.decrementAndGet() + } + } + } + + private fun tryAdmit(connection: Connection): Boolean { + while (true) { + val current = connection.inFlightCount.value + if (current >= connection.maxInFlightHandlers) return false + if (connection.inFlightCount.compareAndSet(current, current + 1)) return true + } + } + + private suspend fun rejectOverloadedRequest(connection: Connection, request: JSONRPCRequest) { + logger.warn { + "Rejecting request ${request.method} (id: ${request.id}): " + + "in-flight handler limit (${connection.maxInFlightHandlers}) reached" + } + try { + connection.transport.send( + JSONRPCError( + id = request.id, + error = RPCError( + code = RPCError.ErrorCode.INTERNAL_ERROR, + message = "Server is busy: too many in-flight messages", + ), + ), + ) + } catch (e: CancellationException) { + throw e + } catch (cause: Throwable) { + logger.error(cause) { "Failed to send busy rejection for request id: ${request.id}" } + onError(cause) + } + } + + private fun handleCancelledNotification(notification: CancelledNotification) { + val connection = connectionRef.value ?: return + val requestId = notification.params.requestId + val job = connection.inFlightRequestJobs.value[requestId] + if (job == null) { + logger.trace { "Ignoring cancellation for unknown or already-completed request: $requestId" } + return + } + job.cancel("Cancelled by peer: ${notification.params.reason ?: "unknown"}") + } + private suspend fun onNotification(notification: JSONRPCNotification) { logger.trace { "Received notification: ${notification.method}" } @@ -284,15 +569,16 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio } } - private suspend fun onRequest(request: JSONRPCRequest) { + private suspend fun onRequest(request: JSONRPCRequest, connection: Connection, trackCancellation: Boolean) { logger.trace { "Received request: ${request.method} (id: ${request.id})" } val handler = requestHandlers[request.method] ?: fallbackRequestHandler + val capturedTransport = connection.transport if (handler === null) { logger.trace { "No handler found for request: ${request.method}" } try { - transport?.send( + capturedTransport.send( JSONRPCError( id = request.id, error = RPCError( @@ -310,36 +596,77 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio return } - try { - val result = handler(request, RequestHandlerExtra()) - logger.trace { "Request handled successfully: ${request.method} (id: ${request.id})" } + val extra = RequestHandlerExtra( + requestId = request.id, + method = Method.from(request.method), + protocol = this, + capturedTransport = capturedTransport, + ) - transport?.send( - JSONRPCResponse( - id = request.id, - result = result ?: EmptyResult(), - ), - ) + // Registration in the in-flight registry already happened in dispatchRequest's launch + // block (synchronously, pre-suspension). Here we only need the job for suppression checks. + val handlerJob = if (trackCancellation) currentCoroutineContext().job else null + + withContext(extra) { + try { + val result = handler(request, extra) + logger.trace { "Request handled successfully: ${request.method} (id: ${request.id})" } + if (handlerJob?.isCancelled == true) { + logger.trace { "Suppressing response for request cancelled by peer (id: ${request.id})" } + return@withContext + } + capturedTransport.send( + JSONRPCResponse( + id = request.id, + result = result ?: EmptyResult(), + ), + ) + } catch (e: CancellationException) { + // Intentional exception to the always-rethrow-CE convention, only at this boundary. + if (handlerJob == null) throw e // serial phase: inline dispatch is not response-suppressed + if (handlerJob.isCancelled) throw e // genuine peer or close cancel: suppress the response + // CE escaped a live handler job (e.g. a leaked inner withTimeout). Answer + // INTERNAL_ERROR so the peer does not hang until its own timeout. + respondWithError(capturedTransport, request, e) + } catch (cause: Throwable) { + if (handlerJob?.isCancelled == true) { + logger.trace { "Suppressing error response for request cancelled by peer (id: ${request.id})" } + return@withContext + } + respondWithError(capturedTransport, request, cause) + } + } + } + + private fun registerInFlight(connection: Connection, id: RequestId, job: Job) { + val previous = connection.inFlightRequestJobs.getAndUpdate { current -> current.putting(id, job) }[id] + if (previous != null) { + logger.warn { "Duplicate in-flight request id $id; replacing the previous handler job" } + } + job.invokeOnCompletion { + // Identity guard: never evict a successor job registered under a reused id. + connection.inFlightRequestJobs.update { current -> + if (current[id] === job) current.removing(id) else current + } + } + } + + private suspend fun respondWithError(transport: Transport, request: JSONRPCRequest, cause: Throwable) { + logger.error(cause) { "Error handling request: ${request.method} (id: ${request.id})" } + try { + val rpcError = if (cause is McpException) { + RPCError(code = cause.code, message = cause.message.orEmpty(), data = cause.data) + } else { + RPCError(code = RPCError.ErrorCode.INTERNAL_ERROR, message = cause.message ?: "Internal error") + } + transport.send(JSONRPCError(id = request.id, error = rpcError)) } catch (e: CancellationException) { throw e - } catch (cause: Throwable) { - logger.error(cause) { "Error handling request: ${request.method} (id: ${request.id})" } - - try { - val rpcError = if (cause is McpException) { - RPCError(code = cause.code, message = cause.message.orEmpty(), data = cause.data) - } else { - RPCError(code = RPCError.ErrorCode.INTERNAL_ERROR, message = cause.message ?: "Internal error") - } - transport?.send(JSONRPCError(id = request.id, error = rpcError)) - } catch (e: CancellationException) { - throw e - } catch (sendError: Throwable) { - logger.error(sendError) { - "Failed to send error response for request: ${request.method} (id: ${request.id})" - } - // Optionally implement fallback behavior here + } catch (sendError: Throwable) { + logger.error(sendError) { + "Failed to send error response for request: ${request.method} (id: ${request.id})" } + onError(sendError) } } @@ -354,6 +681,10 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio val handler = _progressHandlers.value[progressToken] if (handler == null) { + if (isRecentlyCancelled(progressToken)) { + logger.trace { "Ignoring progress for a locally cancelled request: $progressToken" } + return + } val error = Error( "Received a progress notification for an unknown token: ${McpJson.encodeToString(notification)}", ) @@ -370,7 +701,7 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio val oldResponseHandlers = _responseHandlers.getAndUpdate { current -> if (messageId != null && messageId in current) { - current.remove(messageId) + current.removing(messageId) } else { current } @@ -379,8 +710,12 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio val handler = oldResponseHandlers[messageId] if (handler != null) { - messageId?.let { msg -> _progressHandlers.update { it.remove(msg) } } + messageId?.let { msg -> _progressHandlers.update { it.removing(msg) } } } else { + if (isRecentlyCancelled(messageId)) { + logger.trace { "Ignoring response for a locally cancelled request: $messageId" } + return + } onError( IllegalStateException( "Received a response for an unknown message ID: ${McpJson.encodeToString(error ?: response)}", @@ -438,7 +773,8 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio public suspend fun request(request: Request, options: RequestOptions? = null): T { logger.trace { "Sending request: ${request.method}" } val result = CompletableDeferred() - val transport = transport ?: error("Not connected") + val connection = connectionRef.value ?: error("Not connected") + val transport = connection.transport if (this@Protocol.options?.enforceStrictCapabilities == true) { assertCapabilityForMethod(request.method) @@ -448,7 +784,7 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio options?.onProgress?.let { progressHandler -> logger.trace { "Registering progress handler for request id: $id" } _progressHandlers.update { current -> - current.put(id, progressHandler) + current.putting(id, progressHandler) } val paramsObject = (this.params as? JsonObject) ?: JsonObject(emptyMap()) @@ -467,10 +803,10 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio val jsonRpcRequestId = jsonRpcRequest.id _responseHandlers.update { current -> - current.put(jsonRpcRequestId) { response, error -> + current.putting(jsonRpcRequestId) { response, error -> if (error != null) { result.completeExceptionally(error) - return@put + return@putting } try { @@ -482,42 +818,84 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio } } - val cancel: suspend (Throwable) -> Unit = { reason: Throwable -> - _responseHandlers.update { current -> current.remove(jsonRpcRequestId) } - _progressHandlers.update { current -> current.remove(jsonRpcRequestId) } + if (connectionRef.value !== connection) { + // Connection closed or replaced during registration; nothing would complete this handler. + _responseHandlers.update { it.removing(jsonRpcRequestId) } + _progressHandlers.update { it.removing(jsonRpcRequestId) } + throw McpException(RPCError.ErrorCode.CONNECTION_CLOSED, "Connection closed") + } - val notification = CancelledNotification( - params = CancelledNotificationParams( - requestId = jsonRpcRequestId, - reason = reason.message ?: "Unknown", - ), - ) + val cancelPending: suspend (reason: Throwable, notifyPeer: Boolean) -> Unit = { reason, notifyPeer -> + // Remember before removing: a racing onResponse/onProgress that sees the handler gone + // must also see the id, or it reports a late message as unknown. Order is load-bearing. + rememberCancelledRequestId(jsonRpcRequestId) + _responseHandlers.update { current -> current.removing(jsonRpcRequestId) } + _progressHandlers.update { current -> current.removing(jsonRpcRequestId) } - val jsonRpcNotification = notification.toJSON() - - transport.send(jsonRpcNotification, options) + if (notifyPeer) { + val notification = CancelledNotification( + params = CancelledNotificationParams( + requestId = jsonRpcRequestId, + reason = reason.message ?: "Unknown", + ), + ) + transport.send(notification.toJSON(), options) + } result.completeExceptionally(reason) } + // The MCP spec forbids cancelling `initialize`; local cleanup still runs, + // but no notifications/cancelled goes on the wire (timeout and cancel paths alike). + val notifyPeerOnCancel = request.method != Method.Defined.Initialize + val timeout = options?.timeout ?: DEFAULT_REQUEST_TIMEOUT try { - withTimeout(timeout) { + val response = withTimeoutOrNull(timeout) { logger.trace { "Sending request message with id: $jsonRpcRequestId" } - this@Protocol.transport?.send(jsonRpcRequest, options) + transport.send(jsonRpcRequest, options) + result.await() } - return result.await() - } catch (cause: TimeoutCancellationException) { - logger.error { "Request timed out after ${timeout.inWholeMilliseconds}ms: ${request.method}" } - cancel( - McpException( + if (response == null) { + // Our own request timeout expired. An outer withTimeout's cancellation propagates + // through withTimeoutOrNull instead of returning null and is handled by the catch below. + logger.error { "Request timed out after ${timeout.inWholeMilliseconds}ms: ${request.method}" } + val timeoutError = McpException( code = RPCError.ErrorCode.REQUEST_TIMEOUT, message = "Request timed out", data = JsonObject(mutableMapOf("timeout" to JsonPrimitive(timeout.inWholeMilliseconds))), - ), - ) - result.cancel(cause) + ) + withContext(NonCancellable) { + try { + cancelPending(timeoutError, notifyPeerOnCancel) + } catch (e: Throwable) { + logger.warn(e) { "Failed to notify peer about timed-out request" } + onError(e) + } + } + result.cancel() + throw timeoutError + } + return response + } catch (cause: CancellationException) { + // Plain caller cancellation, or an outer withTimeout that propagated through + // withTimeoutOrNull (only our own deadline returns null there). NonCancellable so the + // cancellation notification still goes out even though the caller's job is cancelled. + // A secondary send failure must never replace the original CancellationException. + withContext(NonCancellable) { + try { + cancelPending(cause, notifyPeerOnCancel) + } catch (e: Throwable) { + logger.warn(e) { "Failed to notify peer about cancelled request" } + onError(e) + } + } throw cause + } finally { + // Backstop for the failed-send path. The success, timeout and cancellation paths already + // removed these handlers, so this only bites when the outbound send failed. + _responseHandlers.update { it.removing(jsonRpcRequestId) } + _progressHandlers.update { it.removing(jsonRpcRequestId) } } } @@ -538,6 +916,11 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio * Registers a handler to invoke when this protocol object receives a request with the given method. * * Note that this will replace any previous request handler for the same method. + * + * Handlers may run concurrently after initialization and are cancelled cooperatively, so always + * re-throw [CancellationException]. Replacing a built-in control-method handler (`ping`, + * `notifications/cancelled`, `notifications/progress`, `notifications/initialized`) with slow + * code degrades connection liveness. */ public inline fun setRequestHandler( method: Method, @@ -556,7 +939,7 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio val wrapped = wrapRequestHandler(method, block) _requestHandlers.update { current -> - current.put(method.value) { jSONRPCRequest, extraHandler -> + current.putting(method.value) { jSONRPCRequest, extraHandler -> val request = jSONRPCRequest.fromJSON() val response = wrapped(request as T, extraHandler) response @@ -582,17 +965,23 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio * Removes the request handler for the given method. */ public fun removeRequestHandler(method: Method) { - _requestHandlers.update { current -> current.remove(method.value) } + _requestHandlers.update { current -> current.removing(method.value) } } /** * Registers a handler to invoke when this protocol object receives a notification with the given method. * * Note that this will replace any previous notification handler for the same method. + * + * Handlers may run concurrently after initialization and are cancelled cooperatively, so always + * re-throw [CancellationException]. Replacing a built-in control-method handler (`ping`, + * `notifications/cancelled`, `notifications/progress`, `notifications/initialized`) with slow + * code degrades connection liveness. Replacing `notifications/cancelled` in particular disables + * inbound request cancellation entirely, since that handler is what cancels the running job. */ public fun setNotificationHandler(method: Method, handler: (notification: T) -> Deferred) { _notificationHandlers.update { current -> - current.put(method.value) { + current.putting(method.value) { @Suppress("UNCHECKED_CAST") handler(it.fromJSON() as T) } @@ -603,6 +992,22 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio * Removes the notification handler for the given method. */ public fun removeNotificationHandler(method: Method) { - _notificationHandlers.update { current -> current.remove(method.value) } + _notificationHandlers.update { current -> current.removing(method.value) } + } + + private class Connection( + val transport: Transport, + val handlerScope: CoroutineScope, + val executionSemaphore: Semaphore, + val maxInFlightHandlers: Int, + ) { + /** Init gate: inbound dispatch is inline/serial until this flips. */ + val concurrentDispatchEnabled = atomic(false) + + /** Admission tier: launched-but-not-completed handler jobs (running + parked). */ + val inFlightCount = atomic(0) + + /** In-flight request handler jobs, for `notifications/cancelled`. */ + val inFlightRequestJobs: AtomicRef> = atomic(persistentMapOf()) } } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/methods.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/methods.kt index 19f4da5..90403e8 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/methods.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/methods.kt @@ -53,4 +53,13 @@ public sealed interface Method { */ @Serializable public data class Custom(override val value: String) : Method + + public companion object { + private val definedByValue: Map by lazy { + Defined.entries.associateBy { it.value } + } + + /** Resolves a wire method string to a [Defined] entry when known, [Custom] otherwise. */ + internal fun from(value: String): Method = definedByValue[value] ?: Custom(value) + } } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt index ab85e6d..6eab14a 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt @@ -59,18 +59,11 @@ internal object MethodSerializer : KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("io.modelcontextprotocol.kotlin.sdk.types.Method", PrimitiveKind.STRING) - private val definedMethods: Map by lazy { - Method.Defined.entries.associateBy { it.value } - } - override fun serialize(encoder: Encoder, value: Method) { encoder.encodeString(value.value) } - override fun deserialize(decoder: Decoder): Method { - val decodedString = decoder.decodeString() - return definedMethods[decodedString] ?: Method.Custom(decodedString) - } + override fun deserialize(decoder: Decoder): Method = Method.from(decoder.decodeString()) } // ============================================================================ diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolConcurrencyTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolConcurrencyTest.kt new file mode 100644 index 0000000..b3c95ac --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolConcurrencyTest.kt @@ -0,0 +1,524 @@ +package io.modelcontextprotocol.kotlin.sdk.shared + +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.collections.shouldHaveSize +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain +import io.modelcontextprotocol.kotlin.sdk.types.EmptyResult +import io.modelcontextprotocol.kotlin.sdk.types.InitializedNotification +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse +import io.modelcontextprotocol.kotlin.sdk.types.McpException +import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.PingRequest +import io.modelcontextprotocol.kotlin.sdk.types.RPCError +import io.modelcontextprotocol.kotlin.sdk.types.RequestId +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.async +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import kotlin.coroutines.cancellation.CancellationException +import kotlin.test.Test +import kotlin.time.Duration.Companion.seconds + +class ProtocolConcurrencyTest { + + private fun TestScope.newProtocol(): TestProtocol = + TestProtocol(ProtocolOptions(handlerCoroutineContext = StandardTestDispatcher(testScheduler))) + + @Test + fun `a suspended slow handler does not block a later fast request`() = runTest { + val protocol = newProtocol() + val transport = RecordingTransport() + protocol.connect(transport) + protocol.enableConcurrency() + + protocol.fallbackRequestHandler = { request, _ -> + if (request.method == "test/slow") delay(10.seconds) + EmptyResult() + } + + transport.deliver(JSONRPCRequest(id = RequestId(1L), method = "test/slow")) + transport.deliver(JSONRPCRequest(id = RequestId(2L), method = "test/fast")) + runCurrent() + + responsesOn(transport).map { it.id } shouldBe listOf(RequestId(2L)) + + advanceTimeBy(11.seconds) + runCurrent() + responsesOn(transport).map { it.id } shouldBe listOf(RequestId(2L), RequestId(1L)) + } + + @Test + fun `dispatch is serial until concurrent dispatch is enabled`() = runTest { + val protocol = newProtocol() + val transport = RecordingTransport() + protocol.connect(transport) + // gate NOT flipped + + protocol.fallbackRequestHandler = { request, _ -> + if (request.method == "test/slow") delay(10.seconds) + EmptyResult() + } + + val delivery = launch { + transport.deliver(JSONRPCRequest(id = RequestId(1L), method = "test/slow")) + transport.deliver(JSONRPCRequest(id = RequestId(2L), method = "test/fast")) + } + runCurrent() + // inline handling: the delivering coroutine is parked inside the slow handler + delivery.isCompleted shouldBe false + responsesOn(transport) shouldBe emptyList() + + advanceTimeBy(11.seconds) + runCurrent() + delivery.isCompleted shouldBe true + responsesOn(transport).map { it.id } shouldBe listOf(RequestId(1L), RequestId(2L)) + } + + // the hook fires before handler lookup and cannot be disabled by a user handler + @Test + fun `initialized notification invokes the router hook even when a user handler is registered`() = runTest { + val protocol = newProtocol() + val transport = RecordingTransport() + protocol.connect(transport) + + var userHandlerCalled = false + protocol.setNotificationHandler(Method.Defined.NotificationsInitialized) { + userHandlerCalled = true + COMPLETED + } + transport.deliver(JSONRPCNotification(method = Method.Defined.NotificationsInitialized.value)) + runCurrent() + + protocol.initializedNotificationCount shouldBe 1 + userHandlerCalled shouldBe true + } + + @Test + fun `cancelled notification cancels the in-flight handler and suppresses the response`() = runTest { + val protocol = newProtocol() + val transport = RecordingTransport() + protocol.connect(transport) + protocol.enableConcurrency() + + val entered = CompletableDeferred() + val sawCancellation = CompletableDeferred() + protocol.fallbackRequestHandler = { _, _ -> + entered.complete(Unit) + try { + delay(10.seconds) + } catch (e: CancellationException) { + sawCancellation.complete(Unit) + throw e + } + EmptyResult() + } + + transport.deliver(JSONRPCRequest(id = RequestId(5L), method = "test/slow")) + runCurrent() + entered.await() + + transport.deliver(cancelledNotification(requestId = RequestId(5L), reason = "user gave up")) + runCurrent() + sawCancellation.await() + advanceUntilIdle() + + responsesOn(transport) shouldBe emptyList() + errorsOn(transport) shouldBe emptyList() // JSONRPCError messages on the wire + protocol.errors shouldBe emptyList() + } + + @Test + fun `cancelled notification for an unknown id is a silent no-op`() = runTest { + val protocol = newProtocol() + val transport = RecordingTransport() + protocol.connect(transport) + protocol.enableConcurrency() + + transport.deliver(cancelledNotification(requestId = RequestId(404L), reason = "nope")) + advanceUntilIdle() + + protocol.errors shouldBe emptyList() + } + + @Test + fun `handler leaking CancellationException without being cancelled produces INTERNAL_ERROR`() = runTest { + val protocol = newProtocol() + val transport = RecordingTransport() + protocol.connect(transport) + protocol.enableConcurrency() + + protocol.fallbackRequestHandler = { _, _ -> throw CancellationException("leaked") } + + transport.deliver(JSONRPCRequest(id = RequestId(6L), method = "test/leak")) + advanceUntilIdle() + + val error = errorsOn(transport).single() + error.id shouldBe RequestId(6L) + error.error.code shouldBe RPCError.ErrorCode.INTERNAL_ERROR + } + + // suppression race variants + @Test + fun `handler that swallows cancellation and returns normally still gets no response`() = runTest { + val protocol = newProtocol() + val transport = RecordingTransport() + protocol.connect(transport) + protocol.enableConcurrency() + + val entered = CompletableDeferred() + protocol.fallbackRequestHandler = { _, _ -> + entered.complete(Unit) + try { + delay(10.seconds) + } catch (_: CancellationException) { + // swallow (anti-pattern) and return normally + } + EmptyResult() + } + + transport.deliver(JSONRPCRequest(id = RequestId(7L), method = "test/swallow")) + runCurrent() + entered.await() + transport.deliver(cancelledNotification(requestId = RequestId(7L), reason = "stop")) + advanceUntilIdle() + + responsesOn(transport) shouldBe emptyList() + } + + @Test + fun `handler throwing non-CE while cancelled gets no error response`() = runTest { + val protocol = newProtocol() + val transport = RecordingTransport() + protocol.connect(transport) + protocol.enableConcurrency() + + val entered = CompletableDeferred() + protocol.fallbackRequestHandler = { _, _ -> + entered.complete(Unit) + try { + delay(10.seconds) + } catch (_: CancellationException) { + throw IllegalStateException("boom") + } + EmptyResult() + } + + transport.deliver(JSONRPCRequest(id = RequestId(8L), method = "test/throw")) + runCurrent() + entered.await() + transport.deliver(cancelledNotification(requestId = RequestId(8L), reason = "stop")) + advanceUntilIdle() + + errorsOn(transport) shouldBe emptyList() + protocol.errors shouldBe emptyList() + } + + @Test + fun `suspension-free notification handlers observe strict arrival order`() = runTest { + val protocol = newProtocol() + val transport = RecordingTransport() + protocol.connect(transport) + protocol.enableConcurrency() + + val seen = mutableListOf() + protocol.fallbackNotificationHandler = { notification -> + seen.add((notification.params as JsonObject).getValue("i").jsonPrimitive.int) + } + + repeat(20) { i -> + transport.deliver( + JSONRPCNotification(method = "test/event", params = buildJsonObject { put("i", i) }), + ) + } + advanceUntilIdle() + + seen shouldBe (0 until 20).toList() + } + + @Test + fun `reconnect resets the dispatch gate to serial`() = runTest { + val protocol = newProtocol() + val transport1 = RecordingTransport() + protocol.connect(transport1) + protocol.enableConcurrency() + protocol.close() + + val transport2 = RecordingTransport() + protocol.connect(transport2) + protocol.fallbackRequestHandler = { request, _ -> + if (request.method == "test/slow") delay(10.seconds) + EmptyResult() + } + + val delivery = launch { + transport2.deliver(JSONRPCRequest(id = RequestId(1L), method = "test/slow")) + transport2.deliver(JSONRPCRequest(id = RequestId(2L), method = "test/fast")) + } + runCurrent() + delivery.isCompleted shouldBe false // serial again: gate reset + advanceTimeBy(11.seconds) + runCurrent() + responsesOn(transport2).map { it.id } shouldBe listOf(RequestId(1L), RequestId(2L)) + } + + @Test + fun `close cancels in-flight handlers suppresses responses and fails pending outbound requests`() = runTest { + val protocol = newProtocol() + val transport = RecordingTransport() + protocol.connect(transport) + protocol.enableConcurrency() + + val entered = CompletableDeferred() + val sawCancellation = CompletableDeferred() + protocol.fallbackRequestHandler = { _, _ -> + entered.complete(Unit) + try { + delay(10.seconds) + } catch (e: CancellationException) { + sawCancellation.complete(Unit) + throw e + } + EmptyResult() + } + transport.deliver(JSONRPCRequest(id = RequestId(9L), method = "test/slow")) + runCurrent() + entered.await() + + // shouldThrow inside the async so its failure does not cancel the (non-supervisor) test scope. + val pending = async { shouldThrow { protocol.request(PingRequest()) } } + transport.awaitRequest() // outbound ping on the wire + + protocol.close() + runCurrent() + + sawCancellation.await() + responsesOn(transport) shouldBe emptyList() + val thrown = pending.await() + thrown.code shouldBe RPCError.ErrorCode.CONNECTION_CLOSED + } + + @Test + fun `message delivered after close is dropped without crash or response`() = runTest { + val protocol = newProtocol() + val transport = RecordingTransport() + protocol.connect(transport) + protocol.enableConcurrency() + protocol.close() + + transport.deliver(JSONRPCRequest(id = RequestId(1L), method = "test/anything")) + advanceUntilIdle() + + responsesOn(transport) shouldBe emptyList() + errorsOn(transport) shouldBe emptyList() + } + + @Test + fun `at most maxConcurrentHandlers handlers run concurrently while control messages bypass`() = runTest { + val protocol = TestProtocol( + ProtocolOptions( + handlerCoroutineContext = StandardTestDispatcher(testScheduler), + maxConcurrentHandlers = 2, + ), + ) + val transport = RecordingTransport() + protocol.connect(transport) + protocol.enableConcurrency() + + var entered = 0 + val release = CompletableDeferred() + protocol.fallbackRequestHandler = { _, _ -> + entered += 1 + release.await() + EmptyResult() + } + + repeat(4) { i -> transport.deliver(JSONRPCRequest(id = RequestId(i.toLong()), method = "test/slow")) } + runCurrent() + entered shouldBe 2 // exactly two running, two parked on the semaphore + + // ping (bypass) is answered while saturated + transport.deliver(JSONRPCRequest(id = RequestId(100L), method = Method.Defined.Ping.value)) + runCurrent() + responsesOn(transport).map { it.id } shouldBe listOf(RequestId(100L)) + + // responses (bypass, inline) are processed while saturated + val pending = async { protocol.request(PingRequest()) } + runCurrent() + val outbound = transport.awaitRequest() + transport.deliver(JSONRPCResponse(id = outbound.id, result = EmptyResult())) + runCurrent() + pending.isCompleted shouldBe true + + release.complete(Unit) + advanceUntilIdle() + entered shouldBe 4 + responsesOn(transport) shouldHaveSize 5 + } + + @Test + fun `flood beyond maxInFlightHandlers is rejected fail-fast and the read loop never suspends`() = runTest { + val protocol = TestProtocol( + ProtocolOptions( + handlerCoroutineContext = StandardTestDispatcher(testScheduler), + maxConcurrentHandlers = 1, + maxInFlightHandlers = 4, + ), + ) + val transport = RecordingTransport() + protocol.connect(transport) + protocol.enableConcurrency() + + val release = CompletableDeferred() + protocol.fallbackRequestHandler = { _, _ -> + release.await() + EmptyResult() + } + + // 10 requests delivered back-to-back; delivery must complete without advancing + // virtual time — i.e. the read loop was never suspended on admission. + val delivery = launch { + repeat(10) { i -> transport.deliver(JSONRPCRequest(id = RequestId(i.toLong()), method = "test/slow")) } + } + runCurrent() + delivery.isCompleted shouldBe true + + // 4 admitted (1 running + 3 parked), 6 rejected immediately + val busyErrors = errorsOn(transport) + busyErrors shouldHaveSize 6 + busyErrors.forEach { + it.error.code shouldBe RPCError.ErrorCode.INTERNAL_ERROR + it.error.message shouldBe "Server is busy: too many in-flight messages" + } + + // ping still answered at full saturation + transport.deliver(JSONRPCRequest(id = RequestId(100L), method = Method.Defined.Ping.value)) + runCurrent() + responsesOn(transport).map { it.id } shouldBe listOf(RequestId(100L)) + + release.complete(Unit) + advanceUntilIdle() + responsesOn(transport) shouldHaveSize 5 // ping + the 4 admitted + } + + @Test + fun `overflowing notifications are dropped and reported via onError`() = runTest { + val protocol = TestProtocol( + ProtocolOptions( + handlerCoroutineContext = StandardTestDispatcher(testScheduler), + maxConcurrentHandlers = 1, + maxInFlightHandlers = 2, + ), + ) + val transport = RecordingTransport() + protocol.connect(transport) + protocol.enableConcurrency() + + // parks one handler with a request: holds the sole execution permit and one in-flight slot + val release = CompletableDeferred() + protocol.fallbackRequestHandler = { _, _ -> + release.await() + EmptyResult() + } + transport.deliver(JSONRPCRequest(id = RequestId(1L), method = "test/hog")) + runCurrent() + + // notification 1 is admitted (parked on the semaphore, behind the hogged permit); + // notification 2 overflows the admission cap and is dropped instead of parked. + val notificationRelease = CompletableDeferred() + protocol.fallbackNotificationHandler = { notificationRelease.await() } + transport.deliver(JSONRPCNotification(method = "test/event")) + transport.deliver(JSONRPCNotification(method = "test/event")) + runCurrent() + + protocol.errors shouldHaveSize 1 + protocol.errors.single().message.orEmpty() shouldContain "too many in-flight messages" + errorsOn(transport) shouldBe emptyList() // no wire error for dropped notifications + + release.complete(Unit) + notificationRelease.complete(Unit) + advanceUntilIdle() + } + + @Test + fun `cancelling saturating handlers releases permits and lets parked handlers run`() = runTest { + val protocol = TestProtocol( + ProtocolOptions( + handlerCoroutineContext = StandardTestDispatcher(testScheduler), + maxConcurrentHandlers = 1, + ), + ) + val transport = RecordingTransport() + protocol.connect(transport) + protocol.enableConcurrency() + + val secondCompleted = CompletableDeferred() + protocol.fallbackRequestHandler = { request, _ -> + if (request.method == "test/hog") { + delay(1000.seconds) // cancellable park + error("unreachable") + } + secondCompleted.complete(Unit) + EmptyResult() + } + + transport.deliver(JSONRPCRequest(id = RequestId(1L), method = "test/hog")) // holds the permit + transport.deliver(JSONRPCRequest(id = RequestId(2L), method = "test/next")) // parked on the semaphore + runCurrent() + secondCompleted.isCompleted shouldBe false + + // cancel the hog THROUGH the bypass while saturated + transport.deliver(cancelledNotification(requestId = RequestId(1L), reason = "make room")) + advanceUntilIdle() + + secondCompleted.isCompleted shouldBe true + responsesOn(transport).map { it.id } shouldBe listOf(RequestId(2L)) // hog suppressed, next answered + } + + @Test + fun `a parked handler can itself be cancelled before it ever runs`() = runTest { + val protocol = TestProtocol( + ProtocolOptions( + handlerCoroutineContext = StandardTestDispatcher(testScheduler), + maxConcurrentHandlers = 1, + ), + ) + val transport = RecordingTransport() + protocol.connect(transport) + protocol.enableConcurrency() + + protocol.fallbackRequestHandler = { request, _ -> + if (request.method == "test/hog") { + delay(1000.seconds) // cancellable park + error("unreachable") + } + EmptyResult() + } + + transport.deliver(JSONRPCRequest(id = RequestId(1L), method = "test/hog")) // holds the permit + transport.deliver(JSONRPCRequest(id = RequestId(2L), method = "test/next")) // parked on the semaphore + runCurrent() + + // cancel the parked handler before it ever acquires the permit, then cancel the hog too + transport.deliver(cancelledNotification(requestId = RequestId(2L), reason = "give up while parked")) + transport.deliver(cancelledNotification(requestId = RequestId(1L), reason = "make room")) + advanceUntilIdle() + + // no response for id=2 at all: its withPermit acquire was cancelled before onRequest ever ran + responsesOn(transport) shouldBe emptyList() + errorsOn(transport) shouldBe emptyList() + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolOptionsTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolOptionsTest.kt new file mode 100644 index 0000000..81bf24a --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolOptionsTest.kt @@ -0,0 +1,16 @@ +package io.modelcontextprotocol.kotlin.sdk.shared + +import io.kotest.matchers.shouldBe +import kotlinx.coroutines.Dispatchers +import kotlin.test.Test + +class ProtocolOptionsTest { + + @Test + fun `defaults expose concurrency knobs`() { + val options = ProtocolOptions() + options.handlerCoroutineContext shouldBe Dispatchers.Default + options.maxConcurrentHandlers shouldBe 64 + options.maxInFlightHandlers shouldBe 256 + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolOutboundCancellationTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolOutboundCancellationTest.kt new file mode 100644 index 0000000..38ad556 --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolOutboundCancellationTest.kt @@ -0,0 +1,167 @@ +package io.modelcontextprotocol.kotlin.sdk.shared + +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.collections.shouldHaveSize +import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain +import io.modelcontextprotocol.kotlin.sdk.types.CancelledNotificationParams +import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.EmptyResult +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest +import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse +import io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION +import io.modelcontextprotocol.kotlin.sdk.types.McpException +import io.modelcontextprotocol.kotlin.sdk.types.McpJson +import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.PingRequest +import io.modelcontextprotocol.kotlin.sdk.types.RPCError +import io.modelcontextprotocol.kotlin.sdk.types.RequestId +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.TimeoutCancellationException +import kotlinx.coroutines.async +import kotlinx.coroutines.cancelAndJoin +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.withTimeout +import kotlinx.serialization.json.decodeFromJsonElement +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.long +import kotlin.coroutines.cancellation.CancellationException +import kotlin.test.Test +import kotlin.time.Duration.Companion.seconds + +class ProtocolOutboundCancellationTest { + + @Test + fun `request times out awaiting the response and sends CancelledNotification with timeout reason`() = runTest { + val protocol = TestProtocol() + val transport = RecordingTransport() + protocol.connect(transport) + + val inFlight = async { + shouldThrow { + protocol.request(PingRequest(), RequestOptions(timeout = 5.seconds)) + } + } + val sent = transport.awaitRequest() // request is on the wire; peer never responds + advanceTimeBy(6.seconds) + runCurrent() + + val thrown = inFlight.await() + thrown.code shouldBe RPCError.ErrorCode.REQUEST_TIMEOUT + thrown.data?.jsonObject?.get("timeout")?.jsonPrimitive?.long shouldBe 5.seconds.inWholeMilliseconds + + val cancelledJson = transport.sentWithOptions + .map { it.first } + .filterIsInstance() + .single { it.method == Method.Defined.NotificationsCancelled.value } + val cancelledParams = McpJson.decodeFromJsonElement(cancelledJson.params!!) + cancelledParams.requestId shouldBe sent.id + cancelledParams.reason.shouldNotBeNull() shouldContain "timed out" + } + + @Test + fun `outer withTimeout around request propagates the original timeout exception and notifies the peer`() = runTest { + val protocol = TestProtocol() + val transport = RecordingTransport() + protocol.connect(transport) + + val inFlight = async { + shouldThrow { + withTimeout(1.seconds) { + protocol.request(PingRequest()) + } + } + } + transport.awaitRequest() // request is on the wire; peer never responds + advanceTimeBy(2.seconds) + runCurrent() + inFlight.await() + + transport.sentWithOptions.map { it.first }.filterIsInstance() + .filter { it.method == Method.Defined.NotificationsCancelled.value } shouldHaveSize 1 + } + + @Test + fun `caller cancellation sends CancelledNotification and rethrows the original exception`() = runTest { + val protocol = TestProtocol() + val transport = RecordingTransport() + protocol.connect(transport) + + val cancelled = CompletableDeferred() + val job = launch { + try { + protocol.request(PingRequest()) + } catch (e: CancellationException) { + cancelled.complete(e) + throw e + } + } + transport.awaitRequest() + job.cancelAndJoin() + + cancelled.await() + val wire = transport.sentWithOptions.map { it.first }.filterIsInstance() + .filter { it.method == Method.Defined.NotificationsCancelled.value } + wire shouldHaveSize 1 + } + + @Test + fun `cancelling the initialize request performs local cleanup but sends no CancelledNotification`() = runTest { + val protocol = TestProtocol() + val transport = RecordingTransport() + protocol.connect(transport) + + val job = launch { + protocol.request( + InitializeRequest( + InitializeRequestParams( + protocolVersion = LATEST_PROTOCOL_VERSION, + capabilities = ClientCapabilities(), + clientInfo = Implementation(name = "t", version = "1"), + ), + ), + ) + } + val sent = transport.awaitRequest() + sent.method shouldBe Method.Defined.Initialize.value + job.cancelAndJoin() + + transport.sentWithOptions.map { it.first }.filterIsInstance() + .filter { it.method == Method.Defined.NotificationsCancelled.value } shouldHaveSize 0 + protocol.responseHandlers shouldBe emptyMap() + } + + @Test + fun `late response for a cancelled request is ignored silently`() = runTest { + val protocol = TestProtocol() + val transport = RecordingTransport() + protocol.connect(transport) + + val job = launch { protocol.request(PingRequest()) } + val sent = transport.awaitRequest() + job.cancelAndJoin() + + transport.deliver(JSONRPCResponse(id = sent.id, result = EmptyResult())) + + protocol.errors shouldHaveSize 0 // no "unknown message ID" onError + } + + @Test + fun `response for a genuinely unknown id still reports onError`() = runTest { + val protocol = TestProtocol() + val transport = RecordingTransport() + protocol.connect(transport) + + transport.deliver(JSONRPCResponse(id = RequestId(999L), result = EmptyResult())) + + protocol.errors shouldHaveSize 1 + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolTest.kt index b726c2f..2846ccc 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolTest.kt @@ -5,6 +5,9 @@ import io.kotest.matchers.collections.shouldContainExactly import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import io.kotest.matchers.string.shouldContain +import io.kotest.matchers.types.shouldBeInstanceOf import io.modelcontextprotocol.kotlin.sdk.types.CustomRequest import io.modelcontextprotocol.kotlin.sdk.types.EmptyResult import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage @@ -13,11 +16,16 @@ import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse import io.modelcontextprotocol.kotlin.sdk.types.McpJson import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.PingRequest +import io.modelcontextprotocol.kotlin.sdk.types.ProgressNotification +import io.modelcontextprotocol.kotlin.sdk.types.ProgressNotificationParams +import io.modelcontextprotocol.kotlin.sdk.types.ProgressToken import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.RequestId import io.modelcontextprotocol.kotlin.sdk.types.RequestMeta import kotlinx.coroutines.async -import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObjectBuilder @@ -30,6 +38,7 @@ import kotlinx.serialization.json.jsonPrimitive import kotlin.coroutines.cancellation.CancellationException import kotlin.test.BeforeTest import kotlin.test.Test +import kotlin.time.Duration.Companion.seconds class ProtocolTest { private lateinit var protocol: TestProtocol @@ -185,64 +194,174 @@ class ProtocolTest { transport.deliver(JSONRPCResponse(sent.id, EmptyResult())) inFlight.await() } -} -private class TestProtocol : Protocol(null) { - val errors = mutableListOf() + @Test + fun `request handler receives enriched extra and ambient context element`() = runTest { + protocol.connect(transport) - override fun onError(error: Throwable) { - errors.add(error) + var seenByParameter: RequestHandlerExtra? = null + var seenAmbient: RequestHandlerExtra? = null + protocol.fallbackRequestHandler = { _, extra -> + seenByParameter = extra + seenAmbient = currentRequestHandlerExtra() + EmptyResult() + } + + transport.deliver(JSONRPCRequest(id = RequestId(7L), method = "custom/echo")) + + seenByParameter shouldNotBe null + seenByParameter?.requestId shouldBe RequestId(7L) + seenByParameter?.method shouldBe Method.Custom("custom/echo") + // the SAME instance flows through both delivery paths + seenAmbient shouldBe seenByParameter } - override fun assertCapabilityForMethod(method: Method) { - // noop - } - override fun assertNotificationCapability(method: Method) { - // noop - } - override fun assertRequestHandlerCapability(method: Method) { - // noop - } -} + @Test + fun `extra method resolves defined methods to Defined entries`() = runTest { + protocol.connect(transport) -private class RecordingTransport : Transport { - private val sentMessages = Channel(Channel.UNLIMITED) - private var onMessageCallback: (suspend (JSONRPCMessage) -> Unit)? = null - private var onCloseCallback: (() -> Unit)? = null + var seenMethod: Method? = null + protocol.setRequestHandler(Method.Defined.Ping) { _, extra -> + seenMethod = extra.method + EmptyResult() + } + transport.deliver(JSONRPCRequest(id = RequestId(1L), method = "ping")) - override suspend fun start() { - // noop + seenMethod shouldBe Method.Defined.Ping } - override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { - sentMessages.send(message) + @Test + fun `extra sendNotification stamps relatedRequestId`() = runTest { + protocol.connect(transport) + + protocol.fallbackRequestHandler = { _, extra -> + extra.sendNotification( + ProgressNotification( + ProgressNotificationParams(progressToken = ProgressToken(1L), progress = 0.5), + ), + ) + EmptyResult() + } + + transport.deliver(JSONRPCRequest(id = RequestId(42L), method = "custom/progressing")) + + val sent = transport.sentWithOptions.first { it.first is JSONRPCNotification } + sent.second?.relatedRequestId shouldBe RequestId(42L) } - override suspend fun close() { - onCloseCallback?.invoke() + @Test + fun `extra sendRequest stamps relatedRequestId and preserves caller options`() = runTest { + protocol.connect(transport) + + protocol.fallbackRequestHandler = { _, extra -> + extra.sendRequest( + PingRequest(), + RequestOptions(resumptionToken = "tok", onProgress = { }, timeout = 30.seconds), + ) + EmptyResult() + } + + // The serial phase runs the handler inline inside deliver(), and the handler suspends + // awaiting the nested request's response — drive delivery from a child coroutine. + val delivery = launch { + transport.deliver(JSONRPCRequest(id = RequestId(42L), method = "custom/nested")) + } + + val outbound = transport.awaitRequest() + val recorded = transport.sentWithOptions + .first { (it.first as? JSONRPCRequest)?.id == outbound.id } + .second + val options = recorded.shouldBeInstanceOf() + options.relatedRequestId shouldBe RequestId(42L) + options.resumptionToken shouldBe "tok" + options.timeout shouldBe 30.seconds + options.onProgress shouldNotBe null + + transport.deliver(JSONRPCResponse(id = outbound.id, result = EmptyResult())) + delivery.join() } - override fun onClose(block: () -> Unit) { - onCloseCallback = block + @Test + fun `currentRequestHandlerExtra returns null outside handlers`() = runTest { + currentRequestHandlerExtra() shouldBe null } - override fun onError(block: (Throwable) -> Unit) { - // noop + @Test + fun `connect while already connected throws IllegalStateException`() = runTest { + protocol.connect(transport) + shouldThrow { + protocol.connect(RecordingTransport()) + } } - override fun onMessage(block: suspend (JSONRPCMessage) -> Unit) { - onMessageCallback = block + @Test + fun `stale onClose from a previous transport does not tear down the successor connection`() = runTest { + protocol.connect(transport) + val staleCloseCallback = transport.closeCallback ?: error("onClose not registered") + + protocol.close() // disconnect first transport (fires its own doClose) + val second = RecordingTransport() + protocol.connect(second) // reconnect + + staleCloseCallback() // late duplicate close signal from transport #1 + + // successor connection must still be alive + protocol.transport shouldBe second } - suspend fun awaitRequest(): JSONRPCRequest { - val message = sentMessages.receive() - return message as? JSONRPCRequest - ?: error("Expected JSONRPCRequest but received ${message::class.simpleName}") + @Test + fun `failed transport start rolls back the connection and allows reconnect`() = runTest { + val failing = object : Transport { + override suspend fun start(): Unit = error("boom") + override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) {} + override suspend fun close() {} + override fun onClose(block: () -> Unit) {} + override fun onError(block: (Throwable) -> Unit) {} + override fun onMessage(block: suspend (JSONRPCMessage) -> Unit) {} + } + shouldThrow { protocol.connect(failing) } + protocol.transport shouldBe null // rolled back + protocol.connect(transport) // reconnect succeeds, no "already connected" + protocol.transport shouldBe transport } - suspend fun deliver(message: JSONRPCMessage) { - val callback = onMessageCallback ?: error("onMessage callback not registered") - callback(message) + @Test + fun `request cleans up its handlers when the transport send fails`() = runTest { + // Send fails without closing the connection, so the leak is not masked by doClose(). + val failingSend = object : Transport { + override suspend fun start() {} + override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?): Unit = + throw IllegalStateException("send failed") + override suspend fun close() {} + override fun onClose(block: () -> Unit) {} + override fun onError(block: (Throwable) -> Unit) {} + override fun onMessage(block: suspend (JSONRPCMessage) -> Unit) {} + } + protocol.connect(failingSend) + + shouldThrow { + protocol.request(PingRequest(), RequestOptions(onProgress = {})) + } + + protocol.responseHandlers.size shouldBe 0 + protocol.progressHandlers.size shouldBe 0 + } + + @Test + fun `connect rejects non-positive maxConcurrentHandlers`() = runTest { + val error = shouldThrow { + TestProtocol(ProtocolOptions(maxConcurrentHandlers = 0)).connect(RecordingTransport()) + } + error.message.shouldNotBeNull() shouldContain "maxConcurrentHandlers" + } + + @Test + fun `connect rejects maxInFlightHandlers below maxConcurrentHandlers`() = runTest { + val error = shouldThrow { + TestProtocol(ProtocolOptions(maxConcurrentHandlers = 4, maxInFlightHandlers = 0)) + .connect(RecordingTransport()) + } + error.message.shouldNotBeNull() shouldContain "maxInFlightHandlers" } } diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolTestFakes.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolTestFakes.kt new file mode 100644 index 0000000..9e8dd60 --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolTestFakes.kt @@ -0,0 +1,112 @@ +package io.modelcontextprotocol.kotlin.sdk.shared + +import io.modelcontextprotocol.kotlin.sdk.types.CancelledNotificationParams +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCError +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse +import io.modelcontextprotocol.kotlin.sdk.types.McpJson +import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.RequestId +import kotlinx.coroutines.channels.Channel +import kotlinx.serialization.json.encodeToJsonElement + +/** + * Shared [Protocol] test double: records [onError] calls, counts `notifications/initialized` + * router-hook invocations, and no-ops all capability assertions. + * + * The concurrency gate is flipped only via [enableConcurrency]; nothing in this double flips it + * implicitly, so tests keep today's serial semantics unless they opt in. + */ +internal class TestProtocol(options: ProtocolOptions? = null) : Protocol(options) { + val errors = mutableListOf() + var initializedNotificationCount = 0 + + fun enableConcurrency() { + enableConcurrentDispatch() + } + + override fun onInitializedNotification() { + initializedNotificationCount++ + // deliberately does NOT flip the gate — tests control the flip explicitly + } + + override fun onError(error: Throwable) { + errors.add(error) + } + + override fun assertCapabilityForMethod(method: Method) { + // noop + } + override fun assertNotificationCapability(method: Method) { + // noop + } + public override fun assertRequestHandlerCapability(method: Method) { + // noop + } +} + +/** Shared [Transport] test double: records every sent message (with its options) and replays inbound ones. */ +internal class RecordingTransport : Transport { + val sentMessages = Channel(Channel.UNLIMITED) + private var onMessageCallback: (suspend (JSONRPCMessage) -> Unit)? = null + private var onCloseCallback: (() -> Unit)? = null + + val sentWithOptions = mutableListOf>() + + var closeCallback: (() -> Unit)? = null + private set + + override suspend fun start() { + // noop + } + + override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { + sentWithOptions.add(message to options) + sentMessages.send(message) + } + + override suspend fun close() { + onCloseCallback?.invoke() + } + + override fun onClose(block: () -> Unit) { + closeCallback = block + onCloseCallback = block + } + + override fun onError(block: (Throwable) -> Unit) { + // noop + } + + override fun onMessage(block: suspend (JSONRPCMessage) -> Unit) { + onMessageCallback = block + } + + suspend fun awaitRequest(): JSONRPCRequest { + while (true) { + val message = sentMessages.receive() + if (message is JSONRPCRequest) return message + } + } + + /** Cross-thread-safe reader used by real-dispatcher tests; [sentWithOptions] is single-thread only. */ + suspend fun awaitSent(): JSONRPCMessage = sentMessages.receive() + + suspend fun deliver(message: JSONRPCMessage) { + val callback = onMessageCallback ?: error("onMessage callback not registered") + callback(message) + } +} + +internal fun responsesOn(transport: RecordingTransport): List = + transport.sentWithOptions.map { it.first }.filterIsInstance() + +internal fun errorsOn(transport: RecordingTransport): List = + transport.sentWithOptions.map { it.first }.filterIsInstance() + +internal fun cancelledNotification(requestId: RequestId, reason: String): JSONRPCNotification = JSONRPCNotification( + method = Method.Defined.NotificationsCancelled.value, + params = McpJson.encodeToJsonElement(CancelledNotificationParams(requestId = requestId, reason = reason)), +) diff --git a/kotlin-sdk-core/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolConcurrencyJvmTest.kt b/kotlin-sdk-core/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolConcurrencyJvmTest.kt new file mode 100644 index 0000000..60658f8 --- /dev/null +++ b/kotlin-sdk-core/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ProtocolConcurrencyJvmTest.kt @@ -0,0 +1,114 @@ +package io.modelcontextprotocol.kotlin.sdk.shared + +import io.kotest.matchers.shouldBe +import io.modelcontextprotocol.kotlin.sdk.types.EmptyResult +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse +import io.modelcontextprotocol.kotlin.sdk.types.RequestId +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeout +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import kotlin.test.Test +import kotlin.time.Duration.Companion.seconds + +/** + * Real-dispatcher counterparts to [ProtocolConcurrencyTest]. + * + * These run under [runBlocking] (real time, real threads on [Dispatchers.Default]) rather than + * `runTest`: a virtual-time scheduler auto-advances past the `withTimeout` guards and cannot + * observe genuine cross-thread parallelism, so the concurrency assertions could pass vacuously. + */ +class ProtocolConcurrencyJvmTest { + + @Test + fun `fast request completes while a slow handler is parked on Dispatchers Default`() = runBlocking { + val protocol = TestProtocol(ProtocolOptions(handlerCoroutineContext = Dispatchers.Default)) + val transport = RecordingTransport() + protocol.connect(transport) + protocol.enableConcurrency() + + val slowEntered = CompletableDeferred() + val releaseSlow = CompletableDeferred() + protocol.fallbackRequestHandler = { request, _ -> + if (request.method == "test/slow") { + slowEntered.complete(Unit) + releaseSlow.await() + } + EmptyResult() + } + + transport.deliver(JSONRPCRequest(id = RequestId(1L), method = "test/slow")) + withTimeout(5.seconds) { slowEntered.await() } + transport.deliver(JSONRPCRequest(id = RequestId(2L), method = "test/fast")) + + val fast = withTimeout(5.seconds) { transport.awaitSent() } + (fast as JSONRPCResponse).id shouldBe RequestId(2L) + + releaseSlow.complete(Unit) + val slow = withTimeout(5.seconds) { transport.awaitSent() } + (slow as JSONRPCResponse).id shouldBe RequestId(1L) + Unit + } + + @Test + fun `dispatch stays serial before the gate flips on Dispatchers Default`() = runBlocking { + val protocol = TestProtocol(ProtocolOptions(handlerCoroutineContext = Dispatchers.Default)) + val transport = RecordingTransport() + protocol.connect(transport) + // gate NOT flipped + + val slowEntered = CompletableDeferred() + val releaseSlow = CompletableDeferred() + protocol.fallbackRequestHandler = { request, _ -> + if (request.method == "test/slow") { + slowEntered.complete(Unit) + releaseSlow.await() + } + EmptyResult() + } + + // A single delivering coroutine models the transport read loop: the second deliver cannot + // start until the first returns, so a parked slow handler blocks the fast request. + val delivery = launch { + transport.deliver(JSONRPCRequest(id = RequestId(1L), method = "test/slow")) + transport.deliver(JSONRPCRequest(id = RequestId(2L), method = "test/fast")) + } + withTimeout(5.seconds) { slowEntered.await() } + + // The delivering coroutine is parked inside the slow handler; fast was never delivered. + transport.sentMessages.tryReceive().isFailure shouldBe true + + releaseSlow.complete(Unit) + val first = withTimeout(5.seconds) { transport.awaitSent() } + (first as JSONRPCResponse).id shouldBe RequestId(1L) + val second = withTimeout(5.seconds) { transport.awaitSent() } + (second as JSONRPCResponse).id shouldBe RequestId(2L) + delivery.join() + } + + @Test + fun `suspension-free notification handlers keep arrival order on Dispatchers Default`() = runBlocking { + val protocol = TestProtocol(ProtocolOptions(handlerCoroutineContext = Dispatchers.Default)) + val transport = RecordingTransport() + protocol.connect(transport) + protocol.enableConcurrency() + + // safe: appended only from the delivering coroutine (UNDISPATCHED, suspension-free) + val seen = mutableListOf() + protocol.fallbackNotificationHandler = { notification -> + seen.add((notification.params as JsonObject).getValue("i").jsonPrimitive.int) + } + repeat(100) { i -> + transport.deliver(JSONRPCNotification(method = "test/event", params = buildJsonObject { put("i", i) })) + } + seen shouldBe (0 until 100).toList() + } +} diff --git a/kotlin-sdk-server/api/kotlin-sdk-server.api b/kotlin-sdk-server/api/kotlin-sdk-server.api index 6094b7c..51ac279 100644 --- a/kotlin-sdk-server/api/kotlin-sdk-server.api +++ b/kotlin-sdk-server/api/kotlin-sdk-server.api @@ -169,8 +169,8 @@ public final class io/modelcontextprotocol/kotlin/sdk/server/ServerOptions : io/ public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;)V public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;Z)V public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;ZLio/modelcontextprotocol/kotlin/sdk/utils/ResourceTemplateMatcherFactory;)V - public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;ZLio/modelcontextprotocol/kotlin/sdk/utils/ResourceTemplateMatcherFactory;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;ZLio/modelcontextprotocol/kotlin/sdk/utils/ResourceTemplateMatcherFactory;Lkotlin/coroutines/CoroutineContext;)V + public synthetic fun (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;ZLio/modelcontextprotocol/kotlin/sdk/utils/ResourceTemplateMatcherFactory;Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getCapabilities ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities; public final fun getResourceTemplateMatcherFactory ()Lio/modelcontextprotocol/kotlin/sdk/utils/ResourceTemplateMatcherFactory; } @@ -198,6 +198,7 @@ public class io/modelcontextprotocol/kotlin/sdk/server/ServerSession : io/modelc public fun onClose ()V public final fun onClose (Lkotlin/jvm/functions/Function0;)V public final fun onInitialized (Lkotlin/jvm/functions/Function0;)V + protected fun onInitializedNotification ()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; diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt index c0d3e84..51a2389 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt @@ -53,9 +53,11 @@ import io.modelcontextprotocol.kotlin.sdk.utils.ResourceTemplateMatcher import io.modelcontextprotocol.kotlin.sdk.utils.ResourceTemplateMatcherFactory import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.put +import kotlin.coroutines.CoroutineContext import kotlin.jvm.JvmOverloads import kotlin.time.ExperimentalTime @@ -69,12 +71,17 @@ private val logger = KotlinLogging.logger {} * Forwarded to [ProtocolOptions.enforceStrictCapabilities]. * @property resourceTemplateMatcherFactory The factory used to create [ResourceTemplateMatcher] instances * for matching resource URIs against registered templates. Defaults to [PathSegmentTemplateMatcher.factory]. + * @param handlerCoroutineContext Coroutine context for inbound handlers. See [ProtocolOptions.handlerCoroutineContext]. */ public class ServerOptions( public val capabilities: ServerCapabilities, enforceStrictCapabilities: Boolean = true, public val resourceTemplateMatcherFactory: ResourceTemplateMatcherFactory = PathSegmentTemplateMatcher.factory, -) : ProtocolOptions(enforceStrictCapabilities = enforceStrictCapabilities) { + handlerCoroutineContext: CoroutineContext = Dispatchers.Default, +) : ProtocolOptions( + enforceStrictCapabilities = enforceStrictCapabilities, + handlerCoroutineContext = handlerCoroutineContext, +) { @JvmOverloads public constructor( capabilities: ServerCapabilities, diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt index d215bd5..880f1cf 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt @@ -108,10 +108,18 @@ public open class ServerSession( } } + override fun onInitializedNotification() { + // Enable concurrency before the user's onInitialized callback, which runs synchronously. + enableConcurrentDispatch() + } + internal val clientConnection: ClientConnection = ClientConnectionImpl(this) /** * Registers a callback to be invoked when the server has completed initialization. + * + * The callback must be synchronous and fast: it runs on the message-dispatch path for + * `notifications/initialized`, after concurrent dispatch has been enabled for the session. */ public fun onInitialized(block: () -> Unit) { val old = _onInitialized diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt index c213350..1f8ac39 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt @@ -15,10 +15,12 @@ import io.ktor.server.sse.ServerSSESession import io.ktor.util.collections.ConcurrentMap import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport import io.modelcontextprotocol.kotlin.sdk.shared.TransportSendOptions +import io.modelcontextprotocol.kotlin.sdk.types.CancelledNotificationParams import io.modelcontextprotocol.kotlin.sdk.types.DEFAULT_NEGOTIATED_PROTOCOL_VERSION import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCEmptyMessage import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCError import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse import io.modelcontextprotocol.kotlin.sdk.types.McpJson @@ -27,6 +29,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.RPCError import io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.REQUEST_TIMEOUT import io.modelcontextprotocol.kotlin.sdk.types.RequestId import io.modelcontextprotocol.kotlin.sdk.types.SUPPORTED_PROTOCOL_VERSIONS +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.job @@ -175,6 +178,20 @@ public class StreamableHttpServerTransport(private val configuration: Configurat private val requestToStreamMapping: ConcurrentMap = ConcurrentMap() private val requestToResponseMapping: ConcurrentMap = ConcurrentMap() + /** + * JSON-response mode only: one entry per in-flight POST, completed by [send] with the collected + * responses once every request delivered by that POST has been answered. [handlePostRequest] + * awaits it so the HTTP exchange stays open while handlers run concurrently. + */ + private val pendingJsonResponses: ConcurrentMap>> = ConcurrentMap() + + /** + * JSON-response mode only: request ids retired by an inbound cancellation while their POST is + * still awaiting. Treated as settled-without-response so the POST's "a response for every id" + * completion condition is not blocked by a request that will never be answered. + */ + private val cancelledRequestIds: ConcurrentMap = ConcurrentMap() + private val sessionMutex = Mutex() private val streamMutex = Mutex() @@ -241,54 +258,117 @@ public class StreamableHttpServerTransport(private val configuration: Configurat return } + val isTerminated = message is JSONRPCResponse || message is JSONRPCError + + // In JSON mode request-related notifications have no per-request SSE stream. Route them to the + // standalone GET stream before the lookup below, so a notification for an already-retired + // request is delivered there instead of becoming a spurious error. + if (configuration.enableJsonResponse && !isTerminated) { + val standaloneStream = streamsMapping[STANDALONE_SSE_STREAM_ID] + standaloneStream?.let { emitOnStream(STANDALONE_SSE_STREAM_ID, it.session, message) } + return + } + val streamId = requestToStreamMapping[routingRequestId] - ?: error("No connection established for request id $routingRequestId") + if (streamId == null) { + // Request already retired (completed, cancelled, or the JSON POST disconnected). In JSON + // mode a late terminal response is undeliverable and dropped. In SSE mode an unknown id + // is a real routing error. + if (configuration.enableJsonResponse) return + error("No connection established for request id $routingRequestId") + } val activeStream = streamsMapping[streamId] if (!configuration.enableJsonResponse) { activeStream?.let { stream -> emitOnStream(streamId, stream.session, message) } - } - - val isTerminated = message is JSONRPCResponse || message is JSONRPCError - if (!isTerminated) { - if (configuration.enableJsonResponse) { - // In JSON response mode there is no per-request SSE stream, so route notifications - // that are logically associated with a request to the standalone GET SSE stream. - val standaloneStream = streamsMapping[STANDALONE_SSE_STREAM_ID] - standaloneStream?.let { emitOnStream(STANDALONE_SSE_STREAM_ID, it.session, message) } - } - return + if (!isTerminated) return } requestToResponseMapping[responseRequestId!!] = message - val relatedIds = requestToStreamMapping.filterValues { it == streamId }.keys - - if (relatedIds.any { it !in requestToResponseMapping }) return streamMutex.withLock { - if (activeStream == null) error("No connection established for request ID: $routingRequestId") + // The request may have been retired (disconnect or cancellation cleanup) between recording + // the response above and acquiring the lock. Reclaim the stray mapping and drop it. + if (routingRequestId !in requestToStreamMapping) { + requestToResponseMapping.remove(routingRequestId) + return + } + // Recomputed under the lock. Handlers may respond concurrently, and the sender that + // observes the last settled request also retires the ids, so a losing sender sees an + // empty set. A cancelled request counts as settled even though it produces no response. + val relatedIds = requestToStreamMapping.filterValues { it == streamId }.keys + if (relatedIds.isEmpty() || + relatedIds.any { it !in requestToResponseMapping && it !in cancelledRequestIds } + ) { + return + } if (configuration.enableJsonResponse) { - activeStream.call.response.header(HttpHeaders.ContentType, ContentType.Application.Json.toString()) - sessionId?.let { activeStream.call.response.header(MCP_SESSION_ID_HEADER, it) } - val responses = relatedIds.mapNotNull { requestToResponseMapping[it] } - val payload = if (responses.size == 1) { - responses.first() - } else { - responses + // Hand the collected responses to the POST suspended in [handlePostRequest], which + // completes the HTTP exchange. A null pendingPost means the POST was already retired + // (disconnect or cancellation), so the responses are undeliverable and dropped quietly. + val pendingPost = pendingJsonResponses.remove(streamId) + if (pendingPost == null) { + cleanUpRequests(relatedIds) + return } - activeStream.call.respond(payload) + val responses = relatedIds.mapNotNull { requestToResponseMapping[it] } + cleanUpRequests(relatedIds) + pendingPost.complete(responses) } else { - activeStream.session?.close() + // A null stream means the per-request SSE connection closed before the response + // settled, so the ids are retired rather than leaked. + activeStream?.session?.close() + cleanUpRequests(relatedIds) } + } + } - // Clean up - relatedIds.forEach { requestId -> - requestToResponseMapping.remove(requestId) - requestToStreamMapping.remove(requestId) + private fun cleanUpRequests(ids: Collection) { + ids.forEach { requestId -> + requestToResponseMapping.remove(requestId) + requestToStreamMapping.remove(requestId) + cancelledRequestIds.remove(requestId) + } + } + + /** + * Scans a delivered POST body for `notifications/cancelled` and retires each referenced request + * that is awaiting on a JSON-mode POST. No-op unless [Configuration.enableJsonResponse]. + */ + private suspend fun retireCancelledJsonRequests(messages: List) { + if (!configuration.enableJsonResponse) return + val cancelledIds = messages.asSequence() + .filterIsInstance() + .filter { it.method == Method.Defined.NotificationsCancelled.value } + .mapNotNull { notification -> + val params = notification.params ?: return@mapNotNull null + runCatching { + McpJson.decodeFromJsonElement(params).requestId + }.getOrNull() } + .toList() + cancelledIds.forEach { retireCancelledJsonRequest(it) } + } + + /** + * Marks a request cancelled by the peer as settled without a response and completes its POST once + * every request that POST delivered is settled (answered or cancelled). Without this, a cancelled + * request leaves the POST awaiting a response that, by design, is never sent. + */ + private suspend fun retireCancelledJsonRequest(requestId: RequestId) { + streamMutex.withLock { + val streamId = requestToStreamMapping[requestId] ?: return + cancelledRequestIds[requestId] = Unit + val relatedIds = requestToStreamMapping.filterValues { it == streamId }.keys + val allSettled = relatedIds.all { it in requestToResponseMapping || it in cancelledRequestIds } + if (!allSettled) return + val pendingPost = pendingJsonResponses.remove(streamId) ?: return + val responses = relatedIds.mapNotNull { requestToResponseMapping[it] } + cleanUpRequests(relatedIds) + pendingPost.complete(responses) } } @@ -304,6 +384,10 @@ public class StreamableHttpServerTransport(private val configuration: Configurat streamsMapping.clear() requestToStreamMapping.clear() requestToResponseMapping.clear() + cancelledRequestIds.clear() + // Release any POST calls suspended awaiting JSON responses. + pendingJsonResponses.values.forEach { it.cancel() } + pendingJsonResponses.clear() invokeOnCloseCallback() } } @@ -339,7 +423,12 @@ public class StreamableHttpServerTransport(private val configuration: Configurat } /** - * Handles POST requests containing JSON-RPC messages + * Handles POST requests containing JSON-RPC messages. + * + * In JSON-response mode this suspends until a response has been produced for every request in + * the POST body, then completes the HTTP exchange with the collected JSON (a single object for + * one request, an array for a batch). Bodies without requests are acknowledged with 202 + * immediately. In SSE mode the responses are emitted onto the per-request SSE stream instead. */ public suspend fun handlePostRequest(session: ServerSSESession?, call: ApplicationCall) { try { @@ -407,6 +496,9 @@ public class StreamableHttpServerTransport(private val configuration: Configurat if (!hasRequest) { call.respondNullable(status = HttpStatusCode.Accepted, message = null) messages.forEach { message -> _onMessage(message) } + // A cancellation may target a request still awaiting delivery on a JSON-mode POST; + // retire it so that POST completes instead of waiting for a response that never comes. + retireCancelledJsonRequests(messages) return } @@ -429,13 +521,50 @@ public class StreamableHttpServerTransport(private val configuration: Configurat maybeSendPrimingEvent(streamId, session, clientProtocolVersion) } + val pendingPost = if (configuration.enableJsonResponse) { + CompletableDeferred>() + } else { + null + } + streamMutex.withLock { streamsMapping[streamId] = SessionContext(session, call) messages.filterIsInstance().forEach { requestToStreamMapping[it.id] = streamId } + pendingPost?.let { pendingJsonResponses[streamId] = it } } call.coroutineContext.job.invokeOnCompletion { streamsMapping.remove(streamId) } - messages.forEach { message -> _onMessage(message) } + if (pendingPost == null) { + // SSE mode: responses are emitted onto this call's SSE stream by send(). + messages.forEach { message -> _onMessage(message) } + return + } + + // JSON-response mode. Handlers may run concurrently, so returning from delivery no longer + // implies the responses were produced. Suspend until send() has collected a response for + // every request in this POST. A client disconnect or transport close cancels this call. + try { + messages.forEach { message -> _onMessage(message) } + retireCancelledJsonRequests(messages) + val responses = pendingPost.await() + sessionId?.let { call.response.header(MCP_SESSION_ID_HEADER, it) } + if (responses.isEmpty()) { + // Every request in this POST was cancelled before producing a response; there is + // nothing to return, so acknowledge with 202 rather than an empty JSON body. + call.respondNullable(status = HttpStatusCode.Accepted, message = null) + } else { + call.response.header(HttpHeaders.ContentType, ContentType.Application.Json.toString()) + val payload = if (responses.size == 1) responses.first() else responses + call.respond(payload) + } + } finally { + // On cancellation or failure, retire this POST's ids so a late response is dropped + // quietly at the send() boundary instead of leaking mappings. Idempotent no-ops on + // the success path (send() or retirement already cleaned up). + pendingJsonResponses.remove(streamId) + pendingPost.cancel() + cleanUpRequests(messages.filterIsInstance().map { it.id }) + } } catch (e: CancellationException) { throw e } catch (e: Exception) { diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt index d7dec4e..172889a 100644 --- a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt @@ -19,14 +19,19 @@ import io.ktor.http.HttpHeaders import io.ktor.http.HttpStatusCode import io.ktor.http.contentType import io.ktor.serialization.kotlinx.json.json +import io.ktor.server.application.ApplicationCall import io.ktor.server.application.install import io.ktor.server.routing.post import io.ktor.server.routing.routing +import io.ktor.server.sse.ServerSSESession import io.ktor.server.testing.ApplicationTestBuilder import io.ktor.server.testing.testApplication +import io.ktor.sse.ServerSentEvent import io.ktor.utils.io.ByteReadChannel import io.ktor.utils.io.readLine import io.ktor.utils.io.readUTF8Line +import io.modelcontextprotocol.kotlin.sdk.types.CancelledNotification +import io.modelcontextprotocol.kotlin.sdk.types.CancelledNotificationParams import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities import io.modelcontextprotocol.kotlin.sdk.types.EmptyResult import io.modelcontextprotocol.kotlin.sdk.types.Implementation @@ -46,6 +51,16 @@ import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.types.Tool import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema import io.modelcontextprotocol.kotlin.sdk.types.toJSON +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.async +import kotlinx.coroutines.cancel +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.withTimeout import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.put @@ -53,11 +68,13 @@ import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource import java.util.concurrent.atomic.AtomicBoolean +import kotlin.coroutines.CoroutineContext import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertFalse import kotlin.test.assertNotNull +import kotlin.time.Duration.Companion.seconds import io.ktor.client.plugins.contentnegotiation.ContentNegotiation as ClientContentNegotiation import io.ktor.server.plugins.contentnegotiation.ContentNegotiation as ServerContentNegotiation @@ -349,6 +366,306 @@ class StreamableHttpServerTransportTest { assertEquals("second", secondMeta?.get("label")?.jsonPrimitive?.content)*/ } + @Test + fun `json response waits for a response produced after message delivery returns`() = testApplication { + configTestServer() + + val client = createTestClient() + + val transport = StreamableHttpServerTransport( + StreamableHttpServerTransport.Configuration(enableJsonResponse = true), + ) + // Simulates concurrent dispatch: message delivery returns before the handler responds. + val handlerScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) + try { + transport.onMessage { message -> + if (message is JSONRPCRequest) { + if (message.method == Method.Defined.Initialize.value) { + transport.send(JSONRPCResponse(message.id, EmptyResult())) + } else { + handlerScope.launch { + delay(100) + transport.send(JSONRPCResponse(message.id, EmptyResult())) + } + } + } + } + + configureTransportEndpoint(transport) + + val initResponse = client.post(path) { + addStreamableHeaders() + setBody(buildInitializeRequestPayload()) + } + initResponse.status shouldBe HttpStatusCode.OK + + val request = JSONRPCRequest(id = RequestId("async-1"), method = Method.Defined.ToolsList.value) + val response = withTimeout(10.seconds) { + client.post(path) { + addStreamableHeaders() + header(MCP_SESSION_ID_HEADER, initResponse.headers[MCP_SESSION_ID_HEADER]) + setBody(encodeMessages(listOf(request))) + } + } + + response.status shouldBe HttpStatusCode.OK + response.body() shouldBe JSONRPCResponse(request.id) + } finally { + handlerScope.cancel() + } + } + + @Test + fun `json response for a batch waits until every asynchronously produced response is ready`() = testApplication { + configTestServer() + + val client = createTestClient() + + val transport = StreamableHttpServerTransport( + StreamableHttpServerTransport.Configuration(enableJsonResponse = true), + ) + val firstRequest = JSONRPCRequest(id = RequestId("slow"), method = Method.Defined.ToolsList.value) + val secondRequest = JSONRPCRequest(id = RequestId("fast"), method = Method.Defined.ResourcesList.value) + + // Simulates concurrent dispatch with out-of-order completion: the second request responds + // first; the POST must stay open until the slower first response settles too. + val handlerScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) + try { + transport.onMessage { message -> + if (message is JSONRPCRequest) { + if (message.method == Method.Defined.Initialize.value) { + transport.send(JSONRPCResponse(message.id, EmptyResult())) + } else { + handlerScope.launch { + delay(if (message.id == firstRequest.id) 200 else 50) + transport.send(JSONRPCResponse(message.id, EmptyResult())) + } + } + } + } + + configureTransportEndpoint(transport) + + val initResponse = client.post(path) { + addStreamableHeaders() + setBody(buildInitializeRequestPayload()) + } + initResponse.status shouldBe HttpStatusCode.OK + + val response = withTimeout(10.seconds) { + client.post(path) { + addStreamableHeaders() + header(MCP_SESSION_ID_HEADER, initResponse.headers[MCP_SESSION_ID_HEADER]) + setBody(encodeMessages(listOf(firstRequest, secondRequest))) + } + } + + response.status shouldBe HttpStatusCode.OK + val responses = response.body>() + responses.map { it.id }.toSet() shouldBe setOf(firstRequest.id, secondRequest.id) + } finally { + handlerScope.cancel() + } + } + + @Test + fun `json response POST completes with 202 when its only request is cancelled`() = testApplication { + configTestServer() + + val client = createTestClient() + + val transport = StreamableHttpServerTransport( + StreamableHttpServerTransport.Configuration(enableJsonResponse = true), + ) + val requestDelivered = CompletableDeferred() + // Concurrent dispatch: delivery returns before a response is produced. This request is never + // answered here, modelling a handler cancelled by notifications/cancelled. + transport.onMessage { message -> + if (message is JSONRPCRequest) { + if (message.method == Method.Defined.Initialize.value) { + transport.send(JSONRPCResponse(message.id, EmptyResult())) + } else { + requestDelivered.complete(Unit) + } + } + } + + configureTransportEndpoint(transport) + + val clientScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) + try { + val initResponse = client.post(path) { + addStreamableHeaders() + setBody(buildInitializeRequestPayload()) + } + initResponse.status shouldBe HttpStatusCode.OK + val sessionId = initResponse.headers[MCP_SESSION_ID_HEADER] + + val request = JSONRPCRequest(id = RequestId("cancel-me"), method = Method.Defined.ToolsList.value) + val requestPost = clientScope.async { + client.post(path) { + addStreamableHeaders() + header(MCP_SESSION_ID_HEADER, sessionId) + setBody(encodeMessages(listOf(request))) + } + } + + // The request must be registered in-flight before it can be cancelled. + withTimeout(5.seconds) { requestDelivered.await() } + + val cancellation = CancelledNotification( + CancelledNotificationParams(requestId = request.id, reason = "client cancelled"), + ) + val cancelResponse = client.post(path) { + addStreamableHeaders() + header(MCP_SESSION_ID_HEADER, sessionId) + setBody(encodeMessages(listOf(cancellation.toJSON()))) + } + cancelResponse.status shouldBe HttpStatusCode.Accepted + + // Without retirement of the cancelled id this await never completes and the timeout fires. + val response = withTimeout(10.seconds) { requestPost.await() } + response.status shouldBe HttpStatusCode.Accepted + } finally { + clientScope.cancel() + } + } + + @Test + fun `json response for a batch returns only the non-cancelled responses`() = testApplication { + configTestServer() + + val client = createTestClient() + + val transport = StreamableHttpServerTransport( + StreamableHttpServerTransport.Configuration(enableJsonResponse = true), + ) + val answeredA = JSONRPCRequest(id = RequestId("a"), method = Method.Defined.ToolsList.value) + val answeredB = JSONRPCRequest(id = RequestId("b"), method = Method.Defined.ToolsList.value) + val cancelledC = JSONRPCRequest(id = RequestId("c"), method = Method.Defined.ToolsList.value) + + val cancelledDelivered = CompletableDeferred() + val handlerScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) + try { + transport.onMessage { message -> + if (message is JSONRPCRequest) { + when { + message.method == Method.Defined.Initialize.value -> + transport.send(JSONRPCResponse(message.id, EmptyResult())) + + message.id == answeredA.id || message.id == answeredB.id -> + handlerScope.launch { transport.send(JSONRPCResponse(message.id, EmptyResult())) } + + message.id == cancelledC.id -> cancelledDelivered.complete(Unit) + } + } + } + + configureTransportEndpoint(transport) + + val initResponse = client.post(path) { + addStreamableHeaders() + setBody(buildInitializeRequestPayload()) + } + initResponse.status shouldBe HttpStatusCode.OK + val sessionId = initResponse.headers[MCP_SESSION_ID_HEADER] + + val batchPost = handlerScope.async { + client.post(path) { + addStreamableHeaders() + header(MCP_SESSION_ID_HEADER, sessionId) + setBody(encodeMessages(listOf(answeredA, answeredB, cancelledC))) + } + } + + withTimeout(5.seconds) { cancelledDelivered.await() } + + val cancellation = CancelledNotification(CancelledNotificationParams(requestId = cancelledC.id)) + client.post(path) { + addStreamableHeaders() + header(MCP_SESSION_ID_HEADER, sessionId) + setBody(encodeMessages(listOf(cancellation.toJSON()))) + }.status shouldBe HttpStatusCode.Accepted + + val response = withTimeout(10.seconds) { batchPost.await() } + response.status shouldBe HttpStatusCode.OK + val responses = response.body>() + responses.map { it.id }.toSet() shouldBe setOf(answeredA.id, answeredB.id) + } finally { + handlerScope.cancel() + } + } + + @Test + fun `json response send for a retired request id is dropped without error`() = runTest { + val transport = StreamableHttpServerTransport(enableJsonResponse = true) + // No stream is mapped for this id (already retired by disconnect/cancellation cleanup); + // a late terminal response must be dropped quietly, not raise "No connection established". + transport.send(JSONRPCResponse(RequestId("retired"), EmptyResult())) + } + + @Test + fun `sse response send for an unknown request id still errors`() = runTest { + val transport = StreamableHttpServerTransport(enableJsonResponse = false) + assertFailsWith { + transport.send(JSONRPCResponse(RequestId("unknown"), EmptyResult())) + } + } + + @Test + fun `sse terminal response is dropped without leaking when the per-request stream is gone`() = testApplication { + configTestServer() + val client = createTestClient() + + val transport = StreamableHttpServerTransport( + StreamableHttpServerTransport.Configuration(enableJsonResponse = false), + ) + transport.setSessionIdGenerator(null) // stateless: accept requests without an init handshake + val requestId = RequestId("held-request") + val delivered = CompletableDeferred() + val release = CompletableDeferred() + transport.onMessage { message -> + if (message is JSONRPCRequest) { + delivered.complete(Unit) + release.await() // keep the POST and its stream mapping alive until the test releases it + } + } + + application { + routing { + post(path) { + transport.handlePostRequest(FakeServerSSESession(call, call.coroutineContext), call) + } + } + } + + val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) + try { + val post = scope.async { + client.post(path) { + addStreamableHeaders() + setBody( + encodeMessages( + listOf(JSONRPCRequest(id = requestId, method = Method.Defined.ToolsList.value)), + ), + ) + } + } + withTimeout(5.seconds) { delivered.await() } + + // Drop the per-request SSE stream while the request is still mapped in-flight; the handler's + // terminal response is now undeliverable and must be dropped quietly rather than raise + // "No connection established" (which would also strand the request mappings). + transport.closeSseStream(requestId) + transport.send(JSONRPCResponse(requestId, EmptyResult())) + + post.cancel() + } finally { + release.complete(Unit) + scope.cancel() + } + } + @ParameterizedTest @MethodSource("invalidPayloads") fun `POST with a null or empty body returns an error`(payload: String?) = testApplication { @@ -694,3 +1011,12 @@ class StreamableHttpServerTransportTest { } } } + +/** No-op [ServerSSESession] double that lets a POST route drive [StreamableHttpServerTransport] in SSE mode. */ +private class FakeServerSSESession( + override val call: ApplicationCall, + override val coroutineContext: CoroutineContext, +) : ServerSSESession { + override suspend fun send(event: ServerSentEvent) {} + override suspend fun close() {} +} From 76987a211b08e0d42f688f7e038eeb449a9ab385 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Jul 2026 14:21:41 +0200 Subject: [PATCH 367/449] chore(deps): bump the all-dependencies group across 1 directory with 3 updates (#891) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the all-dependencies group with 3 updates in the /integration-test/src/jvmTest/typescript directory: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node), [tsx](https://github.com/privatenumber/tsx) and [typescript](https://github.com/microsoft/TypeScript). Updates `@types/node` from 26.1.0 to 26.1.1
        Commits

        Updates `tsx` from 4.23.0 to 4.23.1
        Release notes

        Sourced from tsx's releases.

        v4.23.1

        4.23.1 (2026-07-13)

        Bug Fixes

        • support tsImport after global preload (8d4ffc2)
        • watch: avoid clearing piped output (95d0672)
        • watch: treat script and dependency paths literally (79fddde)

        Performance Improvements

        • index transform cache lazily (e818ad6)
        • load esbuild lazily in CLI (d067938)
        • map Node TypeScript formats directly (cdcc623)
        • use sync module hooks on Node v22.22.3+ (f8992f1)

        This release is also available on:

        Commits
        • 79fddde fix(watch): treat script and dependency paths literally
        • e818ad6 perf: index transform cache lazily
        • cdcc623 perf: map Node TypeScript formats directly
        • d067938 perf: load esbuild lazily in CLI
        • 95d0672 fix(watch): avoid clearing piped output
        • 6fd4607 docs: add per-page metadata
        • f4176d8 docs: generate sitemap
        • 8d4ffc2 fix: support tsImport after global preload
        • f0e89b2 docs: document Node's public type-stripping API vs internal loader path
        • f8992f1 perf: use sync module hooks on Node v22.22.3+
        • See full diff in compare view

        Updates `typescript` from 6.0.3 to 7.0.2
        Commits
        Maintainer changes

        This version was pushed to npm by microsoft1es, a new releaser for typescript since your current version.


        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../src/jvmTest/typescript/package-lock.json | 391 +++++++++++++++++- .../src/jvmTest/typescript/package.json | 6 +- 2 files changed, 379 insertions(+), 18 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index 4d0c500..c9d73bf 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -15,10 +15,10 @@ "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^26.1.0", + "@types/node": "^26.1.1", "cors": "^2.8.6", - "tsx": "^4.23.0", - "typescript": "^6.0.3" + "tsx": "^4.23.1", + "typescript": "^7.0.2" } }, "node_modules/@esbuild/aix-ppc64": { @@ -579,9 +579,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "26.1.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.0.tgz", - "integrity": "sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw==", + "version": "26.1.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz", + "integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==", "dev": true, "license": "MIT", "dependencies": { @@ -623,6 +623,346 @@ "@types/node": "*" } }, + "node_modules/@typescript/typescript-aix-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-loong64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-mips64el": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-riscv64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-s390x": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", + "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-sunos-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -1680,9 +2020,9 @@ } }, "node_modules/tsx": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.0.tgz", - "integrity": "sha512-eUdUIaCr963q2h5u3+QwvYp0+eqPvn+egeqZUm0hwERCqqx1E3kK5ehbGCvqSE5MQAULr67ww0cA3jKc3YkM1w==", + "version": "4.23.1", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.1.tgz", + "integrity": "sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1713,17 +2053,38 @@ } }, "node_modules/typescript": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", + "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", "dev": true, "license": "Apache-2.0", "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "tsc": "bin/tsc" }, "engines": { - "node": ">=14.17" + "node": ">=16.20.0" + }, + "optionalDependencies": { + "@typescript/typescript-aix-ppc64": "7.0.2", + "@typescript/typescript-darwin-arm64": "7.0.2", + "@typescript/typescript-darwin-x64": "7.0.2", + "@typescript/typescript-freebsd-arm64": "7.0.2", + "@typescript/typescript-freebsd-x64": "7.0.2", + "@typescript/typescript-linux-arm": "7.0.2", + "@typescript/typescript-linux-arm64": "7.0.2", + "@typescript/typescript-linux-loong64": "7.0.2", + "@typescript/typescript-linux-mips64el": "7.0.2", + "@typescript/typescript-linux-ppc64": "7.0.2", + "@typescript/typescript-linux-riscv64": "7.0.2", + "@typescript/typescript-linux-s390x": "7.0.2", + "@typescript/typescript-linux-x64": "7.0.2", + "@typescript/typescript-netbsd-arm64": "7.0.2", + "@typescript/typescript-netbsd-x64": "7.0.2", + "@typescript/typescript-openbsd-arm64": "7.0.2", + "@typescript/typescript-openbsd-x64": "7.0.2", + "@typescript/typescript-sunos-x64": "7.0.2", + "@typescript/typescript-win32-arm64": "7.0.2", + "@typescript/typescript-win32-x64": "7.0.2" } }, "node_modules/undici-types": { diff --git a/integration-test/src/jvmTest/typescript/package.json b/integration-test/src/jvmTest/typescript/package.json index bf81140..0cbee82 100644 --- a/integration-test/src/jvmTest/typescript/package.json +++ b/integration-test/src/jvmTest/typescript/package.json @@ -16,9 +16,9 @@ "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^26.1.0", + "@types/node": "^26.1.1", "cors": "^2.8.6", - "tsx": "^4.23.0", - "typescript": "^6.0.3" + "tsx": "^4.23.1", + "typescript": "^7.0.2" } } From 8f104268c787e3af2e37b625d45b08b80299d429 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Jul 2026 16:51:27 +0200 Subject: [PATCH 368/449] chore(deps): bump the kotest group across 1 directory with 3 updates (#888) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the kotest group with 3 updates in the / directory: [io.kotest:kotest-assertions-core](https://github.com/kotest/kotest), [io.kotest:kotest-assertions-json](https://github.com/kotest/kotest) and [io.kotest:kotest-assertions-ktor](https://github.com/kotest/kotest). Updates `io.kotest:kotest-assertions-core` from 6.2.1 to 6.2.2
        Release notes

        Sourced from io.kotest:kotest-assertions-core's releases.

        v6.2.2

        What's Changed

        New Contributors

        Full Changelog: https://github.com/kotest/kotest/compare/v6.2.1...v6.2.2

        Commits
        • 4e55e4c Update dependency io.projectreactor.tools:blockhound to v1.0.17.RELEASE (#6164)
        • 3e0202f fix: attach Fail fast scope tracker for each root test case in instance per s...
        • 134c12b Update dependency io.mockk:mockk to v1.14.11 (#6163)
        • 01c7429 fix(kotest-assertions-core): Handle null map values in shouldBeEqualUsingFiel...
        • 44887a5 fix(kotest-framework-engine): Use display-name rendering for ignored tests in...
        • b68ef3e disable return-value-checker for releases (#6165)
        • 86a7d93 Fix typo in proptest config docs (#6155)
        • 1677bd5 Add documentation for OffsetDateTime tolerance matcher (#6161)
        • 47f3efc Document instant-based ZonedDateTime plusOrMinus comparison (#6159)
        • See full diff in compare view

        Updates `io.kotest:kotest-assertions-json` from 6.2.1 to 6.2.2
        Release notes

        Sourced from io.kotest:kotest-assertions-json's releases.

        v6.2.2

        What's Changed

        New Contributors

        Full Changelog: https://github.com/kotest/kotest/compare/v6.2.1...v6.2.2

        Commits
        • 4e55e4c Update dependency io.projectreactor.tools:blockhound to v1.0.17.RELEASE (#6164)
        • 3e0202f fix: attach Fail fast scope tracker for each root test case in instance per s...
        • 134c12b Update dependency io.mockk:mockk to v1.14.11 (#6163)
        • 01c7429 fix(kotest-assertions-core): Handle null map values in shouldBeEqualUsingFiel...
        • 44887a5 fix(kotest-framework-engine): Use display-name rendering for ignored tests in...
        • b68ef3e disable return-value-checker for releases (#6165)
        • 86a7d93 Fix typo in proptest config docs (#6155)
        • 1677bd5 Add documentation for OffsetDateTime tolerance matcher (#6161)
        • 47f3efc Document instant-based ZonedDateTime plusOrMinus comparison (#6159)
        • See full diff in compare view

        Updates `io.kotest:kotest-assertions-ktor` from 6.2.1 to 6.2.2
        Release notes

        Sourced from io.kotest:kotest-assertions-ktor's releases.

        v6.2.2

        What's Changed

        New Contributors

        Full Changelog: https://github.com/kotest/kotest/compare/v6.2.1...v6.2.2

        Commits
        • 4e55e4c Update dependency io.projectreactor.tools:blockhound to v1.0.17.RELEASE (#6164)
        • 3e0202f fix: attach Fail fast scope tracker for each root test case in instance per s...
        • 134c12b Update dependency io.mockk:mockk to v1.14.11 (#6163)
        • 01c7429 fix(kotest-assertions-core): Handle null map values in shouldBeEqualUsingFiel...
        • 44887a5 fix(kotest-framework-engine): Use display-name rendering for ignored tests in...
        • b68ef3e disable return-value-checker for releases (#6165)
        • 86a7d93 Fix typo in proptest config docs (#6155)
        • 1677bd5 Add documentation for OffsetDateTime tolerance matcher (#6161)
        • 47f3efc Document instant-based ZonedDateTime plusOrMinus comparison (#6159)
        • See full diff in compare view

        Updates `io.kotest:kotest-assertions-json` from 6.2.1 to 6.2.2
        Release notes

        Sourced from io.kotest:kotest-assertions-json's releases.

        v6.2.2

        What's Changed

        New Contributors

        Full Changelog: https://github.com/kotest/kotest/compare/v6.2.1...v6.2.2

        Commits
        • 4e55e4c Update dependency io.projectreactor.tools:blockhound to v1.0.17.RELEASE (#6164)
        • 3e0202f fix: attach Fail fast scope tracker for each root test case in instance per s...
        • 134c12b Update dependency io.mockk:mockk to v1.14.11 (#6163)
        • 01c7429 fix(kotest-assertions-core): Handle null map values in shouldBeEqualUsingFiel...
        • 44887a5 fix(kotest-framework-engine): Use display-name rendering for ignored tests in...
        • b68ef3e disable return-value-checker for releases (#6165)
        • 86a7d93 Fix typo in proptest config docs (#6155)
        • 1677bd5 Add documentation for OffsetDateTime tolerance matcher (#6161)
        • 47f3efc Document instant-based ZonedDateTime plusOrMinus comparison (#6159)
        • See full diff in compare view

        Updates `io.kotest:kotest-assertions-ktor` from 6.2.1 to 6.2.2
        Release notes

        Sourced from io.kotest:kotest-assertions-ktor's releases.

        v6.2.2

        What's Changed

        New Contributors

        Full Changelog: https://github.com/kotest/kotest/compare/v6.2.1...v6.2.2

        Commits
        • 4e55e4c Update dependency io.projectreactor.tools:blockhound to v1.0.17.RELEASE (#6164)
        • 3e0202f fix: attach Fail fast scope tracker for each root test case in instance per s...
        • 134c12b Update dependency io.mockk:mockk to v1.14.11 (#6163)
        • 01c7429 fix(kotest-assertions-core): Handle null map values in shouldBeEqualUsingFiel...
        • 44887a5 fix(kotest-framework-engine): Use display-name rendering for ignored tests in...
        • b68ef3e disable return-value-checker for releases (#6165)
        • 86a7d93 Fix typo in proptest config docs (#6155)
        • 1677bd5 Add documentation for OffsetDateTime tolerance matcher (#6161)
        • 47f3efc Document instant-based ZonedDateTime plusOrMinus comparison (#6159)
        • See full diff in compare view

        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0d9b5de..035058d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,7 +15,7 @@ netty = "4.2.15.Final" awaitility = "4.3.0" collections-immutable = "0.5.1" coroutines = "1.11.0" -kotest = "6.2.1" +kotest = "6.2.2" kotlinx-io = "0.9.1" ktor = "3.5.1" logging = "8.0.4" From dbd34ff327c654e3811326192cc0bceb2543992e Mon Sep 17 00:00:00 2001 From: Klyne Chrysler Date: Thu, 16 Jul 2026 00:38:21 +0800 Subject: [PATCH 369/449] close stateless streamable http session after request completion (#872) fixes #786 stateless streamable http endpoint never closed its per request session, so the session registry and notification subscriptions grew forever. now the session is closed in a finally block after each request, which triggers the existing cleanup. also removed the per request server.onClose logging callback that accumulated on shared servers. added tests proving sessions are removed after normal and rejected requests. jvmTest, ktlint, detekt, apiCheck all pass. no public api changes. Co-authored-by: Pavel Gorgulov --- .../kotlin/sdk/server/KtorServer.kt | 14 ++- ...elessStreamableHttpSessionLifecycleTest.kt | 100 ++++++++++++++++++ 2 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StatelessStreamableHttpSessionLifecycleTest.kt diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt index ec0652b..73ef258 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt @@ -380,11 +380,17 @@ private suspend fun RoutingContext.mcpStatelessStreamableHttpEndpoint( logger.info { "New stateless StreamableHttp connection established without sessionId" } val server = block() - server.onClose { logger.info { "Server connection closed without sessionId" } } - server.createSession(transport) + val session = server.createSession(transport) - transport.handleRequest(null, this.call) - logger.debug { "Server connected to transport without sessionId" } + try { + transport.handleRequest(null, this.call) + logger.debug { "Server connected to transport without sessionId" } + } finally { + // A stateless session serves exactly one request: close it so the server's + // session registry and notification subscriptions do not grow unboundedly. + session.close() + logger.debug { "Stateless session closed after request completion" } + } } private suspend fun RoutingContext.mcpPostEndpoint(transportManager: TransportManager) { diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StatelessStreamableHttpSessionLifecycleTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StatelessStreamableHttpSessionLifecycleTest.kt new file mode 100644 index 0000000..3646faf --- /dev/null +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StatelessStreamableHttpSessionLifecycleTest.kt @@ -0,0 +1,100 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.kotest.assertions.nondeterministic.eventually +import io.kotest.matchers.maps.shouldBeEmpty +import io.kotest.matchers.shouldBe +import io.ktor.client.request.HttpRequestBuilder +import io.ktor.client.request.header +import io.ktor.client.request.post +import io.ktor.client.request.setBody +import io.ktor.http.ContentType +import io.ktor.http.HttpHeaders +import io.ktor.http.HttpStatusCode +import io.ktor.http.contentType +import io.ktor.server.testing.testApplication +import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest +import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION +import io.modelcontextprotocol.kotlin.sdk.types.McpJson +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.toJSON +import kotlin.test.Test +import kotlin.time.Duration.Companion.seconds + +/** + * Verifies that the stateless Streamable HTTP endpoint does not leak server sessions: + * every session created to serve a single request must be closed and removed from the + * session registry once that request completes (https://github.com/modelcontextprotocol/kotlin-sdk/issues/786). + */ +class StatelessStreamableHttpSessionLifecycleTest { + + private fun testServer(): Server = Server( + Implementation("test-server", "1.0"), + ServerOptions(capabilities = ServerCapabilities()), + ) + + @Test + fun `stateless endpoint removes session after each request`() = testApplication { + val server = testServer() + application { + mcpStatelessStreamableHttp { server } + } + + repeat(5) { + val response = client.post("/mcp") { + addStreamableHeaders() + setBody(initializeRequestBody()) + } + response.status shouldBe HttpStatusCode.OK + } + + eventually(5.seconds) { + server.sessions.shouldBeEmpty() + } + } + + @Test + fun `stateless endpoint removes session when the request is rejected`() = testApplication { + val server = testServer() + application { + mcpStatelessStreamableHttp { server } + } + + // Missing "text/event-stream" in Accept makes the transport reject the request. + val response = client.post("/mcp") { + header(HttpHeaders.Host, "localhost") + header(HttpHeaders.Accept, ContentType.Application.Json.toString()) + contentType(ContentType.Application.Json) + setBody(initializeRequestBody()) + } + response.status shouldBe HttpStatusCode.NotAcceptable + + eventually(5.seconds) { + server.sessions.shouldBeEmpty() + } + } + + private fun HttpRequestBuilder.addStreamableHeaders() { + header(HttpHeaders.Host, "localhost") + header( + HttpHeaders.Accept, + listOf(ContentType.Application.Json, ContentType.Text.EventStream).joinToString(", "), + ) + contentType(ContentType.Application.Json) + } + + private fun initializeRequestBody(): String { + val request = InitializeRequest( + InitializeRequestParams( + protocolVersion = LATEST_PROTOCOL_VERSION, + capabilities = ClientCapabilities(), + clientInfo = Implementation(name = "test-client", version = "1.0.0"), + ), + ).toJSON() + + return McpJson.encodeToString(JSONRPCMessage.serializer(), request) + } +} From e464e69a081aea36cb5d11393e6f2ae4d4eb9c9a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Jul 2026 23:43:10 +0200 Subject: [PATCH 370/449] chore(deps): bump io.netty:netty-bom from 4.2.15.Final to 4.2.16.Final in the infrastructure group across 1 directory (#889) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the infrastructure group with 1 update in the / directory: [io.netty:netty-bom](https://github.com/netty/netty). Updates `io.netty:netty-bom` from 4.2.15.Final to 4.2.16.Final
        Release notes

        Sourced from io.netty:netty-bom's releases.

        netty-4.2.16.Final

        What's Changed

        ... (truncated)

        Commits
        • 3703d79 [maven-release-plugin] prepare release netty-4.2.16.Final
        • 63bbb2c Update rust toolchain - add required parameters
        • ac06c1b Update rust toolchain
        • 5b68c61 Merge branches from forks (#17063)
        • de5d276 Update lz4-java to 1.11.1 (#17061)
        • da22048 Pin github actions to reduce risk (#17043)
        • 0332676 Fix maxAllocation for brotli-encoded content in HttpContentDecompressor (#17037)
        • 7364401 Support QPACK sensitivity detector for Never Indexed header fields (#17026)
        • 06faf18 Allow WebSocket extension negotiation to be disabled per response (#17030)
        • bc4b983 Implement Adaptive Cumulator (#16731)
        • Additional commits viewable in compare view

        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 035058d..235f0e9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,7 +9,7 @@ kover = "0.9.8" ktlint = "14.2.0" knit = "0.5.1" mavenPublish = "0.37.0" -netty = "4.2.15.Final" +netty = "4.2.16.Final" # libraries version awaitility = "4.3.0" From c984bc7a327dd251d42cea0b7cdbb1ecf7ffb561 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Jul 2026 13:39:09 +0200 Subject: [PATCH 371/449] chore(deps): bump ch.qos.logback:logback-classic from 1.5.37 to 1.5.38 (#890) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.37 to 1.5.38.
        Release notes

        Sourced from ch.qos.logback:logback-classic's releases.

        Logback 1.5.38

        2026-07-09 Release of logback version 1.5.38

        • In HardenedObjectInputStream, fixed a typo preventing Throwable objects from being white-filtered. This issue was reported in [PR #1045](qos-ch/logback#1045) by t0rchwo0d.

        • A bitwise identical binary of this version can be reproduced by building from source code at commit d04984a41fce42977466f45a2f076f0ee5cc4207 associated with the tag v_1.5.38. Release built using Java "21" 2023-10-17 LTS build 21.0.1.+12-LTS-29 under Linux Debian 11.6.

        Commits
        • d04984a prepare release 1.5.38
        • 4fffda6 updateversion of maven-gpg-plugin
        • 7ee48a1 Fixed a typo where java,lang.Throwable was written with a comma instead of a ...
        • 84580a2 remove license profile
        • f817c8d start work on 1.5.28-SNAPSHOT
        • See full diff in compare view

        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 235f0e9..8591f0d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,7 +22,7 @@ logging = "8.0.4" mockk = "1.14.11" serialization = "1.11.0" slf4j = "2.0.18" -logback = "1.5.37" +logback = "1.5.38" junit = "5.14.4" [libraries] From 5cb76fd0401dc33ff279520456dfee62033f363e Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Thu, 16 Jul 2026 15:41:46 +0200 Subject: [PATCH 372/449] fix(core): clear recently-cancelled request ids on connection close (#892) Clear the recently-cancelled request id buffer in `doClose` so connection teardown fully resets per-request bookkeeping. ## Motivation and Context Follow-up to #884. `doClose` already resets `_responseHandlers`, `_progressHandlers`, and `inFlightRequestJobs`, but left `recentlyCancelledRequestIds` populated across a close. This aligns teardown to clear all Protocol-scoped per-request state. Not a correctness bug: the buffer is bounded (256) and self-evicting, request ids are random UUIDs so stale entries cannot false-match a new connection, and the post-close `onMessage` guard drops late messages from the old transport before the buffer is consulted. The change removes the lone asymmetry in `doClose`. ## How Has This Been Tested? - `./gradlew :kotlin-sdk-core:compileKotlinJvm` - `./gradlew :kotlin-sdk-core:jvmTest --tests "*ProtocolTest*"` ## Breaking Changes None. ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 ## Additional context `recentlyCancelledRequestIds` is Protocol-scoped (like `_responseHandlers`/`_progressHandlers`), so it is reset in `doClose` rather than on the per-connection `Connection` object. --- .../kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt index 3515b4a..0c82f5e 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt @@ -436,6 +436,7 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio } val handlersToNotify = _responseHandlers.getAndSet(persistentMapOf()).values.toList() _progressHandlers.getAndSet(persistentMapOf()) + recentlyCancelledRequestIds.getAndSet(persistentListOf()) connection.inFlightRequestJobs.getAndSet(persistentMapOf()) connection.handlerScope.cancel() onClose() From 309d88fe1d4c3e2553553031290b001d65587fef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 13:58:22 +0200 Subject: [PATCH 373/449] chore(deps): bump the other-dependencies group across 2 directories with 3 updates (#897) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the other-dependencies group with 2 updates in the /samples/kotlinlang-mcp-server directory: [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) and [com.gradleup.shadow](https://github.com/GradleUp/shadow). Bumps the other-dependencies group with 2 updates in the /samples/simple-streamable-server directory: [com.gradleup.shadow](https://github.com/GradleUp/shadow) and [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit-framework). Updates `ch.qos.logback:logback-classic` from 1.5.37 to 1.5.38
        Release notes

        Sourced from ch.qos.logback:logback-classic's releases.

        Logback 1.5.38

        2026-07-09 Release of logback version 1.5.38

        • In HardenedObjectInputStream, fixed a typo preventing Throwable objects from being white-filtered. This issue was reported in [PR #1045](qos-ch/logback#1045) by t0rchwo0d.

        • A bitwise identical binary of this version can be reproduced by building from source code at commit d04984a41fce42977466f45a2f076f0ee5cc4207 associated with the tag v_1.5.38. Release built using Java "21" 2023-10-17 LTS build 21.0.1.+12-LTS-29 under Linux Debian 11.6.

        Commits
        • d04984a prepare release 1.5.38
        • 4fffda6 updateversion of maven-gpg-plugin
        • 7ee48a1 Fixed a typo where java,lang.Throwable was written with a comma instead of a ...
        • 84580a2 remove license profile
        • f817c8d start work on 1.5.28-SNAPSHOT
        • See full diff in compare view

        Updates `com.gradleup.shadow` from 9.5.1 to 9.6.0
        Release notes

        Sourced from com.gradleup.shadow's releases.

        9.6.0

        Added

        • Extract R8 rules from dependency JARs when using minimize { r8 { ... } }. (#2089)

        Changed

        • Rename ShadowDslMarker to ShadowDsl. (#2091)
        • POTENTIALLY BREAKING: Apply @ShadowDsl to ShadowJar, ResourceTransformer, DependencyFilter, and Relocator. (#2090)
          This restricts nested DSL configuration blocks from implicitly calling outer receiver APIs in Kotlin script files.

        Fixed

        • Validate ZIP entry names to prevent Zip Slip path traversal.
        • Avoid resolving unused R8 dependency. (#2101)
        Commits

        Updates `com.gradleup.shadow` from 9.5.1 to 9.6.0
        Release notes

        Sourced from com.gradleup.shadow's releases.

        9.6.0

        Added

        • Extract R8 rules from dependency JARs when using minimize { r8 { ... } }. (#2089)

        Changed

        • Rename ShadowDslMarker to ShadowDsl. (#2091)
        • POTENTIALLY BREAKING: Apply @ShadowDsl to ShadowJar, ResourceTransformer, DependencyFilter, and Relocator. (#2090)
          This restricts nested DSL configuration blocks from implicitly calling outer receiver APIs in Kotlin script files.

        Fixed

        • Validate ZIP entry names to prevent Zip Slip path traversal.
        • Avoid resolving unused R8 dependency. (#2101)
        Commits

        Updates `com.gradleup.shadow` from 9.5.1 to 9.6.0
        Release notes

        Sourced from com.gradleup.shadow's releases.

        9.6.0

        Added

        • Extract R8 rules from dependency JARs when using minimize { r8 { ... } }. (#2089)

        Changed

        • Rename ShadowDslMarker to ShadowDsl. (#2091)
        • POTENTIALLY BREAKING: Apply @ShadowDsl to ShadowJar, ResourceTransformer, DependencyFilter, and Relocator. (#2090)
          This restricts nested DSL configuration blocks from implicitly calling outer receiver APIs in Kotlin script files.

        Fixed

        • Validate ZIP entry names to prevent Zip Slip path traversal.
        • Avoid resolving unused R8 dependency. (#2101)
        Commits

        Updates `org.junit.jupiter:junit-jupiter` from 6.1.1 to 6.1.2
        Release notes

        Sourced from org.junit.jupiter:junit-jupiter's releases.

        JUnit 6.1.2 = Platform 6.1.2 + Jupiter 6.1.2 + Vintage 6.1.2

        See Release Notes.

        Full Changelog: https://github.com/junit-team/junit-framework/compare/r6.1.1...r6.1.2

        Commits
        • b685426 Release 6.1.2
        • ae244a6 Remove blanket outputDirectoryCreator from SuiteEngineTests (#5793)
        • 43bd154 Finalize 6.1.2 release notes
        • 0cd9510 Fix order of release note sections
        • a1507cb Add initial 6.1.2 release notes to release notes index
        • 9326641 Fix NoTestsDiscoveredException for suites containing only dynamic tests (#5839)
        • 2ef1123 Create initial 6.1.2 release notes from template
        • 83fa9ab Back to snapshots for further development
        • See full diff in compare view

        Updates `com.gradleup.shadow` from 9.5.1 to 9.6.0
        Release notes

        Sourced from com.gradleup.shadow's releases.

        9.6.0

        Added

        • Extract R8 rules from dependency JARs when using minimize { r8 { ... } }. (#2089)

        Changed

        • Rename ShadowDslMarker to ShadowDsl. (#2091)
        • POTENTIALLY BREAKING: Apply @ShadowDsl to ShadowJar, ResourceTransformer, DependencyFilter, and Relocator. (#2090)
          This restricts nested DSL configuration blocks from implicitly calling outer receiver APIs in Kotlin script files.

        Fixed

        • Validate ZIP entry names to prevent Zip Slip path traversal.
        • Avoid resolving unused R8 dependency. (#2101)
        Commits

        Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
        Dependabot commands and options
        You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlinlang-mcp-server/gradle/libs.versions.toml | 4 ++-- samples/simple-streamable-server/gradle/libs.versions.toml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml index 029aae2..047c0bf 100644 --- a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml @@ -4,8 +4,8 @@ ktor = "3.5.1" mcp-kotlin = "0.14.0" serialization = "1.11.0" coroutines = "1.11.0" -logback = "1.5.37" -shadow = "9.5.1" +logback = "1.5.38" +shadow = "9.6.0" [libraries] ktor-bom = { group = "io.ktor", name = "ktor-bom", version.ref = "ktor" } diff --git a/samples/simple-streamable-server/gradle/libs.versions.toml b/samples/simple-streamable-server/gradle/libs.versions.toml index 376bc9b..bbf7120 100644 --- a/samples/simple-streamable-server/gradle/libs.versions.toml +++ b/samples/simple-streamable-server/gradle/libs.versions.toml @@ -1,10 +1,10 @@ [versions] kotlin = "2.4.0" ktor = "3.5.1" -junit = "6.1.1" +junit = "6.1.2" mcp-kotlin = "0.14.0" slf4j = "2.0.18" -shadow = "9.5.1" +shadow = "9.6.0" [libraries] ktor-bom = { group = "io.ktor", name = "ktor-bom", version.ref = "ktor" } From 0ea42519ed6e83ef3d8e3ef928e1f0df6e96115c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 15:46:44 +0200 Subject: [PATCH 374/449] chore(deps): bump org.jetbrains.kotlin.jvm from 2.4.0 to 2.4.10 in /samples/kotlin-mcp-client (#899) Bumps [org.jetbrains.kotlin.jvm](https://github.com/JetBrains/kotlin) from 2.4.0 to 2.4.10.
        Release notes

        Sourced from org.jetbrains.kotlin.jvm's releases.

        Kotlin 2.4.10

        Changelog

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        Compiler

        • KT-86939 JVM: IllegalStateException "No value for annotation parameter" when using const val in nested Java annotation array argument
        • KT-83766 K2: Wrong sourcePsi is set for SymbolPsiLiteral in SLC for annotation arguments referencing a const val
        • KT-86728 Reified type inference: expected type not propagated into inline call inside lambda with elvis operator

        Compose Compiler

        • b/522127447 Compose Compiler 2.4: classes previously inferred stable now reported runtime/Uncertain

        Klibs

        • KT-86501 Native: IrTypeAliasSymbolImpl is already bound. Signature: kotlinx.datetime/Instant|null[0] on iosSimulatorArm64

        Tools. CLI

        • KT-86930 Introduce kotlinr in the Kotlin distribution

        Tools. Gradle. BCV

        • KT-87223 Gradle, BCV: open version range in kotlinAbiValidationCompatClasspath causes kotlin-build-tools-impl to resolve to 2.4.20-Beta1 instead of 2.4.0

        Tools. Gradle. JS

        • KT-87304 jsBrowserTest fails with "exited with errors (exit code: 1)"
        • KT-86057 kotlinUpgradeYarnLock skips lock file regeneration when kotlinNpmInstall is up-to-date, causing kotlinStoreYarnLock to fail

        Tools. Gradle. Multiplatform

        • KT-87084 False positive warning for JS and Wasm compilations when CRI is enabled

        Tools. Scripts

        • KT-87076 @file:CompilerOptions("-jvm-target", ...) ignored in .main.kts scripts in Kotlin 2.4.0, falling back to JVM target 1.8
        • KT-86352 K2 scripting: FirResolvedTypeRef exception when resolving extension functions from imported scripts

        Kotlin 2.4.10-RC2

        Changelog

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        ... (truncated)

        Changelog

        Sourced from org.jetbrains.kotlin.jvm's changelog.

        2.4.10

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        Compiler

        • KT-86939 JVM: IllegalStateException "No value for annotation parameter" when using const val in nested Java annotation array argument
        • KT-83766 K2: Wrong sourcePsi is set for SymbolPsiLiteral in SLC for annotation arguments referencing a const val
        • KT-86728 Reified type inference: expected type not propagated into inline call inside lambda with elvis operator

        Compose Compiler

        • b/522127447 Compose Compiler 2.4: classes previously inferred stable now reported runtime/Uncertain

        Klibs

        • KT-86501 Native: IrTypeAliasSymbolImpl is already bound. Signature: kotlinx.datetime/Instant|null[0] on iosSimulatorArm64

        Tools. CLI

        • KT-86930 Introduce kotlinr in the Kotlin distribution

        Tools. Gradle. BCV

        • KT-87223 Gradle, BCV: open version range in kotlinAbiValidationCompatClasspath causes kotlin-build-tools-impl to resolve to 2.4.20-Beta1 instead of 2.4.0

        Tools. Gradle. JS

        • KT-87304 jsBrowserTest fails with "exited with errors (exit code: 1)"
        • KT-86057 kotlinUpgradeYarnLock skips lock file regeneration when kotlinNpmInstall is up-to-date, causing kotlinStoreYarnLock to fail

        Tools. Gradle. Multiplatform

        • KT-87084 False positive warning for JS and Wasm compilations when CRI is enabled

        Tools. Scripts

        • KT-87076 @file:CompilerOptions("-jvm-target", ...) ignored in .main.kts scripts in Kotlin 2.4.0, falling back to JVM target 1.8
        • KT-86352 K2 scripting: FirResolvedTypeRef exception when resolving extension functions from imported scripts
        Commits
        • 5687445 Add Changelog for 2.4.10-RC2
        • ba30392 [Wasm] Support regenerate unchanged modules flag on KGP
        • 6307e73 [Wasm] Implement Xwasm-IC-generate-unchanged-modules flag
        • 39d7aee Add ChangeLog for 2.4.10-RC2
        • a2956be [Gradle] Update karma.conf.js to use 'require(...)' instead plain strings
        • 9b1361c [Gradle] KT-87223: Updated regression test
        • a4c153a [Gradle] KT-87223: Fix toochain to 2.4.0 for abiValidation compat config
        • bc04b90 Add ChangeLog for 2.4.10-RC
        • bbcb94b [CRI] explicitly disable CRI generation for non-JVM targets to avoid false-po...
        • 6c2c458 Scripting: fix jvmTarget processing from script annotations
        • Additional commits viewable in compare view

        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-client/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index f674a64..732dfbd 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] anthropic = "2.48.0" -kotlin = "2.4.0" +kotlin = "2.4.10" ktor = "3.5.1" mcp-kotlin = "0.14.0" shadow = "9.5.1" From 078e739424af944089cfab48a0a0ed271f161950 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 16:49:15 +0200 Subject: [PATCH 375/449] chore(deps): bump org.jetbrains.kotlinx.kover from 0.9.8 to 0.9.9 in the gradle-plugins group across 1 directory (#902) Bumps the gradle-plugins group with 1 update in the / directory: [org.jetbrains.kotlinx.kover](https://github.com/Kotlin/kotlinx-kover). Updates `org.jetbrains.kotlinx.kover` from 0.9.8 to 0.9.9
        Release notes

        Sourced from org.jetbrains.kotlinx.kover's releases.

        0.9.9

        Kover Gradle Plugin

        Bugfixes

        Changelog

        Sourced from org.jetbrains.kotlinx.kover's changelog.

        0.9.9 / 2026-07-17

        Kover Gradle Plugin

        Bugfixes

        Commits

        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8591f0d..6d3c5a8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ binaryCompatibilityValidatorPlugin = "0.18.1" detekt = "2.0.0-alpha.5" dokka = "2.2.0" kotlin = "2.4.0" -kover = "0.9.8" +kover = "0.9.9" ktlint = "14.2.0" knit = "0.5.1" mavenPublish = "0.37.0" From 737b46f03299f038270b30176fc3b2f849770266 Mon Sep 17 00:00:00 2001 From: Takuya Akamatsu Date: Wed, 22 Jul 2026 01:17:16 +0900 Subject: [PATCH 376/449] feat(server): validate accepted elicitation content against the requested schema (#896) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validate accepted form-mode elicitation `content` against the `requestedSchema` on the server, rejecting mismatches with an `INVALID_PARAMS` error. fixes #895 ## Motivation and Context `createElicitation` returned accepted content to server code as-is. The specification's form-mode security guidance says "Servers SHOULD validate received data matches the requested schema" ([2025-11-25, Form Mode Security](https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation#form-mode-security)). Comparable validation already exists in the TypeScript SDK's `elicitInput` and Python's `elicit_with_validation` helper. Details and design notes are in #895. ## How Has This Been Tested? New unit tests for the validator and integration tests over an in-memory client–server pair. ## Breaking Changes No API changes. Behavioral note: an accepted form-mode response that does not match the requested schema now fails `createElicitation` with `McpException` (`INVALID_PARAMS`) instead of being returned to the caller. ## 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed Co-authored-by: Pavel Gorgulov --- .../kotlin/sdk/client/ClientTest.kt | 97 ++++ .../kotlin/sdk/server/ClientConnection.kt | 22 +- .../sdk/server/ElicitationValidation.kt | 174 +++++++ .../kotlin/sdk/server/ServerSession.kt | 7 +- .../sdk/server/ElicitationValidationTest.kt | 438 ++++++++++++++++++ 5 files changed, 735 insertions(+), 3 deletions(-) create mode 100644 kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ElicitationValidation.kt create mode 100644 kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ElicitationValidationTest.kt diff --git a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt index 54d25ee..099329d 100644 --- a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt +++ b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt @@ -1124,6 +1124,103 @@ class ClientTest { assertEquals(elicitationResultContent, result.content) } + @Test + fun `should reject accepted elicitation content that does not match requested schema`() = runTest { + val requestedSchema = ElicitRequestParams.RequestedSchema( + properties = mapOf("name" to StringSchema()), + required = listOf("name"), + ) + val (client, serverSession) = setupElicitationPair { + ElicitResult(action = ElicitResult.Action.Accept, content = buildJsonObject { put("name", 42) }) + } + + val exception = assertFailsWith { + serverSession.createElicitation(message = "mismatch", requestedSchema = requestedSchema) + } + assertTrue( + exception.message.orEmpty() + .startsWith("Elicitation response content does not match requested schema: "), + "Unexpected message: ${exception.message}", + ) + assertTrue("'name' must be string" in exception.message.orEmpty(), "Unexpected message: ${exception.message}") + + client.close() + } + + @Test + fun `should reject accepted elicitation without content when schema has required properties`() = runTest { + val requestedSchema = ElicitRequestParams.RequestedSchema( + properties = mapOf("name" to StringSchema()), + required = listOf("name"), + ) + val (client, serverSession) = setupElicitationPair { + ElicitResult(action = ElicitResult.Action.Accept, content = null) + } + + val exception = assertFailsWith { + serverSession.createElicitation(message = "no content", requestedSchema = requestedSchema) + } + assertTrue( + "must have required property 'name'" in exception.message.orEmpty(), + "Unexpected message: ${exception.message}", + ) + + client.close() + } + + @Test + fun `should accept elicitation without content when schema has no required properties`() = runTest { + val requestedSchema = ElicitRequestParams.RequestedSchema( + properties = mapOf("name" to StringSchema()), + ) + val (client, serverSession) = setupElicitationPair { + ElicitResult(action = ElicitResult.Action.Accept, content = null) + } + + val result = serverSession.createElicitation(message = "no required", requestedSchema = requestedSchema) + + assertEquals(ElicitResult.Action.Accept, result.action) + assertNull(result.content) + + client.close() + } + + @Test + fun `should pass through declined elicitation without validation`() = runTest { + val requestedSchema = ElicitRequestParams.RequestedSchema( + properties = mapOf("name" to StringSchema()), + required = listOf("name"), + ) + val (client, serverSession) = setupElicitationPair { + ElicitResult(action = ElicitResult.Action.Decline) + } + + val result = serverSession.createElicitation(message = "decline", requestedSchema = requestedSchema) + + assertEquals(ElicitResult.Action.Decline, result.action) + assertNull(result.content) + + client.close() + } + + @Test + fun `should pass through cancelled elicitation without validation`() = runTest { + val requestedSchema = ElicitRequestParams.RequestedSchema( + properties = mapOf("name" to StringSchema()), + required = listOf("name"), + ) + val (client, serverSession) = setupElicitationPair { + ElicitResult(action = ElicitResult.Action.Cancel) + } + + val result = serverSession.createElicitation(message = "cancel", requestedSchema = requestedSchema) + + assertEquals(ElicitResult.Action.Cancel, result.action) + assertNull(result.content) + + client.close() + } + @Test fun `should apply elicitation defaults for missing fields in empty content`() = runTest { val schema = defaultsTestSchema() diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnection.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnection.kt index 343774b..21e0925 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnection.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ClientConnection.kt @@ -10,12 +10,14 @@ 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.EmptyJsonObject import io.modelcontextprotocol.kotlin.sdk.types.EmptyResult import io.modelcontextprotocol.kotlin.sdk.types.IncludeContext import io.modelcontextprotocol.kotlin.sdk.types.ListRootsRequest import io.modelcontextprotocol.kotlin.sdk.types.ListRootsResult import io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel import io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotification +import io.modelcontextprotocol.kotlin.sdk.types.McpException import io.modelcontextprotocol.kotlin.sdk.types.PingRequest import io.modelcontextprotocol.kotlin.sdk.types.PromptListChangedNotification import io.modelcontextprotocol.kotlin.sdk.types.Request @@ -95,12 +97,17 @@ public interface ClientConnection { * Sends a message to the client requesting elicitation. * This typically results in a form being displayed to the end user. * + * Accepted content is validated against [requestedSchema]. If content is absent, an empty + * object is used for validation only, so declared `required` properties still apply. The + * returned [ElicitResult] is not modified. + * * @param message The message for the elicitation to display. - * @param requestedSchema The schema requested by the client for the elicitation result. + * @param requestedSchema The schema the server requests for the elicitation result. * Influences the form displayed to the user. * @param options Optional request options. * @return The result of the elicitation request. * @throws IllegalStateException If the server or client does not support elicitation. + * @throws McpException If an accepted response does not match [requestedSchema]. */ public suspend fun createElicitation( message: String, @@ -130,10 +137,15 @@ public interface ClientConnection { * Sends a message to the client requesting elicitation. * This typically results in a form being displayed to the user. * + * For form-mode requests, accepted content is validated against the requested schema. If + * content is absent, an empty object is used for validation only, so declared `required` + * properties still apply. The returned [ElicitResult] is not modified. + * * @param request The elicitation request parameters. * @param options Optional request options. * @return The result of the elicitation request. * @throws IllegalStateException If the server or client does not support elicitation. + * @throws McpException If an accepted form-mode response does not match its requested schema. */ public suspend fun createElicitation(request: ElicitRequest, options: RequestOptions? = null): ElicitResult @@ -256,7 +268,13 @@ internal class ClientConnectionImpl(private val session: ServerSession) : Client } } logger.trace { "ElicitRequest: $request" } - return request(request, options) + val result: ElicitResult = request(request, options) + if (params is ElicitRequestFormParams && result.action == ElicitResult.Action.Accept) { + // Peer-response backstop: accepted form content is client-supplied. Absent content is + // validated as an empty object so declared `required` properties still apply. + validateElicitationContent(params.requestedSchema, result.content ?: EmptyJsonObject) + } + return result } override suspend fun createElicitation( diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ElicitationValidation.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ElicitationValidation.kt new file mode 100644 index 0000000..d976d34 --- /dev/null +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ElicitationValidation.kt @@ -0,0 +1,174 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.modelcontextprotocol.kotlin.sdk.types.BooleanSchema +import io.modelcontextprotocol.kotlin.sdk.types.DoubleSchema +import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.IntegerSchema +import io.modelcontextprotocol.kotlin.sdk.types.LegacyTitledEnumSchema +import io.modelcontextprotocol.kotlin.sdk.types.McpException +import io.modelcontextprotocol.kotlin.sdk.types.PrimitiveSchemaDefinition +import io.modelcontextprotocol.kotlin.sdk.types.RPCError +import io.modelcontextprotocol.kotlin.sdk.types.StringSchema +import io.modelcontextprotocol.kotlin.sdk.types.TitledMultiSelectEnumSchema +import io.modelcontextprotocol.kotlin.sdk.types.TitledSingleSelectEnumSchema +import io.modelcontextprotocol.kotlin.sdk.types.UntitledMultiSelectEnumSchema +import io.modelcontextprotocol.kotlin.sdk.types.UntitledSingleSelectEnumSchema +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.booleanOrNull +import kotlinx.serialization.json.doubleOrNull +import kotlinx.serialization.json.longOrNull +import kotlin.math.floor + +/** + * Validates accepted form-mode elicitation [content] against the supported constraints in + * [requestedSchema]: types, `required`, numeric bounds, string lengths, enum membership, and item + * counts. Undeclared properties are permitted. + * [StringSchema.format] values are treated as annotations and are not asserted. + * + * @throws McpException with [RPCError.ErrorCode.INVALID_PARAMS] listing the detected violations + * (at most one per property). + */ +internal fun validateElicitationContent(requestedSchema: ElicitRequestParams.RequestedSchema, content: JsonObject) { + val errors = buildList { + for (name in requestedSchema.required.orEmpty()) { + if (name !in content) add("must have required property '$name'") + } + for ((name, schema) in requestedSchema.properties) { + val value = content[name] ?: continue + checkProperty(name, schema, value)?.let(::add) + } + } + if (errors.isNotEmpty()) { + throw McpException( + code = RPCError.ErrorCode.INVALID_PARAMS, + message = "Elicitation response content does not match requested schema: ${errors.joinToString(", ")}", + ) + } +} + +private fun checkProperty(name: String, schema: PrimitiveSchemaDefinition, value: JsonElement): String? = + when (schema) { + is StringSchema -> { + val string = stringValue(value) ?: return "'$name' must be string" + val minLength = schema.minLength + val maxLength = schema.maxLength + val length = codePointCount(string) + when { + minLength != null && length < minLength -> + "'$name' must NOT have fewer than $minLength characters" + + maxLength != null && length > maxLength -> + "'$name' must NOT have more than $maxLength characters" + + else -> null + } + } + + is IntegerSchema -> { + val integer = integerValue(value) ?: return "'$name' must be integer" + checkRange(name, integer, schema.minimum?.toLong(), schema.maximum?.toLong()) + } + + is DoubleSchema -> { + val number = doubleValue(value) ?: return "'$name' must be number" + checkRange(name, number, schema.minimum, schema.maximum) + } + + is BooleanSchema -> if (booleanValue(value) == null) "'$name' must be boolean" else null + + is UntitledSingleSelectEnumSchema -> checkEnum(name, value, schema.enumValues) + + is TitledSingleSelectEnumSchema -> checkEnum(name, value, schema.oneOf.map { it.const }) + + is LegacyTitledEnumSchema -> checkEnum(name, value, schema.enumValues) + + is UntitledMultiSelectEnumSchema -> + checkMultiSelect(name, value, schema.items.enumValues, schema.minItems, schema.maxItems) + + is TitledMultiSelectEnumSchema -> + checkMultiSelect(name, value, schema.items.anyOf.map { it.const }, schema.minItems, schema.maxItems) + } + +// Keep Double statically typed so comparisons use IEEE 754 semantics; generic Comparable ordering +// treats -0.0 as less than 0.0. +private fun checkRange( + name: String, + value: Long, + minimum: Long?, + maximum: Long?, +): String? = when { + minimum != null && value < minimum -> "'$name' must be >= $minimum" + maximum != null && value > maximum -> "'$name' must be <= $maximum" + else -> null +} + +private fun checkRange( + name: String, + value: Double, + minimum: Double?, + maximum: Double?, +): String? = when { + minimum != null && value < minimum -> "'$name' must be >= $minimum" + maximum != null && value > maximum -> "'$name' must be <= $maximum" + else -> null +} + +private fun checkEnum(name: String, value: JsonElement, allowed: List): String? { + val string = stringValue(value) ?: return "'$name' must be string" + return if (string in allowed) null else "'$name' must be equal to one of the allowed values" +} + +private fun checkMultiSelect( + name: String, + value: JsonElement, + allowed: List, + minItems: Int?, + maxItems: Int?, +): String? { + val array = value as? JsonArray ?: return "'$name' must be array" + if (minItems != null && array.size < minItems) return "'$name' must NOT have fewer than $minItems items" + if (maxItems != null && array.size > maxItems) return "'$name' must NOT have more than $maxItems items" + for (element in array) { + val item = stringValue(element) ?: return "'$name' items must be string" + if (item !in allowed) return "'$name' items must be equal to one of the allowed values" + } + return null +} + +// JSON distinguishes the string "42" from the number 42; kotlinx's JsonPrimitive accessors parse +// string content too, so every helper checks isString to preserve that distinction. + +private fun stringValue(value: JsonElement): String? = (value as? JsonPrimitive)?.takeIf { it.isString }?.content + +private fun integerValue(value: JsonElement): Long? { + val primitive = (value as? JsonPrimitive)?.takeIf { !it.isString } ?: return null + primitive.longOrNull?.let { return it } + // JSON Schema's "integer" also matches numbers with a zero fractional part (e.g. 5.0, 1e2). + // Values that do not parse as Long are evaluated using their finite Double representation. + val number = primitive.doubleOrNull ?: return null + return if (number.isFinite() && number == floor(number)) number.toLong() else null +} + +private fun doubleValue(value: JsonElement): Double? = + (value as? JsonPrimitive)?.takeIf { !it.isString }?.doubleOrNull?.takeIf { it.isFinite() } + +private fun booleanValue(value: JsonElement): Boolean? = + (value as? JsonPrimitive)?.takeIf { !it.isString }?.booleanOrNull + +// JSON Schema string lengths count Unicode code points (RFC 8259 characters), not UTF-16 units, +// so a surrogate pair is one character. +private fun codePointCount(string: String): Int { + var count = 0 + var index = 0 + while (index < string.length) { + if (string[index].isHighSurrogate() && index + 1 < string.length && string[index + 1].isLowSurrogate()) { + index++ + } + index++ + count++ + } + return count +} diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt index 880f1cf..f20d3e1 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSession.kt @@ -421,12 +421,17 @@ public open class ServerSession( * Sends a message to the client requesting an elicitation. * This typically results in a form being displayed to the end user. * + * Accepted content is validated against [requestedSchema]. If content is absent, an empty + * object is used for validation only, so declared `required` properties still apply. The + * returned [ElicitResult] is not modified. + * * @param message The message for the elicitation to display. - * @param requestedSchema The schema requested by the client for the elicitation result. + * @param requestedSchema The schema the server requests for the elicitation result. * Influences the form displayed to the user. * @param options Optional request options. * @return The result of the elicitation request. * @throws IllegalStateException If the server or client does not support elicitation. + * @throws McpException If an accepted response does not match [requestedSchema]. */ public suspend fun createElicitation( message: String, diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ElicitationValidationTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ElicitationValidationTest.kt new file mode 100644 index 0000000..b218b47 --- /dev/null +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ElicitationValidationTest.kt @@ -0,0 +1,438 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.modelcontextprotocol.kotlin.sdk.types.BooleanSchema +import io.modelcontextprotocol.kotlin.sdk.types.DoubleSchema +import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.EnumOption +import io.modelcontextprotocol.kotlin.sdk.types.IntegerSchema +import io.modelcontextprotocol.kotlin.sdk.types.LegacyTitledEnumSchema +import io.modelcontextprotocol.kotlin.sdk.types.McpException +import io.modelcontextprotocol.kotlin.sdk.types.McpJson +import io.modelcontextprotocol.kotlin.sdk.types.PrimitiveSchemaDefinition +import io.modelcontextprotocol.kotlin.sdk.types.RPCError +import io.modelcontextprotocol.kotlin.sdk.types.StringSchema +import io.modelcontextprotocol.kotlin.sdk.types.StringSchemaFormat +import io.modelcontextprotocol.kotlin.sdk.types.TitledMultiSelectEnumSchema +import io.modelcontextprotocol.kotlin.sdk.types.TitledSingleSelectEnumSchema +import io.modelcontextprotocol.kotlin.sdk.types.UntitledMultiSelectEnumSchema +import io.modelcontextprotocol.kotlin.sdk.types.UntitledSingleSelectEnumSchema +import kotlinx.serialization.json.JsonNull +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.add +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import kotlinx.serialization.json.putJsonArray +import kotlinx.serialization.json.putJsonObject +import org.junit.jupiter.api.assertDoesNotThrow +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertTrue + +/** + * Unit tests for [validateElicitationContent]: pure-function checks of accepted form-mode + * content against the requested schema. + * + * End-to-end tests for the `createElicitation` round trip live in the `integration-test` + * module under `io.modelcontextprotocol.kotlin.sdk.client`. + */ +class ElicitationValidationTest { + + private fun schema(vararg properties: Pair, required: List? = null) = + ElicitRequestParams.RequestedSchema(properties = mapOf(*properties), required = required) + + private fun assertRejected( + schema: ElicitRequestParams.RequestedSchema, + content: JsonObject, + expectedError: String, + ) { + val exception = assertFailsWith { validateElicitationContent(schema, content) } + assertEquals(RPCError.ErrorCode.INVALID_PARAMS, exception.code) + val message = exception.message.orEmpty() + assertTrue( + message.startsWith("Elicitation response content does not match requested schema: "), + "Unexpected message prefix: $message", + ) + assertTrue(expectedError in message, "Expected '$expectedError' in: $message") + } + + @Test + fun `validate content matching schema passes`() { + assertDoesNotThrow { + validateElicitationContent( + schema( + "name" to StringSchema(minLength = 1, maxLength = 10), + "age" to IntegerSchema(minimum = 0, maximum = 120), + "score" to DoubleSchema(minimum = 0.0, maximum = 1.0), + "subscribed" to BooleanSchema(), + "color" to UntitledSingleSelectEnumSchema(enumValues = listOf("red", "green")), + "size" to TitledSingleSelectEnumSchema( + oneOf = listOf(EnumOption("s", "Small"), EnumOption("l", "Large")), + ), + "tags" to UntitledMultiSelectEnumSchema( + items = UntitledMultiSelectEnumSchema.Items(enumValues = listOf("a", "b", "c")), + minItems = 1, + maxItems = 2, + ), + required = listOf("name", "age"), + ), + buildJsonObject { + put("name", "octocat") + put("age", 42) + put("score", 0.5) + put("subscribed", true) + put("color", "red") + put("size", "l") + putJsonArray("tags") { add("a") } + }, + ) + } + } + + @Test + fun `validate missing required property fails`() { + assertRejected( + schema("name" to StringSchema(), required = listOf("name")), + JsonObject(emptyMap()), + "must have required property 'name'", + ) + } + + @Test + fun `validate empty content with no required properties passes`() { + assertDoesNotThrow { + validateElicitationContent(schema("name" to StringSchema()), JsonObject(emptyMap())) + } + } + + @Test + fun `validate wrong type for string property fails`() { + assertRejected( + schema("name" to StringSchema()), + buildJsonObject { put("name", 42) }, + "'name' must be string", + ) + } + + @Test + fun `validate string length constraints fail`() { + assertRejected( + schema("name" to StringSchema(minLength = 3)), + buildJsonObject { put("name", "ab") }, + "'name' must NOT have fewer than 3 characters", + ) + assertRejected( + schema("name" to StringSchema(maxLength = 3)), + buildJsonObject { put("name", "abcd") }, + "'name' must NOT have more than 3 characters", + ) + } + + @Test + fun `validate json string is not accepted for integer boolean or number`() { + // JsonPrimitive("42") is the JSON string "42", not the number 42; the validator must not + // parse string content as a number. + assertRejected( + schema("age" to IntegerSchema()), + buildJsonObject { put("age", "42") }, + "'age' must be integer", + ) + assertRejected( + schema("score" to DoubleSchema()), + buildJsonObject { put("score", "1.5") }, + "'score' must be number", + ) + assertRejected( + schema("subscribed" to BooleanSchema()), + buildJsonObject { put("subscribed", "true") }, + "'subscribed' must be boolean", + ) + } + + @Test + fun `validate integer outside minimum and maximum fails`() { + assertRejected( + schema("age" to IntegerSchema(minimum = 0)), + buildJsonObject { put("age", -1) }, + "'age' must be >= 0", + ) + assertRejected( + schema("age" to IntegerSchema(maximum = 120)), + buildJsonObject { put("age", 200) }, + "'age' must be <= 120", + ) + } + + @Test + fun `validate non-integral value for integer property fails`() { + assertRejected( + schema("age" to IntegerSchema()), + buildJsonObject { put("age", 3.5) }, + "'age' must be integer", + ) + } + + @Test + fun `validate integral number forms are accepted for integer property`() { + // JSON Schema's "integer" matches numbers with a zero fractional part. + assertDoesNotThrow { + validateElicitationContent( + schema("age" to IntegerSchema(minimum = 0, maximum = 200)), + buildJsonObject { put("age", 5.0) }, + ) + } + } + + @Test + fun `validate exponent form is accepted for integer property`() { + val content = McpJson.parseToJsonElement("""{"age": 1e2}""") as JsonObject + assertDoesNotThrow { + validateElicitationContent( + schema("age" to IntegerSchema(minimum = 0, maximum = 200)), + content, + ) + } + } + + @Test + fun `validate number outside range fails`() { + assertRejected( + schema("score" to DoubleSchema(maximum = 1.0)), + buildJsonObject { put("score", 1.5) }, + "'score' must be <= 1.0", + ) + } + + @Test + fun `validate integral value is accepted for number property`() { + assertDoesNotThrow { + validateElicitationContent( + schema("score" to DoubleSchema(minimum = 0.0)), + buildJsonObject { put("score", 5) }, + ) + } + } + + @Test + fun `validate string length counts unicode code points`() { + // JSON Schema string length counts RFC 8259 characters: one emoji is one character + // even when it is two UTF-16 units. + assertDoesNotThrow { + validateElicitationContent( + schema("emoji" to StringSchema(maxLength = 1)), + buildJsonObject { put("emoji", "😀") }, + ) + } + assertRejected( + schema("emoji" to StringSchema(minLength = 2)), + buildJsonObject { put("emoji", "😀") }, + "'emoji' must NOT have fewer than 2 characters", + ) + } + + @Test + fun `validate string format is treated as an annotation`() { + assertDoesNotThrow { + validateElicitationContent( + schema("email" to StringSchema(format = StringSchemaFormat.Email)), + buildJsonObject { put("email", "not-an-email") }, + ) + } + } + + @Test + fun `validate negative zero satisfies an inclusive zero bound`() { + // IEEE 754 comparison: -0.0 == 0.0, so an inclusive minimum of 0.0 admits -0.0. + assertDoesNotThrow { + validateElicitationContent( + schema("score" to DoubleSchema(minimum = 0.0)), + buildJsonObject { put("score", -0.0) }, + ) + } + } + + @Test + fun `validate enum value not in allowed set fails`() { + assertRejected( + schema("color" to UntitledSingleSelectEnumSchema(enumValues = listOf("red", "green"))), + buildJsonObject { put("color", "blue") }, + "'color' must be equal to one of the allowed values", + ) + assertRejected( + schema("size" to TitledSingleSelectEnumSchema(oneOf = listOf(EnumOption("s", "Small")))), + buildJsonObject { put("size", "xl") }, + "'size' must be equal to one of the allowed values", + ) + assertRejected( + schema("legacy" to LegacyTitledEnumSchema(enumValues = listOf("a", "b"))), + buildJsonObject { put("legacy", "c") }, + "'legacy' must be equal to one of the allowed values", + ) + } + + @Test + fun `validate multi-select constraints fail`() { + val tags = UntitledMultiSelectEnumSchema( + items = UntitledMultiSelectEnumSchema.Items(enumValues = listOf("a", "b", "c")), + minItems = 1, + maxItems = 2, + ) + assertRejected( + schema("tags" to tags), + buildJsonObject { put("tags", "a") }, + "'tags' must be array", + ) + assertRejected( + schema("tags" to tags), + buildJsonObject { + putJsonArray("tags") { + add("a") + add(42) + } + }, + "'tags' items must be string", + ) + assertRejected( + schema("tags" to tags), + buildJsonObject { + putJsonArray("tags") { + add("a") + add("z") + } + }, + "'tags' items must be equal to one of the allowed values", + ) + assertRejected( + schema("tags" to tags), + buildJsonObject { putJsonArray("tags") {} }, + "'tags' must NOT have fewer than 1 items", + ) + assertRejected( + schema("tags" to tags), + buildJsonObject { + putJsonArray("tags") { + add("a") + add("b") + add("c") + } + }, + "'tags' must NOT have more than 2 items", + ) + } + + @Test + fun `validate titled multi-select membership uses option consts`() { + val sizes = TitledMultiSelectEnumSchema( + items = TitledMultiSelectEnumSchema.Items( + anyOf = listOf(EnumOption("s", "Small"), EnumOption("l", "Large")), + ), + ) + assertDoesNotThrow { + validateElicitationContent( + schema("sizes" to sizes), + buildJsonObject { putJsonArray("sizes") { add("s") } }, + ) + } + assertRejected( + schema("sizes" to sizes), + buildJsonObject { putJsonArray("sizes") { add("Small") } }, + "'sizes' items must be equal to one of the allowed values", + ) + } + + @Test + fun `validate json null is rejected with the type error for each schema kind`() { + // JsonNull is a JsonPrimitive whose content is "null"; it must fail the type check, not + // accidentally parse as a value. + assertRejected( + schema("name" to StringSchema()), + buildJsonObject { put("name", JsonNull) }, + "'name' must be string", + ) + assertRejected( + schema("age" to IntegerSchema()), + buildJsonObject { put("age", JsonNull) }, + "'age' must be integer", + ) + assertRejected( + schema("subscribed" to BooleanSchema()), + buildJsonObject { put("subscribed", JsonNull) }, + "'subscribed' must be boolean", + ) + } + + @Test + fun `validate non-primitive value is rejected for scalar schemas`() { + assertRejected( + schema("name" to StringSchema()), + buildJsonObject { putJsonArray("name") { add("x") } }, + "'name' must be string", + ) + assertRejected( + schema("age" to IntegerSchema()), + buildJsonObject { putJsonObject("age") {} }, + "'age' must be integer", + ) + } + + @Test + fun `validate number below minimum fails`() { + assertRejected( + schema("score" to DoubleSchema(minimum = 0.0)), + buildJsonObject { put("score", -0.5) }, + "'score' must be >= 0.0", + ) + } + + @Test + fun `validate non-finite value for number property fails`() { + // McpJson is lenient, so an unquoted NaN literal can arrive off the wire; it decodes to + // the same primitive as Double.NaN here. + assertRejected( + schema("score" to DoubleSchema(minimum = 0.0, maximum = 1.0)), + buildJsonObject { put("score", Double.NaN) }, + "'score' must be number", + ) + } + + @Test + fun `validate required property not declared in properties is still required`() { + // JSON Schema's `required` is independent of `properties`. + assertRejected( + schema("name" to StringSchema(), required = listOf("name", "consent")), + buildJsonObject { put("name", "octocat") }, + "must have required property 'consent'", + ) + } + + @Test + fun `validate properties not declared in schema pass`() { + // JSON Schema default: without an additionalProperties restriction, undeclared keys are permitted. + assertDoesNotThrow { + validateElicitationContent( + schema("name" to StringSchema(), required = listOf("name")), + buildJsonObject { + put("name", "octocat") + put("undeclared", 42) + }, + ) + } + } + + @Test + fun `validate reports every violation in one error`() { + val exception = assertFailsWith { + validateElicitationContent( + schema( + "name" to StringSchema(), + "age" to IntegerSchema(), + required = listOf("name"), + ), + buildJsonObject { put("age", "old") }, + ) + } + val message = exception.message.orEmpty() + assertTrue("must have required property 'name'" in message, message) + assertTrue("'age' must be integer" in message, message) + } +} From 9ef4f80f85ade12128d4f07b311e93eaf3c244e4 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Tue, 21 Jul 2026 18:37:21 +0200 Subject: [PATCH 377/449] fix(server): reject duplicate feature names on registration (#883) Reject duplicate feature registration instead of silently overwriting the existing entry. ## Motivation and Context `FeatureRegistry.add`/`addAll` silently replaced a feature registered under the same key and emitted a spurious `list_changed` notification. Tool/prompt names and resource URIs are unique identifiers in MCP, and the TypeScript SDK already rejects duplicates at registration time (`Tool X is already registered`). - `addTool`/`addPrompt`/`addResource`/`addResourceTemplate` now throw `IllegalArgumentException` on a duplicate name/URI; the existing registration stays intact and no notification is emitted. - `addTools`/`addPrompts`/`addResources` are all-or-nothing: batches with intra-batch duplicates or conflicts with existing keys are rejected without registering anything. - To replace a feature, remove it first (`removeTool` + `addTool`), matching the TypeScript SDK's remove-then-re-register path. No new public API. ## How Has This Been Tested? New `FeatureRegistryTest` unit suite (duplicate rejection, batch atomicity, no notification on rejection, re-add after remove) plus integration tests for tools, prompts, resources, and resource templates. The check also surfaced a real duplicate `addResource` in `AbstractResourceIntegrationTest`, fixed here. `jvmTest` (all modules), `apiCheck`, `ktlintCheck`, and `detekt` pass locally. ## Breaking Changes No public API signature changes (`apiCheck` passes against the existing dump). Behavioral: registering a duplicate name/URI now throws `IllegalArgumentException` instead of silently overwriting; remove the feature first to replace it. ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [x] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- .../kotlin/AbstractResourceIntegrationTest.kt | 17 --- .../sdk/server/ServerBulkFeaturesTest.kt | 74 ++++++++++++ .../kotlin/sdk/server/ServerPromptsTest.kt | 23 ++++ .../sdk/server/ServerResourceTemplateTest.kt | 19 ++++ .../kotlin/sdk/server/ServerResourcesTest.kt | 21 ++++ .../sdk/server/ServerToolsNotificationTest.kt | 22 ++++ .../kotlin/sdk/server/ServerToolsTest.kt | 39 +++++++ .../kotlin/sdk/server/FeatureRegistry.kt | 40 +++++-- .../kotlin/sdk/server/FeatureRegistryTest.kt | 106 ++++++++++++++++++ 9 files changed, 333 insertions(+), 28 deletions(-) create mode 100644 kotlin-sdk-server/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/FeatureRegistryTest.kt diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt index da7d85b..ab2a854 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractResourceIntegrationTest.kt @@ -77,23 +77,6 @@ abstract class AbstractResourceIntegrationTest : KotlinTestBase() { ) } - server.addResource( - uri = testResourceUri, - name = testResourceName, - description = testResourceDescription, - mimeType = "text/plain", - ) { request -> - ReadResourceResult( - contents = listOf( - TextResourceContents( - text = testResourceContent, - uri = request.params.uri, - mimeType = "text/plain", - ), - ), - ) - } - server.addResource( uri = binaryResourceUri, name = binaryResourceName, diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerBulkFeaturesTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerBulkFeaturesTest.kt index 1eb828f..52f6908 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerBulkFeaturesTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerBulkFeaturesTest.kt @@ -1,5 +1,6 @@ package io.modelcontextprotocol.kotlin.sdk.server +import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.collections.shouldBeEmpty import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder import io.kotest.matchers.collections.shouldHaveSize @@ -57,6 +58,39 @@ class ServerBulkFeaturesTest : AbstractServerFeaturesTest() { tools.map { it.name } shouldContainExactlyInAnyOrder listOf("existing", "new-a", "new-b") } + @Test + fun `addTools should throw and register nothing when a tool is already registered`() = runTest { + server.addTool("existing", "Existing") { CallToolResult(emptyList()) } + + shouldThrow { + server.addTools( + listOf( + RegisteredTool(Tool("new-a", ToolSchema())) { CallToolResult(emptyList()) }, + RegisteredTool(Tool("existing", ToolSchema())) { CallToolResult(emptyList()) }, + ), + ) + } + + val tools = client.listTools().tools + + tools shouldHaveSize 1 + tools.map { it.name } shouldContainExactlyInAnyOrder listOf("existing") + } + + @Test + fun `addTools should throw when batch contains duplicate names`() = runTest { + shouldThrow { + server.addTools( + listOf( + RegisteredTool(Tool("dup", ToolSchema())) { CallToolResult(emptyList()) }, + RegisteredTool(Tool("dup", ToolSchema())) { CallToolResult(emptyList()) }, + ), + ) + } + + client.listTools().tools.shouldBeEmpty() + } + // ── addPrompts ───────────────────────────────────────────────────────────── @Test @@ -89,6 +123,25 @@ class ServerBulkFeaturesTest : AbstractServerFeaturesTest() { prompts.map { it.name } shouldContainExactlyInAnyOrder listOf("existing-prompt", "new-p") } + @Test + fun `addPrompts should throw and register nothing when a prompt is already registered`() = runTest { + server.addPrompt("existing-prompt", "Existing") { GetPromptResult(messages = emptyList()) } + + shouldThrow { + server.addPrompts( + listOf( + RegisteredPrompt(Prompt("new-p")) { GetPromptResult(messages = emptyList()) }, + RegisteredPrompt(Prompt("existing-prompt")) { GetPromptResult(messages = emptyList()) }, + ), + ) + } + + val prompts = client.listPrompts().prompts + + prompts shouldHaveSize 1 + prompts.map { it.name } shouldContainExactlyInAnyOrder listOf("existing-prompt") + } + // ── addResources ─────────────────────────────────────────────────────────── @Test @@ -123,6 +176,27 @@ class ServerBulkFeaturesTest : AbstractServerFeaturesTest() { resources.map { it.uri } shouldContainExactlyInAnyOrder listOf("test://existing-res", "test://new-res") } + @Test + fun `addResources should throw and register nothing when a resource is already registered`() = runTest { + server.addResource("test://existing-res", "Existing", "Existing resource") { + ReadResourceResult(emptyList()) + } + + shouldThrow { + server.addResources( + listOf( + RegisteredResource(Resource("test://new-res", "New")) { ReadResourceResult(emptyList()) }, + RegisteredResource(Resource("test://existing-res", "Dup")) { ReadResourceResult(emptyList()) }, + ), + ) + } + + val resources = client.listResources().resources + + resources shouldHaveSize 1 + resources.map { it.uri } shouldContainExactlyInAnyOrder listOf("test://existing-res") + } + // ── Partial-batch remove ─────────────────────────────────────────────────── @Test diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsTest.kt index d7ae46d..44b1e42 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerPromptsTest.kt @@ -1,5 +1,8 @@ package io.modelcontextprotocol.kotlin.sdk.server +import io.kotest.matchers.string.shouldContain +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequestParams import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult import io.modelcontextprotocol.kotlin.sdk.types.Implementation import io.modelcontextprotocol.kotlin.sdk.types.Method @@ -110,6 +113,26 @@ class ServerPromptsTest : AbstractServerFeaturesTest() { assertFalse(promptListChangedNotificationReceived, "No notification should be sent when prompt doesn't exist") } + @Test + fun `addPrompt should throw when prompt with same name is already registered`() = runTest { + server.addPrompt(Prompt("test-prompt", "Original Prompt", null)) { + GetPromptResult(description = "original", messages = emptyList()) + } + + val exception = assertThrows { + server.addPrompt(Prompt("test-prompt", "Duplicate Prompt", null)) { + GetPromptResult(description = "duplicate", messages = emptyList()) + } + } + exception.message.orEmpty() shouldContain "Prompt \"test-prompt\" is already registered" + + // The original registration is intact + val prompts = client.listPrompts().prompts + assertEquals(1, prompts.size, "Only the original prompt should be registered") + val result = client.getPrompt(GetPromptRequest(GetPromptRequestParams("test-prompt"))) + assertEquals("original", result.description) + } + @Test fun `removePrompt should throw when prompts capability is not supported`() = runTest { var promptListChangedNotificationReceived = false diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourceTemplateTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourceTemplateTest.kt index c8ace19..8405469 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourceTemplateTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourceTemplateTest.kt @@ -5,6 +5,7 @@ import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.maps.shouldContainKey import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain import io.modelcontextprotocol.kotlin.sdk.types.Implementation import io.modelcontextprotocol.kotlin.sdk.types.ListResourceTemplatesRequest import io.modelcontextprotocol.kotlin.sdk.types.McpException @@ -162,6 +163,24 @@ class ServerResourceTemplateTest : AbstractServerFeaturesTest() { removed shouldBe false } + @Test + fun `addResourceTemplate should throw when template with same uriTemplate is already registered`() { + server.addResourceTemplate("test://items/{id}", "Item") { _, _ -> + ReadResourceResult(emptyList()) + } + + val exception = assertThrows { + server.addResourceTemplate("test://items/{id}", "Duplicate Item") { _, _ -> + ReadResourceResult(emptyList()) + } + } + exception.message.orEmpty() shouldContain "ResourceTemplate \"test://items/{id}\" is already registered" + + // The original registration is intact + server.resourceTemplates shouldHaveSize 1 + server.resourceTemplates[0].name shouldBe "Item" + } + @Test fun `addResourceTemplate should throw when resources capability is not supported`() { val noResourcesServer = Server( diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesTest.kt index fafc3c8..a830788 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerResourcesTest.kt @@ -1,7 +1,10 @@ package io.modelcontextprotocol.kotlin.sdk.server +import io.kotest.matchers.string.shouldContain import io.modelcontextprotocol.kotlin.sdk.types.Implementation import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequestParams import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult import io.modelcontextprotocol.kotlin.sdk.types.ResourceListChangedNotification import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities @@ -115,6 +118,24 @@ class ServerResourcesTest : AbstractServerFeaturesTest() { ) } + @Test + fun `addResource should throw when resource with same uri is already registered`() = runTest { + server.addResource("test://resource", "Original", "Original resource") { + ReadResourceResult(listOf(TextResourceContents(text = "original", uri = "test://resource"))) + } + + val exception = assertThrows { + server.addResource("test://resource", "Duplicate", "Duplicate resource") { + ReadResourceResult(listOf(TextResourceContents(text = "duplicate", uri = "test://resource"))) + } + } + exception.message.orEmpty() shouldContain "Resource \"test://resource\" is already registered" + + // The original registration is intact + val result = client.readResource(ReadResourceRequest(ReadResourceRequestParams("test://resource"))) + assertEquals("original", (result.contents.single() as TextResourceContents).text) + } + @Test fun `removeResource should throw when resources capability is not supported`() = runTest { // Create server without resources capability diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsNotificationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsNotificationTest.kt index 3572082..9ac744b 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsNotificationTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsNotificationTest.kt @@ -11,6 +11,7 @@ import kotlinx.coroutines.test.runTest import org.awaitility.kotlin.await import org.awaitility.kotlin.untilAsserted import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -81,6 +82,27 @@ class ServerToolsNotificationTest : AbstractServerFeaturesTest() { } } + @Test + fun `addTool should not send notification when duplicate is rejected`() = runTest { + // Track notifications + val notifications = mutableListOf() + client.setNotificationHandler( + Method.Defined.NotificationsToolsListChanged, + ) { notification -> + notifications.add(notification) + CompletableDeferred(Unit) + } + + server.addTool("test-tool", "Test Tool") { CallToolResult(emptyList()) } + assertThrows { + server.addTool("test-tool", "Duplicate Tool") { CallToolResult(emptyList()) } + } + // Close the server to stop processing further events and flush notifications + server.close() + + assertEquals(1, notifications.size, "Only the first registration should send a notification") + } + @Test fun `notification should not be send when removed tool does not exists`() = runTest { // Track notifications diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsTest.kt index 54c998e..1b3df91 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerToolsTest.kt @@ -1,5 +1,8 @@ package io.modelcontextprotocol.kotlin.sdk.server +import io.kotest.matchers.string.shouldContain +import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest +import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequestParams import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult import io.modelcontextprotocol.kotlin.sdk.types.Implementation import io.modelcontextprotocol.kotlin.sdk.types.Method @@ -108,6 +111,42 @@ class ServerToolsTest : AbstractServerFeaturesTest() { assertTrue(result, "Tool with non-conforming name should have been registered and removable") } + @Test + fun `addTool should throw when tool with same name is already registered`() = runTest { + server.addTool("test-tool", "Original Tool") { + CallToolResult(listOf(TextContent("Original result"))) + } + + val exception = assertThrows { + server.addTool("test-tool", "Duplicate Tool") { + CallToolResult(listOf(TextContent("Duplicate result"))) + } + } + exception.message.orEmpty() shouldContain "Tool \"test-tool\" is already registered" + + // The original registration is intact + val tools = client.listTools().tools + assertEquals(1, tools.size, "Only the original tool should be registered") + val result = client.callTool(CallToolRequest(CallToolRequestParams("test-tool"))) + assertEquals("Original result", (result.content.single() as TextContent).text) + } + + @Test + fun `addTool should succeed after removing existing tool`() = runTest { + server.addTool("test-tool", "Original Tool") { + CallToolResult(listOf(TextContent("Original result"))) + } + + server.removeTool("test-tool") + server.addTool("test-tool", "Replacement Tool") { + CallToolResult(listOf(TextContent("Replacement result"))) + } + + assertEquals(1, client.listTools().tools.size, "Re-adding after removal should not duplicate the tool") + val result = client.callTool(CallToolRequest(CallToolRequestParams("test-tool"))) + assertEquals("Replacement result", (result.content.single() as TextContent).text) + } + @Test fun `removeTool should throw when tools capability is not supported`() = runTest { var toolListChangedNotificationReceived = false diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/FeatureRegistry.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/FeatureRegistry.kt index 0ed6558..142d59c 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/FeatureRegistry.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/FeatureRegistry.kt @@ -20,9 +20,12 @@ internal interface FeatureListener { * A generic registry for managing features of a specified type. This class provides thread-safe * operations for adding, removing, and retrieving features from the registry. * + * Feature keys are unique: [add] and [addAll] reject keys that are already registered. + * To replace a feature, [remove] it first and add the new one. + * * @param T The type of the feature, constrained to implement the [Feature] interface. * @param featureType A string description of the type of feature being managed. - * Used primarily for logging purposes. + * Used for logging and error messages. */ internal class FeatureRegistry(private val featureType: String) { @@ -44,11 +47,11 @@ internal class FeatureRegistry(private val featureType: String) { private val listeners = atomic(persistentListOf()) internal fun addListener(listener: FeatureListener) { - listeners.update { it.add(listener) } + listeners.update { it.adding(listener) } } internal fun removeListener(listener: FeatureListener) { - listeners.update { it.remove(listener) } + listeners.update { it.removing(listener) } } /** @@ -58,27 +61,42 @@ internal class FeatureRegistry(private val featureType: String) { */ internal fun add(feature: T) { logger.info { "Adding $featureType: \"${feature.key}\"" } - val oldMap = registry.getAndUpdate { current -> current.put(feature.key, feature) } - val oldFeature = oldMap[feature.key] + registry.update { current -> + require(!current.containsKey(feature.key)) { + "$featureType \"${feature.key}\" is already registered. Remove it first to replace it." + } + current.putting(feature.key, feature) + } logger.info { "Added $featureType: \"${feature.key}\"" } - notifyFeatureUpdated(oldFeature, feature) + notifyFeatureUpdated(null, feature) } /** * Adds the given list of features to the registry. Each feature is mapped by its key - * and added to the current registry. + * and added to the current registry. The operation is all-or-nothing: if any key is + * rejected, no features are added. * * @param features The list of features to add to the registry. */ internal fun addAll(features: List) { logger.info { "Adding ${featureType}s: ${features.size}" } - val oldMap = registry.getAndUpdate { current -> current.putAll(features.associateBy { it.key }) } + val newEntries = features.associateBy { it.key } + require(newEntries.size == features.size) { + val duplicated = features.groupingBy { it.key }.eachCount().filterValues { it > 1 }.keys + "Duplicate $featureType keys in batch: $duplicated" + } + registry.update { current -> + val conflicting = newEntries.keys.filter { current.containsKey(it) } + require(conflicting.isEmpty()) { + "${featureType}s already registered: $conflicting. Remove them first to replace them." + } + current.puttingAll(newEntries) + } logger.info { "Added ${featureType}s: ${features.size}" } for (feature in features) { - val oldFeature = oldMap[feature.key] - notifyFeatureUpdated(oldFeature, feature) + notifyFeatureUpdated(null, feature) } } @@ -90,7 +108,7 @@ internal class FeatureRegistry(private val featureType: String) { */ internal fun remove(key: FeatureKey): Boolean { logger.info { "Removing $featureType: \"$key\"" } - val oldMap = registry.getAndUpdate { current -> current.remove(key) } + val oldMap = registry.getAndUpdate { current -> current.removing(key) } val removedFeature = oldMap[key] val removed = removedFeature != null diff --git a/kotlin-sdk-server/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/FeatureRegistryTest.kt b/kotlin-sdk-server/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/FeatureRegistryTest.kt new file mode 100644 index 0000000..c6bf72b --- /dev/null +++ b/kotlin-sdk-server/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/FeatureRegistryTest.kt @@ -0,0 +1,106 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.kotest.matchers.shouldBe +import kotlin.test.Test +import kotlin.test.assertFailsWith + +private data class TestFeature(override val key: FeatureKey, val payload: String = "") : Feature + +private class RecordingListener : FeatureListener { + val updatedKeys = mutableListOf() + + override fun onFeatureUpdated(featureKey: String) { + updatedKeys.add(featureKey) + } +} + +class FeatureRegistryTest { + + private val registry = FeatureRegistry("Tool") + private val listener = RecordingListener() + + init { + registry.addListener(listener) + } + + @Test + fun `add should register feature and notify listener`() { + val feature = TestFeature("a") + + registry.add(feature) + + registry.values shouldBe mapOf("a" to feature) + listener.updatedKeys shouldBe listOf("a") + } + + @Test + fun `add should throw when key is already registered`() { + registry.add(TestFeature("a")) + + val exception = assertFailsWith { + registry.add(TestFeature("a", payload = "override")) + } + + exception.message shouldBe "Tool \"a\" is already registered. Remove it first to replace it." + } + + @Test + fun `add should keep original feature and not notify when duplicate is rejected`() { + val original = TestFeature("a", payload = "original") + registry.add(original) + + assertFailsWith { + registry.add(TestFeature("a", payload = "override")) + } + + registry.values shouldBe mapOf("a" to original) + listener.updatedKeys shouldBe listOf("a") + } + + @Test + fun `addAll should register all features and notify per feature`() { + val a = TestFeature("a") + val b = TestFeature("b") + + registry.addAll(listOf(a, b)) + + registry.values shouldBe mapOf("a" to a, "b" to b) + listener.updatedKeys shouldBe listOf("a", "b") + } + + @Test + fun `addAll should throw on duplicate keys within batch and register nothing`() { + val exception = assertFailsWith { + registry.addAll(listOf(TestFeature("a"), TestFeature("b"), TestFeature("a"))) + } + + exception.message shouldBe "Duplicate Tool keys in batch: [a]" + registry.values shouldBe emptyMap() + listener.updatedKeys shouldBe emptyList() + } + + @Test + fun `addAll should throw when any key is already registered and register nothing`() { + val existing = TestFeature("existing") + registry.add(existing) + + val exception = assertFailsWith { + registry.addAll(listOf(TestFeature("new"), TestFeature("existing", payload = "override"))) + } + + exception.message shouldBe "Tools already registered: [existing]. Remove them first to replace them." + registry.values shouldBe mapOf("existing" to existing) + listener.updatedKeys shouldBe listOf("existing") + } + + @Test + fun `add should succeed after the existing feature is removed`() { + registry.add(TestFeature("a", payload = "original")) + registry.remove("a") + + registry.add(TestFeature("a", payload = "replacement")) + + registry.values shouldBe mapOf("a" to TestFeature("a", payload = "replacement")) + listener.updatedKeys shouldBe listOf("a", "a", "a") + } +} From a7bb0ea73ffd0f5b92ab877caa90a99517eb210f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 20:46:05 +0200 Subject: [PATCH 378/449] chore(deps): bump actions/setup-node from 6 to 7 in the all-actions group across 1 directory (#898) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the all-actions group with 1 update in the / directory: [actions/setup-node](https://github.com/actions/setup-node). Updates `actions/setup-node` from 6 to 7
        Release notes

        Sourced from actions/setup-node's releases.

        v7.0.0

        What's Changed

        Enhancements:

        Bug fixes:

        Documentation updates:

        Dependency update:

        New Contributors

        Full Changelog: https://github.com/actions/setup-node/compare/v6...v7.0.0

        v6.5.0

        What's Changed

        Full Changelog: https://github.com/actions/setup-node/compare/v6.4.0...v6.5.0

        v6.4.0

        What's Changed

        Dependency updates:

        New Contributors

        Full Changelog: https://github.com/actions/setup-node/compare/v6...v6.4.0

        v6.3.0

        What's Changed

        Enhancements:

        ... (truncated)

        Commits
        • 8207627 Migrate to ESM and upgrade dependencies (#1574)
        • 04be95c Add cache-primary-key and cache-matched-key as outputs (#1577)
        • 7c2c68d docs: Update caching recommendations to mitigate cache poisoning risks (#1567)
        • 6a61c03 Merge pull request #1569 from jasongin/update-actions-cache-5.1.0
        • 30eb73b Resolve high-severity audit issues
        • 4e1a87a Update dist
        • 360237f Strict equality
        • 4f8aac5 Bump @​actions/cache to 5.1.0, log cache write denied
        • f4a67bb Only use mirrorToken in getManifest if it's provided (#1548)
        • 0355742 Remove dummy NODE_AUTH_TOKEN export (#1558)
        • Additional commits viewable in compare view

        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- .github/workflows/gradle-publish.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2b6e415..147163c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -53,7 +53,7 @@ jobs: - name: Set up Node.js if: matrix.os == 'ubuntu-latest' - uses: actions/setup-node@v6 + uses: actions/setup-node@v7 with: node-version: '22' # increase only after https://github.com/nodejs/node/issues/56645 will be fixed diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml index f9df053..058e041 100644 --- a/.github/workflows/gradle-publish.yml +++ b/.github/workflows/gradle-publish.yml @@ -25,7 +25,7 @@ jobs: distribution: 'temurin' - name: Set up Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@v7 with: node-version: '22' # increase only after https://github.com/nodejs/node/issues/56645 will be fixed From 152a1c88ee774f2bd21ff40e587477e5337931f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jul 2026 01:29:46 +0200 Subject: [PATCH 379/449] chore(deps): bump body-parser from 2.2.2 to 2.3.0 in /integration-test/src/jvmTest/typescript (#907) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [body-parser](https://github.com/expressjs/body-parser) from 2.2.2 to 2.3.0.
        Release notes

        Sourced from body-parser's releases.

        v2.3.0

        Important: Security

        What's Changed

        New Contributors

        Full Changelog: https://github.com/expressjs/body-parser/compare/v2.2.2...v2.3.0

        Changelog

        Sourced from body-parser's changelog.

        2.3.0 / 2026-06-15

        • Security fix for GHSA-v422-hmwv-36x6
        • fix: use static exports instead of lazy getters to improve ESM compatibility
        • feat: add subpath exports for individual parsers
        • fix: improve limit option validation (#698)
          • Invalid limit values (e.g. unparseable strings or NaN) now throw instead of being silently ignored, which previously disabled size limit enforcement
          • null and undefined fall back to the default 100kb limit
        • deps:
          • content-type@^2.0.0
          • http-errors@^2.0.1
          • iconv-lite^0.7.2
          • qs@^6.15.2
          • raw-body@^3.0.2
          • type-is@^2.1.0
        Commits
        • d0f2ace 2.3.0 (#735)
        • 7d03f2f chore: updated deps to latest (#733)
        • 8024ba7 build(deps): bump actions/checkout from 6.0.2 to 6.0.3 (#732)
        • 32b4ed4 build(deps): bump github/codeql-action from 4.35.3 to 4.36.1 (#731)
        • ff0f6b9 docs: update outdated reference to MDN docs (#730)
        • 14d001a refactor: switch to const/let and enable eslint no-var rule (#729)
        • 37f36a2 deps: update content-type and type-is (#728)
        • e1c244b build(deps): bump github/codeql-action from 4.35.1 to 4.35.3 (#723)
        • e01087f build(deps): bump actions/upload-artifact from 7.0.0 to 7.0.1 (#724)
        • a7698d3 build(deps): bump actions/setup-node from 6.3.0 to 6.4.0 (#725)
        • Additional commits viewable in compare view

        [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=body-parser&package-manager=npm_and_yarn&previous-version=2.2.2&new-version=2.3.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
        Dependabot commands and options
        You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/modelcontextprotocol/kotlin-sdk/network/alerts).
        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../src/jvmTest/typescript/package-lock.json | 58 ++++++++++++++----- 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index c9d73bf..83b03fb 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -1010,20 +1010,20 @@ } }, "node_modules/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", - "content-type": "^1.0.5", + "content-type": "^2.0.0", "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" }, "engines": { "node": ">=18" @@ -1033,6 +1033,19 @@ "url": "https://opencollective.com/express" } }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -2039,17 +2052,34 @@ } }, "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", "license": "MIT", "dependencies": { - "content-type": "^1.0.5", + "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/typescript": { From 031a90c8a107beaaebb7f604d93544ae13a9e23c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jul 2026 09:27:20 +0200 Subject: [PATCH 380/449] chore(deps): bump ip-address and express-rate-limit in /integration-test/src/jvmTest/typescript (#906) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [ip-address](https://github.com/beaugunderson/ip-address) and [express-rate-limit](https://github.com/express-rate-limit/express-rate-limit). These dependencies needed to be updated together. Updates `ip-address` from 10.1.0 to 10.2.0
        Commits
        • 80fccaa 10.2.0
        • abaeb4d Type Address4.addressMinusSuffix as non-nilable (closes #143)
        • 2878c29 Preserve subnet prefix through Address6.to4() (closes #123) (#203)
        • 586666e Reject trailing junk in Address6.fromURL (closes #158) (#202)
        • 80bc76e Validate static factories instead of silently overflowing (#201)
        • 98927be Clarify isValid() accepts CIDRs with host bits set (#81)
        • a0eb073 Fix getScope() and broaden getType() classification (closes #122) (#200)
        • ec52105 Add networkForm() for CIDR network-address strings (#199)
        • a9443a7 Add isMapped4() predicate for IPv4-mapped IPv6 addresses (closes #62) (#198)
        • f01d742 Add address-property predicates (private, ULA, loopback, link-local, etc.) (#...
        • Additional commits viewable in compare view

        Updates `express-rate-limit` from 8.3.1 to 8.6.0
        Release notes

        Sourced from express-rate-limit's releases.

        v8.6.0

        You can view the changelog here.

        v8.5.2

        You can view the changelog here.

        v8.5.1

        You can view the changelog here.

        v8.5.0

        You can view the changelog here.

        v8.4.1

        You can view the changelog here.

        v8.4.0

        You can view the changelog here.

        v8.3.2

        You can view the changelog here.

        Commits
        • fffb3c4 8.6.0
        • f366b2d docs: debugging guide, time constants, & v8.6.0 changelog (#652)
        • 593ddd2 fix: make debug output easier to read (#653)
        • ef8c129 fix: Pin safe version of @​asyncapi/specs dev dep (#659)
        • 7b05e0d feat: add time constants to support more readable values for windowMs (#655)
        • 863e730 chore(deps-dev): bump the development-dependencies group with 3 updates (#657)
        • e0e711e fix: correct wording in usage documentation for express-rate-limit (#656)
        • fcd3aa7 chore(deps-dev): bump the development-dependencies group with 3 updates (#651)
        • 99d4298 feat: use debug for debug logging (#641)
        • 23e4dde feat: Run validations once each (#650)
        • Additional commits viewable in compare view

        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../src/jvmTest/typescript/package-lock.json | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index 83b03fb..b778fbb 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -1362,12 +1362,13 @@ } }, "node_modules/express-rate-limit": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz", - "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.6.0.tgz", + "integrity": "sha512-XKJXDsASUOo0LLtFwW5hCcQGH0N4WQc/Rn8/Pvoia+TJFOkkFPvrtW9lZOeeNcxQJspvOIERMwiRLsVFlhHEkA==", "license": "MIT", "dependencies": { - "ip-address": "10.1.0" + "debug": "^4.4.3", + "ip-address": "^10.2.0" }, "engines": { "node": ">= 16" @@ -1588,9 +1589,9 @@ "license": "ISC" }, "node_modules/ip-address": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", - "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", "license": "MIT", "engines": { "node": ">= 12" From 9308473745711b40784cdbc89b6f7ada64c550bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jul 2026 12:26:55 +0200 Subject: [PATCH 381/449] chore(deps): bump fast-uri from 3.1.2 to 3.1.4 in /integration-test/src/jvmTest/typescript (#910) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.2 to 3.1.4.
        Release notes

        Sourced from fast-uri's releases.

        v3.1.4

        ⚠️ Security Release

        Fix for https://github.com/fastify/fast-uri/security/advisories/GHSA-v2hh-gcrm-f6hx

        Full Changelog: https://github.com/fastify/fast-uri/compare/v3.1.3...v3.1.4

        v3.1.3

        ⚠️ Security Release

        Full Changelog: https://github.com/fastify/fast-uri/compare/v3.1.2...v3.1.3

        Commits

        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- integration-test/src/jvmTest/typescript/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index b778fbb..0c70662 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -1387,9 +1387,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", + "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", "funding": [ { "type": "github", From b6cb547d582a89c4e51ac4ae4e29ab1d53dd7ff3 Mon Sep 17 00:00:00 2001 From: King Star Date: Wed, 22 Jul 2026 21:53:01 +0800 Subject: [PATCH 382/449] fix(server): preserve id on duplicate initialize errors (#868) ## Summary Fixes #866. ## Changes - Preserve the parsed JSON-RPC request id when Streamable HTTP rejects a duplicate `initialize` request. - Keep pre-parse and transport-level rejection paths unchanged when the request id is not available. - Extend the duplicate-initialize regression test to assert the error response id as well as the HTTP status. ## Verification ```bash ./gradlew :kotlin-sdk-server:jvmTest --tests 'io.modelcontextprotocol.kotlin.sdk.server.StreamableHttpServerTransportTest.second initialization request returns JSON-RPC error with request id' ./gradlew :kotlin-sdk-server:jvmTest --tests 'io.modelcontextprotocol.kotlin.sdk.server.StreamableHttpServerTransportTest' ./gradlew :kotlin-sdk-server:jvmTest ./gradlew :kotlin-sdk-server:ktlintCheck :kotlin-sdk-server:detekt ``` Co-authored-by: Pavel Gorgulov --- .../server/StreamableHttpServerTransport.kt | 18 ++++++++++++------ .../StreamableHttpServerTransportTest.kt | 9 +++++++-- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt index 1f8ac39..3b02835 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt @@ -459,9 +459,10 @@ public class StreamableHttpServerTransport(private val configuration: Configurat } val messages = parseBody(call) ?: return - val isInitializationRequest = messages.any { - it is JSONRPCRequest && it.method == Method.Defined.Initialize.value + val initializationRequest = messages.filterIsInstance().firstOrNull { + it.method == Method.Defined.Initialize.value } + val isInitializationRequest = initializationRequest != null if (isInitializationRequest) { if (initialized.load() && sessionId != null) { @@ -469,6 +470,7 @@ public class StreamableHttpServerTransport(private val configuration: Configurat HttpStatusCode.BadRequest, RPCError.ErrorCode.INVALID_REQUEST, "Invalid Request: Server already initialized", + initializationRequest.id, ) return } @@ -506,8 +508,7 @@ public class StreamableHttpServerTransport(private val configuration: Configurat // For initialize requests, get from request params. // For other requests, get from header (already validated). val clientProtocolVersion = if (isInitializationRequest) { - val initRequest = messages.first() as JSONRPCRequest - (initRequest.params as? JsonObject)?.get("protocolVersion") + (initializationRequest.params as? JsonObject)?.get("protocolVersion") ?.let { McpJson.decodeFromJsonElement(it) } ?: DEFAULT_NEGOTIATED_PROTOCOL_VERSION } else { @@ -913,7 +914,12 @@ public class StreamableHttpServerTransport(private val configuration: Configurat } } -internal suspend fun ApplicationCall.reject(status: HttpStatusCode, code: Int, message: String) { +internal suspend fun ApplicationCall.reject( + status: HttpStatusCode, + code: Int, + message: String, + id: RequestId? = null, +) { this.response.status(status) - this.respond(JSONRPCError(id = null, error = RPCError(code = code, message = message))) + this.respond(JSONRPCError(id = id, error = RPCError(code = code, message = message))) } diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt index 172889a..0d99f51 100644 --- a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt @@ -38,6 +38,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.Implementation import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequestParams import io.modelcontextprotocol.kotlin.sdk.types.InitializedNotification +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCError import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse @@ -163,7 +164,7 @@ class StreamableHttpServerTransportTest { } @Test - fun `second initialization request returns an HTTP error`() = testApplication { + fun `second initialization request returns JSON-RPC error with request id`() = testApplication { configTestServer() val client = createTestClient() @@ -186,13 +187,17 @@ class StreamableHttpServerTransportTest { firstResponse.status shouldBe HttpStatusCode.OK + val secondRequest = buildInitializeRequestPayload().copy(id = RequestId("second-init")) val secondResponse = client.post(path) { addStreamableHeaders() header("mcp-session-id", firstResponse.headers[MCP_SESSION_ID_HEADER]) - setBody(payload) + setBody(secondRequest) } secondResponse.status shouldBe HttpStatusCode.BadRequest + val error = secondResponse.body() + error.id shouldBe secondRequest.id + error.error.message shouldBe "Invalid Request: Server already initialized" } @Test From cab669b108b0297201e08f5bd703f4e2476c9525 Mon Sep 17 00:00:00 2001 From: King Star Date: Thu, 23 Jul 2026 00:12:12 +0800 Subject: [PATCH 383/449] fix: classify malformed request params as invalid params (#886) ## Summary Classify typed request deserialization failures as JSON-RPC `Invalid Params` instead of `Internal error`. Fixes #830. ## Changes - translate `SerializationException` from typed request decoding to `-32602` - return a stable `Invalid params` message without serialization internals - cover malformed `initialize` requests through session and Streamable HTTP paths ## Scope and risk The catch is limited to request parameter deserialization after the JSON-RPC method has been resolved. Envelope parsing, unknown methods, handler failures, and successful requests keep their existing behavior. ## Verification ```bash ./gradlew :integration-test:jvmTest --tests 'io.modelcontextprotocol.kotlin.sdk.server.ServerSessionInitializeTest' ./gradlew :kotlin-sdk-server:jvmTest --tests 'io.modelcontextprotocol.kotlin.sdk.server.StreamableHttpServerTransportTest' ./gradlew :kotlin-sdk-core:jvmTest --tests 'io.modelcontextprotocol.kotlin.sdk.shared.ProtocolTest' ./gradlew ktlintCheck apiCheck ``` Co-authored-by: Pavel Gorgulov --- .../sdk/server/ServerSessionInitializeTest.kt | 40 +++++++++++++++++++ .../kotlin/sdk/shared/Protocol.kt | 11 ++++- .../StreamableHttpServerTransportTest.kt | 31 ++++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSessionInitializeTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSessionInitializeTest.kt index 82cf91a..38b169a 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSessionInitializeTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerSessionInitializeTest.kt @@ -7,9 +7,11 @@ import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequestParams import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCError import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse import io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION import io.modelcontextprotocol.kotlin.sdk.types.RPCError +import io.modelcontextprotocol.kotlin.sdk.types.RequestId import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.types.toJSON import kotlinx.coroutines.CompletableDeferred @@ -18,9 +20,13 @@ import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest import kotlinx.coroutines.withContext +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import kotlinx.serialization.json.putJsonObject import org.junit.jupiter.api.Test import java.util.concurrent.CopyOnWriteArrayList import kotlin.test.assertEquals +import kotlin.test.assertFalse import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.assertTrue @@ -41,6 +47,18 @@ class ServerSessionInitializeTest { ), ) + private fun createMalformedInitializeRequest(): JSONRPCRequest = JSONRPCRequest( + id = RequestId(1), + method = "initialize", + params = buildJsonObject { + putJsonObject("capabilities") {} + putJsonObject("clientInfo") { + put("name", "repro") + put("version", "0.1.0") + } + }, + ) + @Test fun `should handle first initialize request successfully`() = runTest { val session = createSession() @@ -67,6 +85,28 @@ class ServerSessionInitializeTest { // Both initialize requests arrive before notifications/initialized, in the serial dispatch // phase, so processing and response order stay deterministic. + @Test + fun `should classify malformed initialize params as invalid params`() = runTest { + val session = createSession() + val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() + + val responseDone = CompletableDeferred() + clientTransport.onMessage { message -> + if (message is JSONRPCError) { + responseDone.complete(message) + } + } + + session.connect(serverTransport) + clientTransport.send(createMalformedInitializeRequest()) + + val response = responseDone.await() + assertEquals(RPCError.ErrorCode.INVALID_PARAMS, response.error.code) + assertFalse(response.error.message.contains("kotlinx.serialization")) + assertNull(session.clientCapabilities) + assertNull(session.clientVersion) + } + @Test fun `should reject duplicate initialize request`() = runTest { val session = createSession() diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt index 0c82f5e..908e524 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt @@ -48,6 +48,7 @@ import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeoutOrNull +import kotlinx.serialization.SerializationException import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.encodeToJsonElement @@ -941,7 +942,15 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio _requestHandlers.update { current -> current.putting(method.value) { jSONRPCRequest, extraHandler -> - val request = jSONRPCRequest.fromJSON() + val request = try { + jSONRPCRequest.fromJSON() + } catch (cause: SerializationException) { + throw McpException( + code = RPCError.ErrorCode.INVALID_PARAMS, + message = "Invalid params", + cause = cause, + ) + } val response = wrapped(request as T, extraHandler) response } diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt index 0d99f51..bb522d6 100644 --- a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt @@ -47,6 +47,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.ListResourcesResult import io.modelcontextprotocol.kotlin.sdk.types.ListToolsResult import io.modelcontextprotocol.kotlin.sdk.types.McpJson import io.modelcontextprotocol.kotlin.sdk.types.Method +import io.modelcontextprotocol.kotlin.sdk.types.RPCError import io.modelcontextprotocol.kotlin.sdk.types.RequestId import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.types.Tool @@ -225,6 +226,36 @@ class StreamableHttpServerTransportTest { initResponse.headers[MCP_SESSION_ID_HEADER] shouldBe null } + @Test + fun `malformed initialize params return invalid params over streamable http`() = testApplication { + val mcpPath = "/mcp" + + application { + mcpStreamableHttp(mcpPath, enableDnsRebindingProtection = false) { + Server( + Implementation("test-server", "1.0.0"), + ServerOptions(capabilities = ServerCapabilities()), + ) + } + } + + val client = createTestClient() + + val response = client.post(mcpPath) { + addStreamableHeaders() + setBody( + """ + {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"capabilities":{},"clientInfo":{"name":"repro","version":"0.1.0"}}} + """.trimIndent(), + ) + } + + response.status shouldBe HttpStatusCode.OK + val error = response.body() + error.error.code shouldBe RPCError.ErrorCode.INVALID_PARAMS + error.error.message.contains("kotlinx.serialization") shouldBe false + } + @Test fun `request with unsupported protocol version returns an HTTP error`() = testApplication { configTestServer() From a06d699d6c9dfdf8321e5cbb3f5aebcb0016b6f6 Mon Sep 17 00:00:00 2001 From: UnscientificJsZhai <73090853+UnscientificJsZhai@users.noreply.github.com> Date: Thu, 23 Jul 2026 20:37:37 +0800 Subject: [PATCH 384/449] feat: SSE heartbeat (#761) ## Summary Adds optional SSE heartbeat configuration for streamable HTTP MCP server connections. This lets `Application.mcpStreamableHttp` callers pass Ktor's `Heartbeat` configuration for SSE streams, including heartbeat period and event payload. Heartbeats remain disabled by default, preserving the existing stream behavior unless the new option is explicitly provided. ## Motivation and Context Some MCP clients need heartbeat messages on SSE connections to keep the connection alive. Without those heartbeats, the client may proactively disconnect from the MCP server. MCP server developers need a way to configure an SSE heartbeat mechanism so they can avoid unexpected client disconnects. In my case, my MCP server was consistently disconnected by Gemini CLI. I worked around the issue with some fallback approaches, but I believe the best implementation is to add heartbeat support by using Ktor's built-in API. ## Implementation Notes - Added a function overload of `Application.mcpStreamableHttp` to introduce heartbeat parameter `sseHeartbeatConfig: (Heartbeat.() -> Unit)?`. - Applied the configuration inside the Ktor `sse { ... }` block before handling the existing streamable HTTP transport request. - Updated the server API dump for the new public API surface. ## How Has This Been Tested? Added `StreamableHttpHeartbeatIntegrationTest` covering: - Configured GET SSE streams apply the provided heartbeat configuration. - GET SSE streams do not send default heartbeats when `sseHeartbeatConfig` is omitted. - Configured GET SSE streams emit heartbeat events repeatedly. - Legacy MCP over SSE APIs do not expose the new heartbeat configuration. Verified locally with: ```bash ./gradlew :integration-test:jvmTest --tests "io.modelcontextprotocol.kotlin.sdk.integration.streamablehttp.StreamableHttpHeartbeatIntegrationTest" ``` Result: `BUILD SUCCESSFUL`. ## Breaking Changes None. This change adds a new `Application.mcpStreamableHttp` overload that accepts `sseHeartbeatConfig: (Heartbeat.() -> Unit)?` while keeping the existing overload unchanged. Existing callers continue to compile and keep the previous behavior: SSE heartbeats are not sent unless the new overload is used with an explicit heartbeat configuration. ## 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed ## Additional context Heartbeat behavior is delegated to Ktor's SSE heartbeat support, so applications can use the same configuration semantics they would use in native Ktor SSE routes. Related issue: [SSE needs a heartbeat #344 ](https://github.com/modelcontextprotocol/kotlin-sdk/issues/344) --------- Co-authored-by: Pavel Gorgulov --- .../AbstractStreamableHttpIntegrationTest.kt | 8 +- .../StreamableHttpHeartbeatIntegrationTest.kt | 174 ++++++++++++++++++ kotlin-sdk-server/api/kotlin-sdk-server.api | 4 +- .../kotlin/sdk/server/KtorServer.kt | 7 + 4 files changed, 189 insertions(+), 4 deletions(-) create mode 100644 integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/StreamableHttpHeartbeatIntegrationTest.kt diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/AbstractStreamableHttpIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/AbstractStreamableHttpIntegrationTest.kt index 9bc0c17..535393e 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/AbstractStreamableHttpIntegrationTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/AbstractStreamableHttpIntegrationTest.kt @@ -3,6 +3,7 @@ package io.modelcontextprotocol.kotlin.sdk.integration.streamablehttp import io.ktor.server.cio.CIOApplicationEngine import io.ktor.server.engine.EmbeddedServer import io.ktor.server.engine.embeddedServer +import io.ktor.server.sse.Heartbeat import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions import io.modelcontextprotocol.kotlin.sdk.server.mcpStreamableHttp @@ -17,7 +18,10 @@ import io.ktor.server.cio.CIO as ServerCIO open class AbstractStreamableHttpIntegrationTest { - suspend fun initTestServer(name: String? = null): StreamableHttpTestServer { + suspend fun initTestServer( + name: String? = null, + sseHeartbeatConfig: (Heartbeat.() -> Unit)? = null, + ): StreamableHttpTestServer { val mcpServer = Server( Implementation(name = name ?: DEFAULT_SERVER_NAME, version = VERSION), ServerOptions( @@ -52,7 +56,7 @@ open class AbstractStreamableHttpIntegrationTest { host = URL, port = PORT, ) { - mcpStreamableHttp { mcpServer } + mcpStreamableHttp(sseHeartbeatConfig = sseHeartbeatConfig) { mcpServer } } return StreamableHttpTestServer( diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/StreamableHttpHeartbeatIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/StreamableHttpHeartbeatIntegrationTest.kt new file mode 100644 index 0000000..8019557 --- /dev/null +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/streamablehttp/StreamableHttpHeartbeatIntegrationTest.kt @@ -0,0 +1,174 @@ +package io.modelcontextprotocol.kotlin.sdk.integration.streamablehttp + +import io.kotest.matchers.shouldBe +import io.ktor.client.HttpClient +import io.ktor.client.plugins.sse.SSE +import io.ktor.client.request.header +import io.ktor.client.request.post +import io.ktor.client.request.prepareGet +import io.ktor.client.request.setBody +import io.ktor.client.statement.bodyAsChannel +import io.ktor.http.ContentType +import io.ktor.http.HttpHeaders +import io.ktor.http.HttpStatusCode +import io.ktor.http.contentType +import io.ktor.sse.ServerSentEvent +import io.ktor.utils.io.ByteReadChannel +import io.ktor.utils.io.readUTF8Line +import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest +import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest +import io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION +import io.modelcontextprotocol.kotlin.sdk.types.toJSON +import io.modelcontextprotocol.kotlin.test.utils.actualPort +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeoutOrNull +import kotlinx.serialization.json.Json +import kotlin.test.Test +import kotlin.test.assertNotNull +import kotlin.time.Duration.Companion.milliseconds +import io.ktor.client.engine.cio.CIO as ClientCIO + +private const val SESSION_ID_HEADER = "mcp-session-id" +private const val PROTOCOL_VERSION_HEADER = "mcp-protocol-version" +private const val DEFAULT_HEARTBEAT_LINE = ": heartbeat" +private const val CUSTOM_HEARTBEAT_LINE = ": mcp-heartbeat" + +class StreamableHttpHeartbeatIntegrationTest : AbstractStreamableHttpIntegrationTest() { + + @Test + fun `GET SSE stream emits configured heartbeat`(): Unit = runBlocking(Dispatchers.IO) { + var server: StreamableHttpTestServer? = null + var httpClient: HttpClient? = null + + try { + server = initTestServer("heartbeat-test") { + period = 50.milliseconds + event = ServerSentEvent(comments = "mcp-heartbeat") + } + val mcpUrl = "http://$URL:${server.ktorServer.actualPort()}/mcp" + httpClient = HttpClient(ClientCIO) { install(SSE) } + val sessionId = initializeSession(httpClient, mcpUrl) + + httpClient.prepareGet(mcpUrl) { + addSseHeaders(sessionId) + }.execute { response -> + response.status shouldBe HttpStatusCode.OK + response.headers[SESSION_ID_HEADER] shouldBe sessionId + + response.bodyAsChannel().readLineMatching(CUSTOM_HEARTBEAT_LINE) shouldBe CUSTOM_HEARTBEAT_LINE + } + } finally { + httpClient?.close() + server?.ktorServer?.stopSuspend(1000, 2000) + } + } + + @Test + fun `GET SSE stream does not emit heartbeat by default`(): Unit = runBlocking(Dispatchers.IO) { + var server: StreamableHttpTestServer? = null + var httpClient: HttpClient? = null + + try { + server = initTestServer("no-heartbeat-test") + val mcpUrl = "http://$URL:${server.ktorServer.actualPort()}/mcp" + httpClient = HttpClient(ClientCIO) { install(SSE) } + val sessionId = initializeSession(httpClient, mcpUrl) + + httpClient.prepareGet(mcpUrl) { + addSseHeaders(sessionId) + }.execute { response -> + response.status shouldBe HttpStatusCode.OK + response.headers[SESSION_ID_HEADER] shouldBe sessionId + + response.bodyAsChannel().readLineMatching(timeoutMillis = 150) { line -> + line.isHeartbeatSseLine() + } shouldBe null + } + } finally { + httpClient?.close() + server?.ktorServer?.stopSuspend(1000, 2000) + } + } + + @Test + fun `GET SSE stream emits configured heartbeat repeatedly`(): Unit = runBlocking(Dispatchers.IO) { + var server: StreamableHttpTestServer? = null + var httpClient: HttpClient? = null + + try { + server = initTestServer("repeating-heartbeat-test") { + period = 50.milliseconds + event = ServerSentEvent(comments = "mcp-heartbeat") + } + val mcpUrl = "http://$URL:${server.ktorServer.actualPort()}/mcp" + httpClient = HttpClient(ClientCIO) { install(SSE) } + val sessionId = initializeSession(httpClient, mcpUrl) + + httpClient.prepareGet(mcpUrl) { + addSseHeaders(sessionId) + }.execute { response -> + response.status shouldBe HttpStatusCode.OK + response.headers[SESSION_ID_HEADER] shouldBe sessionId + + val channel = response.bodyAsChannel() + channel.readLineMatching(CUSTOM_HEARTBEAT_LINE) shouldBe CUSTOM_HEARTBEAT_LINE + channel.readLineMatching(CUSTOM_HEARTBEAT_LINE) shouldBe CUSTOM_HEARTBEAT_LINE + } + } finally { + httpClient?.close() + server?.ktorServer?.stopSuspend(1000, 2000) + } + } + + private suspend fun initializeSession(client: HttpClient, mcpUrl: String): String { + val response = client.post(mcpUrl) { + contentType(ContentType.Application.Json) + header( + HttpHeaders.Accept, + "${ContentType.Application.Json}, ${ContentType.Text.EventStream}", + ) + setBody(Json.encodeToString(buildInitPayload())) + } + + response.status shouldBe HttpStatusCode.OK + return assertNotNull(response.headers[SESSION_ID_HEADER]) + } + + private fun buildInitPayload(): JSONRPCRequest = InitializeRequest( + InitializeRequestParams( + protocolVersion = LATEST_PROTOCOL_VERSION, + capabilities = ClientCapabilities(), + clientInfo = Implementation(name = "heartbeat-test-client", version = "1.0.0"), + ), + ).toJSON() + + private fun io.ktor.client.request.HttpRequestBuilder.addSseHeaders(sessionId: String) { + header(HttpHeaders.Accept, ContentType.Text.EventStream.toString()) + header(SESSION_ID_HEADER, sessionId) + header(PROTOCOL_VERSION_HEADER, LATEST_PROTOCOL_VERSION) + } + + private suspend fun ByteReadChannel.readLineMatching(expectedLine: String, timeoutMillis: Long = 2_000): String? = + readLineMatching(timeoutMillis) { line -> line == expectedLine } + + private suspend fun ByteReadChannel.readLineMatching( + timeoutMillis: Long = 2_000, + matches: (String) -> Boolean, + ): String? = withTimeoutOrNull(timeoutMillis.milliseconds) { + var line = readUTF8Line() + while (line != null) { + if (matches(line)) return@withTimeoutOrNull line + line = readUTF8Line() + } + null + } + + private fun String.isHeartbeatSseLine(): Boolean = this == DEFAULT_HEARTBEAT_LINE || + this == CUSTOM_HEARTBEAT_LINE || + (startsWith(":") && contains("heartbeat", ignoreCase = true)) || + (startsWith("event:") && contains("heartbeat", ignoreCase = true)) +} diff --git a/kotlin-sdk-server/api/kotlin-sdk-server.api b/kotlin-sdk-server/api/kotlin-sdk-server.api index 51ac279..1ec2efd 100644 --- a/kotlin-sdk-server/api/kotlin-sdk-server.api +++ b/kotlin-sdk-server/api/kotlin-sdk-server.api @@ -59,8 +59,8 @@ public final class io/modelcontextprotocol/kotlin/sdk/server/KtorServerKt { public static synthetic fun mcp$default (Lio/ktor/server/routing/Route;ZLjava/util/List;Ljava/util/List;JLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V public static final fun mcpStatelessStreamableHttp (Lio/ktor/server/application/Application;Ljava/lang/String;ZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/jvm/functions/Function1;)V public static synthetic fun mcpStatelessStreamableHttp$default (Lio/ktor/server/application/Application;Ljava/lang/String;ZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V - public static final fun mcpStreamableHttp (Lio/ktor/server/application/Application;Ljava/lang/String;ZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/jvm/functions/Function1;)V - public static synthetic fun mcpStreamableHttp$default (Lio/ktor/server/application/Application;Ljava/lang/String;ZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V + public static final fun mcpStreamableHttp (Lio/ktor/server/application/Application;Ljava/lang/String;ZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V + public static synthetic fun mcpStreamableHttp$default (Lio/ktor/server/application/Application;Ljava/lang/String;ZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V } public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredPrompt : io/modelcontextprotocol/kotlin/sdk/server/Feature { diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt index 73ef258..5fc3d22 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt @@ -20,8 +20,10 @@ import io.ktor.server.routing.get import io.ktor.server.routing.post import io.ktor.server.routing.route import io.ktor.server.routing.routing +import io.ktor.server.sse.Heartbeat import io.ktor.server.sse.SSE import io.ktor.server.sse.ServerSSESession +import io.ktor.server.sse.heartbeat import io.ktor.server.sse.sse import io.ktor.utils.io.KtorDsl import io.modelcontextprotocol.kotlin.sdk.types.RPCError @@ -162,6 +164,7 @@ private fun Application.mcpStreamableHttp( allowedHosts: List?, allowedOrigins: List?, configuration: StreamableHttpServerTransport.Configuration, + sseHeartbeatConfig: (Heartbeat.() -> Unit)?, block: RoutingContext.() -> Server, ) { installMcpContentNegotiation() @@ -185,6 +188,7 @@ private fun Application.mcpStreamableHttp( sse { val transport = existingStreamableTransport(call, transportManager) ?: return@sse + sseHeartbeatConfig?.let { config -> heartbeat(config) } transport.handleRequest(this, call) } @@ -227,6 +231,7 @@ private fun Application.mcpStreamableHttp( * With custom `allowedHosts`, `null` skips origin validation. * @param eventStore An optional [EventStore] instance to enable resumable event stream functionality. * Allows storing and replaying events. + * @param sseHeartbeatConfig The heartbeat configuration option for SSE connections. `null` means no heartbeat is sent. * @param block factory block with access to the [RoutingContext] (for reading request headers) * that creates and returns the [Server] to handle the connection. */ @@ -237,6 +242,7 @@ public fun Application.mcpStreamableHttp( allowedHosts: List? = null, allowedOrigins: List? = null, eventStore: EventStore? = null, + sseHeartbeatConfig: (Heartbeat.() -> Unit)? = null, block: RoutingContext.() -> Server, ) { mcpStreamableHttp( @@ -248,6 +254,7 @@ public fun Application.mcpStreamableHttp( eventStore = eventStore, enableJsonResponse = true, ), + sseHeartbeatConfig = sseHeartbeatConfig, block = block, ) } From 82fe56e1e857c992d7de18c3ea90fad040deebf7 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Mon, 27 Jul 2026 13:39:49 +0200 Subject: [PATCH 385/449] fix(server): preserve Streamable HTTP response status and body (#911) Streamable HTTP responses now keep their status when the client accepts only `text/event-stream`. ## How Has This Been Tested? New `StreamableHttpResponseDeliveryTest` ## Breaking Changes None ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- .../kotlin/sdk/server/KtorServer.kt | 23 +++-- .../server/StreamableHttpServerTransport.kt | 27 +++-- .../StreamableHttpResponseDeliveryTest.kt | 99 +++++++++++++++++++ .../StreamableHttpServerTransportTest.kt | 63 +++++++++++- 4 files changed, 191 insertions(+), 21 deletions(-) create mode 100644 kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpResponseDeliveryTest.kt diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt index 5fc3d22..8220a8b 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt @@ -1,6 +1,7 @@ package io.modelcontextprotocol.kotlin.sdk.server import io.github.oshai.kotlinlogging.KotlinLogging +import io.ktor.http.HttpHeaders import io.ktor.http.HttpMethod import io.ktor.http.HttpStatusCode import io.ktor.server.application.Application @@ -282,18 +283,10 @@ private fun Application.mcpStatelessStreamableHttp( ) } get { - call.reject( - HttpStatusCode.MethodNotAllowed, - RPCError.ErrorCode.CONNECTION_CLOSED, - "Method not allowed.", - ) + call.rejectUnsupportedMethod() } delete { - call.reject( - HttpStatusCode.MethodNotAllowed, - RPCError.ErrorCode.CONNECTION_CLOSED, - "Method not allowed.", - ) + call.rejectUnsupportedMethod() } } } @@ -419,6 +412,16 @@ private suspend fun RoutingContext.mcpPostEndpoint(transportManager: TransportMa logger.trace { "Message handled for sessionId: $sessionId" } } +/** A stateless endpoint serves POST only, offering neither an SSE stream to open nor a session to delete. */ +private suspend fun ApplicationCall.rejectUnsupportedMethod() { + response.header(HttpHeaders.Allow, HttpMethod.Post.value) + reject( + HttpStatusCode.MethodNotAllowed, + RPCError.ErrorCode.CONNECTION_CLOSED, + "Method not allowed.", + ) +} + private fun ApplicationRequest.sessionId(): String? = header(MCP_SESSION_ID_HEADER) private suspend fun existingStreamableTransport( diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt index 3b02835..15d0b4c 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt @@ -10,7 +10,7 @@ import io.ktor.server.request.header import io.ktor.server.request.httpMethod import io.ktor.server.response.header import io.ktor.server.response.respond -import io.ktor.server.response.respondNullable +import io.ktor.server.response.respondText import io.ktor.server.sse.ServerSSESession import io.ktor.util.collections.ConcurrentMap import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport @@ -474,12 +474,13 @@ public class StreamableHttpServerTransport(private val configuration: Configurat ) return } - if (!validateProtocolVersion(call)) return + if (!validateProtocolVersion(call, initializationRequest.id)) return if (messages.size > 1) { call.reject( HttpStatusCode.BadRequest, RPCError.ErrorCode.INVALID_REQUEST, "Invalid Request: Only one initialization request is allowed", + initializationRequest.id, ) return } @@ -491,12 +492,13 @@ public class StreamableHttpServerTransport(private val configuration: Configurat sessionId?.let { onSessionInitialized?.invoke(it) } } } else { - if (!validateSession(call) || !validateProtocolVersion(call)) return + val requestId = messages.filterIsInstance().firstOrNull()?.id + if (!validateSession(call, requestId) || !validateProtocolVersion(call, requestId)) return } val hasRequest = messages.any { it is JSONRPCRequest } if (!hasRequest) { - call.respondNullable(status = HttpStatusCode.Accepted, message = null) + call.respond(HttpStatusCode.Accepted) messages.forEach { message -> _onMessage(message) } // A cancellation may target a request still awaiting delivery on a JSON-mode POST; // retire it so that POST completes instead of waiting for a response that never comes. @@ -552,7 +554,7 @@ public class StreamableHttpServerTransport(private val configuration: Configurat if (responses.isEmpty()) { // Every request in this POST was cancelled before producing a response; there is // nothing to return, so acknowledge with 202 rather than an empty JSON body. - call.respondNullable(status = HttpStatusCode.Accepted, message = null) + call.respond(HttpStatusCode.Accepted) } else { call.response.header(HttpHeaders.ContentType, ContentType.Application.Json.toString()) val payload = if (responses.size == 1) responses.first() else responses @@ -645,7 +647,7 @@ public class StreamableHttpServerTransport(private val configuration: Configurat if (!validateSession(call) || !validateProtocolVersion(call)) return sessionId?.let { onSessionClosed?.invoke(it) } close() - call.respondNullable(status = HttpStatusCode.OK, message = null) + call.respond(HttpStatusCode.OK) } /** @@ -734,7 +736,7 @@ public class StreamableHttpServerTransport(private val configuration: Configurat } } - private suspend fun validateSession(call: ApplicationCall): Boolean { + private suspend fun validateSession(call: ApplicationCall, id: RequestId? = null): Boolean { if (sessionIdGenerator == null) return true if (!initialized.load()) { @@ -742,6 +744,7 @@ public class StreamableHttpServerTransport(private val configuration: Configurat HttpStatusCode.BadRequest, RPCError.ErrorCode.CONNECTION_CLOSED, "Bad Request: Server not initialized", + id, ) return false } @@ -753,6 +756,7 @@ public class StreamableHttpServerTransport(private val configuration: Configurat HttpStatusCode.BadRequest, RPCError.ErrorCode.CONNECTION_CLOSED, "Bad Request: Mcp-Session-Id header is required", + id, ) return false } @@ -765,13 +769,14 @@ public class StreamableHttpServerTransport(private val configuration: Configurat HttpStatusCode.NotFound, REQUEST_TIMEOUT, "Session not found", + id, ) false } } } - private suspend fun validateProtocolVersion(call: ApplicationCall): Boolean { + private suspend fun validateProtocolVersion(call: ApplicationCall, id: RequestId? = null): Boolean { val version = call.request.headers[MCP_PROTOCOL_VERSION_HEADER] ?: return true return when (version) { @@ -784,6 +789,7 @@ public class StreamableHttpServerTransport(private val configuration: Configurat ", ", ) })", + id, ) false } @@ -914,12 +920,13 @@ public class StreamableHttpServerTransport(private val configuration: Configurat } } +/** Writes an already serialized body so a client accepting only `text/event-stream` still receives [status]. */ internal suspend fun ApplicationCall.reject( status: HttpStatusCode, code: Int, message: String, id: RequestId? = null, ) { - this.response.status(status) - this.respond(JSONRPCError(id = id, error = RPCError(code = code, message = message))) + val error = JSONRPCError(id = id, error = RPCError(code = code, message = message)) + respondText(McpJson.encodeToString(error), ContentType.Application.Json, status) } diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpResponseDeliveryTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpResponseDeliveryTest.kt new file mode 100644 index 0000000..fc076eb --- /dev/null +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpResponseDeliveryTest.kt @@ -0,0 +1,99 @@ +package io.modelcontextprotocol.kotlin.sdk.server + +import io.kotest.matchers.shouldBe +import io.ktor.client.request.delete +import io.ktor.client.request.get +import io.ktor.client.request.header +import io.ktor.client.request.post +import io.ktor.client.request.setBody +import io.ktor.client.statement.HttpResponse +import io.ktor.client.statement.bodyAsText +import io.ktor.http.ContentType +import io.ktor.http.HttpHeaders +import io.ktor.http.HttpMethod +import io.ktor.http.HttpStatusCode +import io.ktor.http.contentType +import io.ktor.server.testing.testApplication +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCError +import io.modelcontextprotocol.kotlin.sdk.types.McpJson +import io.modelcontextprotocol.kotlin.sdk.types.RPCError +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import kotlin.test.Test + +/** + * A client opening the standalone stream sends `Accept: text/event-stream`, which matches no JSON + * converter. Responses rendered through ContentNegotiation used to reach such a client as an empty + * 406 with the intended status discarded. + */ +class StreamableHttpResponseDeliveryTest { + + private val eventStream = ContentType.Text.EventStream.toString() + + private fun testServer(): Server = Server( + Implementation("test-server", "1.0"), + ServerOptions(capabilities = ServerCapabilities()), + ) + + @Test + fun `stateless GET is rejected with 405 and advertises POST`() = testApplication { + application { mcpStatelessStreamableHttp { testServer() } } + + val response = client.get("/mcp") { + header(HttpHeaders.Host, "localhost") + header(HttpHeaders.Accept, eventStream) + } + + response.assertMethodNotAllowed() + } + + @Test + fun `stateless DELETE is rejected with 405 and advertises POST`() = testApplication { + application { mcpStatelessStreamableHttp { testServer() } } + + val response = client.delete("/mcp") { + header(HttpHeaders.Host, "localhost") + header(HttpHeaders.Accept, eventStream) + } + + response.assertMethodNotAllowed() + } + + @Test + fun `stateful DELETE for an unknown session returns 404`() = testApplication { + application { mcpStreamableHttp { testServer() } } + + val response = client.delete("/mcp") { + header(HttpHeaders.Host, "localhost") + header(HttpHeaders.Accept, eventStream) + header(MCP_SESSION_ID_HEADER, "unknown-session") + } + + response.status shouldBe HttpStatusCode.NotFound + response.decodeError().error.message shouldBe "Session not found" + } + + @Test + fun `POST carrying no request is acknowledged with a bodiless 202`() = testApplication { + application { mcpStatelessStreamableHttp { testServer() } } + + val response = client.post("/mcp") { + header(HttpHeaders.Host, "localhost") + header(HttpHeaders.Accept, "${ContentType.Application.Json}, $eventStream") + contentType(ContentType.Application.Json) + setBody("""{"jsonrpc":"2.0","method":"notifications/initialized"}""") + } + + response.status shouldBe HttpStatusCode.Accepted + response.bodyAsText() shouldBe "" + } + + private suspend fun HttpResponse.assertMethodNotAllowed() { + status shouldBe HttpStatusCode.MethodNotAllowed + headers[HttpHeaders.Allow] shouldBe HttpMethod.Post.value + contentType()?.withoutParameters() shouldBe ContentType.Application.Json + decodeError().error.code shouldBe RPCError.ErrorCode.CONNECTION_CLOSED + } + + private suspend fun HttpResponse.decodeError(): JSONRPCError = McpJson.decodeFromString(bodyAsText()) +} diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt index bb522d6..e5c51e9 100644 --- a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt @@ -216,14 +216,16 @@ class StreamableHttpServerTransportTest { configureTransportEndpoint(transport) + val payload = buildInitializeRequestPayload() val initResponse = client.post(path) { addStreamableHeaders() header("mcp-protocol-version", "1900-01-01") - setBody(buildInitializeRequestPayload()) + setBody(payload) } initResponse.status shouldBe HttpStatusCode.BadRequest initResponse.headers[MCP_SESSION_ID_HEADER] shouldBe null + initResponse.body().id shouldBe payload.id } @Test @@ -297,6 +299,65 @@ class StreamableHttpServerTransportTest { } response.status shouldBe HttpStatusCode.BadRequest + response.body().id shouldBe RequestId("test-1") + } + + @Test + fun `batch with an initialization request echoes the initialize id when rejected`() = testApplication { + configTestServer() + + val client = createTestClient() + + val transport = StreamableHttpServerTransport(enableJsonResponse = true) + transport.onMessage { } + + configureTransportEndpoint(transport) + + val initPayload = buildInitializeRequestPayload() + val response = client.post(path) { + addStreamableHeaders() + setBody( + encodeMessages( + listOf( + initPayload, + JSONRPCRequest(id = RequestId("extra"), method = Method.Defined.ToolsList.value), + ), + ), + ) + } + + response.status shouldBe HttpStatusCode.BadRequest + val error = response.body() + error.error.message shouldBe "Invalid Request: Only one initialization request is allowed" + error.id shouldBe initPayload.id + } + + @Test + fun `non-init request before initialization echoes the request id`() = testApplication { + configTestServer() + + val client = createTestClient() + + val transport = StreamableHttpServerTransport(enableJsonResponse = true) + transport.onMessage { } + + configureTransportEndpoint(transport) + + val response = client.post(path) { + addStreamableHeaders() + setBody( + encodeMessages( + listOf( + JSONRPCRequest(id = RequestId("before-init"), method = Method.Defined.ToolsList.value), + ), + ), + ) + } + + response.status shouldBe HttpStatusCode.BadRequest + val error = response.body() + error.error.message shouldBe "Bad Request: Server not initialized" + error.id shouldBe RequestId("before-init") } @Test From 2b494b763730e3886f415a211ed82ac8fdaf9244 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 14:09:25 +0200 Subject: [PATCH 386/449] chore(deps): bump the other-dependencies group across 5 directories with 3 updates (#914) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the other-dependencies group with 2 updates in the /samples/kotlin-mcp-client directory: [com.anthropic:anthropic-java](https://github.com/anthropics/anthropic-sdk-java) and [com.gradleup.shadow](https://github.com/GradleUp/shadow). Bumps the other-dependencies group with 1 update in the /samples/kotlin-mcp-server directory: [com.gradleup.shadow](https://github.com/GradleUp/shadow). Bumps the other-dependencies group with 2 updates in the /samples/kotlinlang-mcp-server directory: [com.gradleup.shadow](https://github.com/GradleUp/shadow) and [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback). Bumps the other-dependencies group with 1 update in the /samples/simple-streamable-server directory: [com.gradleup.shadow](https://github.com/GradleUp/shadow). Bumps the other-dependencies group with 1 update in the /samples/weather-stdio-server directory: [com.gradleup.shadow](https://github.com/GradleUp/shadow). Updates `com.anthropic:anthropic-java` from 2.48.0 to 2.52.0
        Release notes

        Sourced from com.anthropic:anthropic-java's releases.

        v2.52.0

        2.52.0 (2026-07-24)

        Full Changelog: v2.51.0...v2.52.0

        Features

        • api: add claude-opus-5 model (fb245bd)
        • api: add tool addition/removal blocks and tool_change events (fb245bd)
        • api: expand client-side fallback credit token types and add server-side fallbacks default option (fb245bd)

        v2.51.0

        2.51.0 (2026-07-23)

        Full Changelog: v2.50.0...v2.51.0

        Features

        • api: add new stop reason 'model_context_window_exceeded' (f4b644e)

        Bug Fixes

        • refer to the CLI as ant in NoCredentialsException (#170) (1bc2be9)

        v2.50.0

        2.50.0 (2026-07-22)

        Full Changelog: v2.49.1...v2.50.0

        Features

        • api: add support for Managed Agents model effort, initial session events, and threads delta streaming (e312432)

        v2.49.1

        2.49.1 (2026-07-21)

        Full Changelog: v2.49.0...v2.49.1

        Chores

        • api: add support for new refusal category (40db9c7)
        • client: docs updates (5b25c27)
        • docs: small updates (99a2a8c)
        • docs: small updates (adc11f5)
        • internal: codegen related update (9d85f79)

        v2.49.0

        2.49.0 (2026-07-16)

        ... (truncated)

        Changelog

        Sourced from com.anthropic:anthropic-java's changelog.

        2.52.0 (2026-07-24)

        Full Changelog: v2.51.0...v2.52.0

        Features

        • api: add claude-opus-5 model (fb245bd)
        • api: add tool addition/removal blocks and tool_change events (fb245bd)
        • api: expand client-side fallback credit token types and add server-side fallbacks default option (fb245bd)

        2.51.0 (2026-07-23)

        Full Changelog: v2.50.0...v2.51.0

        Features

        • api: add new stop reason 'model_context_window_exceeded' (f4b644e)

        Bug Fixes

        • refer to the CLI as ant in NoCredentialsException (#170) (1bc2be9)

        2.50.0 (2026-07-22)

        Full Changelog: v2.49.1...v2.50.0

        Features

        • api: add support for Managed Agents model effort, initial session events, and threads delta streaming (e312432)

        2.49.1 (2026-07-21)

        Full Changelog: v2.49.0...v2.49.1

        Chores

        • api: add support for new refusal category (40db9c7)
        • client: docs updates (5b25c27)
        • docs: small updates (99a2a8c)
        • docs: small updates (adc11f5)
        • internal: codegen related update (9d85f79)

        2.49.0 (2026-07-16)

        Full Changelog: v2.48.0...v2.49.0

        Features

        • api: add support for dreaming (836ee60)

        ... (truncated)

        Commits
        • 55ff755 release: 2.52.0
        • 917b495 feat(api): add claude-opus-5 model
        • 4124b5e release: 2.51.0
        • 684e2a8 feat(api): add new stop reason 'model_context_window_exceeded'
        • 2fbb806 fix: refer to the CLI as ant in NoCredentialsException (#170)
        • 8bfe5af release: 2.50.0
        • 46da3ef feat(api): add support for Managed Agents model effort, initial session event...
        • abe2641 release: 2.49.1
        • 847a8c1 chore(api): add support for new refusal category
        • 0d9869f chore(internal): codegen related update
        • Additional commits viewable in compare view

        Updates `com.gradleup.shadow` from 9.5.1 to 9.6.1
        Release notes

        Sourced from com.gradleup.shadow's releases.

        9.6.1

        Changed

        • Use GradleException for expected build failures. (#2113)

        Fixed

        • Preserve repeated lines in R8 rule files when using minimize { r8 { ... } }. (#2115)

        9.6.0

        Added

        • Extract R8 rules from dependency JARs when using minimize { r8 { ... } }. (#2089)

        Changed

        • Rename ShadowDslMarker to ShadowDsl. (#2091)
        • POTENTIALLY BREAKING: Apply @ShadowDsl to ShadowJar, ResourceTransformer, DependencyFilter, and Relocator. (#2090)
          This restricts nested DSL configuration blocks from implicitly calling outer receiver APIs in Kotlin script files.

        Fixed

        • Validate ZIP entry names to prevent Zip Slip path traversal.
        • Avoid resolving unused R8 dependency. (#2101)
        Commits

        Updates `com.gradleup.shadow` from 9.5.1 to 9.6.1
        Release notes

        Sourced from com.gradleup.shadow's releases.

        9.6.1

        Changed

        • Use GradleException for expected build failures. (#2113)

        Fixed

        • Preserve repeated lines in R8 rule files when using minimize { r8 { ... } }. (#2115)

        9.6.0

        Added

        • Extract R8 rules from dependency JARs when using minimize { r8 { ... } }. (#2089)

        Changed

        • Rename ShadowDslMarker to ShadowDsl. (#2091)
        • POTENTIALLY BREAKING: Apply @ShadowDsl to ShadowJar, ResourceTransformer, DependencyFilter, and Relocator. (#2090)
          This restricts nested DSL configuration blocks from implicitly calling outer receiver APIs in Kotlin script files.

        Fixed

        • Validate ZIP entry names to prevent Zip Slip path traversal.
        • Avoid resolving unused R8 dependency. (#2101)
        Commits

        Updates `com.gradleup.shadow` from 9.5.1 to 9.6.1
        Release notes

        Sourced from com.gradleup.shadow's releases.

        9.6.1

        Changed

        • Use GradleException for expected build failures. (#2113)

        Fixed

        • Preserve repeated lines in R8 rule files when using minimize { r8 { ... } }. (#2115)

        9.6.0

        Added

        • Extract R8 rules from dependency JARs when using minimize { r8 { ... } }. (#2089)

        Changed

        • Rename ShadowDslMarker to ShadowDsl. (#2091)
        • POTENTIALLY BREAKING: Apply @ShadowDsl to ShadowJar, ResourceTransformer, DependencyFilter, and Relocator. (#2090)
          This restricts nested DSL configuration blocks from implicitly calling outer receiver APIs in Kotlin script files.

        Fixed

        • Validate ZIP entry names to prevent Zip Slip path traversal.
        • Avoid resolving unused R8 dependency. (#2101)
        Commits

        Updates `com.gradleup.shadow` from 9.5.1 to 9.6.1
        Release notes

        Sourced from com.gradleup.shadow's releases.

        9.6.1

        Changed

        • Use GradleException for expected build failures. (#2113)

        Fixed

        • Preserve repeated lines in R8 rule files when using minimize { r8 { ... } }. (#2115)

        9.6.0

        Added

        • Extract R8 rules from dependency JARs when using minimize { r8 { ... } }. (#2089)

        Changed

        • Rename ShadowDslMarker to ShadowDsl. (#2091)
        • POTENTIALLY BREAKING: Apply @ShadowDsl to ShadowJar, ResourceTransformer, DependencyFilter, and Relocator. (#2090)
          This restricts nested DSL configuration blocks from implicitly calling outer receiver APIs in Kotlin script files.

        Fixed

        • Validate ZIP entry names to prevent Zip Slip path traversal.
        • Avoid resolving unused R8 dependency. (#2101)
        Commits

        Updates `com.gradleup.shadow` from 9.5.1 to 9.6.1
        Release notes

        Sourced from com.gradleup.shadow's releases.

        9.6.1

        Changed

        • Use GradleException for expected build failures. (#2113)

        Fixed

        • Preserve repeated lines in R8 rule files when using minimize { r8 { ... } }. (#2115)

        9.6.0

        Added

        • Extract R8 rules from dependency JARs when using minimize { r8 { ... } }. (#2089)

        Changed

        • Rename ShadowDslMarker to ShadowDsl. (#2091)
        • POTENTIALLY BREAKING: Apply @ShadowDsl to ShadowJar, ResourceTransformer, DependencyFilter, and Relocator. (#2090)
          This restricts nested DSL configuration blocks from implicitly calling outer receiver APIs in Kotlin script files.

        Fixed

        • Validate ZIP entry names to prevent Zip Slip path traversal.
        • Avoid resolving unused R8 dependency. (#2101)
        Commits

        Updates `com.gradleup.shadow` from 9.5.1 to 9.6.1
        Release notes

        Sourced from com.gradleup.shadow's releases.

        9.6.1

        Changed

        • Use GradleException for expected build failures. (#2113)

        Fixed

        • Preserve repeated lines in R8 rule files when using minimize { r8 { ... } }. (#2115)

        9.6.0

        Added

        • Extract R8 rules from dependency JARs when using minimize { r8 { ... } }. (#2089)

        Changed

        • Rename ShadowDslMarker to ShadowDsl. (#2091)
        • POTENTIALLY BREAKING: Apply @ShadowDsl to ShadowJar, ResourceTransformer, DependencyFilter, and Relocator. (#2090)
          This restricts nested DSL configuration blocks from implicitly calling outer receiver APIs in Kotlin script files.

        Fixed

        • Validate ZIP entry names to prevent Zip Slip path traversal.
        • Avoid resolving unused R8 dependency. (#2101)
        Commits

        Updates `com.gradleup.shadow` from 9.5.1 to 9.6.1
        Release notes

        Sourced from com.gradleup.shadow's releases.

        9.6.1

        Changed

        • Use GradleException for expected build failures. (#2113)

        Fixed

        • Preserve repeated lines in R8 rule files when using minimize { r8 { ... } }. (#2115)

        9.6.0

        Added

        • Extract R8 rules from dependency JARs when using minimize { r8 { ... } }. (#2089)

        Changed

        • Rename ShadowDslMarker to ShadowDsl. (#2091)
        • POTENTIALLY BREAKING: Apply @ShadowDsl to ShadowJar, ResourceTransformer, DependencyFilter, and Relocator. (#2090)
          This restricts nested DSL configuration blocks from implicitly calling outer receiver APIs in Kotlin script files.

        Fixed

        • Validate ZIP entry names to prevent Zip Slip path traversal.
        • Avoid resolving unused R8 dependency. (#2101)
        Commits

        Updates `com.gradleup.shadow` from 9.5.1 to 9.6.1
        Release notes

        Sourced from com.gradleup.shadow's releases.

        9.6.1

        Changed

        • Use GradleException for expected build failures. (#2113)

        Fixed

        • Preserve repeated lines in R8 rule files when using minimize { r8 { ... } }. (#2115)

        9.6.0

        Added

        • Extract R8 rules from dependency JARs when using minimize { r8 { ... } }. (#2089)

        Changed

        • Rename ShadowDslMarker to ShadowDsl. (#2091)
        • POTENTIALLY BREAKING: Apply @ShadowDsl to ShadowJar, ResourceTransformer, DependencyFilter, and Relocator. (#2090)
          This restricts nested DSL configuration blocks from implicitly calling outer receiver APIs in Kotlin script files.

        Fixed

        • Validate ZIP entry names to prevent Zip Slip path traversal.
        • Avoid resolving unused R8 dependency. (#2101)
        Commits

        Updates `com.gradleup.shadow` from 9.5.1 to 9.6.1
        Release notes

        Sourced from com.gradleup.shadow's releases.

        9.6.1

        Changed

        • Use GradleException for expected build failures. (#2113)

        Fixed

        • Preserve repeated lines in R8 rule files when using minimize { r8 { ... } }. (#2115)

        9.6.0

        Added

        • Extract R8 rules from dependency JARs when using minimize { r8 { ... } }. (#2089)

        Changed

        • Rename ShadowDslMarker to ShadowDsl. (#2091)
        • POTENTIALLY BREAKING: Apply @ShadowDsl to ShadowJar, ResourceTransformer, DependencyFilter, and Relocator. (#2090)
          This restricts nested DSL configuration blocks from implicitly calling outer receiver APIs in Kotlin script files.

        Fixed

        • Validate ZIP entry names to prevent Zip Slip path traversal.
        • Avoid resolving unused R8 dependency. (#2101)
        Commits

        Updates `com.gradleup.shadow` from 9.5.1 to 9.6.1
        Release notes

        Sourced from com.gradleup.shadow's releases.

        9.6.1

        Changed

        • Use GradleException for expected build failures. (#2113)

        Fixed

        • Preserve repeated lines in R8 rule files when using minimize { r8 { ... } }. (#2115)

        9.6.0

        Added

        • Extract R8 rules from dependency JARs when using minimize { r8 { ... } }. (#2089)

        Changed

        • Rename ShadowDslMarker to ShadowDsl. (#2091)
        • POTENTIALLY BREAKING: Apply @ShadowDsl to ShadowJar, ResourceTransformer, DependencyFilter, and Relocator. (#2090)
          This restricts nested DSL configuration blocks from implicitly calling outer receiver APIs in Kotlin script files.

        Fixed

        • Validate ZIP entry names to prevent Zip Slip path traversal.
        • Avoid resolving unused R8 dependency. (#2101)
        Commits

        Updates `com.gradleup.shadow` from 9.6.0 to 9.6.1
        Release notes

        Sourced from com.gradleup.shadow's releases.

        9.6.1

        Changed

        • Use GradleException for expected build failures. (#2113)

        Fixed

        • Preserve repeated lines in R8 rule files when using minimize { r8 { ... } }. (#2115)

        9.6.0

        Added

        • Extract R8 rules from dependency JARs when using minimize { r8 { ... } }. (#2089)

        Changed

        • Rename ShadowDslMarker to ShadowDsl. (#2091)
        • POTENTIALLY BREAKING: Apply @ShadowDsl to ShadowJar, ResourceTransformer, DependencyFilter, and Relocator. (#2090)
          This restricts nested DSL configuration blocks from implicitly calling outer receiver APIs in Kotlin script files.

        Fixed

        • Validate ZIP entry names to prevent Zip Slip path traversal.
        • Avoid resolving unused R8 dependency. (#2101)
        Commits

        Updates `com.gradleup.shadow` from 9.6.0 to 9.6.1
        Release notes

        Sourced from com.gradleup.shadow's releases.

        9.6.1

        Changed

        • Use GradleException for expected build failures. (#2113)

        Fixed

        • Preserve repeated lines in R8 rule files when using minimize { r8 { ... } }. (#2115)

        9.6.0

        Added

        • Extract R8 rules from dependency JARs when using minimize { r8 { ... } }. (#2089)

        Changed

        • Rename ShadowDslMarker to ShadowDsl. (#2091)
        • POTENTIALLY BREAKING: Apply @ShadowDsl to ShadowJar, ResourceTransformer, DependencyFilter, and Relocator. (#2090)
          This restricts nested DSL configuration blocks from implicitly calling outer receiver APIs in Kotlin script files.

        Fixed

        • Validate ZIP entry names to prevent Zip Slip path traversal.
        • Avoid resolving unused R8 dependency. (#2101)
        Commits

        Updates `ch.qos.logback:logback-classic` from 1.5.38 to 1.6.0
        Release notes

        Sourced from ch.qos.logback:logback-classic's releases.

        Logback 1.6.0

        2026-07-23 Release of logback version 1.6.0

        • Removed certain deprecated variables, methods, and classes. For the list of removed members see release_1.6.0.txt.

        • In AsyncAppenderBase, the put(ILoggingEvent) method now has the protected modifier to allow access from derived classes. This change was requested by Thomas Skjølberg in pr#1053.

        • Bump SLF4J dependency to version 2.0.18.

        • See also the overview of the 1.6.x series.

        • A bit-wise identical binary of this version can be reproduced by building from source code at commit b07adf36019b51a10f824fdd94009985c587b1d3 associated with the tag v_1.6.0. The release was built using Java "21" 2023-10-17 LTS build 21.0.1.+12-LTS-29 under Linux Debian 11.6.

        Commits
        • b07adf3 synronized logback-access with future 2.0.14
        • 8381cdc prepare release 1.6.0
        • 15db759 notes on removed deprecated classes, methods and variables.
        • 90ced5a revert removal of support for packaing data
        • 64c5d3f Packaging data is no longer supported. It was rarely used.
        • 5474044 fix failing tests
        • ac484c2 in version 1.6.x target JDK 11
        • 01eaa90 start work on version 1.6.0-SNAPSHOT
        • d5e658c Allow subclasses to use put(ILoggingEvent) by making method access protected
        • 94ba1a0 improve message in case of version mismatch
        • Additional commits viewable in compare view

        Updates `com.gradleup.shadow` from 9.6.0 to 9.6.1
        Release notes

        Sourced from com.gradleup.shadow's releases.

        9.6.1

        Changed

        • Use GradleException for expected build failures. (#2113)

        Fixed

        • Preserve repeated lines in R8 rule files when using minimize { r8 { ... } }. (#2115)

        9.6.0

        Added

        • Extract R8 rules from dependency JARs when using minimize { r8 { ... } }. (#2089)

        Changed

        • Rename ShadowDslMarker to ShadowDsl. (#2091)
        • POTENTIALLY BREAKING: Apply @ShadowDsl to ShadowJar, ResourceTransformer, DependencyFilter, and Relocator. (#2090)
          This restricts nested DSL configuration blocks from implicitly calling outer receiver APIs in Kotlin script files.

        Fixed

        • Validate ZIP entry names to prevent Zip Slip path traversal.
        • Avoid resolving unused R8 dependency. (#2101)
        Commits

        Updates `com.gradleup.shadow` from 9.6.0 to 9.6.1
        Release notes

        Sourced from com.gradleup.shadow's releases.

        9.6.1

        Changed

        • Use GradleException for expected build failures. (#2113)

        Fixed

        • Preserve repeated lines in R8 rule files when using minimize { r8 { ... } }. (#2115)

        9.6.0

        Added

        • Extract R8 rules from dependency JARs when using minimize { r8 { ... } }. (#2089)

        Changed Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-client/gradle/libs.versions.toml | 4 ++-- samples/kotlin-mcp-server/gradle/libs.versions.toml | 2 +- samples/kotlinlang-mcp-server/gradle/libs.versions.toml | 4 ++-- samples/simple-streamable-server/gradle/libs.versions.toml | 2 +- samples/weather-stdio-server/gradle/libs.versions.toml | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index 732dfbd..921a7b2 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -1,9 +1,9 @@ [versions] -anthropic = "2.48.0" +anthropic = "2.52.0" kotlin = "2.4.10" ktor = "3.5.1" mcp-kotlin = "0.14.0" -shadow = "9.5.1" +shadow = "9.6.1" slf4j = "2.0.18" [libraries] diff --git a/samples/kotlin-mcp-server/gradle/libs.versions.toml b/samples/kotlin-mcp-server/gradle/libs.versions.toml index 6cdd81f..230336b 100644 --- a/samples/kotlin-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-server/gradle/libs.versions.toml @@ -3,7 +3,7 @@ kotlin = "2.4.0" ktor = "3.5.1" mcp-kotlin = "0.14.0" slf4j = "2.0.18" -shadow = "9.5.1" +shadow = "9.6.1" [libraries] ktor-bom = { group = "io.ktor", name = "ktor-bom", version.ref = "ktor" } diff --git a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml index 047c0bf..363c373 100644 --- a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml @@ -4,8 +4,8 @@ ktor = "3.5.1" mcp-kotlin = "0.14.0" serialization = "1.11.0" coroutines = "1.11.0" -logback = "1.5.38" -shadow = "9.6.0" +logback = "1.6.0" +shadow = "9.6.1" [libraries] ktor-bom = { group = "io.ktor", name = "ktor-bom", version.ref = "ktor" } diff --git a/samples/simple-streamable-server/gradle/libs.versions.toml b/samples/simple-streamable-server/gradle/libs.versions.toml index bbf7120..abd1290 100644 --- a/samples/simple-streamable-server/gradle/libs.versions.toml +++ b/samples/simple-streamable-server/gradle/libs.versions.toml @@ -4,7 +4,7 @@ ktor = "3.5.1" junit = "6.1.2" mcp-kotlin = "0.14.0" slf4j = "2.0.18" -shadow = "9.6.0" +shadow = "9.6.1" [libraries] ktor-bom = { group = "io.ktor", name = "ktor-bom", version.ref = "ktor" } diff --git a/samples/weather-stdio-server/gradle/libs.versions.toml b/samples/weather-stdio-server/gradle/libs.versions.toml index 67fec97..69b8d1c 100644 --- a/samples/weather-stdio-server/gradle/libs.versions.toml +++ b/samples/weather-stdio-server/gradle/libs.versions.toml @@ -4,7 +4,7 @@ kotlin = "2.4.0" ktor = "3.5.1" logging = "8.0.4" mcp-kotlin = "0.14.0" -shadow = "9.5.1" +shadow = "9.6.1" slf4j = "2.0.18" [libraries] From 94745e617cada0b89e084dd4ffd5010f26174e54 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 14:52:31 +0200 Subject: [PATCH 387/449] chore(deps): bump the kotest group across 1 directory with 3 updates (#915) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the kotest group with 3 updates in the / directory: [io.kotest:kotest-assertions-core](https://github.com/kotest/kotest), [io.kotest:kotest-assertions-json](https://github.com/kotest/kotest) and [io.kotest:kotest-assertions-ktor](https://github.com/kotest/kotest). Updates `io.kotest:kotest-assertions-core` from 6.2.2 to 6.2.3
        Release notes

        Sourced from io.kotest:kotest-assertions-core's releases.

        v6.2.3

        What's Changed

        New Contributors

        Full Changelog: https://github.com/kotest/kotest/compare/6.2.2...v6.2.3

        Commits

        Updates `io.kotest:kotest-assertions-json` from 6.2.2 to 6.2.3
        Release notes

        Sourced from io.kotest:kotest-assertions-json's releases.

        v6.2.3

        What's Changed

        New Contributors

        Full Changelog: https://github.com/kotest/kotest/compare/6.2.2...v6.2.3

        Commits

        Updates `io.kotest:kotest-assertions-ktor` from 6.2.2 to 6.2.3
        Release notes

        Sourced from io.kotest:kotest-assertions-ktor's releases.

        v6.2.3

        What's Changed

        New Contributors

        Full Changelog: https://github.com/kotest/kotest/compare/6.2.2...v6.2.3

        Commits

        Updates `io.kotest:kotest-assertions-json` from 6.2.2 to 6.2.3
        Release notes

        Sourced from io.kotest:kotest-assertions-json's releases.

        v6.2.3

        What's Changed

        New Contributors

        Full Changelog: https://github.com/kotest/kotest/compare/6.2.2...v6.2.3

        Commits

        Updates `io.kotest:kotest-assertions-ktor` from 6.2.2 to 6.2.3
        Release notes

        Sourced from io.kotest:kotest-assertions-ktor's releases.

        v6.2.3

        What's Changed

        New Contributors

        Full Changelog: https://github.com/kotest/kotest/compare/6.2.2...v6.2.3

        Commits

        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6d3c5a8..d648750 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,7 +15,7 @@ netty = "4.2.16.Final" awaitility = "4.3.0" collections-immutable = "0.5.1" coroutines = "1.11.0" -kotest = "6.2.2" +kotest = "6.2.3" kotlinx-io = "0.9.1" ktor = "3.5.1" logging = "8.0.4" From 7fa438ce00e1bdb2fb1cc22d3efacb07dafa448e Mon Sep 17 00:00:00 2001 From: "Andre.Kalberer" <83852559+AndreKalberer@users.noreply.github.com> Date: Mon, 27 Jul 2026 06:29:51 -0700 Subject: [PATCH 388/449] feat(client): send MCP standard POST headers (#894) ## Problem SEP-2243 requires Streamable HTTP clients to mirror applicable JSON-RPC request metadata into standard HTTP headers. The Kotlin client currently sends `Mcp-Protocol-Version` and session metadata but does not emit `Mcp-Method` or `Mcp-Name`. ## Solution - Add `Mcp-Method` for JSON-RPC requests and notifications. - Add `Mcp-Name` from a string `params.name`, falling back to a string `params.uri`. - Omit both headers for JSON-RPC responses. - Add focused transport tests for name-, URI-, notification-, and response-based behavior. ## Validation - `.\gradlew.bat :kotlin-sdk-client:jvmTest --tests "io.modelcontextprotocol.kotlin.sdk.client.streamable.http.StreamableHttpClientTransportTest"` - `.\gradlew.bat :kotlin-sdk-client:ktlintCheck :kotlin-sdk-client:detekt :kotlin-sdk-client:apiCheck` - `git diff --check` Closes #787 AI assistance was used while preparing this PR. I reviewed the final change, ran the validation listed above, and take responsibility for it. --------- Co-authored-by: Pavel Gorgulov --- .../client/StreamableHttpClientTransport.kt | 39 ++++++++ .../http/StreamableHttpClientTransportTest.kt | 94 +++++++++++++++++++ 2 files changed, 133 insertions(+) diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt index a63c1d2..6df352a 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport.kt @@ -43,6 +43,10 @@ import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlin.io.encoding.Base64 +import kotlin.io.encoding.ExperimentalEncodingApi import kotlin.math.pow import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds @@ -51,6 +55,10 @@ import kotlin.time.Duration.Companion.seconds private const val MCP_SESSION_ID_HEADER = "mcp-session-id" private const val MCP_PROTOCOL_VERSION_HEADER = "mcp-protocol-version" private const val MCP_RESUMPTION_TOKEN_HEADER = "Last-Event-ID" +private const val MCP_METHOD_HEADER = "Mcp-Method" +private const val MCP_NAME_HEADER = "Mcp-Name" +private const val MCP_BASE64_PREFIX = "=?base64?" +private const val MCP_BASE64_SUFFIX = "?=" /** * Default maximum size, in characters, of a single inline SSE event assembled from a POST response. @@ -167,6 +175,7 @@ public class StreamableHttpClientTransport( val jsonBody = McpJson.encodeToString(message) val response = client.post(url) { applyCommonHeaders(this) + applyStandardPostHeaders(this, message) headers.append(HttpHeaders.Accept, "${ContentType.Application.Json}, ${ContentType.Text.EventStream}") contentType(ContentType.Application.Json) setBody(jsonBody) @@ -390,6 +399,36 @@ public class StreamableHttpClientTransport( } } + private fun applyStandardPostHeaders(builder: HttpRequestBuilder, message: JSONRPCMessage) { + val (method, params) = when (message) { + is JSONRPCRequest -> message.method to message.params + is JSONRPCNotification -> message.method to message.params + else -> return + } + + builder.headers { + append(MCP_METHOD_HEADER, method) + + val paramsObject = params as? JsonObject ?: return@headers + val mcpName = paramsObject.stringValue("name") ?: paramsObject.stringValue("uri") + mcpName?.let { append(MCP_NAME_HEADER, it.encodeMcpHeaderValue()) } + } + } + + private fun JsonObject.stringValue(key: String): String? = + (get(key) as? JsonPrimitive)?.takeIf { it.isString }?.content + + @OptIn(ExperimentalEncodingApi::class) + private fun String.encodeMcpHeaderValue(): String { + val containsUnsafeCharacters = any { it != '\t' && it.code !in 0x20..0x7e } + val hasEdgeWhitespace = firstOrNull()?.isWhitespace() == true || lastOrNull()?.isWhitespace() == true + val matchesBase64Sentinel = startsWith(MCP_BASE64_PREFIX) && endsWith(MCP_BASE64_SUFFIX) + + if (!containsUnsafeCharacters && !hasEdgeWhitespace && !matchesBase64Sentinel) return this + + return "$MCP_BASE64_PREFIX${Base64.Default.encode(encodeToByteArray())}$MCP_BASE64_SUFFIX" + } + private suspend fun collectSse( session: ClientSSESession, replayMessageId: RequestId?, diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt index ef55867..d9aaf7a 100644 --- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt +++ b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/streamable/http/StreamableHttpClientTransportTest.kt @@ -246,6 +246,100 @@ class StreamableHttpClientTransportTest { transport.close() } + @Test + fun `should send MCP method and name headers from name`() = runTest { + val transport = createTransport { request -> + assertEquals("tools/call", request.headers["Mcp-Method"]) + assertEquals("read_file", request.headers["Mcp-Name"]) + respond(content = "", status = HttpStatusCode.Accepted) + } + + transport.start() + transport.send( + JSONRPCRequest( + id = "test-id", + method = "tools/call", + params = buildJsonObject { + put("name", JsonPrimitive("read_file")) + put("arguments", buildJsonObject { put("path", JsonPrimitive("README.md")) }) + }, + ), + ) + transport.close() + } + + @Test + fun `should send MCP name header from URI`() = runTest { + val transport = createTransport { request -> + assertEquals("resources/read", request.headers["Mcp-Method"]) + assertEquals("file:///README.md", request.headers["Mcp-Name"]) + respond(content = "", status = HttpStatusCode.Accepted) + } + + transport.start() + transport.send( + JSONRPCRequest( + id = "test-id", + method = "resources/read", + params = buildJsonObject { put("uri", JsonPrimitive("file:///README.md")) }, + ), + ) + transport.close() + } + + @Test + fun `should encode MCP name header values when required`() = runTest { + val cases = listOf( + "Hello, 世界" to "=?base64?SGVsbG8sIOS4lueVjA==?=", + " padded " to "=?base64?IHBhZGRlZCA=?=", + "line1\nline2" to "=?base64?bGluZTEKbGluZTI=?=", + "=?base64?literal?=" to "=?base64?PT9iYXNlNjQ/bGl0ZXJhbD89?=", + ) + + cases.forEachIndexed { index, (name, expectedHeader) -> + val transport = createTransport { request -> + assertEquals(expectedHeader, request.headers["Mcp-Name"]) + respond(content = "", status = HttpStatusCode.Accepted) + } + + transport.start() + transport.send( + JSONRPCRequest( + id = "test-id-$index", + method = "tools/call", + params = buildJsonObject { put("name", JsonPrimitive(name)) }, + ), + ) + transport.close() + } + } + + @Test + fun `should send MCP method header for notifications`() = runTest { + val transport = createTransport { request -> + assertEquals("notifications/tools/list_changed", request.headers["Mcp-Method"]) + assertNull(request.headers["Mcp-Name"]) + respond(content = "", status = HttpStatusCode.Accepted) + } + + transport.start() + transport.send(JSONRPCNotification(method = "notifications/tools/list_changed")) + transport.close() + } + + @Test + fun `should omit MCP standard headers for responses`() = runTest { + val transport = createTransport { request -> + assertNull(request.headers["Mcp-Method"]) + assertNull(request.headers["Mcp-Name"]) + respond(content = "", status = HttpStatusCode.Accepted) + } + + transport.start() + transport.send(JSONRPCResponse(id = RequestId.StringId("test-id"))) + transport.close() + } + // Engine doesn't support SSECapability: https://youtrack.jetbrains.com/issue/KTOR-8177/MockEngine-Add-SSE-support @Ignore @Test From 24c3cd3a6943556d7cd6c9024fd7860ff7b2dd13 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Tue, 28 Jul 2026 14:51:21 +0200 Subject: [PATCH 389/449] fix(server): match Accept header as media ranges (#912) The header is now parsed into media ranges (`parseHeaderValue` + `ContentType.match`) ## Breaking Changes none ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- .../server/StreamableHttpServerTransport.kt | 22 +++- .../StreamableHttpServerTransportTest.kt | 107 ++++++++++++++++++ 2 files changed, 127 insertions(+), 2 deletions(-) diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt index 15d0b4c..dcba848 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt @@ -1,9 +1,11 @@ package io.modelcontextprotocol.kotlin.sdk.server +import io.ktor.http.BadContentTypeFormatException import io.ktor.http.ContentType import io.ktor.http.HttpHeaders import io.ktor.http.HttpMethod import io.ktor.http.HttpStatusCode +import io.ktor.http.parseHeaderValue import io.ktor.server.application.ApplicationCall import io.ktor.server.request.contentType import io.ktor.server.request.header @@ -864,8 +866,24 @@ public class StreamableHttpServerTransport(private val configuration: Configurat } } - private fun String?.accepts(mime: ContentType): Boolean = - this?.lowercase()?.contains(mime.toString().lowercase()) == true + /** + * Reports whether the `Accept` header admits [mime]. + * + * Comparison is over parsed media ranges rather than raw substrings, so wildcards are honored + * and a near miss such as `application/jsonp` no longer passes as `application/json`. An absent + * header states no preference and admits any type. + */ + private fun String?.accepts(mime: ContentType): Boolean { + if (this == null) return true + return parseHeaderValue(this).any { range -> + if (range.quality <= 0.0) return@any false + try { + mime.match(ContentType.parse(range.value)) + } catch (_: BadContentTypeFormatException) { + false + } + } + } private suspend fun emitOnStream(streamId: String, session: ServerSSESession?, message: JSONRPCMessage) { val eventId = configuration.eventStore?.storeEvent(streamId, message) diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt index e5c51e9..43ff605 100644 --- a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt @@ -9,6 +9,7 @@ import io.ktor.client.call.body import io.ktor.client.plugins.logging.LogLevel import io.ktor.client.plugins.logging.Logging import io.ktor.client.request.HttpRequestBuilder +import io.ktor.client.request.get import io.ktor.client.request.header import io.ktor.client.request.post import io.ktor.client.request.prepareGet @@ -21,6 +22,7 @@ import io.ktor.http.contentType import io.ktor.serialization.kotlinx.json.json import io.ktor.server.application.ApplicationCall import io.ktor.server.application.install +import io.ktor.server.routing.get import io.ktor.server.routing.post import io.ktor.server.routing.routing import io.ktor.server.sse.ServerSSESession @@ -76,6 +78,7 @@ import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertFalse import kotlin.test.assertNotNull +import kotlin.test.assertTrue import kotlin.time.Duration.Companion.seconds import io.ktor.client.plugins.contentnegotiation.ContentNegotiation as ClientContentNegotiation import io.ktor.server.plugins.contentnegotiation.ContentNegotiation as ServerContentNegotiation @@ -100,6 +103,26 @@ class StreamableHttpServerTransportTest { Arguments.of(sizeTestPayload.length.toLong(), HttpStatusCode.BadRequest), Arguments.of(sizeTestPayload.length.toLong() + 1, HttpStatusCode.BadRequest), ) + + @JvmStatic + fun postAcceptHeaderCases(): List = listOf( + Arguments.of("application/json, text/event-stream", HttpStatusCode.OK), + Arguments.of("Application/JSON;q=0.9, TEXT/Event-Stream;charset=utf-8", HttpStatusCode.OK), + Arguments.of("*/*", HttpStatusCode.OK), + Arguments.of("application/*, text/*", HttpStatusCode.OK), + Arguments.of(null, HttpStatusCode.OK), + Arguments.of("application/json", HttpStatusCode.NotAcceptable), + Arguments.of("text/event-stream", HttpStatusCode.NotAcceptable), + Arguments.of("application/jsonp, text/event-stream-bogus", HttpStatusCode.NotAcceptable), + Arguments.of("application/json, text/event-stream;q=0", HttpStatusCode.NotAcceptable), + ) + + @JvmStatic + fun getAcceptHeaderCases() = listOf( + "application/json", + "text/event-stream-bogus", + "text/event-stream;q=0", + ) } private val path = "/transport" @@ -130,6 +153,90 @@ class StreamableHttpServerTransportTest { assertFalse(onMessageCalled.get(), "Transport should not deliver messages when headers are invalid") } + @ParameterizedTest + @MethodSource("postAcceptHeaderCases") + fun `POST Accept header is matched as media ranges`(acceptHeader: String?, expectedStatus: HttpStatusCode) = + testApplication { + configTestServer() + + // Bare client: the ContentNegotiation plugin would supply an Accept header of its own, + // leaving the absent-header case untestable. + val client = createClient {} + + val transport = StreamableHttpServerTransport(enableJsonResponse = true) + val onMessageCalled = AtomicBoolean(false) + transport.onMessage { message -> + onMessageCalled.set(true) + if (message is JSONRPCRequest) { + transport.send(JSONRPCResponse(message.id, EmptyResult())) + } + } + + configureTransportEndpoint(transport) + + val response = client.post(path) { + contentType(ContentType.Application.Json) + acceptHeader?.let { header(HttpHeaders.Accept, it) } + setBody(McpJson.encodeToString(JSONRPCMessage.serializer(), buildInitializeRequestPayload())) + } + + assertEquals(expectedStatus, response.status) + assertEquals( + expectedStatus == HttpStatusCode.OK, + onMessageCalled.get(), + "Only a request that clears the Accept gate may reach the transport", + ) + } + + @Test + fun `POST with an unparsable Accept header is rejected without failing the request`() = testApplication { + configTestServer() + + val client = createClient {} + + val transport = StreamableHttpServerTransport(enableJsonResponse = true) + val onMessageCalled = AtomicBoolean(false) + transport.onMessage { + onMessageCalled.set(true) + } + + configureTransportEndpoint(transport) + + val response = client.post(path) { + contentType(ContentType.Application.Json) + header(HttpHeaders.Accept, "not-a-media-type") + setBody(McpJson.encodeToString(JSONRPCMessage.serializer(), buildInitializeRequestPayload())) + } + + // The exact code is decided by content negotiation once the transport has rejected the request. + assertTrue(response.status.value in 400..499, "Expected a client error, got ${response.status}") + assertFalse(onMessageCalled.get(), "Transport should not deliver messages when headers are invalid") + } + + @ParameterizedTest + @MethodSource("getAcceptHeaderCases") + fun `GET whose Accept header admits no event-stream range is rejected`(acceptHeader: String) = testApplication { + configTestServer() + + val client = createClient {} + + val transport = StreamableHttpServerTransport(enableJsonResponse = true) + + application { + routing { + get(path) { + transport.handleGetRequest(FakeServerSSESession(call, call.coroutineContext), call) + } + } + } + + val response = client.get(path) { + header(HttpHeaders.Accept, acceptHeader) + } + + assertEquals(HttpStatusCode.NotAcceptable, response.status) + } + @Test fun `initialization request establishes session and returns json response`() = testApplication { configTestServer() From dab340f9bff1662cc2975d675ec14550d14adcc5 Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Tue, 28 Jul 2026 15:43:24 +0200 Subject: [PATCH 390/449] fix(server): drop inert eventStore from stateless Streamable HTTP (#909) Removes the `eventStore` parameter from `mcpStatelessStreamableHttp`, where it was never read or written. closes #908 ## Breaking Changes Source-breaking only ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --- kotlin-sdk-server/api/kotlin-sdk-server.api | 2 + .../kotlin/sdk/server/KtorServer.kt | 39 ++++++++++++++++--- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/kotlin-sdk-server/api/kotlin-sdk-server.api b/kotlin-sdk-server/api/kotlin-sdk-server.api index 1ec2efd..d8e4c7a 100644 --- a/kotlin-sdk-server/api/kotlin-sdk-server.api +++ b/kotlin-sdk-server/api/kotlin-sdk-server.api @@ -58,7 +58,9 @@ public final class io/modelcontextprotocol/kotlin/sdk/server/KtorServerKt { public static synthetic fun mcp$default (Lio/ktor/server/routing/Route;Ljava/lang/String;ZLjava/util/List;Ljava/util/List;JLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V public static synthetic fun mcp$default (Lio/ktor/server/routing/Route;ZLjava/util/List;Ljava/util/List;JLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V public static final fun mcpStatelessStreamableHttp (Lio/ktor/server/application/Application;Ljava/lang/String;ZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/jvm/functions/Function1;)V + public static final fun mcpStatelessStreamableHttp (Lio/ktor/server/application/Application;Ljava/lang/String;ZLjava/util/List;Ljava/util/List;Lkotlin/jvm/functions/Function1;)V public static synthetic fun mcpStatelessStreamableHttp$default (Lio/ktor/server/application/Application;Ljava/lang/String;ZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V + public static synthetic fun mcpStatelessStreamableHttp$default (Lio/ktor/server/application/Application;Ljava/lang/String;ZLjava/util/List;Ljava/util/List;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V public static final fun mcpStreamableHttp (Lio/ktor/server/application/Application;Ljava/lang/String;ZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V public static synthetic fun mcpStreamableHttp$default (Lio/ktor/server/application/Application;Ljava/lang/String;ZLjava/util/List;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/server/EventStore;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V } diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt index 8220a8b..3127499 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt @@ -270,7 +270,6 @@ private fun Application.mcpStatelessStreamableHttp( block: RoutingContext.() -> Server, ) { installMcpContentNegotiation() - install(SSE) routing { route(path) { @@ -297,10 +296,11 @@ private fun Application.mcpStatelessStreamableHttp( * over _stateless_ [Streamable HTTP Transport](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#streamable-http) * * Sets up an HTTP POST endpoint at [path]. GET and DELETE requests return 405 Method Not Allowed. - * Simple request/response pairs are returned as JSON (not SSE streams). + * Every request/response pair is returned as JSON, so this endpoint opens no SSE stream and + * offers no resumability. Use [mcpStreamableHttp] when either is required. * * Automatically installs [ContentNegotiation][io.ktor.server.plugins.contentnegotiation.ContentNegotiation] - * with [McpJson][io.modelcontextprotocol.kotlin.sdk.types.McpJson] and [SSE]. + * with [McpJson][io.modelcontextprotocol.kotlin.sdk.types.McpJson]. * * @param path The URL path where the server listens for incoming JSON-RPC requests. Defaults to "/mcp". * @param enableDnsRebindingProtection Determines whether DNS rebinding protection is enabled. Defaults to `true`. @@ -309,7 +309,6 @@ private fun Application.mcpStatelessStreamableHttp( * @param allowedOrigins A list of allowed `Origin` header values, compared by hostname only * (scheme and port are ignored). Requests without an `Origin` header are allowed. * If `null`, origin validation is disabled. - * @param eventStore An optional [EventStore] implementation to provide resumability and event replay support. * @param block factory block with access to the [RoutingContext] (for reading request headers) * that creates and returns the [Server] to handle the connection. */ @@ -319,7 +318,6 @@ public fun Application.mcpStatelessStreamableHttp( enableDnsRebindingProtection: Boolean = true, allowedHosts: List? = null, allowedOrigins: List? = null, - eventStore: EventStore? = null, block: RoutingContext.() -> Server, ) { mcpStatelessStreamableHttp( @@ -328,13 +326,42 @@ public fun Application.mcpStatelessStreamableHttp( allowedHosts = allowedHosts, allowedOrigins = allowedOrigins, configuration = StreamableHttpServerTransport.Configuration( - eventStore = eventStore, enableJsonResponse = true, ), block = block, ) } +/** + * Retained only so that existing call sites get a migration hint instead of an unresolved parameter name. + * + * @param eventStore never consulted by a stateless endpoint. Resumption is driven by a `GET` carrying + * `Last-Event-ID`, and this endpoint answers `GET` with `405 Method Not Allowed`, so no stream + * exists to store events on or replay them to. Use [mcpStreamableHttp] when you need resumability. + */ +@Deprecated( + "Use mcpStatelessStreamableHttp without eventStore.", + ReplaceWith("mcpStatelessStreamableHttp(path, enableDnsRebindingProtection, allowedHosts, allowedOrigins, block)"), + DeprecationLevel.ERROR, +) +@Suppress("UnusedParameter") +public fun Application.mcpStatelessStreamableHttp( + path: String = "/mcp", + enableDnsRebindingProtection: Boolean = true, + allowedHosts: List? = null, + allowedOrigins: List? = null, + eventStore: EventStore?, + block: RoutingContext.() -> Server, +) { + mcpStatelessStreamableHttp( + path = path, + enableDnsRebindingProtection = enableDnsRebindingProtection, + allowedHosts = allowedHosts, + allowedOrigins = allowedOrigins, + block = block, + ) +} + private suspend fun ServerSSESession.mcpSseEndpoint( postEndpoint: String, transportManager: TransportManager, From 76b8e7d63aac39fe41e03684e3b91a6c3724a8da Mon Sep 17 00:00:00 2001 From: Pavel Gorgulov Date: Tue, 28 Jul 2026 16:41:12 +0200 Subject: [PATCH 391/449] release: 0.15.0 (#918) Bump version to 0.15.0 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 434144d..a5655c3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,4 +8,4 @@ kotlin.daemon.jvmargs=-Xmx4G kotlin.native.ignoreDisabledTargets=true group=io.modelcontextprotocol -version=0.14.0 +version=0.15.0 From b133d7ecdd42a498d09dc3e60094fae94e1e899c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 13:48:11 +0200 Subject: [PATCH 392/449] chore(deps): bump ch.qos.logback:logback-classic from 1.5.38 to 1.6.1 (#916) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.38 to 1.6.1.
        Release notes

        Sourced from ch.qos.logback:logback-classic's releases.

        Logback 1.6.1

        2026-07-28 Release of logback version 1.6.1

        • In TimeBasedRollingPolicy, when the file option is set, the intermediate file renamed before asynchronous compression now receives the target archive name without the compression suffix (e.g. .gz, .zip, .xz). Previously it used a nanotime-based .tmp suffix. This makes the file easier to identify if compression fails during rollover. (See also the following paragraph.)

        • On GZ, ZIP, or XZ compression failure, the original (uncompressed) log file is no longer deleted. Compression strategies now delete the source file only after successful compression and emit a warning that the original was left intact.

        • ConsoleAppender with now probes JLine's org.jline.jansi.AnsiConsole first and falls back to the legacy FuseSource org.fusesource.jansi.AnsiConsole class. This keeps ANSI coloring working after Jansi moved under the JLine project. The optional org.jline:jansi-core artifact is declared as a dependency alongside the existing FuseSource jansi dependency. A preferredJansiClassName property was added for tests. This issue was reported in issues/1043 by seonwoo_jung who also provided the relevant PR.

        • LayoutWrappingEncoder now reports an error at start() when no layout is set and guards encode() against a null layout. Previously, a missing layout (for example after an ignored // branch) allowed the encoder to start and then fail with a NullPointerException on every event, resulting in silent log loss. This issue was reported in issues/1046 by seonwoo_jung who also provided the relevant PR.

        • FileCollisionAnalyser now detects file collisions involving nested appenders of SiftingAppender. When the nested file or fileNamePattern does not textually reference the discriminator key (e.g. ${userId}), a warning is issued at configuration time naming the appender, the key, and the shared target. This closes a gap where statically declared file appenders were checked but sifted nested appenders were not. This enhancement was contributed in [PR #1041](qos-ch/logback#1041) by seonwoo_jung.

        • More defensive handling in SyslogOutputStream and SyslogAppenderBase: the close() method now ensures that resources are closed, writes and flushes check that the underlying resources are in a valid state and fallback to no-op otherwise.

        • A bit-wise identical binary of this version can be reproduced by building from source code at commit 57759f433000a133088ef0441038963134437fbd associated with the tag v_1.6.1. The release was built using Java "21" 2023-10-17 LTS build 21.0.1.+12-LTS-29 under Linux Debian 11.6.

        • See https://logback.qos.ch/news.html#1.6.1 for the original text.

        Logback 1.6.0

        2026-07-23 Release of logback version 1.6.0

        • Removed certain deprecated variables, methods, and classes. For the list of removed members see release_1.6.0.txt.

        • In AsyncAppenderBase, the put(ILoggingEvent) method now has the protected modifier to allow access from derived classes. This change was requested by Thomas Skjølberg in pr#1053.

        • Bump SLF4J dependency to version 2.0.18.

        • See also the overview of the 1.6.x series.

        • A bit-wise identical binary of this version can be reproduced by building from source code at commit b07adf36019b51a10f824fdd94009985c587b1d3 associated with the tag v_1.6.0. The release was built using Java "21" 2023-10-17 LTS build 21.0.1.+12-LTS-29 under Linux Debian 11.6.

        Commits
        • 57759f4 prepare release 1.6.1
        • 175f99f fix imports
        • 4b8773e add compressionFailureLeavesOriginalFileIntact test for XZ compression
        • cafaf11 do not delete original file if compression fails
        • ee50125 let the temporary file before compression be target file without the .gz or ....
        • 5626acc minor refactoring
        • d97da4f minor refactoring
        • 159c045 more defensive coding in SyslogOutputStream and in SyslogAppenderBase
        • 9427d6b slight refactoring for clarity
        • 79c4179 slight refactoring
        • Additional commits viewable in compare view

        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d648750..6d7bbbb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,7 +22,7 @@ logging = "8.0.4" mockk = "1.14.11" serialization = "1.11.0" slf4j = "2.0.18" -logback = "1.5.38" +logback = "1.6.1" junit = "5.14.4" [libraries] From cce22c282aa5f9fc2bbb7c7c58f24f9c7973087f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Aug 2026 12:23:43 +0200 Subject: [PATCH 393/449] chore(deps): bump ch.qos.logback:logback-classic from 1.6.0 to 1.6.1 in /samples/kotlinlang-mcp-server in the other-dependencies group across 1 directory (#924) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the other-dependencies group with 1 update in the /samples/kotlinlang-mcp-server directory: [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback). Updates `ch.qos.logback:logback-classic` from 1.6.0 to 1.6.1
        Release notes

        Sourced from ch.qos.logback:logback-classic's releases.

        Logback 1.6.1

        2026-07-28 Release of logback version 1.6.1

        • In TimeBasedRollingPolicy, when the file option is set, the intermediate file renamed before asynchronous compression now receives the target archive name without the compression suffix (e.g. .gz, .zip, .xz). Previously it used a nanotime-based .tmp suffix. This makes the file easier to identify if compression fails during rollover. (See also the following paragraph.)

        • On GZ, ZIP, or XZ compression failure, the original (uncompressed) log file is no longer deleted. Compression strategies now delete the source file only after successful compression and emit a warning that the original was left intact.

        • ConsoleAppender with now probes JLine's org.jline.jansi.AnsiConsole first and falls back to the legacy FuseSource org.fusesource.jansi.AnsiConsole class. This keeps ANSI coloring working after Jansi moved under the JLine project. The optional org.jline:jansi-core artifact is declared as a dependency alongside the existing FuseSource jansi dependency. A preferredJansiClassName property was added for tests. This issue was reported in issues/1043 by seonwoo_jung who also provided the relevant PR.

        • LayoutWrappingEncoder now reports an error at start() when no layout is set and guards encode() against a null layout. Previously, a missing layout (for example after an ignored // branch) allowed the encoder to start and then fail with a NullPointerException on every event, resulting in silent log loss. This issue was reported in issues/1046 by seonwoo_jung who also provided the relevant PR.

        • FileCollisionAnalyser now detects file collisions involving nested appenders of SiftingAppender. When the nested file or fileNamePattern does not textually reference the discriminator key (e.g. ${userId}), a warning is issued at configuration time naming the appender, the key, and the shared target. This closes a gap where statically declared file appenders were checked but sifted nested appenders were not. This enhancement was contributed in [PR #1041](qos-ch/logback#1041) by seonwoo_jung.

        • More defensive handling in SyslogOutputStream and SyslogAppenderBase: the close() method now ensures that resources are closed, writes and flushes check that the underlying resources are in a valid state and fallback to no-op otherwise.

        • A bit-wise identical binary of this version can be reproduced by building from source code at commit 57759f433000a133088ef0441038963134437fbd associated with the tag v_1.6.1. The release was built using Java "21" 2023-10-17 LTS build 21.0.1.+12-LTS-29 under Linux Debian 11.6.

        • See https://logback.qos.ch/news.html#1.6.1 for the original text.

        Commits
        • 57759f4 prepare release 1.6.1
        • 175f99f fix imports
        • 4b8773e add compressionFailureLeavesOriginalFileIntact test for XZ compression
        • cafaf11 do not delete original file if compression fails
        • ee50125 let the temporary file before compression be target file without the .gz or ....
        • 5626acc minor refactoring
        • d97da4f minor refactoring
        • 159c045 more defensive coding in SyslogOutputStream and in SyslogAppenderBase
        • 9427d6b slight refactoring for clarity
        • 79c4179 slight refactoring
        • Additional commits viewable in compare view

        [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=ch.qos.logback:logback-classic&package-manager=gradle&previous-version=1.6.0&new-version=1.6.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
        Dependabot commands and options
        You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlinlang-mcp-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml index 363c373..f059fa4 100644 --- a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml @@ -4,7 +4,7 @@ ktor = "3.5.1" mcp-kotlin = "0.14.0" serialization = "1.11.0" coroutines = "1.11.0" -logback = "1.6.0" +logback = "1.6.1" shadow = "9.6.1" [libraries] From 6c9ca01922ead6a2a0e7e6c29e30e549f76c0a79 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Aug 2026 13:03:13 +0200 Subject: [PATCH 394/449] chore(deps): bump io.modelcontextprotocol:kotlin-sdk-client from 0.14.0 to 0.15.0 in /samples/kotlin-mcp-client (#925) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [io.modelcontextprotocol:kotlin-sdk-client](https://github.com/modelcontextprotocol/kotlin-sdk) from 0.14.0 to 0.15.0.
        Release notes

        Sourced from io.modelcontextprotocol:kotlin-sdk-client's releases.

        0.15.0

        Description

        This release makes inbound message handling concurrent after the initialization handshake, adds SSE heartbeats and elicitation schema validation, and fixes several Streamable HTTP response and session-lifecycle bugs.

        Breaking Changes

        Concurrent inbound dispatch by @​devcrocod in #884

        Inbound messages were processed serially on the transport read loop, so one slow or peer-awaiting handler (sampling, elicitation, roots) blocked every later message on the connection, including the responses and notifications/cancelled those handlers depend on. After the handshake, handlers may now run concurrently; processing stays serial during initialization.

        • ProtocolOptions / ClientOptions / ServerOptions gain a handlerCoroutineContext parameter (defaults to Dispatchers.Default). Concurrency is bounded internally; there is no opt-out flag by design.
        • RequestHandlerExtra is now a CoroutineContext.Element and is no longer user-constructible. It exposes requestId, method, sendRequest, and sendNotification, and can be read inside a handler via the new currentRequestHandlerExtra().
        - val extra = RequestHandlerExtra()
        + val extra = currentRequestHandlerExtra()
        

        Duplicate feature names are rejected at registration by @​devcrocod in #883

        addTool/addPrompt/addResource/addResourceTemplate silently replaced an existing entry and emitted a spurious list_changed notification. They now throw IllegalArgumentException and leave the existing registration intact. The batch variants (addTools/addPrompts/addResources) are all-or-nothing. To replace a feature, remove it first.

        - server.addTool(name = "search", ...)
        // silently overwrote
        + server.removeTool("search")
        + server.addTool(name = "search", ...)
        

        eventStore removed from mcpStatelessStreamableHttp by @​devcrocod in #909

        The parameter was never read or written: a stateless endpoint answers GET with 405, so no stream exists to store or replay events on. The old overload is retained at DeprecationLevel.ERROR with a ReplaceWith migration hint. Use mcpStreamableHttp when you need resumability.

          application.mcpStatelessStreamableHttp(
              path = "/mcp",
        -     eventStore = myEventStore,
          ) { server }
        

        Features

        • Optional SSE heartbeats for Streamable HTTP servers via mcpStreamableHttp(sseHeartbeatConfig = { ... }), keeping long-lived streams alive against clients that disconnect on idle. Heartbeats stay off by default, by @​UnscientificJsZhai in #761
        • Accepted form-mode elicitation content is now validated against requestedSchema; a mismatch fails createElicitation with McpException (INVALID_PARAMS) instead of reaching server code unchecked, by @​rea9r in #896
        • The Streamable HTTP client now sends the standard Mcp-Method and Mcp-Name POST headers, by @​AndreKalberer in #894

        Fixed

        • Stateless Streamable HTTP now closes its per-request session, fixing unbounded growth of the session registry and notification subscriptions, by @​KlyneChrysler in #872
        • Streamable HTTP responses keep their status and body when the client accepts only text/event-stream, by @​devcrocod in #911

        ... (truncated)

        Commits
        • 76b8e7d release: 0.15.0 (#918)
        • dab340f fix(server): drop inert eventStore from stateless Streamable HTTP (#909)
        • 24c3cd3 fix(server): match Accept header as media ranges (#912)
        • 7fa438c feat(client): send MCP standard POST headers (#894)
        • 94745e6 chore(deps): bump the kotest group across 1 directory with 3 updates (#915)
        • 2b494b7 chore(deps): bump the other-dependencies group across 5 directories with 3 up...
        • 82fe56e fix(server): preserve Streamable HTTP response status and body (#911)
        • a06d699 feat: SSE heartbeat (#761)
        • cab669b fix: classify malformed request params as invalid params (#886)
        • b6cb547 fix(server): preserve id on duplicate initialize errors (#868)
        • Additional commits viewable in compare view

        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-client/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index 921a7b2..ea0043d 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -2,7 +2,7 @@ anthropic = "2.52.0" kotlin = "2.4.10" ktor = "3.5.1" -mcp-kotlin = "0.14.0" +mcp-kotlin = "0.15.0" shadow = "9.6.1" slf4j = "2.0.18" From 47938a056c87ab862e83f52a1a619956cebbcf80 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Aug 2026 13:20:37 +0200 Subject: [PATCH 395/449] chore(deps): bump mcp-kotlin from 0.14.0 to 0.15.0 in /samples/kotlin-mcp-server (#926) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `mcp-kotlin` from 0.14.0 to 0.15.0. Updates `io.modelcontextprotocol:kotlin-sdk-client` from 0.14.0 to 0.15.0
        Release notes

        Sourced from io.modelcontextprotocol:kotlin-sdk-client's releases.

        0.15.0

        Description

        This release makes inbound message handling concurrent after the initialization handshake, adds SSE heartbeats and elicitation schema validation, and fixes several Streamable HTTP response and session-lifecycle bugs.

        Breaking Changes

        Concurrent inbound dispatch by @​devcrocod in #884

        Inbound messages were processed serially on the transport read loop, so one slow or peer-awaiting handler (sampling, elicitation, roots) blocked every later message on the connection, including the responses and notifications/cancelled those handlers depend on. After the handshake, handlers may now run concurrently; processing stays serial during initialization.

        • ProtocolOptions / ClientOptions / ServerOptions gain a handlerCoroutineContext parameter (defaults to Dispatchers.Default). Concurrency is bounded internally; there is no opt-out flag by design.
        • RequestHandlerExtra is now a CoroutineContext.Element and is no longer user-constructible. It exposes requestId, method, sendRequest, and sendNotification, and can be read inside a handler via the new currentRequestHandlerExtra().
        - val extra = RequestHandlerExtra()
        + val extra = currentRequestHandlerExtra()
        

        Duplicate feature names are rejected at registration by @​devcrocod in #883

        addTool/addPrompt/addResource/addResourceTemplate silently replaced an existing entry and emitted a spurious list_changed notification. They now throw IllegalArgumentException and leave the existing registration intact. The batch variants (addTools/addPrompts/addResources) are all-or-nothing. To replace a feature, remove it first.

        - server.addTool(name = "search", ...)
        // silently overwrote
        + server.removeTool("search")
        + server.addTool(name = "search", ...)
        

        eventStore removed from mcpStatelessStreamableHttp by @​devcrocod in #909

        The parameter was never read or written: a stateless endpoint answers GET with 405, so no stream exists to store or replay events on. The old overload is retained at DeprecationLevel.ERROR with a ReplaceWith migration hint. Use mcpStreamableHttp when you need resumability.

          application.mcpStatelessStreamableHttp(
              path = "/mcp",
        -     eventStore = myEventStore,
          ) { server }
        

        Features

        • Optional SSE heartbeats for Streamable HTTP servers via mcpStreamableHttp(sseHeartbeatConfig = { ... }), keeping long-lived streams alive against clients that disconnect on idle. Heartbeats stay off by default, by @​UnscientificJsZhai in #761
        • Accepted form-mode elicitation content is now validated against requestedSchema; a mismatch fails createElicitation with McpException (INVALID_PARAMS) instead of reaching server code unchecked, by @​rea9r in #896
        • The Streamable HTTP client now sends the standard Mcp-Method and Mcp-Name POST headers, by @​AndreKalberer in #894

        Fixed

        • Stateless Streamable HTTP now closes its per-request session, fixing unbounded growth of the session registry and notification subscriptions, by @​KlyneChrysler in #872
        • Streamable HTTP responses keep their status and body when the client accepts only text/event-stream, by @​devcrocod in #911

        ... (truncated)

        Commits
        • 76b8e7d release: 0.15.0 (#918)
        • dab340f fix(server): drop inert eventStore from stateless Streamable HTTP (#909)
        • 24c3cd3 fix(server): match Accept header as media ranges (#912)
        • 7fa438c feat(client): send MCP standard POST headers (#894)
        • 94745e6 chore(deps): bump the kotest group across 1 directory with 3 updates (#915)
        • 2b494b7 chore(deps): bump the other-dependencies group across 5 directories with 3 up...
        • 82fe56e fix(server): preserve Streamable HTTP response status and body (#911)
        • a06d699 feat: SSE heartbeat (#761)
        • cab669b fix: classify malformed request params as invalid params (#886)
        • b6cb547 fix(server): preserve id on duplicate initialize errors (#868)
        • Additional commits viewable in compare view

        Updates `io.modelcontextprotocol:kotlin-sdk-server` from 0.14.0 to 0.15.0
        Release notes

        Sourced from io.modelcontextprotocol:kotlin-sdk-server's releases.

        0.15.0

        Description

        This release makes inbound message handling concurrent after the initialization handshake, adds SSE heartbeats and elicitation schema validation, and fixes several Streamable HTTP response and session-lifecycle bugs.

        Breaking Changes

        Concurrent inbound dispatch by @​devcrocod in #884

        Inbound messages were processed serially on the transport read loop, so one slow or peer-awaiting handler (sampling, elicitation, roots) blocked every later message on the connection, including the responses and notifications/cancelled those handlers depend on. After the handshake, handlers may now run concurrently; processing stays serial during initialization.

        • ProtocolOptions / ClientOptions / ServerOptions gain a handlerCoroutineContext parameter (defaults to Dispatchers.Default). Concurrency is bounded internally; there is no opt-out flag by design.
        • RequestHandlerExtra is now a CoroutineContext.Element and is no longer user-constructible. It exposes requestId, method, sendRequest, and sendNotification, and can be read inside a handler via the new currentRequestHandlerExtra().
        - val extra = RequestHandlerExtra()
        + val extra = currentRequestHandlerExtra()
        

        Duplicate feature names are rejected at registration by @​devcrocod in #883

        addTool/addPrompt/addResource/addResourceTemplate silently replaced an existing entry and emitted a spurious list_changed notification. They now throw IllegalArgumentException and leave the existing registration intact. The batch variants (addTools/addPrompts/addResources) are all-or-nothing. To replace a feature, remove it first.

        - server.addTool(name = "search", ...)
        // silently overwrote
        + server.removeTool("search")
        + server.addTool(name = "search", ...)
        

        eventStore removed from mcpStatelessStreamableHttp by @​devcrocod in #909

        The parameter was never read or written: a stateless endpoint answers GET with 405, so no stream exists to store or replay events on. The old overload is retained at DeprecationLevel.ERROR with a ReplaceWith migration hint. Use mcpStreamableHttp when you need resumability.

          application.mcpStatelessStreamableHttp(
              path = "/mcp",
        -     eventStore = myEventStore,
          ) { server }
        

        Features

        • Optional SSE heartbeats for Streamable HTTP servers via mcpStreamableHttp(sseHeartbeatConfig = { ... }), keeping long-lived streams alive against clients that disconnect on idle. Heartbeats stay off by default, by @​UnscientificJsZhai in #761
        • Accepted form-mode elicitation content is now validated against requestedSchema; a mismatch fails createElicitation with McpException (INVALID_PARAMS) instead of reaching server code unchecked, by @​rea9r in #896
        • The Streamable HTTP client now sends the standard Mcp-Method and Mcp-Name POST headers, by @​AndreKalberer in #894

        Fixed

        • Stateless Streamable HTTP now closes its per-request session, fixing unbounded growth of the session registry and notification subscriptions, by @​KlyneChrysler in #872
        • Streamable HTTP responses keep their status and body when the client accepts only text/event-stream, by @​devcrocod in #911

        ... (truncated)

        Commits
        • 76b8e7d release: 0.15.0 (#918)
        • dab340f fix(server): drop inert eventStore from stateless Streamable HTTP (#909)
        • 24c3cd3 fix(server): match Accept header as media ranges (#912)
        • 7fa438c feat(client): send MCP standard POST headers (#894)
        • 94745e6 chore(deps): bump the kotest group across 1 directory with 3 updates (#915)
        • 2b494b7 chore(deps): bump the other-dependencies group across 5 directories with 3 up...
        • 82fe56e fix(server): preserve Streamable HTTP response status and body (#911)
        • a06d699 feat: SSE heartbeat (#761)
        • cab669b fix: classify malformed request params as invalid params (#886)
        • b6cb547 fix(server): preserve id on duplicate initialize errors (#868)
        • Additional commits viewable in compare view

        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-server/gradle/libs.versions.toml b/samples/kotlin-mcp-server/gradle/libs.versions.toml index 230336b..83a262e 100644 --- a/samples/kotlin-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-server/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] kotlin = "2.4.0" ktor = "3.5.1" -mcp-kotlin = "0.14.0" +mcp-kotlin = "0.15.0" slf4j = "2.0.18" shadow = "9.6.1" From b7f280118ded1e6a04aad60d8ff14aa7cba10d47 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Aug 2026 13:39:04 +0200 Subject: [PATCH 396/449] chore(deps): bump mcp-kotlin from 0.14.0 to 0.15.0 in /samples/kotlinlang-mcp-server (#927) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `mcp-kotlin` from 0.14.0 to 0.15.0. Updates `io.modelcontextprotocol:kotlin-sdk-server` from 0.14.0 to 0.15.0
        Release notes

        Sourced from io.modelcontextprotocol:kotlin-sdk-server's releases.

        0.15.0

        Description

        This release makes inbound message handling concurrent after the initialization handshake, adds SSE heartbeats and elicitation schema validation, and fixes several Streamable HTTP response and session-lifecycle bugs.

        Breaking Changes

        Concurrent inbound dispatch by @​devcrocod in #884

        Inbound messages were processed serially on the transport read loop, so one slow or peer-awaiting handler (sampling, elicitation, roots) blocked every later message on the connection, including the responses and notifications/cancelled those handlers depend on. After the handshake, handlers may now run concurrently; processing stays serial during initialization.

        • ProtocolOptions / ClientOptions / ServerOptions gain a handlerCoroutineContext parameter (defaults to Dispatchers.Default). Concurrency is bounded internally; there is no opt-out flag by design.
        • RequestHandlerExtra is now a CoroutineContext.Element and is no longer user-constructible. It exposes requestId, method, sendRequest, and sendNotification, and can be read inside a handler via the new currentRequestHandlerExtra().
        - val extra = RequestHandlerExtra()
        + val extra = currentRequestHandlerExtra()
        

        Duplicate feature names are rejected at registration by @​devcrocod in #883

        addTool/addPrompt/addResource/addResourceTemplate silently replaced an existing entry and emitted a spurious list_changed notification. They now throw IllegalArgumentException and leave the existing registration intact. The batch variants (addTools/addPrompts/addResources) are all-or-nothing. To replace a feature, remove it first.

        - server.addTool(name = "search", ...)
        // silently overwrote
        + server.removeTool("search")
        + server.addTool(name = "search", ...)
        

        eventStore removed from mcpStatelessStreamableHttp by @​devcrocod in #909

        The parameter was never read or written: a stateless endpoint answers GET with 405, so no stream exists to store or replay events on. The old overload is retained at DeprecationLevel.ERROR with a ReplaceWith migration hint. Use mcpStreamableHttp when you need resumability.

          application.mcpStatelessStreamableHttp(
              path = "/mcp",
        -     eventStore = myEventStore,
          ) { server }
        

        Features

        • Optional SSE heartbeats for Streamable HTTP servers via mcpStreamableHttp(sseHeartbeatConfig = { ... }), keeping long-lived streams alive against clients that disconnect on idle. Heartbeats stay off by default, by @​UnscientificJsZhai in #761
        • Accepted form-mode elicitation content is now validated against requestedSchema; a mismatch fails createElicitation with McpException (INVALID_PARAMS) instead of reaching server code unchecked, by @​rea9r in #896
        • The Streamable HTTP client now sends the standard Mcp-Method and Mcp-Name POST headers, by @​AndreKalberer in #894

        Fixed

        • Stateless Streamable HTTP now closes its per-request session, fixing unbounded growth of the session registry and notification subscriptions, by @​KlyneChrysler in #872
        • Streamable HTTP responses keep their status and body when the client accepts only text/event-stream, by @​devcrocod in #911

        ... (truncated)

        Commits
        • 76b8e7d release: 0.15.0 (#918)
        • dab340f fix(server): drop inert eventStore from stateless Streamable HTTP (#909)
        • 24c3cd3 fix(server): match Accept header as media ranges (#912)
        • 7fa438c feat(client): send MCP standard POST headers (#894)
        • 94745e6 chore(deps): bump the kotest group across 1 directory with 3 updates (#915)
        • 2b494b7 chore(deps): bump the other-dependencies group across 5 directories with 3 up...
        • 82fe56e fix(server): preserve Streamable HTTP response status and body (#911)
        • a06d699 feat: SSE heartbeat (#761)
        • cab669b fix: classify malformed request params as invalid params (#886)
        • b6cb547 fix(server): preserve id on duplicate initialize errors (#868)
        • Additional commits viewable in compare view

        Updates `io.modelcontextprotocol:kotlin-sdk-client` from 0.14.0 to 0.15.0
        Release notes

        Sourced from io.modelcontextprotocol:kotlin-sdk-client's releases.

        0.15.0

        Description

        This release makes inbound message handling concurrent after the initialization handshake, adds SSE heartbeats and elicitation schema validation, and fixes several Streamable HTTP response and session-lifecycle bugs.

        Breaking Changes

        Concurrent inbound dispatch by @​devcrocod in #884

        Inbound messages were processed serially on the transport read loop, so one slow or peer-awaiting handler (sampling, elicitation, roots) blocked every later message on the connection, including the responses and notifications/cancelled those handlers depend on. After the handshake, handlers may now run concurrently; processing stays serial during initialization.

        • ProtocolOptions / ClientOptions / ServerOptions gain a handlerCoroutineContext parameter (defaults to Dispatchers.Default). Concurrency is bounded internally; there is no opt-out flag by design.
        • RequestHandlerExtra is now a CoroutineContext.Element and is no longer user-constructible. It exposes requestId, method, sendRequest, and sendNotification, and can be read inside a handler via the new currentRequestHandlerExtra().
        - val extra = RequestHandlerExtra()
        + val extra = currentRequestHandlerExtra()
        

        Duplicate feature names are rejected at registration by @​devcrocod in #883

        addTool/addPrompt/addResource/addResourceTemplate silently replaced an existing entry and emitted a spurious list_changed notification. They now throw IllegalArgumentException and leave the existing registration intact. The batch variants (addTools/addPrompts/addResources) are all-or-nothing. To replace a feature, remove it first.

        - server.addTool(name = "search", ...)
        // silently overwrote
        + server.removeTool("search")
        + server.addTool(name = "search", ...)
        

        eventStore removed from mcpStatelessStreamableHttp by @​devcrocod in #909

        The parameter was never read or written: a stateless endpoint answers GET with 405, so no stream exists to store or replay events on. The old overload is retained at DeprecationLevel.ERROR with a ReplaceWith migration hint. Use mcpStreamableHttp when you need resumability.

          application.mcpStatelessStreamableHttp(
              path = "/mcp",
        -     eventStore = myEventStore,
          ) { server }
        

        Features

        • Optional SSE heartbeats for Streamable HTTP servers via mcpStreamableHttp(sseHeartbeatConfig = { ... }), keeping long-lived streams alive against clients that disconnect on idle. Heartbeats stay off by default, by @​UnscientificJsZhai in #761
        • Accepted form-mode elicitation content is now validated against requestedSchema; a mismatch fails createElicitation with McpException (INVALID_PARAMS) instead of reaching server code unchecked, by @​rea9r in #896
        • The Streamable HTTP client now sends the standard Mcp-Method and Mcp-Name POST headers, by @​AndreKalberer in #894

        Fixed

        • Stateless Streamable HTTP now closes its per-request session, fixing unbounded growth of the session registry and notification subscriptions, by @​KlyneChrysler in #872
        • Streamable HTTP responses keep their status and body when the client accepts only text/event-stream, by @​devcrocod in #911

        ... (truncated)

        Commits
        • 76b8e7d release: 0.15.0 (#918)
        • dab340f fix(server): drop inert eventStore from stateless Streamable HTTP (#909)
        • 24c3cd3 fix(server): match Accept header as media ranges (#912)
        • 7fa438c feat(client): send MCP standard POST headers (#894)
        • 94745e6 chore(deps): bump the kotest group across 1 directory with 3 updates (#915)
        • 2b494b7 chore(deps): bump the other-dependencies group across 5 directories with 3 up...
        • 82fe56e fix(server): preserve Streamable HTTP response status and body (#911)
        • a06d699 feat: SSE heartbeat (#761)
        • cab669b fix: classify malformed request params as invalid params (#886)
        • b6cb547 fix(server): preserve id on duplicate initialize errors (#868)
        • Additional commits viewable in compare view

        Updates `io.modelcontextprotocol:kotlin-sdk-testing` from 0.14.0 to 0.15.0
        Release notes

        Sourced from io.modelcontextprotocol:kotlin-sdk-testing's releases.

        0.15.0

        Description

        This release makes inbound message handling concurrent after the initialization handshake, adds SSE heartbeats and elicitation schema validation, and fixes several Streamable HTTP response and session-lifecycle bugs.

        Breaking Changes

        Concurrent inbound dispatch by @​devcrocod in #884

        Inbound messages were processed serially on the transport read loop, so one slow or peer-awaiting handler (sampling, elicitation, roots) blocked every later message on the connection, including the responses and notifications/cancelled those handlers depend on. After the handshake, handlers may now run concurrently; processing stays serial during initialization.

        • ProtocolOptions / ClientOptions / ServerOptions gain a handlerCoroutineContext parameter (defaults to Dispatchers.Default). Concurrency is bounded internally; there is no opt-out flag by design.
        • RequestHandlerExtra is now a CoroutineContext.Element and is no longer user-constructible. It exposes requestId, method, sendRequest, and sendNotification, and can be read inside a handler via the new currentRequestHandlerExtra().
        - val extra = RequestHandlerExtra()
        + val extra = currentRequestHandlerExtra()
        

        Duplicate feature names are rejected at registration by @​devcrocod in #883

        addTool/addPrompt/addResource/addResourceTemplate silently replaced an existing entry and emitted a spurious list_changed notification. They now throw IllegalArgumentException and leave the existing registration intact. The batch variants (addTools/addPrompts/addResources) are all-or-nothing. To replace a feature, remove it first.

        - server.addTool(name = "search", ...)
        // silently overwrote
        + server.removeTool("search")
        + server.addTool(name = "search", ...)
        

        eventStore removed from mcpStatelessStreamableHttp by @​devcrocod in #909

        The parameter was never read or written: a stateless endpoint answers GET with 405, so no stream exists to store or replay events on. The old overload is retained at DeprecationLevel.ERROR with a ReplaceWith migration hint. Use mcpStreamableHttp when you need resumability.

          application.mcpStatelessStreamableHttp(
              path = "/mcp",
        -     eventStore = myEventStore,
          ) { server }
        

        Features

        • Optional SSE heartbeats for Streamable HTTP servers via mcpStreamableHttp(sseHeartbeatConfig = { ... }), keeping long-lived streams alive against clients that disconnect on idle. Heartbeats stay off by default, by @​UnscientificJsZhai in #761
        • Accepted form-mode elicitation content is now validated against requestedSchema; a mismatch fails createElicitation with McpException (INVALID_PARAMS) instead of reaching server code unchecked, by @​rea9r in #896
        • The Streamable HTTP client now sends the standard Mcp-Method and Mcp-Name POST headers, by @​AndreKalberer in #894

        Fixed

        • Stateless Streamable HTTP now closes its per-request session, fixing unbounded growth of the session registry and notification subscriptions, by @​KlyneChrysler in #872
        • Streamable HTTP responses keep their status and body when the client accepts only text/event-stream, by @​devcrocod in #911

        ... (truncated)

        Commits
        • 76b8e7d release: 0.15.0 (#918)
        • dab340f fix(server): drop inert eventStore from stateless Streamable HTTP (#909)
        • 24c3cd3 fix(server): match Accept header as media ranges (#912)
        • 7fa438c feat(client): send MCP standard POST headers (#894)
        • 94745e6 chore(deps): bump the kotest group across 1 directory with 3 updates (#915)
        • 2b494b7 chore(deps): bump the other-dependencies group across 5 directories with 3 up...
        • 82fe56e fix(server): preserve Streamable HTTP response status and body (#911)
        • a06d699 feat: SSE heartbeat (#761)
        • cab669b fix: classify malformed request params as invalid params (#886)
        • b6cb547 fix(server): preserve id on duplicate initialize errors (#868)
        • Additional commits viewable in compare view

        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlinlang-mcp-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml index f059fa4..2d48f35 100644 --- a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] kotlin = "2.4.0" ktor = "3.5.1" -mcp-kotlin = "0.14.0" +mcp-kotlin = "0.15.0" serialization = "1.11.0" coroutines = "1.11.0" logback = "1.6.1" From bb716cd44d6fcb205f77ad34a7cd5b7f0d6ec46d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Aug 2026 17:31:38 +0200 Subject: [PATCH 397/449] chore(deps): bump mcp-kotlin from 0.14.0 to 0.15.0 in /samples/simple-streamable-server (#928) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `mcp-kotlin` from 0.14.0 to 0.15.0. Updates `io.modelcontextprotocol:kotlin-sdk-client` from 0.14.0 to 0.15.0
        Release notes

        Sourced from io.modelcontextprotocol:kotlin-sdk-client's releases.

        0.15.0

        Description

        This release makes inbound message handling concurrent after the initialization handshake, adds SSE heartbeats and elicitation schema validation, and fixes several Streamable HTTP response and session-lifecycle bugs.

        Breaking Changes

        Concurrent inbound dispatch by @​devcrocod in #884

        Inbound messages were processed serially on the transport read loop, so one slow or peer-awaiting handler (sampling, elicitation, roots) blocked every later message on the connection, including the responses and notifications/cancelled those handlers depend on. After the handshake, handlers may now run concurrently; processing stays serial during initialization.

        • ProtocolOptions / ClientOptions / ServerOptions gain a handlerCoroutineContext parameter (defaults to Dispatchers.Default). Concurrency is bounded internally; there is no opt-out flag by design.
        • RequestHandlerExtra is now a CoroutineContext.Element and is no longer user-constructible. It exposes requestId, method, sendRequest, and sendNotification, and can be read inside a handler via the new currentRequestHandlerExtra().
        - val extra = RequestHandlerExtra()
        + val extra = currentRequestHandlerExtra()
        

        Duplicate feature names are rejected at registration by @​devcrocod in #883

        addTool/addPrompt/addResource/addResourceTemplate silently replaced an existing entry and emitted a spurious list_changed notification. They now throw IllegalArgumentException and leave the existing registration intact. The batch variants (addTools/addPrompts/addResources) are all-or-nothing. To replace a feature, remove it first.

        - server.addTool(name = "search", ...)
        // silently overwrote
        + server.removeTool("search")
        + server.addTool(name = "search", ...)
        

        eventStore removed from mcpStatelessStreamableHttp by @​devcrocod in #909

        The parameter was never read or written: a stateless endpoint answers GET with 405, so no stream exists to store or replay events on. The old overload is retained at DeprecationLevel.ERROR with a ReplaceWith migration hint. Use mcpStreamableHttp when you need resumability.

          application.mcpStatelessStreamableHttp(
              path = "/mcp",
        -     eventStore = myEventStore,
          ) { server }
        

        Features

        • Optional SSE heartbeats for Streamable HTTP servers via mcpStreamableHttp(sseHeartbeatConfig = { ... }), keeping long-lived streams alive against clients that disconnect on idle. Heartbeats stay off by default, by @​UnscientificJsZhai in #761
        • Accepted form-mode elicitation content is now validated against requestedSchema; a mismatch fails createElicitation with McpException (INVALID_PARAMS) instead of reaching server code unchecked, by @​rea9r in #896
        • The Streamable HTTP client now sends the standard Mcp-Method and Mcp-Name POST headers, by @​AndreKalberer in #894

        Fixed

        • Stateless Streamable HTTP now closes its per-request session, fixing unbounded growth of the session registry and notification subscriptions, by @​KlyneChrysler in #872
        • Streamable HTTP responses keep their status and body when the client accepts only text/event-stream, by @​devcrocod in #911

        ... (truncated)

        Commits
        • 76b8e7d release: 0.15.0 (#918)
        • dab340f fix(server): drop inert eventStore from stateless Streamable HTTP (#909)
        • 24c3cd3 fix(server): match Accept header as media ranges (#912)
        • 7fa438c feat(client): send MCP standard POST headers (#894)
        • 94745e6 chore(deps): bump the kotest group across 1 directory with 3 updates (#915)
        • 2b494b7 chore(deps): bump the other-dependencies group across 5 directories with 3 up...
        • 82fe56e fix(server): preserve Streamable HTTP response status and body (#911)
        • a06d699 feat: SSE heartbeat (#761)
        • cab669b fix: classify malformed request params as invalid params (#886)
        • b6cb547 fix(server): preserve id on duplicate initialize errors (#868)
        • Additional commits viewable in compare view

        Updates `io.modelcontextprotocol:kotlin-sdk-server` from 0.14.0 to 0.15.0
        Release notes

        Sourced from io.modelcontextprotocol:kotlin-sdk-server's releases.

        0.15.0

        Description

        This release makes inbound message handling concurrent after the initialization handshake, adds SSE heartbeats and elicitation schema validation, and fixes several Streamable HTTP response and session-lifecycle bugs.

        Breaking Changes

        Concurrent inbound dispatch by @​devcrocod in #884

        Inbound messages were processed serially on the transport read loop, so one slow or peer-awaiting handler (sampling, elicitation, roots) blocked every later message on the connection, including the responses and notifications/cancelled those handlers depend on. After the handshake, handlers may now run concurrently; processing stays serial during initialization.

        • ProtocolOptions / ClientOptions / ServerOptions gain a handlerCoroutineContext parameter (defaults to Dispatchers.Default). Concurrency is bounded internally; there is no opt-out flag by design.
        • RequestHandlerExtra is now a CoroutineContext.Element and is no longer user-constructible. It exposes requestId, method, sendRequest, and sendNotification, and can be read inside a handler via the new currentRequestHandlerExtra().
        - val extra = RequestHandlerExtra()
        + val extra = currentRequestHandlerExtra()
        

        Duplicate feature names are rejected at registration by @​devcrocod in #883

        addTool/addPrompt/addResource/addResourceTemplate silently replaced an existing entry and emitted a spurious list_changed notification. They now throw IllegalArgumentException and leave the existing registration intact. The batch variants (addTools/addPrompts/addResources) are all-or-nothing. To replace a feature, remove it first.

        - server.addTool(name = "search", ...)
        // silently overwrote
        + server.removeTool("search")
        + server.addTool(name = "search", ...)
        

        eventStore removed from mcpStatelessStreamableHttp by @​devcrocod in #909

        The parameter was never read or written: a stateless endpoint answers GET with 405, so no stream exists to store or replay events on. The old overload is retained at DeprecationLevel.ERROR with a ReplaceWith migration hint. Use mcpStreamableHttp when you need resumability.

          application.mcpStatelessStreamableHttp(
              path = "/mcp",
        -     eventStore = myEventStore,
          ) { server }
        

        Features

        • Optional SSE heartbeats for Streamable HTTP servers via mcpStreamableHttp(sseHeartbeatConfig = { ... }), keeping long-lived streams alive against clients that disconnect on idle. Heartbeats stay off by default, by @​UnscientificJsZhai in #761
        • Accepted form-mode elicitation content is now validated against requestedSchema; a mismatch fails createElicitation with McpException (INVALID_PARAMS) instead of reaching server code unchecked, by @​rea9r in #896
        • The Streamable HTTP client now sends the standard Mcp-Method and Mcp-Name POST headers, by @​AndreKalberer in #894

        Fixed

        • Stateless Streamable HTTP now closes its per-request session, fixing unbounded growth of the session registry and notification subscriptions, by @​KlyneChrysler in #872
        • Streamable HTTP responses keep their status and body when the client accepts only text/event-stream, by @​devcrocod in #911

        ... (truncated)

        Commits
        • 76b8e7d release: 0.15.0 (#918)
        • dab340f fix(server): drop inert eventStore from stateless Streamable HTTP (#909)
        • 24c3cd3 fix(server): match Accept header as media ranges (#912)
        • 7fa438c feat(client): send MCP standard POST headers (#894)
        • 94745e6 chore(deps): bump the kotest group across 1 directory with 3 updates (#915)
        • 2b494b7 chore(deps): bump the other-dependencies group across 5 directories with 3 up...
        • 82fe56e fix(server): preserve Streamable HTTP response status and body (#911)
        • a06d699 feat: SSE heartbeat (#761)
        • cab669b fix: classify malformed request params as invalid params (#886)
        • b6cb547 fix(server): preserve id on duplicate initialize errors (#868)
        • Additional commits viewable in compare view

        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/simple-streamable-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/simple-streamable-server/gradle/libs.versions.toml b/samples/simple-streamable-server/gradle/libs.versions.toml index abd1290..28ab094 100644 --- a/samples/simple-streamable-server/gradle/libs.versions.toml +++ b/samples/simple-streamable-server/gradle/libs.versions.toml @@ -2,7 +2,7 @@ kotlin = "2.4.0" ktor = "3.5.1" junit = "6.1.2" -mcp-kotlin = "0.14.0" +mcp-kotlin = "0.15.0" slf4j = "2.0.18" shadow = "9.6.1" From c61a7ca00c50d86ae7d67c37bf36a89c8289986c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Aug 2026 18:46:44 +0200 Subject: [PATCH 398/449] chore(deps): bump mcp-kotlin from 0.14.0 to 0.15.0 in /samples/weather-stdio-server (#929) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `mcp-kotlin` from 0.14.0 to 0.15.0. Updates `io.modelcontextprotocol:kotlin-sdk-client` from 0.14.0 to 0.15.0
        Release notes

        Sourced from io.modelcontextprotocol:kotlin-sdk-client's releases.

        0.15.0

        Description

        This release makes inbound message handling concurrent after the initialization handshake, adds SSE heartbeats and elicitation schema validation, and fixes several Streamable HTTP response and session-lifecycle bugs.

        Breaking Changes

        Concurrent inbound dispatch by @​devcrocod in #884

        Inbound messages were processed serially on the transport read loop, so one slow or peer-awaiting handler (sampling, elicitation, roots) blocked every later message on the connection, including the responses and notifications/cancelled those handlers depend on. After the handshake, handlers may now run concurrently; processing stays serial during initialization.

        • ProtocolOptions / ClientOptions / ServerOptions gain a handlerCoroutineContext parameter (defaults to Dispatchers.Default). Concurrency is bounded internally; there is no opt-out flag by design.
        • RequestHandlerExtra is now a CoroutineContext.Element and is no longer user-constructible. It exposes requestId, method, sendRequest, and sendNotification, and can be read inside a handler via the new currentRequestHandlerExtra().
        - val extra = RequestHandlerExtra()
        + val extra = currentRequestHandlerExtra()
        

        Duplicate feature names are rejected at registration by @​devcrocod in #883

        addTool/addPrompt/addResource/addResourceTemplate silently replaced an existing entry and emitted a spurious list_changed notification. They now throw IllegalArgumentException and leave the existing registration intact. The batch variants (addTools/addPrompts/addResources) are all-or-nothing. To replace a feature, remove it first.

        - server.addTool(name = "search", ...)
        // silently overwrote
        + server.removeTool("search")
        + server.addTool(name = "search", ...)
        

        eventStore removed from mcpStatelessStreamableHttp by @​devcrocod in #909

        The parameter was never read or written: a stateless endpoint answers GET with 405, so no stream exists to store or replay events on. The old overload is retained at DeprecationLevel.ERROR with a ReplaceWith migration hint. Use mcpStreamableHttp when you need resumability.

          application.mcpStatelessStreamableHttp(
              path = "/mcp",
        -     eventStore = myEventStore,
          ) { server }
        

        Features

        • Optional SSE heartbeats for Streamable HTTP servers via mcpStreamableHttp(sseHeartbeatConfig = { ... }), keeping long-lived streams alive against clients that disconnect on idle. Heartbeats stay off by default, by @​UnscientificJsZhai in #761
        • Accepted form-mode elicitation content is now validated against requestedSchema; a mismatch fails createElicitation with McpException (INVALID_PARAMS) instead of reaching server code unchecked, by @​rea9r in #896
        • The Streamable HTTP client now sends the standard Mcp-Method and Mcp-Name POST headers, by @​AndreKalberer in #894

        Fixed

        • Stateless Streamable HTTP now closes its per-request session, fixing unbounded growth of the session registry and notification subscriptions, by @​KlyneChrysler in #872
        • Streamable HTTP responses keep their status and body when the client accepts only text/event-stream, by @​devcrocod in #911

        ... (truncated)

        Commits
        • 76b8e7d release: 0.15.0 (#918)
        • dab340f fix(server): drop inert eventStore from stateless Streamable HTTP (#909)
        • 24c3cd3 fix(server): match Accept header as media ranges (#912)
        • 7fa438c feat(client): send MCP standard POST headers (#894)
        • 94745e6 chore(deps): bump the kotest group across 1 directory with 3 updates (#915)
        • 2b494b7 chore(deps): bump the other-dependencies group across 5 directories with 3 up...
        • 82fe56e fix(server): preserve Streamable HTTP response status and body (#911)
        • a06d699 feat: SSE heartbeat (#761)
        • cab669b fix: classify malformed request params as invalid params (#886)
        • b6cb547 fix(server): preserve id on duplicate initialize errors (#868)
        • Additional commits viewable in compare view

        Updates `io.modelcontextprotocol:kotlin-sdk-server` from 0.14.0 to 0.15.0
        Release notes

        Sourced from io.modelcontextprotocol:kotlin-sdk-server's releases.

        0.15.0

        Description

        This release makes inbound message handling concurrent after the initialization handshake, adds SSE heartbeats and elicitation schema validation, and fixes several Streamable HTTP response and session-lifecycle bugs.

        Breaking Changes

        Concurrent inbound dispatch by @​devcrocod in #884

        Inbound messages were processed serially on the transport read loop, so one slow or peer-awaiting handler (sampling, elicitation, roots) blocked every later message on the connection, including the responses and notifications/cancelled those handlers depend on. After the handshake, handlers may now run concurrently; processing stays serial during initialization.

        • ProtocolOptions / ClientOptions / ServerOptions gain a handlerCoroutineContext parameter (defaults to Dispatchers.Default). Concurrency is bounded internally; there is no opt-out flag by design.
        • RequestHandlerExtra is now a CoroutineContext.Element and is no longer user-constructible. It exposes requestId, method, sendRequest, and sendNotification, and can be read inside a handler via the new currentRequestHandlerExtra().
        - val extra = RequestHandlerExtra()
        + val extra = currentRequestHandlerExtra()
        

        Duplicate feature names are rejected at registration by @​devcrocod in #883

        addTool/addPrompt/addResource/addResourceTemplate silently replaced an existing entry and emitted a spurious list_changed notification. They now throw IllegalArgumentException and leave the existing registration intact. The batch variants (addTools/addPrompts/addResources) are all-or-nothing. To replace a feature, remove it first.

        - server.addTool(name = "search", ...)
        // silently overwrote
        + server.removeTool("search")
        + server.addTool(name = "search", ...)
        

        eventStore removed from mcpStatelessStreamableHttp by @​devcrocod in #909

        The parameter was never read or written: a stateless endpoint answers GET with 405, so no stream exists to store or replay events on. The old overload is retained at DeprecationLevel.ERROR with a ReplaceWith migration hint. Use mcpStreamableHttp when you need resumability.

          application.mcpStatelessStreamableHttp(
              path = "/mcp",
        -     eventStore = myEventStore,
          ) { server }
        

        Features

        • Optional SSE heartbeats for Streamable HTTP servers via mcpStreamableHttp(sseHeartbeatConfig = { ... }), keeping long-lived streams alive against clients that disconnect on idle. Heartbeats stay off by default, by @​UnscientificJsZhai in #761
        • Accepted form-mode elicitation content is now validated against requestedSchema; a mismatch fails createElicitation with McpException (INVALID_PARAMS) instead of reaching server code unchecked, by @​rea9r in #896
        • The Streamable HTTP client now sends the standard Mcp-Method and Mcp-Name POST headers, by @​AndreKalberer in #894

        Fixed

        • Stateless Streamable HTTP now closes its per-request session, fixing unbounded growth of the session registry and notification subscriptions, by @​KlyneChrysler in #872
        • Streamable HTTP responses keep their status and body when the client accepts only text/event-stream, by @​devcrocod in #911

        ... (truncated)

        Commits
        • 76b8e7d release: 0.15.0 (#918)
        • dab340f fix(server): drop inert eventStore from stateless Streamable HTTP (#909)
        • 24c3cd3 fix(server): match Accept header as media ranges (#912)
        • 7fa438c feat(client): send MCP standard POST headers (#894)
        • 94745e6 chore(deps): bump the kotest group across 1 directory with 3 updates (#915)
        • 2b494b7 chore(deps): bump the other-dependencies group across 5 directories with 3 up...
        • 82fe56e fix(server): preserve Streamable HTTP response status and body (#911)
        • a06d699 feat: SSE heartbeat (#761)
        • cab669b fix: classify malformed request params as invalid params (#886)
        • b6cb547 fix(server): preserve id on duplicate initialize errors (#868)
        • Additional commits viewable in compare view

        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/weather-stdio-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/weather-stdio-server/gradle/libs.versions.toml b/samples/weather-stdio-server/gradle/libs.versions.toml index 69b8d1c..ea351f1 100644 --- a/samples/weather-stdio-server/gradle/libs.versions.toml +++ b/samples/weather-stdio-server/gradle/libs.versions.toml @@ -3,7 +3,7 @@ coroutines = "1.11.0" kotlin = "2.4.0" ktor = "3.5.1" logging = "8.0.4" -mcp-kotlin = "0.14.0" +mcp-kotlin = "0.15.0" shadow = "9.6.1" slf4j = "2.0.18" From fe9843ded917460233e47fe1bf5a0a4dd9256440 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 11:56:24 +0200 Subject: [PATCH 399/449] chore(deps): bump the all-actions group across 1 directory with 3 updates (#932) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the all-actions group with 3 updates in the / directory: [gradle/actions](https://github.com/gradle/actions), [actions/stale](https://github.com/actions/stale) and [github/codeql-action](https://github.com/github/codeql-action). Updates `gradle/actions` from 6 to 6.2.0
        Release notes

        Sourced from gradle/actions's releases.

        v6.2.0

        Highlights

        This release brings significant behaviour improvements to Enhanced caching, improvements to the generated Job Summary, and a number of correctness and security fixes.

        1. Improved cache-cleanup mechanism. Cleanup of stale files from the Gradle User Home is now faster, and no longer depends on Gradle or a JVM. It works by inspecting the local file state directly, removing the Gradle invocation from the post-build step.
        2. More granular, more stable caching. The local build cache is stored as a separate cache entry, so it can be restored and invalidated independently of the main Gradle User Home entry. Transient Gradle housekeeping files are excluded from the cache, reducing its size and improving stability.
        3. Hide obsolete Job summaries in PR commments: When a new Job summary comment is added to a PR, previous outdated Job summaries are now hidden.
        4. Improved caching report in the job summary. The cache report now uses a single, consistent layout across all cache states and providers. Provider information is integrated directly into the report, and per-entry details are available in an expandable section. (#985)
        5. Correctness and security fixes. A unique cache key is now used per run attempt, so re-runs no longer collide; the job summary shows the cache key string rather than an internal id; and bundled dependencies have been updated, including a ReDoS fix and a fast-xml CVE fix.

        What's Changed

        New Contributors

        Full Changelog: https://github.com/gradle/actions/compare/v6.1.1...v6.2.0

        v6.1.1

        This release updates various dependency versions, resolving several reported security vulnerabilities. No functional changes are included

        What's Changed

        Full Changelog: https://github.com/gradle/actions/compare/v6.1.0...v6.1.1

        v6.1.0

        New: Basic Cache Provider

        A new MIT-licensed Basic Caching provider is now available as an alternative to the proprietary Enhanced Caching provided by gradle-actions-caching. Choose Basic Caching by setting cache-provider: basic on setup-gradle or dependency-submission actions.

        • Built on @actions/cache -- fully open source

        ... (truncated)

        Commits
        • b6eebf3 [bot] Update dist directory
        • 9901393 Remove unnecessary dependency overrides (#981)
        • 20ce680 Update RELEASING.md
        • 5e2ebd0 [bot] Update dist directory
        • 9e2c1cc Resolve npm security vulnerabilities via dependency overrides (#980)
        • 657f690 [bot] Update dist directory
        • 9d6bacd Update @​actions/cache and @​actions/artifact, stop ignoring them in Dependabot...
        • 79f6b19 [bot] Update dist directory
        • 6781648 Bump the npm-dependencies group in /sources with 5 updates (#977)
        • a3b40d2 [bot] Update dist directory
        • Additional commits viewable in compare view

        Updates `actions/stale` from 10 to 11
        Release notes

        Sourced from actions/stale's releases.

        v11.0.0

        What's Changed

        Enhancement

        Dependency Update

        Full Changelog: https://github.com/actions/stale/compare/v10...v11.0.0

        v10.4.0

        What's Changed

        Bug Fix

        Dependency Updates

        New Contributors

        Full Changelog: https://github.com/actions/stale/compare/v10.3.0...v10.4.0

        v10.3.0

        What's Changed

        Bug Fix

        Dependency Updates

        New Contributors

        Full Changelog: https://github.com/actions/stale/compare/v10...v10.3.0

        v10.2.0

        What's Changed

        Bug Fix

        Dependency Updates

        New Contributors

        Full Changelog: https://github.com/actions/stale/compare/v10...v10.2.0

        ... (truncated)

        Changelog

        Sourced from actions/stale's changelog.

        Changelog

        [10.1.0]

        What's Changed

        [10.0.0]

        What's Changed

        Breaking Changes

        Enhancement

        Dependency Upgrades

        Documentation changes

        [9.1.0]

        What's Changed

        [9.0.0]

        Breaking Changes

        1. Action is now stateful: If the action ends because of operations-per-run then the next run will start from the first unprocessed issue skipping the issues processed during the previous run(s). The state is reset when all the issues are processed. This should be considered for scheduling workflow runs.
        2. Version 9 of this action updated the runtime to Node.js 20. All scripts are now run with Node.js 20 instead of Node.js 16 and are affected by any breaking changes between Node.js 16 and 20.

        ... (truncated)

        Commits
        • 4391f3d Fix 24 high severity vulnerabilities by overriding brace-expansion to 5.0.8 (...
        • eaf9131 refactor: update imports to use ES module syntax and improve test structure (...
        • See full diff in compare view

        Updates `github/codeql-action` from 4 to 4.37.4
        Release notes

        Sourced from github/codeql-action's releases.

        v4.37.4

        • This version of the CodeQL Action adds support for the tools input for the codeql-action/init step to be specified using a github-codeql-tools repository property. This feature will gradually be rolled out following the release of this version. Once rolled out, this allows for the CodeQL CLI version that is used in GitHub-managed workflows, such as Default Setup, to be set to a custom value. For example, customers who run into issues with rate limits when a new CodeQL CLI version is released can set the value to toolcache to always use the CodeQL CLI version that is available in the runner toolcache. For Advanced Setup workflows, the value provided for tools in the workflow definition always takes precedence unless the value of the repository property starts with !. #4037
        • Update default CodeQL bundle version to 2.26.2. #4051

        v4.37.3

        No user facing changes.

        v4.37.2

        • The new address format for the config-file input that was introduced in CodeQL Action 4.37.0 is now enabled by default. In addition to the format described there, the remote= prefix can now be used to explicitly indicate that the input refers to a remote file. All previous input formats continue to be accepted as well. #4023
        • The CodeQL Action can now make use of configured private registries in Default Setup to retrieve CodeQL configuration files from remote repositories that require authentication. This will allow customers to store their CodeQL configuration in a single repository that can then be referenced by Default Setup workflows in other repositories. We expect to roll this and other, related changes out to everyone in July. #4007

        v4.37.1

        • Upcoming breaking change: Add a deprecation warning for customers using CodeQL version 2.20.6 and earlier. These versions of CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise Server 3.16, and will be unsupported by the next minor release of the CodeQL Action. #3956
        • Update default CodeQL bundle version to 2.26.1. #4019

        v4.37.0

        • Update default CodeQL bundle version to 2.26.0. #3995
        • In addition to the existing input format, the config-file input for the codeql-action/init step will soon support a new [owner/]repo[@ref][:path] format. All components except the repository name are optional. If omitted, owner defaults to the same owner as the repository the analysis is running for, ref to main, and path to .github/codeql-action.yaml. Support for this format ships in this version of the CodeQL Action, but will only be enabled over the coming weeks. #3973

        v4.36.3

        No user facing changes.

        v4.36.2

        • Cache CodeQL CLI version information across Actions steps. #3943
        • Reduce requests while waiting for analysis processing by using exponential backoff when polling SARIF processing status. #3937
        • Update default CodeQL bundle version to 2.25.6. #3948

        v4.36.1

        No user facing changes.

        v4.36.0

        • Breaking change: Bump the minimum required CodeQL bundle version to 2.19.4. #3894
        • Add support for SHA-256 Git object IDs. #3893
        • Update default CodeQL bundle version to 2.25.5. #3926

        v4.35.5

        • We have improved how the JavaScript bundles for the CodeQL Action are generated to avoid duplication across bundles and reduce the size of the repository by around 70%. This should have no effect on the runtime behaviour of the CodeQL Action. #3899
        • For performance and accuracy reasons, improved incremental analysis will now only be enabled on a pull request when diff-informed analysis is also enabled for that run. If diff-informed analysis is unavailable (for example, because the PR diff ranges could not be computed), the action will fall back to a full analysis. #3791
        • If multiple inputs are provided for the GitHub-internal analysis-kinds input, only code-scanning will be enabled. The analysis-kinds input is experimental, for GitHub-internal use only, and may change without notice at any time. #3892
        • Added an experimental change which, when running a Code Scanning analysis for a PR with improved incremental analysis enabled, prefers CodeQL CLI versions that have a cached overlay-base database for the configured languages. This speeds up analysis for a repository when there is not yet a cached overlay-base database for the latest CLI version. We expect to roll this change out to everyone in May. #3880

        v4.35.4

        • Update default CodeQL bundle version to 2.25.4. #3881

        v4.35.3

        • Upcoming breaking change: Add a deprecation warning for customers using CodeQL version 2.19.3 and earlier. These versions of CodeQL were discontinued on 9 April 2026 alongside GitHub Enterprise Server 3.15, and will be unsupported by the next minor release of the CodeQL Action. #3837
        • Configurations for private registries that use Cloudsmith or GCP OIDC are now accepted. #3850
        • Best-effort connection tests for private registries now use GET requests instead of HEAD for better compatibility with various registry implementations. For NuGet feeds, the test is now always performed against the service index. #3853
        • Fixed a bug where two diagnostics produced within the same millisecond could overwrite each other on disk, causing one of them to be lost. #3852
        • Update default CodeQL bundle version to 2.25.3. #3865

        ... (truncated)

        Changelog

        Sourced from github/codeql-action's changelog.

        4.37.4 - 29 Jul 2026

        • This version of the CodeQL Action adds support for the tools input for the codeql-action/init step to be specified using a github-codeql-tools repository property. This feature will gradually be rolled out following the release of this version. Once rolled out, this allows for the CodeQL CLI version that is used in GitHub-managed workflows, such as Default Setup, to be set to a custom value. For example, customers who run into issues with rate limits when a new CodeQL CLI version is released can set the value to toolcache to always use the CodeQL CLI version that is available in the runner toolcache. For Advanced Setup workflows, the value provided for tools in the workflow definition always takes precedence unless the value of the repository property starts with !. #4037
        • Update default CodeQL bundle version to 2.26.2. #4051

        4.37.3 - 22 Jul 2026

        No user facing changes.

        4.37.2 - 21 Jul 2026

        • The new address format for the config-file input that was introduced in CodeQL Action 4.37.0 is now enabled by default. In addition to the format described there, the remote= prefix can now be used to explicitly indicate that the input refers to a remote file. All previous input formats continue to be accepted as well. #4023
        • The CodeQL Action can now make use of configured private registries in Default Setup to retrieve CodeQL configuration files from remote repositories that require authentication. This will allow customers to store their CodeQL configuration in a single repository that can then be referenced by Default Setup workflows in other repositories. We expect to roll this and other, related changes out to everyone in July. #4007

        4.37.1 - 16 Jul 2026

        • Upcoming breaking change: Add a deprecation warning for customers using CodeQL version 2.20.6 and earlier. These versions of CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise Server 3.16, and will be unsupported by the next minor release of the CodeQL Action. #3956
        • Update default CodeQL bundle version to 2.26.1. #4019

        4.37.0 - 08 Jul 2026

        • Update default CodeQL bundle version to 2.26.0. #3995
        • In addition to the existing input format, the config-file input for the codeql-action/init step will soon support a new [owner/]repo[@ref][:path] format. All components except the repository name are optional. If omitted, owner defaults to the same owner as the repository the analysis is running for, ref to main, and path to .github/codeql-action.yaml. Support for this format ships in this version of the CodeQL Action, but will only be enabled over the coming weeks. #3973

        4.36.3 - 01 Jul 2026

        No user facing changes.

        Commits
        • 18420e3 Merge pull request #4043 from github/mbg/ts/changelog
        • 7e8d897 Merge pull request #4046 from github/mbg/repo-prop/code-quality
        • 2d4c474 Log !analysisKindSupported case
        • 98c05a1 Fix argument validation in rollback-changelog.ts
        • 8289a49 Ignore repository property for unsupported analysis kinds
        • 2a8731c Move config-file computation after determining the analysisKinds
        • 3434fbb Merge pull request #4044 from github/mbg/ff/promote-toolcache
        • 3013ac0 Promote AllowToolcacheInput feature
        • 74b15aa Install JS deps if needed in post-release-mergeback workflow
        • f00f809 Fix checking keys rather than values
        • Additional commits viewable in compare view

        Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
        Dependabot commands and options
        You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/apidocs.yaml | 2 +- .github/workflows/automations.yml | 4 ++-- .github/workflows/build.yml | 6 +++--- .github/workflows/codeql.yml | 6 +++--- .github/workflows/conformance.yml | 8 ++++---- .github/workflows/gradle-publish.yml | 2 +- .github/workflows/samples.yml | 2 +- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/apidocs.yaml b/.github/workflows/apidocs.yaml index 510af19..8887169 100644 --- a/.github/workflows/apidocs.yaml +++ b/.github/workflows/apidocs.yaml @@ -34,7 +34,7 @@ jobs: cache: gradle - name: Setup Gradle - uses: gradle/actions/setup-gradle@v6 + uses: gradle/actions/setup-gradle@v6.2.0 - name: Generate Dokka Site run: |- diff --git a/.github/workflows/automations.yml b/.github/workflows/automations.yml index 4f88d37..60a9d3b 100644 --- a/.github/workflows/automations.yml +++ b/.github/workflows/automations.yml @@ -27,7 +27,7 @@ jobs: name: Mark/unmark stale PRs and issues runs-on: ubuntu-latest steps: - - uses: actions/stale@v10 + - uses: actions/stale@v11 with: debug-only: ${{ env.DRY_RUN }} exempt-draft-pr: true @@ -66,7 +66,7 @@ jobs: name: Close stale PRs and issues runs-on: ubuntu-latest steps: - - uses: actions/stale@v10 + - uses: actions/stale@v11 with: debug-only: ${{ env.DRY_RUN }} only-labels: 'stale' diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 147163c..0707d44 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -63,7 +63,7 @@ jobs: run: npm ci - name: Setup Gradle - uses: gradle/actions/setup-gradle@v6 + uses: gradle/actions/setup-gradle@v6.2.0 with: validate-wrappers: true add-job-summary: 'always' @@ -135,7 +135,7 @@ jobs: distribution: ${{ env.JAVA_DISTRIBUTION }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v6 + uses: gradle/actions/setup-gradle@v6.2.0 with: validate-wrappers: true add-job-summary: 'always' @@ -177,7 +177,7 @@ jobs: distribution: ${{ env.JAVA_DISTRIBUTION }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v6 + uses: gradle/actions/setup-gradle@v6.2.0 with: add-job-summary: 'always' cache-read-only: true diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index f25ec64..368f044 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -34,13 +34,13 @@ jobs: java-version: ${{ env.JAVA_VERSION }} - name: Initialize CodeQL - uses: github/codeql-action/init@v4 + uses: github/codeql-action/init@v4.37.4 with: languages: java-kotlin build-mode: manual - name: Setup Gradle - uses: gradle/actions/setup-gradle@v6 + uses: gradle/actions/setup-gradle@v6.2.0 with: validate-wrappers: true cache-read-only: true @@ -52,6 +52,6 @@ jobs: --no-daemon --rerun-tasks --stacktrace - name: Analyze - uses: github/codeql-action/analyze@v4 + uses: github/codeql-action/analyze@v4.37.4 with: category: '/language:java-kotlin' diff --git a/.github/workflows/conformance.yml b/.github/workflows/conformance.yml index 2511286..0605b10 100644 --- a/.github/workflows/conformance.yml +++ b/.github/workflows/conformance.yml @@ -34,7 +34,7 @@ jobs: distribution: ${{ env.JAVA_DISTRIBUTION }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v6 + uses: gradle/actions/setup-gradle@v6.2.0 with: cache-read-only: ${{ github.ref != 'refs/heads/main' }} @@ -85,7 +85,7 @@ jobs: distribution: ${{ env.JAVA_DISTRIBUTION }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v6 + uses: gradle/actions/setup-gradle@v6.2.0 with: cache-read-only: ${{ github.ref != 'refs/heads/main' }} @@ -116,7 +116,7 @@ jobs: distribution: ${{ env.JAVA_DISTRIBUTION }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v6 + uses: gradle/actions/setup-gradle@v6.2.0 with: cache-read-only: ${{ github.ref != 'refs/heads/main' }} @@ -155,7 +155,7 @@ jobs: distribution: ${{ env.JAVA_DISTRIBUTION }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v6 + uses: gradle/actions/setup-gradle@v6.2.0 with: cache-read-only: ${{ github.ref != 'refs/heads/main' }} diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml index 058e041..c9dc966 100644 --- a/.github/workflows/gradle-publish.yml +++ b/.github/workflows/gradle-publish.yml @@ -30,7 +30,7 @@ jobs: node-version: '22' # increase only after https://github.com/nodejs/node/issues/56645 will be fixed - name: Setup Gradle - uses: gradle/actions/setup-gradle@v6 + uses: gradle/actions/setup-gradle@v6.2.0 - name: Check run: ./gradlew check --no-configuration-cache diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index 89de699..812fbb9 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -45,7 +45,7 @@ jobs: distribution: ${{ env.JAVA_DISTRIBUTION }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v6 + uses: gradle/actions/setup-gradle@v6.2.0 with: validate-wrappers: true add-job-summary: 'always' From 4a8ffdfea998e03a0e4b27236035bb6dc030c0fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 14:07:06 +0200 Subject: [PATCH 400/449] chore(deps): bump the kotlin group across 1 directory with 2 updates (#901) Bumps the kotlin group with 2 updates in the / directory: [org.jetbrains.kotlin:kotlin-gradle-plugin](https://github.com/JetBrains/kotlin) and [org.jetbrains.kotlin:kotlin-serialization](https://github.com/JetBrains/kotlin). Updates `org.jetbrains.kotlin:kotlin-gradle-plugin` from 2.4.0 to 2.4.10
        Release notes

        Sourced from org.jetbrains.kotlin:kotlin-gradle-plugin's releases.

        Kotlin 2.4.10

        Changelog

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        Compiler

        • KT-86939 JVM: IllegalStateException "No value for annotation parameter" when using const val in nested Java annotation array argument
        • KT-83766 K2: Wrong sourcePsi is set for SymbolPsiLiteral in SLC for annotation arguments referencing a const val
        • KT-86728 Reified type inference: expected type not propagated into inline call inside lambda with elvis operator

        Compose Compiler

        • b/522127447 Compose Compiler 2.4: classes previously inferred stable now reported runtime/Uncertain

        Klibs

        • KT-86501 Native: IrTypeAliasSymbolImpl is already bound. Signature: kotlinx.datetime/Instant|null[0] on iosSimulatorArm64

        Tools. CLI

        • KT-86930 Introduce kotlinr in the Kotlin distribution

        Tools. Gradle. BCV

        • KT-87223 Gradle, BCV: open version range in kotlinAbiValidationCompatClasspath causes kotlin-build-tools-impl to resolve to 2.4.20-Beta1 instead of 2.4.0

        Tools. Gradle. JS

        • KT-87304 jsBrowserTest fails with "exited with errors (exit code: 1)"
        • KT-86057 kotlinUpgradeYarnLock skips lock file regeneration when kotlinNpmInstall is up-to-date, causing kotlinStoreYarnLock to fail

        Tools. Gradle. Multiplatform

        • KT-87084 False positive warning for JS and Wasm compilations when CRI is enabled

        Tools. Scripts

        • KT-87076 @file:CompilerOptions("-jvm-target", ...) ignored in .main.kts scripts in Kotlin 2.4.0, falling back to JVM target 1.8
        • KT-86352 K2 scripting: FirResolvedTypeRef exception when resolving extension functions from imported scripts

        Kotlin 2.4.10-RC2

        Changelog

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        ... (truncated)

        Changelog

        Sourced from org.jetbrains.kotlin:kotlin-gradle-plugin's changelog.

        2.4.10

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        Compiler

        • KT-86939 JVM: IllegalStateException "No value for annotation parameter" when using const val in nested Java annotation array argument
        • KT-83766 K2: Wrong sourcePsi is set for SymbolPsiLiteral in SLC for annotation arguments referencing a const val
        • KT-86728 Reified type inference: expected type not propagated into inline call inside lambda with elvis operator

        Compose Compiler

        • b/522127447 Compose Compiler 2.4: classes previously inferred stable now reported runtime/Uncertain

        Klibs

        • KT-86501 Native: IrTypeAliasSymbolImpl is already bound. Signature: kotlinx.datetime/Instant|null[0] on iosSimulatorArm64

        Tools. CLI

        • KT-86930 Introduce kotlinr in the Kotlin distribution

        Tools. Gradle. BCV

        • KT-87223 Gradle, BCV: open version range in kotlinAbiValidationCompatClasspath causes kotlin-build-tools-impl to resolve to 2.4.20-Beta1 instead of 2.4.0

        Tools. Gradle. JS

        • KT-87304 jsBrowserTest fails with "exited with errors (exit code: 1)"
        • KT-86057 kotlinUpgradeYarnLock skips lock file regeneration when kotlinNpmInstall is up-to-date, causing kotlinStoreYarnLock to fail

        Tools. Gradle. Multiplatform

        • KT-87084 False positive warning for JS and Wasm compilations when CRI is enabled

        Tools. Scripts

        • KT-87076 @file:CompilerOptions("-jvm-target", ...) ignored in .main.kts scripts in Kotlin 2.4.0, falling back to JVM target 1.8
        • KT-86352 K2 scripting: FirResolvedTypeRef exception when resolving extension functions from imported scripts
        Commits
        • 5687445 Add Changelog for 2.4.10-RC2
        • ba30392 [Wasm] Support regenerate unchanged modules flag on KGP
        • 6307e73 [Wasm] Implement Xwasm-IC-generate-unchanged-modules flag
        • 39d7aee Add ChangeLog for 2.4.10-RC2
        • a2956be [Gradle] Update karma.conf.js to use 'require(...)' instead plain strings
        • 9b1361c [Gradle] KT-87223: Updated regression test
        • a4c153a [Gradle] KT-87223: Fix toochain to 2.4.0 for abiValidation compat config
        • bc04b90 Add ChangeLog for 2.4.10-RC
        • bbcb94b [CRI] explicitly disable CRI generation for non-JVM targets to avoid false-po...
        • 6c2c458 Scripting: fix jvmTarget processing from script annotations
        • Additional commits viewable in compare view

        Updates `org.jetbrains.kotlin:kotlin-serialization` from 2.4.0 to 2.4.10
        Release notes

        Sourced from org.jetbrains.kotlin:kotlin-serialization's releases.

        Kotlin 2.4.10

        Changelog

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        Compiler

        • KT-86939 JVM: IllegalStateException "No value for annotation parameter" when using const val in nested Java annotation array argument
        • KT-83766 K2: Wrong sourcePsi is set for SymbolPsiLiteral in SLC for annotation arguments referencing a const val
        • KT-86728 Reified type inference: expected type not propagated into inline call inside lambda with elvis operator

        Compose Compiler

        • b/522127447 Compose Compiler 2.4: classes previously inferred stable now reported runtime/Uncertain

        Klibs

        • KT-86501 Native: IrTypeAliasSymbolImpl is already bound. Signature: kotlinx.datetime/Instant|null[0] on iosSimulatorArm64

        Tools. CLI

        • KT-86930 Introduce kotlinr in the Kotlin distribution

        Tools. Gradle. BCV

        • KT-87223 Gradle, BCV: open version range in kotlinAbiValidationCompatClasspath causes kotlin-build-tools-impl to resolve to 2.4.20-Beta1 instead of 2.4.0

        Tools. Gradle. JS

        • KT-87304 jsBrowserTest fails with "exited with errors (exit code: 1)"
        • KT-86057 kotlinUpgradeYarnLock skips lock file regeneration when kotlinNpmInstall is up-to-date, causing kotlinStoreYarnLock to fail

        Tools. Gradle. Multiplatform

        • KT-87084 False positive warning for JS and Wasm compilations when CRI is enabled

        Tools. Scripts

        • KT-87076 @file:CompilerOptions("-jvm-target", ...) ignored in .main.kts scripts in Kotlin 2.4.0, falling back to JVM target 1.8
        • KT-86352 K2 scripting: FirResolvedTypeRef exception when resolving extension functions from imported scripts

        Kotlin 2.4.10-RC2

        Changelog

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        ... (truncated)

        Changelog

        Sourced from org.jetbrains.kotlin:kotlin-serialization's changelog.

        2.4.10

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        Compiler

        • KT-86939 JVM: IllegalStateException "No value for annotation parameter" when using const val in nested Java annotation array argument
        • KT-83766 K2: Wrong sourcePsi is set for SymbolPsiLiteral in SLC for annotation arguments referencing a const val
        • KT-86728 Reified type inference: expected type not propagated into inline call inside lambda with elvis operator

        Compose Compiler

        • b/522127447 Compose Compiler 2.4: classes previously inferred stable now reported runtime/Uncertain

        Klibs

        • KT-86501 Native: IrTypeAliasSymbolImpl is already bound. Signature: kotlinx.datetime/Instant|null[0] on iosSimulatorArm64

        Tools. CLI

        • KT-86930 Introduce kotlinr in the Kotlin distribution

        Tools. Gradle. BCV

        • KT-87223 Gradle, BCV: open version range in kotlinAbiValidationCompatClasspath causes kotlin-build-tools-impl to resolve to 2.4.20-Beta1 instead of 2.4.0

        Tools. Gradle. JS

        • KT-87304 jsBrowserTest fails with "exited with errors (exit code: 1)"
        • KT-86057 kotlinUpgradeYarnLock skips lock file regeneration when kotlinNpmInstall is up-to-date, causing kotlinStoreYarnLock to fail

        Tools. Gradle. Multiplatform

        • KT-87084 False positive warning for JS and Wasm compilations when CRI is enabled

        Tools. Scripts

        • KT-87076 @file:CompilerOptions("-jvm-target", ...) ignored in .main.kts scripts in Kotlin 2.4.0, falling back to JVM target 1.8
        • KT-86352 K2 scripting: FirResolvedTypeRef exception when resolving extension functions from imported scripts
        Commits
        • 5687445 Add Changelog for 2.4.10-RC2
        • ba30392 [Wasm] Support regenerate unchanged modules flag on KGP
        • 6307e73 [Wasm] Implement Xwasm-IC-generate-unchanged-modules flag
        • 39d7aee Add ChangeLog for 2.4.10-RC2
        • a2956be [Gradle] Update karma.conf.js to use 'require(...)' instead plain strings
        • 9b1361c [Gradle] KT-87223: Updated regression test
        • a4c153a [Gradle] KT-87223: Fix toochain to 2.4.0 for abiValidation compat config
        • bc04b90 Add ChangeLog for 2.4.10-RC
        • bbcb94b [CRI] explicitly disable CRI generation for non-JVM targets to avoid false-po...
        • 6c2c458 Scripting: fix jvmTarget processing from script annotations
        • Additional commits viewable in compare view

        Updates `org.jetbrains.kotlin:kotlin-serialization` from 2.4.0 to 2.4.10
        Release notes

        Sourced from org.jetbrains.kotlin:kotlin-serialization's releases.

        Kotlin 2.4.10

        Changelog

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        Compiler

        • KT-86939 JVM: IllegalStateException "No value for annotation parameter" when using const val in nested Java annotation array argument
        • KT-83766 K2: Wrong sourcePsi is set for SymbolPsiLiteral in SLC for annotation arguments referencing a const val
        • KT-86728 Reified type inference: expected type not propagated into inline call inside lambda with elvis operator

        Compose Compiler

        • b/522127447 Compose Compiler 2.4: classes previously inferred stable now reported runtime/Uncertain

        Klibs

        • KT-86501 Native: IrTypeAliasSymbolImpl is already bound. Signature: kotlinx.datetime/Instant|null[0] on iosSimulatorArm64

        Tools. CLI

        • KT-86930 Introduce kotlinr in the Kotlin distribution

        Tools. Gradle. BCV

        • KT-87223 Gradle, BCV: open version range in kotlinAbiValidationCompatClasspath causes kotlin-build-tools-impl to resolve to 2.4.20-Beta1 instead of 2.4.0

        Tools. Gradle. JS

        • KT-87304 jsBrowserTest fails with "exited with errors (exit code: 1)"
        • KT-86057 kotlinUpgradeYarnLock skips lock file regeneration when kotlinNpmInstall is up-to-date, causing kotlinStoreYarnLock to fail

        Tools. Gradle. Multiplatform

        • KT-87084 False positive warning for JS and Wasm compilations when CRI is enabled

        Tools. Scripts

        • KT-87076 @file:CompilerOptions("-jvm-target", ...) ignored in .main.kts scripts in Kotlin 2.4.0, falling back to JVM target 1.8
        • KT-86352 K2 scripting: FirResolvedTypeRef exception when resolving extension functions from imported scripts

        Kotlin 2.4.10-RC2

        Changelog

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        ... (truncated)

        Changelog

        Sourced from org.jetbrains.kotlin:kotlin-serialization's changelog.

        2.4.10

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        Compiler

        • KT-86939 JVM: IllegalStateException "No value for annotation parameter" when using const val in nested Java annotation array argument
        • KT-83766 K2: Wrong sourcePsi is set for SymbolPsiLiteral in SLC for annotation arguments referencing a const val
        • KT-86728 Reified type inference: expected type not propagated into inline call inside lambda with elvis operator

        Compose Compiler

        • b/522127447 Compose Compiler 2.4: classes previously inferred stable now reported runtime/Uncertain

        Klibs

        • KT-86501 Native: IrTypeAliasSymbolImpl is already bound. Signature: kotlinx.datetime/Instant|null[0] on iosSimulatorArm64

        Tools. CLI

        • KT-86930 Introduce kotlinr in the Kotlin distribution

        Tools. Gradle. BCV

        • KT-87223 Gradle, BCV: open version range in kotlinAbiValidationCompatClasspath causes kotlin-build-tools-impl to resolve to 2.4.20-Beta1 instead of 2.4.0

        Tools. Gradle. JS

        • KT-87304 jsBrowserTest fails with "exited with errors (exit code: 1)"
        • KT-86057 kotlinUpgradeYarnLock skips lock file regeneration when kotlinNpmInstall is up-to-date, causing kotlinStoreYarnLock to fail

        Tools. Gradle. Multiplatform

        • KT-87084 False positive warning for JS and Wasm compilations when CRI is enabled

        Tools. Scripts

        • KT-87076 @file:CompilerOptions("-jvm-target", ...) ignored in .main.kts scripts in Kotlin 2.4.0, falling back to JVM target 1.8
        • KT-86352 K2 scripting: FirResolvedTypeRef exception when resolving extension functions from imported scripts
        Commits
        • 5687445 Add Changelog for 2.4.10-RC2
        • ba30392 [Wasm] Support regenerate unchanged modules flag on KGP
        • 6307e73 [Wasm] Implement Xwasm-IC-generate-unchanged-modules flag
        • 39d7aee Add ChangeLog for 2.4.10-RC2
        • a2956be [Gradle] Update karma.conf.js to use 'require(...)' instead plain strings
        • 9b1361c [Gradle] KT-87223: Updated regression test
        • a4c153a [Gradle] KT-87223: Fix toochain to 2.4.0 for abiValidation compat config
        • bc04b90 Add ChangeLog for 2.4.10-RC
        • bbcb94b [CRI] explicitly disable CRI generation for non-JVM targets to avoid false-po...
        • 6c2c458 Scripting: fix jvmTarget processing from script annotations
        • Additional commits viewable in compare view

        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6d7bbbb..051e361 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ atomicfu = "0.33.0" binaryCompatibilityValidatorPlugin = "0.18.1" detekt = "2.0.0-alpha.5" dokka = "2.2.0" -kotlin = "2.4.0" +kotlin = "2.4.10" kover = "0.9.9" ktlint = "14.2.0" knit = "0.5.1" From 9249c6929596dc129e0851e2952abfafb5e528d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 14:27:10 +0200 Subject: [PATCH 401/449] chore(deps): bump ip-address from 10.2.0 to 10.4.0 in /integration-test/src/jvmTest/typescript (#934) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [ip-address](https://github.com/beaugunderson/ip-address) from 10.2.0 to 10.4.0.
        Release notes

        Sourced from ip-address's releases.

        v10.4.0

        What's Changed

        Full Changelog: https://github.com/beaugunderson/ip-address/compare/v10.3.1...v10.4.0

        v10.3.1

        Full Changelog: https://github.com/beaugunderson/ip-address/compare/v10.3.0...v10.3.1

        v10.3.0

        Full Changelog: https://github.com/beaugunderson/ip-address/compare/v10.2.2...v10.3.0

        v10.2.2

        Full Changelog: https://github.com/beaugunderson/ip-address/compare/v10.2.1...v10.2.2

        v10.2.1

        Full Changelog: https://github.com/beaugunderson/ip-address/compare/v10.2.0...v10.2.1

        Commits
        • fbb8db2 10.4.0
        • 45a2b11 Validate the byte arrays Address6 is given (#217)
        • bac8810 Keep the package loadable on node 12, and enforce it (#216)
        • 9b3d848 Add a security policy and a README section on security posture
        • e84a7b3 Order the README API reference Address4, Address6, AddressError
        • 015160b Collapse each class in the README API reference
        • 34061a8 Pin checkout and setup-node to commits in the release job
        • c5fae5d Pin action-gh-release to a commit and move it to 3.0.2
        • e0ef048 Replace CircleCI with GitHub Actions
        • 5e3ceb7 Add GitHub Actions CI across Node 20, 22, 24 and 25 (#213)
        • Additional commits viewable in compare view
        Maintainer changes

        This version was pushed to npm by GitHub Actions, a new releaser for ip-address since your current version.

        Install script changes

        This version adds prepare script that runs during installation. Review the package contents before updating.


        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- integration-test/src/jvmTest/typescript/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index 0c70662..7cbfffd 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -1589,9 +1589,9 @@ "license": "ISC" }, "node_modules/ip-address": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", - "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.4.0.tgz", + "integrity": "sha512-oSK96Grm3aP6OrS263xVxbNDGVL7rzBtYdpGqlDG8iQdoenDoTs/nkki+DflYbAEE8Xl6o5YxhxlrKvI3nqKXQ==", "license": "MIT", "engines": { "node": ">= 12" From 7284373aa4124f1170ee3b17a20f0c61b907ff44 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 18:21:38 +0200 Subject: [PATCH 402/449] chore(deps): bump @hono/node-server and @modelcontextprotocol/sdk in /integration-test/src/jvmTest/typescript (#933) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [@hono/node-server](https://github.com/honojs/node-server) to 2.1.0 and updates ancestor dependency [@modelcontextprotocol/sdk](https://github.com/modelcontextprotocol/typescript-sdk). These dependencies need to be updated together. Updates `@hono/node-server` from 1.19.13 to 2.1.0
        Release notes

        Sourced from @​hono/node-server's releases.

        v2.1.0

        What's Changed

        New Contributors

        Full Changelog: https://github.com/honojs/node-server/compare/v2.0.12...v2.1.0

        v2.0.12

        What's Changed

        Full Changelog: https://github.com/honojs/node-server/compare/v2.0.11...v2.0.12

        v2.0.11

        What's Changed

        Full Changelog: https://github.com/honojs/node-server/compare/v2.0.10...v2.0.11

        v2.0.10

        Security fixes

        This release includes a fix for the following security issue:

        Unauthenticated memory-leak DoS via aborted WebSocket handshake

        Affects: upgradeWebSocket. A WebSocket upgrade request with a missing or malformed Sec-WebSocket-Key header leaked the request's IncomingMessage and left a promise pending, even though no connection was established. Since the route is reachable pre-handshake without authentication, an attacker could flood it to gradually exhaust memory. GHSA-9mqv-5hh9-4cgg


        Users of upgradeWebSocket are encouraged to upgrade to this version.

        v2.0.9

        What's Changed

        New Contributors

        ... (truncated)

        Commits
        Maintainer changes

        This version was pushed to npm by GitHub Actions, a new releaser for @​hono/node-server since your current version.


        Updates `@modelcontextprotocol/sdk` from 1.29.0 to 1.30.0
        Release notes

        Sourced from @​modelcontextprotocol/sdk's releases.

        1.30.0

        What's Changed

        New Contributors

        Full Changelog: https://github.com/modelcontextprotocol/typescript-sdk/compare/v1.29.0...1.30.0

        Commits
        • 2d889f2 chore: bump version to 1.30.0 (#2563)
        • e3f3daa Fix SSE keep-alive timer lifecycle in Streamable HTTP server transport (v1.x)...
        • bb5a718 fix(deps): widen @​hono/node-server past GHSA-frvp-7c67-39w9 (#2549)
        • 1dad263 fix: send SSE keep-alive comment frames from Streamable HTTP server transport...
        • 69749aa Validate Content-Type by parsed media type instead of substring match (v1.x) ...
        • 369513d fix: support Zod 3.25 method literals (#2368)
        • e7ee57c v1 stdio buffer limit (#2239)
        • c36e1ef Add end-to-end test suite (#2167)
        • bf1e022 chore(ci): switch publish to OIDC trusted publishing (#1839)
        • 9edbab7 fix(server): prioritize zod issues and format them (#1503)
        • See full diff in compare view
        Maintainer changes

        This version was pushed to npm by GitHub Actions, a new releaser for @​modelcontextprotocol/sdk since your current version.


        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../src/jvmTest/typescript/package-lock.json | 10 +++++----- integration-test/src/jvmTest/typescript/package.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index 7cbfffd..fad2995 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -8,7 +8,7 @@ "name": "mcp-typescript-test-sdk", "version": "1.0.0", "dependencies": { - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/sdk": "^1.30.0", "express": "^5.2.1", "zod": "^4.4.3" }, @@ -476,12 +476,12 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", - "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz", + "integrity": "sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==", "license": "MIT", "dependencies": { - "@hono/node-server": "^1.19.9", + "@hono/node-server": "^1.19.9 || ^2.0.5", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", diff --git a/integration-test/src/jvmTest/typescript/package.json b/integration-test/src/jvmTest/typescript/package.json index 0cbee82..9bf2360 100644 --- a/integration-test/src/jvmTest/typescript/package.json +++ b/integration-test/src/jvmTest/typescript/package.json @@ -9,7 +9,7 @@ "server:http": "tsx server/http-server.ts" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/sdk": "^1.30.0", "express": "^5.2.1", "zod": "^4.4.3" }, From dae3476f7d44dadab04da83b9e3309ce026e1266 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 19:15:31 +0200 Subject: [PATCH 403/449] chore(deps): bump kotlin from 2.4.0 to 2.4.10 in /samples/weather-stdio-server (#905) Bumps `kotlin` from 2.4.0 to 2.4.10. Updates `org.jetbrains.kotlin.jvm` from 2.4.0 to 2.4.10
        Release notes

        Sourced from org.jetbrains.kotlin.jvm's releases.

        Kotlin 2.4.10

        Changelog

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        Compiler

        • KT-86939 JVM: IllegalStateException "No value for annotation parameter" when using const val in nested Java annotation array argument
        • KT-83766 K2: Wrong sourcePsi is set for SymbolPsiLiteral in SLC for annotation arguments referencing a const val
        • KT-86728 Reified type inference: expected type not propagated into inline call inside lambda with elvis operator

        Compose Compiler

        • b/522127447 Compose Compiler 2.4: classes previously inferred stable now reported runtime/Uncertain

        Klibs

        • KT-86501 Native: IrTypeAliasSymbolImpl is already bound. Signature: kotlinx.datetime/Instant|null[0] on iosSimulatorArm64

        Tools. CLI

        • KT-86930 Introduce kotlinr in the Kotlin distribution

        Tools. Gradle. BCV

        • KT-87223 Gradle, BCV: open version range in kotlinAbiValidationCompatClasspath causes kotlin-build-tools-impl to resolve to 2.4.20-Beta1 instead of 2.4.0

        Tools. Gradle. JS

        • KT-87304 jsBrowserTest fails with "exited with errors (exit code: 1)"
        • KT-86057 kotlinUpgradeYarnLock skips lock file regeneration when kotlinNpmInstall is up-to-date, causing kotlinStoreYarnLock to fail

        Tools. Gradle. Multiplatform

        • KT-87084 False positive warning for JS and Wasm compilations when CRI is enabled

        Tools. Scripts

        • KT-87076 @file:CompilerOptions("-jvm-target", ...) ignored in .main.kts scripts in Kotlin 2.4.0, falling back to JVM target 1.8
        • KT-86352 K2 scripting: FirResolvedTypeRef exception when resolving extension functions from imported scripts

        Kotlin 2.4.10-RC2

        Changelog

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        ... (truncated)

        Changelog

        Sourced from org.jetbrains.kotlin.jvm's changelog.

        2.4.10

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        Compiler

        • KT-86939 JVM: IllegalStateException "No value for annotation parameter" when using const val in nested Java annotation array argument
        • KT-83766 K2: Wrong sourcePsi is set for SymbolPsiLiteral in SLC for annotation arguments referencing a const val
        • KT-86728 Reified type inference: expected type not propagated into inline call inside lambda with elvis operator

        Compose Compiler

        • b/522127447 Compose Compiler 2.4: classes previously inferred stable now reported runtime/Uncertain

        Klibs

        • KT-86501 Native: IrTypeAliasSymbolImpl is already bound. Signature: kotlinx.datetime/Instant|null[0] on iosSimulatorArm64

        Tools. CLI

        • KT-86930 Introduce kotlinr in the Kotlin distribution

        Tools. Gradle. BCV

        • KT-87223 Gradle, BCV: open version range in kotlinAbiValidationCompatClasspath causes kotlin-build-tools-impl to resolve to 2.4.20-Beta1 instead of 2.4.0

        Tools. Gradle. JS

        • KT-87304 jsBrowserTest fails with "exited with errors (exit code: 1)"
        • KT-86057 kotlinUpgradeYarnLock skips lock file regeneration when kotlinNpmInstall is up-to-date, causing kotlinStoreYarnLock to fail

        Tools. Gradle. Multiplatform

        • KT-87084 False positive warning for JS and Wasm compilations when CRI is enabled

        Tools. Scripts

        • KT-87076 @file:CompilerOptions("-jvm-target", ...) ignored in .main.kts scripts in Kotlin 2.4.0, falling back to JVM target 1.8
        • KT-86352 K2 scripting: FirResolvedTypeRef exception when resolving extension functions from imported scripts
        Commits
        • 5687445 Add Changelog for 2.4.10-RC2
        • ba30392 [Wasm] Support regenerate unchanged modules flag on KGP
        • 6307e73 [Wasm] Implement Xwasm-IC-generate-unchanged-modules flag
        • 39d7aee Add ChangeLog for 2.4.10-RC2
        • a2956be [Gradle] Update karma.conf.js to use 'require(...)' instead plain strings
        • 9b1361c [Gradle] KT-87223: Updated regression test
        • a4c153a [Gradle] KT-87223: Fix toochain to 2.4.0 for abiValidation compat config
        • bc04b90 Add ChangeLog for 2.4.10-RC
        • bbcb94b [CRI] explicitly disable CRI generation for non-JVM targets to avoid false-po...
        • 6c2c458 Scripting: fix jvmTarget processing from script annotations
        • Additional commits viewable in compare view

        Updates `org.jetbrains.kotlin.plugin.serialization` from 2.4.0 to 2.4.10
        Release notes

        Sourced from org.jetbrains.kotlin.plugin.serialization's releases.

        Kotlin 2.4.10

        Changelog

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        Compiler

        • KT-86939 JVM: IllegalStateException "No value for annotation parameter" when using const val in nested Java annotation array argument
        • KT-83766 K2: Wrong sourcePsi is set for SymbolPsiLiteral in SLC for annotation arguments referencing a const val
        • KT-86728 Reified type inference: expected type not propagated into inline call inside lambda with elvis operator

        Compose Compiler

        • b/522127447 Compose Compiler 2.4: classes previously inferred stable now reported runtime/Uncertain

        Klibs

        • KT-86501 Native: IrTypeAliasSymbolImpl is already bound. Signature: kotlinx.datetime/Instant|null[0] on iosSimulatorArm64

        Tools. CLI

        • KT-86930 Introduce kotlinr in the Kotlin distribution

        Tools. Gradle. BCV

        • KT-87223 Gradle, BCV: open version range in kotlinAbiValidationCompatClasspath causes kotlin-build-tools-impl to resolve to 2.4.20-Beta1 instead of 2.4.0

        Tools. Gradle. JS

        • KT-87304 jsBrowserTest fails with "exited with errors (exit code: 1)"
        • KT-86057 kotlinUpgradeYarnLock skips lock file regeneration when kotlinNpmInstall is up-to-date, causing kotlinStoreYarnLock to fail

        Tools. Gradle. Multiplatform

        • KT-87084 False positive warning for JS and Wasm compilations when CRI is enabled

        Tools. Scripts

        • KT-87076 @file:CompilerOptions("-jvm-target", ...) ignored in .main.kts scripts in Kotlin 2.4.0, falling back to JVM target 1.8
        • KT-86352 K2 scripting: FirResolvedTypeRef exception when resolving extension functions from imported scripts

        Kotlin 2.4.10-RC2

        Changelog

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        ... (truncated)

        Changelog

        Sourced from org.jetbrains.kotlin.plugin.serialization's changelog.

        2.4.10

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        Compiler

        • KT-86939 JVM: IllegalStateException "No value for annotation parameter" when using const val in nested Java annotation array argument
        • KT-83766 K2: Wrong sourcePsi is set for SymbolPsiLiteral in SLC for annotation arguments referencing a const val
        • KT-86728 Reified type inference: expected type not propagated into inline call inside lambda with elvis operator

        Compose Compiler

        • b/522127447 Compose Compiler 2.4: classes previously inferred stable now reported runtime/Uncertain

        Klibs

        • KT-86501 Native: IrTypeAliasSymbolImpl is already bound. Signature: kotlinx.datetime/Instant|null[0] on iosSimulatorArm64

        Tools. CLI

        • KT-86930 Introduce kotlinr in the Kotlin distribution

        Tools. Gradle. BCV

        • KT-87223 Gradle, BCV: open version range in kotlinAbiValidationCompatClasspath causes kotlin-build-tools-impl to resolve to 2.4.20-Beta1 instead of 2.4.0

        Tools. Gradle. JS

        • KT-87304 jsBrowserTest fails with "exited with errors (exit code: 1)"
        • KT-86057 kotlinUpgradeYarnLock skips lock file regeneration when kotlinNpmInstall is up-to-date, causing kotlinStoreYarnLock to fail

        Tools. Gradle. Multiplatform

        • KT-87084 False positive warning for JS and Wasm compilations when CRI is enabled

        Tools. Scripts

        • KT-87076 @file:CompilerOptions("-jvm-target", ...) ignored in .main.kts scripts in Kotlin 2.4.0, falling back to JVM target 1.8
        • KT-86352 K2 scripting: FirResolvedTypeRef exception when resolving extension functions from imported scripts
        Commits
        • 5687445 Add Changelog for 2.4.10-RC2
        • ba30392 [Wasm] Support regenerate unchanged modules flag on KGP
        • 6307e73 [Wasm] Implement Xwasm-IC-generate-unchanged-modules flag
        • 39d7aee Add ChangeLog for 2.4.10-RC2
        • a2956be [Gradle] Update karma.conf.js to use 'require(...)' instead plain strings
        • 9b1361c [Gradle] KT-87223: Updated regression test
        • a4c153a [Gradle] KT-87223: Fix toochain to 2.4.0 for abiValidation compat config
        • bc04b90 Add ChangeLog for 2.4.10-RC
        • bbcb94b [CRI] explicitly disable CRI generation for non-JVM targets to avoid false-po...
        • 6c2c458 Scripting: fix jvmTarget processing from script annotations
        • Additional commits viewable in compare view

        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/weather-stdio-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/weather-stdio-server/gradle/libs.versions.toml b/samples/weather-stdio-server/gradle/libs.versions.toml index ea351f1..5f92a6c 100644 --- a/samples/weather-stdio-server/gradle/libs.versions.toml +++ b/samples/weather-stdio-server/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] coroutines = "1.11.0" -kotlin = "2.4.0" +kotlin = "2.4.10" ktor = "3.5.1" logging = "8.0.4" mcp-kotlin = "0.15.0" From 00a27e6ceb7da85f3aac9e1d68998e2426410be5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 19:46:01 +0200 Subject: [PATCH 404/449] chore(deps): bump kotlin from 2.4.0 to 2.4.10 in /samples/simple-streamable-server (#904) Bumps `kotlin` from 2.4.0 to 2.4.10. Updates `org.jetbrains.kotlin.jvm` from 2.4.0 to 2.4.10
        Release notes

        Sourced from org.jetbrains.kotlin.jvm's releases.

        Kotlin 2.4.10

        Changelog

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        Compiler

        • KT-86939 JVM: IllegalStateException "No value for annotation parameter" when using const val in nested Java annotation array argument
        • KT-83766 K2: Wrong sourcePsi is set for SymbolPsiLiteral in SLC for annotation arguments referencing a const val
        • KT-86728 Reified type inference: expected type not propagated into inline call inside lambda with elvis operator

        Compose Compiler

        • b/522127447 Compose Compiler 2.4: classes previously inferred stable now reported runtime/Uncertain

        Klibs

        • KT-86501 Native: IrTypeAliasSymbolImpl is already bound. Signature: kotlinx.datetime/Instant|null[0] on iosSimulatorArm64

        Tools. CLI

        • KT-86930 Introduce kotlinr in the Kotlin distribution

        Tools. Gradle. BCV

        • KT-87223 Gradle, BCV: open version range in kotlinAbiValidationCompatClasspath causes kotlin-build-tools-impl to resolve to 2.4.20-Beta1 instead of 2.4.0

        Tools. Gradle. JS

        • KT-87304 jsBrowserTest fails with "exited with errors (exit code: 1)"
        • KT-86057 kotlinUpgradeYarnLock skips lock file regeneration when kotlinNpmInstall is up-to-date, causing kotlinStoreYarnLock to fail

        Tools. Gradle. Multiplatform

        • KT-87084 False positive warning for JS and Wasm compilations when CRI is enabled

        Tools. Scripts

        • KT-87076 @file:CompilerOptions("-jvm-target", ...) ignored in .main.kts scripts in Kotlin 2.4.0, falling back to JVM target 1.8
        • KT-86352 K2 scripting: FirResolvedTypeRef exception when resolving extension functions from imported scripts

        Kotlin 2.4.10-RC2

        Changelog

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        ... (truncated)

        Changelog

        Sourced from org.jetbrains.kotlin.jvm's changelog.

        2.4.10

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        Compiler

        • KT-86939 JVM: IllegalStateException "No value for annotation parameter" when using const val in nested Java annotation array argument
        • KT-83766 K2: Wrong sourcePsi is set for SymbolPsiLiteral in SLC for annotation arguments referencing a const val
        • KT-86728 Reified type inference: expected type not propagated into inline call inside lambda with elvis operator

        Compose Compiler

        • b/522127447 Compose Compiler 2.4: classes previously inferred stable now reported runtime/Uncertain

        Klibs

        • KT-86501 Native: IrTypeAliasSymbolImpl is already bound. Signature: kotlinx.datetime/Instant|null[0] on iosSimulatorArm64

        Tools. CLI

        • KT-86930 Introduce kotlinr in the Kotlin distribution

        Tools. Gradle. BCV

        • KT-87223 Gradle, BCV: open version range in kotlinAbiValidationCompatClasspath causes kotlin-build-tools-impl to resolve to 2.4.20-Beta1 instead of 2.4.0

        Tools. Gradle. JS

        • KT-87304 jsBrowserTest fails with "exited with errors (exit code: 1)"
        • KT-86057 kotlinUpgradeYarnLock skips lock file regeneration when kotlinNpmInstall is up-to-date, causing kotlinStoreYarnLock to fail

        Tools. Gradle. Multiplatform

        • KT-87084 False positive warning for JS and Wasm compilations when CRI is enabled

        Tools. Scripts

        • KT-87076 @file:CompilerOptions("-jvm-target", ...) ignored in .main.kts scripts in Kotlin 2.4.0, falling back to JVM target 1.8
        • KT-86352 K2 scripting: FirResolvedTypeRef exception when resolving extension functions from imported scripts
        Commits
        • 5687445 Add Changelog for 2.4.10-RC2
        • ba30392 [Wasm] Support regenerate unchanged modules flag on KGP
        • 6307e73 [Wasm] Implement Xwasm-IC-generate-unchanged-modules flag
        • 39d7aee Add ChangeLog for 2.4.10-RC2
        • a2956be [Gradle] Update karma.conf.js to use 'require(...)' instead plain strings
        • 9b1361c [Gradle] KT-87223: Updated regression test
        • a4c153a [Gradle] KT-87223: Fix toochain to 2.4.0 for abiValidation compat config
        • bc04b90 Add ChangeLog for 2.4.10-RC
        • bbcb94b [CRI] explicitly disable CRI generation for non-JVM targets to avoid false-po...
        • 6c2c458 Scripting: fix jvmTarget processing from script annotations
        • Additional commits viewable in compare view

        Updates `org.jetbrains.kotlin.plugin.serialization` from 2.4.0 to 2.4.10
        Release notes

        Sourced from org.jetbrains.kotlin.plugin.serialization's releases.

        Kotlin 2.4.10

        Changelog

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        Compiler

        • KT-86939 JVM: IllegalStateException "No value for annotation parameter" when using const val in nested Java annotation array argument
        • KT-83766 K2: Wrong sourcePsi is set for SymbolPsiLiteral in SLC for annotation arguments referencing a const val
        • KT-86728 Reified type inference: expected type not propagated into inline call inside lambda with elvis operator

        Compose Compiler

        • b/522127447 Compose Compiler 2.4: classes previously inferred stable now reported runtime/Uncertain

        Klibs

        • KT-86501 Native: IrTypeAliasSymbolImpl is already bound. Signature: kotlinx.datetime/Instant|null[0] on iosSimulatorArm64

        Tools. CLI

        • KT-86930 Introduce kotlinr in the Kotlin distribution

        Tools. Gradle. BCV

        • KT-87223 Gradle, BCV: open version range in kotlinAbiValidationCompatClasspath causes kotlin-build-tools-impl to resolve to 2.4.20-Beta1 instead of 2.4.0

        Tools. Gradle. JS

        • KT-87304 jsBrowserTest fails with "exited with errors (exit code: 1)"
        • KT-86057 kotlinUpgradeYarnLock skips lock file regeneration when kotlinNpmInstall is up-to-date, causing kotlinStoreYarnLock to fail

        Tools. Gradle. Multiplatform

        • KT-87084 False positive warning for JS and Wasm compilations when CRI is enabled

        Tools. Scripts

        • KT-87076 @file:CompilerOptions("-jvm-target", ...) ignored in .main.kts scripts in Kotlin 2.4.0, falling back to JVM target 1.8
        • KT-86352 K2 scripting: FirResolvedTypeRef exception when resolving extension functions from imported scripts

        Kotlin 2.4.10-RC2

        Changelog

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        ... (truncated)

        Changelog

        Sourced from org.jetbrains.kotlin.plugin.serialization's changelog.

        2.4.10

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        Compiler

        • KT-86939 JVM: IllegalStateException "No value for annotation parameter" when using const val in nested Java annotation array argument
        • KT-83766 K2: Wrong sourcePsi is set for SymbolPsiLiteral in SLC for annotation arguments referencing a const val
        • KT-86728 Reified type inference: expected type not propagated into inline call inside lambda with elvis operator

        Compose Compiler

        • b/522127447 Compose Compiler 2.4: classes previously inferred stable now reported runtime/Uncertain

        Klibs

        • KT-86501 Native: IrTypeAliasSymbolImpl is already bound. Signature: kotlinx.datetime/Instant|null[0] on iosSimulatorArm64

        Tools. CLI

        • KT-86930 Introduce kotlinr in the Kotlin distribution

        Tools. Gradle. BCV

        • KT-87223 Gradle, BCV: open version range in kotlinAbiValidationCompatClasspath causes kotlin-build-tools-impl to resolve to 2.4.20-Beta1 instead of 2.4.0

        Tools. Gradle. JS

        • KT-87304 jsBrowserTest fails with "exited with errors (exit code: 1)"
        • KT-86057 kotlinUpgradeYarnLock skips lock file regeneration when kotlinNpmInstall is up-to-date, causing kotlinStoreYarnLock to fail

        Tools. Gradle. Multiplatform

        • KT-87084 False positive warning for JS and Wasm compilations when CRI is enabled

        Tools. Scripts

        • KT-87076 @file:CompilerOptions("-jvm-target", ...) ignored in .main.kts scripts in Kotlin 2.4.0, falling back to JVM target 1.8
        • KT-86352 K2 scripting: FirResolvedTypeRef exception when resolving extension functions from imported scripts
        Commits
        • 5687445 Add Changelog for 2.4.10-RC2
        • ba30392 [Wasm] Support regenerate unchanged modules flag on KGP
        • 6307e73 [Wasm] Implement Xwasm-IC-generate-unchanged-modules flag
        • 39d7aee Add ChangeLog for 2.4.10-RC2
        • a2956be [Gradle] Update karma.conf.js to use 'require(...)' instead plain strings
        • 9b1361c [Gradle] KT-87223: Updated regression test
        • a4c153a [Gradle] KT-87223: Fix toochain to 2.4.0 for abiValidation compat config
        • bc04b90 Add ChangeLog for 2.4.10-RC
        • bbcb94b [CRI] explicitly disable CRI generation for non-JVM targets to avoid false-po...
        • 6c2c458 Scripting: fix jvmTarget processing from script annotations
        • Additional commits viewable in compare view

        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/simple-streamable-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/simple-streamable-server/gradle/libs.versions.toml b/samples/simple-streamable-server/gradle/libs.versions.toml index 28ab094..328b154 100644 --- a/samples/simple-streamable-server/gradle/libs.versions.toml +++ b/samples/simple-streamable-server/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin = "2.4.0" +kotlin = "2.4.10" ktor = "3.5.1" junit = "6.1.2" mcp-kotlin = "0.15.0" From 046af272134af081ed2319602f294860c703e63d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 20:30:46 +0200 Subject: [PATCH 405/449] chore(deps): bump kotlin from 2.4.0 to 2.4.10 in /samples/kotlin-mcp-server (#900) Bumps `kotlin` from 2.4.0 to 2.4.10. Updates `org.jetbrains.kotlin.jvm` from 2.4.0 to 2.4.10
        Release notes

        Sourced from org.jetbrains.kotlin.jvm's releases.

        Kotlin 2.4.10

        Changelog

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        Compiler

        • KT-86939 JVM: IllegalStateException "No value for annotation parameter" when using const val in nested Java annotation array argument
        • KT-83766 K2: Wrong sourcePsi is set for SymbolPsiLiteral in SLC for annotation arguments referencing a const val
        • KT-86728 Reified type inference: expected type not propagated into inline call inside lambda with elvis operator

        Compose Compiler

        • b/522127447 Compose Compiler 2.4: classes previously inferred stable now reported runtime/Uncertain

        Klibs

        • KT-86501 Native: IrTypeAliasSymbolImpl is already bound. Signature: kotlinx.datetime/Instant|null[0] on iosSimulatorArm64

        Tools. CLI

        • KT-86930 Introduce kotlinr in the Kotlin distribution

        Tools. Gradle. BCV

        • KT-87223 Gradle, BCV: open version range in kotlinAbiValidationCompatClasspath causes kotlin-build-tools-impl to resolve to 2.4.20-Beta1 instead of 2.4.0

        Tools. Gradle. JS

        • KT-87304 jsBrowserTest fails with "exited with errors (exit code: 1)"
        • KT-86057 kotlinUpgradeYarnLock skips lock file regeneration when kotlinNpmInstall is up-to-date, causing kotlinStoreYarnLock to fail

        Tools. Gradle. Multiplatform

        • KT-87084 False positive warning for JS and Wasm compilations when CRI is enabled

        Tools. Scripts

        • KT-87076 @file:CompilerOptions("-jvm-target", ...) ignored in .main.kts scripts in Kotlin 2.4.0, falling back to JVM target 1.8
        • KT-86352 K2 scripting: FirResolvedTypeRef exception when resolving extension functions from imported scripts

        Kotlin 2.4.10-RC2

        Changelog

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        ... (truncated)

        Changelog

        Sourced from org.jetbrains.kotlin.jvm's changelog.

        2.4.10

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        Compiler

        • KT-86939 JVM: IllegalStateException "No value for annotation parameter" when using const val in nested Java annotation array argument
        • KT-83766 K2: Wrong sourcePsi is set for SymbolPsiLiteral in SLC for annotation arguments referencing a const val
        • KT-86728 Reified type inference: expected type not propagated into inline call inside lambda with elvis operator

        Compose Compiler

        • b/522127447 Compose Compiler 2.4: classes previously inferred stable now reported runtime/Uncertain

        Klibs

        • KT-86501 Native: IrTypeAliasSymbolImpl is already bound. Signature: kotlinx.datetime/Instant|null[0] on iosSimulatorArm64

        Tools. CLI

        • KT-86930 Introduce kotlinr in the Kotlin distribution

        Tools. Gradle. BCV

        • KT-87223 Gradle, BCV: open version range in kotlinAbiValidationCompatClasspath causes kotlin-build-tools-impl to resolve to 2.4.20-Beta1 instead of 2.4.0

        Tools. Gradle. JS

        • KT-87304 jsBrowserTest fails with "exited with errors (exit code: 1)"
        • KT-86057 kotlinUpgradeYarnLock skips lock file regeneration when kotlinNpmInstall is up-to-date, causing kotlinStoreYarnLock to fail

        Tools. Gradle. Multiplatform

        • KT-87084 False positive warning for JS and Wasm compilations when CRI is enabled

        Tools. Scripts

        • KT-87076 @file:CompilerOptions("-jvm-target", ...) ignored in .main.kts scripts in Kotlin 2.4.0, falling back to JVM target 1.8
        • KT-86352 K2 scripting: FirResolvedTypeRef exception when resolving extension functions from imported scripts
        Commits
        • 5687445 Add Changelog for 2.4.10-RC2
        • ba30392 [Wasm] Support regenerate unchanged modules flag on KGP
        • 6307e73 [Wasm] Implement Xwasm-IC-generate-unchanged-modules flag
        • 39d7aee Add ChangeLog for 2.4.10-RC2
        • a2956be [Gradle] Update karma.conf.js to use 'require(...)' instead plain strings
        • 9b1361c [Gradle] KT-87223: Updated regression test
        • a4c153a [Gradle] KT-87223: Fix toochain to 2.4.0 for abiValidation compat config
        • bc04b90 Add ChangeLog for 2.4.10-RC
        • bbcb94b [CRI] explicitly disable CRI generation for non-JVM targets to avoid false-po...
        • 6c2c458 Scripting: fix jvmTarget processing from script annotations
        • Additional commits viewable in compare view

        Updates `org.jetbrains.kotlin.plugin.serialization` from 2.4.0 to 2.4.10
        Release notes

        Sourced from org.jetbrains.kotlin.plugin.serialization's releases.

        Kotlin 2.4.10

        Changelog

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        Compiler

        • KT-86939 JVM: IllegalStateException "No value for annotation parameter" when using const val in nested Java annotation array argument
        • KT-83766 K2: Wrong sourcePsi is set for SymbolPsiLiteral in SLC for annotation arguments referencing a const val
        • KT-86728 Reified type inference: expected type not propagated into inline call inside lambda with elvis operator

        Compose Compiler

        • b/522127447 Compose Compiler 2.4: classes previously inferred stable now reported runtime/Uncertain

        Klibs

        • KT-86501 Native: IrTypeAliasSymbolImpl is already bound. Signature: kotlinx.datetime/Instant|null[0] on iosSimulatorArm64

        Tools. CLI

        • KT-86930 Introduce kotlinr in the Kotlin distribution

        Tools. Gradle. BCV

        • KT-87223 Gradle, BCV: open version range in kotlinAbiValidationCompatClasspath causes kotlin-build-tools-impl to resolve to 2.4.20-Beta1 instead of 2.4.0

        Tools. Gradle. JS

        • KT-87304 jsBrowserTest fails with "exited with errors (exit code: 1)"
        • KT-86057 kotlinUpgradeYarnLock skips lock file regeneration when kotlinNpmInstall is up-to-date, causing kotlinStoreYarnLock to fail

        Tools. Gradle. Multiplatform

        • KT-87084 False positive warning for JS and Wasm compilations when CRI is enabled

        Tools. Scripts

        • KT-87076 @file:CompilerOptions("-jvm-target", ...) ignored in .main.kts scripts in Kotlin 2.4.0, falling back to JVM target 1.8
        • KT-86352 K2 scripting: FirResolvedTypeRef exception when resolving extension functions from imported scripts

        Kotlin 2.4.10-RC2

        Changelog

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        ... (truncated)

        Changelog

        Sourced from org.jetbrains.kotlin.plugin.serialization's changelog.

        2.4.10

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        Compiler

        • KT-86939 JVM: IllegalStateException "No value for annotation parameter" when using const val in nested Java annotation array argument
        • KT-83766 K2: Wrong sourcePsi is set for SymbolPsiLiteral in SLC for annotation arguments referencing a const val
        • KT-86728 Reified type inference: expected type not propagated into inline call inside lambda with elvis operator

        Compose Compiler

        • b/522127447 Compose Compiler 2.4: classes previously inferred stable now reported runtime/Uncertain

        Klibs

        • KT-86501 Native: IrTypeAliasSymbolImpl is already bound. Signature: kotlinx.datetime/Instant|null[0] on iosSimulatorArm64

        Tools. CLI

        • KT-86930 Introduce kotlinr in the Kotlin distribution

        Tools. Gradle. BCV

        • KT-87223 Gradle, BCV: open version range in kotlinAbiValidationCompatClasspath causes kotlin-build-tools-impl to resolve to 2.4.20-Beta1 instead of 2.4.0

        Tools. Gradle. JS

        • KT-87304 jsBrowserTest fails with "exited with errors (exit code: 1)"
        • KT-86057 kotlinUpgradeYarnLock skips lock file regeneration when kotlinNpmInstall is up-to-date, causing kotlinStoreYarnLock to fail

        Tools. Gradle. Multiplatform

        • KT-87084 False positive warning for JS and Wasm compilations when CRI is enabled

        Tools. Scripts

        • KT-87076 @file:CompilerOptions("-jvm-target", ...) ignored in .main.kts scripts in Kotlin 2.4.0, falling back to JVM target 1.8
        • KT-86352 K2 scripting: FirResolvedTypeRef exception when resolving extension functions from imported scripts
        Commits
        • 5687445 Add Changelog for 2.4.10-RC2
        • ba30392 [Wasm] Support regenerate unchanged modules flag on KGP
        • 6307e73 [Wasm] Implement Xwasm-IC-generate-unchanged-modules flag
        • 39d7aee Add ChangeLog for 2.4.10-RC2
        • a2956be [Gradle] Update karma.conf.js to use 'require(...)' instead plain strings
        • 9b1361c [Gradle] KT-87223: Updated regression test
        • a4c153a [Gradle] KT-87223: Fix toochain to 2.4.0 for abiValidation compat config
        • bc04b90 Add ChangeLog for 2.4.10-RC
        • bbcb94b [CRI] explicitly disable CRI generation for non-JVM targets to avoid false-po...
        • 6c2c458 Scripting: fix jvmTarget processing from script annotations
        • Additional commits viewable in compare view

        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-server/gradle/libs.versions.toml b/samples/kotlin-mcp-server/gradle/libs.versions.toml index 83a262e..ba4f306 100644 --- a/samples/kotlin-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-server/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin = "2.4.0" +kotlin = "2.4.10" ktor = "3.5.1" mcp-kotlin = "0.15.0" slf4j = "2.0.18" From f1cf484e25573b8aa858633af8e0830aa9cabf45 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 20:55:25 +0200 Subject: [PATCH 406/449] chore(deps): bump kotlin from 2.4.0 to 2.4.10 in /samples/kotlinlang-mcp-server (#903) Bumps `kotlin` from 2.4.0 to 2.4.10. Updates `org.jetbrains.kotlin.jvm` from 2.4.0 to 2.4.10
        Release notes

        Sourced from org.jetbrains.kotlin.jvm's releases.

        Kotlin 2.4.10

        Changelog

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        Compiler

        • KT-86939 JVM: IllegalStateException "No value for annotation parameter" when using const val in nested Java annotation array argument
        • KT-83766 K2: Wrong sourcePsi is set for SymbolPsiLiteral in SLC for annotation arguments referencing a const val
        • KT-86728 Reified type inference: expected type not propagated into inline call inside lambda with elvis operator

        Compose Compiler

        • b/522127447 Compose Compiler 2.4: classes previously inferred stable now reported runtime/Uncertain

        Klibs

        • KT-86501 Native: IrTypeAliasSymbolImpl is already bound. Signature: kotlinx.datetime/Instant|null[0] on iosSimulatorArm64

        Tools. CLI

        • KT-86930 Introduce kotlinr in the Kotlin distribution

        Tools. Gradle. BCV

        • KT-87223 Gradle, BCV: open version range in kotlinAbiValidationCompatClasspath causes kotlin-build-tools-impl to resolve to 2.4.20-Beta1 instead of 2.4.0

        Tools. Gradle. JS

        • KT-87304 jsBrowserTest fails with "exited with errors (exit code: 1)"
        • KT-86057 kotlinUpgradeYarnLock skips lock file regeneration when kotlinNpmInstall is up-to-date, causing kotlinStoreYarnLock to fail

        Tools. Gradle. Multiplatform

        • KT-87084 False positive warning for JS and Wasm compilations when CRI is enabled

        Tools. Scripts

        • KT-87076 @file:CompilerOptions("-jvm-target", ...) ignored in .main.kts scripts in Kotlin 2.4.0, falling back to JVM target 1.8
        • KT-86352 K2 scripting: FirResolvedTypeRef exception when resolving extension functions from imported scripts

        Kotlin 2.4.10-RC2

        Changelog

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        ... (truncated)

        Changelog

        Sourced from org.jetbrains.kotlin.jvm's changelog.

        2.4.10

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        Compiler

        • KT-86939 JVM: IllegalStateException "No value for annotation parameter" when using const val in nested Java annotation array argument
        • KT-83766 K2: Wrong sourcePsi is set for SymbolPsiLiteral in SLC for annotation arguments referencing a const val
        • KT-86728 Reified type inference: expected type not propagated into inline call inside lambda with elvis operator

        Compose Compiler

        • b/522127447 Compose Compiler 2.4: classes previously inferred stable now reported runtime/Uncertain

        Klibs

        • KT-86501 Native: IrTypeAliasSymbolImpl is already bound. Signature: kotlinx.datetime/Instant|null[0] on iosSimulatorArm64

        Tools. CLI

        • KT-86930 Introduce kotlinr in the Kotlin distribution

        Tools. Gradle. BCV

        • KT-87223 Gradle, BCV: open version range in kotlinAbiValidationCompatClasspath causes kotlin-build-tools-impl to resolve to 2.4.20-Beta1 instead of 2.4.0

        Tools. Gradle. JS

        • KT-87304 jsBrowserTest fails with "exited with errors (exit code: 1)"
        • KT-86057 kotlinUpgradeYarnLock skips lock file regeneration when kotlinNpmInstall is up-to-date, causing kotlinStoreYarnLock to fail

        Tools. Gradle. Multiplatform

        • KT-87084 False positive warning for JS and Wasm compilations when CRI is enabled

        Tools. Scripts

        • KT-87076 @file:CompilerOptions("-jvm-target", ...) ignored in .main.kts scripts in Kotlin 2.4.0, falling back to JVM target 1.8
        • KT-86352 K2 scripting: FirResolvedTypeRef exception when resolving extension functions from imported scripts
        Commits
        • 5687445 Add Changelog for 2.4.10-RC2
        • ba30392 [Wasm] Support regenerate unchanged modules flag on KGP
        • 6307e73 [Wasm] Implement Xwasm-IC-generate-unchanged-modules flag
        • 39d7aee Add ChangeLog for 2.4.10-RC2
        • a2956be [Gradle] Update karma.conf.js to use 'require(...)' instead plain strings
        • 9b1361c [Gradle] KT-87223: Updated regression test
        • a4c153a [Gradle] KT-87223: Fix toochain to 2.4.0 for abiValidation compat config
        • bc04b90 Add ChangeLog for 2.4.10-RC
        • bbcb94b [CRI] explicitly disable CRI generation for non-JVM targets to avoid false-po...
        • 6c2c458 Scripting: fix jvmTarget processing from script annotations
        • Additional commits viewable in compare view

        Updates `org.jetbrains.kotlin.plugin.serialization` from 2.4.0 to 2.4.10
        Release notes

        Sourced from org.jetbrains.kotlin.plugin.serialization's releases.

        Kotlin 2.4.10

        Changelog

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        Compiler

        • KT-86939 JVM: IllegalStateException "No value for annotation parameter" when using const val in nested Java annotation array argument
        • KT-83766 K2: Wrong sourcePsi is set for SymbolPsiLiteral in SLC for annotation arguments referencing a const val
        • KT-86728 Reified type inference: expected type not propagated into inline call inside lambda with elvis operator

        Compose Compiler

        • b/522127447 Compose Compiler 2.4: classes previously inferred stable now reported runtime/Uncertain

        Klibs

        • KT-86501 Native: IrTypeAliasSymbolImpl is already bound. Signature: kotlinx.datetime/Instant|null[0] on iosSimulatorArm64

        Tools. CLI

        • KT-86930 Introduce kotlinr in the Kotlin distribution

        Tools. Gradle. BCV

        • KT-87223 Gradle, BCV: open version range in kotlinAbiValidationCompatClasspath causes kotlin-build-tools-impl to resolve to 2.4.20-Beta1 instead of 2.4.0

        Tools. Gradle. JS

        • KT-87304 jsBrowserTest fails with "exited with errors (exit code: 1)"
        • KT-86057 kotlinUpgradeYarnLock skips lock file regeneration when kotlinNpmInstall is up-to-date, causing kotlinStoreYarnLock to fail

        Tools. Gradle. Multiplatform

        • KT-87084 False positive warning for JS and Wasm compilations when CRI is enabled

        Tools. Scripts

        • KT-87076 @file:CompilerOptions("-jvm-target", ...) ignored in .main.kts scripts in Kotlin 2.4.0, falling back to JVM target 1.8
        • KT-86352 K2 scripting: FirResolvedTypeRef exception when resolving extension functions from imported scripts

        Kotlin 2.4.10-RC2

        Changelog

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        ... (truncated)

        Changelog

        Sourced from org.jetbrains.kotlin.plugin.serialization's changelog.

        2.4.10

        Backend. Wasm

        • KT-87066 K/Wasm: Not all files are presented in compiler output directory with multimodule-closed-world and incremental compilation

        Compiler

        • KT-86939 JVM: IllegalStateException "No value for annotation parameter" when using const val in nested Java annotation array argument
        • KT-83766 K2: Wrong sourcePsi is set for SymbolPsiLiteral in SLC for annotation arguments referencing a const val
        • KT-86728 Reified type inference: expected type not propagated into inline call inside lambda with elvis operator

        Compose Compiler

        • b/522127447 Compose Compiler 2.4: classes previously inferred stable now reported runtime/Uncertain

        Klibs

        • KT-86501 Native: IrTypeAliasSymbolImpl is already bound. Signature: kotlinx.datetime/Instant|null[0] on iosSimulatorArm64

        Tools. CLI

        • KT-86930 Introduce kotlinr in the Kotlin distribution

        Tools. Gradle. BCV

        • KT-87223 Gradle, BCV: open version range in kotlinAbiValidationCompatClasspath causes kotlin-build-tools-impl to resolve to 2.4.20-Beta1 instead of 2.4.0

        Tools. Gradle. JS

        • KT-87304 jsBrowserTest fails with "exited with errors (exit code: 1)"
        • KT-86057 kotlinUpgradeYarnLock skips lock file regeneration when kotlinNpmInstall is up-to-date, causing kotlinStoreYarnLock to fail

        Tools. Gradle. Multiplatform

        • KT-87084 False positive warning for JS and Wasm compilations when CRI is enabled

        Tools. Scripts

        • KT-87076 @file:CompilerOptions("-jvm-target", ...) ignored in .main.kts scripts in Kotlin 2.4.0, falling back to JVM target 1.8
        • KT-86352 K2 scripting: FirResolvedTypeRef exception when resolving extension functions from imported scripts
        Commits
        • 5687445 Add Changelog for 2.4.10-RC2
        • ba30392 [Wasm] Support regenerate unchanged modules flag on KGP
        • 6307e73 [Wasm] Implement Xwasm-IC-generate-unchanged-modules flag
        • 39d7aee Add ChangeLog for 2.4.10-RC2
        • a2956be [Gradle] Update karma.conf.js to use 'require(...)' instead plain strings
        • 9b1361c [Gradle] KT-87223: Updated regression test
        • a4c153a [Gradle] KT-87223: Fix toochain to 2.4.0 for abiValidation compat config
        • bc04b90 Add ChangeLog for 2.4.10-RC
        • bbcb94b [CRI] explicitly disable CRI generation for non-JVM targets to avoid false-po...
        • 6c2c458 Scripting: fix jvmTarget processing from script annotations
        • Additional commits viewable in compare view

        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlinlang-mcp-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml index 2d48f35..feecf5b 100644 --- a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin = "2.4.0" +kotlin = "2.4.10" ktor = "3.5.1" mcp-kotlin = "0.15.0" serialization = "1.11.0" From 455a4b30c333003b2063630bfe23b1d656181ef0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Aug 2026 20:46:39 +0200 Subject: [PATCH 407/449] chore(deps): bump fast-uri from 3.1.4 to 3.1.5 in /integration-test/src/jvmTest/typescript (#935) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.4 to 3.1.5.
        Release notes

        Sourced from fast-uri's releases.

        v3.1.5

        ⚠️ Security Warning

        Fix for https://github.com/fastify/fast-uri/security/advisories/GHSA-7p8r-x3mc-p8w7

        Full Changelog: https://github.com/fastify/fast-uri/compare/v3.1.4...v3.1.5

        Commits

        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- integration-test/src/jvmTest/typescript/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index fad2995..1577f3d 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -1387,9 +1387,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", - "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", "funding": [ { "type": "github", From 0ab49f30bea3c57dbf41f0ce4994d4ca23f37f9d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Aug 2026 21:29:09 +0200 Subject: [PATCH 408/449] chore(deps): bump the all-dependencies group across 1 directory with 2 updates (#937) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the all-dependencies group with 2 updates in the /integration-test/src/jvmTest/typescript directory: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) and [tsx](https://github.com/privatenumber/tsx). Updates `@types/node` from 26.1.1 to 26.1.2
        Commits

        Updates `tsx` from 4.23.1 to 4.23.4
        Release notes

        Sourced from tsx's releases.

        v4.23.4

        4.23.4 (2026-08-02)

        Bug Fixes

        • cli: allow async process.once() signal handlers to finish (#827) (2afc7bb)

        This release is also available on:

        v4.23.3

        4.23.3 (2026-08-02)

        Bug Fixes

        • preserve listenerCount overload semantics (#823) (b6574d4)

        This release is also available on:

        v4.23.2

        4.23.2 (2026-08-02)

        Bug Fixes

        • watch: exit with 128 + signal number when interrupted (#820) (0b00bc8)

        This release is also available on:

        Commits
        • 2afc7bb fix(cli): allow async process.once() signal handlers to finish (#827)
        • b6574d4 fix: preserve listenerCount overload semantics (#823)
        • 0b00bc8 fix(watch): exit with 128 + signal number when interrupted (#820)
        • 3216332 docs(faq): minor grammar nit (#686)
        • 5828ffe chore: require cross-spawn 7.0.6 (#815)
        • 33afa36 ci: gate releases on test workflow
        • See full diff in compare view

        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../src/jvmTest/typescript/package-lock.json | 16 ++++++++-------- .../src/jvmTest/typescript/package.json | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index 1577f3d..26c6e7f 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -15,9 +15,9 @@ "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^26.1.1", + "@types/node": "^26.1.2", "cors": "^2.8.6", - "tsx": "^4.23.1", + "tsx": "^4.23.4", "typescript": "^7.0.2" } }, @@ -579,9 +579,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "26.1.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz", - "integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==", + "version": "26.1.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.2.tgz", + "integrity": "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg==", "dev": true, "license": "MIT", "dependencies": { @@ -2034,9 +2034,9 @@ } }, "node_modules/tsx": { - "version": "4.23.1", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.1.tgz", - "integrity": "sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==", + "version": "4.23.4", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.4.tgz", + "integrity": "sha512-ZiUQ8oT/KzN51mJUWPqARYqwFLFJZtGZipRkw1ynHMr9vy3eU77m5yfF3Gzm6meEg/beW+lUu3fHYgskTN2oVQ==", "dev": true, "license": "MIT", "dependencies": { diff --git a/integration-test/src/jvmTest/typescript/package.json b/integration-test/src/jvmTest/typescript/package.json index 9bf2360..240e861 100644 --- a/integration-test/src/jvmTest/typescript/package.json +++ b/integration-test/src/jvmTest/typescript/package.json @@ -16,9 +16,9 @@ "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^26.1.1", + "@types/node": "^26.1.2", "cors": "^2.8.6", - "tsx": "^4.23.1", + "tsx": "^4.23.4", "typescript": "^7.0.2" } } From 76c12274ef4795859924950f511d3c8813d81b6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20=C4=B0hsan=20G=C3=B6rgel?= Date: Thu, 6 Aug 2026 00:20:07 +0300 Subject: [PATCH 409/449] feat(core): add SEP-2575 request metadata and discovery types (#893) ## Summary Part of #815. Adds the core types and codecs needed for SEP-2575: - experimental accessors for request-scoped metadata while preserving legacy request shapes and unknown `_meta` entries; - `server/discover` request/result types, cache fields, and unsupported-version error data; and - polymorphic request/result decoding with wire-format and malformed-input coverage. The discovery types match the merged draft schema in modelcontextprotocol/modelcontextprotocol#3002: `protocolVersion` and `clientCapabilities` are required, `clientInfo` is optional, and optional server information remains in result `_meta`. ## Motivation and Context SEP-2575 moves protocol and client context onto individual requests and introduces `server/discover`. The shared `RequestMeta` accessors remain nullable so legacy requests stay representable; `DiscoverRequestParams` enforces the fields required by the new discovery request. This PR stops at the core type and codec boundary. It does not add server admission, request-scoped dispatch, Streamable HTTP, authentication, subscriptions, MRTR, lifecycle hooks, or a discovery DSL. ## How Has This Been Tested? - JVM: 597/597 tests - JS Node and Chrome: 528/528 tests per target - WasmJS Node and Chrome: 528/528 tests per target - iOS Simulator arm64 and macOS arm64: 528/528 tests per target - API dump/check, ktlint, detekt, and diff checks Coverage includes required discovery metadata, optional `clientInfo`, required cache fields, unknown metadata round-trips, the legacy `resultType` fallback, unsupported-version data, and polymorphic result classification. ## Breaking Changes This adds `DiscoverRequest` as a new `ClientRequest` subtype. Consumers recompiling exhaustive `when` expressions over `ClientRequest` may need to add a branch. Existing request types and wire representations are unchanged. ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [x] 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 - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed ## Additional context `ttlMs` and `cacheScope` have Kotlin construction defaults but remain required when decoding. An absent `resultType` decodes as `"complete"` for compatibility with legacy results. Discovery result selection requires the complete required shape so unrelated result extensions are not misclassified. --------- Co-authored-by: Pavel Gorgulov --- kotlin-sdk-core/api/kotlin-sdk-core.api | 146 ++++++++ .../kotlin/sdk/types/discovery.kt | 107 ++++++ .../kotlin/sdk/types/jsonRpc.kt | 3 + .../kotlin/sdk/types/methods.kt | 1 + .../kotlin/sdk/types/request.kt | 91 +++++ .../kotlin/sdk/types/serializers.kt | 10 + .../kotlin/sdk/types/DiscoveryTest.kt | 324 ++++++++++++++++++ .../kotlin/sdk/types/RequestTest.kt | 100 ++++++ 8 files changed, 782 insertions(+) create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/discovery.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/DiscoveryTest.kt diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index 28d6737..b3e88d8 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -390,6 +390,19 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/BooleanSchema$Compan public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/CacheScope : java/lang/Enum { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CacheScope$Companion; + public static final field Private Lio/modelcontextprotocol/kotlin/sdk/types/CacheScope; + public static final field Public Lio/modelcontextprotocol/kotlin/sdk/types/CacheScope; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/CacheScope; + public static fun values ()[Lio/modelcontextprotocol/kotlin/sdk/types/CacheScope; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/CacheScope$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/CallToolRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequest$Companion; public fun (Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequestParams;)V @@ -1395,6 +1408,104 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CustomRequest$Compan public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/DiscoverRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequest$Companion; + public fun (Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequestParams;)V + public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequestParams; + public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequestParams;)Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequest; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequest;Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequestParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequest; + public fun equals (Ljava/lang/Object;)Z + public final fun getMeta--Khlwt8 ()Lkotlinx/serialization/json/JsonObject; + public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method; + public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequestParams; + public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestParams; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/DiscoverRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequest$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequest; + 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/DiscoverRequest;)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/DiscoverRequest$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/DiscoverRequestParams : io/modelcontextprotocol/kotlin/sdk/types/RequestParams { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequestParams$Companion; + public synthetic fun (Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1--Khlwt8 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy-hVVZdjo (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequestParams; + public static synthetic fun copy-hVVZdjo$default (Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequestParams;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequestParams; + public fun equals (Ljava/lang/Object;)Z + public fun getMeta--Khlwt8 ()Lkotlinx/serialization/json/JsonObject; + public synthetic fun getMeta-VI-3G7E ()Lkotlinx/serialization/json/JsonObject; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/DiscoverRequestParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequestParams$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverRequestParams; + 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/DiscoverRequestParams;)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/DiscoverRequestParams$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class io/modelcontextprotocol/kotlin/sdk/types/DiscoverResult : io/modelcontextprotocol/kotlin/sdk/types/ServerResult { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverResult$Companion; + public fun (Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;Ljava/lang/String;Ljava/lang/String;JLio/modelcontextprotocol/kotlin/sdk/types/CacheScope;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;Ljava/lang/String;Ljava/lang/String;JLio/modelcontextprotocol/kotlin/sdk/types/CacheScope;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()J + public final fun component6 ()Lio/modelcontextprotocol/kotlin/sdk/types/CacheScope; + public final fun component7 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;Ljava/lang/String;Ljava/lang/String;JLio/modelcontextprotocol/kotlin/sdk/types/CacheScope;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverResult; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverResult;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;Ljava/lang/String;Ljava/lang/String;JLio/modelcontextprotocol/kotlin/sdk/types/CacheScope;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverResult; + public fun equals (Ljava/lang/Object;)Z + public final fun getCacheScope ()Lio/modelcontextprotocol/kotlin/sdk/types/CacheScope; + public final fun getCapabilities ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities; + public final fun getInstructions ()Ljava/lang/String; + public fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public final fun getResultType ()Ljava/lang/String; + public final fun getSupportedVersions ()Ljava/util/List; + public final fun getTtlMs ()J + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/DiscoverResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/DiscoverResult; + 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/DiscoverResult;)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/DiscoverResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/DoubleSchema : io/modelcontextprotocol/kotlin/sdk/types/NumberSchemaDefinition { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/DoubleSchema$Companion; public fun ()V @@ -3245,6 +3356,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/Method$Defined : jav public static final field ResourcesUnsubscribe Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; public static final field RootsList Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; public static final field SamplingCreateMessage Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; + public static final field ServerDiscover Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; public static final field TasksCancel Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; public static final field TasksGet Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; public static final field TasksList Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined; @@ -3767,6 +3879,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/RPCError$ErrorCode { public static final field PARSE_ERROR I public static final field REQUEST_TIMEOUT I public static final field RESOURCE_NOT_FOUND I + public static final field UNSUPPORTED_PROTOCOL_VERSION I public static final field URL_ELICITATION_REQUIRED I } @@ -4012,8 +4125,12 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/RequestMeta { public static fun equals-impl (Lkotlinx/serialization/json/JsonObject;Ljava/lang/Object;)Z public static final fun equals-impl0 (Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Z public static final fun get-impl (Lkotlinx/serialization/json/JsonObject;Ljava/lang/String;)Lkotlinx/serialization/json/JsonElement; + public static final fun getClientCapabilities-impl (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities; + public static final fun getClientInfo-impl (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/Implementation; public final fun getJson ()Lkotlinx/serialization/json/JsonObject; + public static final fun getLogLevel-impl (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; public static final fun getProgressToken-impl (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/RequestId; + public static final fun getProtocolVersion-impl (Lkotlinx/serialization/json/JsonObject;)Ljava/lang/String; public static final fun getRelatedTask-impl (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/RelatedTaskMetadata; public fun hashCode ()I public static fun hashCode-impl (Lkotlinx/serialization/json/JsonObject;)I @@ -5736,6 +5853,35 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequestPa public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/UnsupportedProtocolVersionData { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/UnsupportedProtocolVersionData$Companion; + public fun (Ljava/util/List;Ljava/lang/String;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/util/List;Ljava/lang/String;)Lio/modelcontextprotocol/kotlin/sdk/types/UnsupportedProtocolVersionData; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/UnsupportedProtocolVersionData;Ljava/util/List;Ljava/lang/String;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/UnsupportedProtocolVersionData; + public fun equals (Ljava/lang/Object;)Z + public final fun getRequested ()Ljava/lang/String; + public final fun getSupported ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/UnsupportedProtocolVersionData$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/UnsupportedProtocolVersionData$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/UnsupportedProtocolVersionData; + 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/UnsupportedProtocolVersionData;)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/UnsupportedProtocolVersionData$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/UntitledMultiSelectEnumSchema : io/modelcontextprotocol/kotlin/sdk/types/MultiSelectEnumSchema { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/UntitledMultiSelectEnumSchema$Companion; public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Lio/modelcontextprotocol/kotlin/sdk/types/UntitledMultiSelectEnumSchema$Items;Ljava/util/List;)V diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/discovery.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/discovery.kt new file mode 100644 index 0000000..d486b1f --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/discovery.kt @@ -0,0 +1,107 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import kotlinx.serialization.EncodeDefault +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.Required +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.SerializationException +import kotlinx.serialization.json.JsonObject + +/** + * Parameters for a `server/discover` request. + * + * Discovery belongs to the request-scoped lifecycle and therefore requires a protocol version and + * client capabilities in [meta]. Client information is recommended by the protocol but optional. + * Unknown metadata entries are preserved by [RequestMeta]. + * + * @property meta request-scoped protocol and client metadata + * @throws IllegalArgumentException if required request-scoped metadata is absent + * @throws SerializationException if required metadata is malformed + */ +@Serializable +@ExperimentalMcpApi +public data class DiscoverRequestParams( + @SerialName("_meta") + override val meta: RequestMeta, +) : RequestParams { + init { + requireNotNull(meta.protocolVersion) { "${RequestMetaKeys.PROTOCOL_VERSION} is required" } + requireNotNull(meta.clientCapabilities) { "${RequestMetaKeys.CLIENT_CAPABILITIES} is required" } + } +} + +/** + * Requests the protocol versions, capabilities, and metadata advertised by a server. + * + * @property params request metadata required by the request-scoped lifecycle + */ +@Serializable +@ExperimentalMcpApi +public data class DiscoverRequest(override val params: DiscoverRequestParams) : ClientRequest { + @OptIn(ExperimentalSerializationApi::class) + @EncodeDefault + override val method: Method = Method.Defined.ServerDiscover + + /** Metadata for this request. */ + public val meta: RequestMeta + get() = params.meta +} + +/** Defines where a cached discovery result may be reused. */ +@Serializable +@ExperimentalMcpApi +public enum class CacheScope { + /** The result may only be reused within the same authorization context. */ + @SerialName("private") + Private, + + /** The result may be shared across authorization contexts. */ + @SerialName("public") + Public, +} + +/** + * Result returned by `server/discover`. + * + * @property supportedVersions protocol versions supported by the server + * @property capabilities capabilities advertised by the server + * @property instructions optional guidance for clients using the server + * @property resultType discriminator for the result representation + * @property ttlMs number of milliseconds clients may treat this result as fresh + * @property cacheScope authorization boundary within which the result may be reused + * @property meta optional response metadata, including optional server information + * @throws IllegalArgumentException if [ttlMs] is negative + */ +@Serializable +@ExperimentalMcpApi +public data class DiscoverResult( + val supportedVersions: List, + val capabilities: ServerCapabilities, + val instructions: String? = null, + @EncodeDefault + val resultType: String = COMPLETE_RESULT_TYPE, + @Required + val ttlMs: Long = 0, + @Required + val cacheScope: CacheScope = CacheScope.Private, + @SerialName("_meta") + override val meta: JsonObject? = null, +) : ServerResult { + init { + require(ttlMs >= 0) { "ttlMs must be non-negative, but was $ttlMs" } + } +} + +/** + * Data returned when a request selects an unsupported protocol version. + * + * @property supported protocol versions supported by the receiver + * @property requested protocol version selected by the request + */ +@Serializable +@ExperimentalMcpApi +public data class UnsupportedProtocolVersionData(val supported: List, val requested: String) + +internal const val COMPLETE_RESULT_TYPE: String = "complete" diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt index a9d465f..7e10904 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/jsonRpc.kt @@ -269,6 +269,9 @@ public data class RPCError(val code: Int, val message: String, val data: JsonEle /** Resource not found */ public const val RESOURCE_NOT_FOUND: Int = -32002 + /** The request selected a protocol version that the receiver does not support. */ + public const val UNSUPPORTED_PROTOCOL_VERSION: Int = -32022 + /** * A URL-mode elicitation must be completed before the request can be processed. * The error [data][RPCError.data] carries the required URL-mode elicitations diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/methods.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/methods.kt index 90403e8..1824ad0 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/methods.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/methods.kt @@ -16,6 +16,7 @@ public sealed interface Method { @Serializable public enum class Defined(override val value: String) : Method { Initialize("initialize"), + ServerDiscover("server/discover"), Ping("ping"), ResourcesList("resources/list"), ResourcesTemplatesList("resources/templates/list"), diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.kt index aacab9a..7d885fb 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.kt @@ -1,8 +1,10 @@ package io.modelcontextprotocol.kotlin.sdk.types +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlinx.serialization.SerializationException import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive @@ -11,6 +13,13 @@ import kotlinx.serialization.json.long import kotlinx.serialization.json.longOrNull import kotlin.jvm.JvmInline +internal object RequestMetaKeys { + const val PROTOCOL_VERSION: String = "io.modelcontextprotocol/protocolVersion" + const val CLIENT_INFO: String = "io.modelcontextprotocol/clientInfo" + const val CLIENT_CAPABILITIES: String = "io.modelcontextprotocol/clientCapabilities" + const val LOG_LEVEL: String = "io.modelcontextprotocol/logLevel" +} + /** * Metadata attached to a request's `_meta` field. * @@ -40,6 +49,88 @@ public value class RequestMeta(public val json: JsonObject) { McpJson.decodeFromJsonElement(element) } + /** + * The MCP protocol version selected for this request, or `null` when absent. + * + * This field is required by the request-scoped lifecycle introduced in protocol version + * `2026-07-28`, but remains optional here so older requests retain their wire shape. + * + * @throws SerializationException if the field is present but is not a string + */ + @ExperimentalMcpApi + public val protocolVersion: String? + get() = json[RequestMetaKeys.PROTOCOL_VERSION]?.let { element -> + if (element !is JsonPrimitive || !element.isString) { + throw SerializationException("${RequestMetaKeys.PROTOCOL_VERSION} must be a JSON string") + } + element.content + } + + /** + * Information about the client making this request, or `null` when absent. + * + * Request-scoped clients should include this field unless specifically configured not to do + * so. The self-reported value is intended for display, logging, and debugging; servers should + * not use it to change behavior or rely on it for security decisions. + * + * @throws SerializationException if the field is present but is not a valid [Implementation] + */ + @ExperimentalMcpApi + public val clientInfo: Implementation? + get() = json[RequestMetaKeys.CLIENT_INFO]?.let { element -> + try { + McpJson.decodeFromJsonElement(element) + } catch (cause: SerializationException) { + throw SerializationException( + "${RequestMetaKeys.CLIENT_INFO} must be a valid client implementation", + cause, + ) + } + } + + /** + * Capabilities declared by the client for this request, or `null` when absent. + * + * Servers using the request-scoped lifecycle must not infer capabilities from an earlier + * request. An empty object means the client supports no optional capabilities. + * + * @throws SerializationException if the field is present but is not valid [ClientCapabilities] + */ + @ExperimentalMcpApi + public val clientCapabilities: ClientCapabilities? + get() = json[RequestMetaKeys.CLIENT_CAPABILITIES]?.let { element -> + try { + McpJson.decodeFromJsonElement(element) + } catch (cause: SerializationException) { + throw SerializationException( + "${RequestMetaKeys.CLIENT_CAPABILITIES} must be a valid client capabilities object", + cause, + ) + } + } + + /** + * Minimum severity of request-associated log notifications, or `null` when absent. + * + * In the request-scoped lifecycle, absence means that the server must not emit log + * notifications for this request. + * + * @throws SerializationException if the field is present but is not a valid [LoggingLevel] + */ + @Deprecated("Per-request log levels are deprecated as of MCP protocol version 2026-07-28 (SEP-2577).") + @ExperimentalMcpApi + public val logLevel: LoggingLevel? + get() = json[RequestMetaKeys.LOG_LEVEL]?.let { element -> + try { + McpJson.decodeFromJsonElement(element) + } catch (cause: SerializationException) { + throw SerializationException( + "${RequestMetaKeys.LOG_LEVEL} must be a valid logging level", + cause, + ) + } + } + /** * Retrieves the value associated with the specified key from the JSON object. * diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt index 6eab14a..36b7e37 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/serializers.kt @@ -1,6 +1,7 @@ package io.modelcontextprotocol.kotlin.sdk.types import io.github.oshai.kotlinlogging.KotlinLogging +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.KSerializer import kotlinx.serialization.SerializationException @@ -238,9 +239,11 @@ internal object ResourceContentsPolymorphicSerializer : // Request Serializers // ============================================================================ +@OptIn(ExperimentalMcpApi::class) private val clientRequestDeserializers: Map> by lazy { mapOf( Method.Defined.CompletionComplete.value to CompleteRequest.serializer(), + Method.Defined.ServerDiscover.value to DiscoverRequest.serializer(), Method.Defined.Initialize.value to InitializeRequest.serializer(), Method.Defined.Ping.value to PingRequest.serializer(), Method.Defined.LoggingSetLevel.value to SetLevelRequest.serializer(), @@ -417,9 +420,16 @@ private fun selectClientResultDeserializer(element: JsonElement): Deserializatio * Selects the appropriate deserializer for server results based on JSON content. * Returns null if the structure doesn't match any known server result type. */ +@OptIn(ExperimentalMcpApi::class) private fun selectServerResultDeserializer(element: JsonElement): DeserializationStrategy? { val jsonObject = element.jsonObject + val isDiscoverResult = + "supportedVersions" in jsonObject && + "capabilities" in jsonObject && + "ttlMs" in jsonObject && + "cacheScope" in jsonObject return when { + isDiscoverResult -> DiscoverResult.serializer() "protocolVersion" in jsonObject && "capabilities" in jsonObject -> InitializeResult.serializer() "completion" in jsonObject -> CompleteResult.serializer() "tools" in jsonObject -> ListToolsResult.serializer() diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/DiscoveryTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/DiscoveryTest.kt new file mode 100644 index 0000000..691af45 --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/DiscoveryTest.kt @@ -0,0 +1,324 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import io.kotest.assertions.json.shouldEqualJson +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonObject +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFails +import kotlin.test.assertFailsWith +import kotlin.test.assertIs +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue + +@OptIn(ExperimentalMcpApi::class) +class DiscoveryTest { + + @Test + fun `should decode server discover request from its wire shape`() { + val request = McpJson.decodeFromString( + """ + { + "method": "server/discover", + "params": { + "_meta": { + "io.modelcontextprotocol/protocolVersion": "2026-07-28", + "io.modelcontextprotocol/clientInfo": { + "name": "test-client", + "version": "1.0.0" + }, + "io.modelcontextprotocol/clientCapabilities": {} + } + } + } + """.trimIndent(), + ) + + val discover = assertIs(request) + assertEquals(Method.Defined.ServerDiscover, discover.method) + assertEquals("2026-07-28", discover.meta.protocolVersion) + assertEquals("test-client", discover.meta.clientInfo?.name) + assertEquals(ClientCapabilities(), discover.meta.clientCapabilities) + } + + @Test + fun `should decode discovery request without optional client info`() { + val request = McpJson.decodeFromString( + """ + { + "method": "server/discover", + "params": { + "_meta": { + "io.modelcontextprotocol/protocolVersion": "2026-07-28", + "io.modelcontextprotocol/clientCapabilities": {}, + "com.example/traceId": "trace-123" + } + } + } + """.trimIndent(), + ) + + val discover = assertIs(request) + assertEquals("2026-07-28", discover.meta.protocolVersion) + assertEquals(ClientCapabilities(), discover.meta.clientCapabilities) + assertNull(discover.meta.clientInfo) + assertNotNull(discover.meta["com.example/traceId"]) + McpJson.encodeToString(discover) shouldEqualJson """ + { + "method": "server/discover", + "params": { + "_meta": { + "io.modelcontextprotocol/protocolVersion": "2026-07-28", + "io.modelcontextprotocol/clientCapabilities": {}, + "com.example/traceId": "trace-123" + } + } + } + """.trimIndent() + } + + @Test + fun `should reject discovery requests without required request metadata`() { + val invalidParams = listOf( + "{}" to "_meta", + """{"_meta": {}}""" to RequestMetaKeys.PROTOCOL_VERSION, + """ + { + "_meta": { + "${RequestMetaKeys.PROTOCOL_VERSION}": "2026-07-28", + "${RequestMetaKeys.CLIENT_INFO}": { + "name": "test-client", + "version": "1.0.0" + } + } + } + """.trimIndent() to RequestMetaKeys.CLIENT_CAPABILITIES, + ) + + invalidParams.forEach { (params, missingField) -> + val failure = assertFails { + McpJson.decodeFromString( + """{"method": "server/discover", "params": $params}""", + ) + } + + assertTrue( + failure.message.orEmpty().contains(missingField), + "Expected failure to identify missing field $missingField, but was: $failure", + ) + } + } + + @Test + fun `should serialize every required discovery result field`() { + val result = DiscoverResult( + supportedVersions = listOf("2026-07-28"), + capabilities = ServerCapabilities( + tools = ServerCapabilities.Tools(listChanged = true), + ), + instructions = "Use tools intentionally.", + ) + + McpJson.encodeToString(result) shouldEqualJson """ + { + "supportedVersions": ["2026-07-28"], + "capabilities": { + "tools": {"listChanged": true} + }, + "instructions": "Use tools intentionally.", + "resultType": "complete", + "ttlMs": 0, + "cacheScope": "private" + } + """.trimIndent() + } + + @Test + fun `should encode optional server info in result metadata`() { + val result = DiscoverResult( + supportedVersions = listOf("2026-07-28"), + capabilities = ServerCapabilities(), + meta = Json.parseToJsonElement( + """ + { + "io.modelcontextprotocol/serverInfo": { + "name": "test-server", + "version": "2.0.0" + }, + "com.example/source": "edge" + } + """.trimIndent(), + ).jsonObject, + ) + + McpJson.encodeToString(result) shouldEqualJson """ + { + "supportedVersions": ["2026-07-28"], + "capabilities": {}, + "resultType": "complete", + "ttlMs": 0, + "cacheScope": "private", + "_meta": { + "io.modelcontextprotocol/serverInfo": { + "name": "test-server", + "version": "2.0.0" + }, + "com.example/source": "edge" + } + } + """.trimIndent() + assertEquals( + Json.parseToJsonElement("""{"name":"test-server","version":"2.0.0"}"""), + result.meta?.get("io.modelcontextprotocol/serverInfo"), + ) + } + + @Test + fun `should encode required defaults independently of McpJson`() { + val result = DiscoverResult( + supportedVersions = listOf("2026-07-28"), + capabilities = ServerCapabilities(), + ) + + Json.encodeToString(result) shouldEqualJson """ + { + "supportedVersions": ["2026-07-28"], + "capabilities": {}, + "resultType": "complete", + "ttlMs": 0, + "cacheScope": "private" + } + """.trimIndent() + } + + @Test + fun `should reject discovery results without required cache fields`() { + val invalidResults = listOf( + """ + { + "supportedVersions": ["2026-07-28"], + "capabilities": {}, + "cacheScope": "private" + } + """.trimIndent(), + """ + { + "supportedVersions": ["2026-07-28"], + "capabilities": {}, + "ttlMs": 0 + } + """.trimIndent(), + ) + + invalidResults.forEach { wire -> + assertFails { McpJson.decodeFromString(wire) } + assertFails { McpJson.decodeFromString(wire) } + } + } + + @Test + fun `should use complete when a discovery result omits resultType`() { + val result = McpJson.decodeFromString( + """ + { + "supportedVersions": ["2026-07-28"], + "capabilities": {}, + "ttlMs": 0, + "cacheScope": "private" + } + """.trimIndent(), + ) + + assertEquals(COMPLETE_RESULT_TYPE, assertIs(result).resultType) + } + + @Test + fun `should deserialize discovery results polymorphically`() { + val wire = + """ + { + "supportedVersions": ["2026-07-28"], + "capabilities": {}, + "resultType": "complete", + "ttlMs": 250, + "cacheScope": "public", + "_meta": { + "io.modelcontextprotocol/serverInfo": { + "name": "server", + "version": "1" + }, + "com.example/source": "edge" + } + } + """.trimIndent() + val result = McpJson.decodeFromString(wire) + + val discover = assertIs(result) + assertEquals(250, discover.ttlMs) + assertEquals(CacheScope.Public, discover.cacheScope) + assertEquals( + Json.parseToJsonElement("""{"name":"server","version":"1"}"""), + discover.meta?.get("io.modelcontextprotocol/serverInfo"), + ) + assertNotNull(discover.meta?.get("com.example/source")) + McpJson.encodeToString(discover) shouldEqualJson wire + } + + @Test + fun `should not treat supportedVersions alone as a discovery discriminator`() { + val result = McpJson.decodeFromString( + """ + { + "tools": [], + "supportedVersions": ["extension-value"] + } + """.trimIndent(), + ) + + assertIs(result) + } + + @Test + fun `should not treat an initialize extension as a discovery result`() { + val result = McpJson.decodeFromString( + """ + { + "protocolVersion": "2025-06-18", + "capabilities": {}, + "serverInfo": { + "name": "test-server", + "version": "1.0.0" + }, + "supportedVersions": ["extension-value"] + } + """.trimIndent(), + ) + + assertIs(result) + } + + @Test + fun `should reject a negative discovery ttl`() { + assertFailsWith { + DiscoverResult( + supportedVersions = listOf("2026-07-28"), + capabilities = ServerCapabilities(), + ttlMs = -1, + ) + } + } + + @Test + fun `unsupported version data should round trip without losing versions`() { + val data = UnsupportedProtocolVersionData( + supported = listOf("2026-07-28", "2025-11-25"), + requested = "2099-01-01", + ) + + val encoded = McpJson.encodeToString(data) + assertEquals(data, McpJson.decodeFromString(encoded)) + assertEquals(-32022, RPCError.ErrorCode.UNSUPPORTED_PROTOCOL_VERSION) + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/RequestTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/RequestTest.kt index 1d6da19..6087b20 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/RequestTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/RequestTest.kt @@ -2,20 +2,120 @@ package io.modelcontextprotocol.kotlin.sdk.types import io.kotest.assertions.json.shouldEqualJson import io.kotest.matchers.shouldBe +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi import io.modelcontextprotocol.kotlin.test.utils.verifyDeserialization import io.modelcontextprotocol.kotlin.test.utils.verifySerialization +import kotlinx.serialization.SerializationException +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.double import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.put import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFailsWith import kotlin.test.assertIs import kotlin.test.assertNotNull import kotlin.test.assertNull +import kotlin.test.assertTrue class RequestTest { + @OptIn(ExperimentalMcpApi::class) + @Suppress("DEPRECATION") + @Test + fun `should decode typed request metadata without discarding extensions`() { + val request = McpJson.decodeFromString( + """ + { + "method": "tools/list", + "params": { + "_meta": { + "io.modelcontextprotocol/protocolVersion": "2026-07-28", + "io.modelcontextprotocol/clientInfo": { + "name": "wire-client", + "version": "1.2.3" + }, + "io.modelcontextprotocol/clientCapabilities": { + "sampling": {}, + "com.example/custom-capability": {"enabled": true} + }, + "io.modelcontextprotocol/logLevel": "warning", + "com.example/traceId": "trace-123" + } + } + } + """.trimIndent(), + ) + + val listTools = assertIs(request) + val meta = assertNotNull(listTools.params?.meta) + meta.protocolVersion shouldBe "2026-07-28" + meta.clientInfo shouldBe Implementation(name = "wire-client", version = "1.2.3") + assertNotNull(meta.clientCapabilities?.sampling) + meta.logLevel shouldBe LoggingLevel.Warning + meta["com.example/traceId"]?.jsonPrimitive?.content shouldBe "trace-123" + assertNotNull(meta.json[RequestMetaKeys.CLIENT_CAPABILITIES]?.let { it as? JsonObject }) + } + + @OptIn(ExperimentalMcpApi::class) + @Test + fun `empty client capabilities should mean no optional capabilities`() { + val json = Json.parseToJsonElement( + """{"${RequestMetaKeys.CLIENT_CAPABILITIES}": {}}""", + ) as JsonObject + + RequestMeta(json).clientCapabilities shouldBe ClientCapabilities() + } + + @OptIn(ExperimentalMcpApi::class) + @Suppress("DEPRECATION") + @Test + fun `typed request metadata should reject malformed fields`() { + val malformedValues = listOf( + RequestMetaKeys.PROTOCOL_VERSION to "{}", + RequestMetaKeys.PROTOCOL_VERSION to "null", + RequestMetaKeys.CLIENT_INFO to "\"not-an-implementation\"", + RequestMetaKeys.CLIENT_INFO to "{\"name\":\"missing-version\"}", + RequestMetaKeys.CLIENT_INFO to "null", + RequestMetaKeys.CLIENT_CAPABILITIES to "\"not-capabilities\"", + RequestMetaKeys.CLIENT_CAPABILITIES to "[]", + RequestMetaKeys.CLIENT_CAPABILITIES to "null", + RequestMetaKeys.LOG_LEVEL to "\"verbose\"", + RequestMetaKeys.LOG_LEVEL to "7", + RequestMetaKeys.LOG_LEVEL to "null", + ) + + malformedValues.forEach { (key, value) -> + val json = Json.parseToJsonElement("""{"$key":$value}""") as JsonObject + val meta = RequestMeta(json) + + val failure = assertFailsWith { + when (key) { + RequestMetaKeys.PROTOCOL_VERSION -> meta.protocolVersion + RequestMetaKeys.CLIENT_INFO -> meta.clientInfo + RequestMetaKeys.CLIENT_CAPABILITIES -> meta.clientCapabilities + else -> meta.logLevel + } + } + assertTrue(failure.message.orEmpty().contains(key)) + } + } + + @OptIn(ExperimentalMcpApi::class) + @Test + fun `legacy requests should not gain request scoped metadata`() { + val request = ListToolsRequest() + + McpJson.encodeToString(request) shouldEqualJson """ + { + "method": "tools/list" + } + """.trimIndent() + assertNull(request.params?.meta?.protocolVersion) + } + @Test fun `should expose progress token from RequestMeta`() { val stringTokenMeta = RequestMeta(buildJsonObject { put("progressToken", "sync-1") }) From 04a72353e15cf865440208797a1209139889c2f1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 12:02:22 +0200 Subject: [PATCH 410/449] chore(deps): bump @hono/node-server from 1.19.13 to 2.1.0 in /integration-test/src/jvmTest/typescript (#939) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [@hono/node-server](https://github.com/honojs/node-server) from 1.19.13 to 2.1.0.
        Release notes

        Sourced from @​hono/node-server's releases.

        v2.1.0

        What's Changed

        New Contributors

        Full Changelog: https://github.com/honojs/node-server/compare/v2.0.12...v2.1.0

        v2.0.12

        What's Changed

        Full Changelog: https://github.com/honojs/node-server/compare/v2.0.11...v2.0.12

        v2.0.11

        What's Changed

        Full Changelog: https://github.com/honojs/node-server/compare/v2.0.10...v2.0.11

        v2.0.10

        Security fixes

        This release includes a fix for the following security issue:

        Unauthenticated memory-leak DoS via aborted WebSocket handshake

        Affects: upgradeWebSocket. A WebSocket upgrade request with a missing or malformed Sec-WebSocket-Key header leaked the request's IncomingMessage and left a promise pending, even though no connection was established. Since the route is reachable pre-handshake without authentication, an attacker could flood it to gradually exhaust memory. GHSA-9mqv-5hh9-4cgg


        Users of upgradeWebSocket are encouraged to upgrade to this version.

        v2.0.9

        What's Changed

        New Contributors

        ... (truncated)

        Commits
        Maintainer changes

        This version was pushed to npm by GitHub Actions, a new releaser for @​hono/node-server since your current version.


        [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@hono/node-server&package-manager=npm_and_yarn&previous-version=1.19.13&new-version=2.1.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
        Dependabot commands and options
        You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/modelcontextprotocol/kotlin-sdk/network/alerts).
        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- integration-test/src/jvmTest/typescript/package-lock.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index 26c6e7f..52ba6fa 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -464,12 +464,12 @@ } }, "node_modules/@hono/node-server": { - "version": "1.19.13", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.13.tgz", - "integrity": "sha512-TsQLe4i2gvoTtrHje625ngThGBySOgSK3Xo2XRYOdqGN1teR8+I7vchQC46uLJi8OF62YTYA3AhSpumtkhsaKQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.0.tgz", + "integrity": "sha512-XovyyCCnBzW+zKu+z/zq8hwNs4KOR5rEMAOxo2f40Q5xoOI37IMm6MIg2COOUtUApo0i6850MTBKH2u4QLGIqg==", "license": "MIT", "engines": { - "node": ">=18.14.1" + "node": ">=20" }, "peerDependencies": { "hono": "^4" From e34cb2244c5807d83b5f0caeb46ea9f9c894ad3b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 12:26:30 +0200 Subject: [PATCH 411/449] chore(deps): bump hono from 4.12.27 to 4.13.1 in /integration-test/src/jvmTest/typescript (#940) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [hono](https://github.com/honojs/hono) from 4.12.27 to 4.13.1.
        Release notes

        Sourced from hono's releases.

        v4.13.1

        What's Changed

        New Contributors

        Full Changelog: https://github.com/honojs/hono/compare/v4.13.0...v4.13.1

        v4.13.0

        Hono v4.13.0 is now available!

        The highlight of this release is performance: a batch of low-level optimizations makes the core request/response path significantly faster — up to 1.25x on common routes in our benchmark. This release also adds first-class support for the HTTP QUERY method, defined in RFC 10008, a new Method Not Allowed middleware, and more.

        Performance improvements

        This release includes a series of small optimizations: skipping unnecessary Headers allocations, replacing regex tests with indexOf, allocating internal state lazily, and more.

        Here is benchmarks/fetch comparing v4.12 and v4.13 (ROUNDS=5 ./compare.sh, Bun 1.4.0, Apple Silicon — each measurement runs in a fresh process, and the variant order is reversed every round to avoid warm-up bias):

        Benchmark v4.12 v4.13 Speedup
        ping — GET / 165.83 ns 163.99 ns 1.01x
        query — GET /id/1?name=bun 674.40 ns 616.99 ns 1.09x
        json — GET /user 528.99 ns 422.44 ns 1.25x
        body — POST /json 1.16 µs 1.00 µs 1.15x

        The individual changes:

        In addition, the RegExpRouter rewrite described below makes route registration plus the first match roughly 20% faster.

        Thanks @​kibertoad for the contributions!

        First-class QUERY method support

        The QUERY method — a safe, idempotent method that carries a request body — is now a first-class citizen in Hono. You can define QUERY handlers with app.query():

        const app = new Hono()
        

        </tr></table>

        ... (truncated)

        Commits
        • cf78528 4.13.1
        • f6aa913 fix(etag): skip unsafe methods or error responses on non-* case (#5196)
        • cd31bc1 fix(utils/stream): re-acquire writer lock when pipe() throws (#4988)
        • 569b419 fix(trie-router): count every slash a pattern consumes (#5189)
        • 192768f 4.13.0
        • b0c2d90 Merge pull request #5154 from honojs/next
        • 8f07028 fix(compress): set Vary: Accept-Encoding on negotiated responses (#5137)
        • 8a0b18f feat(reg-exp-router): throw UnsupportedPathError during route registration (#...
        • 3feb355 fix(jsx): allow a function component to return an array (#5179)
        • 5d911d2 feat(utils/headers): add HTTP fields newly registered with IANA (#5153)
        • Additional commits viewable in compare view

        Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- integration-test/src/jvmTest/typescript/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index 52ba6fa..b014f88 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -1538,9 +1538,9 @@ } }, "node_modules/hono": { - "version": "4.12.27", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.27.tgz", - "integrity": "sha512-1yrb/+w6HWQJrUCLkJ2IF5jNIPvvFkblV5RNOYl6bV+OA6p9GLcMpHFFGTosSvHvcAUibuUukRqhlYI4z32C7Q==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.1.tgz", + "integrity": "sha512-kdJoFVv2xmayw6cY09H7AbMJMt8Jn5jdlEdXsP7AGBdF2DIptVlKlOLKXP41yPip4/a3yQPv9gVcJYI8YY04dw==", "license": "MIT", "engines": { "node": ">=16.9.0" From 8ceb779dd45e4a5705084fe34772231d89176c10 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 13:04:29 +0200 Subject: [PATCH 412/449] chore(deps): bump the ktor group across 1 directory with 15 updates (#941) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the ktor group with 15 updates in the / directory: | Package | From | To | | --- | --- | --- | | [io.ktor:ktor-client-apache5](https://github.com/ktorio/ktor) | `3.5.1` | `3.5.2` | | [io.ktor:ktor-client-auth](https://github.com/ktorio/ktor) | `3.5.1` | `3.5.2` | | [io.ktor:ktor-client-core](https://github.com/ktorio/ktor) | `3.5.1` | `3.5.2` | | [io.ktor:ktor-client-logging](https://github.com/ktorio/ktor) | `3.5.1` | `3.5.2` | | [io.ktor:ktor-server-content-negotiation](https://github.com/ktorio/ktor) | `3.5.1` | `3.5.2` | | [io.ktor:ktor-client-content-negotiation](https://github.com/ktorio/ktor) | `3.5.1` | `3.5.2` | | [io.ktor:ktor-serialization-kotlinx-json](https://github.com/ktorio/ktor) | `3.5.1` | `3.5.2` | | [io.ktor:ktor-server-auth](https://github.com/ktorio/ktor) | `3.5.1` | `3.5.2` | | [io.ktor:ktor-server-core](https://github.com/ktorio/ktor) | `3.5.1` | `3.5.2` | | [io.ktor:ktor-server-sse](https://github.com/ktorio/ktor) | `3.5.1` | `3.5.2` | | [io.ktor:ktor-server-websockets](https://github.com/ktorio/ktor) | `3.5.1` | `3.5.2` | | [io.ktor:ktor-client-mock](https://github.com/ktorio/ktor) | `3.5.1` | `3.5.2` | | [io.ktor:ktor-server-test-host](https://github.com/ktorio/ktor) | `3.5.1` | `3.5.2` | | [io.ktor:ktor-client-cio](https://github.com/ktorio/ktor) | `3.5.1` | `3.5.2` | | [io.ktor:ktor-server-cio](https://github.com/ktorio/ktor) | `3.5.1` | `3.5.2` | Updates `io.ktor:ktor-client-apache5` from 3.5.1 to 3.5.2
        Release notes

        Sourced from io.ktor:ktor-client-apache5's releases.

        3.5.2

        Published 31 July 2026

        Improvements

        • KTOR-8032 RateLimit: Allow limit requests based on authentication result
        • KTOR-9719 ApplicationCall.isStaticContent returns false when called from plugin interceptors
        • KTOR-8450 ContentNegotiation: it doesn't handle content type suffixes
        • KTOR-7892 SwaggerUI: Missing oauth2-redirect.html support in the plugin
        • KTOR-9759 Reject whitespace in URL hosts during parsing
        • KTOR-9681 Update kotlinx-io to 0.9.1
        • KTOR-9743 ByteReadChannel.readLineStrict: Misleading definition of limit parameter in KDocs
        • KTOR-9504 Improve KDoc for EmbeddedServer.addShutdownHook

        Bugfixes

        • KTOR-9741 CIO: request-handler coroutine leak on half-closed idle connection after ≥1MB response
        • KTOR-7661 Maven + YAML config file does not work
        • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
        • KTOR-9704 UTF-8 encoding in ktor-io bypasses JVM String.getBytes intrinsics, slowing every text response by 5-7x
        • KTOR-9588 SSE client (CIO engine) sends spurious Content-Length: 0 on body-less GET (RFC 9110 § 8.6 violation; rejected by AWS ELB)
        • KTOR-9679 ByteReadChannel.readLine corrupts multi-byte UTF-8 by splitting a character at a buffer boundary
        • KTOR-9767 ByteReadChannel.readLineTo: Return read bytes count instead of decoded characters count
        • KTOR-8992 HoconConfigLoader is not loaded when ktor-server-config-yaml on the classpath
        • KTOR-9698 OpenAPI: schema for kotlinx.serialization sealed types omits discriminator property in variant schemas
        • KTOR-9690 Netty: in-flight requests are aborted on stop(), ignoring shutdown grace period
        • KTOR-8903 Netty: Close channel connection on errors properly
        • KTOR-9707 SaveBody performance regression
        • KTOR-9703 Inefficient text reading function in DefaultTransform
        • KTOR-9711 Netty HTTP/2: a single client-canceled stream (RST_STREAM) breaks response flushing for the whole connection
        • KTOR-9688 RateLimit: No access to principal when wrapped with authentication since 3.5.1
        • KTOR-9677 Auto-reloading: server is reloaded only once since 3.5.0
        • KTOR-9683 Sockets: Canceling a SelectorManager job doesn't close it properly
        • KTOR-9671 Darwin: Semicolons in URL path are sanitized
        Changelog

        Sourced from io.ktor:ktor-client-apache5's changelog.

        3.5.2

        Published 31 July 2026

        Improvements

        • KTOR-8032 RateLimit: Allow limit requests based on authentication result
        • KTOR-9719 ApplicationCall.isStaticContent returns false when called from plugin interceptors
        • KTOR-8450 ContentNegotiation: it doesn't handle content type suffixes
        • KTOR-7892 SwaggerUI: Missing oauth2-redirect.html support in the plugin
        • KTOR-9759 Reject whitespace in URL hosts during parsing
        • KTOR-9681 Update kotlinx-io to 0.9.1
        • KTOR-9743 ByteReadChannel.readLineStrict: Misleading definition of limit parameter in KDocs
        • KTOR-9504 Improve KDoc for EmbeddedServer.addShutdownHook

        Bugfixes

        • KTOR-9741 CIO: request-handler coroutine leak on half-closed idle connection after ≥1MB response
        • KTOR-7661 Maven + YAML config file does not work
        • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
        • KTOR-9704 UTF-8 encoding in ktor-io bypasses JVM String.getBytes intrinsics, slowing every text response by 5-7x
        • KTOR-9588 SSE client (CIO engine) sends spurious Content-Length: 0 on body-less GET (RFC 9110 § 8.6 violation; rejected by AWS ELB)
        • KTOR-9679 ByteReadChannel.readLine corrupts multi-byte UTF-8 by splitting a character at a buffer boundary
        • KTOR-9767 ByteReadChannel.readLineTo: Return read bytes count instead of decoded characters count
        • KTOR-8992 HoconConfigLoader is not loaded when ktor-server-config-yaml on the classpath
        • KTOR-9698 OpenAPI: schema for kotlinx.serialization sealed types omits discriminator property in variant schemas
        • KTOR-9690 Netty: in-flight requests are aborted on stop(), ignoring shutdown grace period
        • KTOR-8903 Netty: Close channel connection on errors properly
        • KTOR-9707 SaveBody performance regression
        • KTOR-9703 Inefficient text reading function in DefaultTransform
        • KTOR-9711 Netty HTTP/2: a single client-canceled stream (RST_STREAM) breaks response flushing for the whole connection
        • KTOR-9688 RateLimit: No access to principal when wrapped with authentication since 3.5.1
        • KTOR-9677 Auto-reloading: server is reloaded only once since 3.5.0
        • KTOR-9683 Sockets: Canceling a SelectorManager job doesn't close it properly
        • KTOR-9671 Darwin: Semicolons in URL path are sanitized
        Commits
        • 01c469a Release 3.5.2 (#5781)
        • c965a0c KTOR-9741 Prevent CIO request handler leak (#5783)
        • 74df458 chore: fix some comments to improve readability
        • 2672567 [KTOR-9704] Optimized encoding flow for UTF-8 (#5743)
        • d5680ed KTOR-5391: Document ECDSA certificate limitation for client authentication (#...
        • 9b0f356 something::javaClass.name is not the name of the class of something but `"j...
        • 58256cd KTOR-9743 Clarify readLine documentation
        • 088bd60 KTOR-9679 Fix regressions caused by adding buffering
        • 167176a KTOR-9588 Do not send Content-Length: 0 for body-less GET requests (#5620)
        • 7fb0844 KTOR-9585 Fix schema title trimming
        • Additional commits viewable in compare view

        Updates `io.ktor:ktor-client-auth` from 3.5.1 to 3.5.2
        Release notes

        Sourced from io.ktor:ktor-client-auth's releases.

        3.5.2

        Published 31 July 2026

        Improvements

        • KTOR-8032 RateLimit: Allow limit requests based on authentication result
        • KTOR-9719 ApplicationCall.isStaticContent returns false when called from plugin interceptors
        • KTOR-8450 ContentNegotiation: it doesn't handle content type suffixes
        • KTOR-7892 SwaggerUI: Missing oauth2-redirect.html support in the plugin
        • KTOR-9759 Reject whitespace in URL hosts during parsing
        • KTOR-9681 Update kotlinx-io to 0.9.1
        • KTOR-9743 ByteReadChannel.readLineStrict: Misleading definition of limit parameter in KDocs
        • KTOR-9504 Improve KDoc for EmbeddedServer.addShutdownHook

        Bugfixes

        • KTOR-9741 CIO: request-handler coroutine leak on half-closed idle connection after ≥1MB response
        • KTOR-7661 Maven + YAML config file does not work
        • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
        • KTOR-9704 UTF-8 encoding in ktor-io bypasses JVM String.getBytes intrinsics, slowing every text response by 5-7x
        • KTOR-9588 SSE client (CIO engine) sends spurious Content-Length: 0 on body-less GET (RFC 9110 § 8.6 violation; rejected by AWS ELB)
        • KTOR-9679 ByteReadChannel.readLine corrupts multi-byte UTF-8 by splitting a character at a buffer boundary
        • KTOR-9767 ByteReadChannel.readLineTo: Return read bytes count instead of decoded characters count
        • KTOR-8992 HoconConfigLoader is not loaded when ktor-server-config-yaml on the classpath
        • KTOR-9698 OpenAPI: schema for kotlinx.serialization sealed types omits discriminator property in variant schemas
        • KTOR-9690 Netty: in-flight requests are aborted on stop(), ignoring shutdown grace period
        • KTOR-8903 Netty: Close channel connection on errors properly
        • KTOR-9707 SaveBody performance regression
        • KTOR-9703 Inefficient text reading function in DefaultTransform
        • KTOR-9711 Netty HTTP/2: a single client-canceled stream (RST_STREAM) breaks response flushing for the whole connection
        • KTOR-9688 RateLimit: No access to principal when wrapped with authentication since 3.5.1
        • KTOR-9677 Auto-reloading: server is reloaded only once since 3.5.0
        • KTOR-9683 Sockets: Canceling a SelectorManager job doesn't close it properly
        • KTOR-9671 Darwin: Semicolons in URL path are sanitized
        Changelog

        Sourced from io.ktor:ktor-client-auth's changelog.

        3.5.2

        Published 31 July 2026

        Improvements

        • KTOR-8032 RateLimit: Allow limit requests based on authentication result
        • KTOR-9719 ApplicationCall.isStaticContent returns false when called from plugin interceptors
        • KTOR-8450 ContentNegotiation: it doesn't handle content type suffixes
        • KTOR-7892 SwaggerUI: Missing oauth2-redirect.html support in the plugin
        • KTOR-9759 Reject whitespace in URL hosts during parsing
        • KTOR-9681 Update kotlinx-io to 0.9.1
        • KTOR-9743 ByteReadChannel.readLineStrict: Misleading definition of limit parameter in KDocs
        • KTOR-9504 Improve KDoc for EmbeddedServer.addShutdownHook

        Bugfixes

        • KTOR-9741 CIO: request-handler coroutine leak on half-closed idle connection after ≥1MB response
        • KTOR-7661 Maven + YAML config file does not work
        • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
        • KTOR-9704 UTF-8 encoding in ktor-io bypasses JVM String.getBytes intrinsics, slowing every text response by 5-7x
        • KTOR-9588 SSE client (CIO engine) sends spurious Content-Length: 0 on body-less GET (RFC 9110 § 8.6 violation; rejected by AWS ELB)
        • KTOR-9679 ByteReadChannel.readLine corrupts multi-byte UTF-8 by splitting a character at a buffer boundary
        • KTOR-9767 ByteReadChannel.readLineTo: Return read bytes count instead of decoded characters count
        • KTOR-8992 HoconConfigLoader is not loaded when ktor-server-config-yaml on the classpath
        • KTOR-9698 OpenAPI: schema for kotlinx.serialization sealed types omits discriminator property in variant schemas
        • KTOR-9690 Netty: in-flight requests are aborted on stop(), ignoring shutdown grace period
        • KTOR-8903 Netty: Close channel connection on errors properly
        • KTOR-9707 SaveBody performance regression
        • KTOR-9703 Inefficient text reading function in DefaultTransform
        • KTOR-9711 Netty HTTP/2: a single client-canceled stream (RST_STREAM) breaks response flushing for the whole connection
        • KTOR-9688 RateLimit: No access to principal when wrapped with authentication since 3.5.1
        • KTOR-9677 Auto-reloading: server is reloaded only once since 3.5.0
        • KTOR-9683 Sockets: Canceling a SelectorManager job doesn't close it properly
        • KTOR-9671 Darwin: Semicolons in URL path are sanitized
        Commits
        • 01c469a Release 3.5.2 (#5781)
        • c965a0c KTOR-9741 Prevent CIO request handler leak (#5783)
        • 74df458 chore: fix some comments to improve readability
        • 2672567 [KTOR-9704] Optimized encoding flow for UTF-8 (#5743)
        • d5680ed KTOR-5391: Document ECDSA certificate limitation for client authentication (#...
        • 9b0f356 something::javaClass.name is not the name of the class of something but `"j...
        • 58256cd KTOR-9743 Clarify readLine documentation
        • 088bd60 KTOR-9679 Fix regressions caused by adding buffering
        • 167176a KTOR-9588 Do not send Content-Length: 0 for body-less GET requests (#5620)
        • 7fb0844 KTOR-9585 Fix schema title trimming
        • Additional commits viewable in compare view

        Updates `io.ktor:ktor-client-core` from 3.5.1 to 3.5.2
        Release notes

        Sourced from io.ktor:ktor-client-core's releases.

        3.5.2

        Published 31 July 2026

        Improvements

        • KTOR-8032 RateLimit: Allow limit requests based on authentication result
        • KTOR-9719 ApplicationCall.isStaticContent returns false when called from plugin interceptors
        • KTOR-8450 ContentNegotiation: it doesn't handle content type suffixes
        • KTOR-7892 SwaggerUI: Missing oauth2-redirect.html support in the plugin
        • KTOR-9759 Reject whitespace in URL hosts during parsing
        • KTOR-9681 Update kotlinx-io to 0.9.1
        • KTOR-9743 ByteReadChannel.readLineStrict: Misleading definition of limit parameter in KDocs
        • KTOR-9504 Improve KDoc for EmbeddedServer.addShutdownHook

        Bugfixes

        • KTOR-9741 CIO: request-handler coroutine leak on half-closed idle connection after ≥1MB response
        • KTOR-7661 Maven + YAML config file does not work
        • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
        • KTOR-9704 UTF-8 encoding in ktor-io bypasses JVM String.getBytes intrinsics, slowing every text response by 5-7x
        • KTOR-9588 SSE client (CIO engine) sends spurious Content-Length: 0 on body-less GET (RFC 9110 § 8.6 violation; rejected by AWS ELB)
        • KTOR-9679 ByteReadChannel.readLine corrupts multi-byte UTF-8 by splitting a character at a buffer boundary
        • KTOR-9767 ByteReadChannel.readLineTo: Return read bytes count instead of decoded characters count
        • KTOR-8992 HoconConfigLoader is not loaded when ktor-server-config-yaml on the classpath
        • KTOR-9698 OpenAPI: schema for kotlinx.serialization sealed types omits discriminator property in variant schemas
        • KTOR-9690 Netty: in-flight requests are aborted on stop(), ignoring shutdown grace period
        • KTOR-8903 Netty: Close channel connection on errors properly
        • KTOR-9707 SaveBody performance regression
        • KTOR-9703 Inefficient text reading function in DefaultTransform
        • KTOR-9711 Netty HTTP/2: a single client-canceled stream (RST_STREAM) breaks response flushing for the whole connection
        • KTOR-9688 RateLimit: No access to principal when wrapped with authentication since 3.5.1
        • KTOR-9677 Auto-reloading: server is reloaded only once since 3.5.0
        • KTOR-9683 Sockets: Canceling a SelectorManager job doesn't close it properly
        • KTOR-9671 Darwin: Semicolons in URL path are sanitized
        Changelog

        Sourced from io.ktor:ktor-client-core's changelog.

        3.5.2

        Published 31 July 2026

        Improvements

        • KTOR-8032 RateLimit: Allow limit requests based on authentication result
        • KTOR-9719 ApplicationCall.isStaticContent returns false when called from plugin interceptors
        • KTOR-8450 ContentNegotiation: it doesn't handle content type suffixes
        • KTOR-7892 SwaggerUI: Missing oauth2-redirect.html support in the plugin
        • KTOR-9759 Reject whitespace in URL hosts during parsing
        • KTOR-9681 Update kotlinx-io to 0.9.1
        • KTOR-9743 ByteReadChannel.readLineStrict: Misleading definition of limit parameter in KDocs
        • KTOR-9504 Improve KDoc for EmbeddedServer.addShutdownHook

        Bugfixes

        • KTOR-9741 CIO: request-handler coroutine leak on half-closed idle connection after ≥1MB response
        • KTOR-7661 Maven + YAML config file does not work
        • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
        • KTOR-9704 UTF-8 encoding in ktor-io bypasses JVM String.getBytes intrinsics, slowing every text response by 5-7x
        • KTOR-9588 SSE client (CIO engine) sends spurious Content-Length: 0 on body-less GET (RFC 9110 § 8.6 violation; rejected by AWS ELB)
        • KTOR-9679 ByteReadChannel.readLine corrupts multi-byte UTF-8 by splitting a character at a buffer boundary
        • KTOR-9767 ByteReadChannel.readLineTo: Return read bytes count instead of decoded characters count
        • KTOR-8992 HoconConfigLoader is not loaded when ktor-server-config-yaml on the classpath
        • KTOR-9698 OpenAPI: schema for kotlinx.serialization sealed types omits discriminator property in variant schemas
        • KTOR-9690 Netty: in-flight requests are aborted on stop(), ignoring shutdown grace period
        • KTOR-8903 Netty: Close channel connection on errors properly
        • KTOR-9707 SaveBody performance regression
        • KTOR-9703 Inefficient text reading function in DefaultTransform
        • KTOR-9711 Netty HTTP/2: a single client-canceled stream (RST_STREAM) breaks response flushing for the whole connection
        • KTOR-9688 RateLimit: No access to principal when wrapped with authentication since 3.5.1
        • KTOR-9677 Auto-reloading: server is reloaded only once since 3.5.0
        • KTOR-9683 Sockets: Canceling a SelectorManager job doesn't close it properly
        • KTOR-9671 Darwin: Semicolons in URL path are sanitized
        Commits
        • 01c469a Release 3.5.2 (#5781)
        • c965a0c KTOR-9741 Prevent CIO request handler leak (#5783)
        • 74df458 chore: fix some comments to improve readability
        • 2672567 [KTOR-9704] Optimized encoding flow for UTF-8 (#5743)
        • d5680ed KTOR-5391: Document ECDSA certificate limitation for client authentication (#...
        • 9b0f356 something::javaClass.name is not the name of the class of something but `"j...
        • 58256cd KTOR-9743 Clarify readLine documentation
        • 088bd60 KTOR-9679 Fix regressions caused by adding buffering
        • 167176a KTOR-9588 Do not send Content-Length: 0 for body-less GET requests (#5620)
        • 7fb0844 KTOR-9585 Fix schema title trimming
        • Additional commits viewable in compare view

        Updates `io.ktor:ktor-client-logging` from 3.5.1 to 3.5.2
        Release notes

        Sourced from io.ktor:ktor-client-logging's releases.

        3.5.2

        Published 31 July 2026

        Improvements

        • KTOR-8032 RateLimit: Allow limit requests based on authentication result
        • KTOR-9719 ApplicationCall.isStaticContent returns false when called from plugin interceptors
        • KTOR-8450 ContentNegotiation: it doesn't handle content type suffixes
        • KTOR-7892 SwaggerUI: Missing oauth2-redirect.html support in the plugin
        • KTOR-9759 Reject whitespace in URL hosts during parsing
        • KTOR-9681 Update kotlinx-io to 0.9.1
        • KTOR-9743 ByteReadChannel.readLineStrict: Misleading definition of limit parameter in KDocs
        • KTOR-9504 Improve KDoc for EmbeddedServer.addShutdownHook

        Bugfixes

        • KTOR-9741 CIO: request-handler coroutine leak on half-closed idle connection after ≥1MB response
        • KTOR-7661 Maven + YAML config file does not work
        • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
        • KTOR-9704 UTF-8 encoding in ktor-io bypasses JVM String.getBytes intrinsics, slowing every text response by 5-7x
        • KTOR-9588 SSE client (CIO engine) sends spurious Content-Length: 0 on body-less GET (RFC 9110 § 8.6 violation; rejected by AWS ELB)
        • KTOR-9679 ByteReadChannel.readLine corrupts multi-byte UTF-8 by splitting a character at a buffer boundary
        • KTOR-9767 ByteReadChannel.readLineTo: Return read bytes count instead of decoded characters count
        • KTOR-8992 HoconConfigLoader is not loaded when ktor-server-config-yaml on the classpath
        • KTOR-9698 OpenAPI: schema for kotlinx.serialization sealed types omits discriminator property in variant schemas
        • KTOR-9690 Netty: in-flight requests are aborted on stop(), ignoring shutdown grace period
        • KTOR-8903 Netty: Close channel connection on errors properly
        • KTOR-9707 SaveBody performance regression
        • KTOR-9703 Inefficient text reading function in DefaultTransform
        • KTOR-9711 Netty HTTP/2: a single client-canceled stream (RST_STREAM) breaks response flushing for the whole connection
        • KTOR-9688 RateLimit: No access to principal when wrapped with authentication since 3.5.1
        • KTOR-9677 Auto-reloading: server is reloaded only once since 3.5.0
        • KTOR-9683 Sockets: Canceling a SelectorManager job doesn't close it properly
        • KTOR-9671 Darwin: Semicolons in URL path are sanitized
        Changelog

        Sourced from io.ktor:ktor-client-logging's changelog.

        3.5.2

        Published 31 July 2026

        Improvements

        • KTOR-8032 RateLimit: Allow limit requests based on authentication result
        • KTOR-9719 ApplicationCall.isStaticContent returns false when called from plugin interceptors
        • KTOR-8450 ContentNegotiation: it doesn't handle content type suffixes
        • KTOR-7892 SwaggerUI: Missing oauth2-redirect.html support in the plugin
        • KTOR-9759 Reject whitespace in URL hosts during parsing
        • KTOR-9681 Update kotlinx-io to 0.9.1
        • KTOR-9743 ByteReadChannel.readLineStrict: Misleading definition of limit parameter in KDocs
        • KTOR-9504 Improve KDoc for EmbeddedServer.addShutdownHook

        Bugfixes

        • KTOR-9741 CIO: request-handler coroutine leak on half-closed idle connection after ≥1MB response
        • KTOR-7661 Maven + YAML config file does not work
        • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
        • KTOR-9704 UTF-8 encoding in ktor-io bypasses JVM String.getBytes intrinsics, slowing every text response by 5-7x
        • KTOR-9588 SSE client (CIO engine) sends spurious Content-Length: 0 on body-less GET (RFC 9110 § 8.6 violation; rejected by AWS ELB)
        • KTOR-9679 ByteReadChannel.readLine corrupts multi-byte UTF-8 by splitting a character at a buffer boundary
        • KTOR-9767 ByteReadChannel.readLineTo: Return read bytes count instead of decoded characters count
        • KTOR-8992 HoconConfigLoader is not loaded when ktor-server-config-yaml on the classpath
        • KTOR-9698 OpenAPI: schema for kotlinx.serialization sealed types omits discriminator property in variant schemas
        • KTOR-9690 Netty: in-flight requests are aborted on stop(), ignoring shutdown grace period
        • KTOR-8903 Netty: Close channel connection on errors properly
        • KTOR-9707 SaveBody performance regression
        • KTOR-9703 Inefficient text reading function in DefaultTransform
        • KTOR-9711 Netty HTTP/2: a single client-canceled stream (RST_STREAM) breaks response flushing for the whole connection
        • KTOR-9688 RateLimit: No access to principal when wrapped with authentication since 3.5.1
        • KTOR-9677 Auto-reloading: server is reloaded only once since 3.5.0
        • KTOR-9683 Sockets: Canceling a SelectorManager job doesn't close it properly
        • KTOR-9671 Darwin: Semicolons in URL path are sanitized
        Commits
        • 01c469a Release 3.5.2 (#5781)
        • c965a0c KTOR-9741 Prevent CIO request handler leak (#5783)
        • 74df458 chore: fix some comments to improve readability
        • 2672567 [KTOR-9704] Optimized encoding flow for UTF-8 (#5743)
        • d5680ed KTOR-5391: Document ECDSA certificate limitation for client authentication (#...
        • 9b0f356 something::javaClass.name is not the name of the class of something but `"j...
        • 58256cd KTOR-9743 Clarify readLine documentation
        • 088bd60 KTOR-9679 Fix regressions caused by adding buffering
        • 167176a KTOR-9588 Do not send Content-Length: 0 for body-less GET requests (#5620)
        • 7fb0844 KTOR-9585 Fix schema title trimming
        • Additional commits viewable in compare view

        Updates `io.ktor:ktor-server-content-negotiation` from 3.5.1 to 3.5.2
        Release notes

        Sourced from io.ktor:ktor-server-content-negotiation's releases.

        3.5.2

        Published 31 July 2026

        Improvements

        • KTOR-8032 RateLimit: Allow limit requests based on authentication result
        • KTOR-9719 ApplicationCall.isStaticContent returns false when called from plugin interceptors
        • KTOR-8450 ContentNegotiation: it doesn't handle content type suffixes
        • KTOR-7892 SwaggerUI: Missing oauth2-redirect.html support in the plugin
        • KTOR-9759 Reject whitespace in URL hosts during parsing
        • KTOR-9681 Update kotlinx-io to 0.9.1
        • KTOR-9743 ByteReadChannel.readLineStrict: Misleading definition of limit parameter in KDocs
        • KTOR-9504 Improve KDoc for EmbeddedServer.addShutdownHook

        Bugfixes

        • KTOR-9741 CIO: request-handler coroutine leak on half-closed idle connection after ≥1MB response
        • KTOR-7661 Maven + YAML config file does not work
        • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
        • KTOR-9704 UTF-8 encoding in ktor-io bypasses JVM String.getBytes intrinsics, slowing every text response by 5-7x
        • KTOR-9588 SSE client (CIO engine) sends spurious Content-Length: 0 on body-less GET (RFC 9110 § 8.6 violation; rejected by AWS ELB)
        • KTOR-9679 ByteReadChannel.readLine corrupts multi-byte UTF-8 by splitting a character at a buffer boundary
        • KTOR-9767 ByteReadChannel.readLineTo: Return read bytes count instead of decoded characters count
        • KTOR-8992 HoconConfigLoader is not loaded when ktor-server-config-yaml on the classpath
        • KTOR-9698 OpenAPI: schema for kotlinx.serialization sealed types omits discriminator property in variant schemas
        • KTOR-9690 Netty: in-flight requests are aborted on stop(), ignoring shutdown grace period
        • KTOR-8903 Netty: Close channel connection on errors properly
        • KTOR-9707 SaveBody performance regression
        • KTOR-9703 Inefficient text reading function in DefaultTransform
        • KTOR-9711 Netty HTTP/2: a single client-canceled stream (RST_STREAM) breaks response flushing for the whole connection
        • KTOR-9688 RateLimit: No access to principal when wrapped with authentication since 3.5.1
        • KTOR-9677 Auto-reloading: server is reloaded only once since 3.5.0
        • KTOR-9683 Sockets: Canceling a SelectorManager job doesn't close it properly
        • KTOR-9671 Darwin: Semicolons in URL path are sanitized
        Changelog

        Sourced from io.ktor:ktor-server-content-negotiation's changelog.

        3.5.2

        Published 31 July 2026

        Improvements

        • KTOR-8032 RateLimit: Allow limit requests based on authentication result
        • KTOR-9719 ApplicationCall.isStaticContent returns false when called from plugin interceptors
        • KTOR-8450 ContentNegotiation: it doesn't handle content type suffixes
        • KTOR-7892 SwaggerUI: Missing oauth2-redirect.html support in the plugin
        • KTOR-9759 Reject whitespace in URL hosts during parsing
        • KTOR-9681 Update kotlinx-io to 0.9.1
        • KTOR-9743 ByteReadChannel.readLineStrict: Misleading definition of limit parameter in KDocs
        • KTOR-9504 Improve KDoc for EmbeddedServer.addShutdownHook

        Bugfixes

        • KTOR-9741 CIO: request-handler coroutine leak on half-closed idle connection after ≥1MB response
        • KTOR-7661 Maven + YAML config file does not work
        • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
        • KTOR-9704 UTF-8 encoding in ktor-io bypasses JVM String.getBytes intrinsics, slowing every text response by 5-7x
        • KTOR-9588 SSE client (CIO engine) sends spurious Content-Length: 0 on body-less GET (RFC 9110 § 8.6 violation; rejected by AWS ELB)
        • KTOR-9679 ByteReadChannel.readLine corrupts multi-byte UTF-8 by splitting a character at a buffer boundary
        • KTOR-9767 ByteReadChannel.readLineTo: Return read bytes count instead of decoded characters count
        • KTOR-8992 HoconConfigLoader is not loaded when ktor-server-config-yaml on the classpath
        • KTOR-9698 OpenAPI: schema for kotlinx.serialization sealed types omits discriminator property in variant schemas
        • KTOR-9690 Netty: in-flight requests are aborted on stop(), ignoring shutdown grace period
        • KTOR-8903 Netty: Close channel connection on errors properly
        • KTOR-9707 SaveBody performance regression
        • KTOR-9703 Inefficient text reading function in DefaultTransform
        • KTOR-9711 Netty HTTP/2: a single client-canceled stream (RST_STREAM) breaks response flushing for the whole connection
        • KTOR-9688 RateLimit: No access to principal when wrapped with authentication since 3.5.1
        • KTOR-9677 Auto-reloading: server is reloaded only once since 3.5.0
        • KTOR-9683 Sockets: Canceling a SelectorManager job doesn't close it properly
        • KTOR-9671 Darwin: Semicolons in URL path are sanitized
        Commits
        • 01c469a Release 3.5.2 (#5781)
        • c965a0c KTOR-9741 Prevent CIO request handler leak (#5783)
        • 74df458 chore: fix some comments to improve readability
        • 2672567 [KTOR-9704] Optimized encoding flow for UTF-8 (#5743)
        • d5680ed KTOR-5391: Document ECDSA certificate limitation for client authentication (#...
        • 9b0f356 something::javaClass.name is not the name of the class of something but `"j...
        • 58256cd KTOR-9743 Clarify readLine documentation
        • 088bd60 KTOR-9679 Fix regressions caused by adding buffering
        • 167176a KTOR-9588 Do not send Content-Length: 0 for body-less GET requests (#5620)
        • 7fb0844 KTOR-9585 Fix schema title trimming
        • Additional commits viewable in compare view

        Updates `io.ktor:ktor-client-content-negotiation` from 3.5.1 to 3.5.2
        Release notes

        Sourced from io.ktor:ktor-client-content-negotiation's releases.

        3.5.2

        Published 31 July 2026

        Improvements

        • KTOR-8032 RateLimit: Allow limit requests based on authentication result
        • KTOR-9719 ApplicationCall.isStaticContent returns false when called from plugin interceptors
        • KTOR-8450 ContentNegotiation: it doesn't handle content type suffixes
        • KTOR-7892 SwaggerUI: Missing oauth2-redirect.html support in the plugin
        • KTOR-9759 Reject whitespace in URL hosts during parsing
        • KTOR-9681 Update kotlinx-io to 0.9.1
        • KTOR-9743 ByteReadChannel.readLineStrict: Misleading definition of limit parameter in KDocs
        • KTOR-9504 Improve KDoc for EmbeddedServer.addShutdownHook

        Bugfixes

        • KTOR-9741 CIO: request-handler coroutine leak on half-closed idle connection after ≥1MB response
        • KTOR-7661 Maven + YAML config file does not work
        • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
        • KTOR-9704 UTF-8 encoding in ktor-io bypasses JVM String.getBytes intrinsics, slowing every text response by 5-7x
        • KTOR-9588 SSE client (CIO engine) sends spurious Content-Length: 0 on body-less GET (RFC 9110 § 8.6 violation; rejected by AWS ELB)
        • KTOR-9679 ByteReadChannel.readLine corrupts multi-byte UTF-8 by splitting a character at a buffer boundary
        • KTOR-9767 ByteReadChannel.readLineTo: Return read bytes count instead of decoded characters count
        • KTOR-8992 HoconConfigLoader is not loaded when ktor-server-config-yaml on the classpath
        • KTOR-9698 OpenAPI: schema for kotlinx.serialization sealed types omits discriminator property in variant schemas
        • KTOR-9690 Netty: in-flight requests are aborted on stop(), ignoring shutdown grace period
        • KTOR-8903 Netty: Close channel connection on errors properly
        • KTOR-9707 SaveBody performance regression
        • KTOR-9703 Inefficient text reading function in DefaultTransform
        • KTOR-9711 Netty HTTP/2: a single client-canceled stream (RST_STREAM) breaks response flushing for the whole connection
        • KTOR-9688 RateLimit: No access to principal when wrapped with authentication since 3.5.1
        • KTOR-9677 Auto-reloading: server is reloaded only once since 3.5.0
        • KTOR-9683 Sockets: Canceling a SelectorManager job doesn't close it properly
        • KTOR-9671 Darwin: Semicolons in URL path are sanitized
        Changelog

        Sourced from io.ktor:ktor-client-content-negotiation's changelog.

        3.5.2

        Published 31 July 2026

        Improvements

        • KTOR-8032 RateLimit: Allow limit requests based on authentication result
        • KTOR-9719 ApplicationCall.isStaticContent returns false when called from plugin interceptors
        • KTOR-8450 ContentNegotiation: it doesn't handle content type suffixes
        • KTOR-7892 SwaggerUI: Missing oauth2-redirect.html support in the plugin
        • KTOR-9759 Reject whitespace in URL hosts during parsing
        • KTOR-9681 Update kotlinx-io to 0.9.1
        • KTOR-9743 ByteReadChannel.readLineStrict: Misleading definition of limit parameter in KDocs
        • KTOR-9504 Improve KDoc for EmbeddedServer.addShutdownHook

        Bugfixes

        • KTOR-9741 CIO: request-handler coroutine leak on half-closed idle connection after ≥1MB response
        • KTOR-7661 Maven + YAML config file does not work
        • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
        • KTOR-9704 UTF-8 encoding in ktor-io bypasses JVM String.getBytes intrinsics, slowing every text response by 5-7x
        • KTOR-9588 SSE client (CIO engine) sends spurious Content-Length: 0 on body-less GET (RFC 9110 § 8.6 violation; rejected by AWS ELB)
        • KTOR-9679 ByteReadChannel.readLine corrupts multi-byte UTF-8 by splitting a character at a buffer boundary
        • KTOR-9767 ByteReadChannel.readLineTo: Return read bytes count instead of decoded characters count
        • KTOR-8992 HoconConfigLoader is not loaded when ktor-server-config-yaml on the classpath
        • KTOR-9698 OpenAPI: schema for kotlinx.serialization sealed types omits discriminator property in variant schemas
        • KTOR-9690 Netty: in-flight requests are aborted on stop(), ignoring shutdown grace period
        • KTOR-8903 Netty: Close channel connection on errors properly
        • KTOR-9707 SaveBody performance regression
        • KTOR-9703 Inefficient text reading function in DefaultTransform
        • KTOR-9711 Netty HTTP/2: a single client-canceled stream (RST_STREAM) breaks response flushing for the whole connection
        • KTOR-9688 RateLimit: No access to principal when wrapped with authentication since 3.5.1
        • KTOR-9677 Auto-reloading: server is reloaded only once since 3.5.0
        • KTOR-9683 Sockets: Canceling a SelectorManager job doesn't... _Description has been truncated_ Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 051e361..7257a7d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,7 +17,7 @@ collections-immutable = "0.5.1" coroutines = "1.11.0" kotest = "6.2.3" kotlinx-io = "0.9.1" -ktor = "3.5.1" +ktor = "3.5.2" logging = "8.0.4" mockk = "1.14.11" serialization = "1.11.0" From 68042497a0038253afce1093a36837b7af42699a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 14:59:51 +0200 Subject: [PATCH 413/449] chore(deps): bump the other-dependencies group across 5 directories with 1 update (#942) Bumps the other-dependencies group with 1 update in the /samples/kotlin-mcp-client directory: [gradle-wrapper](https://github.com/gradle/gradle). Bumps the other-dependencies group with 1 update in the /samples/kotlin-mcp-server directory: [gradle-wrapper](https://github.com/gradle/gradle). Bumps the other-dependencies group with 1 update in the /samples/kotlinlang-mcp-server directory: [gradle-wrapper](https://github.com/gradle/gradle). Bumps the other-dependencies group with 1 update in the /samples/simple-streamable-server directory: [gradle-wrapper](https://github.com/gradle/gradle). Bumps the other-dependencies group with 1 update in the /samples/weather-stdio-server directory: [gradle-wrapper](https://github.com/gradle/gradle). Updates `gradle-wrapper` from 9.6.1 to 9.7.0
          Release notes

          Sourced from gradle-wrapper's releases.

          9.7.0

          The Gradle team is excited to announce Gradle 9.7.0.

          Here are the highlights of this release:

          • Isolated Projects graduates to incubating
          • Broader Configuration Cache compatibility
          • More source locations in problem reports

          Read the Release Notes

          We would like to thank the following community members for their contributions to this release of Gradle: Adam, Aman Gautam, Aman Kumar, Anton Dubrouski, Aurimas, gbhavya07, Josh Friend, nicklauslittle-gov, Pragati, project516, Qin Mi, Ravi, sk-reddy17, Suvrat Acharya, Yongshun Ye.

          Upgrade instructions

          Switch your build to use Gradle 9.7.0 by updating your wrapper:

          ./gradlew :wrapper --gradle-version=9.7.0 &&
          ./gradlew :wrapper
          

          See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

          For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

          Reporting problems

          If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

          We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

          9.7.0 RC3

          The Gradle team is excited to announce Gradle 9.7.0 RC3.

          Here are the highlights of this release:

          • Isolated Projects graduates to incubating

          ... (truncated)

          Commits
          • 3defbfc make DistributionIntegrationSpec more permissive for releases (#38766)
          • f176043 make DistributionIntegrationSpec more permissive for releases
          • b0828da Prepare release notes for Gradle 9.7.0GA (#38756)
          • 064b6d6 cleanup
          • dfe7bdc add new training to release notes
          • 7561968 add release notes for 37801
          • 68a1f35 cherrypick 38367 to release
          • a462c95 Rebalance AllVersionsCrossVersion buckets for agents without TestDistribution...
          • 820e2b1 Update Gradle wrapper to version 9.7.0-rc-3 (#38743)
          • 75ea3d7 Update Gradle wrapper to version 9.7.0-rc-3
          • Additional commits viewable in compare view

          Updates `gradle-wrapper` from 9.6.1 to 9.7.0
          Release notes

          Sourced from gradle-wrapper's releases.

          9.7.0

          The Gradle team is excited to announce Gradle 9.7.0.

          Here are the highlights of this release:

          • Isolated Projects graduates to incubating
          • Broader Configuration Cache compatibility
          • More source locations in problem reports

          Read the Release Notes

          We would like to thank the following community members for their contributions to this release of Gradle: Adam, Aman Gautam, Aman Kumar, Anton Dubrouski, Aurimas, gbhavya07, Josh Friend, nicklauslittle-gov, Pragati, project516, Qin Mi, Ravi, sk-reddy17, Suvrat Acharya, Yongshun Ye.

          Upgrade instructions

          Switch your build to use Gradle 9.7.0 by updating your wrapper:

          ./gradlew :wrapper --gradle-version=9.7.0 &&
          ./gradlew :wrapper
          

          See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

          For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

          Reporting problems

          If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

          We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

          9.7.0 RC3

          The Gradle team is excited to announce Gradle 9.7.0 RC3.

          Here are the highlights of this release:

          • Isolated Projects graduates to incubating

          ... (truncated)

          Commits
          • 3defbfc make DistributionIntegrationSpec more permissive for releases (#38766)
          • f176043 make DistributionIntegrationSpec more permissive for releases
          • b0828da Prepare release notes for Gradle 9.7.0GA (#38756)
          • 064b6d6 cleanup
          • dfe7bdc add new training to release notes
          • 7561968 add release notes for 37801
          • 68a1f35 cherrypick 38367 to release
          • a462c95 Rebalance AllVersionsCrossVersion buckets for agents without TestDistribution...
          • 820e2b1 Update Gradle wrapper to version 9.7.0-rc-3 (#38743)
          • 75ea3d7 Update Gradle wrapper to version 9.7.0-rc-3
          • Additional commits viewable in compare view

          Updates `gradle-wrapper` from 9.6.1 to 9.7.0
          Release notes

          Sourced from gradle-wrapper's releases.

          9.7.0

          The Gradle team is excited to announce Gradle 9.7.0.

          Here are the highlights of this release:

          • Isolated Projects graduates to incubating
          • Broader Configuration Cache compatibility
          • More source locations in problem reports

          Read the Release Notes

          We would like to thank the following community members for their contributions to this release of Gradle: Adam, Aman Gautam, Aman Kumar, Anton Dubrouski, Aurimas, gbhavya07, Josh Friend, nicklauslittle-gov, Pragati, project516, Qin Mi, Ravi, sk-reddy17, Suvrat Acharya, Yongshun Ye.

          Upgrade instructions

          Switch your build to use Gradle 9.7.0 by updating your wrapper:

          ./gradlew :wrapper --gradle-version=9.7.0 &&
          ./gradlew :wrapper
          

          See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

          For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

          Reporting problems

          If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

          We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

          9.7.0 RC3

          The Gradle team is excited to announce Gradle 9.7.0 RC3.

          Here are the highlights of this release:

          • Isolated Projects graduates to incubating

          ... (truncated)

          Commits
          • 3defbfc make DistributionIntegrationSpec more permissive for releases (#38766)
          • f176043 make DistributionIntegrationSpec more permissive for releases
          • b0828da Prepare release notes for Gradle 9.7.0GA (#38756)
          • 064b6d6 cleanup
          • dfe7bdc add new training to release notes
          • 7561968 add release notes for 37801
          • 68a1f35 cherrypick 38367 to release
          • a462c95 Rebalance AllVersionsCrossVersion buckets for agents without TestDistribution...
          • 820e2b1 Update Gradle wrapper to version 9.7.0-rc-3 (#38743)
          • 75ea3d7 Update Gradle wrapper to version 9.7.0-rc-3
          • Additional commits viewable in compare view

          Updates `gradle-wrapper` from 9.6.1 to 9.7.0
          Release notes

          Sourced from gradle-wrapper's releases.

          9.7.0

          The Gradle team is excited to announce Gradle 9.7.0.

          Here are the highlights of this release:

          • Isolated Projects graduates to incubating
          • Broader Configuration Cache compatibility
          • More source locations in problem reports

          Read the Release Notes

          We would like to thank the following community members for their contributions to this release of Gradle: Adam, Aman Gautam, Aman Kumar, Anton Dubrouski, Aurimas, gbhavya07, Josh Friend, nicklauslittle-gov, Pragati, project516, Qin Mi, Ravi, sk-reddy17, Suvrat Acharya, Yongshun Ye.

          Upgrade instructions

          Switch your build to use Gradle 9.7.0 by updating your wrapper:

          ./gradlew :wrapper --gradle-version=9.7.0 &&
          ./gradlew :wrapper
          

          See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

          For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

          Reporting problems

          If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

          We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

          9.7.0 RC3

          The Gradle team is excited to announce Gradle 9.7.0 RC3.

          Here are the highlights of this release:

          • Isolated Projects graduates to incubating

          ... (truncated)

          Commits
          • 3defbfc make DistributionIntegrationSpec more permissive for releases (#38766)
          • f176043 make DistributionIntegrationSpec more permissive for releases
          • b0828da Prepare release notes for Gradle 9.7.0GA (#38756)
          • 064b6d6 cleanup
          • dfe7bdc add new training to release notes
          • 7561968 add release notes for 37801
          • 68a1f35 cherrypick 38367 to release
          • a462c95 Rebalance AllVersionsCrossVersion buckets for agents without TestDistribution...
          • 820e2b1 Update Gradle wrapper to version 9.7.0-rc-3 (#38743)
          • 75ea3d7 Update Gradle wrapper to version 9.7.0-rc-3
          • Additional commits viewable in compare view

          Updates `gradle-wrapper` from 9.6.1 to 9.7.0
          Release notes

          Sourced from gradle-wrapper's releases.

          9.7.0

          The Gradle team is excited to announce Gradle 9.7.0.

          Here are the highlights of this release:

          • Isolated Projects graduates to incubating
          • Broader Configuration Cache compatibility
          • More source locations in problem reports

          Read the Release Notes

          We would like to thank the following community members for their contributions to this release of Gradle: Adam, Aman Gautam, Aman Kumar, Anton Dubrouski, Aurimas, gbhavya07, Josh Friend, nicklauslittle-gov, Pragati, project516, Qin Mi, Ravi, sk-reddy17, Suvrat Acharya, Yongshun Ye.

          Upgrade instructions

          Switch your build to use Gradle 9.7.0 by updating your wrapper:

          ./gradlew :wrapper --gradle-version=9.7.0 &&
          ./gradlew :wrapper
          

          See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

          For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

          Reporting problems

          If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

          We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

          9.7.0 RC3

          The Gradle team is excited to announce Gradle 9.7.0 RC3.

          Here are the highlights of this release:

          • Isolated Projects graduates to incubating

          ... (truncated)

          Commits
          • 3defbfc make DistributionIntegrationSpec more permissive for releases (#38766)
          • f176043 make DistributionIntegrationSpec more permissive for releases
          • b0828da Prepare release notes for Gradle 9.7.0GA (#38756)
          • 064b6d6 cleanup
          • dfe7bdc add new training to release notes
          • 7561968 add release notes for 37801
          • 68a1f35 cherrypick 38367 to release
          • a462c95 Rebalance AllVersionsCrossVersion buckets for agents without TestDistribution...
          • 820e2b1 Update Gradle wrapper to version 9.7.0-rc-3 (#38743)
          • 75ea3d7 Update Gradle wrapper to version 9.7.0-rc-3
          • Additional commits viewable in compare view

          Updates `gradle-wrapper` from 9.6.1 to 9.7.0
          Release notes

          Sourced from gradle-wrapper's releases.

          9.7.0

          The Gradle team is excited to announce Gradle 9.7.0.

          Here are the highlights of this release:

          • Isolated Projects graduates to incubating
          • Broader Configuration Cache compatibility
          • More source locations in problem reports

          Read the Release Notes

          We would like to thank the following community members for their contributions to this release of Gradle: Adam, Aman Gautam, Aman Kumar, Anton Dubrouski, Aurimas, gbhavya07, Josh Friend, nicklauslittle-gov, Pragati, project516, Qin Mi, Ravi, sk-reddy17, Suvrat Acharya, Yongshun Ye.

          Upgrade instructions

          Switch your build to use Gradle 9.7.0 by updating your wrapper:

          ./gradlew :wrapper --gradle-version=9.7.0 &&
          ./gradlew :wrapper
          

          See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

          For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

          Reporting problems

          If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

          We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

          9.7.0 RC3

          The Gradle team is excited to announce Gradle 9.7.0 RC3.

          Here are the highlights of this release:

          • Isolated Projects graduates to incubating

          ... (truncated)

          Commits
          • 3defbfc make DistributionIntegrationSpec more permissive for releases (#38766)
          • f176043 make DistributionIntegrationSpec more permissive for releases
          • b0828da Prepare release notes for Gradle 9.7.0GA (#38756)
          • 064b6d6 cleanup
          • dfe7bdc add new training to release notes
          • 7561968 add release notes for 37801
          • 68a1f35 cherrypick 38367 to release
          • a462c95 Rebalance AllVersionsCrossVersion buckets for agents without TestDistribution...
          • 820e2b1 Update Gradle wrapper to version 9.7.0-rc-3 (#38743)
          • 75ea3d7 Update Gradle wrapper to version 9.7.0-rc-3
          • Additional commits viewable in compare view

          Updates `gradle-wrapper` from 9.6.1 to 9.7.0
          Release notes

          Sourced from gradle-wrapper's releases.

          9.7.0

          The Gradle team is excited to announce Gradle 9.7.0.

          Here are the highlights of this release:

          • Isolated Projects graduates to incubating
          • Broader Configuration Cache compatibility
          • More source locations in problem reports

          Read the Release Notes

          We would like to thank the following community members for their contributions to this release of Gradle: Adam, Aman Gautam, Aman Kumar, Anton Dubrouski, Aurimas, gbhavya07, Josh Friend, nicklauslittle-gov, Pragati, project516, Qin Mi, Ravi, sk-reddy17, Suvrat Acharya, Yongshun Ye.

          Upgrade instructions

          Switch your build to use Gradle 9.7.0 by updating your wrapper:

          ./gradlew :wrapper --gradle-version=9.7.0 &&
          ./gradlew :wrapper
          

          See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

          For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

          Reporting problems

          If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

          We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

          9.7.0 RC3

          The Gradle team is excited to announce Gradle 9.7.0 RC3.

          Here are the highlights of this release:

          • Isolated Projects graduates to incubating

          ... (truncated)

          Commits
          • 3defbfc make DistributionIntegrationSpec more permissive for releases (#38766)
          • f176043 make DistributionIntegrationSpec more permissive for releases
          • b0828da Prepare release notes for Gradle 9.7.0GA (#38756)
          • 064b6d6 cleanup
          • dfe7bdc add new training to release notes
          • 7561968 add release notes for 37801
          • 68a1f35 cherrypick 38367 to release
          • a462c95 Rebalance AllVersionsCrossVersion buckets for agents without TestDistribution...
          • 820e2b1 Update Gradle wrapper to version 9.7.0-rc-3 (#38743)
          • 75ea3d7 Update Gradle wrapper to version 9.7.0-rc-3
          • Additional commits viewable in compare view

          Updates `gradle-wrapper` from 9.6.1 to 9.7.0
          Release notes

          Sourced from gradle-wrapper's releases.

          9.7.0

          The Gradle team is excited to announce Gradle 9.7.0.

          Here are the highlights of this release:

          • Isolated Projects graduates to incubating
          • Broader Configuration Cache compatibility
          • More source locations in problem reports

          Read the Release Notes

          We would like to thank the following community members for their contributions to this release of Gradle: Adam, Aman Gautam, Aman Kumar, Anton Dubrouski, Aurimas, gbhavya07, Josh Friend, nicklauslittle-gov, Pragati, project516, Qin Mi, Ravi, sk-reddy17, Suvrat Acharya, Yongshun Ye.

          Upgrade instructions

          Switch your build to use Gradle 9.7.0 by updating your wrapper:

          ./gradlew :wrapper --gradle-version=9.7.0 &&
          ./gradlew :wrapper
          

          See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

          For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

          Reporting problems

          If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

          We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

          9.7.0 RC3

          The Gradle team is excited to announce Gradle 9.7.0 RC3.

          Here are the highlights of this release:

          • Isolated Projects graduates to incubating

          ... (truncated)

          Commits
          • 3defbfc make DistributionIntegrationSpec more permissive for releases (#38766)
          • f176043 make DistributionIntegrationSpec more permissive for releases
          • b0828da Prepare release notes for Gradle 9.7.0GA (#38756)
          • 064b6d6 cleanup
          • dfe7bdc add new training to release notes
          • 7561968 add release notes for 37801
          • 68a1f35 cherrypick 38367 to release
          • a462c95 Rebalance AllVersionsCrossVersion buckets for agents without TestDistribution...
          • 820e2b1 Update Gradle wrapper to version 9.7.0-rc-3 (#38743)
          • 75ea3d7 Update Gradle wrapper to version 9.7.0-rc-3
          • Additional commits viewable in compare view

          Updates `gradle-wrapper` from 9.6.1 to 9.7.0
          Release notes

          Sourced from gradle-wrapper's releases.

          9.7.0

          The Gradle team is excited to announce Gradle 9.7.0.

          Here are the highlights of this release:

          • Isolated Projects graduates to incubating
          • Broader Configuration Cache compatibility
          • More source locations in problem reports

          Read the Release Notes

          We would like to thank the following community members for their contributions to this release of Gradle: Adam, Aman Gautam, Aman Kumar, Anton Dubrouski, Aurimas, gbhavya07, Josh Friend, nicklauslittle-gov, Pragati, project516, Qin Mi, Ravi, sk-reddy17, Suvrat Acharya, Yongshun Ye.

          Upgrade instructions

          Switch your build to use Gradle 9.7.0 by updating your wrapper:

          ./gradlew :wrapper --gradle-version=9.7.0 &&
          ./gradlew :wrapper
          

          See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

          For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

          Reporting problems

          If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

          We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

          9.7.0 RC3

          The Gradle team is excited to announce Gradle 9.7.0 RC3.

          Here are the highlights of this release:

          • Isolated Projects graduates to incubating

          ... (truncated)

          Commits
          • 3defbfc make DistributionIntegrationSpec more permissive for releases (#38766)
          • f176043 make DistributionIntegrationSpec more permissive for releases
          • b0828da Prepare release notes for Gradle 9.7.0GA (#38756)
          • 064b6d6 cleanup
          • dfe7bdc add new training to release notes
          • 7561968 add release notes for 37801
          • 68a1f35 cherrypick 38367 to release
          • a462c95 Rebalance AllVersionsCrossVersion buckets for agents without TestDistribution...
          • 820e2b1 Update Gradle wrapper to version 9.7.0-rc-3 (#38743)
          • 75ea3d7 Update Gradle wrapper to version 9.7.0-rc-3
          • Additional commits viewable in compare view

          Updates `gradle-wrapper` from 9.6.1 to 9.7.0
          Release notes

          Sourced from gradle-wrapper's releases.

          9.7.0

          The Gradle team is excited to announce Gradle 9.7.0.

          Here are the highlights of this release:

          • Isolated Projects graduates to incubating
          • Broader Configuration Cache compatibility
          • More source locations in problem reports

          Read the Release Notes

          We would like to thank the following community members for their contributions to this release of Gradle: Adam, Aman Gautam, Aman Kumar, Anton Dubrouski, Aurimas, gbhavya07, Josh Friend, nicklauslittle-gov, Pragati, project516, Qin Mi, Ravi, sk-reddy17, Suvrat Acharya, Yongshun Ye.

          Upgrade instructions

          Switch your build to use Gradle 9.7.0 by updating your wrapper:

          ./gradlew :wrapper --gradle-version=9.7.0 &&
          ./gradlew :wrapper
          

          See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

          For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

          Reporting problems

          If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

          We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

          9.7.0 RC3

          The Gradle team is excited to announce Gradle 9.7.0 RC3.

          Here are the highlights of this release:

          • Isolated Projects graduates to incubating

          ... (truncated)

          Commits
          • 3defbfc make DistributionIntegrationSpec more permissive for releases (#38766)
          • f176043 make DistributionIntegrationSpec more permissive for releases
          • b0828da Prepare release notes for Gradle 9.7.0GA (#38756)
          • 064b6d6 cleanup
          • dfe7bdc add new training to release notes
          • 7561968 add release notes for 37801
          • 68a1f35 cherrypick 38367 to release
          • a462c95 Rebalance AllVersionsCrossVersion buckets for agents without TestDistribution...
          • 820e2b1 Update Gradle wrapper to version 9.7.0-rc-3 (#38743)
          • 75ea3d7 Update Gradle wrapper to version 9.7.0-rc-3
          • Additional commits viewable in compare view

          Updates `gradle-wrapper` from 9.6.1 to 9.7.0
          Release notes

          Sourced from gradle-wrapper's releases.

          9.7.0

          The Gradle team is excited to announce Gradle 9.7.0.

          Here are the highlights of this release:

          • Isolated Projects graduates to incubating
          • Broader Configuration Cache compatibility
          • More source locations in problem reports

          Read the Release Notes

          We would like to thank the following community members for their contributions to this release of Gradle: Adam, Aman Gautam, Aman Kumar, Anton Dubrouski, Aurimas, gbhavya07, Josh Friend, nicklauslittle-gov, Pragati, project516, Qin Mi, Ravi, sk-reddy17, Suvrat Acharya, Yongshun Ye.

          Upgrade instructions

          Switch your build to use Gradle 9.7.0 by updating your wrapper:

          ./gradlew :wrapper --gradle-version=9.7.0 &&
          ./gradlew :wrapper
          

          See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

          For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

          Reporting problems

          If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

          We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

          9.7.0 RC3

          The Gradle team is excited to announce Gradle 9.7.0 RC3.

          Here are the highlights of this release:

          • Isolated Projects graduates to incubating

          ... (truncated)

          Commits
          • 3defbfc make DistributionIntegrationSpec more permissive for releases (#38766)
          • f176043 make DistributionIntegrationSpec more permissive for releases
          • b0828da Prepare release notes for Gradle 9.7.0GA (#38756)
          • 064b6d6 cleanup
          • dfe7bdc add new training to release notes
          • 7561968 add release notes for 37801
          • 68a1f35 cherrypick 38367 to release
          • a462c95 Rebalance AllVersionsCrossVersion buckets for agents without TestDistribution...
          • 820e2b1 Update Gradle wrapper to version 9.7.0-rc-3 (#38743)
          • 75ea3d7 Update Gradle wrapper to version 9.7.0-rc-3
          • Additional commits viewable in compare view

          Updates `gradle-wrapper` from 9.6.1 to 9.7.0
          Release notes

          Sourced from gradle-wrapper's releases.

          9.7.0

          The Gradle team is excited to announce Gradle 9.7.0.

          Here are the highlights of this release:

          • Isolated Projects graduates to incubating
          • Broader Configuration Cache compatibility
          • More source locations in problem reports

          Read the Release Notes

          We would like to thank the following community members for their contributions to this release of Gradle: Adam, Aman Gautam, Aman Kumar, Anton Dubrouski, Aurimas, gbhavya07, Josh Friend, nicklauslittle-gov, Pragati, project516, Qin Mi, Ravi, sk-reddy17, Suvrat Acharya, Yongshun Ye.

          Upgrade instructions

          Switch your build to use Gradle 9.7.0 by updating your wrapper:

          ./gradlew :wrapper --gradle-version=9.7.0 &&
          ./gradlew :wrapper
          

          See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

          For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

          Reporting problems

          If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

          We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

          9.7.0 RC3

          The Gradle team is excited to announce Gradle 9.7.0 RC3.

          Here are the highlights of this release:

          • Isolated Projects graduates to incubating

          ... (truncated)

          Commits
          • 3defbfc make DistributionIntegrationSpec more permissive for releases (#38766)
          • f176043 make DistributionIntegrationSpec more permissive for releases
          • b0828da Prepare release notes for Gradle 9.7.0GA (#38756)
          • 064b6d6 cleanup
          • dfe7bdc add new training to release notes
          • 7561968 add release notes for 37801
          • 68a1f35 cherrypick 38367 to release
          • a462c95 Rebalance AllVersionsCrossVersion buckets for agents without TestDistribution...
          • 820e2b1 Update Gradle wrapper to version 9.7.0-rc-3 (#38743)
          • 75ea3d7 Update Gradle wrapper to version 9.7.0-rc-3
          • Additional commits viewable in compare view

          Updates `gradle-wrapper` from 9.6.1 to 9.7.0
          Release notes

          Sourced from gradle-wrapper's releases.

          9.7.0

          The Gradle team is excited to announce Gradle 9.7.0.

          Here are the highlights of this release:

          • Isolated Projects graduates to incubating
          • Broader Configuration Cache compatibility
          • More source locations in problem reports

          Read the Release Notes

          We would like to thank the following community members for their contributions to this release of Gradle: Adam, Aman Gautam, Aman Kumar, Anton Dubrouski, Aurimas, gbhavya07, Josh Friend, nicklauslittle-gov, Pragati, project516, Qin Mi, Ravi, sk-reddy17, Suvrat Acharya, Yongshun Ye.

          Upgrade instructions

          Switch your build to use Gradle 9.7.0 by updating your wrapper:

          ./gradlew :wrapper --gradle-version=9.7.0 &&
          ./gradlew :wrapper
          

          See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

          For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

          Reporting problems

          If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

          We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

          9.7.0 RC3

          The Gradle team is excited to announce Gradle 9.7.0 RC3.

          Here are the highlights of this release:

          • Isolated Projects graduates to incubating

          ... (truncated)

          Commits
          • 3defbfc make DistributionIntegrationSpec more permissive for releases (#38766)
          • f176043 make DistributionIntegrationSpec more permissive for releases
          • b0828da Prepare release notes for Gradle 9.7.0GA (#38756)
          • 064b6d6 cleanup
          • dfe7bdc add new training to release notes
          • 7561968 add release notes for 37801
          • 68a1f35 cherrypick 38367 to release
          • a462c95 Rebalance AllVersionsCrossVersion buckets for agents without TestDistribution...
          • 820e2b1 Update Gradle wrapper to version 9.7.0-rc-3 (#38743)
          • Date: Mon, 10 Aug 2026 15:51:01 +0200 Subject: [PATCH 414/449] chore(deps): bump io.ktor:ktor-bom from 3.5.1 to 3.5.2 in /samples/kotlin-mcp-client (#944) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [io.ktor:ktor-bom](https://github.com/ktorio/ktor) from 3.5.1 to 3.5.2.
            Release notes

            Sourced from io.ktor:ktor-bom's releases.

            3.5.2

            Published 31 July 2026

            Improvements

            • KTOR-8032 RateLimit: Allow limit requests based on authentication result
            • KTOR-9719 ApplicationCall.isStaticContent returns false when called from plugin interceptors
            • KTOR-8450 ContentNegotiation: it doesn't handle content type suffixes
            • KTOR-7892 SwaggerUI: Missing oauth2-redirect.html support in the plugin
            • KTOR-9759 Reject whitespace in URL hosts during parsing
            • KTOR-9681 Update kotlinx-io to 0.9.1
            • KTOR-9743 ByteReadChannel.readLineStrict: Misleading definition of limit parameter in KDocs
            • KTOR-9504 Improve KDoc for EmbeddedServer.addShutdownHook

            Bugfixes

            • KTOR-9741 CIO: request-handler coroutine leak on half-closed idle connection after ≥1MB response
            • KTOR-7661 Maven + YAML config file does not work
            • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
            • KTOR-9704 UTF-8 encoding in ktor-io bypasses JVM String.getBytes intrinsics, slowing every text response by 5-7x
            • KTOR-9588 SSE client (CIO engine) sends spurious Content-Length: 0 on body-less GET (RFC 9110 § 8.6 violation; rejected by AWS ELB)
            • KTOR-9679 ByteReadChannel.readLine corrupts multi-byte UTF-8 by splitting a character at a buffer boundary
            • KTOR-9767 ByteReadChannel.readLineTo: Return read bytes count instead of decoded characters count
            • KTOR-8992 HoconConfigLoader is not loaded when ktor-server-config-yaml on the classpath
            • KTOR-9698 OpenAPI: schema for kotlinx.serialization sealed types omits discriminator property in variant schemas
            • KTOR-9690 Netty: in-flight requests are aborted on stop(), ignoring shutdown grace period
            • KTOR-8903 Netty: Close channel connection on errors properly
            • KTOR-9707 SaveBody performance regression
            • KTOR-9703 Inefficient text reading function in DefaultTransform
            • KTOR-9711 Netty HTTP/2: a single client-canceled stream (RST_STREAM) breaks response flushing for the whole connection
            • KTOR-9688 RateLimit: No access to principal when wrapped with authentication since 3.5.1
            • KTOR-9677 Auto-reloading: server is reloaded only once since 3.5.0
            • KTOR-9683 Sockets: Canceling a SelectorManager job doesn't close it properly
            • KTOR-9671 Darwin: Semicolons in URL path are sanitized
            Changelog

            Sourced from io.ktor:ktor-bom's changelog.

            3.5.2

            Published 31 July 2026

            Improvements

            • KTOR-8032 RateLimit: Allow limit requests based on authentication result
            • KTOR-9719 ApplicationCall.isStaticContent returns false when called from plugin interceptors
            • KTOR-8450 ContentNegotiation: it doesn't handle content type suffixes
            • KTOR-7892 SwaggerUI: Missing oauth2-redirect.html support in the plugin
            • KTOR-9759 Reject whitespace in URL hosts during parsing
            • KTOR-9681 Update kotlinx-io to 0.9.1
            • KTOR-9743 ByteReadChannel.readLineStrict: Misleading definition of limit parameter in KDocs
            • KTOR-9504 Improve KDoc for EmbeddedServer.addShutdownHook

            Bugfixes

            • KTOR-9741 CIO: request-handler coroutine leak on half-closed idle connection after ≥1MB response
            • KTOR-7661 Maven + YAML config file does not work
            • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
            • KTOR-9704 UTF-8 encoding in ktor-io bypasses JVM String.getBytes intrinsics, slowing every text response by 5-7x
            • KTOR-9588 SSE client (CIO engine) sends spurious Content-Length: 0 on body-less GET (RFC 9110 § 8.6 violation; rejected by AWS ELB)
            • KTOR-9679 ByteReadChannel.readLine corrupts multi-byte UTF-8 by splitting a character at a buffer boundary
            • KTOR-9767 ByteReadChannel.readLineTo: Return read bytes count instead of decoded characters count
            • KTOR-8992 HoconConfigLoader is not loaded when ktor-server-config-yaml on the classpath
            • KTOR-9698 OpenAPI: schema for kotlinx.serialization sealed types omits discriminator property in variant schemas
            • KTOR-9690 Netty: in-flight requests are aborted on stop(), ignoring shutdown grace period
            • KTOR-8903 Netty: Close channel connection on errors properly
            • KTOR-9707 SaveBody performance regression
            • KTOR-9703 Inefficient text reading function in DefaultTransform
            • KTOR-9711 Netty HTTP/2: a single client-canceled stream (RST_STREAM) breaks response flushing for the whole connection
            • KTOR-9688 RateLimit: No access to principal when wrapped with authentication since 3.5.1
            • KTOR-9677 Auto-reloading: server is reloaded only once since 3.5.0
            • KTOR-9683 Sockets: Canceling a SelectorManager job doesn't close it properly
            • KTOR-9671 Darwin: Semicolons in URL path are sanitized
            Commits
            • 01c469a Release 3.5.2 (#5781)
            • c965a0c KTOR-9741 Prevent CIO request handler leak (#5783)
            • 74df458 chore: fix some comments to improve readability
            • 2672567 [KTOR-9704] Optimized encoding flow for UTF-8 (#5743)
            • d5680ed KTOR-5391: Document ECDSA certificate limitation for client authentication (#...
            • 9b0f356 something::javaClass.name is not the name of the class of something but `"j...
            • 58256cd KTOR-9743 Clarify readLine documentation
            • 088bd60 KTOR-9679 Fix regressions caused by adding buffering
            • 167176a KTOR-9588 Do not send Content-Length: 0 for body-less GET requests (#5620)
            • 7fb0844 KTOR-9585 Fix schema title trimming
            • Additional commits viewable in compare view

            Most Recent Ignore Conditions Applied to This Pull Request | Dependency Name | Ignore Conditions | | --- | --- | | io.ktor:ktor-bom | [>= 3.4.a, < 3.5] |
            Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-client/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index ea0043d..4e1f8bb 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] anthropic = "2.52.0" kotlin = "2.4.10" -ktor = "3.5.1" +ktor = "3.5.2" mcp-kotlin = "0.15.0" shadow = "9.6.1" slf4j = "2.0.18" From 05c5953a18e351795e793e76ad321c77db810388 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 16:11:51 +0200 Subject: [PATCH 415/449] chore(deps): bump io.ktor:ktor-bom from 3.5.1 to 3.5.2 in /samples/kotlin-mcp-server (#945) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [io.ktor:ktor-bom](https://github.com/ktorio/ktor) from 3.5.1 to 3.5.2.
            Release notes

            Sourced from io.ktor:ktor-bom's releases.

            3.5.2

            Published 31 July 2026

            Improvements

            • KTOR-8032 RateLimit: Allow limit requests based on authentication result
            • KTOR-9719 ApplicationCall.isStaticContent returns false when called from plugin interceptors
            • KTOR-8450 ContentNegotiation: it doesn't handle content type suffixes
            • KTOR-7892 SwaggerUI: Missing oauth2-redirect.html support in the plugin
            • KTOR-9759 Reject whitespace in URL hosts during parsing
            • KTOR-9681 Update kotlinx-io to 0.9.1
            • KTOR-9743 ByteReadChannel.readLineStrict: Misleading definition of limit parameter in KDocs
            • KTOR-9504 Improve KDoc for EmbeddedServer.addShutdownHook

            Bugfixes

            • KTOR-9741 CIO: request-handler coroutine leak on half-closed idle connection after ≥1MB response
            • KTOR-7661 Maven + YAML config file does not work
            • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
            • KTOR-9704 UTF-8 encoding in ktor-io bypasses JVM String.getBytes intrinsics, slowing every text response by 5-7x
            • KTOR-9588 SSE client (CIO engine) sends spurious Content-Length: 0 on body-less GET (RFC 9110 § 8.6 violation; rejected by AWS ELB)
            • KTOR-9679 ByteReadChannel.readLine corrupts multi-byte UTF-8 by splitting a character at a buffer boundary
            • KTOR-9767 ByteReadChannel.readLineTo: Return read bytes count instead of decoded characters count
            • KTOR-8992 HoconConfigLoader is not loaded when ktor-server-config-yaml on the classpath
            • KTOR-9698 OpenAPI: schema for kotlinx.serialization sealed types omits discriminator property in variant schemas
            • KTOR-9690 Netty: in-flight requests are aborted on stop(), ignoring shutdown grace period
            • KTOR-8903 Netty: Close channel connection on errors properly
            • KTOR-9707 SaveBody performance regression
            • KTOR-9703 Inefficient text reading function in DefaultTransform
            • KTOR-9711 Netty HTTP/2: a single client-canceled stream (RST_STREAM) breaks response flushing for the whole connection
            • KTOR-9688 RateLimit: No access to principal when wrapped with authentication since 3.5.1
            • KTOR-9677 Auto-reloading: server is reloaded only once since 3.5.0
            • KTOR-9683 Sockets: Canceling a SelectorManager job doesn't close it properly
            • KTOR-9671 Darwin: Semicolons in URL path are sanitized
            Changelog

            Sourced from io.ktor:ktor-bom's changelog.

            3.5.2

            Published 31 July 2026

            Improvements

            • KTOR-8032 RateLimit: Allow limit requests based on authentication result
            • KTOR-9719 ApplicationCall.isStaticContent returns false when called from plugin interceptors
            • KTOR-8450 ContentNegotiation: it doesn't handle content type suffixes
            • KTOR-7892 SwaggerUI: Missing oauth2-redirect.html support in the plugin
            • KTOR-9759 Reject whitespace in URL hosts during parsing
            • KTOR-9681 Update kotlinx-io to 0.9.1
            • KTOR-9743 ByteReadChannel.readLineStrict: Misleading definition of limit parameter in KDocs
            • KTOR-9504 Improve KDoc for EmbeddedServer.addShutdownHook

            Bugfixes

            • KTOR-9741 CIO: request-handler coroutine leak on half-closed idle connection after ≥1MB response
            • KTOR-7661 Maven + YAML config file does not work
            • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
            • KTOR-9704 UTF-8 encoding in ktor-io bypasses JVM String.getBytes intrinsics, slowing every text response by 5-7x
            • KTOR-9588 SSE client (CIO engine) sends spurious Content-Length: 0 on body-less GET (RFC 9110 § 8.6 violation; rejected by AWS ELB)
            • KTOR-9679 ByteReadChannel.readLine corrupts multi-byte UTF-8 by splitting a character at a buffer boundary
            • KTOR-9767 ByteReadChannel.readLineTo: Return read bytes count instead of decoded characters count
            • KTOR-8992 HoconConfigLoader is not loaded when ktor-server-config-yaml on the classpath
            • KTOR-9698 OpenAPI: schema for kotlinx.serialization sealed types omits discriminator property in variant schemas
            • KTOR-9690 Netty: in-flight requests are aborted on stop(), ignoring shutdown grace period
            • KTOR-8903 Netty: Close channel connection on errors properly
            • KTOR-9707 SaveBody performance regression
            • KTOR-9703 Inefficient text reading function in DefaultTransform
            • KTOR-9711 Netty HTTP/2: a single client-canceled stream (RST_STREAM) breaks response flushing for the whole connection
            • KTOR-9688 RateLimit: No access to principal when wrapped with authentication since 3.5.1
            • KTOR-9677 Auto-reloading: server is reloaded only once since 3.5.0
            • KTOR-9683 Sockets: Canceling a SelectorManager job doesn't close it properly
            • KTOR-9671 Darwin: Semicolons in URL path are sanitized
            Commits
            • 01c469a Release 3.5.2 (#5781)
            • c965a0c KTOR-9741 Prevent CIO request handler leak (#5783)
            • 74df458 chore: fix some comments to improve readability
            • 2672567 [KTOR-9704] Optimized encoding flow for UTF-8 (#5743)
            • d5680ed KTOR-5391: Document ECDSA certificate limitation for client authentication (#...
            • 9b0f356 something::javaClass.name is not the name of the class of something but `"j...
            • 58256cd KTOR-9743 Clarify readLine documentation
            • 088bd60 KTOR-9679 Fix regressions caused by adding buffering
            • 167176a KTOR-9588 Do not send Content-Length: 0 for body-less GET requests (#5620)
            • 7fb0844 KTOR-9585 Fix schema title trimming
            • Additional commits viewable in compare view

            Most Recent Ignore Conditions Applied to This Pull Request | Dependency Name | Ignore Conditions | | --- | --- | | io.ktor:ktor-bom | [>= 3.4.a, < 3.5] |
            Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-server/gradle/libs.versions.toml b/samples/kotlin-mcp-server/gradle/libs.versions.toml index ba4f306..5f8b83f 100644 --- a/samples/kotlin-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-server/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] kotlin = "2.4.10" -ktor = "3.5.1" +ktor = "3.5.2" mcp-kotlin = "0.15.0" slf4j = "2.0.18" shadow = "9.6.1" From df346975054ef7495f2fac0d45ddc8f3c3072b99 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 16:43:19 +0200 Subject: [PATCH 416/449] chore(deps): bump io.ktor:ktor-bom from 3.5.1 to 3.5.2 in /samples/kotlinlang-mcp-server (#946) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [io.ktor:ktor-bom](https://github.com/ktorio/ktor) from 3.5.1 to 3.5.2.
            Release notes

            Sourced from io.ktor:ktor-bom's releases.

            3.5.2

            Published 31 July 2026

            Improvements

            • KTOR-8032 RateLimit: Allow limit requests based on authentication result
            • KTOR-9719 ApplicationCall.isStaticContent returns false when called from plugin interceptors
            • KTOR-8450 ContentNegotiation: it doesn't handle content type suffixes
            • KTOR-7892 SwaggerUI: Missing oauth2-redirect.html support in the plugin
            • KTOR-9759 Reject whitespace in URL hosts during parsing
            • KTOR-9681 Update kotlinx-io to 0.9.1
            • KTOR-9743 ByteReadChannel.readLineStrict: Misleading definition of limit parameter in KDocs
            • KTOR-9504 Improve KDoc for EmbeddedServer.addShutdownHook

            Bugfixes

            • KTOR-9741 CIO: request-handler coroutine leak on half-closed idle connection after ≥1MB response
            • KTOR-7661 Maven + YAML config file does not work
            • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
            • KTOR-9704 UTF-8 encoding in ktor-io bypasses JVM String.getBytes intrinsics, slowing every text response by 5-7x
            • KTOR-9588 SSE client (CIO engine) sends spurious Content-Length: 0 on body-less GET (RFC 9110 § 8.6 violation; rejected by AWS ELB)
            • KTOR-9679 ByteReadChannel.readLine corrupts multi-byte UTF-8 by splitting a character at a buffer boundary
            • KTOR-9767 ByteReadChannel.readLineTo: Return read bytes count instead of decoded characters count
            • KTOR-8992 HoconConfigLoader is not loaded when ktor-server-config-yaml on the classpath
            • KTOR-9698 OpenAPI: schema for kotlinx.serialization sealed types omits discriminator property in variant schemas
            • KTOR-9690 Netty: in-flight requests are aborted on stop(), ignoring shutdown grace period
            • KTOR-8903 Netty: Close channel connection on errors properly
            • KTOR-9707 SaveBody performance regression
            • KTOR-9703 Inefficient text reading function in DefaultTransform
            • KTOR-9711 Netty HTTP/2: a single client-canceled stream (RST_STREAM) breaks response flushing for the whole connection
            • KTOR-9688 RateLimit: No access to principal when wrapped with authentication since 3.5.1
            • KTOR-9677 Auto-reloading: server is reloaded only once since 3.5.0
            • KTOR-9683 Sockets: Canceling a SelectorManager job doesn't close it properly
            • KTOR-9671 Darwin: Semicolons in URL path are sanitized
            Changelog

            Sourced from io.ktor:ktor-bom's changelog.

            3.5.2

            Published 31 July 2026

            Improvements

            • KTOR-8032 RateLimit: Allow limit requests based on authentication result
            • KTOR-9719 ApplicationCall.isStaticContent returns false when called from plugin interceptors
            • KTOR-8450 ContentNegotiation: it doesn't handle content type suffixes
            • KTOR-7892 SwaggerUI: Missing oauth2-redirect.html support in the plugin
            • KTOR-9759 Reject whitespace in URL hosts during parsing
            • KTOR-9681 Update kotlinx-io to 0.9.1
            • KTOR-9743 ByteReadChannel.readLineStrict: Misleading definition of limit parameter in KDocs
            • KTOR-9504 Improve KDoc for EmbeddedServer.addShutdownHook

            Bugfixes

            • KTOR-9741 CIO: request-handler coroutine leak on half-closed idle connection after ≥1MB response
            • KTOR-7661 Maven + YAML config file does not work
            • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
            • KTOR-9704 UTF-8 encoding in ktor-io bypasses JVM String.getBytes intrinsics, slowing every text response by 5-7x
            • KTOR-9588 SSE client (CIO engine) sends spurious Content-Length: 0 on body-less GET (RFC 9110 § 8.6 violation; rejected by AWS ELB)
            • KTOR-9679 ByteReadChannel.readLine corrupts multi-byte UTF-8 by splitting a character at a buffer boundary
            • KTOR-9767 ByteReadChannel.readLineTo: Return read bytes count instead of decoded characters count
            • KTOR-8992 HoconConfigLoader is not loaded when ktor-server-config-yaml on the classpath
            • KTOR-9698 OpenAPI: schema for kotlinx.serialization sealed types omits discriminator property in variant schemas
            • KTOR-9690 Netty: in-flight requests are aborted on stop(), ignoring shutdown grace period
            • KTOR-8903 Netty: Close channel connection on errors properly
            • KTOR-9707 SaveBody performance regression
            • KTOR-9703 Inefficient text reading function in DefaultTransform
            • KTOR-9711 Netty HTTP/2: a single client-canceled stream (RST_STREAM) breaks response flushing for the whole connection
            • KTOR-9688 RateLimit: No access to principal when wrapped with authentication since 3.5.1
            • KTOR-9677 Auto-reloading: server is reloaded only once since 3.5.0
            • KTOR-9683 Sockets: Canceling a SelectorManager job doesn't close it properly
            • KTOR-9671 Darwin: Semicolons in URL path are sanitized
            Commits
            • 01c469a Release 3.5.2 (#5781)
            • c965a0c KTOR-9741 Prevent CIO request handler leak (#5783)
            • 74df458 chore: fix some comments to improve readability
            • 2672567 [KTOR-9704] Optimized encoding flow for UTF-8 (#5743)
            • d5680ed KTOR-5391: Document ECDSA certificate limitation for client authentication (#...
            • 9b0f356 something::javaClass.name is not the name of the class of something but `"j...
            • 58256cd KTOR-9743 Clarify readLine documentation
            • 088bd60 KTOR-9679 Fix regressions caused by adding buffering
            • 167176a KTOR-9588 Do not send Content-Length: 0 for body-less GET requests (#5620)
            • 7fb0844 KTOR-9585 Fix schema title trimming
            • Additional commits viewable in compare view

            Most Recent Ignore Conditions Applied to This Pull Request | Dependency Name | Ignore Conditions | | --- | --- | | io.ktor:ktor-bom | [>= 3.4.a, < 3.5] |
            Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlinlang-mcp-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml index feecf5b..cf37f51 100644 --- a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] kotlin = "2.4.10" -ktor = "3.5.1" +ktor = "3.5.2" mcp-kotlin = "0.15.0" serialization = "1.11.0" coroutines = "1.11.0" From 9482283aa325a04973a890cdfb09cd6bd11df66c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 17:03:14 +0200 Subject: [PATCH 417/449] chore(deps): bump io.ktor:ktor-bom from 3.5.1 to 3.5.2 in /samples/simple-streamable-server (#947) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [io.ktor:ktor-bom](https://github.com/ktorio/ktor) from 3.5.1 to 3.5.2.
            Release notes

            Sourced from io.ktor:ktor-bom's releases.

            3.5.2

            Published 31 July 2026

            Improvements

            • KTOR-8032 RateLimit: Allow limit requests based on authentication result
            • KTOR-9719 ApplicationCall.isStaticContent returns false when called from plugin interceptors
            • KTOR-8450 ContentNegotiation: it doesn't handle content type suffixes
            • KTOR-7892 SwaggerUI: Missing oauth2-redirect.html support in the plugin
            • KTOR-9759 Reject whitespace in URL hosts during parsing
            • KTOR-9681 Update kotlinx-io to 0.9.1
            • KTOR-9743 ByteReadChannel.readLineStrict: Misleading definition of limit parameter in KDocs
            • KTOR-9504 Improve KDoc for EmbeddedServer.addShutdownHook

            Bugfixes

            • KTOR-9741 CIO: request-handler coroutine leak on half-closed idle connection after ≥1MB response
            • KTOR-7661 Maven + YAML config file does not work
            • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
            • KTOR-9704 UTF-8 encoding in ktor-io bypasses JVM String.getBytes intrinsics, slowing every text response by 5-7x
            • KTOR-9588 SSE client (CIO engine) sends spurious Content-Length: 0 on body-less GET (RFC 9110 § 8.6 violation; rejected by AWS ELB)
            • KTOR-9679 ByteReadChannel.readLine corrupts multi-byte UTF-8 by splitting a character at a buffer boundary
            • KTOR-9767 ByteReadChannel.readLineTo: Return read bytes count instead of decoded characters count
            • KTOR-8992 HoconConfigLoader is not loaded when ktor-server-config-yaml on the classpath
            • KTOR-9698 OpenAPI: schema for kotlinx.serialization sealed types omits discriminator property in variant schemas
            • KTOR-9690 Netty: in-flight requests are aborted on stop(), ignoring shutdown grace period
            • KTOR-8903 Netty: Close channel connection on errors properly
            • KTOR-9707 SaveBody performance regression
            • KTOR-9703 Inefficient text reading function in DefaultTransform
            • KTOR-9711 Netty HTTP/2: a single client-canceled stream (RST_STREAM) breaks response flushing for the whole connection
            • KTOR-9688 RateLimit: No access to principal when wrapped with authentication since 3.5.1
            • KTOR-9677 Auto-reloading: server is reloaded only once since 3.5.0
            • KTOR-9683 Sockets: Canceling a SelectorManager job doesn't close it properly
            • KTOR-9671 Darwin: Semicolons in URL path are sanitized
            Changelog

            Sourced from io.ktor:ktor-bom's changelog.

            3.5.2

            Published 31 July 2026

            Improvements

            • KTOR-8032 RateLimit: Allow limit requests based on authentication result
            • KTOR-9719 ApplicationCall.isStaticContent returns false when called from plugin interceptors
            • KTOR-8450 ContentNegotiation: it doesn't handle content type suffixes
            • KTOR-7892 SwaggerUI: Missing oauth2-redirect.html support in the plugin
            • KTOR-9759 Reject whitespace in URL hosts during parsing
            • KTOR-9681 Update kotlinx-io to 0.9.1
            • KTOR-9743 ByteReadChannel.readLineStrict: Misleading definition of limit parameter in KDocs
            • KTOR-9504 Improve KDoc for EmbeddedServer.addShutdownHook

            Bugfixes

            • KTOR-9741 CIO: request-handler coroutine leak on half-closed idle connection after ≥1MB response
            • KTOR-7661 Maven + YAML config file does not work
            • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
            • KTOR-9704 UTF-8 encoding in ktor-io bypasses JVM String.getBytes intrinsics, slowing every text response by 5-7x
            • KTOR-9588 SSE client (CIO engine) sends spurious Content-Length: 0 on body-less GET (RFC 9110 § 8.6 violation; rejected by AWS ELB)
            • KTOR-9679 ByteReadChannel.readLine corrupts multi-byte UTF-8 by splitting a character at a buffer boundary
            • KTOR-9767 ByteReadChannel.readLineTo: Return read bytes count instead of decoded characters count
            • KTOR-8992 HoconConfigLoader is not loaded when ktor-server-config-yaml on the classpath
            • KTOR-9698 OpenAPI: schema for kotlinx.serialization sealed types omits discriminator property in variant schemas
            • KTOR-9690 Netty: in-flight requests are aborted on stop(), ignoring shutdown grace period
            • KTOR-8903 Netty: Close channel connection on errors properly
            • KTOR-9707 SaveBody performance regression
            • KTOR-9703 Inefficient text reading function in DefaultTransform
            • KTOR-9711 Netty HTTP/2: a single client-canceled stream (RST_STREAM) breaks response flushing for the whole connection
            • KTOR-9688 RateLimit: No access to principal when wrapped with authentication since 3.5.1
            • KTOR-9677 Auto-reloading: server is reloaded only once since 3.5.0
            • KTOR-9683 Sockets: Canceling a SelectorManager job doesn't close it properly
            • KTOR-9671 Darwin: Semicolons in URL path are sanitized
            Commits
            • 01c469a Release 3.5.2 (#5781)
            • c965a0c KTOR-9741 Prevent CIO request handler leak (#5783)
            • 74df458 chore: fix some comments to improve readability
            • 2672567 [KTOR-9704] Optimized encoding flow for UTF-8 (#5743)
            • d5680ed KTOR-5391: Document ECDSA certificate limitation for client authentication (#...
            • 9b0f356 something::javaClass.name is not the name of the class of something but `"j...
            • 58256cd KTOR-9743 Clarify readLine documentation
            • 088bd60 KTOR-9679 Fix regressions caused by adding buffering
            • 167176a KTOR-9588 Do not send Content-Length: 0 for body-less GET requests (#5620)
            • 7fb0844 KTOR-9585 Fix schema title trimming
            • Additional commits viewable in compare view

            Most Recent Ignore Conditions Applied to This Pull Request | Dependency Name | Ignore Conditions | | --- | --- | | io.ktor:ktor-bom | [>= 3.4.a, < 3.5] |
            Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/simple-streamable-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/simple-streamable-server/gradle/libs.versions.toml b/samples/simple-streamable-server/gradle/libs.versions.toml index 328b154..966a7e2 100644 --- a/samples/simple-streamable-server/gradle/libs.versions.toml +++ b/samples/simple-streamable-server/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] kotlin = "2.4.10" -ktor = "3.5.1" +ktor = "3.5.2" junit = "6.1.2" mcp-kotlin = "0.15.0" slf4j = "2.0.18" From 5ca99555006662ecf1300ca921c5744d304712f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 17:53:45 +0200 Subject: [PATCH 418/449] chore(deps): bump io.ktor:ktor-bom from 3.5.1 to 3.5.2 in /samples/weather-stdio-server (#948) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [io.ktor:ktor-bom](https://github.com/ktorio/ktor) from 3.5.1 to 3.5.2.
            Release notes

            Sourced from io.ktor:ktor-bom's releases.

            3.5.2

            Published 31 July 2026

            Improvements

            • KTOR-8032 RateLimit: Allow limit requests based on authentication result
            • KTOR-9719 ApplicationCall.isStaticContent returns false when called from plugin interceptors
            • KTOR-8450 ContentNegotiation: it doesn't handle content type suffixes
            • KTOR-7892 SwaggerUI: Missing oauth2-redirect.html support in the plugin
            • KTOR-9759 Reject whitespace in URL hosts during parsing
            • KTOR-9681 Update kotlinx-io to 0.9.1
            • KTOR-9743 ByteReadChannel.readLineStrict: Misleading definition of limit parameter in KDocs
            • KTOR-9504 Improve KDoc for EmbeddedServer.addShutdownHook

            Bugfixes

            • KTOR-9741 CIO: request-handler coroutine leak on half-closed idle connection after ≥1MB response
            • KTOR-7661 Maven + YAML config file does not work
            • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
            • KTOR-9704 UTF-8 encoding in ktor-io bypasses JVM String.getBytes intrinsics, slowing every text response by 5-7x
            • KTOR-9588 SSE client (CIO engine) sends spurious Content-Length: 0 on body-less GET (RFC 9110 § 8.6 violation; rejected by AWS ELB)
            • KTOR-9679 ByteReadChannel.readLine corrupts multi-byte UTF-8 by splitting a character at a buffer boundary
            • KTOR-9767 ByteReadChannel.readLineTo: Return read bytes count instead of decoded characters count
            • KTOR-8992 HoconConfigLoader is not loaded when ktor-server-config-yaml on the classpath
            • KTOR-9698 OpenAPI: schema for kotlinx.serialization sealed types omits discriminator property in variant schemas
            • KTOR-9690 Netty: in-flight requests are aborted on stop(), ignoring shutdown grace period
            • KTOR-8903 Netty: Close channel connection on errors properly
            • KTOR-9707 SaveBody performance regression
            • KTOR-9703 Inefficient text reading function in DefaultTransform
            • KTOR-9711 Netty HTTP/2: a single client-canceled stream (RST_STREAM) breaks response flushing for the whole connection
            • KTOR-9688 RateLimit: No access to principal when wrapped with authentication since 3.5.1
            • KTOR-9677 Auto-reloading: server is reloaded only once since 3.5.0
            • KTOR-9683 Sockets: Canceling a SelectorManager job doesn't close it properly
            • KTOR-9671 Darwin: Semicolons in URL path are sanitized
            Changelog

            Sourced from io.ktor:ktor-bom's changelog.

            3.5.2

            Published 31 July 2026

            Improvements

            • KTOR-8032 RateLimit: Allow limit requests based on authentication result
            • KTOR-9719 ApplicationCall.isStaticContent returns false when called from plugin interceptors
            • KTOR-8450 ContentNegotiation: it doesn't handle content type suffixes
            • KTOR-7892 SwaggerUI: Missing oauth2-redirect.html support in the plugin
            • KTOR-9759 Reject whitespace in URL hosts during parsing
            • KTOR-9681 Update kotlinx-io to 0.9.1
            • KTOR-9743 ByteReadChannel.readLineStrict: Misleading definition of limit parameter in KDocs
            • KTOR-9504 Improve KDoc for EmbeddedServer.addShutdownHook

            Bugfixes

            • KTOR-9741 CIO: request-handler coroutine leak on half-closed idle connection after ≥1MB response
            • KTOR-7661 Maven + YAML config file does not work
            • KTOR-9585 OpenAPI: JsonSchema title is truncated when it contains a dot
            • KTOR-9704 UTF-8 encoding in ktor-io bypasses JVM String.getBytes intrinsics, slowing every text response by 5-7x
            • KTOR-9588 SSE client (CIO engine) sends spurious Content-Length: 0 on body-less GET (RFC 9110 § 8.6 violation; rejected by AWS ELB)
            • KTOR-9679 ByteReadChannel.readLine corrupts multi-byte UTF-8 by splitting a character at a buffer boundary
            • KTOR-9767 ByteReadChannel.readLineTo: Return read bytes count instead of decoded characters count
            • KTOR-8992 HoconConfigLoader is not loaded when ktor-server-config-yaml on the classpath
            • KTOR-9698 OpenAPI: schema for kotlinx.serialization sealed types omits discriminator property in variant schemas
            • KTOR-9690 Netty: in-flight requests are aborted on stop(), ignoring shutdown grace period
            • KTOR-8903 Netty: Close channel connection on errors properly
            • KTOR-9707 SaveBody performance regression
            • KTOR-9703 Inefficient text reading function in DefaultTransform
            • KTOR-9711 Netty HTTP/2: a single client-canceled stream (RST_STREAM) breaks response flushing for the whole connection
            • KTOR-9688 RateLimit: No access to principal when wrapped with authentication since 3.5.1
            • KTOR-9677 Auto-reloading: server is reloaded only once since 3.5.0
            • KTOR-9683 Sockets: Canceling a SelectorManager job doesn't close it properly
            • KTOR-9671 Darwin: Semicolons in URL path are sanitized
            Commits
            • 01c469a Release 3.5.2 (#5781)
            • c965a0c KTOR-9741 Prevent CIO request handler leak (#5783)
            • 74df458 chore: fix some comments to improve readability
            • 2672567 [KTOR-9704] Optimized encoding flow for UTF-8 (#5743)
            • d5680ed KTOR-5391: Document ECDSA certificate limitation for client authentication (#...
            • 9b0f356 something::javaClass.name is not the name of the class of something but `"j...
            • 58256cd KTOR-9743 Clarify readLine documentation
            • 088bd60 KTOR-9679 Fix regressions caused by adding buffering
            • 167176a KTOR-9588 Do not send Content-Length: 0 for body-less GET requests (#5620)
            • 7fb0844 KTOR-9585 Fix schema title trimming
            • Additional commits viewable in compare view

            Most Recent Ignore Conditions Applied to This Pull Request | Dependency Name | Ignore Conditions | | --- | --- | | io.ktor:ktor-bom | [>= 3.4.a, < 3.5] |
            Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/weather-stdio-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/weather-stdio-server/gradle/libs.versions.toml b/samples/weather-stdio-server/gradle/libs.versions.toml index 5f92a6c..b84a655 100644 --- a/samples/weather-stdio-server/gradle/libs.versions.toml +++ b/samples/weather-stdio-server/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] coroutines = "1.11.0" kotlin = "2.4.10" -ktor = "3.5.1" +ktor = "3.5.2" logging = "8.0.4" mcp-kotlin = "0.15.0" shadow = "9.6.1" From d6235597e07541c07a70053e516668b5b881f4d6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Aug 2026 00:12:50 +0200 Subject: [PATCH 419/449] chore(deps): bump gradle-wrapper from 9.6.1 to 9.7.0 (#949) Bumps [gradle-wrapper](https://github.com/gradle/gradle) from 9.6.1 to 9.7.0.
            Release notes

            Sourced from gradle-wrapper's releases.

            9.7.0

            The Gradle team is excited to announce Gradle 9.7.0.

            Here are the highlights of this release:

            • Isolated Projects graduates to incubating
            • Broader Configuration Cache compatibility
            • More source locations in problem reports

            Read the Release Notes

            We would like to thank the following community members for their contributions to this release of Gradle: Adam, Aman Gautam, Aman Kumar, Anton Dubrouski, Aurimas, gbhavya07, Josh Friend, nicklauslittle-gov, Pragati, project516, Qin Mi, Ravi, sk-reddy17, Suvrat Acharya, Yongshun Ye.

            Upgrade instructions

            Switch your build to use Gradle 9.7.0 by updating your wrapper:

            ./gradlew :wrapper --gradle-version=9.7.0 &&
            ./gradlew :wrapper
            

            See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

            For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

            Reporting problems

            If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

            We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

            9.7.0 RC3

            The Gradle team is excited to announce Gradle 9.7.0 RC3.

            Here are the highlights of this release:

            • Isolated Projects graduates to incubating

            ... (truncated)

            Commits
            • 3defbfc make DistributionIntegrationSpec more permissive for releases (#38766)
            • f176043 make DistributionIntegrationSpec more permissive for releases
            • b0828da Prepare release notes for Gradle 9.7.0GA (#38756)
            • 064b6d6 cleanup
            • dfe7bdc add new training to release notes
            • 7561968 add release notes for 37801
            • 68a1f35 cherrypick 38367 to release
            • a462c95 Rebalance AllVersionsCrossVersion buckets for agents without TestDistribution...
            • 820e2b1 Update Gradle wrapper to version 9.7.0-rc-3 (#38743)
            • 75ea3d7 Update Gradle wrapper to version 9.7.0-rc-3
            • Additional commits viewable in compare view

            Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/wrapper/gradle-wrapper.jar | Bin 48462 -> 47505 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index b1b8ef56b44f16b14dc800fa8103a6d89abb526f..eddabd2eef8d94a5437d6168ff9c87a78ff725b3 100644 GIT binary patch delta 39079 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 F{Rc_uT_ykk delta 40101 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a9db115..69dd0d0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.0-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 From de2a0aafd6bf070321ed29926132dbc14b3679ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Aug 2026 11:28:03 +0200 Subject: [PATCH 420/449] chore(deps): bump dev.detekt:detekt-gradle-plugin from 2.0.0-alpha.5 to 2.0.0-alpha.6 (#950) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [dev.detekt:detekt-gradle-plugin](https://github.com/detekt/detekt) from 2.0.0-alpha.5 to 2.0.0-alpha.6.
            Release notes

            Sourced from dev.detekt:detekt-gradle-plugin's releases.

            v2.0.0-alpha.6

            2.0.0-alpha.6 - 2026-08-04

            This is an alpha release of Detekt 2.0.0. It's built against Kotlin 2.4.10, Gradle 9.6.1, AGP 9.3.1 and is tested against JDK 25.

            Notable Changes

            • detekt-rules-ktlint-wrapper now targets JVM 17, in preparation for the next ktlint major release which requires JVM 17+, the rest of detekt still targets JVM 8+ - #9453

            Changelog

            • feat(rules): add ignoreSealedClasses option to AbstractClassCanBeInterface - #9557
            • docs: drop stale Android compile classpath warning note - #9550
            • Recommend to use Locale.ROOT instead of Locale.US - #9532
            • Recognize params swallowed by KDoc lexer in OutdatedDocumentation - #9529
            • Fix UnusedPrivateProperty false negative for constructor properties - #9491
            • Fix MissingSuperCall false positive when annotation is in grandparent - #9469
            • Fix UseDataClass false positive for properties with a custom accessor - #9462
            • Fix a potential bug on Config.getListOrDefault - #9461
            • Fix UnusedPrivateFunction false positive for overloaded private extension functions with a Java (or generated) receiver type - #9450
            • Fix CanBeNonNullable false negative on local variables - #9432
            • Fix CognitiveComplexMethod false positive for object literal methods - #9431
            • Fix ReturnCount/ThrowsCount false positive where a guard clause following a local variable declaration was still counted (with excludeGuardClauses enabled) - #9424
            • Extend PropertyUsedBeforeDeclaration to follow private init calls - #9334
            • Add configuration ignoredFullyQualifiedNames to UnnecessaryFullyQualifiedName rule - #9301
            • Expose multiline parameter in ClassSignature - #9084

            Dependency Updates

            • Update agp to v9.3.1 - #9538
            • Update kotlin monorepo to v2.4.10 - #9518
            • Update Gradle to v9.6.1 - #9444
            • Update Gradle to v9.6.0 - #9420

            Housekeeping & Refactorings

            • Reduce scope of @Suppress("UNCHECKED_CAST") - #9459
            • Remove irrelevant test for YamlConfig - #9457
            • fix: handle trailing commas in @​Configuration annotation parsing in JS generator - #9447
            • Production code should not depend on fixtures - #9439

            Contributors

            We would like to thank the following contributors that made this release possible: @​3flex, @​BraisGabin, @​ChristophyBarth, @​FilippoVigani, @​Phoenix1808, @​ShubhamWorks02, @​cortinico, @​e5l, @​eyupcanakman, @​iuryeng, @​loganrosen, @​marpalat, @​roian6, @​sahilpatel2521, @​tonytonycoder11

            See all issues at: 2.0.0

            Commits
            • 401c64b Update github/codeql-action digest to 5595cca (#9569)
            • 7d92d70 Migrate to the OSS Community Develocity Instance (#9549)
            • 7212708 Update gradle/actions digest to 9c97196 (#9568)
            • 529f2ba Update github/codeql-action digest to d1ba80a (#9567)
            • 6df1cf1 feat(rules): add ignoreSealedClasses option to AbstractClassCanBeInterface (#...
            • 6fa04a6 Update actions/setup-java digest to b6effb0 (#9560)
            • d744c2f Update plugin com.gradle.common-custom-user-data-gradle-plugin to v2.8.0 (#9561)
            • 688c42b Update github/codeql-action digest to f205ea1 (#9559)
            • a2f4740 fix(doc): remove mention of fixed issue (#9556)
            • 1ac699e Fix UnusedPrivateProperty false negative (#9491)
            • Additional commits viewable in compare view

            Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7257a7d..f8ccbd9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ # plugins version atomicfu = "0.33.0" binaryCompatibilityValidatorPlugin = "0.18.1" -detekt = "2.0.0-alpha.5" +detekt = "2.0.0-alpha.6" dokka = "2.2.0" kotlin = "2.4.10" kover = "0.9.9" From 450f9e082ce1be2584e561e09737b88aec86a372 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Aug 2026 12:01:50 +0200 Subject: [PATCH 421/449] chore(deps): bump the all-dependencies group across 1 directory with 2 updates (#954) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the all-dependencies group with 2 updates in the /integration-test/src/jvmTest/typescript directory: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) and [tsx](https://github.com/privatenumber/tsx). Updates `@types/node` from 26.1.2 to 26.2.0
            Commits

            Updates `tsx` from 4.23.4 to 4.23.11
            Release notes

            Sourced from tsx's releases.

            v4.23.11

            4.23.11 (2026-08-07)

            Bug Fixes

            • preserve async ESM require fallback (55cbece)

            This release is also available on:

            v4.23.10

            4.23.10 (2026-08-07)

            Bug Fixes


            This release is also available on:

            v4.23.9

            4.23.9 (2026-08-06)

            Bug Fixes

            • map Node test locations (2f55884)
            • support data URLs in tsImport (b94f46f)

            This release is also available on:

            v4.23.8

            4.23.8 (2026-08-05)

            Bug Fixes

            • preserve package subpath resolution (be1315e)
            • preserve typeless ESM dependency exports (70dfc5e)

            This release is also available on:

            ... (truncated)

            Commits
            • bd3bc64 test: cover CommonJS loader source fallback
            • 55cbece fix: preserve async ESM require fallback
            • 6c5ba85 docs: document CommonJS default interop
            • ec1bcd5 fix: support nyc coverage discovery (#710)
            • b6e5b48 docs: clarify CommonJS default imports
            • 2f55884 fix: map Node test locations
            • de935d5 docs: document Node source-map stack formatting
            • b94f46f fix: support data URLs in tsImport
            • be1315e fix: preserve package subpath resolution
            • 5efba41 docs: organize transform backend research
            • Additional commits viewable in compare view

            Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
            Dependabot commands and options
            You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
            Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../src/jvmTest/typescript/package-lock.json | 16 ++++++++-------- .../src/jvmTest/typescript/package.json | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index b014f88..fcf0b0d 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -15,9 +15,9 @@ "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^26.1.2", + "@types/node": "^26.2.0", "cors": "^2.8.6", - "tsx": "^4.23.4", + "tsx": "^4.23.11", "typescript": "^7.0.2" } }, @@ -579,9 +579,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "26.1.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.2.tgz", - "integrity": "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg==", + "version": "26.2.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz", + "integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==", "dev": true, "license": "MIT", "dependencies": { @@ -2034,9 +2034,9 @@ } }, "node_modules/tsx": { - "version": "4.23.4", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.4.tgz", - "integrity": "sha512-ZiUQ8oT/KzN51mJUWPqARYqwFLFJZtGZipRkw1ynHMr9vy3eU77m5yfF3Gzm6meEg/beW+lUu3fHYgskTN2oVQ==", + "version": "4.23.11", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.11.tgz", + "integrity": "sha512-Ry2oTEUnhBdeEdWIztY8kf3/nBGnPnjMLVGL0YfdRXMORuPER5NlKmayqxtxRxwB1xBN+RivRaJfe7PM1rtiyw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/integration-test/src/jvmTest/typescript/package.json b/integration-test/src/jvmTest/typescript/package.json index 240e861..f5be3b4 100644 --- a/integration-test/src/jvmTest/typescript/package.json +++ b/integration-test/src/jvmTest/typescript/package.json @@ -16,9 +16,9 @@ "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^26.1.2", + "@types/node": "^26.2.0", "cors": "^2.8.6", - "tsx": "^4.23.4", + "tsx": "^4.23.11", "typescript": "^7.0.2" } } From d9b42aed683d00243a722e11be10d003baa0a3d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Aug 2026 12:31:41 +0200 Subject: [PATCH 422/449] chore(deps): bump the all-actions group across 1 directory with 2 updates (#952) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the all-actions group with 2 updates in the / directory: [gradle/actions](https://github.com/gradle/actions) and [github/codeql-action](https://github.com/github/codeql-action). Updates `gradle/actions` from 6.2.0 to 6.3.0
            Release notes

            Sourced from gradle/actions's releases.

            v6.3.0

            Highlights

            Enhanced Caching: Windows fixes and a cache-protocol bump

            This release updates gradle-actions-caching to v1.0.0 (up from v0.7.0), which fixes two significant caching defects, both most visible on Windows:

            • Cache entries failed to store at all on Windows.. Every entry failed with Path Validation Error: Path(s) specified in the action for caching do(es) not exist, even though the Gradle User Home was fully intact. Nothing was stored, so every downstream job ran against an empty Gradle User Home. The cause was a nested, unpatched copy of @actions/glob combined with a silently swallowed require() in the bundle, which left Windows path separators unnormalized.

            • Cache cleanup deleted instrumented jars that were in use. A bug in key hashing for paths shorter than 64 characters made cleanup judge freshly created caches/jars-9 entries as unused and remove them, so the instrumented-jars entry was never saved and every job re-instrumented its classpaths.

              Also included: cache entry names are now consistent between the save and restore reports — restore previously fell back to showing the raw glob pattern (e.g. /home/runner/.gradle/caches/modules-*/files-*/*/*/*/*/) instead of dependencies.

            [!IMPORTANT] Existing cache entries are invalidated by this release. The cache protocol version was bumped to v2, so the first run after upgrading will be a cache miss and will repopulate the cache. No configuration changes are required.

            Basic caching warns instead of failing silently

            The basic (open-source) caching provider now emits a warning and reports (Entry not saved: save failed) in the Job Summary when a cache save fails, rather than reporting success (#1028).

            Dependency submission works with Isolated Projects

            dependency-submission now disables Isolated Projects via a promoted property, so dependency graph generation works on builds that enable it (#1025). Thanks to @​reinsch82 for the contribution.

            Updated defaults

            • Injected Develocity Gradle plugin: 4.4.2 → 4.5.0
            • 36 new known-good wrapper checksums added for wrapper-validation

            What's Changed

            ... (truncated)

            Commits
            • 9c97196 Bump the github-actions group across 2 directories with 9 updates (#1024)
            • 760e4a4 Bump the npm-dependencies group across 1 directory with 2 updates (#1037)
            • 73e4c42 Update gradle-actions-caching library to v1.0.0 (#1029)
            • a9d1438 Add dependabot ignore rules for TypeScript 7.x and @​types/node 25.x/26.x
            • 68f3700 [bot] Update dist directory
            • 5971332 Bump Gradle Wrapper to 9.6.1, wrapper checksums, and Develocity plugin to 4.5...
            • b5bc804 [bot] Update dist directory
            • dcbab4e Bump npm-dependencies group with TypeScript 6.0.3, @​types/node 24.x, and secu...
            • ca8d957 Move non-smoke restore-gradle-home tests back to the integ-test suite (#1032)
            • 4318659 [bot] Update dist directory
            • Additional commits viewable in compare view

            Updates `github/codeql-action` from 4.37.4 to 4.37.6
            Release notes

            Sourced from github/codeql-action's releases.

            v4.37.6

            • Changed the default filepath for the new remote file address format that was introduced in CodeQL Action 4.37.0 / 3.37.0 to .github/codeql-config.yml to align it with the suggested path that is used elsewhere. #4070

            v4.37.5

            • Fixed a bug where a network error while streaming the download of the CodeQL bundle could terminate the init Action instead of falling back to downloading the bundle before extracting it. #4061
            Changelog

            Sourced from github/codeql-action's changelog.

            4.37.6 - 04 Aug 2026

            • Changed the default filepath for the new remote file address format that was introduced in CodeQL Action 4.37.0 / 3.37.0 to .github/codeql-config.yml to align it with the suggested path that is used elsewhere. #4070

            4.37.5 - 03 Aug 2026

            • Fixed a bug where a network error while streaming the download of the CodeQL bundle could terminate the init Action instead of falling back to downloading the bundle before extracting it. #4061
            Commits
            • 5595cca Merge pull request #4071 from github/update-v4.37.6-6a9359a1b
            • ec9c757 Add change note for PR 4070
            • 45c8742 Update changelog for v4.37.6
            • 6a9359a Merge pull request #4070 from github/mbg/remote-address/change-file-default
            • 065cdc0 Change DEFAULT_CONFIG_FILE_NAME
            • f99dd5a Merge pull request #4066 from github/dependabot/npm_and_yarn/js-yaml-5.2.2
            • 1804b21 Merge pull request #4068 from github/mergeback/v4.37.5-to-main-d1ba80a1
            • 3020a2f Rebuild
            • 93c3a5a Update changelog and version after v4.37.5
            • d1ba80a Merge pull request #4067 from github/update-v4.37.5-1cd4d01d5
            • Additional commits viewable in compare view

            Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/apidocs.yaml | 2 +- .github/workflows/build.yml | 6 +++--- .github/workflows/codeql.yml | 6 +++--- .github/workflows/conformance.yml | 8 ++++---- .github/workflows/gradle-publish.yml | 2 +- .github/workflows/samples.yml | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/apidocs.yaml b/.github/workflows/apidocs.yaml index 8887169..c3967d6 100644 --- a/.github/workflows/apidocs.yaml +++ b/.github/workflows/apidocs.yaml @@ -34,7 +34,7 @@ jobs: cache: gradle - name: Setup Gradle - uses: gradle/actions/setup-gradle@v6.2.0 + uses: gradle/actions/setup-gradle@v6.3.0 - name: Generate Dokka Site run: |- diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0707d44..fb4f43e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -63,7 +63,7 @@ jobs: run: npm ci - name: Setup Gradle - uses: gradle/actions/setup-gradle@v6.2.0 + uses: gradle/actions/setup-gradle@v6.3.0 with: validate-wrappers: true add-job-summary: 'always' @@ -135,7 +135,7 @@ jobs: distribution: ${{ env.JAVA_DISTRIBUTION }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v6.2.0 + uses: gradle/actions/setup-gradle@v6.3.0 with: validate-wrappers: true add-job-summary: 'always' @@ -177,7 +177,7 @@ jobs: distribution: ${{ env.JAVA_DISTRIBUTION }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v6.2.0 + uses: gradle/actions/setup-gradle@v6.3.0 with: add-job-summary: 'always' cache-read-only: true diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 368f044..44e903b 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -34,13 +34,13 @@ jobs: java-version: ${{ env.JAVA_VERSION }} - name: Initialize CodeQL - uses: github/codeql-action/init@v4.37.4 + uses: github/codeql-action/init@v4.37.6 with: languages: java-kotlin build-mode: manual - name: Setup Gradle - uses: gradle/actions/setup-gradle@v6.2.0 + uses: gradle/actions/setup-gradle@v6.3.0 with: validate-wrappers: true cache-read-only: true @@ -52,6 +52,6 @@ jobs: --no-daemon --rerun-tasks --stacktrace - name: Analyze - uses: github/codeql-action/analyze@v4.37.4 + uses: github/codeql-action/analyze@v4.37.6 with: category: '/language:java-kotlin' diff --git a/.github/workflows/conformance.yml b/.github/workflows/conformance.yml index 0605b10..908107f 100644 --- a/.github/workflows/conformance.yml +++ b/.github/workflows/conformance.yml @@ -34,7 +34,7 @@ jobs: distribution: ${{ env.JAVA_DISTRIBUTION }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v6.2.0 + uses: gradle/actions/setup-gradle@v6.3.0 with: cache-read-only: ${{ github.ref != 'refs/heads/main' }} @@ -85,7 +85,7 @@ jobs: distribution: ${{ env.JAVA_DISTRIBUTION }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v6.2.0 + uses: gradle/actions/setup-gradle@v6.3.0 with: cache-read-only: ${{ github.ref != 'refs/heads/main' }} @@ -116,7 +116,7 @@ jobs: distribution: ${{ env.JAVA_DISTRIBUTION }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v6.2.0 + uses: gradle/actions/setup-gradle@v6.3.0 with: cache-read-only: ${{ github.ref != 'refs/heads/main' }} @@ -155,7 +155,7 @@ jobs: distribution: ${{ env.JAVA_DISTRIBUTION }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v6.2.0 + uses: gradle/actions/setup-gradle@v6.3.0 with: cache-read-only: ${{ github.ref != 'refs/heads/main' }} diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml index c9dc966..1cf24e4 100644 --- a/.github/workflows/gradle-publish.yml +++ b/.github/workflows/gradle-publish.yml @@ -30,7 +30,7 @@ jobs: node-version: '22' # increase only after https://github.com/nodejs/node/issues/56645 will be fixed - name: Setup Gradle - uses: gradle/actions/setup-gradle@v6.2.0 + uses: gradle/actions/setup-gradle@v6.3.0 - name: Check run: ./gradlew check --no-configuration-cache diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index 812fbb9..463c024 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -45,7 +45,7 @@ jobs: distribution: ${{ env.JAVA_DISTRIBUTION }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v6.2.0 + uses: gradle/actions/setup-gradle@v6.3.0 with: validate-wrappers: true add-job-summary: 'always' From d67b0b7804b0acfd4e9c760699e1239df288f472 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 13:43:30 +0200 Subject: [PATCH 423/449] chore(deps): bump tsx from 4.23.11 to 4.23.12 in /integration-test/src/jvmTest/typescript in the all-dependencies group (#963) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the all-dependencies group in /integration-test/src/jvmTest/typescript with 1 update: [tsx](https://github.com/privatenumber/tsx). Updates `tsx` from 4.23.11 to 4.23.12
            Release notes

            Sourced from tsx's releases.

            v4.23.12

            4.23.12 (2026-08-10)

            Bug Fixes

            • shim import.meta when tokens are split by comments or newlines (#829) (ed9d330), closes #828

            This release is also available on:

            Commits
            • ed9d330 fix: shim import.meta when tokens are split by comments or newlines (#829)
            • 651f5be test: cover CommonJS TypeScript import.meta paths
            • See full diff in compare view

            [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=tsx&package-manager=npm_and_yarn&previous-version=4.23.11&new-version=4.23.12)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
            Dependabot commands and options
            You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
            Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- integration-test/src/jvmTest/typescript/package-lock.json | 8 ++++---- integration-test/src/jvmTest/typescript/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index fcf0b0d..77509cc 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -17,7 +17,7 @@ "@types/express": "^5.0.6", "@types/node": "^26.2.0", "cors": "^2.8.6", - "tsx": "^4.23.11", + "tsx": "^4.23.12", "typescript": "^7.0.2" } }, @@ -2034,9 +2034,9 @@ } }, "node_modules/tsx": { - "version": "4.23.11", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.11.tgz", - "integrity": "sha512-Ry2oTEUnhBdeEdWIztY8kf3/nBGnPnjMLVGL0YfdRXMORuPER5NlKmayqxtxRxwB1xBN+RivRaJfe7PM1rtiyw==", + "version": "4.23.12", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.12.tgz", + "integrity": "sha512-FDf4L4sYzKtzWYhU/Xm0AQFdTjdIxNo9ElTf2mxXM6k8YMHXzYUe4yODVaXP4V9uMFbVg8c0qyBccK2OOxb45Q==", "dev": true, "license": "MIT", "dependencies": { diff --git a/integration-test/src/jvmTest/typescript/package.json b/integration-test/src/jvmTest/typescript/package.json index f5be3b4..2321ffb 100644 --- a/integration-test/src/jvmTest/typescript/package.json +++ b/integration-test/src/jvmTest/typescript/package.json @@ -18,7 +18,7 @@ "@types/express": "^5.0.6", "@types/node": "^26.2.0", "cors": "^2.8.6", - "tsx": "^4.23.11", + "tsx": "^4.23.12", "typescript": "^7.0.2" } } From 815a70e7e7f3d9b6684ef6e132bd3f124d51585f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 15:54:20 +0200 Subject: [PATCH 424/449] chore(deps): bump github/codeql-action from 4.37.6 to 4.37.7 in the all-actions group across 1 directory (#962) Bumps the all-actions group with 1 update in the / directory: [github/codeql-action](https://github.com/github/codeql-action). Updates `github/codeql-action` from 4.37.6 to 4.37.7
            Release notes

            Sourced from github/codeql-action's releases.

            v4.37.7

            • Update default CodeQL bundle version to 2.26.3. #4085
            Changelog

            Sourced from github/codeql-action's changelog.

            4.37.7 - 13 Aug 2026

            • Update default CodeQL bundle version to 2.26.3. #4085
            Commits
            • ff2f1c6 Merge pull request #4093 from github/update-v4.37.7-be7a3dbb8
            • 951a133 Update changelog for v4.37.7
            • be7a3db Merge pull request #4087 from github/dependabot/npm_and_yarn/npm-minor-0aa561...
            • 9310334 Merge pull request #4086 from github/mbg/thread-action-state-to-codeql
            • b4d8a54 Rebuild
            • ab5db25 Bump the npm-minor group across 1 directory with 8 updates
            • 38055a3 Drop logger from databaseInitCluster in interface
            • 1f87aed Merge pull request #4085 from github/update-bundle/codeql-bundle-v2.26.3
            • dc1b98a Make logger available to getCodeQLForCmd
            • 6f0220e Merge pull request #4084 from github/navntoft/bump-undici
            • Additional commits viewable in compare view

            Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 44e903b..c697ff8 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -34,7 +34,7 @@ jobs: java-version: ${{ env.JAVA_VERSION }} - name: Initialize CodeQL - uses: github/codeql-action/init@v4.37.6 + uses: github/codeql-action/init@v4.37.7 with: languages: java-kotlin build-mode: manual @@ -52,6 +52,6 @@ jobs: --no-daemon --rerun-tasks --stacktrace - name: Analyze - uses: github/codeql-action/analyze@v4.37.6 + uses: github/codeql-action/analyze@v4.37.7 with: category: '/language:java-kotlin' From 86243340578cae98eda9341f72ee211e93c28760 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Aug 2026 12:08:48 +0200 Subject: [PATCH 425/449] chore(deps): bump the kotest group across 1 directory with 3 updates (#960) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the kotest group with 3 updates in the / directory: [io.kotest:kotest-assertions-core](https://github.com/kotest/kotest), [io.kotest:kotest-assertions-json](https://github.com/kotest/kotest) and [io.kotest:kotest-assertions-ktor](https://github.com/kotest/kotest). Updates `io.kotest:kotest-assertions-core` from 6.2.3 to 6.2.4
            Release notes

            Sourced from io.kotest:kotest-assertions-core's releases.

            v6.2.4

            What's Changed

            New Contributors

            Full Changelog: https://github.com/kotest/kotest/compare/6.2.3...v6.2.4

            Commits
            • 736a47b Add module-factory support to KoinExtension (#6006) (#6138)
            • cb91c7e junit testFinished improvements (#6205)
            • 5c898b4 spec tags cache (#6206)
            • 69fdc8d make ai skill available to users via claude marketplace and tie versioning to...
            • 2403f4f fix kdoc on StrictThrowableHandling (#6212)
            • f96cf2a introduce a TeamCity renderer that allows tree view and force it for our IJ p...
            • 0579271 sourceRef improvements (#6203)
            • da26f16 spring extension improvements (#6204)
            • cc5cf25 update docs to clarify that copies in shouldContainExactCopies must be 1 or...
            • 1d648f9 Update dependency io.github.classgraph:classgraph to v4.8.186 (#6201)
            • Additional commits viewable in compare view

            Updates `io.kotest:kotest-assertions-json` from 6.2.3 to 6.2.4
            Release notes

            Sourced from io.kotest:kotest-assertions-json's releases.

            v6.2.4

            What's Changed

            New Contributors

            Full Changelog: https://github.com/kotest/kotest/compare/6.2.3...v6.2.4

            Commits
            • 736a47b Add module-factory support to KoinExtension (#6006) (#6138)
            • cb91c7e junit testFinished improvements (#6205)
            • 5c898b4 spec tags cache (#6206)
            • 69fdc8d make ai skill available to users via claude marketplace and tie versioning to...
            • 2403f4f fix kdoc on StrictThrowableHandling (#6212)
            • f96cf2a introduce a TeamCity renderer that allows tree view and force it for our IJ p...
            • 0579271 sourceRef improvements (#6203)
            • da26f16 spring extension improvements (#6204)
            • cc5cf25 update docs to clarify that copies in shouldContainExactCopies must be 1 or...
            • 1d648f9 Update dependency io.github.classgraph:classgraph to v4.8.186 (#6201)
            • Additional commits viewable in compare view

            Updates `io.kotest:kotest-assertions-ktor` from 6.2.3 to 6.2.4
            Release notes

            Sourced from io.kotest:kotest-assertions-ktor's releases.

            v6.2.4

            What's Changed

            New Contributors

            Full Changelog: https://github.com/kotest/kotest/compare/6.2.3...v6.2.4

            Commits
            • 736a47b Add module-factory support to KoinExtension (#6006) (#6138)
            • cb91c7e junit testFinished improvements (#6205)
            • 5c898b4 spec tags cache (#6206)
            • 69fdc8d make ai skill available to users via claude marketplace and tie versioning to...
            • 2403f4f fix kdoc on StrictThrowableHandling (#6212)
            • f96cf2a introduce a TeamCity renderer that allows tree view and force it for our IJ p...
            • 0579271 sourceRef improvements (#6203)
            • da26f16 spring extension improvements (#6204)
            • cc5cf25 update docs to clarify that copies in shouldContainExactCopies must be 1 or...
            • 1d648f9 Update dependency io.github.classgraph:classgraph to v4.8.186 (#6201)
            • Additional commits viewable in compare view

            Updates `io.kotest:kotest-assertions-json` from 6.2.3 to 6.2.4
            Release notes

            Sourced from io.kotest:kotest-assertions-json's releases.

            v6.2.4

            What's Changed

            New Contributors

            Full Changelog: https://github.com/kotest/kotest/compare/6.2.3...v6.2.4

            Commits
            • 736a47b Add module-factory support to KoinExtension (#6006) (#6138)
            • cb91c7e junit testFinished improvements (#6205)
            • 5c898b4 spec tags cache (#6206)
            • 69fdc8d make ai skill available to users via claude marketplace and tie versioning to...
            • 2403f4f fix kdoc on StrictThrowableHandling (#6212)
            • f96cf2a introduce a TeamCity renderer that allows tree view and force it for our IJ p...
            • 0579271 sourceRef improvements (#6203)
            • da26f16 spring extension improvements (#6204)
            • cc5cf25 update docs to clarify that copies in shouldContainExactCopies must be 1 or...
            • 1d648f9 Update dependency io.github.classgraph:classgraph to v4.8.186 (#6201)
            • Additional commits viewable in compare view

            Updates `io.kotest:kotest-assertions-ktor` from 6.2.3 to 6.2.4
            Release notes

            Sourced from io.kotest:kotest-assertions-ktor's releases.

            v6.2.4

            What's Changed

            New Contributors

            Full Changelog: https://github.com/kotest/kotest/compare/6.2.3...v6.2.4

            Commits
            • 736a47b Add module-factory support to KoinExtension (#6006) (#6138)
            • cb91c7e junit testFinished improvements (#6205)
            • 5c898b4 spec tags cache (#6206)
            • 69fdc8d make ai skill available to users via claude marketplace and tie versioning to...
            • 2403f4f fix kdoc on StrictThrowableHandling (#6212)
            • f96cf2a introduce a TeamCity renderer that allows tree view and force it for our IJ p...
            • 0579271 sourceRef improvements (#6203)
            • da26f16 spring extension improvements (#6204)
            • cc5cf25 update docs to clarify that copies in shouldContainExactCopies must be 1 or...
            • 1d648f9 Update dependency io.github.classgraph:classgraph to v4.8.186 (#6201)
            • Additional commits viewable in compare view

            Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f8ccbd9..6f8aaaa 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,7 +15,7 @@ netty = "4.2.16.Final" awaitility = "4.3.0" collections-immutable = "0.5.1" coroutines = "1.11.0" -kotest = "6.2.3" +kotest = "6.2.4" kotlinx-io = "0.9.1" ktor = "3.5.2" logging = "8.0.4" From 4aa06febfcc220e1deb42411c4920185b1feb231 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Aug 2026 13:14:04 +0200 Subject: [PATCH 426/449] chore(deps): bump the other-dependencies group across 3 directories with 4 updates (#968) Bumps the other-dependencies group with 2 updates in the /samples/kotlin-mcp-client directory: [gradle-wrapper](https://github.com/gradle/gradle) and [com.anthropic:anthropic-java](https://github.com/anthropics/anthropic-sdk-java). Bumps the other-dependencies group with 2 updates in the /samples/kotlinlang-mcp-server directory: [gradle-wrapper](https://github.com/gradle/gradle) and [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback). Bumps the other-dependencies group with 2 updates in the /samples/simple-streamable-server directory: [gradle-wrapper](https://github.com/gradle/gradle) and [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit-framework). Updates `gradle-wrapper` from 9.7.0 to 9.7.1
            Release notes

            Sourced from gradle-wrapper's releases.

            9.7.1

            The Gradle team is excited to announce Gradle 9.7.1.

            This is a patch release for 9.7.0. We recommend using 9.7.1 instead of 9.7.0.

            Here are the highlights of 9.7.0 release:

            • Isolated Projects graduates to incubating
            • Broader Configuration Cache compatibility
            • Resilient Sync helps you fix broken builds
            • More source locations in problem reports

            Read the Release Notes

            We would like to thank the following community members for their contributions to this release of Gradle: Adam, Aman Gautam, Aman Kumar, Anton Dubrouski, Aurimas, gbhavya07, Josh Friend, nicklauslittle-gov, Pragati, project516, Qin Mi, Ravi, sk-reddy17, Suvrat Acharya, Yongshun Ye.

            Upgrade instructions

            Switch your build to use Gradle 9.7.1 by updating your wrapper:

            ./gradlew :wrapper --gradle-version=9.7.1 &&
            ./gradlew :wrapper
            

            See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

            For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

            Reporting problems

            If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

            We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

            Commits

            Updates `com.anthropic:anthropic-java` from 2.52.0 to 2.57.0
            Release notes

            Sourced from com.anthropic:anthropic-java's releases.

            v2.57.0

            2.57.0 (2026-08-19)

            Full Changelog: v2.56.0...v2.57.0

            Features

            • api: managed agents web search config and self hosted sandbox memory (83b03c0)

            v2.56.0

            2.56.0 (2026-08-19)

            Full Changelog: v2.55.0...v2.56.0

            Features

            • api: Files and Skills APIs are now GA; add computer use and browser use toolsets (762f342)

            v2.55.0

            2.55.0 (2026-08-18)

            Full Changelog: v2.54.0...v2.55.0

            Features

            • api: additions to files and memory stores (1f89b9b)
            • api: updates to skill, files, and user profiles (a5febda)
            • client: add helpers for accessing the workspace ID in response headers (718d5c1)
            • internal: upgrade to Gradle 9 and Kotlin 2.3 with a shared version catalog (596395e)

            Bug Fixes

            • api: remove unsupported mid_conv_system content block (89bf901)
            • client: don't let null usage fields in message_delta overwrite accumulated usage (#226) (603d2f5)
            • client: keep unknown content block and delta types when accumulating a message stream (#225) (9bf944d)
            • tool-runner: forward the server-assigned container on follow-up requests (#216) (b754c45)
            • tool-runner: send the BetaToolRunner helper header (#220) (95b359c)

            Chores

            • internal: lint and format build logic sources with ktfmt (9b52a2a)
            • internal: remove leftover prism references (7624523)

            v2.54.0

            2.54.0 (2026-08-13)

            Full Changelog: v2.53.0...v2.54.0

            ... (truncated)

            Changelog

            Sourced from com.anthropic:anthropic-java's changelog.

            2.57.0 (2026-08-19)

            Full Changelog: v2.56.0...v2.57.0

            Features

            • api: managed agents web search config and self hosted sandbox memory (83b03c0)

            2.56.0 (2026-08-19)

            Full Changelog: v2.55.0...v2.56.0

            Features

            • api: Files and Skills APIs are now GA; add computer use and browser use toolsets (762f342)

            2.55.0 (2026-08-18)

            Full Changelog: v2.54.0...v2.55.0

            Features

            • api: additions to files and memory stores (1f89b9b)
            • api: updates to skill, files, and user profiles (a5febda)
            • client: add helpers for accessing the workspace ID in response headers (718d5c1)
            • internal: upgrade to Gradle 9 and Kotlin 2.3 with a shared version catalog (596395e)

            Bug Fixes

            • api: remove unsupported mid_conv_system content block (89bf901)
            • client: don't let null usage fields in message_delta overwrite accumulated usage (#226) (603d2f5)
            • client: keep unknown content block and delta types when accumulating a message stream (#225) (9bf944d)
            • tool-runner: forward the server-assigned container on follow-up requests (#216) (b754c45)
            • tool-runner: send the BetaToolRunner helper header (#220) (95b359c)

            Chores

            • internal: lint and format build logic sources with ktfmt (9b52a2a)
            • internal: remove leftover prism references (7624523)

            2.54.0 (2026-08-13)

            Full Changelog: v2.53.0...v2.54.0

            Features

            • api: add output_behavior to dream creation (create a new memory store or update the input store in place) (a49ea9a)
            • client: add StructuredOutputConfig for combining Class<T> structured outputs with other output options (#192) (8caead3)

            ... (truncated)

            Commits
            • 8f5861c release: 2.57.0
            • 71074ed feat(api): managed agents web search config and self hosted sandbox memory
            • 76e0cfa release: 2.56.0
            • ce2a502 feat(api): Files and Skills APIs are now GA; add computer use and browser use...
            • 8752d28 release: 2.55.0
            • fb31467 feat(api): updates to skill, files, and user profiles
            • ddaf1ec fix(client): don't let null usage fields in message_delta overwrite accumulat...
            • a544b28 fix(client): keep unknown content block and delta types when accumulating a m...
            • 27a08d0 fix(tool-runner): send the BetaToolRunner helper header (#220)
            • df22a59 chore(internal): lint and format build logic sources with ktfmt
            • Additional commits viewable in compare view

            Updates `gradle-wrapper` from 9.7.0 to 9.7.1
            Release notes

            Sourced from gradle-wrapper's releases.

            9.7.1

            The Gradle team is excited to announce Gradle 9.7.1.

            This is a patch release for 9.7.0. We recommend using 9.7.1 instead of 9.7.0.

            Here are the highlights of 9.7.0 release:

            • Isolated Projects graduates to incubating
            • Broader Configuration Cache compatibility
            • Resilient Sync helps you fix broken builds
            • More source locations in problem reports

            Read the Release Notes

            We would like to thank the following community members for their contributions to this release of Gradle: Adam, Aman Gautam, Aman Kumar, Anton Dubrouski, Aurimas, gbhavya07, Josh Friend, nicklauslittle-gov, Pragati, project516, Qin Mi, Ravi, sk-reddy17, Suvrat Acharya, Yongshun Ye.

            Upgrade instructions

            Switch your build to use Gradle 9.7.1 by updating your wrapper:

            ./gradlew :wrapper --gradle-version=9.7.1 &&
            ./gradlew :wrapper
            

            See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

            For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

            Reporting problems

            If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

            We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

            Commits

            Updates `gradle-wrapper` from 9.7.0 to 9.7.1
            Release notes

            Sourced from gradle-wrapper's releases.

            9.7.1

            The Gradle team is excited to announce Gradle 9.7.1.

            This is a patch release for 9.7.0. We recommend using 9.7.1 instead of 9.7.0.

            Here are the highlights of 9.7.0 release:

            • Isolated Projects graduates to incubating
            • Broader Configuration Cache compatibility
            • Resilient Sync helps you fix broken builds
            • More source locations in problem reports

            Read the Release Notes

            We would like to thank the following community members for their contributions to this release of Gradle: Adam, Aman Gautam, Aman Kumar, Anton Dubrouski, Aurimas, gbhavya07, Josh Friend, nicklauslittle-gov, Pragati, project516, Qin Mi, Ravi, sk-reddy17, Suvrat Acharya, Yongshun Ye.

            Upgrade instructions

            Switch your build to use Gradle 9.7.1 by updating your wrapper:

            ./gradlew :wrapper --gradle-version=9.7.1 &&
            ./gradlew :wrapper
            

            See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

            For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

            Reporting problems

            If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

            We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

            Commits

            Updates `gradle-wrapper` from 9.7.0 to 9.7.1
            Release notes

            Sourced from gradle-wrapper's releases.

            9.7.1

            The Gradle team is excited to announce Gradle 9.7.1.

            This is a patch release for 9.7.0. We recommend using 9.7.1 instead of 9.7.0.

            Here are the highlights of 9.7.0 release:

            • Isolated Projects graduates to incubating
            • Broader Configuration Cache compatibility
            • Resilient Sync helps you fix broken builds
            • More source locations in problem reports

            Read the Release Notes

            We would like to thank the following community members for their contributions to this release of Gradle: Adam, Aman Gautam, Aman Kumar, Anton Dubrouski, Aurimas, gbhavya07, Josh Friend, nicklauslittle-gov, Pragati, project516, Qin Mi, Ravi, sk-reddy17, Suvrat Acharya, Yongshun Ye.

            Upgrade instructions

            Switch your build to use Gradle 9.7.1 by updating your wrapper:

            ./gradlew :wrapper --gradle-version=9.7.1 &&
            ./gradlew :wrapper
            

            See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

            For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

            Reporting problems

            If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

            We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

            Commits

            Updates `gradle-wrapper` from 9.7.0 to 9.7.1
            Release notes

            Sourced from gradle-wrapper's releases.

            9.7.1

            The Gradle team is excited to announce Gradle 9.7.1.

            This is a patch release for 9.7.0. We recommend using 9.7.1 instead of 9.7.0.

            Here are the highlights of 9.7.0 release:

            • Isolated Projects graduates to incubating
            • Broader Configuration Cache compatibility
            • Resilient Sync helps you fix broken builds
            • More source locations in problem reports

            Read the Release Notes

            We would like to thank the following community members for their contributions to this release of Gradle: Adam, Aman Gautam, Aman Kumar, Anton Dubrouski, Aurimas, gbhavya07, Josh Friend, nicklauslittle-gov, Pragati, project516, Qin Mi, Ravi, sk-reddy17, Suvrat Acharya, Yongshun Ye.

            Upgrade instructions

            Switch your build to use Gradle 9.7.1 by updating your wrapper:

            ./gradlew :wrapper --gradle-version=9.7.1 &&
            ./gradlew :wrapper
            

            See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

            For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

            Reporting problems

            If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

            We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

            Commits

            Updates `ch.qos.logback:logback-classic` from 1.6.1 to 1.6.3
            Release notes

            Sourced from ch.qos.logback:logback-classic's releases.

            Logback 1.6.3

            2026-08-14 Release of logback version 1.6.3

            • In response CVE-2026-19880, MDCBasedDiscriminator (used by SiftingAppender) now strips forward and backward slashes (/, \) from MDC values before they are used as discriminating keys. This prevents path segments from escaping into destinations controlled by an attacker. When sanitisation actually changes a value, a warning is emitted; the warning is rate-limited (a small batch, then a lull of about ten minutes).

            • Colour console support is split out into a dedicated JansiConsoleAppender. It wraps stdout or stderr with Jansi so ANSI escape sequences (for example coloured patterns) render correctly on terminals that need it, notably Windows. Prefer this class over the older path described next. See the appenders documentation.

            • The withJansi property on ConsoleAppender is deprecated. Existing configurations that still set <withJansi>true</withJansi> continue to work for compatibility, but new setups should use JansiConsoleAppender instead.

            • ConsoleAppender no longer treats the process console as an exclusive resource: stopping it does not close System.out / System.err. JansiConsoleAppender pairs each AnsiConsole.systemInstall() with systemUninstall() on stop, so repeated start/stop cycles do not leave Jansi installed or tear down streams shared with the rest of the JVM. Related behavior is covered by tests for issues/1063.

            • Invocation throttling helpers were reworked: SimpleInvocationGate is renamed FixedIntervalInvocationGate, and BatchedFixedIntervalInvocationGate allows a short burst of invocations before applying a fixed lull. The sanitisation warning above uses the batched gate.

            • The JPMS module-info for logback-core now exports the ch.qos.logback.core.property package, which had been missing from the module descriptor.

            • A bit-wise identical binary of this version can be reproduced by building from source code at commit e8e824dede022a6d7208b36cfa875b0d1b7772f3 associated with the tag v_1.6.3. The release was built using Java "21" 2023-10-17 LTS build 21.0.1.+12-LTS-29 under Linux Debian 11.6.

            -- Sponsoring SLF4J/logback/reload4j at https://github.com/sponsors/qos-ch

            Logback 1.6.2

            https://github.com/user-attachments/assets/9ceaf157-b758-4188-815d-edfe4e1b4edd

            2026-08-10 Release of logback version 1.6.2

            • Configuration analysis now detects contradictory caller-data inclusion instructions. For example, an AsyncAppender, SocketAppender or SMTPAppender with includeCallerData left at the default false is incompatible with a layout or encoder pattern that uses a caller-data converter such as %C, %M, %L, %F, %l or %caller. At runtime those converters would print question marks and still incur extraction cost on a worker thread. Logback now emits a configuration-time warning when such instructions disagree. See codes.html#callerContradiction for details. This issue was reported in issues/1059 by leeychee. The initial analysis was contributed by seonwoo_jung.

            • Caller-contradiction analysis can be turned off by setting the logback.skipCallerContradictionAnalysis variable to true, either as a system property (-Dlogback.skipCallerContradictionAnalysis=true) or as a property in the configuration file:

              <property
              name="logback.skipCallerContradictionAnalysis"
              value="true"/>
              
            • SimpleSocketServer and SimpleSSLSocketServer now require an explicit client IP whitelist. On the command line, pass one or more allowed addresses (single IPs or CIDR ranges) after the configuration file. An empty whitelist means no clients are accepted. When embedding the server programmatically, register allowed addresses with addAllowedClientAddress(String) or setAllowedClientAddresses(Collection) before clients connect. See the documentation on restricting client access.

            • Added ThrowableProxyVOBuilder for assembling a ThrowableProxyVO field by field, with a corresponding ThrowableProxyVO.builder() entry point.

            • Dependency analysis handlers now run their postHandle method after child models have been processed, so checks that depend on nested appenders (such as caller-contradiction analysis) see a complete picture.

            • Updated several dependencies, including Angus Mail to 2.0.4 and Jetty (test) to 12.1.12.

            • A bit-wise identical binary of this version can be reproduced by building from source code at commit e3d78330ad1ba024fd987fd00c3ffb9cfcdb07dc associated with the tag v_1.6.2. The release was built using Java "21" 2023-10-17 LTS build 21.0.1.+12-LTS-29 under Linux Debian 11.6.

            Commits
            • e8e824d prepare release 1.6.3
            • 761821b MDCBasedDiscriminator has a gated warning mechanism
            • 53ed122 update copyright year
            • c7e2db2 rename SimpleInvocationGate as FixedIntervalInvocationGate
            • b5aa931 added BatchedSimpleInvocationGate
            • 1f22af7 add javadocs to SimpleInvocationGate
            • 638ffa7 prevent forward and backward slashes to escape to other directories
            • 7d6b9a4 add missing ch.qos.logback.core.property package
            • fa25930 add an extension path in ConsoleAppender for JansiConsoleAppender
            • c73b43f deprecate the withJansi path
            • Additional commits viewable in compare view

            Updates `gradle-wrapper` from 9.7.0 to 9.7.1
            Release notes

            Sourced from gradle-wrapper's releases.

            9.7.1

            The Gradle team is excited to announce Gradle 9.7.1.

            This is a patch release for 9.7.0. We recommend using 9.7.1 instead of 9.7.0.

            Here are the highlights of 9.7.0 release:

            • Isolated Projects graduates to incubating
            • Broader Configuration Cache compatibility
            • Resilient Sync helps you fix broken builds
            • More source locations in problem reports

            Read the Release Notes

            We would like to thank the following community members for their contributions to this release of Gradle: Adam, Aman Gautam, Aman Kumar, Anton Dubrouski, Aurimas, gbhavya07, Josh Friend, nicklauslittle-gov, Pragati, project516, Qin Mi, Ravi, sk-reddy17, Suvrat Acharya, Yongshun Ye.

            Upgrade instructions

            Switch your build to use Gradle 9.7.1 by updating your wrapper:

            ./gradlew :wrapper --gradle-version=9.7.1 &&
            ./gradlew :wrapper
            

            See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

            For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

            Reporting problems

            If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

            We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

            Commits

            Updates `gradle-wrapper` from 9.7.0 to 9.7.1
            Release notes

            Sourced from gradle-wrapper's releases.

            9.7.1

            The Gradle team is excited to announce Gradle 9.7.1.

            This is a patch release for 9.7.0. We recommend using 9.7.1 instead of 9.7.0.

            Here are the highlights of 9.7.0 release:

            • Isolated Projects graduates to incubating
            • Broader Configuration Cache compatibility
            • Resilient Sync helps you fix broken builds
            • More source locations in problem reports

            Read the Release Notes

            We would like to thank the following community members for their contributions to this release of Gradle: Adam, Aman Gautam, Aman Kumar, Anton Dubrouski, Aurimas, gbhavya07, Josh Friend, nicklauslittle-gov, Pragati, project516, Qin Mi, Ravi, sk-reddy17, Suvrat Acharya, Yongshun Ye.

            Upgrade instructions

            Switch your build to use Gradle 9.7.1 by updating your wrapper:

            ./gradlew :wrapper --gradle-version=9.7.1 &&
            ./gradlew :wrapper
            

            See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

            For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

            Reporting problems

            If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

            We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

            Commits

            Updates `gradle-wrapper` from 9.7.0 to 9.7.1
            Release notes

            Sourced from gradle-wrapper's releases.

            9.7.1

            The Gradle team is excited to announce Gradle 9.7.1.

            This is a patch release for 9.7.0. We recommend using 9.7.1 instead of 9.7.0.

            Here are the highlights of 9.7.0 release:

            • Isolated Projects graduates to incubating
            • Broader Configuration Cache compatibility
            • Resilient Sync helps you fix broken builds
            • More source locations in problem reports

            Read the Release Notes

            We would like to thank the following community members for their contributions to this release of Gradle: Adam, Aman Gautam, Aman Kumar, Anton Dubrouski, Aurimas, gbhavya07, Josh Friend, nicklauslittle-gov, Pragati, project516, Qin Mi, Ravi, sk-reddy17, Suvrat Acharya, Yongshun Ye.

            Upgrade instructions

            Switch your build to use Gradle 9.7.1 by updating your wrapper:

            ./gradlew :wrapper --gradle-version=9.7.1 &&
            ./gradlew :wrapper
            

            See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

            For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

            Reporting problems

            If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

            We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

            Commits

            Updates `gradle-wrapper` from 9.7.0 to 9.7.1
            Release notes

            Sourced from gradle-wrapper's releases.

            9.7.1

            The Gradle team is excited to announce Gradle 9.7.1.

            This is a patch release for 9.7.0. We recommend using 9.7.1 instead of 9.7.0.

            Here are the highlights of 9.7.0 release:

            • Isolated Projects graduates to incubating
            • Broader Configuration Cache compatibility
            • Resilient Sync helps you fix broken builds
            • More source locations in problem reports

            Read the Release Notes

            We would like to thank the following community members for their contributions to this release of Gradle: Adam, Aman Gautam, Aman Kumar, Anton Dubrouski, Aurimas, gbhavya07, Josh Friend, nicklauslittle-gov, Pragati, project516, Qin Mi, Ravi, sk-reddy17, Suvrat Acharya, Yongshun Ye.

            Upgrade instructions

            Switch your build to use Gradle 9.7.1 by updating your wrapper:

            ./gradlew :wrapper --gradle-version=9.7.1 &&
            ./gradlew :wrapper
            

            See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

            For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

            Reporting problems

            If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

            We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

            Commits

            Updates `org.junit.jupiter:junit-jupiter`... _Description has been truncated_ Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-client/gradle/libs.versions.toml | 2 +- .../kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties | 2 +- samples/kotlinlang-mcp-server/gradle/libs.versions.toml | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- samples/simple-streamable-server/gradle/libs.versions.toml | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index 4e1f8bb..4c2f14c 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -anthropic = "2.52.0" +anthropic = "2.57.0" kotlin = "2.4.10" ktor = "3.5.2" mcp-kotlin = "0.15.0" diff --git a/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties b/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties index 69dd0d0..ad7845b 100644 --- a/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties +++ b/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 diff --git a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml index cf37f51..a113c64 100644 --- a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml @@ -4,7 +4,7 @@ ktor = "3.5.2" mcp-kotlin = "0.15.0" serialization = "1.11.0" coroutines = "1.11.0" -logback = "1.6.1" +logback = "1.6.3" shadow = "9.6.1" [libraries] diff --git a/samples/kotlinlang-mcp-server/gradle/wrapper/gradle-wrapper.properties b/samples/kotlinlang-mcp-server/gradle/wrapper/gradle-wrapper.properties index 69dd0d0..ad7845b 100644 --- a/samples/kotlinlang-mcp-server/gradle/wrapper/gradle-wrapper.properties +++ b/samples/kotlinlang-mcp-server/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 diff --git a/samples/simple-streamable-server/gradle/libs.versions.toml b/samples/simple-streamable-server/gradle/libs.versions.toml index 966a7e2..fd03619 100644 --- a/samples/simple-streamable-server/gradle/libs.versions.toml +++ b/samples/simple-streamable-server/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] kotlin = "2.4.10" ktor = "3.5.2" -junit = "6.1.2" +junit = "6.1.3" mcp-kotlin = "0.15.0" slf4j = "2.0.18" shadow = "9.6.1" diff --git a/samples/simple-streamable-server/gradle/wrapper/gradle-wrapper.properties b/samples/simple-streamable-server/gradle/wrapper/gradle-wrapper.properties index 69dd0d0..ad7845b 100644 --- a/samples/simple-streamable-server/gradle/wrapper/gradle-wrapper.properties +++ b/samples/simple-streamable-server/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 From e06014fc8333f69683c8dbc04e299194f55e2bfb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Aug 2026 17:18:50 +0200 Subject: [PATCH 427/449] chore(deps): bump gradle-wrapper from 9.7.0 to 9.7.1 (#969) Bumps [gradle-wrapper](https://github.com/gradle/gradle) from 9.7.0 to 9.7.1.
            Release notes

            Sourced from gradle-wrapper's releases.

            9.7.1

            The Gradle team is excited to announce Gradle 9.7.1.

            This is a patch release for 9.7.0. We recommend using 9.7.1 instead of 9.7.0.

            Here are the highlights of 9.7.0 release:

            • Isolated Projects graduates to incubating
            • Broader Configuration Cache compatibility
            • Resilient Sync helps you fix broken builds
            • More source locations in problem reports

            Read the Release Notes

            We would like to thank the following community members for their contributions to this release of Gradle: Adam, Aman Gautam, Aman Kumar, Anton Dubrouski, Aurimas, gbhavya07, Josh Friend, nicklauslittle-gov, Pragati, project516, Qin Mi, Ravi, sk-reddy17, Suvrat Acharya, Yongshun Ye.

            Upgrade instructions

            Switch your build to use Gradle 9.7.1 by updating your wrapper:

            ./gradlew :wrapper --gradle-version=9.7.1 &&
            ./gradlew :wrapper
            

            See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

            For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

            Reporting problems

            If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

            We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

            Commits

            Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 69dd0d0..ad7845b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 From dfdd8820da5f4f4572a2624d25f7d7d8650d131a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Aug 2026 19:50:19 +0200 Subject: [PATCH 428/449] chore(deps): bump ch.qos.logback:logback-classic from 1.6.1 to 1.6.3 (#970) Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.6.1 to 1.6.3.
            Release notes

            Sourced from ch.qos.logback:logback-classic's releases.

            Logback 1.6.3

            2026-08-14 Release of logback version 1.6.3

            • In response CVE-2026-19880, MDCBasedDiscriminator (used by SiftingAppender) now strips forward and backward slashes (/, \) from MDC values before they are used as discriminating keys. This prevents path segments from escaping into destinations controlled by an attacker. When sanitisation actually changes a value, a warning is emitted; the warning is rate-limited (a small batch, then a lull of about ten minutes).

            • Colour console support is split out into a dedicated JansiConsoleAppender. It wraps stdout or stderr with Jansi so ANSI escape sequences (for example coloured patterns) render correctly on terminals that need it, notably Windows. Prefer this class over the older path described next. See the appenders documentation.

            • The withJansi property on ConsoleAppender is deprecated. Existing configurations that still set <withJansi>true</withJansi> continue to work for compatibility, but new setups should use JansiConsoleAppender instead.

            • ConsoleAppender no longer treats the process console as an exclusive resource: stopping it does not close System.out / System.err. JansiConsoleAppender pairs each AnsiConsole.systemInstall() with systemUninstall() on stop, so repeated start/stop cycles do not leave Jansi installed or tear down streams shared with the rest of the JVM. Related behavior is covered by tests for issues/1063.

            • Invocation throttling helpers were reworked: SimpleInvocationGate is renamed FixedIntervalInvocationGate, and BatchedFixedIntervalInvocationGate allows a short burst of invocations before applying a fixed lull. The sanitisation warning above uses the batched gate.

            • The JPMS module-info for logback-core now exports the ch.qos.logback.core.property package, which had been missing from the module descriptor.

            • A bit-wise identical binary of this version can be reproduced by building from source code at commit e8e824dede022a6d7208b36cfa875b0d1b7772f3 associated with the tag v_1.6.3. The release was built using Java "21" 2023-10-17 LTS build 21.0.1.+12-LTS-29 under Linux Debian 11.6.

            -- Sponsoring SLF4J/logback/reload4j at https://github.com/sponsors/qos-ch

            Logback 1.6.2

            https://github.com/user-attachments/assets/9ceaf157-b758-4188-815d-edfe4e1b4edd

            2026-08-10 Release of logback version 1.6.2

            • Configuration analysis now detects contradictory caller-data inclusion instructions. For example, an AsyncAppender, SocketAppender or SMTPAppender with includeCallerData left at the default false is incompatible with a layout or encoder pattern that uses a caller-data converter such as %C, %M, %L, %F, %l or %caller. At runtime those converters would print question marks and still incur extraction cost on a worker thread. Logback now emits a configuration-time warning when such instructions disagree. See codes.html#callerContradiction for details. This issue was reported in issues/1059 by leeychee. The initial analysis was contributed by seonwoo_jung.

            • Caller-contradiction analysis can be turned off by setting the logback.skipCallerContradictionAnalysis variable to true, either as a system property (-Dlogback.skipCallerContradictionAnalysis=true) or as a property in the configuration file:

              <property
              name="logback.skipCallerContradictionAnalysis"
              value="true"/>
              
            • SimpleSocketServer and SimpleSSLSocketServer now require an explicit client IP whitelist. On the command line, pass one or more allowed addresses (single IPs or CIDR ranges) after the configuration file. An empty whitelist means no clients are accepted. When embedding the server programmatically, register allowed addresses with addAllowedClientAddress(String) or setAllowedClientAddresses(Collection) before clients connect. See the documentation on restricting client access.

            • Added ThrowableProxyVOBuilder for assembling a ThrowableProxyVO field by field, with a corresponding ThrowableProxyVO.builder() entry point.

            • Dependency analysis handlers now run their postHandle method after child models have been processed, so checks that depend on nested appenders (such as caller-contradiction analysis) see a complete picture.

            • Updated several dependencies, including Angus Mail to 2.0.4 and Jetty (test) to 12.1.12.

            • A bit-wise identical binary of this version can be reproduced by building from source code at commit e3d78330ad1ba024fd987fd00c3ffb9cfcdb07dc associated with the tag v_1.6.2. The release was built using Java "21" 2023-10-17 LTS build 21.0.1.+12-LTS-29 under Linux Debian 11.6.

            Commits
            • e8e824d prepare release 1.6.3
            • 761821b MDCBasedDiscriminator has a gated warning mechanism
            • 53ed122 update copyright year
            • c7e2db2 rename SimpleInvocationGate as FixedIntervalInvocationGate
            • b5aa931 added BatchedSimpleInvocationGate
            • 1f22af7 add javadocs to SimpleInvocationGate
            • 638ffa7 prevent forward and backward slashes to escape to other directories
            • 7d6b9a4 add missing ch.qos.logback.core.property package
            • fa25930 add an extension path in ConsoleAppender for JansiConsoleAppender
            • c73b43f deprecate the withJansi path
            • Additional commits viewable in compare view

            Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6f8aaaa..c4d4f90 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,7 +22,7 @@ logging = "8.0.4" mockk = "1.14.11" serialization = "1.11.0" slf4j = "2.0.18" -logback = "1.6.1" +logback = "1.6.3" junit = "5.14.4" [libraries] From cf5cca553b81fb78c9dcf444463a89793caa4e98 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Aug 2026 20:56:55 +0200 Subject: [PATCH 429/449] chore(deps): bump github/codeql-action from 4.37.7 to 4.37.8 in the all-actions group across 1 directory (#971) Bumps the all-actions group with 1 update in the / directory: [github/codeql-action](https://github.com/github/codeql-action). Updates `github/codeql-action` from 4.37.7 to 4.37.8
            Release notes

            Sourced from github/codeql-action's releases.

            v4.37.8

            No user facing changes.

            Changelog

            Sourced from github/codeql-action's changelog.

            4.37.8 - 21 Aug 2026

            No user facing changes.

            Commits
            • db488dd Merge pull request #4102 from github/update-v4.37.8-9ee088e13
            • 1845f5b Update changelog for v4.37.8
            • 9ee088e Merge pull request #4080 from github/henrymercer/studious-giggle
            • 1aef003 Address review feedback on overlay disk flags
            • 508b83b Merge main into overlay minimum disk feature branch
            • d97b342 Merge pull request #4098 from github/mbg/permission-error-as-configuration-error
            • 47fa622 Make EACCES a ConfigurationError
            • 45693cc Refactor ENOSPC check into isDiskConfigurationError function
            • c2fd8f5 Merge pull request #4081 from github/mario-campos/version-cache-to-disk
            • c56f48e Log unexpected conditions during caching CLI output
            • Additional commits viewable in compare view

            Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c697ff8..84f0523 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -34,7 +34,7 @@ jobs: java-version: ${{ env.JAVA_VERSION }} - name: Initialize CodeQL - uses: github/codeql-action/init@v4.37.7 + uses: github/codeql-action/init@v4.37.8 with: languages: java-kotlin build-mode: manual @@ -52,6 +52,6 @@ jobs: --no-daemon --rerun-tasks --stacktrace - name: Analyze - uses: github/codeql-action/analyze@v4.37.7 + uses: github/codeql-action/analyze@v4.37.8 with: category: '/language:java-kotlin' From 52fc8878cd756afcd4d5f033ed8207c381b76ce3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 13:26:31 +0200 Subject: [PATCH 430/449] chore(deps): bump the other-dependencies group across 3 directories with 2 updates (#974) Bumps the other-dependencies group with 1 update in the /samples/kotlin-mcp-client directory: [com.anthropic:anthropic-java](https://github.com/anthropics/anthropic-sdk-java). Bumps the other-dependencies group with 1 update in the /samples/kotlin-mcp-server directory: [gradle-wrapper](https://github.com/gradle/gradle). Bumps the other-dependencies group with 1 update in the /samples/weather-stdio-server directory: [gradle-wrapper](https://github.com/gradle/gradle). Updates `com.anthropic:anthropic-java` from 2.57.0 to 2.59.0
            Release notes

            Sourced from com.anthropic:anthropic-java's releases.

            v2.59.0

            2.59.0 (2026-08-27)

            Full Changelog: v2.58.0...v2.59.0

            Features

            • api: beta files/skills namespaces use GA shapes; drop dated beta header pins (e4e0e25)

            Bug Fixes

            • aws,bedrock: don't consume the request body while SigV4 signing (#257) (95238b1)
            • client: page backward via before_id and stop on has_more=false (81f16e7)
            • webhooks: require headers to be passed to unwrap() (4b43e22)

            Documentation

            • api: clarify pagination on the organization rate-limit list endpoints (1d0e987)

            v2.58.0

            2.58.0 (2026-08-26)

            Full Changelog: v2.57.0...v2.58.0

            Features

            • api: add updates thinking display mode (beta) (792b318)
            • api: add missing anthropic-beta values (60cc62a)
            • api: add support for Organization API endpoints (3adde69)

            Bug Fixes

            • bedrock: stop clobbering body anthropic_beta with header betas (#58) (e2609a9)
            • client: let explicit and ANTHROPIC_BASE_URL base URLs override profile base_url (#241) (7486e65)
            • client: resolve base URL in ClientOptions instead of the transport (#242) (a3e988a)
            • tools: keep the tool runner going on pause_turn (#196) (557c72d)

            Chores

            • docs: clarify skill version latest support and memory version retention (cc7fc00)
            • internal: cancel superseded CI runs (ae7cc8f)
            • internal: minor workflow reformat (36b5b98)

            Documentation

            ... (truncated)

            Changelog

            Sourced from com.anthropic:anthropic-java's changelog.

            2.59.0 (2026-08-27)

            Full Changelog: v2.58.0...v2.59.0

            Features

            • api: beta files/skills namespaces use GA shapes; drop dated beta header pins (e4e0e25)

            Bug Fixes

            • aws,bedrock: don't consume the request body while SigV4 signing (#257) (95238b1)
            • client: page backward via before_id and stop on has_more=false (81f16e7)
            • webhooks: require headers to be passed to unwrap() (4b43e22)

            Documentation

            • api: clarify pagination on the organization rate-limit list endpoints (1d0e987)

            2.58.0 (2026-08-26)

            Full Changelog: v2.57.0...v2.58.0

            Features

            • api: add updates thinking display mode (beta) (792b318)
            • api: add missing anthropic-beta values (60cc62a)
            • api: add support for Organization API endpoints (3adde69)

            Bug Fixes

            • bedrock: stop clobbering body anthropic_beta with header betas (#58) (e2609a9)
            • client: let explicit and ANTHROPIC_BASE_URL base URLs override profile base_url (#241) (7486e65)
            • client: resolve base URL in ClientOptions instead of the transport (#242) (a3e988a)
            • tools: keep the tool runner going on pause_turn (#196) (557c72d)

            Chores

            • docs: clarify skill version latest support and memory version retention (cc7fc00)
            • internal: cancel superseded CI runs (ae7cc8f)
            • internal: minor workflow reformat (36b5b98)

            Documentation

            • api: clarify session thread agent and usage.iterations compaction descriptions (beta) (2283253)
            • examples: use adaptive thinking in thinking examples (da9aaba)
            Commits
            • 8722270 release: 2.59.0
            • bc1e199 docs(api): clarify pagination on the organization rate-limit list endpoints
            • 4da20e1 fix(client): page backward via before_id and stop on has_more=false
            • b7046bc feat(api): beta files/skills namespaces use GA shapes; drop dated beta header...
            • 6531a27 fix(webhooks): require headers to be passed to unwrap()
            • 8786a45 fix(aws,bedrock): don't consume the request body while SigV4 signing (#257)
            • accd19e codegen metadata
            • 51bd492 release: 2.58.0
            • 82b2544 codegen metadata
            • 7bec4e1 feat(api): add support for Organization API endpoints
            • Additional commits viewable in compare view

            Updates `gradle-wrapper` from 9.7.0 to 9.7.1
            Release notes

            Sourced from gradle-wrapper's releases.

            9.7.1

            The Gradle team is excited to announce Gradle 9.7.1.

            This is a patch release for 9.7.0. We recommend using 9.7.1 instead of 9.7.0.

            Here are the highlights of 9.7.0 release:

            • Isolated Projects graduates to incubating
            • Broader Configuration Cache compatibility
            • Resilient Sync helps you fix broken builds
            • More source locations in problem reports

            Read the Release Notes

            We would like to thank the following community members for their contributions to this release of Gradle: Adam, Aman Gautam, Aman Kumar, Anton Dubrouski, Aurimas, gbhavya07, Josh Friend, nicklauslittle-gov, Pragati, project516, Qin Mi, Ravi, sk-reddy17, Suvrat Acharya, Yongshun Ye.

            Upgrade instructions

            Switch your build to use Gradle 9.7.1 by updating your wrapper:

            ./gradlew :wrapper --gradle-version=9.7.1 &&
            ./gradlew :wrapper
            

            See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

            For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

            Reporting problems

            If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

            We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

            Commits

            Updates `gradle-wrapper` from 9.7.0 to 9.7.1
            Release notes

            Sourced from gradle-wrapper's releases.

            9.7.1

            The Gradle team is excited to announce Gradle 9.7.1.

            This is a patch release for 9.7.0. We recommend using 9.7.1 instead of 9.7.0.

            Here are the highlights of 9.7.0 release:

            • Isolated Projects graduates to incubating
            • Broader Configuration Cache compatibility
            • Resilient Sync helps you fix broken builds
            • More source locations in problem reports

            Read the Release Notes

            We would like to thank the following community members for their contributions to this release of Gradle: Adam, Aman Gautam, Aman Kumar, Anton Dubrouski, Aurimas, gbhavya07, Josh Friend, nicklauslittle-gov, Pragati, project516, Qin Mi, Ravi, sk-reddy17, Suvrat Acharya, Yongshun Ye.

            Upgrade instructions

            Switch your build to use Gradle 9.7.1 by updating your wrapper:

            ./gradlew :wrapper --gradle-version=9.7.1 &&
            ./gradlew :wrapper
            

            See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes and other considerations when upgrading.

            For Java, Groovy, Kotlin and Android compatibility, see the full compatibility notes.

            Reporting problems

            If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure you're encountering a bug, please use the forum.

            We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.

            Commits

            Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
            Dependabot commands and options
            You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
            Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-client/gradle/libs.versions.toml | 2 +- .../kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index 4c2f14c..03b7ad4 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -anthropic = "2.57.0" +anthropic = "2.59.0" kotlin = "2.4.10" ktor = "3.5.2" mcp-kotlin = "0.15.0" diff --git a/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties b/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties index 69dd0d0..ad7845b 100644 --- a/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties +++ b/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 diff --git a/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties b/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties index 69dd0d0..ad7845b 100644 --- a/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties +++ b/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 From abce6d5367f5ee5e87be5871c6298798f53710de Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Sep 2026 12:20:21 +0200 Subject: [PATCH 431/449] chore(deps): bump the all-dependencies group across 1 directory with 2 updates (#977) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the all-dependencies group with 2 updates in the /integration-test/src/jvmTest/typescript directory: [zod](https://github.com/colinhacks/zod) and [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node). Updates `zod` from 4.4.3 to 4.5.2
            Release notes

            Sourced from zod's releases.

            v4.5.2

            Commits:

            • a354314ac04fdd5484aa62dd5c3a4b553211a0e4 fix(docs): keep blog posts out of the docs collection (#6484)
            • d378c42aff6869f0929058a7923cd775880f5c4c ci: drop canary publishing from the release workflow (#6487)
            • 212b941791e7faae078e17645eb612824fd8f79a fix(v4): let a prototype method getter answer a bare call so vi.spyOn works (#6488)
            • e7576f542a7bc7ef3cc5eeec237714fd0e6b6e98 docs(blog): let the page show through the navbar in dark mode (#6489)
            • fedb06fafe33a66ce0b5c236ad2557e0a5a170fe fix(docs): match the blog TOC hover bar to the 2px active indicator
            • 6c932fcb2eea6eb671710ea058ca9fdc382ada89 chore: bump devcontainer image to Node 24 (#6470)
            • 6635d9dd367a664109de83c021995821f48efa29 docs(blog): soften the "method memoization" attribution
            • 019ae299cc75daa132bf1acf59086a520abf6b85 fix(docs): drop ISR on the docs route so the home page hydrates
            • 652bb438aa4c626c1cd7948c6849c4691239fca7 chore(docs): drop the scroll log from the route-change scroller
            • 571c8e8a3d73b4305f4abfdd6977773cc12f2bf5 fix(docs): render blog tabs with the stock fumadocs tab card
            • 9a193aa24b4efa3b315b91d4c56c8bc385b8513f 4.5.2

            v4.5.1

            Commits:

            • 2e862dbf89da2835e5206a8fd3d3be61afe3cf7f ci: gate the GitHub release and JSR publish on the version being live on npm
            • 8e03380510db36fa6fda979fc78a375fdea8021c 4.5.1

            v4.5.0

            Zod 4.5 is now available.

            npm install zod@latest
            

            At a glance:

            z.compile()

            You can now pre-compile any Zod schema using z.compile(schema). This dramatically speeds up parsing performance.

            import * as z from "zod";
            

            const Player = z.object({ username: z.string(), bio: z.string(), xp: z.number(), // ...20 more properties... </tr></table>

            ... (truncated)

            Commits
            • 9a193aa 4.5.2
            • 571c8e8 fix(docs): render blog tabs with the stock fumadocs tab card
            • 652bb43 chore(docs): drop the scroll log from the route-change scroller
            • 019ae29 fix(docs): drop ISR on the docs route so the home page hydrates
            • 6635d9d docs(blog): soften the "method memoization" attribution
            • 6c932fc chore: bump devcontainer image to Node 24 (#6470)
            • fedb06f fix(docs): match the blog TOC hover bar to the 2px active indicator
            • e7576f5 docs(blog): let the page show through the navbar in dark mode (#6489)
            • 212b941 fix(v4): let a prototype method getter answer a bare call so vi.spyOn works (...
            • d378c42 ci: drop canary publishing from the release workflow (#6487)
            • Additional commits viewable in compare view

            Updates `@types/node` from 26.2.0 to 26.4.0
            Commits

            Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
            Dependabot commands and options
            You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
            Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../src/jvmTest/typescript/package-lock.json | 16 ++++++++-------- .../src/jvmTest/typescript/package.json | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index 77509cc..f7769d6 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -10,12 +10,12 @@ "dependencies": { "@modelcontextprotocol/sdk": "^1.30.0", "express": "^5.2.1", - "zod": "^4.4.3" + "zod": "^4.5.2" }, "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^26.2.0", + "@types/node": "^26.4.0", "cors": "^2.8.6", "tsx": "^4.23.12", "typescript": "^7.0.2" @@ -579,9 +579,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "26.2.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz", - "integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==", + "version": "26.4.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.4.0.tgz", + "integrity": "sha512-faiGnoIrLH/V8cibOMEAZ8pMw6oXqSukl29ra4mN8GdaB2ZewzeaLj+INpV5N+Z1eKWzY+IzaIZH2EIR6YZRNQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2165,9 +2165,9 @@ "license": "ISC" }, "node_modules/zod": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", - "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.5.2.tgz", + "integrity": "sha512-XkYXCol10+ba/6F/cueWV+TezUeOqXW0hdeJt5CdXjTYeAgAQg5N03RQdJ80mhfFE72+pblvYMW4wy2Qp4Qbrg==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/integration-test/src/jvmTest/typescript/package.json b/integration-test/src/jvmTest/typescript/package.json index 2321ffb..312c4f8 100644 --- a/integration-test/src/jvmTest/typescript/package.json +++ b/integration-test/src/jvmTest/typescript/package.json @@ -11,12 +11,12 @@ "dependencies": { "@modelcontextprotocol/sdk": "^1.30.0", "express": "^5.2.1", - "zod": "^4.4.3" + "zod": "^4.5.2" }, "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^26.2.0", + "@types/node": "^26.4.0", "cors": "^2.8.6", "tsx": "^4.23.12", "typescript": "^7.0.2" From 358b2d5a1206304143b12a3732242b4eac8efa4f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Sep 2026 12:46:46 +0200 Subject: [PATCH 432/449] chore(deps): bump qs from 6.15.2 to 6.16.0 in /integration-test/src/jvmTest/typescript (#979) Bumps [qs](https://github.com/ljharb/qs) from 6.15.2 to 6.16.0.
            Changelog

            Sourced from qs's changelog.

            6.16.0

            • [New] stringify: add a depth option to bound recursion depth (default Infinity)
            • [Fix] stringify: serialize Date values when a filter is provided
            • [Fix] parse: enforce arrayLimit on comma groups under []= when throwOnLimitExceeded is set
            • [Fix] parse: flatten a collection appended to an overflowed array (#571)
            • [Fix] utils: isBuffer: do not invoke a non-callable constructor.isBuffer
            • [Fix] stringify: do not let allowEmptyArrays skip cycle detection (or drop own keys) on an empty array with own properties
            • [Fix] stringify: encode dots in a top-level key with a primitive value when encodeDotInKeys is set (#562)
            • [Docs] threat model: clarify stringify deep-nesting DoS is caller-bounded
            • [Docs] clarify arrayLimit is a representation threshold, not an element-count cap
            • [Tests] parse: remove a test that pinned []= comma groups escaping arrayLimit
            • [Tests] stringify: pin current encodeDotInKeys separator-dot behavior
            • [Dev Deps] update @ljharb/eslint-config, eslint
            • [Dev Deps] update eslint, evalmd

            6.15.3

            • [Fix] parse: enforce throwOnLimitExceeded for cumulative array growth via combine/merge
            • [Fix] utils: respect encoding of surrogate pairs across chunks (#559)
            • [Robustness] parse: throw the arrayLimit error before splitting oversized comma values
            • [Robustness] utils.merge / utils.assign: avoid invoking __proto__ setter when copying own properties
            • [Robustness] utils: enforce arrayLimit consistently across merge's array paths
            • [Perf] utils: make compact O(n) via a side-channel visited-set instead of Array.indexOf
            • [Deps] update side-channel
            • [Dev Deps] update eslint, mock-property, tape
            • [Tests] parse: characterize current lenient handling of unbalanced bracket keys (#558)
            Commits
            • bb9379e v6.16.0
            • 62fd254 [Fix] stringify: serialize Date values when a filter is provided
            • 8859c37 [Fix] parse: enforce arrayLimit on comma groups under []= when `throwOn...
            • 8079adc [Tests] parse: remove a test that pinned []= comma groups escaping `array...
            • d56f48c [Fix] parse: flatten a collection appended to an overflowed array
            • e83d321 [Fix] utils: isBuffer: do not invoke a non-callable constructor.isBuffer
            • 7e87a07 [Dev Deps] update @ljharb/eslint-config, eslint
            • 9a76af2 [Dev Deps] update eslint, evalmd
            • 3a890d4 [Dev Deps] update eslint, evalmd
            • b433a9b [Fix] stringify: do not let allowEmptyArrays skip cycle detection (or dro...
            • Additional commits viewable in compare view

            Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../src/jvmTest/typescript/package-lock.json | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index f7769d6..f523ea3 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -1229,9 +1229,9 @@ } }, "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -1526,9 +1526,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -1802,12 +1802,13 @@ } }, "node_modules/qs": { - "version": "6.15.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", - "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.1.0" + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" }, "engines": { "node": ">=0.6" @@ -1944,14 +1945,14 @@ } }, "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, @@ -1963,13 +1964,13 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" From d78d2d8b6fcf1ff34291c11774298b83b458f4bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Sep 2026 13:26:35 +0200 Subject: [PATCH 433/449] chore(deps): bump fast-uri from 3.1.5 to 3.1.7 in /integration-test/src/jvmTest/typescript (#978) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.5 to 3.1.7.
            Release notes

            Sourced from fast-uri's releases.

            v3.1.7

            ⚠️ Security Warning

            This is a security release that fixes the following high-severity security advisories:

            Users of the v3.x release line should upgrade to v3.1.7.

            Full Changelog: https://github.com/fastify/fast-uri/compare/v3.1.6...v3.1.7

            v3.1.6

            ⚠️ Security Warning

            This release addresses the following high-severity security advisories:

            Users of the v3.x release line should upgrade to v3.1.6.

            Full Changelog: https://github.com/fastify/fast-uri/compare/v3.1.5...v3.1.6

            Commits
            • 412e40a Bumped v3.1.7
            • 9f4c943 fix: backport port and IP-literal validation to v3.x (#216)
            • 1eb3ce4 fix: treat unterminated bracket hosts as reg-names again (#214)
            • 6f970b2 Bumped v3.1.6
            • d941579 fix: never run IDN canonicalization on bracketed IP literals
            • c0f0279 test: adapt decoded-scheme handler assertion to 3.x (no mailto scheme)
            • 37f3417 Merge commit from fork
            • 607bfbe Merge commit from fork
            • ae92a4c Merge commit from fork
            • 444ecda Merge commit from fork
            • Additional commits viewable in compare view

            Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- integration-test/src/jvmTest/typescript/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index f523ea3..a5c2f86 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -1387,9 +1387,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", - "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", + "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", "funding": [ { "type": "github", From 40a14756cafd5a2aefa14e847a1ede855435b685 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Sep 2026 11:48:16 +0000 Subject: [PATCH 434/449] chore(deps): bump the all-actions group across 1 directory with 2 updates (#975) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the all-actions group with 2 updates in the / directory: [actions/setup-java](https://github.com/actions/setup-java) and [github/codeql-action](https://github.com/github/codeql-action). Updates `actions/setup-java` from 5 to 6
            Release notes

            Sourced from actions/setup-java's releases.

            v6.0.0

            What's Changed

            ... (truncated)

            Commits

            Updates `github/codeql-action` from 4.37.8 to 4.37.9
            Release notes

            Sourced from github/codeql-action's releases.

            v4.37.9

            • Update default CodeQL bundle version to 2.26.4. #4106
            Changelog

            Sourced from github/codeql-action's changelog.

            4.37.9 - 26 Aug 2026

            • Update default CodeQL bundle version to 2.26.4. #4106
            Commits
            • cdf488f Merge pull request #4107 from github/update-v4.37.9-920ba7cd1
            • 7243f38 Update changelog for v4.37.9
            • 920ba7c Merge pull request #4106 from github/update-bundle/codeql-bundle-v2.26.4
            • ecfa6e1 Add changelog note
            • adcdf4a Update default bundle to codeql-bundle-v2.26.4
            • 486fec2 Merge pull request #4099 from github/update-supported-enterprise-server-versions
            • 134624c Merge pull request #4101 from github/dependabot/npm_and_yarn/npm-minor-457d82...
            • ff43db8 Merge pull request #4103 from github/mergeback/v4.37.8-to-main-db488dde
            • 4605e03 Rebuild
            • 099c869 Update changelog and version after v4.37.8
            • Additional commits viewable in compare view

            Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/apidocs.yaml | 2 +- .github/workflows/build.yml | 6 +++--- .github/workflows/codeql.yml | 6 +++--- .github/workflows/conformance.yml | 8 ++++---- .github/workflows/gradle-publish.yml | 2 +- .github/workflows/samples.yml | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/apidocs.yaml b/.github/workflows/apidocs.yaml index c3967d6..67770a7 100644 --- a/.github/workflows/apidocs.yaml +++ b/.github/workflows/apidocs.yaml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@v7 - name: Set up JDK ${{ env.JAVA_VERSION }} - uses: actions/setup-java@v5 + uses: actions/setup-java@v6 with: java-version: ${{ env.JAVA_VERSION }} distribution: ${{ env.JAVA_DISTRIBUTION }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fb4f43e..ff99581 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -46,7 +46,7 @@ jobs: - uses: actions/checkout@v7 - name: Set up JDK ${{ env.JAVA_VERSION }} - uses: actions/setup-java@v5 + uses: actions/setup-java@v6 with: java-version: ${{ env.JAVA_VERSION }} distribution: ${{ env.JAVA_DISTRIBUTION }} @@ -129,7 +129,7 @@ jobs: - uses: actions/checkout@v7 - name: Set up JDK ${{ env.JAVA_VERSION }} - uses: actions/setup-java@v5 + uses: actions/setup-java@v6 with: java-version: ${{ env.JAVA_VERSION }} distribution: ${{ env.JAVA_DISTRIBUTION }} @@ -171,7 +171,7 @@ jobs: - uses: actions/checkout@v7 - name: Set up JDK ${{ env.JAVA_VERSION }} - uses: actions/setup-java@v5 + uses: actions/setup-java@v6 with: java-version: ${{ env.JAVA_VERSION }} distribution: ${{ env.JAVA_DISTRIBUTION }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 84f0523..a9ac7ff 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -28,13 +28,13 @@ jobs: uses: actions/checkout@v7 - name: Set up JDK ${{ env.JAVA_VERSION }} - uses: actions/setup-java@v5 + uses: actions/setup-java@v6 with: distribution: ${{ env.JAVA_DISTRIBUTION }} java-version: ${{ env.JAVA_VERSION }} - name: Initialize CodeQL - uses: github/codeql-action/init@v4.37.8 + uses: github/codeql-action/init@v4.37.9 with: languages: java-kotlin build-mode: manual @@ -52,6 +52,6 @@ jobs: --no-daemon --rerun-tasks --stacktrace - name: Analyze - uses: github/codeql-action/analyze@v4.37.8 + uses: github/codeql-action/analyze@v4.37.9 with: category: '/language:java-kotlin' diff --git a/.github/workflows/conformance.yml b/.github/workflows/conformance.yml index 908107f..b3e3935 100644 --- a/.github/workflows/conformance.yml +++ b/.github/workflows/conformance.yml @@ -28,7 +28,7 @@ jobs: - uses: actions/checkout@v7 - name: Set up JDK - uses: actions/setup-java@v5 + uses: actions/setup-java@v6 with: java-version: ${{ env.JAVA_VERSION }} distribution: ${{ env.JAVA_DISTRIBUTION }} @@ -79,7 +79,7 @@ jobs: - uses: actions/checkout@v7 - name: Set up JDK - uses: actions/setup-java@v5 + uses: actions/setup-java@v6 with: java-version: ${{ env.JAVA_VERSION }} distribution: ${{ env.JAVA_DISTRIBUTION }} @@ -110,7 +110,7 @@ jobs: - uses: actions/checkout@v7 - name: Set up JDK - uses: actions/setup-java@v5 + uses: actions/setup-java@v6 with: java-version: ${{ env.JAVA_VERSION }} distribution: ${{ env.JAVA_DISTRIBUTION }} @@ -149,7 +149,7 @@ jobs: - uses: actions/checkout@v7 - name: Set up JDK - uses: actions/setup-java@v5 + uses: actions/setup-java@v6 with: java-version: ${{ env.JAVA_VERSION }} distribution: ${{ env.JAVA_DISTRIBUTION }} diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml index 1cf24e4..f01f9c8 100644 --- a/.github/workflows/gradle-publish.yml +++ b/.github/workflows/gradle-publish.yml @@ -19,7 +19,7 @@ jobs: steps: - uses: actions/checkout@v7 - name: Set up JDK 21 - uses: actions/setup-java@v5 + uses: actions/setup-java@v6 with: java-version: '21' distribution: 'temurin' diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index 463c024..7463a9e 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -39,7 +39,7 @@ jobs: - uses: actions/checkout@v7 - name: Set up JDK ${{ env.JAVA_VERSION }} - uses: actions/setup-java@v5 + uses: actions/setup-java@v6 with: java-version: ${{ env.JAVA_VERSION }} distribution: ${{ env.JAVA_DISTRIBUTION }} From 2f91865f3d100dced75f3570b17d40ff35c33880 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 13:57:00 +0200 Subject: [PATCH 435/449] chore(deps): bump com.anthropic:anthropic-java from 2.59.0 to 2.60.0 in /samples/kotlin-mcp-client in the other-dependencies group across 1 directory (#980) Bumps the other-dependencies group with 1 update in the /samples/kotlin-mcp-client directory: [com.anthropic:anthropic-java](https://github.com/anthropics/anthropic-sdk-java). Updates `com.anthropic:anthropic-java` from 2.59.0 to 2.60.0
            Release notes

            Sourced from com.anthropic:anthropic-java's releases.

            v2.60.0

            2.60.0 (2026-09-01)

            Full Changelog: v2.59.0...v2.60.0

            Features

            • api: add external_user_onboarded_at to user profiles (2aa81c0)
            • api: add support for claude-fable-5-1 and claude-mythos-5-1 (f41a324)
            • api: add support for organization compliance settings in the Admin API (4b64a84)
            • api: support pagination on organization rate limit list endpoints (2aa81c0)
            • user_profiles: sort the user profile list by name with order_by=name (931543e)

            Bug Fixes

            • api: mark the beta header required on Admin API Tunnel operations (898dabf)
            • api: remove unsupported allowed_callers from the toolset entries (53bd597)
            • client: include non-JSON error response bodies in API errors (04045b5)

            Chores

            • api: remove unreleased thinking prefix_mismatched_behavior param (e01193a)
            • api: remove user profile relationship in favor of access_type (e01193a)
            • ci: keep the Maven artifact upload steps under stlc (7437d73)
            • docs: clarify disallowed characters in memory paths (20582c1)
            • internal: add browser-stubbed internal/node module and lint rule for Node built-in imports (2aa81c0)
            • internal: pin JAVA_HOME for detekt-rules tests and relax UnusedPrivateProperty for tests/examples (2aa81c0)
            • internal: read the request id shown in Terraform error diagnostics from the response header (2aa81c0)
            • internal: remove discriminator support from PropertyInfo (4b64a84)

            Documentation

            • api: backtick identifiers in managed-agents API descriptions (db40558)
            • api: describe external_key_id and compartment_id behavior on Claude Platform on AWS (5eefbe1)
            • changelog: detail the beta files/skills GA-shape change (#398) (0105936)
            Changelog

            Sourced from com.anthropic:anthropic-java's changelog.

            2.60.0 (2026-09-01)

            Full Changelog: v2.59.0...v2.60.0

            Features

            • api: add external_user_onboarded_at to user profiles (2aa81c0)
            • api: add support for claude-fable-5-1 and claude-mythos-5-1 (f41a324)
            • api: add support for organization compliance settings in the Admin API (4b64a84)
            • api: support pagination on organization rate limit list endpoints (2aa81c0)
            • user_profiles: sort the user profile list by name with order_by=name (931543e)

            Bug Fixes

            • api: mark the beta header required on Admin API Tunnel operations (898dabf)
            • api: remove unsupported allowed_callers from the toolset entries (53bd597)
            • client: include non-JSON error response bodies in API errors (04045b5)

            Chores

            • api: remove unreleased thinking prefix_mismatched_behavior param (e01193a)
            • api: remove user profile relationship in favor of access_type (e01193a)
            • ci: keep the Maven artifact upload steps under stlc (7437d73)
            • docs: clarify disallowed characters in memory paths (20582c1)
            • internal: add browser-stubbed internal/node module and lint rule for Node built-in imports (2aa81c0)
            • internal: pin JAVA_HOME for detekt-rules tests and relax UnusedPrivateProperty for tests/examples (2aa81c0)
            • internal: read the request id shown in Terraform error diagnostics from the response header (2aa81c0)
            • internal: remove discriminator support from PropertyInfo (4b64a84)

            Documentation

            • api: backtick identifiers in managed-agents API descriptions (db40558)
            • api: describe external_key_id and compartment_id behavior on Claude Platform on AWS (5eefbe1)
            • changelog: detail the beta files/skills GA-shape change (#398) (0105936)
            Commits
            • 1dfef96 Merge pull request #397 from anthropics/release-please--branches--main--chang...
            • 110af9e release: 2.60.0
            • f41a324 feat(api): add support for claude-fable-5-1 and claude-mythos-5-1
            • 04045b5 fix(client): include non-JSON error response bodies in API errors
            • 5eefbe1 docs(api): describe external_key_id and compartment_id behavior on Claude Pla...
            • 841463e Merge branch 'main' into next
            • 0105936 docs(changelog): detail the beta files/skills GA-shape change (#398)
            • 53bd597 fix(api): remove unsupported allowed_callers from the toolset entries
            • 20582c1 chore(docs): clarify disallowed characters in memory paths
            • db40558 docs(api): backtick identifiers in managed-agents API descriptions
            • Additional commits viewable in compare view

            [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=com.anthropic:anthropic-java&package-manager=gradle&previous-version=2.59.0&new-version=2.60.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
            Dependabot commands and options
            You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
            Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-client/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index 03b7ad4..06a1c8f 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -anthropic = "2.59.0" +anthropic = "2.60.0" kotlin = "2.4.10" ktor = "3.5.2" mcp-kotlin = "0.15.0" From 45e3b8c1ec5513517e3a2016d32e8ddc759d18cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:55:00 +0200 Subject: [PATCH 436/449] chore(deps): bump org.jetbrains.kotlinx.binary-compatibility-validator from 0.18.1 to 0.18.2 in the gradle-plugins group across 1 directory (#982) Bumps the gradle-plugins group with 1 update in the / directory: [org.jetbrains.kotlinx.binary-compatibility-validator](https://github.com/Kotlin/binary-compatibility-validator). Updates `org.jetbrains.kotlinx.binary-compatibility-validator` from 0.18.1 to 0.18.2
            Release notes

            Sourced from org.jetbrains.kotlinx.binary-compatibility-validator's releases.

            0.18.2

            • Prevent configuration crash on hosts unrecognized by Kotlin/Native
            Commits
            • 534b4eb Release 0.18.2
            • 0332e22 Prevent configuration crash on hosts unrecognized by Kotlin/Native (#316)
            • 31dafe4 Updated warning about freezing development
            • 74e2c57 Updated warning about freezing development (#309)
            • See full diff in compare view

            Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c4d4f90..a40a5b3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] # plugins version atomicfu = "0.33.0" -binaryCompatibilityValidatorPlugin = "0.18.1" +binaryCompatibilityValidatorPlugin = "0.18.2" detekt = "2.0.0-alpha.6" dokka = "2.2.0" kotlin = "2.4.10" From b7047e77cf0625241bd177f17bbe35a1d4ae642c Mon Sep 17 00:00:00 2001 From: Recoordinate <296084221+latent-9@users.noreply.github.com> Date: Thu, 10 Sep 2026 05:00:31 +1200 Subject: [PATCH 437/449] fix(server): return INVALID_PARAMS for unknown prompt in prompts/get (#955) ## Problem `prompts/get` for a name that is not registered responds with JSON-RPC `-32603` (INTERNAL_ERROR). `handleGetPrompt` in `Server.kt` throws a bare `IllegalArgumentException` when the prompt is missing. `Protocol.respondWithError` maps any non-`McpException` cause to `INTERNAL_ERROR`, so the client receives `-32603` for what is a client-side bad parameter. The sibling handler `handleReadResource` in the same file already throws a typed `McpException(RESOURCE_NOT_FOUND)` for the equivalent not-found case, which reaches the client as `-32002`. The two not-found paths are inconsistent. In JSON-RPC, `-32603` means the server hit an internal fault. Requesting a prompt name that does not exist is a client error (an invalid `name` parameter), so a client cannot tell "the prompt does not exist" apart from "the server failed" and may retry a request that can never succeed. ## Change Throw a typed `McpException(INVALID_PARAMS)` from `handleGetPrompt`, consistent with `handleReadResource`. The message is unchanged. `AbstractPromptIntegrationTest.testNonExistentPrompt` asserted `-32603`; it now asserts `INVALID_PARAMS`. If you would prefer a dedicated error code for this case instead of `INVALID_PARAMS`, I am glad to adjust. --- .../sdk/integration/kotlin/AbstractPromptIntegrationTest.kt | 4 ++-- .../io/modelcontextprotocol/kotlin/sdk/server/Server.kt | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt index eaa51ad..fb97782 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractPromptIntegrationTest.kt @@ -698,8 +698,8 @@ abstract class AbstractPromptIntegrationTest : KotlinTestBase() { val expectedMessage = "Prompt not found: non-existent-prompt" - withClue("Exception code should be INTERNAL_ERROR: -32603") { - exception.code shouldBe -32603 + withClue("Exception code should be INVALID_PARAMS: ${RPCError.ErrorCode.INVALID_PARAMS}") { + exception.code shouldBe RPCError.ErrorCode.INVALID_PARAMS } withClue("Unexpected error message for non-existent prompt") { exception.message shouldBe expectedMessage diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt index 51a2389..6f7711f 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt @@ -682,7 +682,10 @@ public open class Server( val prompt = promptRegistry.get(requestParams.name) ?: run { logger.error { "Prompt not found: ${requestParams.name}" } - throw IllegalArgumentException("Prompt not found: ${requestParams.name}") + throw McpException( + code = RPCError.ErrorCode.INVALID_PARAMS, + message = "Prompt not found: ${requestParams.name}", + ) } return prompt.run { session.clientConnection.messageProvider(request) From ba5b1cce652eda3112cc3e9a9f02b62d50a49d35 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Sep 2026 14:47:39 +0200 Subject: [PATCH 438/449] chore(deps): bump the all-dependencies group across 1 directory with 3 updates (#983) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the all-dependencies group with 3 updates in the /integration-test/src/jvmTest/typescript directory: [zod](https://github.com/colinhacks/zod), [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) and [tsx](https://github.com/privatenumber/tsx). Updates `zod` from 4.5.2 to 4.5.4
            Release notes

            Sourced from zod's releases.

            v4.5.4

            Commits:

            • 84e416fbf4740527bbc8f319634f4e1b065bb42c fix(v4): stop the cycle walk from firing a default factory (#6500)
            • e8e206fa33ac5fe7ce20a2beb12d57b1cb3df653 4.5.4

            v4.5.3

            Commits:

            • e6b6ab347675cd2bd54b1bdbed16f98c59be82a9 docs(blog): widen the z.compile example to a 20-property schema
            • 87d6464418582bb96fc665a01f852ca6da324ad0 fix(docs): drop the OG description when the title wraps past two lines
            • 99fce394a026823e602b9c30d8d5d9f5f1932ce7 bench(v4): z.compile() against zod-compiler (#6499)
            • e3a695b6bf3f0d591ea682816e3cdaea04b0f967 docs(v4): record the email regex and container output-shape findings under Open
            • 7e24a24288183ce02554f1ded7775d0650a7b7e6 docs(blog): drop the reading time and put a GitHub link in the navbar
            • eab51ff3592b2d11d863f4ee4d5452f31a3de1b6 fix(v4): emit record numeric keys as strings in toJSONSchema (#6497)
            Commits
            • e8e206f 4.5.4
            • 84e416f fix(v4): stop the cycle walk from firing a default factory (#6500)
            • 1a16102 4.5.3
            • eab51ff fix(v4): emit record numeric keys as strings in toJSONSchema (#6497)
            • 7e24a24 docs(blog): drop the reading time and put a GitHub link in the navbar
            • e3a695b docs(v4): record the email regex and container output-shape findings under Open
            • 99fce39 bench(v4): z.compile() against zod-compiler (#6499)
            • 87d6464 fix(docs): drop the OG description when the title wraps past two lines
            • e6b6ab3 docs(blog): widen the z.compile example to a 20-property schema
            • See full diff in compare view

            Updates `@types/node` from 26.4.0 to 26.4.1
            Commits

            Updates `tsx` from 4.23.12 to 4.23.13
            Release notes

            Sourced from tsx's releases.

            v4.23.13

            4.23.13 (2026-08-30)

            Bug Fixes

            • cache: bound shared transform cache memory (#835) (28e1f12)

            This release is also available on:

            Commits

            Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../src/jvmTest/typescript/package-lock.json | 24 +++++++++---------- .../src/jvmTest/typescript/package.json | 6 ++--- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index a5c2f86..5c6746d 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -10,14 +10,14 @@ "dependencies": { "@modelcontextprotocol/sdk": "^1.30.0", "express": "^5.2.1", - "zod": "^4.5.2" + "zod": "^4.5.4" }, "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^26.4.0", + "@types/node": "^26.4.1", "cors": "^2.8.6", - "tsx": "^4.23.12", + "tsx": "^4.23.13", "typescript": "^7.0.2" } }, @@ -579,9 +579,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "26.4.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.4.0.tgz", - "integrity": "sha512-faiGnoIrLH/V8cibOMEAZ8pMw6oXqSukl29ra4mN8GdaB2ZewzeaLj+INpV5N+Z1eKWzY+IzaIZH2EIR6YZRNQ==", + "version": "26.4.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.4.1.tgz", + "integrity": "sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA==", "dev": true, "license": "MIT", "dependencies": { @@ -2035,9 +2035,9 @@ } }, "node_modules/tsx": { - "version": "4.23.12", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.12.tgz", - "integrity": "sha512-FDf4L4sYzKtzWYhU/Xm0AQFdTjdIxNo9ElTf2mxXM6k8YMHXzYUe4yODVaXP4V9uMFbVg8c0qyBccK2OOxb45Q==", + "version": "4.23.13", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.13.tgz", + "integrity": "sha512-BL5MGkRln6aDYhb0xbQlEAGw743BaZYWdbWtdJOBriYJboKgUUYCadFp2/FpBBZquBC/ezNBn7wMMPx7FDZUDw==", "dev": true, "license": "MIT", "dependencies": { @@ -2166,9 +2166,9 @@ "license": "ISC" }, "node_modules/zod": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.5.2.tgz", - "integrity": "sha512-XkYXCol10+ba/6F/cueWV+TezUeOqXW0hdeJt5CdXjTYeAgAQg5N03RQdJ80mhfFE72+pblvYMW4wy2Qp4Qbrg==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.5.4.tgz", + "integrity": "sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/integration-test/src/jvmTest/typescript/package.json b/integration-test/src/jvmTest/typescript/package.json index 312c4f8..287f0b8 100644 --- a/integration-test/src/jvmTest/typescript/package.json +++ b/integration-test/src/jvmTest/typescript/package.json @@ -11,14 +11,14 @@ "dependencies": { "@modelcontextprotocol/sdk": "^1.30.0", "express": "^5.2.1", - "zod": "^4.5.2" + "zod": "^4.5.4" }, "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^26.4.0", + "@types/node": "^26.4.1", "cors": "^2.8.6", - "tsx": "^4.23.12", + "tsx": "^4.23.13", "typescript": "^7.0.2" } } From 318aabe74e3e7c9d1b57cc1ba216132ef06c82ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Sep 2026 16:10:41 +0200 Subject: [PATCH 439/449] chore(deps): bump hono from 4.13.1 to 4.13.7 in /integration-test/src/jvmTest/typescript (#987) Bumps [hono](https://github.com/honojs/hono) from 4.13.1 to 4.13.7.
            Release notes

            Sourced from hono's releases.

            v4.13.7

            Security fixes

            This release includes a fix for the following security issue:

            hono/jsx renders plain strings unescaped in boundary components, leading to XSS

            Affects: Suspense, ErrorBoundary, and Context.Provider in hono/jsx, and renderToString() / renderToReadableStream() in hono/jsx/dom/server. Fixes missing HTML escaping for a plain string placed directly as a child or fallback of these components, or as the root value of the server rendering functions, so untrusted strings could be emitted as markup. GHSA-hxh3-vqpv-xpqv


            Users who render untrusted strings inside Suspense, ErrorBoundary, or Context.Provider, or pass them directly to hono/jsx/dom/server, are strongly encouraged to upgrade to this version.

            v4.13.6

            What's Changed

            Full Changelog: https://github.com/honojs/hono/compare/v4.13.5...v4.13.6

            v4.13.5

            Security fixes

            This release includes fixes for the following security issues:

            Query parser reads parameters after the URL fragment, causing cache-key and proxy interpretation differentials

            Affects: Cache Middleware and applications behind a proxy, WAF, or logging layer that inspects query strings. Fixes query parsing that did not stop at the URL fragment, so a ? after a # was treated as the start of a query string and the application could read parameters that the other component never saw. GHSA-crvj-82cr-hjcx

            Incomplete fix for CVE-2026-39408: toSSG() still writes files outside the output directory

            Affects: toSSG() for Static Site Generation. Fixes a path normalization gap where consecutive parent-directory segments in ssgParams values were not fully collapsed, bypassing the containment check added in 4.12.12. GHSA-gqvv-2mrq-wpjv

            Unbounded dot-notation nesting in parseBody() can cause memory exhaustion

            Affects: parseBody() when dot-notation parsing is enabled. Fixes unbounded expansion of dot-separated field names, where a small request body could allocate a disproportionately large object graph and concurrent requests could exhaust the heap. GHSA-g6gw-c38x-mqfc


            Users who use Cache Middleware, deploy behind a proxy or WAF that inspects query strings, use Static Site Generation, or use parseBody({ dot: true }) are strongly encouraged to upgrade to this version.

            v4.13.4

            What's Changed

            • fix(request): handle params on unmatched requests in honojs/hono#5268
            • fix(jsx/dom): execute previous ref cleanup when ref prop changes on re-render in honojs/hono#5264
            • fix(reg-exp-router): associate wildcard middleware with matching routes in honojs/hono#5266
            • perf(router): share null object creation in honojs/hono#5267

            ... (truncated)

            Commits

            Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- integration-test/src/jvmTest/typescript/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index 5c6746d..d9312b7 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -1538,9 +1538,9 @@ } }, "node_modules/hono": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.1.tgz", - "integrity": "sha512-kdJoFVv2xmayw6cY09H7AbMJMt8Jn5jdlEdXsP7AGBdF2DIptVlKlOLKXP41yPip4/a3yQPv9gVcJYI8YY04dw==", + "version": "4.13.7", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.7.tgz", + "integrity": "sha512-c8/gF9ac8Y78/agExVocyLevgR+JlpNB444Py0FSX8pJoPdYUfUzRcXtYEYGwt6l19qIlVZPN5Mfsw9jFShmQQ==", "license": "MIT", "engines": { "node": ">=16.9.0" From 5555b0d8ae964ca9dec24a3de4097fc85c66a930 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Sep 2026 23:50:17 +0200 Subject: [PATCH 440/449] chore(deps): bump org.jetbrains.kotlinx:kotlinx-collections-immutable from 0.5.1 to 0.5.2 in the kotlinx group across 1 directory (#981) Bumps the kotlinx group with 1 update in the / directory: [org.jetbrains.kotlinx:kotlinx-collections-immutable](https://github.com/Kotlin/kotlinx.collections.immutable). Updates `org.jetbrains.kotlinx:kotlinx-collections-immutable` from 0.5.1 to 0.5.2
            Release notes

            Sourced from org.jetbrains.kotlinx:kotlinx-collections-immutable's releases.

            v0.5.2

            • Fixed addingAll, removingAll, retainingAll, intersect, and containsAll on PersistentHashSet, puttingAll on PersistentHashMap, and their builder counterparts returning wrong results when a collision node meets a single element at the bottom trie level #294
            • Fixed puttingAll on PersistentHashMap, and putAll on its builder, keeping the receiver's value for a key that sits in a collision node on both sides #300
            • Fixed PersistentHashSet.equals being asymmetric after intersect, retainingAll, or the builder's retainAll #291
            • Fixed PersistentHashMapBuilder.put of an already stored value invalidating live iterators and making build() return a new map when the key is in a collision node #304
            • Fixed a live iterator of PersistentHashMapBuilder returning the old values after a putAll that only replaces values #308
            • Fixed PersistentOrderedMapBuilder iterators throwing ConcurrentModificationException after MutableMap.MutableEntry.setValue, or a put that only replaces a value, during iteration #307
            • Fixed MutableMap.MutableEntry.setValue on a PersistentOrderedMapBuilder entry re-adding the key after it was removed from the builder #310

            Full changelog: https://github.com/Kotlin/kotlinx.collections.immutable/compare/v0.5.1...v0.5.2

            Changelog

            Sourced from org.jetbrains.kotlinx:kotlinx-collections-immutable's changelog.

            0.5.2

            • Fixed addingAll, removingAll, retainingAll, intersect, and containsAll on PersistentHashSet, puttingAll on PersistentHashMap, and their builder counterparts returning wrong results when a collision node meets a single element at the bottom trie level #294
            • Fixed puttingAll on PersistentHashMap, and putAll on its builder, keeping the receiver's value for a key that sits in a collision node on both sides #300
            • Fixed PersistentHashSet.equals being asymmetric after intersect, retainingAll, or the builder's retainAll #291
            • Fixed PersistentHashMapBuilder.put of an already stored value invalidating live iterators and making build() return a new map when the key is in a collision node #304
            • Fixed a live iterator of PersistentHashMapBuilder returning the old values after a putAll that only replaces values #308
            • Fixed PersistentOrderedMapBuilder iterators throwing ConcurrentModificationException after MutableMap.MutableEntry.setValue, or a put that only replaces a value, during iteration #307
            • Fixed MutableMap.MutableEntry.setValue on a PersistentOrderedMapBuilder entry re-adding the key after it was removed from the builder #310
            Commits
            • 9f961e4 Release v0.5.2
            • 1d00eea Skip the setValue write when the entry's key is gone from the builder (#315)
            • 8a49b20 Count the value overwrites of putAll and keep the stored keys (#312)
            • 59bc873 Count only size changes for the ordered map builder's iterators (#311)
            • 2b9cc44 Return the collision node as is when the put value is already stored (#309)
            • c9a67cb Take the argument's value when two collision nodes share a key (#302)
            • 024f04c Dispatch bulk trie ops to collision helpers at the bottom level (#301)
            • 578f6ed Update kotlinx.benchmark to 0.4.17
            • e72f86e Lift the single remaining element to the root in set retainAll (#293)
            • 1fd89dd Restore commented-out invariant asserts (#290)
            • Additional commits viewable in compare view

            Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a40a5b3..a00822b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,7 +13,7 @@ netty = "4.2.16.Final" # libraries version awaitility = "4.3.0" -collections-immutable = "0.5.1" +collections-immutable = "0.5.2" coroutines = "1.11.0" kotest = "6.2.4" kotlinx-io = "0.9.1" From be06a71053dde72af6197a68828d23adb1ad8be4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2026 15:44:06 +0200 Subject: [PATCH 441/449] chore(deps): bump org.jetbrains.kotlin.jvm from 2.4.10 to 2.4.20 in /samples/kotlin-mcp-client (#989) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [org.jetbrains.kotlin.jvm](https://github.com/JetBrains/kotlin) from 2.4.10 to 2.4.20.
            Release notes

            Sourced from org.jetbrains.kotlin.jvm's releases.

            Kotlin 2.4.20

            Changelog

            Analysis API

            • KT-86546 Check suspicious when over ConeKotlinType in ConeTypeCompatibilityChecker
            • KT-85418 Implement an API for accessing deserialized file annotations in Analysis API
            • KT-74448 K2. False positive MISSING_DEPENDENCY_SUPERCLASS in LinkedListTest.kt, kotlinx.coroutines
            • KT-85856 containingSymbol of constructor property differs for local and non-local classes
            • KT-65417 K2 IDE: KTOR false positive expect-actual matching error on enum class because of implicit clone() in non-JVM source sets

            Analysis API. Code Compilation

            • KT-76457 K2 IDE / KMP Debugger: KISEWA “Cannot compile a common source without a JVM counterpart” on evaluating inline fun from common module inside jvm

            Analysis API. FIR

            • KT-70552 No expects for actual
            • KT-69727 K2 IDE. Wrong error in the editor on calling clone function of actual enum instance in non-jvm platform
            • KT-69726 FP errors on declaring fun clone() in actual enum in not-jvm source-set
            • KT-86014 Types are broken after remove parameter through change signature
            • KT-86363 KotlinIllegalArgumentExceptionWithAttachments: No dangling modifier found on companion blocks
            • KT-86147 Drop kotlin.parallel.resolve.under.global.lock registry key
            • KT-85543 Avoid lazy resolve for the contracts phase if no constracts might be resolved

            Analysis API. Infrastructure

            • KT-84914 Do not publish analysis-api-test-framework
            • KT-86986 kotlin-compiler-common-for-ide bundles unrelated Analysis API modules
            • KT-86186 Analysis API: Codebase tests run twice in some analysis modules — pick a single JUnit runner and migrate
            • KT-85360 Drop kotlin-compiler-testdata-for-ide artifact
            • KT-85585 Simplify the dependencies graph for the Analysis API modules
            • KT-85381 Remove tests for the FE10 implementation

            Analysis API. Light Classes

            New Features

            • KT-84645 Support resolving to companion block members & extensions from Java (light classes)
            • KT-80775 Support PsiClass#getRecordComponents in light classes

            Fixes

            • KT-57537 SLC: propagate default parameter value from (@JvmOverloads) expect declarations to actual declarations
            • KT-85040 [Analysis API] Improve Java / Kotlin interop in KMP projects
            • KT-87301 SymbolLightAccessorMethod#isValid returns false for delegated properties
            • KT-87171 SLC: non-mapped Kotlin collection supertype is dropped from supertype list
            • KT-87250 JvmExposeBoxed: light classes shouldn't be autogenerated for private declarations
            • KT-70428 AA: good code is red when a Java class extends a Kotlin class implementing MutableList by delegation
            • KT-63568 Symbol Light Classes: KtAnnotationApplicationWithArgumentsInfo.normalizedArguments() may work incorrectly when psi is not set

            ... (truncated)

            Changelog

            Sourced from org.jetbrains.kotlin.jvm's changelog.

            2.4.20

            Analysis API

            • KT-86546 Check suspicious when over ConeKotlinType in ConeTypeCompatibilityChecker
            • KT-85418 Implement an API for accessing deserialized file annotations in Analysis API
            • KT-74448 K2. False positive MISSING_DEPENDENCY_SUPERCLASS in LinkedListTest.kt, kotlinx.coroutines
            • KT-85856 containingSymbol of constructor property differs for local and non-local classes
            • KT-65417 K2 IDE: KTOR false positive expect-actual matching error on enum class because of implicit clone() in non-JVM source sets

            Analysis API. Code Compilation

            • KT-76457 K2 IDE / KMP Debugger: KISEWA “Cannot compile a common source without a JVM counterpart” on evaluating inline fun from common module inside jvm

            Analysis API. FIR

            • KT-70552 No expects for actual
            • KT-69727 K2 IDE. Wrong error in the editor on calling clone function of actual enum instance in non-jvm platform
            • KT-69726 FP errors on declaring fun clone() in actual enum in not-jvm source-set
            • KT-86014 Types are broken after remove parameter through change signature
            • KT-86363 KotlinIllegalArgumentExceptionWithAttachments: No dangling modifier found on companion blocks
            • KT-86147 Drop kotlin.parallel.resolve.under.global.lock registry key
            • KT-85543 Avoid lazy resolve for the contracts phase if no constracts might be resolved

            Analysis API. Infrastructure

            • KT-84914 Do not publish analysis-api-test-framework
            • KT-86986 kotlin-compiler-common-for-ide bundles unrelated Analysis API modules
            • KT-86186 Analysis API: Codebase tests run twice in some analysis modules — pick a single JUnit runner and migrate
            • KT-85360 Drop kotlin-compiler-testdata-for-ide artifact
            • KT-85585 Simplify the dependencies graph for the Analysis API modules
            • KT-85381 Remove tests for the FE10 implementation

            Analysis API. Light Classes

            New Features

            • KT-84645 Support resolving to companion block members & extensions from Java (light classes)
            • KT-80775 Support PsiClass#getRecordComponents in light classes

            Fixes

            • KT-57537 SLC: propagate default parameter value from (@JvmOverloads) expect declarations to actual declarations
            • KT-85040 [Analysis API] Improve Java / Kotlin interop in KMP projects
            • KT-87301 SymbolLightAccessorMethod#isValid returns false for delegated properties
            • KT-87171 SLC: non-mapped Kotlin collection supertype is dropped from supertype list
            • KT-87250 JvmExposeBoxed: light classes shouldn't be autogenerated for private declarations
            • KT-70428 AA: good code is red when a Java class extends a Kotlin class implementing MutableList by delegation
            • KT-63568 Symbol Light Classes: KtAnnotationApplicationWithArgumentsInfo.normalizedArguments() may work incorrectly when psi is not set
            • KT-36740 MPP: False-positive incompatible types in .java when using expect-class returned by non-expect member from common when actual is actual typealias

            ... (truncated)

            Commits
            • 890ac1d Add Changelog for 2.4.20-RC3
            • 8860aed 🍒 [FIR] Fix suspend conversion when expected type is nullable (#7769)
            • ab5bcd9 Edit ChangeLog for 2.4.20-RC2
            • 9464edc Add ChangeLog for 2.4.20-RC2
            • 0261e43 Cherry-pick "Fix asBodyAndResultVar call in `visitInlinedLambdaInComposable...
            • 0763513 [box-tests] Workaround for klib compatibility tests (#7626)
            • 045aec6 [Wasm] Append scripts from webpack.config.d in the end of the file (#7615)
            • cd7173b 🍒 [2.4.20] [K/JS] Keep associated obj annotation only if getInstance survives...
            • d36a2ff CastsOptimizationPass: break aliases cycles (#7578)
            • 8664983 [CLI] Restore custom IC search scope creation
            • Additional commits viewable in compare view

            [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.jetbrains.kotlin.jvm&package-manager=gradle&previous-version=2.4.10&new-version=2.4.20)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
            Dependabot commands and options
            You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
            Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-client/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index 06a1c8f..c2edddb 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] anthropic = "2.60.0" -kotlin = "2.4.10" +kotlin = "2.4.20" ktor = "3.5.2" mcp-kotlin = "0.15.0" shadow = "9.6.1" From 12e4020d8d1bce6577e35d69a98afaf196055d52 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2026 16:07:40 +0200 Subject: [PATCH 442/449] chore(deps): bump github/codeql-action from 4.37.9 to 4.38.0 in the all-actions group across 1 directory (#990) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the all-actions group with 1 update in the / directory: [github/codeql-action](https://github.com/github/codeql-action). Updates `github/codeql-action` from 4.37.9 to 4.38.0
            Release notes

            Sourced from github/codeql-action's releases.

            v4.38.0

            • On GitHub-hosted runners, the CodeQL Action now deletes unused CodeQL bundles from the toolcache before downloading a different bundle, which frees up disk space for the analysis. We expect to roll this change out to everyone in September. #4124
            • The CodeQL Action now supports CodeQL releases that are compatible with Linux Arm64 and downloads the native linux-arm64 CodeQL bundle when available. #4072
            • Update default CodeQL bundle version to 2.27.0. #4129
            Changelog

            Sourced from github/codeql-action's changelog.

            4.38.0 - 09 Sept 2026

            • On GitHub-hosted runners, the CodeQL Action now deletes unused CodeQL bundles from the toolcache before downloading a different bundle, which frees up disk space for the analysis. We expect to roll this change out to everyone in September. #4124
            • The CodeQL Action now supports CodeQL releases that are compatible with Linux Arm64 and downloads the native linux-arm64 CodeQL bundle when available. #4072
            • Update default CodeQL bundle version to 2.27.0. #4129
            Commits
            • b96794f Merge pull request #4131 from github/update-v4.38.0-7e08580a9
            • 02d5093 Update changelog for v4.38.0
            • 7e08580 Merge pull request #4130 from github/henrymercer/workflow-runner-sizing
            • bfcc52b Run slow macOS checks on larger runners
            • 8c251e7 Merge pull request #4129 from github/update-bundle/codeql-bundle-v2.27.0
            • 0b7ca40 Add changelog note
            • 40484b3 Update default bundle to codeql-bundle-v2.27.0
            • 977e6ce Merge pull request #4124 from github/henrymercer/toolcache-bundle-cleanup
            • 40a6b38 Address toolcache cleanup review feedback
            • deece8f Apply suggestion from @​henrymercer
            • Additional commits viewable in compare view

            Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index a9ac7ff..2e7c58f 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -34,7 +34,7 @@ jobs: java-version: ${{ env.JAVA_VERSION }} - name: Initialize CodeQL - uses: github/codeql-action/init@v4.37.9 + uses: github/codeql-action/init@v4.38.0 with: languages: java-kotlin build-mode: manual @@ -52,6 +52,6 @@ jobs: --no-daemon --rerun-tasks --stacktrace - name: Analyze - uses: github/codeql-action/analyze@v4.37.9 + uses: github/codeql-action/analyze@v4.38.0 with: category: '/language:java-kotlin' From f7877f2040f3b8dad5a0048a54ba5b9b3c8b974d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2026 17:03:30 +0200 Subject: [PATCH 443/449] chore(deps): bump the all-dependencies group across 1 directory with 2 updates (#991) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the all-dependencies group with 2 updates in the /integration-test/src/jvmTest/typescript directory: [zod](https://github.com/colinhacks/zod) and [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node). Updates `zod` from 4.5.4 to 4.6.2
            Release notes

            Sourced from zod's releases.

            v4.6.2

            A patch on top of 4.6.1.

            v4.6.1

            A patch on top of 4.6.0.

            v4.6.0

            Zod 4.6 is now available.

            npm install zod@latest
            

            At a glance:

            • .validate() — checks input validity without building a result (up to 35x faster than .safeParse().success on a compiled schema)
            • z.instanceof().properties() — validates properties of an instance
            • fromJSONSchema() — enforces six validation keywords it used to ignore
            • z.iban() — electronic-format IBAN plus mod-97 checksum
            • z.withParser() — installs a parser generated elsewhere, for environments without new Function
            • Faster CommonJS — drops the getter on every export (~3x faster z.validate() under require)
            • Memory retention in recursive schemas — releases the parsed input, fixing a 4.5 out-of-memory regression
            • @zod/mini — Zod Mini as a standalone package, versioned in lockstep with zod since 4.5

            .validate()

            Standalone boolean validation, in Zod, Zod Mini, and Zod Core. It answers "is this input valid?" without constructing a ZodError, which makes rejection cheap. The return type is a guard on the schema's input type.

            z.validate(z.string(), "hi"); // true
            z.validate(z.string(), 42);   // false
            

            It is a method on Zod Classic schemas too. (#6547)

            const Player = z.object({
              username: z.string(),
              xp: z.number(),
            });
            

            </tr></table>

            ... (truncated)

            Commits
            • e359f73 4.6.2
            • 9446b5c fix: preserve undefined prefault outputs and object keys (#6587)
            • 0c483c5 docs: Zod 4.6 announcement post (#6546)
            • a00c3f3 docs: use Trigger.dev's brand-kit lockups for the platinum card
            • 62311eb 4.6.1
            • 2efa8b8 ci: give the npm wait a real budget and drop the back-publish path (#6583)
            • b12aa52 fix: preserve unique tags with defaulted discriminators (#6582)
            • dd9c36f fix(v4): defer recursive object index inference (#6580)
            • 574d480 fix(locales): clarify Tajik discriminator value message
            • c532d76 test(locales): cover Tajik error branches
            • Additional commits viewable in compare view

            Updates `@types/node` from 26.4.1 to 26.5.1
            Commits

            Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../src/jvmTest/typescript/package-lock.json | 24 +++++++++---------- .../src/jvmTest/typescript/package.json | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/integration-test/src/jvmTest/typescript/package-lock.json b/integration-test/src/jvmTest/typescript/package-lock.json index d9312b7..ffb53fc 100644 --- a/integration-test/src/jvmTest/typescript/package-lock.json +++ b/integration-test/src/jvmTest/typescript/package-lock.json @@ -10,12 +10,12 @@ "dependencies": { "@modelcontextprotocol/sdk": "^1.30.0", "express": "^5.2.1", - "zod": "^4.5.4" + "zod": "^4.6.2" }, "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^26.4.1", + "@types/node": "^26.5.1", "cors": "^2.8.6", "tsx": "^4.23.13", "typescript": "^7.0.2" @@ -579,13 +579,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "26.4.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.4.1.tgz", - "integrity": "sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA==", + "version": "26.5.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.5.1.tgz", + "integrity": "sha512-CzNm2FezW4VR/LjG6yUdiEgLE/rAQ9Slj5gCu/C2VrdcW7I0ahNZ8DRbHT7zOZ6r3ONgd/bsQIeSaoDGrd1C6g==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~8.3.0" + "undici-types": "~8.9.0" } }, "node_modules/@types/qs": { @@ -2120,9 +2120,9 @@ } }, "node_modules/undici-types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", - "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", + "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", "dev": true, "license": "MIT" }, @@ -2166,9 +2166,9 @@ "license": "ISC" }, "node_modules/zod": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.5.4.tgz", - "integrity": "sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.2.tgz", + "integrity": "sha512-lh5RCAGFa1Cm2hjtNwLQhSs/AsqdWnTQaBER9fEwN/88pSh7KOtJavtBx/0VlkN/uFd61SwYmljLMDAsHlvzBQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/integration-test/src/jvmTest/typescript/package.json b/integration-test/src/jvmTest/typescript/package.json index 287f0b8..b0147e4 100644 --- a/integration-test/src/jvmTest/typescript/package.json +++ b/integration-test/src/jvmTest/typescript/package.json @@ -11,12 +11,12 @@ "dependencies": { "@modelcontextprotocol/sdk": "^1.30.0", "express": "^5.2.1", - "zod": "^4.5.4" + "zod": "^4.6.2" }, "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", - "@types/node": "^26.4.1", + "@types/node": "^26.5.1", "cors": "^2.8.6", "tsx": "^4.23.13", "typescript": "^7.0.2" From d5ff46eda8d6897bbbfe2ccb1a452a45ae78ff27 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Sep 2026 15:56:45 +0200 Subject: [PATCH 444/449] chore(deps): bump the kotest group across 1 directory with 3 updates (#992) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the kotest group with 3 updates in the / directory: [io.kotest:kotest-assertions-core](https://github.com/kotest/kotest), [io.kotest:kotest-assertions-json](https://github.com/kotest/kotest) and [io.kotest:kotest-assertions-ktor](https://github.com/kotest/kotest). Updates `io.kotest:kotest-assertions-core` from 6.2.4 to 6.2.5
            Release notes

            Sourced from io.kotest:kotest-assertions-core's releases.

            v6.2.5

            What's Changed

            Full Changelog: https://github.com/kotest/kotest/compare/6.2.4...v6.2.5

            Commits
            • 41df99d Fix Android StackWalker compatibility (#6222)
            • 8655ca7 add back jsoup assertions (#6226)
            • e2271eb Alex/make default extension registry thread safe (#6199)
            • fdca609 Bound the short/byte/uShort/uByte shrinkers by the generator range (#6224)
            • 56301a6 Make the shouldBeSingleton block overloads infix (#6223)
            • dfee83a Use withEdtSafeAnalysis to improve safe analysis for spec and style… (#6215)
            • c44d1c3 use a singleton strategy for getConfigurationFactory of Kotest legacy produce...
            • b74611b skill version update PR upon release (#6213)
            • 0d3c09a 6.2.4 claude marketplace manual update
            • See full diff in compare view

            Updates `io.kotest:kotest-assertions-json` from 6.2.4 to 6.2.5
            Release notes

            Sourced from io.kotest:kotest-assertions-json's releases.

            v6.2.5

            What's Changed

            Full Changelog: https://github.com/kotest/kotest/compare/6.2.4...v6.2.5

            Commits
            • 41df99d Fix Android StackWalker compatibility (#6222)
            • 8655ca7 add back jsoup assertions (#6226)
            • e2271eb Alex/make default extension registry thread safe (#6199)
            • fdca609 Bound the short/byte/uShort/uByte shrinkers by the generator range (#6224)
            • 56301a6 Make the shouldBeSingleton block overloads infix (#6223)
            • dfee83a Use withEdtSafeAnalysis to improve safe analysis for spec and style… (#6215)
            • c44d1c3 use a singleton strategy for getConfigurationFactory of Kotest legacy produce...
            • b74611b skill version update PR upon release (#6213)
            • 0d3c09a 6.2.4 claude marketplace manual update
            • See full diff in compare view

            Updates `io.kotest:kotest-assertions-ktor` from 6.2.4 to 6.2.5
            Release notes

            Sourced from io.kotest:kotest-assertions-ktor's releases.

            v6.2.5

            What's Changed

            Full Changelog: https://github.com/kotest/kotest/compare/6.2.4...v6.2.5

            Commits
            • 41df99d Fix Android StackWalker compatibility (#6222)
            • 8655ca7 add back jsoup assertions (#6226)
            • e2271eb Alex/make default extension registry thread safe (#6199)
            • fdca609 Bound the short/byte/uShort/uByte shrinkers by the generator range (#6224)
            • 56301a6 Make the shouldBeSingleton block overloads infix (#6223)
            • dfee83a Use withEdtSafeAnalysis to improve safe analysis for spec and style… (#6215)
            • c44d1c3 use a singleton strategy for getConfigurationFactory of Kotest legacy produce...
            • b74611b skill version update PR upon release (#6213)
            • 0d3c09a 6.2.4 claude marketplace manual update
            • See full diff in compare view

            Updates `io.kotest:kotest-assertions-json` from 6.2.4 to 6.2.5
            Release notes

            Sourced from io.kotest:kotest-assertions-json's releases.

            v6.2.5

            What's Changed

            Full Changelog: https://github.com/kotest/kotest/compare/6.2.4...v6.2.5

            Commits
            • 41df99d Fix Android StackWalker compatibility (#6222)
            • 8655ca7 add back jsoup assertions (#6226)
            • e2271eb Alex/make default extension registry thread safe (#6199)
            • fdca609 Bound the short/byte/uShort/uByte shrinkers by the generator range (#6224)
            • 56301a6 Make the shouldBeSingleton block overloads infix (#6223)
            • dfee83a Use withEdtSafeAnalysis to improve safe analysis for spec and style… (#6215)
            • c44d1c3 use a singleton strategy for getConfigurationFactory of Kotest legacy produce...
            • b74611b skill version update PR upon release (#6213)
            • 0d3c09a 6.2.4 claude marketplace manual update
            • See full diff in compare view

            Updates `io.kotest:kotest-assertions-ktor` from 6.2.4 to 6.2.5
            Release notes

            Sourced from io.kotest:kotest-assertions-ktor's releases.

            v6.2.5

            What's Changed

            Full Changelog: https://github.com/kotest/kotest/compare/6.2.4...v6.2.5

            Commits
            • 41df99d Fix Android StackWalker compatibility (#6222)
            • 8655ca7 add back jsoup assertions (#6226)
            • e2271eb Alex/make default extension registry thread safe (#6199)
            • fdca609 Bound the short/byte/uShort/uByte shrinkers by the generator range (#6224)
            • 56301a6 Make the shouldBeSingleton block overloads infix (#6223)
            • dfee83a Use withEdtSafeAnalysis to improve safe analysis for spec and style… (#6215)
            • c44d1c3 use a singleton strategy for getConfigurationFactory of Kotest legacy produce...
            • b74611b skill version update PR upon release (#6213)
            • 0d3c09a 6.2.4 claude marketplace manual update
            • See full diff in compare view

            Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a00822b..9c2b948 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,7 +15,7 @@ netty = "4.2.16.Final" awaitility = "4.3.0" collections-immutable = "0.5.2" coroutines = "1.11.0" -kotest = "6.2.4" +kotest = "6.2.5" kotlinx-io = "0.9.1" ktor = "3.5.2" logging = "8.0.4" From e40ed0c7d738a56c83dd4c3187f5d93485a1868a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Sep 2026 16:15:30 +0200 Subject: [PATCH 445/449] chore(deps): bump the infrastructure group across 1 directory with 2 updates (#993) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the infrastructure group with 2 updates in the / directory: [io.netty:netty-bom](https://github.com/netty/netty) and org.slf4j:slf4j-simple. Updates `io.netty:netty-bom` from 4.2.16.Final to 4.2.18.Final
            Release notes

            Sourced from io.netty:netty-bom's releases.

            netty-4.2.18.Final

            Security fixes

            • CVE-2026-XXXXX : unbounded resource usage in io.netty:netty-codec-http (SPDY)
            • CVE-2026-XXXXX : unbounded resource usage in io.netty:netty-codec-http (HTTP/1.1)
            • CVE-2026-XXXXX : denial of service vector in io.netty:netty-codec-http2
            • CVE-2026-XXXXX : improper certificate validation in io.netty:netty-handler-ssl-ocsp
            • CVE-2026-XXXXX : memory leak in io.netty:netty-codec-stomp
            • CVE-2026-XXXXX : unbounded resource usage in io.netty:netty-codec-http
            • CVE-2026-XXXXX : improper header validation in io.netty:netty-codec-http3
            • CVE-2026-XXXXX : denial of service vector in io.netty:netty-codec-stomp
            • CVE-2026-XXXXX : parser desync/response smuggling in io.netty:netty-codec-memcache
            • CVE-2026-XXXXX : unbounded resource usage in io.netty:netty-codec-http3
            • CVE-2026-XXXXX : unbounded resource usage in io.netty:netty-codec-http3
            • CVE-2026-XXXXX : unbounded resource usage in io.netty:netty-codec-http3
            • CVE-2026-XXXXX : improper header validation in io.netty:netty-codec-http
            • CVE-2026-XXXXX : unbounded resource usage in io.netty:netty-codec-smtp
            • CVE-2026-XXXXX : memory leak in io.netty:netty-codec-haproxy
            • CVE-2026-XXXXX : request smuggling in io.netty:netty-codec-http (RTSP)
            • CVE-2026-XXXXX : request smuggling in io.netty:netty-codec-http (HTTP/1)
            • CVE-2026-XXXXX : improper header validation in io.netty:netty-codec-http2
            • CVE-2026-XXXXX : improper header validation in io.netty:netty-codec-http (HTTP/1)
            • CVE-2026-XXXXX : improper header validation in io.netty:netty-codec-http3
            • CVE-2026-XXXXX : improper header validation in io.netty:netty-codec-http3 and in io.netty:netty-codec-http2
            • CVE-2026-XXXXX : improper header validation in io.netty:netty-codec-http2
            • CVE-2026-XXXXX : improper hostname verification in io.netty:netty-codec-classes-quic
            • CVE-2026-XXXXX : unbounded resource usage in io.netty:netty-codec-redis
            • CVE-2026-XXXXX : improper header validation in io.netty:netty-codec-http (HTTP/1.1)
            • CVE-2026-XXXXX : request smuggling vector in io.netty:netty-codec-http (HTTP/1.1)
            • CVE-2026-XXXXX : unbounded resource usage in io.netty:netty-codec-mqtt
            • CVE-2026-XXXXX : improper CRLF neutralization in io.netty:netty-codec-smtp
            • CVE-2026-XXXXX : improper certificate validation in io.netty:netty-handler-ssl-ocsp
            • CVE-2026-XXXXX : improper header validation in io.netty:netty-codec-http2

            Compatibility Notes

            A number of security fixes have added additional validation and impose new resource usage limits, which may cause existing workloads to fail or be rejected. We recommend that you test your systems thoroughly as part of your Netty upgrade.

            Two specific changes are worth calling out:

            QUIC now explicitly requires X509ExtendedTrustManager when hostname verification is enabled. Previously, when configuring QUIC with an endpoint identification algorithm and an X509TrustManager, hostname verification would be silently skipped. This is now considered a misconfiguration and an exception will be thrown.

            HTTP/2 header value validation is now enabled by default. HTTP/2 header name validation has always been enabled by default, with an option to disable it, but HTTP/2 header value validation has been disabled by default until now. Configuration options still exist to disable this, but validation of HTTP header names and values are now both opt-in by default rather than opt-out.

            What's Changed

            ... (truncated)

            Commits
            • 2521f49 [maven-release-plugin] prepare release netty-4.2.18.Final
            • 6fd5327 HTTP/1 absolute-form Host mismatch is translated to HTTP/3 :authority, overri...
            • c44a052 SPDY: SpdySessionHandler must limit the concurrent streams
            • 374d965 HTTP: Limit the maximum number of concurrent pipelined requests
            • 7e8b325 HTTP/2: Limit HPACK encoding table size
            • e3ebf70 OCSP: Correctly handle that nextUpdate is optional
            • 5388535 STOMP: Correctly release partial content on handler removal
            • 3a80f5a WebSockets: Enforce a limit for the max pipelined requests in WebSocketServer...
            • 1b6ea48 HTTP3: Correctly handle ":authority" and "host" headers
            • 3630659 STOMP codec content-length long-to-int truncation causes infinite decode loop...
            • Additional commits viewable in compare view

            Updates `org.slf4j:slf4j-simple` from 2.0.18 to 2.0.19 Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9c2b948..a8d034f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,7 +9,7 @@ kover = "0.9.9" ktlint = "14.2.0" knit = "0.5.1" mavenPublish = "0.37.0" -netty = "4.2.16.Final" +netty = "4.2.18.Final" # libraries version awaitility = "4.3.0" @@ -21,7 +21,7 @@ ktor = "3.5.2" logging = "8.0.4" mockk = "1.14.11" serialization = "1.11.0" -slf4j = "2.0.18" +slf4j = "2.0.19" logback = "1.6.3" junit = "5.14.4" From e9b976198be75b75d5903fd4f4f632ba463ebd09 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Sep 2026 16:34:29 +0200 Subject: [PATCH 446/449] chore(deps): bump the other-dependencies group across 4 directories with 2 updates (#988) Bumps the other-dependencies group with 2 updates in the /samples/kotlin-mcp-client directory: [com.anthropic:anthropic-java](https://github.com/anthropics/anthropic-sdk-java) and org.slf4j:slf4j-simple. Bumps the other-dependencies group with 1 update in the /samples/kotlin-mcp-server directory: org.slf4j:slf4j-simple. Bumps the other-dependencies group with 1 update in the /samples/simple-streamable-server directory: org.slf4j:slf4j-simple. Bumps the other-dependencies group with 1 update in the /samples/weather-stdio-server directory: org.slf4j:slf4j-simple. Updates `com.anthropic:anthropic-java` from 2.60.0 to 2.62.0
            Release notes

            Sourced from com.anthropic:anthropic-java's releases.

            v2.62.0

            2.62.0 (2026-09-10)

            Full Changelog: v2.61.0...v2.62.0

            Features

            • api: add auto mode tool permissions for Managed Agents (e373fea)
            • api: add content_too_large web_fetch tool error code (96451de)
            • api: add the user-profiles-2026-09-04 beta value and external_user_details to user profiles (eb8c5b8)
            • api: support mounting public GitHub repositories without an authorization_token in Managed Agents sessions (2e1c9ed)

            Performance Improvements

            • streaming: append tool input JSON in place (d330f8b)
            • streaming: buffer text deltas instead of rebuilding blocks per delta (2bf8f70)

            Chores

            • docs: correct the environment scope field description (7888770)
            • internal: cap formatter JVM heap at 1 GiB (9b7b5a8)
            • internal: restore package version (28151f2)
            • internal: restore package version (6044d63)
            • internal: stop stamping the package version into generated files (ee275ad)
            • tests: restore empty test package marker files (3e2284c)

            v2.61.0

            2.61.0 (2026-09-04)

            Full Changelog: v2.60.0...v2.61.0

            Features

            • api: add Claude Tag category and user breakdowns to usage reports (7caaeb9)
            • api: add named types for organization compliance settings state (9576c86)
            • api: add workspace_id request option for multi-workspace credentials (cf7f13c)
            • api: raise Managed Agents vault refresh token limit to 8192 chars (d0c2286)

            Bug Fixes

            • api: remove response text block maxLength constraint (b6225ce)
            • client: read union common properties from raw JSON for unknown variants (3d2f38f)

            Chores

            • api: accept anthropic-version on Claude Enterprise admin endpoints (165fa6b)
            • api: declare specific error statuses instead of a generic 4XX response (e0dc35b)
            • api: update authentication parameters on spend limit create and delete (d054f6c)

            ... (truncated)

            Changelog

            Sourced from com.anthropic:anthropic-java's changelog.

            2.62.0 (2026-09-10)

            Full Changelog: v2.61.0...v2.62.0

            Features

            • api: add auto mode tool permissions for Managed Agents (e373fea)
            • api: add content_too_large web_fetch tool error code (96451de)
            • api: add the user-profiles-2026-09-04 beta value and external_user_details to user profiles (eb8c5b8)
            • api: support mounting public GitHub repositories without an authorization_token in Managed Agents sessions (2e1c9ed)

            Performance Improvements

            • streaming: append tool input JSON in place (d330f8b)
            • streaming: buffer text deltas instead of rebuilding blocks per delta (2bf8f70)

            Chores

            • docs: correct the environment scope field description (7888770)
            • internal: cap formatter JVM heap at 1 GiB (9b7b5a8)
            • internal: restore package version (28151f2)
            • internal: restore package version (6044d63)
            • internal: stop stamping the package version into generated files (ee275ad)
            • tests: restore empty test package marker files (3e2284c)

            2.61.0 (2026-09-04)

            Full Changelog: v2.60.0...v2.61.0

            Features

            • api: add Claude Tag category and user breakdowns to usage reports (7caaeb9)
            • api: add named types for organization compliance settings state (9576c86)
            • api: add workspace_id request option for multi-workspace credentials (cf7f13c)
            • api: raise Managed Agents vault refresh token limit to 8192 chars (d0c2286)
            • client: keep vault credential secrets out of Terraform state with write-only arguments (7556952)

            Bug Fixes

            • api: remove response text block maxLength constraint (b6225ce)
            • client: raise a clear error when an httpx object is passed instead of an httpx2 one (130d7af)
            • client: read union common properties from raw JSON for unknown variants (3d2f38f)

            Chores

            • api: accept anthropic-version on Claude Enterprise admin endpoints (165fa6b)

            ... (truncated)

            Commits
            • 88a1753 Merge pull request #400 from anthropics/release-please--branches--main--chang...
            • 6964f07 release: 2.62.0
            • e373fea feat(api): add auto mode tool permissions for Managed Agents
            • 2e1c9ed feat(api): support mounting public GitHub repositories without an authorizati...
            • eb8c5b8 feat(api): add the user-profiles-2026-09-04 beta value and external_user_deta...
            • 2bf8f70 perf(streaming): buffer text deltas instead of rebuilding blocks per delta
            • d330f8b perf(streaming): append tool input JSON in place
            • 7888770 chore(docs): correct the environment scope field description
            • 9b7b5a8 chore(internal): cap formatter JVM heap at 1 GiB
            • 96451de feat(api): add content_too_large web_fetch tool error code
            • Additional commits viewable in compare view

            Updates `org.slf4j:slf4j-simple` from 2.0.18 to 2.0.19 Updates `org.slf4j:slf4j-simple` from 2.0.18 to 2.0.19 Updates `org.slf4j:slf4j-simple` from 2.0.18 to 2.0.19 Updates `org.slf4j:slf4j-simple` from 2.0.18 to 2.0.19 Updates `org.slf4j:slf4j-simple` from 2.0.18 to 2.0.19 Updates `org.slf4j:slf4j-simple` from 2.0.18 to 2.0.19 Updates `org.slf4j:slf4j-simple` from 2.0.18 to 2.0.19 Updates `org.slf4j:slf4j-simple` from 2.0.18 to 2.0.19 Updates `org.slf4j:slf4j-simple` from 2.0.18 to 2.0.19 Updates `org.slf4j:slf4j-simple` from 2.0.18 to 2.0.19 Updates `org.slf4j:slf4j-simple` from 2.0.18 to 2.0.19 Updates `org.slf4j:slf4j-simple` from 2.0.18 to 2.0.19 Updates `org.slf4j:slf4j-simple` from 2.0.18 to 2.0.19 Updates `org.slf4j:slf4j-simple` from 2.0.18 to 2.0.19 Updates `org.slf4j:slf4j-simple` from 2.0.18 to 2.0.19 Updates `org.slf4j:slf4j-simple` from 2.0.18 to 2.0.19 Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlin-mcp-client/gradle/libs.versions.toml | 4 ++-- samples/kotlin-mcp-server/gradle/libs.versions.toml | 2 +- samples/simple-streamable-server/gradle/libs.versions.toml | 2 +- samples/weather-stdio-server/gradle/libs.versions.toml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/samples/kotlin-mcp-client/gradle/libs.versions.toml b/samples/kotlin-mcp-client/gradle/libs.versions.toml index c2edddb..b58eef2 100644 --- a/samples/kotlin-mcp-client/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-client/gradle/libs.versions.toml @@ -1,10 +1,10 @@ [versions] -anthropic = "2.60.0" +anthropic = "2.62.0" kotlin = "2.4.20" ktor = "3.5.2" mcp-kotlin = "0.15.0" shadow = "9.6.1" -slf4j = "2.0.18" +slf4j = "2.0.19" [libraries] anthropic-java = { group = "com.anthropic", name = "anthropic-java", version.ref = "anthropic" } diff --git a/samples/kotlin-mcp-server/gradle/libs.versions.toml b/samples/kotlin-mcp-server/gradle/libs.versions.toml index 5f8b83f..d46c2f1 100644 --- a/samples/kotlin-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlin-mcp-server/gradle/libs.versions.toml @@ -2,7 +2,7 @@ kotlin = "2.4.10" ktor = "3.5.2" mcp-kotlin = "0.15.0" -slf4j = "2.0.18" +slf4j = "2.0.19" shadow = "9.6.1" [libraries] diff --git a/samples/simple-streamable-server/gradle/libs.versions.toml b/samples/simple-streamable-server/gradle/libs.versions.toml index fd03619..0102599 100644 --- a/samples/simple-streamable-server/gradle/libs.versions.toml +++ b/samples/simple-streamable-server/gradle/libs.versions.toml @@ -3,7 +3,7 @@ kotlin = "2.4.10" ktor = "3.5.2" junit = "6.1.3" mcp-kotlin = "0.15.0" -slf4j = "2.0.18" +slf4j = "2.0.19" shadow = "9.6.1" [libraries] diff --git a/samples/weather-stdio-server/gradle/libs.versions.toml b/samples/weather-stdio-server/gradle/libs.versions.toml index b84a655..4301857 100644 --- a/samples/weather-stdio-server/gradle/libs.versions.toml +++ b/samples/weather-stdio-server/gradle/libs.versions.toml @@ -5,7 +5,7 @@ ktor = "3.5.2" logging = "8.0.4" mcp-kotlin = "0.15.0" shadow = "9.6.1" -slf4j = "2.0.18" +slf4j = "2.0.19" [libraries] kotlin-logging = { group = "io.github.oshai", name = "kotlin-logging-jvm", version.ref = "logging" } From 8b0badb88315ef55e1aaebb82314510e210a0866 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Sep 2026 17:26:51 +0200 Subject: [PATCH 447/449] chore(deps): bump kotlin from 2.4.10 to 2.4.20 in /samples/kotlinlang-mcp-server (#995) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `kotlin` from 2.4.10 to 2.4.20. Updates `org.jetbrains.kotlin.jvm` from 2.4.10 to 2.4.20
            Release notes

            Sourced from org.jetbrains.kotlin.jvm's releases.

            Kotlin 2.4.20

            Changelog

            Analysis API

            • KT-86546 Check suspicious when over ConeKotlinType in ConeTypeCompatibilityChecker
            • KT-85418 Implement an API for accessing deserialized file annotations in Analysis API
            • KT-74448 K2. False positive MISSING_DEPENDENCY_SUPERCLASS in LinkedListTest.kt, kotlinx.coroutines
            • KT-85856 containingSymbol of constructor property differs for local and non-local classes
            • KT-65417 K2 IDE: KTOR false positive expect-actual matching error on enum class because of implicit clone() in non-JVM source sets

            Analysis API. Code Compilation

            • KT-76457 K2 IDE / KMP Debugger: KISEWA “Cannot compile a common source without a JVM counterpart” on evaluating inline fun from common module inside jvm

            Analysis API. FIR

            • KT-70552 No expects for actual
            • KT-69727 K2 IDE. Wrong error in the editor on calling clone function of actual enum instance in non-jvm platform
            • KT-69726 FP errors on declaring fun clone() in actual enum in not-jvm source-set
            • KT-86014 Types are broken after remove parameter through change signature
            • KT-86363 KotlinIllegalArgumentExceptionWithAttachments: No dangling modifier found on companion blocks
            • KT-86147 Drop kotlin.parallel.resolve.under.global.lock registry key
            • KT-85543 Avoid lazy resolve for the contracts phase if no constracts might be resolved

            Analysis API. Infrastructure

            • KT-84914 Do not publish analysis-api-test-framework
            • KT-86986 kotlin-compiler-common-for-ide bundles unrelated Analysis API modules
            • KT-86186 Analysis API: Codebase tests run twice in some analysis modules — pick a single JUnit runner and migrate
            • KT-85360 Drop kotlin-compiler-testdata-for-ide artifact
            • KT-85585 Simplify the dependencies graph for the Analysis API modules
            • KT-85381 Remove tests for the FE10 implementation

            Analysis API. Light Classes

            New Features

            • KT-84645 Support resolving to companion block members & extensions from Java (light classes)
            • KT-80775 Support PsiClass#getRecordComponents in light classes

            Fixes

            • KT-57537 SLC: propagate default parameter value from (@JvmOverloads) expect declarations to actual declarations
            • KT-85040 [Analysis API] Improve Java / Kotlin interop in KMP projects
            • KT-87301 SymbolLightAccessorMethod#isValid returns false for delegated properties
            • KT-87171 SLC: non-mapped Kotlin collection supertype is dropped from supertype list
            • KT-87250 JvmExposeBoxed: light classes shouldn't be autogenerated for private declarations
            • KT-70428 AA: good code is red when a Java class extends a Kotlin class implementing MutableList by delegation
            • KT-63568 Symbol Light Classes: KtAnnotationApplicationWithArgumentsInfo.normalizedArguments() may work incorrectly when psi is not set

            ... (truncated)

            Changelog

            Sourced from org.jetbrains.kotlin.jvm's changelog.

            2.4.20

            Analysis API

            • KT-86546 Check suspicious when over ConeKotlinType in ConeTypeCompatibilityChecker
            • KT-85418 Implement an API for accessing deserialized file annotations in Analysis API
            • KT-74448 K2. False positive MISSING_DEPENDENCY_SUPERCLASS in LinkedListTest.kt, kotlinx.coroutines
            • KT-85856 containingSymbol of constructor property differs for local and non-local classes
            • KT-65417 K2 IDE: KTOR false positive expect-actual matching error on enum class because of implicit clone() in non-JVM source sets

            Analysis API. Code Compilation

            • KT-76457 K2 IDE / KMP Debugger: KISEWA “Cannot compile a common source without a JVM counterpart” on evaluating inline fun from common module inside jvm

            Analysis API. FIR

            • KT-70552 No expects for actual
            • KT-69727 K2 IDE. Wrong error in the editor on calling clone function of actual enum instance in non-jvm platform
            • KT-69726 FP errors on declaring fun clone() in actual enum in not-jvm source-set
            • KT-86014 Types are broken after remove parameter through change signature
            • KT-86363 KotlinIllegalArgumentExceptionWithAttachments: No dangling modifier found on companion blocks
            • KT-86147 Drop kotlin.parallel.resolve.under.global.lock registry key
            • KT-85543 Avoid lazy resolve for the contracts phase if no constracts might be resolved

            Analysis API. Infrastructure

            • KT-84914 Do not publish analysis-api-test-framework
            • KT-86986 kotlin-compiler-common-for-ide bundles unrelated Analysis API modules
            • KT-86186 Analysis API: Codebase tests run twice in some analysis modules — pick a single JUnit runner and migrate
            • KT-85360 Drop kotlin-compiler-testdata-for-ide artifact
            • KT-85585 Simplify the dependencies graph for the Analysis API modules
            • KT-85381 Remove tests for the FE10 implementation

            Analysis API. Light Classes

            New Features

            • KT-84645 Support resolving to companion block members & extensions from Java (light classes)
            • KT-80775 Support PsiClass#getRecordComponents in light classes

            Fixes

            • KT-57537 SLC: propagate default parameter value from (@JvmOverloads) expect declarations to actual declarations
            • KT-85040 [Analysis API] Improve Java / Kotlin interop in KMP projects
            • KT-87301 SymbolLightAccessorMethod#isValid returns false for delegated properties
            • KT-87171 SLC: non-mapped Kotlin collection supertype is dropped from supertype list
            • KT-87250 JvmExposeBoxed: light classes shouldn't be autogenerated for private declarations
            • KT-70428 AA: good code is red when a Java class extends a Kotlin class implementing MutableList by delegation
            • KT-63568 Symbol Light Classes: KtAnnotationApplicationWithArgumentsInfo.normalizedArguments() may work incorrectly when psi is not set
            • KT-36740 MPP: False-positive incompatible types in .java when using expect-class returned by non-expect member from common when actual is actual typealias

            ... (truncated)

            Commits
            • 890ac1d Add Changelog for 2.4.20-RC3
            • 8860aed 🍒 [FIR] Fix suspend conversion when expected type is nullable (#7769)
            • ab5bcd9 Edit ChangeLog for 2.4.20-RC2
            • 9464edc Add ChangeLog for 2.4.20-RC2
            • 0261e43 Cherry-pick "Fix asBodyAndResultVar call in `visitInlinedLambdaInComposable...
            • 0763513 [box-tests] Workaround for klib compatibility tests (#7626)
            • 045aec6 [Wasm] Append scripts from webpack.config.d in the end of the file (#7615)
            • cd7173b 🍒 [2.4.20] [K/JS] Keep associated obj annotation only if getInstance survives...
            • d36a2ff CastsOptimizationPass: break aliases cycles (#7578)
            • 8664983 [CLI] Restore custom IC search scope creation
            • Additional commits viewable in compare view

            Updates `org.jetbrains.kotlin.plugin.serialization` from 2.4.10 to 2.4.20
            Release notes

            Sourced from org.jetbrains.kotlin.plugin.serialization's releases.

            Kotlin 2.4.20

            Changelog

            Analysis API

            • KT-86546 Check suspicious when over ConeKotlinType in ConeTypeCompatibilityChecker
            • KT-85418 Implement an API for accessing deserialized file annotations in Analysis API
            • KT-74448 K2. False positive MISSING_DEPENDENCY_SUPERCLASS in LinkedListTest.kt, kotlinx.coroutines
            • KT-85856 containingSymbol of constructor property differs for local and non-local classes
            • KT-65417 K2 IDE: KTOR false positive expect-actual matching error on enum class because of implicit clone() in non-JVM source sets

            Analysis API. Code Compilation

            • KT-76457 K2 IDE / KMP Debugger: KISEWA “Cannot compile a common source without a JVM counterpart” on evaluating inline fun from common module inside jvm

            Analysis API. FIR

            • KT-70552 No expects for actual
            • KT-69727 K2 IDE. Wrong error in the editor on calling clone function of actual enum instance in non-jvm platform
            • KT-69726 FP errors on declaring fun clone() in actual enum in not-jvm source-set
            • KT-86014 Types are broken after remove parameter through change signature
            • KT-86363 KotlinIllegalArgumentExceptionWithAttachments: No dangling modifier found on companion blocks
            • KT-86147 Drop kotlin.parallel.resolve.under.global.lock registry key
            • KT-85543 Avoid lazy resolve for the contracts phase if no constracts might be resolved

            Analysis API. Infrastructure

            • KT-84914 Do not publish analysis-api-test-framework
            • KT-86986 kotlin-compiler-common-for-ide bundles unrelated Analysis API modules
            • KT-86186 Analysis API: Codebase tests run twice in some analysis modules — pick a single JUnit runner and migrate
            • KT-85360 Drop kotlin-compiler-testdata-for-ide artifact
            • KT-85585 Simplify the dependencies graph for the Analysis API modules
            • KT-85381 Remove tests for the FE10 implementation

            Analysis API. Light Classes

            New Features

            • KT-84645 Support resolving to companion block members & extensions from Java (light classes)
            • KT-80775 Support PsiClass#getRecordComponents in light classes

            Fixes

            • KT-57537 SLC: propagate default parameter value from (@JvmOverloads) expect declarations to actual declarations
            • KT-85040 [Analysis API] Improve Java / Kotlin interop in KMP projects
            • KT-87301 SymbolLightAccessorMethod#isValid returns false for delegated properties
            • KT-87171 SLC: non-mapped Kotlin collection supertype is dropped from supertype list
            • KT-87250 JvmExposeBoxed: light classes shouldn't be autogenerated for private declarations
            • KT-70428 AA: good code is red when a Java class extends a Kotlin class implementing MutableList by delegation
            • KT-63568 Symbol Light Classes: KtAnnotationApplicationWithArgumentsInfo.normalizedArguments() may work incorrectly when psi is not set

            ... (truncated)

            Changelog

            Sourced from org.jetbrains.kotlin.plugin.serialization's changelog.

            2.4.20

            Analysis API

            • KT-86546 Check suspicious when over ConeKotlinType in ConeTypeCompatibilityChecker
            • KT-85418 Implement an API for accessing deserialized file annotations in Analysis API
            • KT-74448 K2. False positive MISSING_DEPENDENCY_SUPERCLASS in LinkedListTest.kt, kotlinx.coroutines
            • KT-85856 containingSymbol of constructor property differs for local and non-local classes
            • KT-65417 K2 IDE: KTOR false positive expect-actual matching error on enum class because of implicit clone() in non-JVM source sets

            Analysis API. Code Compilation

            • KT-76457 K2 IDE / KMP Debugger: KISEWA “Cannot compile a common source without a JVM counterpart” on evaluating inline fun from common module inside jvm

            Analysis API. FIR

            • KT-70552 No expects for actual
            • KT-69727 K2 IDE. Wrong error in the editor on calling clone function of actual enum instance in non-jvm platform
            • KT-69726 FP errors on declaring fun clone() in actual enum in not-jvm source-set
            • KT-86014 Types are broken after remove parameter through change signature
            • KT-86363 KotlinIllegalArgumentExceptionWithAttachments: No dangling modifier found on companion blocks
            • KT-86147 Drop kotlin.parallel.resolve.under.global.lock registry key
            • KT-85543 Avoid lazy resolve for the contracts phase if no constracts might be resolved

            Analysis API. Infrastructure

            • KT-84914 Do not publish analysis-api-test-framework
            • KT-86986 kotlin-compiler-common-for-ide bundles unrelated Analysis API modules
            • KT-86186 Analysis API: Codebase tests run twice in some analysis modules — pick a single JUnit runner and migrate
            • KT-85360 Drop kotlin-compiler-testdata-for-ide artifact
            • KT-85585 Simplify the dependencies graph for the Analysis API modules
            • KT-85381 Remove tests for the FE10 implementation

            Analysis API. Light Classes

            New Features

            • KT-84645 Support resolving to companion block members & extensions from Java (light classes)
            • KT-80775 Support PsiClass#getRecordComponents in light classes

            Fixes

            • KT-57537 SLC: propagate default parameter value from (@JvmOverloads) expect declarations to actual declarations
            • KT-85040 [Analysis API] Improve Java / Kotlin interop in KMP projects
            • KT-87301 SymbolLightAccessorMethod#isValid returns false for delegated properties
            • KT-87171 SLC: non-mapped Kotlin collection supertype is dropped from supertype list
            • KT-87250 JvmExposeBoxed: light classes shouldn't be autogenerated for private declarations
            • KT-70428 AA: good code is red when a Java class extends a Kotlin class implementing MutableList by delegation
            • KT-63568 Symbol Light Classes: KtAnnotationApplicationWithArgumentsInfo.normalizedArguments() may work incorrectly when psi is not set
            • KT-36740 MPP: False-positive incompatible types in .java when using expect-class returned by non-expect member from common when actual is actual typealias

            ... (truncated)

            Commits
            • 890ac1d Add Changelog for 2.4.20-RC3
            • 8860aed 🍒 [FIR] Fix suspend conversion when expected type is nullable (#7769)
            • ab5bcd9 Edit ChangeLog for 2.4.20-RC2
            • 9464edc Add ChangeLog for 2.4.20-RC2
            • 0261e43 Cherry-pick "Fix asBodyAndResultVar call in `visitInlinedLambdaInComposable...
            • 0763513 [box-tests] Workaround for klib compatibility tests (#7626)
            • 045aec6 [Wasm] Append scripts from webpack.config.d in the end of the file (#7615)
            • cd7173b 🍒 [2.4.20] [K/JS] Keep associated obj annotation only if getInstance survives...
            • d36a2ff CastsOptimizationPass: break aliases cycles (#7578)
            • 8664983 [CLI] Restore custom IC search scope creation
            • Additional commits viewable in compare view

            Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/kotlinlang-mcp-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml index a113c64..0c11d3b 100644 --- a/samples/kotlinlang-mcp-server/gradle/libs.versions.toml +++ b/samples/kotlinlang-mcp-server/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin = "2.4.10" +kotlin = "2.4.20" ktor = "3.5.2" mcp-kotlin = "0.15.0" serialization = "1.11.0" From 1d04427cff47b31932409b90959869aa2934d680 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Sep 2026 17:58:02 +0200 Subject: [PATCH 448/449] chore(deps): bump kotlin from 2.4.10 to 2.4.20 in /samples/weather-stdio-server (#997) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `kotlin` from 2.4.10 to 2.4.20. Updates `org.jetbrains.kotlin.jvm` from 2.4.10 to 2.4.20
            Release notes

            Sourced from org.jetbrains.kotlin.jvm's releases.

            Kotlin 2.4.20

            Changelog

            Analysis API

            • KT-86546 Check suspicious when over ConeKotlinType in ConeTypeCompatibilityChecker
            • KT-85418 Implement an API for accessing deserialized file annotations in Analysis API
            • KT-74448 K2. False positive MISSING_DEPENDENCY_SUPERCLASS in LinkedListTest.kt, kotlinx.coroutines
            • KT-85856 containingSymbol of constructor property differs for local and non-local classes
            • KT-65417 K2 IDE: KTOR false positive expect-actual matching error on enum class because of implicit clone() in non-JVM source sets

            Analysis API. Code Compilation

            • KT-76457 K2 IDE / KMP Debugger: KISEWA “Cannot compile a common source without a JVM counterpart” on evaluating inline fun from common module inside jvm

            Analysis API. FIR

            • KT-70552 No expects for actual
            • KT-69727 K2 IDE. Wrong error in the editor on calling clone function of actual enum instance in non-jvm platform
            • KT-69726 FP errors on declaring fun clone() in actual enum in not-jvm source-set
            • KT-86014 Types are broken after remove parameter through change signature
            • KT-86363 KotlinIllegalArgumentExceptionWithAttachments: No dangling modifier found on companion blocks
            • KT-86147 Drop kotlin.parallel.resolve.under.global.lock registry key
            • KT-85543 Avoid lazy resolve for the contracts phase if no constracts might be resolved

            Analysis API. Infrastructure

            • KT-84914 Do not publish analysis-api-test-framework
            • KT-86986 kotlin-compiler-common-for-ide bundles unrelated Analysis API modules
            • KT-86186 Analysis API: Codebase tests run twice in some analysis modules — pick a single JUnit runner and migrate
            • KT-85360 Drop kotlin-compiler-testdata-for-ide artifact
            • KT-85585 Simplify the dependencies graph for the Analysis API modules
            • KT-85381 Remove tests for the FE10 implementation

            Analysis API. Light Classes

            New Features

            • KT-84645 Support resolving to companion block members & extensions from Java (light classes)
            • KT-80775 Support PsiClass#getRecordComponents in light classes

            Fixes

            • KT-57537 SLC: propagate default parameter value from (@JvmOverloads) expect declarations to actual declarations
            • KT-85040 [Analysis API] Improve Java / Kotlin interop in KMP projects
            • KT-87301 SymbolLightAccessorMethod#isValid returns false for delegated properties
            • KT-87171 SLC: non-mapped Kotlin collection supertype is dropped from supertype list
            • KT-87250 JvmExposeBoxed: light classes shouldn't be autogenerated for private declarations
            • KT-70428 AA: good code is red when a Java class extends a Kotlin class implementing MutableList by delegation
            • KT-63568 Symbol Light Classes: KtAnnotationApplicationWithArgumentsInfo.normalizedArguments() may work incorrectly when psi is not set

            ... (truncated)

            Changelog

            Sourced from org.jetbrains.kotlin.jvm's changelog.

            2.4.20

            Analysis API

            • KT-86546 Check suspicious when over ConeKotlinType in ConeTypeCompatibilityChecker
            • KT-85418 Implement an API for accessing deserialized file annotations in Analysis API
            • KT-74448 K2. False positive MISSING_DEPENDENCY_SUPERCLASS in LinkedListTest.kt, kotlinx.coroutines
            • KT-85856 containingSymbol of constructor property differs for local and non-local classes
            • KT-65417 K2 IDE: KTOR false positive expect-actual matching error on enum class because of implicit clone() in non-JVM source sets

            Analysis API. Code Compilation

            • KT-76457 K2 IDE / KMP Debugger: KISEWA “Cannot compile a common source without a JVM counterpart” on evaluating inline fun from common module inside jvm

            Analysis API. FIR

            • KT-70552 No expects for actual
            • KT-69727 K2 IDE. Wrong error in the editor on calling clone function of actual enum instance in non-jvm platform
            • KT-69726 FP errors on declaring fun clone() in actual enum in not-jvm source-set
            • KT-86014 Types are broken after remove parameter through change signature
            • KT-86363 KotlinIllegalArgumentExceptionWithAttachments: No dangling modifier found on companion blocks
            • KT-86147 Drop kotlin.parallel.resolve.under.global.lock registry key
            • KT-85543 Avoid lazy resolve for the contracts phase if no constracts might be resolved

            Analysis API. Infrastructure

            • KT-84914 Do not publish analysis-api-test-framework
            • KT-86986 kotlin-compiler-common-for-ide bundles unrelated Analysis API modules
            • KT-86186 Analysis API: Codebase tests run twice in some analysis modules — pick a single JUnit runner and migrate
            • KT-85360 Drop kotlin-compiler-testdata-for-ide artifact
            • KT-85585 Simplify the dependencies graph for the Analysis API modules
            • KT-85381 Remove tests for the FE10 implementation

            Analysis API. Light Classes

            New Features

            • KT-84645 Support resolving to companion block members & extensions from Java (light classes)
            • KT-80775 Support PsiClass#getRecordComponents in light classes

            Fixes

            • KT-57537 SLC: propagate default parameter value from (@JvmOverloads) expect declarations to actual declarations
            • KT-85040 [Analysis API] Improve Java / Kotlin interop in KMP projects
            • KT-87301 SymbolLightAccessorMethod#isValid returns false for delegated properties
            • KT-87171 SLC: non-mapped Kotlin collection supertype is dropped from supertype list
            • KT-87250 JvmExposeBoxed: light classes shouldn't be autogenerated for private declarations
            • KT-70428 AA: good code is red when a Java class extends a Kotlin class implementing MutableList by delegation
            • KT-63568 Symbol Light Classes: KtAnnotationApplicationWithArgumentsInfo.normalizedArguments() may work incorrectly when psi is not set
            • KT-36740 MPP: False-positive incompatible types in .java when using expect-class returned by non-expect member from common when actual is actual typealias

            ... (truncated)

            Commits
            • 890ac1d Add Changelog for 2.4.20-RC3
            • 8860aed 🍒 [FIR] Fix suspend conversion when expected type is nullable (#7769)
            • ab5bcd9 Edit ChangeLog for 2.4.20-RC2
            • 9464edc Add ChangeLog for 2.4.20-RC2
            • 0261e43 Cherry-pick "Fix asBodyAndResultVar call in `visitInlinedLambdaInComposable...
            • 0763513 [box-tests] Workaround for klib compatibility tests (#7626)
            • 045aec6 [Wasm] Append scripts from webpack.config.d in the end of the file (#7615)
            • cd7173b 🍒 [2.4.20] [K/JS] Keep associated obj annotation only if getInstance survives...
            • d36a2ff CastsOptimizationPass: break aliases cycles (#7578)
            • 8664983 [CLI] Restore custom IC search scope creation
            • Additional commits viewable in compare view

            Updates `org.jetbrains.kotlin.plugin.serialization` from 2.4.10 to 2.4.20
            Release notes

            Sourced from org.jetbrains.kotlin.plugin.serialization's releases.

            Kotlin 2.4.20

            Changelog

            Analysis API

            • KT-86546 Check suspicious when over ConeKotlinType in ConeTypeCompatibilityChecker
            • KT-85418 Implement an API for accessing deserialized file annotations in Analysis API
            • KT-74448 K2. False positive MISSING_DEPENDENCY_SUPERCLASS in LinkedListTest.kt, kotlinx.coroutines
            • KT-85856 containingSymbol of constructor property differs for local and non-local classes
            • KT-65417 K2 IDE: KTOR false positive expect-actual matching error on enum class because of implicit clone() in non-JVM source sets

            Analysis API. Code Compilation

            • KT-76457 K2 IDE / KMP Debugger: KISEWA “Cannot compile a common source without a JVM counterpart” on evaluating inline fun from common module inside jvm

            Analysis API. FIR

            • KT-70552 No expects for actual
            • KT-69727 K2 IDE. Wrong error in the editor on calling clone function of actual enum instance in non-jvm platform
            • KT-69726 FP errors on declaring fun clone() in actual enum in not-jvm source-set
            • KT-86014 Types are broken after remove parameter through change signature
            • KT-86363 KotlinIllegalArgumentExceptionWithAttachments: No dangling modifier found on companion blocks
            • KT-86147 Drop kotlin.parallel.resolve.under.global.lock registry key
            • KT-85543 Avoid lazy resolve for the contracts phase if no constracts might be resolved

            Analysis API. Infrastructure

            • KT-84914 Do not publish analysis-api-test-framework
            • KT-86986 kotlin-compiler-common-for-ide bundles unrelated Analysis API modules
            • KT-86186 Analysis API: Codebase tests run twice in some analysis modules — pick a single JUnit runner and migrate
            • KT-85360 Drop kotlin-compiler-testdata-for-ide artifact
            • KT-85585 Simplify the dependencies graph for the Analysis API modules
            • KT-85381 Remove tests for the FE10 implementation

            Analysis API. Light Classes

            New Features

            • KT-84645 Support resolving to companion block members & extensions from Java (light classes)
            • KT-80775 Support PsiClass#getRecordComponents in light classes

            Fixes

            • KT-57537 SLC: propagate default parameter value from (@JvmOverloads) expect declarations to actual declarations
            • KT-85040 [Analysis API] Improve Java / Kotlin interop in KMP projects
            • KT-87301 SymbolLightAccessorMethod#isValid returns false for delegated properties
            • KT-87171 SLC: non-mapped Kotlin collection supertype is dropped from supertype list
            • KT-87250 JvmExposeBoxed: light classes shouldn't be autogenerated for private declarations
            • KT-70428 AA: good code is red when a Java class extends a Kotlin class implementing MutableList by delegation
            • KT-63568 Symbol Light Classes: KtAnnotationApplicationWithArgumentsInfo.normalizedArguments() may work incorrectly when psi is not set

            ... (truncated)

            Changelog

            Sourced from org.jetbrains.kotlin.plugin.serialization's changelog.

            2.4.20

            Analysis API

            • KT-86546 Check suspicious when over ConeKotlinType in ConeTypeCompatibilityChecker
            • KT-85418 Implement an API for accessing deserialized file annotations in Analysis API
            • KT-74448 K2. False positive MISSING_DEPENDENCY_SUPERCLASS in LinkedListTest.kt, kotlinx.coroutines
            • KT-85856 containingSymbol of constructor property differs for local and non-local classes
            • KT-65417 K2 IDE: KTOR false positive expect-actual matching error on enum class because of implicit clone() in non-JVM source sets

            Analysis API. Code Compilation

            • KT-76457 K2 IDE / KMP Debugger: KISEWA “Cannot compile a common source without a JVM counterpart” on evaluating inline fun from common module inside jvm

            Analysis API. FIR

            • KT-70552 No expects for actual
            • KT-69727 K2 IDE. Wrong error in the editor on calling clone function of actual enum instance in non-jvm platform
            • KT-69726 FP errors on declaring fun clone() in actual enum in not-jvm source-set
            • KT-86014 Types are broken after remove parameter through change signature
            • KT-86363 KotlinIllegalArgumentExceptionWithAttachments: No dangling modifier found on companion blocks
            • KT-86147 Drop kotlin.parallel.resolve.under.global.lock registry key
            • KT-85543 Avoid lazy resolve for the contracts phase if no constracts might be resolved

            Analysis API. Infrastructure

            • KT-84914 Do not publish analysis-api-test-framework
            • KT-86986 kotlin-compiler-common-for-ide bundles unrelated Analysis API modules
            • KT-86186 Analysis API: Codebase tests run twice in some analysis modules — pick a single JUnit runner and migrate
            • KT-85360 Drop kotlin-compiler-testdata-for-ide artifact
            • KT-85585 Simplify the dependencies graph for the Analysis API modules
            • KT-85381 Remove tests for the FE10 implementation

            Analysis API. Light Classes

            New Features

            • KT-84645 Support resolving to companion block members & extensions from Java (light classes)
            • KT-80775 Support PsiClass#getRecordComponents in light classes

            Fixes

            • KT-57537 SLC: propagate default parameter value from (@JvmOverloads) expect declarations to actual declarations
            • KT-85040 [Analysis API] Improve Java / Kotlin interop in KMP projects
            • KT-87301 SymbolLightAccessorMethod#isValid returns false for delegated properties
            • KT-87171 SLC: non-mapped Kotlin collection supertype is dropped from supertype list
            • KT-87250 JvmExposeBoxed: light classes shouldn't be autogenerated for private declarations
            • KT-70428 AA: good code is red when a Java class extends a Kotlin class implementing MutableList by delegation
            • KT-63568 Symbol Light Classes: KtAnnotationApplicationWithArgumentsInfo.normalizedArguments() may work incorrectly when psi is not set
            • KT-36740 MPP: False-positive incompatible types in .java when using expect-class returned by non-expect member from common when actual is actual typealias

            ... (truncated)

            Commits
            • 890ac1d Add Changelog for 2.4.20-RC3
            • 8860aed 🍒 [FIR] Fix suspend conversion when expected type is nullable (#7769)
            • ab5bcd9 Edit ChangeLog for 2.4.20-RC2
            • 9464edc Add ChangeLog for 2.4.20-RC2
            • 0261e43 Cherry-pick "Fix asBodyAndResultVar call in `visitInlinedLambdaInComposable...
            • 0763513 [box-tests] Workaround for klib compatibility tests (#7626)
            • 045aec6 [Wasm] Append scripts from webpack.config.d in the end of the file (#7615)
            • cd7173b 🍒 [2.4.20] [K/JS] Keep associated obj annotation only if getInstance survives...
            • d36a2ff CastsOptimizationPass: break aliases cycles (#7578)
            • 8664983 [CLI] Restore custom IC search scope creation
            • Additional commits viewable in compare view

            Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/weather-stdio-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/weather-stdio-server/gradle/libs.versions.toml b/samples/weather-stdio-server/gradle/libs.versions.toml index 4301857..9d609a9 100644 --- a/samples/weather-stdio-server/gradle/libs.versions.toml +++ b/samples/weather-stdio-server/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] coroutines = "1.11.0" -kotlin = "2.4.10" +kotlin = "2.4.20" ktor = "3.5.2" logging = "8.0.4" mcp-kotlin = "0.15.0" From ae44babacce79c29509e78199880cb14bc62a71e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Sep 2026 14:04:06 +0200 Subject: [PATCH 449/449] chore(deps): bump kotlin from 2.4.10 to 2.4.20 in /samples/simple-streamable-server (#996) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps `kotlin` from 2.4.10 to 2.4.20. Updates `org.jetbrains.kotlin.jvm` from 2.4.10 to 2.4.20
            Release notes

            Sourced from org.jetbrains.kotlin.jvm's releases.

            Kotlin 2.4.20

            Changelog

            Analysis API

            • KT-86546 Check suspicious when over ConeKotlinType in ConeTypeCompatibilityChecker
            • KT-85418 Implement an API for accessing deserialized file annotations in Analysis API
            • KT-74448 K2. False positive MISSING_DEPENDENCY_SUPERCLASS in LinkedListTest.kt, kotlinx.coroutines
            • KT-85856 containingSymbol of constructor property differs for local and non-local classes
            • KT-65417 K2 IDE: KTOR false positive expect-actual matching error on enum class because of implicit clone() in non-JVM source sets

            Analysis API. Code Compilation

            • KT-76457 K2 IDE / KMP Debugger: KISEWA “Cannot compile a common source without a JVM counterpart” on evaluating inline fun from common module inside jvm

            Analysis API. FIR

            • KT-70552 No expects for actual
            • KT-69727 K2 IDE. Wrong error in the editor on calling clone function of actual enum instance in non-jvm platform
            • KT-69726 FP errors on declaring fun clone() in actual enum in not-jvm source-set
            • KT-86014 Types are broken after remove parameter through change signature
            • KT-86363 KotlinIllegalArgumentExceptionWithAttachments: No dangling modifier found on companion blocks
            • KT-86147 Drop kotlin.parallel.resolve.under.global.lock registry key
            • KT-85543 Avoid lazy resolve for the contracts phase if no constracts might be resolved

            Analysis API. Infrastructure

            • KT-84914 Do not publish analysis-api-test-framework
            • KT-86986 kotlin-compiler-common-for-ide bundles unrelated Analysis API modules
            • KT-86186 Analysis API: Codebase tests run twice in some analysis modules — pick a single JUnit runner and migrate
            • KT-85360 Drop kotlin-compiler-testdata-for-ide artifact
            • KT-85585 Simplify the dependencies graph for the Analysis API modules
            • KT-85381 Remove tests for the FE10 implementation

            Analysis API. Light Classes

            New Features

            • KT-84645 Support resolving to companion block members & extensions from Java (light classes)
            • KT-80775 Support PsiClass#getRecordComponents in light classes

            Fixes

            • KT-57537 SLC: propagate default parameter value from (@JvmOverloads) expect declarations to actual declarations
            • KT-85040 [Analysis API] Improve Java / Kotlin interop in KMP projects
            • KT-87301 SymbolLightAccessorMethod#isValid returns false for delegated properties
            • KT-87171 SLC: non-mapped Kotlin collection supertype is dropped from supertype list
            • KT-87250 JvmExposeBoxed: light classes shouldn't be autogenerated for private declarations
            • KT-70428 AA: good code is red when a Java class extends a Kotlin class implementing MutableList by delegation
            • KT-63568 Symbol Light Classes: KtAnnotationApplicationWithArgumentsInfo.normalizedArguments() may work incorrectly when psi is not set

            ... (truncated)

            Changelog

            Sourced from org.jetbrains.kotlin.jvm's changelog.

            2.4.20

            Analysis API

            • KT-86546 Check suspicious when over ConeKotlinType in ConeTypeCompatibilityChecker
            • KT-85418 Implement an API for accessing deserialized file annotations in Analysis API
            • KT-74448 K2. False positive MISSING_DEPENDENCY_SUPERCLASS in LinkedListTest.kt, kotlinx.coroutines
            • KT-85856 containingSymbol of constructor property differs for local and non-local classes
            • KT-65417 K2 IDE: KTOR false positive expect-actual matching error on enum class because of implicit clone() in non-JVM source sets

            Analysis API. Code Compilation

            • KT-76457 K2 IDE / KMP Debugger: KISEWA “Cannot compile a common source without a JVM counterpart” on evaluating inline fun from common module inside jvm

            Analysis API. FIR

            • KT-70552 No expects for actual
            • KT-69727 K2 IDE. Wrong error in the editor on calling clone function of actual enum instance in non-jvm platform
            • KT-69726 FP errors on declaring fun clone() in actual enum in not-jvm source-set
            • KT-86014 Types are broken after remove parameter through change signature
            • KT-86363 KotlinIllegalArgumentExceptionWithAttachments: No dangling modifier found on companion blocks
            • KT-86147 Drop kotlin.parallel.resolve.under.global.lock registry key
            • KT-85543 Avoid lazy resolve for the contracts phase if no constracts might be resolved

            Analysis API. Infrastructure

            • KT-84914 Do not publish analysis-api-test-framework
            • KT-86986 kotlin-compiler-common-for-ide bundles unrelated Analysis API modules
            • KT-86186 Analysis API: Codebase tests run twice in some analysis modules — pick a single JUnit runner and migrate
            • KT-85360 Drop kotlin-compiler-testdata-for-ide artifact
            • KT-85585 Simplify the dependencies graph for the Analysis API modules
            • KT-85381 Remove tests for the FE10 implementation

            Analysis API. Light Classes

            New Features

            • KT-84645 Support resolving to companion block members & extensions from Java (light classes)
            • KT-80775 Support PsiClass#getRecordComponents in light classes

            Fixes

            • KT-57537 SLC: propagate default parameter value from (@JvmOverloads) expect declarations to actual declarations
            • KT-85040 [Analysis API] Improve Java / Kotlin interop in KMP projects
            • KT-87301 SymbolLightAccessorMethod#isValid returns false for delegated properties
            • KT-87171 SLC: non-mapped Kotlin collection supertype is dropped from supertype list
            • KT-87250 JvmExposeBoxed: light classes shouldn't be autogenerated for private declarations
            • KT-70428 AA: good code is red when a Java class extends a Kotlin class implementing MutableList by delegation
            • KT-63568 Symbol Light Classes: KtAnnotationApplicationWithArgumentsInfo.normalizedArguments() may work incorrectly when psi is not set
            • KT-36740 MPP: False-positive incompatible types in .java when using expect-class returned by non-expect member from common when actual is actual typealias

            ... (truncated)

            Commits
            • 890ac1d Add Changelog for 2.4.20-RC3
            • 8860aed 🍒 [FIR] Fix suspend conversion when expected type is nullable (#7769)
            • ab5bcd9 Edit ChangeLog for 2.4.20-RC2
            • 9464edc Add ChangeLog for 2.4.20-RC2
            • 0261e43 Cherry-pick "Fix asBodyAndResultVar call in `visitInlinedLambdaInComposable...
            • 0763513 [box-tests] Workaround for klib compatibility tests (#7626)
            • 045aec6 [Wasm] Append scripts from webpack.config.d in the end of the file (#7615)
            • cd7173b 🍒 [2.4.20] [K/JS] Keep associated obj annotation only if getInstance survives...
            • d36a2ff CastsOptimizationPass: break aliases cycles (#7578)
            • 8664983 [CLI] Restore custom IC search scope creation
            • Additional commits viewable in compare view

            Updates `org.jetbrains.kotlin.plugin.serialization` from 2.4.10 to 2.4.20
            Release notes

            Sourced from org.jetbrains.kotlin.plugin.serialization's releases.

            Kotlin 2.4.20

            Changelog

            Analysis API

            • KT-86546 Check suspicious when over ConeKotlinType in ConeTypeCompatibilityChecker
            • KT-85418 Implement an API for accessing deserialized file annotations in Analysis API
            • KT-74448 K2. False positive MISSING_DEPENDENCY_SUPERCLASS in LinkedListTest.kt, kotlinx.coroutines
            • KT-85856 containingSymbol of constructor property differs for local and non-local classes
            • KT-65417 K2 IDE: KTOR false positive expect-actual matching error on enum class because of implicit clone() in non-JVM source sets

            Analysis API. Code Compilation

            • KT-76457 K2 IDE / KMP Debugger: KISEWA “Cannot compile a common source without a JVM counterpart” on evaluating inline fun from common module inside jvm

            Analysis API. FIR

            • KT-70552 No expects for actual
            • KT-69727 K2 IDE. Wrong error in the editor on calling clone function of actual enum instance in non-jvm platform
            • KT-69726 FP errors on declaring fun clone() in actual enum in not-jvm source-set
            • KT-86014 Types are broken after remove parameter through change signature
            • KT-86363 KotlinIllegalArgumentExceptionWithAttachments: No dangling modifier found on companion blocks
            • KT-86147 Drop kotlin.parallel.resolve.under.global.lock registry key
            • KT-85543 Avoid lazy resolve for the contracts phase if no constracts might be resolved

            Analysis API. Infrastructure

            • KT-84914 Do not publish analysis-api-test-framework
            • KT-86986 kotlin-compiler-common-for-ide bundles unrelated Analysis API modules
            • KT-86186 Analysis API: Codebase tests run twice in some analysis modules — pick a single JUnit runner and migrate
            • KT-85360 Drop kotlin-compiler-testdata-for-ide artifact
            • KT-85585 Simplify the dependencies graph for the Analysis API modules
            • KT-85381 Remove tests for the FE10 implementation

            Analysis API. Light Classes

            New Features

            • KT-84645 Support resolving to companion block members & extensions from Java (light classes)
            • KT-80775 Support PsiClass#getRecordComponents in light classes

            Fixes

            • KT-57537 SLC: propagate default parameter value from (@JvmOverloads) expect declarations to actual declarations
            • KT-85040 [Analysis API] Improve Java / Kotlin interop in KMP projects
            • KT-87301 SymbolLightAccessorMethod#isValid returns false for delegated properties
            • KT-87171 SLC: non-mapped Kotlin collection supertype is dropped from supertype list
            • KT-87250 JvmExposeBoxed: light classes shouldn't be autogenerated for private declarations
            • KT-70428 AA: good code is red when a Java class extends a Kotlin class implementing MutableList by delegation
            • KT-63568 Symbol Light Classes: KtAnnotationApplicationWithArgumentsInfo.normalizedArguments() may work incorrectly when psi is not set

            ... (truncated)

            Changelog

            Sourced from org.jetbrains.kotlin.plugin.serialization's changelog.

            2.4.20

            Analysis API

            • KT-86546 Check suspicious when over ConeKotlinType in ConeTypeCompatibilityChecker
            • KT-85418 Implement an API for accessing deserialized file annotations in Analysis API
            • KT-74448 K2. False positive MISSING_DEPENDENCY_SUPERCLASS in LinkedListTest.kt, kotlinx.coroutines
            • KT-85856 containingSymbol of constructor property differs for local and non-local classes
            • KT-65417 K2 IDE: KTOR false positive expect-actual matching error on enum class because of implicit clone() in non-JVM source sets

            Analysis API. Code Compilation

            • KT-76457 K2 IDE / KMP Debugger: KISEWA “Cannot compile a common source without a JVM counterpart” on evaluating inline fun from common module inside jvm

            Analysis API. FIR

            • KT-70552 No expects for actual
            • KT-69727 K2 IDE. Wrong error in the editor on calling clone function of actual enum instance in non-jvm platform
            • KT-69726 FP errors on declaring fun clone() in actual enum in not-jvm source-set
            • KT-86014 Types are broken after remove parameter through change signature
            • KT-86363 KotlinIllegalArgumentExceptionWithAttachments: No dangling modifier found on companion blocks
            • KT-86147 Drop kotlin.parallel.resolve.under.global.lock registry key
            • KT-85543 Avoid lazy resolve for the contracts phase if no constracts might be resolved

            Analysis API. Infrastructure

            • KT-84914 Do not publish analysis-api-test-framework
            • KT-86986 kotlin-compiler-common-for-ide bundles unrelated Analysis API modules
            • KT-86186 Analysis API: Codebase tests run twice in some analysis modules — pick a single JUnit runner and migrate
            • KT-85360 Drop kotlin-compiler-testdata-for-ide artifact
            • KT-85585 Simplify the dependencies graph for the Analysis API modules
            • KT-85381 Remove tests for the FE10 implementation

            Analysis API. Light Classes

            New Features

            • KT-84645 Support resolving to companion block members & extensions from Java (light classes)
            • KT-80775 Support PsiClass#getRecordComponents in light classes

            Fixes

            • KT-57537 SLC: propagate default parameter value from (@JvmOverloads) expect declarations to actual declarations
            • KT-85040 [Analysis API] Improve Java / Kotlin interop in KMP projects
            • KT-87301 SymbolLightAccessorMethod#isValid returns false for delegated properties
            • KT-87171 SLC: non-mapped Kotlin collection supertype is dropped from supertype list
            • KT-87250 JvmExposeBoxed: light classes shouldn't be autogenerated for private declarations
            • KT-70428 AA: good code is red when a Java class extends a Kotlin class implementing MutableList by delegation
            • KT-63568 Symbol Light Classes: KtAnnotationApplicationWithArgumentsInfo.normalizedArguments() may work incorrectly when psi is not set
            • KT-36740 MPP: False-positive incompatible types in .java when using expect-class returned by non-expect member from common when actual is actual typealias

            ... (truncated)

            Commits
            • 890ac1d Add Changelog for 2.4.20-RC3
            • 8860aed 🍒 [FIR] Fix suspend conversion when expected type is nullable (#7769)
            • ab5bcd9 Edit ChangeLog for 2.4.20-RC2
            • 9464edc Add ChangeLog for 2.4.20-RC2
            • 0261e43 Cherry-pick "Fix asBodyAndResultVar call in `visitInlinedLambdaInComposable...
            • 0763513 [box-tests] Workaround for klib compatibility tests (#7626)
            • 045aec6 [Wasm] Append scripts from webpack.config.d in the end of the file (#7615)
            • cd7173b 🍒 [2.4.20] [K/JS] Keep associated obj annotation only if getInstance survives...
            • d36a2ff CastsOptimizationPass: break aliases cycles (#7578)
            • 8664983 [CLI] Restore custom IC search scope creation
            • Additional commits viewable in compare view

            Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- samples/simple-streamable-server/gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/simple-streamable-server/gradle/libs.versions.toml b/samples/simple-streamable-server/gradle/libs.versions.toml index 0102599..5f35f74 100644 --- a/samples/simple-streamable-server/gradle/libs.versions.toml +++ b/samples/simple-streamable-server/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin = "2.4.10" +kotlin = "2.4.20" ktor = "3.5.2" junit = "6.1.3" mcp-kotlin = "0.15.0"