Skip to content

Commit cc3db2b

Browse files
authored
Merge branch 'main' into devcrocod/update-dependabot
2 parents ca30936 + 958e5e5 commit cc3db2b

File tree

4 files changed

+66
-9
lines changed

4 files changed

+66
-9
lines changed

gradle/libs.versions.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ kotlin = "2.2.0"
44
dokka = "2.0.0"
55

66
# libraries version
7-
serialization = "1.8.1"
7+
serialization = "1.9.0"
88
coroutines = "1.10.2"
99
ktor = "3.1.3"
10-
mockk = "1.13.13"
10+
mockk = "1.14.4"
1111
logging = "7.0.7"
1212
jreleaser = "1.17.0"
1313
binaryCompatibilityValidatorPlugin = "0.18.0"
14-
slf4j = "2.0.16"
14+
slf4j = "2.0.17"
1515
kotest = "5.9.1"
1616

1717
[libraries]

src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import io.ktor.http.HttpHeaders
1212
import io.ktor.http.Url
1313
import io.ktor.http.append
1414
import io.ktor.http.isSuccess
15+
import io.ktor.http.protocolWithAuthority
1516
import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage
1617
import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport
1718
import io.modelcontextprotocol.kotlin.sdk.shared.McpJson
@@ -24,7 +25,6 @@ import kotlinx.coroutines.SupervisorJob
2425
import kotlinx.coroutines.cancel
2526
import kotlinx.coroutines.cancelAndJoin
2627
import kotlinx.coroutines.launch
27-
import kotlinx.serialization.encodeToString
2828
import kotlin.concurrent.atomics.AtomicBoolean
2929
import kotlin.concurrent.atomics.ExperimentalAtomicApi
3030
import kotlin.properties.Delegates
@@ -55,7 +55,18 @@ public class SseClientTransport(
5555
private var job: Job? = null
5656

5757
private val baseUrl by lazy {
58-
session.call.request.url.toString().removeSuffix("/sse")
58+
val requestUrl = session.call.request.url.toString()
59+
val url = Url(requestUrl)
60+
var path = url.encodedPath
61+
if (path.isEmpty()) {
62+
url.protocolWithAuthority
63+
} else if (path.endsWith("/")) {
64+
url.protocolWithAuthority + path.removeSuffix("/")
65+
} else {
66+
// the last item is not a directory, so will not be taken into account
67+
path = path.substring(0, path.lastIndexOf("/"))
68+
url.protocolWithAuthority + path
69+
}
5970
}
6071

6172
override suspend fun start() {
@@ -68,7 +79,7 @@ public class SseClientTransport(
6879

6980
session = urlString?.let {
7081
client.sseSession(
71-
urlString = "$it/sse",
82+
urlString = it,
7283
reconnectionTime = reconnectionTime,
7384
block = requestBuilder,
7485
)
@@ -95,8 +106,7 @@ public class SseClientTransport(
95106
val eventData = event.data ?: ""
96107

97108
// check url correctness
98-
val maybeEndpoint = Url(baseUrl + eventData)
99-
109+
val maybeEndpoint = Url("$baseUrl/${if (eventData.startsWith("/")) eventData.substring(1) else eventData}")
100110
endpoint.complete(maybeEndpoint.toString())
101111
} catch (e: Exception) {
102112
_onError(e)

src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import io.ktor.server.application.install
66
import io.ktor.server.cio.CIO
77
import io.ktor.server.engine.embeddedServer
88
import io.ktor.server.routing.post
9+
import io.ktor.server.routing.route
910
import io.ktor.server.routing.routing
1011
import io.ktor.server.sse.sse
1112
import io.ktor.util.collections.ConcurrentMap
@@ -82,4 +83,43 @@ class SseTransportTest : BaseTransportTest() {
8283
testClientRead(client)
8384
server.stopSuspend()
8485
}
86+
87+
@Test
88+
fun `test sse path not root path`() = runTest {
89+
val port = 3007
90+
val server = embeddedServer(CIO, port = port) {
91+
install(io.ktor.server.sse.SSE)
92+
val transports = ConcurrentMap<String, SseServerTransport>()
93+
routing {
94+
route("/sse") {
95+
sse {
96+
mcpSseTransport("", transports).apply {
97+
onMessage {
98+
send(it)
99+
}
100+
101+
start()
102+
}
103+
}
104+
105+
post {
106+
mcpPostEndpoint(transports)
107+
}
108+
}
109+
}
110+
}.startSuspend(wait = false)
111+
112+
val client = HttpClient {
113+
install(SSE)
114+
}.mcpSseTransport {
115+
url {
116+
host = "localhost"
117+
this.port = port
118+
pathSegments = listOf("sse")
119+
}
120+
}
121+
122+
testClientRead(client)
123+
server.stopSuspend()
124+
}
85125
}

src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/SseIntegrationTest.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package io.modelcontextprotocol.kotlin.sdk.integration
22

33
import io.ktor.client.HttpClient
44
import io.ktor.client.plugins.sse.SSE
5+
import io.ktor.server.application.install
56
import io.ktor.server.cio.CIOApplicationEngine
67
import io.ktor.server.engine.EmbeddedServer
78
import io.ktor.server.engine.embeddedServer
9+
import io.ktor.server.routing.routing
810
import io.modelcontextprotocol.kotlin.sdk.Implementation
911
import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities
1012
import io.modelcontextprotocol.kotlin.sdk.client.Client
@@ -52,7 +54,12 @@ class SseIntegrationTest {
5254
ServerOptions(capabilities = ServerCapabilities()),
5355
)
5456

55-
return embeddedServer(ServerCIO, host = URL, port = PORT) { mcp { server } }.startSuspend(wait = false)
57+
return embeddedServer(ServerCIO, host = URL, port = PORT) {
58+
install(io.ktor.server.sse.SSE)
59+
routing {
60+
mcp { server }
61+
}
62+
}.startSuspend(wait = false)
5663
}
5764

5865
companion object {

0 commit comments

Comments
 (0)