Skip to content

Commit 70a11d4

Browse files
committed
Added mcp Ktor server route alongside Application extension
1 parent a9f524a commit 70a11d4

File tree

6 files changed

+145
-218
lines changed

6 files changed

+145
-218
lines changed

README.md

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,13 @@ server.connect(transport)
114114

115115
### Using SSE Transport
116116

117+
Directly in Ktor's `Application`:
117118
```kotlin
118119
import io.ktor.server.application.*
119-
import io.modelcontextprotocol.kotlin.sdk.server.MCP
120+
import io.modelcontextprotocol.kotlin.sdk.server.mcp
120121

121122
fun Application.module() {
122-
MCP {
123+
mcp {
123124
Server(
124125
serverInfo = Implementation(
125126
name = "example-sse-server",
@@ -136,6 +137,35 @@ fun Application.module() {
136137
}
137138
```
138139

140+
Inside a custom Ktor's `Route`:
141+
```kotlin
142+
import io.ktor.server.application.*
143+
import io.ktor.server.sse.SSE
144+
import io.modelcontextprotocol.kotlin.sdk.server.mcp
145+
146+
fun Application.module() {
147+
install(SSE)
148+
149+
routing {
150+
route("myRoute") {
151+
mcp {
152+
Server(
153+
serverInfo = Implementation(
154+
name = "example-sse-server",
155+
version = "1.0.0"
156+
),
157+
options = ServerOptions(
158+
capabilities = ServerCapabilities(
159+
prompts = ServerCapabilities.Prompts(listChanged = null),
160+
resources = ServerCapabilities.Resources(subscribe = null, listChanged = null)
161+
)
162+
)
163+
)
164+
}
165+
}
166+
}
167+
}
168+
```
139169
## Contributing
140170

141171
Please see the [contribution guide](CONTRIBUTING.md) and the [Code of conduct](CODE_OF_CONDUCT.md) before contributing.

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ kotlin {
225225
jvmTest {
226226
dependencies {
227227
implementation(libs.mockk)
228+
implementation(libs.slf4j.simple)
228229
}
229230
}
230231
}

gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ mockk = "1.13.13"
1212
logging = "7.0.0"
1313
jreleaser = "1.15.0"
1414
binaryCompatibilityValidatorPlugin = "0.17.0"
15+
slf4j = "2.0.16"
1516

1617
[libraries]
1718
# Kotlinx libraries
@@ -30,6 +31,7 @@ kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-cor
3031
kotlinx-coroutines-debug = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-debug", version.ref = "coroutines" }
3132
ktor-server-test-host = { group = "io.ktor", name = "ktor-server-test-host", version.ref = "ktor" }
3233
mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" }
34+
slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" }
3335

3436

3537

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package io.modelcontextprotocol.kotlin.sdk.server
2+
3+
import io.github.oshai.kotlinlogging.KotlinLogging
4+
import io.ktor.http.*
5+
import io.ktor.server.application.*
6+
import io.ktor.server.response.*
7+
import io.ktor.server.routing.*
8+
import io.ktor.server.sse.*
9+
import io.ktor.util.collections.*
10+
import io.ktor.utils.io.KtorDsl
11+
12+
private val logger = KotlinLogging.logger {}
13+
14+
@KtorDsl
15+
public fun Routing.mcp(path: String, block: () -> Server) {
16+
route(path) {
17+
mcp(block)
18+
}
19+
}
20+
21+
/**
22+
* Configures the Ktor Application to handle Model Context Protocol (MCP) over Server-Sent Events (SSE).
23+
*/
24+
@KtorDsl
25+
public fun Routing.mcp(block: () -> Server) {
26+
val transports = ConcurrentMap<String, SseServerTransport>()
27+
28+
sse {
29+
mcpSseEndpoint("", transports, block)
30+
}
31+
32+
post {
33+
mcpPostEndpoint(transports)
34+
}
35+
}
36+
37+
@Suppress("FunctionName")
38+
@Deprecated("Use mcp() instead", ReplaceWith("mcp(block)"), DeprecationLevel.WARNING)
39+
public fun Application.MCP(block: () -> Server) {
40+
mcp(block)
41+
}
42+
43+
@KtorDsl
44+
public fun Application.mcp(block: () -> Server) {
45+
val transports = ConcurrentMap<String, SseServerTransport>()
46+
47+
install(SSE)
48+
49+
routing {
50+
sse("/sse") {
51+
mcpSseEndpoint("/message", transports, block)
52+
}
53+
54+
post("/message") {
55+
mcpPostEndpoint(transports)
56+
}
57+
}
58+
}
59+
60+
private suspend fun ServerSSESession.mcpSseEndpoint(
61+
postEndpoint: String,
62+
transports: ConcurrentMap<String, SseServerTransport>,
63+
block: () -> Server,
64+
) {
65+
val transport = mcpSseTransport(postEndpoint, transports)
66+
67+
val server = block()
68+
69+
server.onClose {
70+
logger.info { "Server connection closed for sessionId: ${transport.sessionId}" }
71+
transports.remove(transport.sessionId)
72+
}
73+
74+
server.connect(transport)
75+
logger.debug { "Server connected to transport for sessionId: ${transport.sessionId}" }
76+
}
77+
78+
internal fun ServerSSESession.mcpSseTransport(
79+
postEndpoint: String,
80+
transports: ConcurrentMap<String, SseServerTransport>,
81+
): SseServerTransport {
82+
val transport = SseServerTransport(postEndpoint, this)
83+
transports[transport.sessionId] = transport
84+
85+
logger.info { "New SSE connection established and stored with sessionId: ${transport.sessionId}" }
86+
87+
return transport
88+
}
89+
90+
internal suspend fun RoutingContext.mcpPostEndpoint(
91+
transports: ConcurrentMap<String, SseServerTransport>,
92+
) {
93+
val sessionId: String = call.request.queryParameters["sessionId"]
94+
?: run {
95+
call.respond(HttpStatusCode.BadRequest, "sessionId query parameter is not provided")
96+
return
97+
}
98+
99+
logger.debug { "Received message for sessionId: $sessionId" }
100+
101+
val transport = transports[sessionId]
102+
if (transport == null) {
103+
logger.warn { "Session not found for sessionId: $sessionId" }
104+
call.respond(HttpStatusCode.NotFound, "Session not found")
105+
return
106+
}
107+
108+
transport.handlePostMessage(call)
109+
logger.trace { "Message handled for sessionId: $sessionId" }
110+
}

src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/McpKtorServerPlugin.kt

Lines changed: 0 additions & 54 deletions
This file was deleted.

0 commit comments

Comments
 (0)