Adds a comprehensive conformance test suite for the Kotlin MCP SDK, covering core protocol operations, tool calls, elicitation, resources, prompts, and 20 OAuth/auth scenarios - Conformance server and client implementations - OAuth/auth test scenarios: JWT, authorization code flow, client credentials, PKCE, scope handling, cross-app access, client registration - CI workflow - Baseline file for tracking expected failures - Shell script fixes: - #592 - #593 - #596 ## Remaining known failures (tracked issues, will be fixed directly in `main`) - [x] `tools-call-with-logging`, `tools-call-with-progress`, `tools-call-sampling`, `tools-call-elicitation`, `elicitation-sep1034-defaults`- see #599, - [x] `elicitation-sep1330-enums` - #587 #600 - [x] `initialize` - #588 - [x] `tools_call`, `auth/scope-step-up`, `auth/scope-retry-limit` - #589 - [ ] `elicitation-sep1034-client-defaults` - #414 - [x] `sse-retry` - #590 - [ ] `resources-templates-read` - #591 ## Breaking Changes from #596 - `StreamableHttpClientTransport` and `mcpStreamableHttp`/`mcpStreamableHttpTransport`: old constructors accepting `Duration` timeout are now `@Deprecated` — use the new overloads with `ReconnectionOptions` instead - `StreamableHttpClientTransport.close()` no longer calls `terminateSession()` automatically ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [x] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update ## Checklist - [x] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [x] My code follows the repository's style guidelines - [x] New and existing tests pass locally - [x] I have added appropriate error handling - [x] I have added or updated documentation as needed --------- Co-authored-by: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com>
This commit is contained in:
parent
338f94804f
commit
e88b34170c
1 changed files with 3186 additions and 1122 deletions
|
|
@ -45,6 +45,7 @@ internal const val MCP_SESSION_ID_HEADER = "mcp-session-id"
|
|||
private const val MCP_PROTOCOL_VERSION_HEADER = "mcp-protocol-version"
|
||||
private const val MCP_RESUMPTION_TOKEN_HEADER = "Last-Event-ID"
|
||||
private const val MAXIMUM_MESSAGE_SIZE = 4 * 1024 * 1024 // 4 MB
|
||||
private const val MIN_PRIMING_EVENT_PROTOCOL_VERSION = "2025-11-25"
|
||||
|
||||
/**
|
||||
* A holder for an active request call.
|
||||
|
|
@ -388,7 +389,7 @@ public class StreamableHttpServerTransport(private val configuration: Configurat
|
|||
if (!configuration.enableJsonResponse) {
|
||||
call.appendSseHeaders()
|
||||
flushSse(session) // flush headers immediately
|
||||
maybeSendPrimingEvent(streamId, session)
|
||||
maybeSendPrimingEvent(streamId, session, call.request.header(MCP_PROTOCOL_VERSION_HEADER))
|
||||
}
|
||||
|
||||
streamMutex.withLock {
|
||||
|
|
@ -451,7 +452,7 @@ public class StreamableHttpServerTransport(private val configuration: Configurat
|
|||
call.appendSseHeaders()
|
||||
flushSse(sseSession) // flush headers immediately
|
||||
streamsMapping[STANDALONE_SSE_STREAM_ID] = SessionContext(sseSession, call)
|
||||
maybeSendPrimingEvent(STANDALONE_SSE_STREAM_ID, sseSession)
|
||||
maybeSendPrimingEvent(STANDALONE_SSE_STREAM_ID, sseSession, call.request.header(MCP_PROTOCOL_VERSION_HEADER))
|
||||
sseSession.coroutineContext.job.invokeOnCompletion {
|
||||
streamsMapping.remove(STANDALONE_SSE_STREAM_ID)
|
||||
}
|
||||
|
|
@ -702,12 +703,20 @@ public class StreamableHttpServerTransport(private val configuration: Configurat
|
|||
}
|
||||
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
private suspend fun maybeSendPrimingEvent(streamId: String, session: ServerSSESession?) {
|
||||
val store = configuration.eventStore ?: return
|
||||
val sseSession = session ?: return
|
||||
private suspend fun maybeSendPrimingEvent(
|
||||
streamId: String,
|
||||
session: ServerSSESession?,
|
||||
clientProtocolVersion: String? = null,
|
||||
) {
|
||||
val store = configuration.eventStore
|
||||
if (store == null || session == null) return
|
||||
// Priming events have empty data which older clients cannot handle.
|
||||
// Only send priming events to clients with protocol version >= 2025-11-25
|
||||
// which includes the fix for handling empty SSE data.
|
||||
if (clientProtocolVersion != null && clientProtocolVersion < MIN_PRIMING_EVENT_PROTOCOL_VERSION) return
|
||||
try {
|
||||
val primingEventId = store.storeEvent(streamId, JSONRPCEmptyMessage)
|
||||
sseSession.send(
|
||||
session.send(
|
||||
id = primingEventId,
|
||||
retry = configuration.retryInterval?.inWholeMilliseconds,
|
||||
data = "",
|
||||
|
|
|
|||
Loading…
Reference in a new issue