Skip to content

Commit 4fe34c7

Browse files
authored
Merge branch 'main' into kpavlov/185-codegen
2 parents 9ab62c6 + d868136 commit 4fe34c7

File tree

20 files changed

+1191
-602
lines changed

20 files changed

+1191
-602
lines changed

kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,11 @@ public open class Client(private val clientInfo: Implementation, options: Client
157157

158158
notification(InitializedNotification())
159159
} catch (error: Throwable) {
160+
logger.error(error) { "Failed to initialize client: ${error.message}" }
160161
close()
162+
161163
if (error !is CancellationException) {
162-
throw IllegalStateException("Error connecting to transport: ${error.message}")
164+
throw IllegalStateException("Error connecting to transport: ${error.message}", error)
163165
}
164166

165167
throw error

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.modelcontextprotocol.kotlin.sdk.client
22

3+
import io.github.oshai.kotlinlogging.KotlinLogging
34
import io.ktor.client.HttpClient
45
import io.ktor.client.plugins.sse.ClientSSESession
56
import io.ktor.client.plugins.sse.sseSession
@@ -46,6 +47,8 @@ public class SseClientTransport(
4647
private val reconnectionTime: Duration? = null,
4748
private val requestBuilder: HttpRequestBuilder.() -> Unit = {},
4849
) : AbstractTransport() {
50+
private val logger = KotlinLogging.logger {}
51+
4952
private val initialized: AtomicBoolean = AtomicBoolean(false)
5053
private val endpoint = CompletableDeferred<String>()
5154

@@ -111,6 +114,8 @@ public class SseClientTransport(
111114
val text = response.bodyAsText()
112115
error("Error POSTing to endpoint (HTTP ${response.status}): $text")
113116
}
117+
118+
logger.debug { "Client successfully sent message via SSE $endpoint" }
114119
} catch (e: Throwable) {
115120
_onError(e)
116121
throw e
@@ -158,6 +163,7 @@ public class SseClientTransport(
158163
val path = if (eventData.startsWith("/")) eventData.substring(1) else eventData
159164
val endpointUrl = Url("$baseUrl/$path")
160165
endpoint.complete(endpointUrl.toString())
166+
logger.debug { "Client connected to endpoint: $endpointUrl" }
161167
} catch (e: Throwable) {
162168
_onError(e)
163169
endpoint.completeExceptionally(e)

kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketClientTransport.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.modelcontextprotocol.kotlin.sdk.client
22

3+
import io.github.oshai.kotlinlogging.KotlinLogging
34
import io.ktor.client.HttpClient
45
import io.ktor.client.plugins.websocket.webSocketSession
56
import io.ktor.client.request.HttpRequestBuilder
@@ -10,6 +11,8 @@ import io.modelcontextprotocol.kotlin.sdk.shared.MCP_SUBPROTOCOL
1011
import io.modelcontextprotocol.kotlin.sdk.shared.WebSocketMcpTransport
1112
import kotlin.properties.Delegates
1213

14+
private val logger = KotlinLogging.logger {}
15+
1316
/**
1417
* Client transport for WebSocket: this will connect to a server over the WebSocket protocol.
1518
*/
@@ -21,6 +24,8 @@ public class WebSocketClientTransport(
2124
override var session: WebSocketSession by Delegates.notNull()
2225

2326
override suspend fun initializeSession() {
27+
logger.debug { "Websocket session initialization started..." }
28+
2429
session = urlString?.let {
2530
client.webSocketSession(it) {
2631
requestBuilder()
@@ -32,5 +37,7 @@ public class WebSocketClientTransport(
3237

3338
header(HttpHeaders.SecWebSocketProtocol, MCP_SUBPROTOCOL)
3439
}
40+
41+
logger.debug { "Websocket session initialization finished" }
3542
}
3643
}

kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketMcpKtorClientExtensions.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package io.modelcontextprotocol.kotlin.sdk.client
22

3+
import io.github.oshai.kotlinlogging.KotlinLogging
34
import io.ktor.client.HttpClient
45
import io.ktor.client.request.HttpRequestBuilder
56
import io.modelcontextprotocol.kotlin.sdk.Implementation
67
import io.modelcontextprotocol.kotlin.sdk.LIB_VERSION
78
import io.modelcontextprotocol.kotlin.sdk.shared.IMPLEMENTATION_NAME
89

10+
private val logger = KotlinLogging.logger {}
11+
912
/**
1013
* Returns a new WebSocket transport for the Model Context Protocol using the provided HttpClient.
1114
*
@@ -36,6 +39,8 @@ public suspend fun HttpClient.mcpWebSocket(
3639
version = LIB_VERSION,
3740
),
3841
)
42+
logger.debug { "Client started to connect to server" }
3943
client.connect(transport)
44+
logger.debug { "Client finished to connect to server" }
4045
return client
4146
}

kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import kotlin.reflect.typeOf
4141
import kotlin.time.Duration
4242
import kotlin.time.Duration.Companion.seconds
4343

44-
private val LOGGER = KotlinLogging.logger { }
44+
private val logger = KotlinLogging.logger { }
4545

4646
public const val IMPLEMENTATION_NAME: String = "mcp-ktor"
4747

@@ -211,6 +211,7 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio
211211
}
212212
}
213213

214+
logger.info { "Starting transport" }
214215
return transport.start()
215216
}
216217

@@ -228,29 +229,29 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio
228229
}
229230

230231
private suspend fun onNotification(notification: JSONRPCNotification) {
231-
LOGGER.trace { "Received notification: ${notification.method}" }
232+
logger.trace { "Received notification: ${notification.method}" }
232233

233234
val handler = notificationHandlers[notification.method] ?: fallbackNotificationHandler
234235

235236
if (handler == null) {
236-
LOGGER.trace { "No handler found for notification: ${notification.method}" }
237+
logger.trace { "No handler found for notification: ${notification.method}" }
237238
return
238239
}
239240
try {
240241
handler(notification)
241242
} catch (cause: Throwable) {
242-
LOGGER.error(cause) { "Error handling notification: ${notification.method}" }
243+
logger.error(cause) { "Error handling notification: ${notification.method}" }
243244
onError(cause)
244245
}
245246
}
246247

247248
private suspend fun onRequest(request: JSONRPCRequest) {
248-
LOGGER.trace { "Received request: ${request.method} (id: ${request.id})" }
249+
logger.trace { "Received request: ${request.method} (id: ${request.id})" }
249250

250251
val handler = requestHandlers[request.method] ?: fallbackRequestHandler
251252

252253
if (handler === null) {
253-
LOGGER.trace { "No handler found for request: ${request.method}" }
254+
logger.trace { "No handler found for request: ${request.method}" }
254255
try {
255256
transport?.send(
256257
JSONRPCResponse(
@@ -262,15 +263,15 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio
262263
),
263264
)
264265
} catch (cause: Throwable) {
265-
LOGGER.error(cause) { "Error sending method not found response" }
266+
logger.error(cause) { "Error sending method not found response" }
266267
onError(cause)
267268
}
268269
return
269270
}
270271

271272
try {
272273
val result = handler(request, RequestHandlerExtra())
273-
LOGGER.trace { "Request handled successfully: ${request.method} (id: ${request.id})" }
274+
logger.trace { "Request handled successfully: ${request.method} (id: ${request.id})" }
274275

275276
transport?.send(
276277
JSONRPCResponse(
@@ -279,7 +280,7 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio
279280
),
280281
)
281282
} catch (cause: Throwable) {
282-
LOGGER.error(cause) { "Error handling request: ${request.method} (id: ${request.id})" }
283+
logger.error(cause) { "Error handling request: ${request.method} (id: ${request.id})" }
283284

284285
try {
285286
transport?.send(
@@ -292,7 +293,7 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio
292293
),
293294
)
294295
} catch (sendError: Throwable) {
295-
LOGGER.error(sendError) {
296+
logger.error(sendError) {
296297
"Failed to send error response for request: ${request.method} (id: ${request.id})"
297298
}
298299
// Optionally implement fallback behavior here
@@ -301,7 +302,7 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio
301302
}
302303

303304
private fun onProgress(notification: ProgressNotification) {
304-
LOGGER.trace {
305+
logger.trace {
305306
"Received progress notification: token=${notification.params.progressToken}, progress=${notification.params.progress}/${notification.params.total}"
306307
}
307308
val progress = notification.params.progress
@@ -314,7 +315,7 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio
314315
val error = Error(
315316
"Received a progress notification for an unknown token: ${McpJson.encodeToString(notification)}",
316317
)
317-
LOGGER.error { error.message }
318+
logger.error { error.message }
318319
onError(error)
319320
return
320321
}
@@ -389,9 +390,9 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio
389390
* Do not use this method to emit notifications! Use notification() instead.
390391
*/
391392
public suspend fun <T : RequestResult> request(request: Request, options: RequestOptions? = null): T {
392-
LOGGER.trace { "Sending request: ${request.method}" }
393+
logger.trace { "Sending request: ${request.method}" }
393394
val result = CompletableDeferred<T>()
394-
val transport = this@Protocol.transport ?: throw Error("Not connected")
395+
val transport = transport ?: throw Error("Not connected")
395396

396397
if (this@Protocol.options?.enforceStrictCapabilities == true) {
397398
assertCapabilityForMethod(request.method)
@@ -401,7 +402,7 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio
401402
val messageId = message.id
402403

403404
if (options?.onProgress != null) {
404-
LOGGER.trace { "Registering progress handler for request id: $messageId" }
405+
logger.trace { "Registering progress handler for request id: $messageId" }
405406
_progressHandlers.update { current ->
406407
current.put(messageId, options.onProgress)
407408
}
@@ -451,12 +452,12 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio
451452
val timeout = options?.timeout ?: DEFAULT_REQUEST_TIMEOUT
452453
try {
453454
withTimeout(timeout) {
454-
LOGGER.trace { "Sending request message with id: $messageId" }
455+
logger.trace { "Sending request message with id: $messageId" }
455456
this@Protocol.transport?.send(message)
456457
}
457458
return result.await()
458459
} catch (cause: TimeoutCancellationException) {
459-
LOGGER.error { "Request timed out after ${timeout.inWholeMilliseconds}ms: ${request.method}" }
460+
logger.error { "Request timed out after ${timeout.inWholeMilliseconds}ms: ${request.method}" }
460461
cancel(
461462
McpError(
462463
ErrorCode.Defined.RequestTimeout.code,
@@ -473,7 +474,7 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio
473474
* Emits a notification, which is a one-way message that does not expect a response.
474475
*/
475476
public suspend fun notification(notification: Notification) {
476-
LOGGER.trace { "Sending notification: ${notification.method}" }
477+
logger.trace { "Sending notification: ${notification.method}" }
477478
val transport = this.transport ?: error("Not connected")
478479
assertNotificationCapability(notification.method)
479480

kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt

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

3+
import io.github.oshai.kotlinlogging.KotlinLogging
34
import io.ktor.websocket.Frame
45
import io.ktor.websocket.WebSocketSession
56
import io.ktor.websocket.close
@@ -17,6 +18,8 @@ import kotlin.concurrent.atomics.ExperimentalAtomicApi
1718

1819
public const val MCP_SUBPROTOCOL: String = "mcp"
1920

21+
private val logger = KotlinLogging.logger {}
22+
2023
/**
2124
* Abstract class representing a WebSocket transport for the Model Context Protocol (MCP).
2225
* Handles communication over a WebSocket session.
@@ -40,6 +43,8 @@ public abstract class WebSocketMcpTransport : AbstractTransport() {
4043
protected abstract suspend fun initializeSession()
4144

4245
override suspend fun start() {
46+
logger.debug { "Starting websocket transport" }
47+
4348
if (!initialized.compareAndSet(expectedValue = false, newValue = true)) {
4449
error(
4550
"WebSocketClientTransport already started! " +
@@ -53,7 +58,8 @@ public abstract class WebSocketMcpTransport : AbstractTransport() {
5358
while (true) {
5459
val message = try {
5560
session.incoming.receive()
56-
} catch (_: ClosedReceiveChannelException) {
61+
} catch (e: ClosedReceiveChannelException) {
62+
logger.debug { "Closed receive channel, exiting" }
5763
return@launch
5864
}
5965

@@ -84,6 +90,7 @@ public abstract class WebSocketMcpTransport : AbstractTransport() {
8490
}
8591

8692
override suspend fun send(message: JSONRPCMessage) {
93+
logger.debug { "Sending message" }
8794
if (!initialized.load()) {
8895
error("Not connected")
8996
}
@@ -96,6 +103,7 @@ public abstract class WebSocketMcpTransport : AbstractTransport() {
96103
error("Not connected")
97104
}
98105

106+
logger.debug { "Closing websocket session" }
99107
session.close()
100108
session.coroutineContext.job.join()
101109
}

0 commit comments

Comments
 (0)