Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
3c36239
add conformance tests for server and client
devcrocod Mar 2, 2026
42d14f0
rewrite conformance tests, rework server-client implementation
devcrocod Mar 5, 2026
21846f5
add OAuth authorization and client credentials scenarios to conforman…
devcrocod Mar 5, 2026
c241bda
add OAuth authorization and client credentials scenarios to conforman…
devcrocod Mar 6, 2026
4c7900a
replace `System.err` with KotlinLogging in ConformanceClient, add bas…
devcrocod Mar 6, 2026
aebff7a
remove toolLists from basic client
devcrocod Mar 6, 2026
6e30638
update protocol version constants
devcrocod Mar 6, 2026
09ff656
remove unused `allowedOrigins` configuration from ConformanceServer s…
devcrocod Mar 6, 2026
0e80c11
add support for baseline file of expected failures in conformance tests
devcrocod Mar 6, 2026
b37f568
update elicitation schema to include `username` and `email`, adjust r…
devcrocod Mar 6, 2026
70ace75
delete `results` directory during `clean` task in conformance-test build
devcrocod Mar 6, 2026
928c68e
conditionally apply Detekt plugin to exclude conformance-test
devcrocod Mar 6, 2026
30ab88e
add resource parameter support and pre-registration in OAuth authoriz…
devcrocod Mar 6, 2026
914b531
remove OAuth authorization handler and simplify resource metadata dis…
devcrocod Mar 6, 2026
1ba27f8
add CSRF state parameter validation and enhance error handling in OAu…
devcrocod Mar 7, 2026
7c3b6ce
remove redundant non-null assertions in authCodeFlow implementation
devcrocod Mar 7, 2026
d0c0d96
add new expected failures to conformance test baseline
devcrocod Mar 7, 2026
5369a57
specify explicit types for cached discovery and credentials in authCo…
devcrocod Mar 7, 2026
451940f
improve error handling in `run-conformance.sh` by adding fallback for…
devcrocod Mar 7, 2026
94dfe80
update README to reflect 18 OAuth scenarios in client-auth suite and …
devcrocod Mar 7, 2026
c465ffe
add Cross-App Access scenario and validate PRM resource in authCodeFl…
devcrocod Mar 7, 2026
c7695d3
fix: handle sse 404 (#593)
devcrocod Mar 9, 2026
d9e82b2
fix: serialization empty response without id (#592)
devcrocod Mar 9, 2026
5ef15e9
add HTTP timeout configuration to ConformanceClient for improved requ…
devcrocod Mar 9, 2026
6e9c082
refactor ConformanceClient to centralize HTTP client creation with sh…
devcrocod Mar 9, 2026
7c1a1cf
parameterize JDK and Node.js versions in workflow, add new auth scena…
devcrocod Mar 9, 2026
4892b7c
add support for additional auth scenarios in conformance tests and re…
devcrocod Mar 9, 2026
0a3b4bd
feat: add SSE reconnection with retry support (#596)
devcrocod Mar 11, 2026
6f1ae61
feat(conformance): add `list` command to conformance test script
kpavlov Mar 11, 2026
3c097bd
feat(conformance): add logging to `test_tool_with_logging` and update…
kpavlov Mar 12, 2026
c1f1818
fix(server): keep SSE connection open until explicitly cancelled
kpavlov Mar 12, 2026
7ec51bd
chore(conformance): remove 5 passing server tests from conformance ba…
kpavlov Mar 12, 2026
b883af6
remove network traffic capture examples from README
kpavlov Mar 12, 2026
145c173
Merge branch 'main' into kpavlov/fix-sse-notifications
kpavlov Mar 12, 2026
92eecc8
chore(conformance): remove outdated server tests from README failure …
kpavlov Mar 12, 2026
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
15 changes: 5 additions & 10 deletions conformance-test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,16 +114,11 @@ Tests the conformance server against all server scenarios:
8 scenarios are expected to fail due to current SDK limitations (tracked in [
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please reduce the number of expected failed scenarios

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Will do it in the next PR

`conformance-baseline.yml`](conformance-baseline.yml).

| Scenario | Suite | Root Cause |
|---------------------------------------|--------|--------------------------------------------------------------------------------------------------------------------------------------------------------|
| `tools-call-with-logging` | server | Notifications from tool handlers have no `relatedRequestId`; transport routes them to the standalone SSE stream instead of the request-specific stream |
| `tools-call-with-progress` | server | *(same as above)* |
| `tools-call-sampling` | server | *(same as above)* |
| `tools-call-elicitation` | server | *(same as above)* |
| `elicitation-sep1034-defaults` | server | *(same as above)* |
| `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 |
| Scenario | Suite | Root Cause |
|---------------------------------------|--------|----------------------------------------------------------------------------------------------------------------|
| `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 |

These failures reveal SDK gaps and are intentionally not fixed in this module.

Expand Down
5 changes: 0 additions & 5 deletions conformance-test/conformance-baseline.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
# Conformance test baseline - expected failures
# Add entries here as tests are identified as known SDK limitations
server:
- tools-call-with-logging
- tools-call-with-progress
- tools-call-sampling
- tools-call-elicitation
- elicitation-sep1034-defaults
- elicitation-sep1330-enums
- resources-templates-read

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.modelcontextprotocol.kotlin.sdk.conformance

import io.github.oshai.kotlinlogging.KotlinLogging
import io.modelcontextprotocol.kotlin.sdk.server.Server
import io.modelcontextprotocol.kotlin.sdk.types.AudioContent
import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult
Expand Down Expand Up @@ -33,6 +34,8 @@ internal const val PNG_BASE64 =
// Minimal WAV (base64)
internal const val WAV_BASE64 = "UklGRiYAAABXQVZFZm10IBAAAAABAAEAQB8AAAB9AAACABAAZGF0YQIAAAA="

private val logger = KotlinLogging.logger {}

@Suppress("LongMethod")
fun Server.registerConformanceTools() {
// 1. Simple text
Expand Down Expand Up @@ -429,6 +432,7 @@ fun Server.registerConformanceTools() {
name = "test_tool_with_logging",
description = "test_tool_with_logging",
) {
logger.debug { "[test_tool_with_logging] Sending message 1" }
sendLoggingMessage(
LoggingMessageNotification(
LoggingMessageNotificationParams(
Expand All @@ -439,6 +443,7 @@ fun Server.registerConformanceTools() {
),
)
delay(50.milliseconds)
logger.debug { "[test_tool_with_logging] Sending message #2" }
sendLoggingMessage(
LoggingMessageNotification(
LoggingMessageNotificationParams(
Expand All @@ -449,6 +454,7 @@ fun Server.registerConformanceTools() {
),
)
delay(50.milliseconds)
logger.debug { "[test_tool_with_logging] Sending message 3" }
sendLoggingMessage(
LoggingMessageNotification(
LoggingMessageNotificationParams(
Expand All @@ -458,6 +464,7 @@ fun Server.registerConformanceTools() {
),
),
)

CallToolResult(listOf(TextContent("Simple text content")))
}

Expand Down
9 changes: 9 additions & 0 deletions conformance-test/src/main/resources/simplelogger.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Level of logging for the ROOT logger: ERROR, WARN, INFO, DEBUG, TRACE (default is INFO)
org.slf4j.simpleLogger.defaultLogLevel=INFO
org.slf4j.simpleLogger.showThreadName=true
org.slf4j.simpleLogger.showDateTime=false

# Log level for specific packages or classes
org.slf4j.simpleLogger.log.io.ktor.server=DEBUG
org.slf4j.simpleLogger.log.io.modelcontextprotocol=DEBUG
org.slf4j.simpleLogger.log.io.modelcontextprotocol.kotlin.sdk.conformance=INFO
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.RPCError
import io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.REQUEST_TIMEOUT
import io.modelcontextprotocol.kotlin.sdk.types.RequestId
import io.modelcontextprotocol.kotlin.sdk.types.SUPPORTED_PROTOCOL_VERSIONS
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.job
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
Expand Down Expand Up @@ -242,7 +243,15 @@ public class StreamableHttpServerTransport(private val configuration: Configurat
}

val isTerminated = message is JSONRPCResponse || message is JSONRPCError
if (!isTerminated) return
if (!isTerminated) {
if (configuration.enableJsonResponse) {
// In JSON response mode there is no per-request SSE stream, so route notifications
// that are logically associated with a request to the standalone GET SSE stream.
val standaloneStream = streamsMapping[STANDALONE_SSE_STREAM_ID]
standaloneStream?.let { emitOnStream(STANDALONE_SSE_STREAM_ID, it.session, message) }
}
return
}

requestToResponseMapping[responseRequestId!!] = message
val relatedIds = requestToStreamMapping.filterValues { it == streamId }.keys
Expand Down Expand Up @@ -411,14 +420,9 @@ public class StreamableHttpServerTransport(private val configuration: Configurat

@Suppress("ReturnCount")
public suspend fun handleGetRequest(session: ServerSSESession?, call: ApplicationCall) {
if (configuration.enableJsonResponse) {
call.reject(
HttpStatusCode.MethodNotAllowed,
RPCError.ErrorCode.CONNECTION_CLOSED,
"Method not allowed.",
)
return
}
// NOTE: enableJsonResponse only controls how POST responses are delivered (JSON vs. SSE).
// The standalone GET SSE stream is always supported — it is the only channel available
// for server-to-client notifications when enableJsonResponse = true.
val sseSession = session ?: error("Server session can't be null for streaming GET requests")

val acceptHeader = call.request.header(HttpHeaders.Accept)
Expand Down Expand Up @@ -456,6 +460,9 @@ public class StreamableHttpServerTransport(private val configuration: Configurat
sseSession.coroutineContext.job.invokeOnCompletion {
streamsMapping.remove(STANDALONE_SSE_STREAM_ID)
}
// Keep the SSE connection open until the client disconnects or the transport is closed.
// Without this, the Ktor sse{} handler returns immediately, closing the stream.
awaitCancellation()
}

public suspend fun handleDeleteRequest(call: ApplicationCall) {
Expand Down
Loading