Skip to content
Merged
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
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ subprojects {
apply(plugin = "org.jlleitschuh.gradle.ktlint")
apply(plugin = "org.jetbrains.kotlinx.kover")

if (name != "conformance-test") {
if (name != "conformance-test" && name != "docs") {
apply(plugin = "dev.detekt")

detekt {
Expand Down
3 changes: 1 addition & 2 deletions conformance-test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ Tests the conformance server against all server scenarios:

## Known SDK Limitations

9 scenarios are expected to fail due to current SDK limitations (tracked in [
8 scenarios are expected to fail due to current SDK limitations (tracked in [
`conformance-baseline.yml`](conformance-baseline.yml).

| Scenario | Suite | Root Cause |
Expand All @@ -123,6 +123,5 @@ Tests the conformance server against all server scenarios:
| `elicitation-sep1330-enums` | server | *(same as above)* |
| `resources-templates-read` | server | SDK does not implement `addResourceTemplate()` with URI pattern matching; resources are looked up by exact URI |
| `elicitation-sep1034-client-defaults` | client | SDK does not fill in `default` values from the elicitation request schema before sending the response |
| `sse-retry` | client | Transport does not respect the SSE `retry` field timing or send `Last-Event-ID` on reconnection |

These failures reveal SDK gaps and are intentionally not fixed in this module.
1 change: 0 additions & 1 deletion conformance-test/conformance-baseline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,3 @@ server:

client:
- elicitation-sep1034-client-defaults
- sse-retry
18 changes: 18 additions & 0 deletions kotlin-sdk-client/api/kotlin-sdk-client.api
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,18 @@ public final class io/modelcontextprotocol/kotlin/sdk/client/KtorClientKt {
public static synthetic fun mcpSseTransport-5_5nbZA$default (Lio/ktor/client/HttpClient;Ljava/lang/String;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/client/SseClientTransport;
}

public final class io/modelcontextprotocol/kotlin/sdk/client/ReconnectionOptions {
public synthetic fun <init> (JJDIILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (JJDILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun equals (Ljava/lang/Object;)Z
public final fun getInitialReconnectionDelay-UwyO8pc ()J
public final fun getMaxReconnectionDelay-UwyO8pc ()J
public final fun getMaxRetries ()I
public final fun getReconnectionDelayMultiplier ()D
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class io/modelcontextprotocol/kotlin/sdk/client/SseClientTransport : io/modelcontextprotocol/kotlin/sdk/shared/AbstractClientTransport {
public synthetic fun <init> (Lio/ktor/client/HttpClient;Ljava/lang/String;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Lio/ktor/client/HttpClient;Ljava/lang/String;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
Expand All @@ -88,6 +100,8 @@ public final class io/modelcontextprotocol/kotlin/sdk/client/StdioClientTranspor
}

public final class io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport : io/modelcontextprotocol/kotlin/sdk/shared/AbstractClientTransport {
public fun <init> (Lio/ktor/client/HttpClient;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/client/ReconnectionOptions;Lkotlin/jvm/functions/Function1;)V
public synthetic fun <init> (Lio/ktor/client/HttpClient;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/client/ReconnectionOptions;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Lio/ktor/client/HttpClient;Ljava/lang/String;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Lio/ktor/client/HttpClient;Ljava/lang/String;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getProtocolVersion ()Ljava/lang/String;
Expand All @@ -106,8 +120,12 @@ public final class io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpError
}

public final class io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpMcpKtorClientExtensionsKt {
public static final fun mcpStreamableHttp (Lio/ktor/client/HttpClient;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/client/ReconnectionOptions;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun mcpStreamableHttp$default (Lio/ktor/client/HttpClient;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/client/ReconnectionOptions;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public static final fun mcpStreamableHttp-BZiP2OM (Lio/ktor/client/HttpClient;Ljava/lang/String;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun mcpStreamableHttp-BZiP2OM$default (Lio/ktor/client/HttpClient;Ljava/lang/String;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public static final fun mcpStreamableHttpTransport (Lio/ktor/client/HttpClient;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/client/ReconnectionOptions;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport;
public static synthetic fun mcpStreamableHttpTransport$default (Lio/ktor/client/HttpClient;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/client/ReconnectionOptions;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport;
public static final fun mcpStreamableHttpTransport-5_5nbZA (Lio/ktor/client/HttpClient;Ljava/lang/String;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport;
public static synthetic fun mcpStreamableHttpTransport-5_5nbZA$default (Lio/ktor/client/HttpClient;Ljava/lang/String;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package io.modelcontextprotocol.kotlin.sdk.client

import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds

/**
* Options for controlling SSE reconnection behavior.
*
* @property initialReconnectionDelay The initial delay before the first reconnection attempt.
* @property maxReconnectionDelay The maximum delay between reconnection attempts.
* @property reconnectionDelayMultiplier The factor by which the delay grows on each attempt.
* @property maxRetries The maximum number of reconnection attempts per disconnect.
*/
public class ReconnectionOptions(
public val initialReconnectionDelay: Duration = 1.seconds,
public val maxReconnectionDelay: Duration = 30.seconds,
public val reconnectionDelayMultiplier: Double = 1.5,
public val maxRetries: Int = 2,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is also advantageous to have a jitter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea, will consider in follow-up

) {
override fun equals(other: Any?): Boolean {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why don't use data class for this? to avoid manual hashCode/equals, and also it gives you the pattern options.copy([only specific fields])

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Data classes are indeed more convenient

the problem is that any changes to them can introduce binary incompatibility in the future
https://kotlinlang.org/docs/api-guidelines-backward-compatibility.html#avoid-using-data-classes-in-your-api

if (this === other) return true
if (other == null || this::class != other::class) return false

other as ReconnectionOptions

if (reconnectionDelayMultiplier != other.reconnectionDelayMultiplier) return false
if (maxRetries != other.maxRetries) return false
if (initialReconnectionDelay != other.initialReconnectionDelay) return false
if (maxReconnectionDelay != other.maxReconnectionDelay) return false

return true
}

override fun hashCode(): Int {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the hashCode() ordering doesn't match the equals() ordering (reconnectionDelayMultiplier first vs. last), which is a style inconsistency even if not a correctness bug.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

var result = reconnectionDelayMultiplier.hashCode()
result = 31 * result + maxRetries
result = 31 * result + initialReconnectionDelay.hashCode()
result = 31 * result + maxReconnectionDelay.hashCode()
return result
}

override fun toString(): String =
"ReconnectionOptions(initialReconnectionDelay=$initialReconnectionDelay, maxReconnectionDelay=$maxReconnectionDelay, reconnectionDelayMultiplier=$reconnectionDelayMultiplier, maxRetries=$maxRetries)"
}
Loading
Loading