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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -865,6 +865,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
Expand Down Expand Up @@ -913,8 +918,6 @@ embeddedServer(CIO, port = 3000) {
-->
<!--- KNIT example-server-routes-03.kt -->

Prefer Streamable HTTP for new projects.

### WebSocket Transport

`WebSocketClientTransport` plus the matching server utilities provide full-duplex, low-latency connections—useful when
Expand Down
12 changes: 6 additions & 6 deletions docs/moduledoc.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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()
Expand All @@ -101,6 +103,7 @@ class ClientDisconnectIntegrationTest {
val handlerStarted = CompletableDeferred<Unit>()
val handlerCompleted = CompletableDeferred<Boolean>()
withCioServer({
@Suppress("DEPRECATION")
mcp { testServer() }
routing {
get("/slow") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 }
Expand Down Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@file:Suppress("DEPRECATION")

package io.modelcontextprotocol.kotlin.sdk.integration.sse

import io.kotest.matchers.shouldBe
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@file:Suppress("DEPRECATION")

package io.modelcontextprotocol.kotlin.sdk.integration.sse

import io.kotest.matchers.shouldBe
Expand Down
6 changes: 4 additions & 2 deletions kotlin-sdk-client/Module.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ import kotlin.time.Duration
* @param requestBuilder Optional lambda to configure the HTTP request.
* @return A [SseClientTransport] configured for MCP communication.
*/
@Deprecated(
"The SSE transport is deprecated in the MCP specification (SEP-2596) " +
"and will be removed in a future release. Use mcpStreamableHttpTransport instead.",
level = DeprecationLevel.WARNING,
)
@Suppress("DEPRECATION")
public fun HttpClient.mcpSseTransport(
urlString: String? = null,
reconnectionTime: Duration? = null,
Expand All @@ -29,6 +35,12 @@ public fun HttpClient.mcpSseTransport(
* @param requestBuilder Optional lambda to configure the HTTP request.
* @return A connected [Client] ready for MCP communication.
*/
@Deprecated(
"The SSE transport is deprecated in the MCP specification (SEP-2596) " +
"and will be removed in a future release. Use mcpStreamableHttp instead.",
level = DeprecationLevel.WARNING,
)
@Suppress("DEPRECATION")
public suspend fun HttpClient.mcpSse(
urlString: String? = null,
reconnectionTime: Duration? = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ import kotlin.time.Duration
* 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(
"The SSE transport is deprecated in the MCP specification (SEP-2596) " +
"and will be removed in a future release. Use StreamableHttpClientTransport instead.",
Comment thread
devcrocod marked this conversation as resolved.
Comment thread
devcrocod marked this conversation as resolved.
level = DeprecationLevel.WARNING,
)
@OptIn(ExperimentalAtomicApi::class)
public class SseClientTransport(
private val client: HttpClient,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@file:Suppress("DEPRECATION")

package io.modelcontextprotocol.kotlin.sdk.client.sse

import io.kotest.assertions.throwables.shouldThrow
Expand Down
7 changes: 5 additions & 2 deletions kotlin-sdk-server/Module.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,13 @@ 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(
"The SSE transport is deprecated in the MCP specification (SEP-2596) " +
"and will be removed in a future release. Use StreamableHttpServerTransport instead.",
level = DeprecationLevel.WARNING,
)
@KtorDsl
@Suppress("LongParameterList")
@Suppress("DEPRECATION", "LongParameterList")
public fun Route.mcp(
path: String,
enableDnsRebindingProtection: Boolean = true,
Expand Down Expand Up @@ -103,8 +108,13 @@ public fun Route.mcp(
* that creates and returns the [Server] to handle the connection.
* @throws IllegalStateException if the [SSE] plugin is not installed.
*/
@Deprecated(
"The SSE transport is deprecated in the MCP specification (SEP-2596) " +
"and will be removed in a future release. Use StreamableHttpServerTransport instead.",
level = DeprecationLevel.WARNING,
)
@KtorDsl
@Suppress("LongParameterList")
@Suppress("DEPRECATION", "LongParameterList")
public fun Route.mcp(
enableDnsRebindingProtection: Boolean = true,
allowedHosts: List<String>? = null,
Expand Down Expand Up @@ -162,8 +172,13 @@ public fun Route.mcp(
* @param block factory block with access to the [ServerSSESession]
* that creates and returns the [Server] to handle the connection.
*/
@Deprecated(
"The SSE transport is deprecated in the MCP specification (SEP-2596) " +
"and will be removed in a future release. Use mcpStreamableHttp instead.",
level = DeprecationLevel.WARNING,
)
@KtorDsl
@Suppress("LongParameterList")
@Suppress("DEPRECATION", "LongParameterList")
public fun Application.mcp(
enableDnsRebindingProtection: Boolean = true,
allowedHosts: List<String>? = null,
Expand Down Expand Up @@ -397,6 +412,7 @@ public fun Application.mcpStatelessStreamableHttp(
)
}

@Suppress("DEPRECATION")
private suspend fun ServerSSESession.mcpSseEndpoint(
postEndpoint: String,
transportManager: TransportManager<SseServerTransport>,
Expand Down Expand Up @@ -424,6 +440,7 @@ private suspend fun ServerSSESession.mcpSseEndpoint(
}
}

@Suppress("DEPRECATION")
private fun ServerSSESession.mcpSseTransport(
postEndpoint: String,
transportManager: TransportManager<SseServerTransport>,
Expand Down Expand Up @@ -460,6 +477,7 @@ private suspend fun RoutingContext.mcpStatelessStreamableHttpEndpoint(
}
}

@Suppress("DEPRECATION")
private suspend fun RoutingContext.mcpPostEndpoint(transportManager: TransportManager<SseServerTransport>) {
val sessionId: String = call.request.queryParameters["sessionId"] ?: run {
call.respond(HttpStatusCode.BadRequest, "sessionId query parameter is not provided")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ 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(
"The SSE transport is deprecated in the MCP specification (SEP-2596) " +
"and will be removed in a future release. Use StreamableHttpServerTransport instead.",
level = DeprecationLevel.WARNING,
)
@OptIn(ExperimentalAtomicApi::class)
public class SseServerTransport(
private val endpoint: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<IllegalStateException> {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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()
Expand All @@ -101,6 +105,7 @@ class KtorExtensionsTest {
}
}

@Suppress("DEPRECATION")
@Test
fun `Application mcp should install SSE and register endpoints at the root`() = testApplication {
application {
Expand All @@ -110,6 +115,7 @@ class KtorExtensionsTest {
client.assertMcpEndpointsAt("/")
}

@Suppress("DEPRECATION")
@Test
fun `Application mcp should reuse an installed SSE plugin`() = testApplication {
application {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading