diff --git a/README.md b/README.md index b81aeb7aa..462b2e7ff 100644 --- a/README.md +++ b/README.md @@ -864,6 +864,11 @@ install(CORS) { ### SSE Transport +> [!WARNING] +> The SSE transport is [deprecated](https://modelcontextprotocol.io/specification/2026-07-28/deprecated) in the MCP +> specification and will be removed in a future release. +> Use the [Streamable HTTP transport](#streamable-http-transport) instead. + Server-Sent Events remain available for backwards compatibility with older MCP clients. Two Ktor helpers are provided: - **`Application.mcp { }`** — installs SSE and `ContentNegotiation` with `McpJson` automatically, then registers MCP @@ -912,8 +917,6 @@ embeddedServer(CIO, port = 3000) { --> -Prefer Streamable HTTP for new projects. - ### WebSocket Transport `WebSocketClientTransport` plus the matching server utilities provide full-duplex, low-latency connections—useful when diff --git a/docs/moduledoc.md b/docs/moduledoc.md index 05ce37098..2a49728bb 100644 --- a/docs/moduledoc.md +++ b/docs/moduledoc.md @@ -41,8 +41,8 @@ High-level client for connecting to MCP servers and invoking their features: `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. +- Transports: `StdioClientTransport`, `StreamableHttpClientTransport`, `WebSocketClientTransport`, and the deprecated + `SseClientTransport`, plus Ktor client extensions for quick wiring. Minimal WebSocket client: @@ -66,15 +66,15 @@ Server toolkit for exposing MCP tools, prompts, and resources: - `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. +- Transports: `StdioServerTransport` for CLI/editor bridges; Ktor extensions (`mcpStreamableHttp` for Streamable HTTP, + `mcpWebSocket` for WebSocket, and the deprecated `mcp` for SSE) for HTTP hosting. - Built-in notifications for list changes and resource subscriptions when capabilities enable them. -Minimal Ktor SSE server: +Minimal Ktor Streamable HTTP server: ```kotlin fun Application.module() { - mcp { + mcpStreamableHttp { Server( serverInfo = Implementation("sample-server", "1.0.0"), options = ServerOptions(ServerCapabilities( 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 f4a0ae490..c63a8b84d 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 @@ -18,6 +18,7 @@ import kotlin.test.Test import io.ktor.client.plugins.sse.SSE as ClientSSE import io.ktor.server.sse.SSE as ServerSSE +@Suppress("DEPRECATION") class SseTransportTest { @Test diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/ClientDisconnectIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/ClientDisconnectIntegrationTest.kt index e84a10ce0..3c2d0215d 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/ClientDisconnectIntegrationTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/ClientDisconnectIntegrationTest.kt @@ -54,6 +54,7 @@ class ClientDisconnectIntegrationTest { private fun testServer() = Server(Implementation("test-server", "1.0"), ServerOptions(ServerCapabilities())) + @Suppress("DEPRECATION") @Test fun `an SSE session ends when its client disconnects`(): Unit = runBlocking { val server = testServer() @@ -75,6 +76,7 @@ class ClientDisconnectIntegrationTest { } } + @Suppress("DEPRECATION") @Test fun `an SSE handler ends when the server closes its session`(): Unit = runBlocking { val server = testServer() @@ -101,6 +103,7 @@ class ClientDisconnectIntegrationTest { val handlerStarted = CompletableDeferred() val handlerCompleted = CompletableDeferred() withCioServer({ + @Suppress("DEPRECATION") mcp { testServer() } routing { get("/slow") { diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/TransportIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/TransportIntegrationTest.kt index 41f2b0379..131da9ec5 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/TransportIntegrationTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/TransportIntegrationTest.kt @@ -10,9 +10,9 @@ import io.ktor.client.plugins.websocket.WebSockets import io.ktor.server.engine.EmbeddedServer import io.ktor.server.engine.embeddedServer 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.client.mcpSseTransport import io.modelcontextprotocol.kotlin.sdk.client.mcpWebSocketTransport import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions @@ -139,7 +139,10 @@ class TransportIntegrationTest { engine = when (kind) { TransportKind.STDIO -> null - TransportKind.SSE -> embeddedServer(ServerCIO, host = HOST, port = 0) { mcp { server } } + TransportKind.SSE -> embeddedServer(ServerCIO, host = HOST, port = 0) { + @Suppress("DEPRECATION") + mcp { server } + } TransportKind.STREAMABLE_HTTP -> embeddedServer(ServerCIO, host = HOST, port = 0) { mcpStreamableHttp { server } @@ -170,7 +173,9 @@ class TransportIntegrationTest { private suspend fun clientTransport(): Transport = when (kind) { TransportKind.STDIO -> stdioClientTransport() - TransportKind.SSE -> SseClientTransport(httpClient { install(SSE) }, "http://$HOST:${port()}") + TransportKind.SSE -> + @Suppress("DEPRECATION") + httpClient { install(SSE) }.mcpSseTransport("http://$HOST:${port()}") TransportKind.STREAMABLE_HTTP -> StreamableHttpClientTransport(httpClient { install(SSE) }, "http://$HOST:${port()}/mcp") 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 index 33202147b..83b41c746 100644 --- 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 @@ -1,3 +1,5 @@ +@file:Suppress("DEPRECATION") + package io.modelcontextprotocol.kotlin.sdk.integration.sse import io.kotest.matchers.shouldBe diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/SseSubpathTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/SseSubpathTest.kt index b780eec8a..24791f051 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/SseSubpathTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/SseSubpathTest.kt @@ -1,3 +1,5 @@ +@file:Suppress("DEPRECATION") + package io.modelcontextprotocol.kotlin.sdk.integration.sse import io.kotest.matchers.shouldBe diff --git a/kotlin-sdk-client/Module.md b/kotlin-sdk-client/Module.md index 4e6fd6303..2e372e7ec 100644 --- a/kotlin-sdk-client/Module.md +++ b/kotlin-sdk-client/Module.md @@ -14,7 +14,8 @@ protocol types from `kotlin-sdk-core` and ships with transports for CLI and Ktor - **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. + POST back-channel, deprecated), 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. @@ -50,7 +51,8 @@ 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. + `HttpClient.mcpSseTransport(url)`); sessions are keyed by `sessionId` managed by the SDK. Deprecated; use Streamable + HTTP instead. - **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 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 0c695041e..7af68ad27 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 @@ -15,6 +15,11 @@ import kotlin.time.Duration * @param requestBuilder Optional lambda to configure the HTTP request. * @return A [SseClientTransport] configured for MCP communication. */ +@Deprecated( + "$SSE_DEPRECATION_MESSAGE Use mcpStreamableHttpTransport instead.", + level = DeprecationLevel.WARNING, +) +@Suppress("DEPRECATION") public fun HttpClient.mcpSseTransport( urlString: String? = null, reconnectionTime: Duration? = null, @@ -29,6 +34,11 @@ public fun HttpClient.mcpSseTransport( * @param requestBuilder Optional lambda to configure the HTTP request. * @return A connected [Client] ready for MCP communication. */ +@Deprecated( + "$SSE_DEPRECATION_MESSAGE Use mcpStreamableHttp instead.", + level = DeprecationLevel.WARNING, +) +@Suppress("DEPRECATION") public suspend fun HttpClient.mcpSse( urlString: String? = null, reconnectionTime: Duration? = null, 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 29d1d3b98..0179c1645 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 @@ -37,10 +37,17 @@ import kotlinx.serialization.SerializationException import kotlin.concurrent.atomics.ExperimentalAtomicApi import kotlin.time.Duration +internal const val SSE_DEPRECATION_MESSAGE = + "The SSE transport is deprecated in the MCP specification (SEP-2596) and will be removed in a future release." + /** * 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. */ +@Deprecated( + "$SSE_DEPRECATION_MESSAGE Use StreamableHttpClientTransport instead.", + level = DeprecationLevel.WARNING, +) @OptIn(ExperimentalAtomicApi::class) public class SseClientTransport( private val client: HttpClient, 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 265b648cb..2a7dc56aa 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,3 +1,5 @@ +@file:Suppress("DEPRECATION") + package io.modelcontextprotocol.kotlin.sdk.client.sse import io.kotest.assertions.throwables.shouldThrow diff --git a/kotlin-sdk-server/Module.md b/kotlin-sdk-server/Module.md index 176a398f3..6ce59f305 100644 --- a/kotlin-sdk-server/Module.md +++ b/kotlin-sdk-server/Module.md @@ -10,8 +10,8 @@ that make it straightforward to expose tools, prompts, and resources to MCP clie 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. +- **Transports**: Ready-to-use transports for STDIO (CLI-style hosting), Ktor Server-Sent Events (SSE, deprecated) with + POST back-channel, and Ktor WebSocket. All share the common `Transport` abstraction from the SDK. - **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 @@ -58,6 +58,9 @@ server.createSession(transport) ### Hosting with Ktor (SSE) +The SSE transport is deprecated in the MCP specification and will be removed in a future release. Use +`mcpStreamableHttp` instead. + ```kotlin fun Application.module() { mcp { 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 f2d6cd589..c405f7bfe 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 @@ -64,8 +64,12 @@ private val logger = KotlinLogging.logger {} * that creates and returns the [Server] to handle the connection. * @throws IllegalStateException if the [SSE] plugin is not installed. */ +@Deprecated( + "$SSE_DEPRECATION_MESSAGE Use StreamableHttpServerTransport instead.", + level = DeprecationLevel.WARNING, +) @KtorDsl -@Suppress("LongParameterList") +@Suppress("DEPRECATION", "LongParameterList") public fun Route.mcp( path: String, enableDnsRebindingProtection: Boolean = true, @@ -103,8 +107,12 @@ public fun Route.mcp( * that creates and returns the [Server] to handle the connection. * @throws IllegalStateException if the [SSE] plugin is not installed. */ +@Deprecated( + "$SSE_DEPRECATION_MESSAGE Use StreamableHttpServerTransport instead.", + level = DeprecationLevel.WARNING, +) @KtorDsl -@Suppress("LongParameterList") +@Suppress("DEPRECATION", "LongParameterList") public fun Route.mcp( enableDnsRebindingProtection: Boolean = true, allowedHosts: List? = null, @@ -162,8 +170,12 @@ public fun Route.mcp( * @param block factory block with access to the [ServerSSESession] * that creates and returns the [Server] to handle the connection. */ +@Deprecated( + "$SSE_DEPRECATION_MESSAGE Use mcpStreamableHttp instead.", + level = DeprecationLevel.WARNING, +) @KtorDsl -@Suppress("LongParameterList") +@Suppress("DEPRECATION", "LongParameterList") public fun Application.mcp( enableDnsRebindingProtection: Boolean = true, allowedHosts: List? = null, @@ -367,6 +379,7 @@ public fun Application.mcpStatelessStreamableHttp( ) } +@Suppress("DEPRECATION") private suspend fun ServerSSESession.mcpSseEndpoint( postEndpoint: String, transportManager: TransportManager, @@ -394,6 +407,7 @@ private suspend fun ServerSSESession.mcpSseEndpoint( } } +@Suppress("DEPRECATION") private fun ServerSSESession.mcpSseTransport( postEndpoint: String, transportManager: TransportManager, @@ -430,6 +444,7 @@ private suspend fun RoutingContext.mcpStatelessStreamableHttpEndpoint( } } +@Suppress("DEPRECATION") 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") 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 f525ec29e..c65fe52b7 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,6 +21,9 @@ import kotlin.uuid.Uuid internal const val SESSION_ID_PARAM = "sessionId" +internal const val SSE_DEPRECATION_MESSAGE = + "The SSE transport is deprecated in the MCP specification (SEP-2596) and will be removed in a future release." + /** * Server transport for SSE: this will send messages over an SSE connection and receive messages from HTTP POST requests. * @@ -31,6 +34,10 @@ internal const val SESSION_ID_PARAM = "sessionId" * @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. */ +@Deprecated( + "$SSE_DEPRECATION_MESSAGE Use StreamableHttpServerTransport instead.", + level = DeprecationLevel.WARNING, +) @OptIn(ExperimentalAtomicApi::class) public class SseServerTransport( private val endpoint: String, 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 index c3969b54d..a77014487 100644 --- 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 @@ -202,6 +202,7 @@ class DnsRebindingProtectionTest { ex.message shouldContain "not-a-url" } + @Suppress("DEPRECATION") @Test fun `Route mcp with DNS protection enabled rejects non-localhost`() = testApplication { application { @@ -218,6 +219,7 @@ class DnsRebindingProtectionTest { response.shouldHaveStatus(HttpStatusCode.Forbidden) } + @Suppress("DEPRECATION") @Test fun `Route mcp with DNS protection disabled allows any host`() = testApplication { application { diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorExtensionsTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorExtensionsTest.kt index a132341ad..8b04cbaa4 100644 --- a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorExtensionsTest.kt +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorExtensionsTest.kt @@ -31,6 +31,7 @@ import kotlin.time.Duration.Companion.seconds class KtorExtensionsTest { + @Suppress("DEPRECATION") @Test fun `Route mcp should throw at registration time if SSE plugin is not installed`() { val exception = shouldThrow { @@ -46,6 +47,7 @@ class KtorExtensionsTest { exception.message shouldContain "install(SSE)" } + @Suppress("DEPRECATION") @Test fun `Route mcp should register SSE and POST endpoints at the given subpath`() = testApplication { application { @@ -60,6 +62,7 @@ class KtorExtensionsTest { client.assertMcpEndpointsAt("/api/mcp") } + @Suppress("DEPRECATION") @Test fun `Route mcp with path parameter should register endpoints at the resolved path`() = testApplication { application { @@ -79,6 +82,7 @@ class KtorExtensionsTest { * Verifies that a closed session stops receiving POSTs and is not retained until the * [Server] itself closes, even while its SSE connection is still open. */ + @Suppress("DEPRECATION") @Test fun `Route mcp stops routing POSTs to a session once it closes`() = testApplication { val server = testServer() @@ -101,6 +105,7 @@ class KtorExtensionsTest { } } + @Suppress("DEPRECATION") @Test fun `Application mcp should install SSE and register endpoints at the root`() = testApplication { application { @@ -110,6 +115,7 @@ class KtorExtensionsTest { client.assertMcpEndpointsAt("/") } + @Suppress("DEPRECATION") @Test fun `Application mcp should reuse an installed SSE plugin`() = testApplication { application { 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 index b5431fcc2..48098ec96 100644 --- 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 @@ -16,6 +16,7 @@ import io.ktor.server.testing.testApplication import java.util.concurrent.atomic.AtomicBoolean import kotlin.test.Test +@Suppress("DEPRECATION") class SseServerTransportTest { @Test