From 98995ec95375086d2c7b75ec0daef140ecec7bbd Mon Sep 17 00:00:00 2001 From: tpp-builder Date: Wed, 12 Feb 2025 10:03:20 +0800 Subject: [PATCH 1/2] fix SSEClientTransport endpoint process with none directory sse path --- .../kotlin/sdk/client/SSEClientTransport.kt | 16 ++++++-- src/jvmTest/kotlin/client/SseTransportTest.kt | 37 +++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt index 9e21347..b52fb7a 100644 --- a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt +++ b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt @@ -39,7 +39,18 @@ public class SseClientTransport( private var job: Job? = null private val baseUrl by lazy { - session.call.request.url.toString().removeSuffix("/") + val requestUrl = session.call.request.url.toString() + val url = Url(requestUrl) + var path = url.encodedPath + if (path.isEmpty()) { + url.protocolWithAuthority + } else if (path.endsWith("/")) { + url.protocolWithAuthority + path.removeSuffix("/") + } else { + // the last item is not a directory, so will not be taken into account + path = path.substring(0, path.lastIndexOf("/")) + url.protocolWithAuthority + path + } } override suspend fun start() { @@ -79,8 +90,7 @@ public class SseClientTransport( val eventData = event.data ?: "" // check url correctness - val maybeEndpoint = Url("$baseUrl/$eventData") - + val maybeEndpoint = Url("$baseUrl/${if (eventData.startsWith("/")) eventData.substring(1) else eventData}") endpoint.complete(maybeEndpoint.toString()) } catch (e: Exception) { _onError(e) diff --git a/src/jvmTest/kotlin/client/SseTransportTest.kt b/src/jvmTest/kotlin/client/SseTransportTest.kt index b056893..8a2858a 100644 --- a/src/jvmTest/kotlin/client/SseTransportTest.kt +++ b/src/jvmTest/kotlin/client/SseTransportTest.kt @@ -82,4 +82,41 @@ class SseTransportTest : BaseTransportTest() { testClientRead(client) server.stop() } + + @Test + fun `test sse path not root path`() = runTest { + val server = embeddedServer(CIO, port = PORT) { + install(io.ktor.server.sse.SSE) + val transports = ConcurrentMap() + routing { + sse("/sse") { + mcpSseTransport("/messages", transports).apply { + onMessage { + send(it) + } + + start() + } + } + + post("/messages") { + + mcpPostEndpoint(transports) + } + } + }.start(wait = false) + + val client = HttpClient { + install(SSE) + }.mcpSseTransport { + url { + host = "localhost" + port = PORT + pathSegments = listOf("sse") + } + } + + testClientRead(client) + server.stop() + } } From 53421f5dbc37714b6b87e309df6d3a71b128289c Mon Sep 17 00:00:00 2001 From: tpp-builder Date: Wed, 12 Feb 2025 10:03:20 +0800 Subject: [PATCH 2/2] fix SSEClientTransport endpoint process with none directory sse path --- src/jvmTest/kotlin/client/SseTransportTest.kt | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/jvmTest/kotlin/client/SseTransportTest.kt b/src/jvmTest/kotlin/client/SseTransportTest.kt index 8a2858a..5d7094c 100644 --- a/src/jvmTest/kotlin/client/SseTransportTest.kt +++ b/src/jvmTest/kotlin/client/SseTransportTest.kt @@ -119,4 +119,31 @@ class SseTransportTest : BaseTransportTest() { testClientRead(client) server.stop() } + + @Test + fun `test sse path not root path`() = runTest { + val server = embeddedServer(CIO, port = PORT) { + install(io.ktor.server.sse.SSE) + routing { + mcpSseTransport(path = "/sse", incomingPath = "/messages") { + onMessage = { + send(it) + } + } + } + }.start(wait = false) + + val client = HttpClient { + install(SSE) + }.mcpSseTransport { + url { + host = "localhost" + port = PORT + pathSegments = listOf("sse") + } + } + + testClientRead(client) + server.stop() + } }