feat!: add mcp conformance test infra (#585); add SSE reconnection with retry support (#596) (#585)

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:
Pavel Gorgulov 2026-03-12 14:44:59 +01:00 • committed by GitHub
commit e88b34170c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 3186 additions and 1122 deletions

View file

@ -8,12 +8,13 @@ import kotlinx.serialization.json.JsonObject
// Protocol Version Constants
// ============================================================================
public const val LATEST_PROTOCOL_VERSION: String = "2025-06-18"
public const val LATEST_PROTOCOL_VERSION: String = "2025-11-25"
public const val DEFAULT_NEGOTIATED_PROTOCOL_VERSION: String = "2025-03-26"
public const val DEFAULT_NEGOTIATED_PROTOCOL_VERSION: String = "2025-06-18"
public val SUPPORTED_PROTOCOL_VERSIONS: List<String> = listOf(
LATEST_PROTOCOL_VERSION,
"2025-06-18",
"2025-03-26",
"2024-11-05",
)

View file

@ -371,21 +371,23 @@ internal object ServerResultPolymorphicSerializer :
* Polymorphic serializer for [JSONRPCMessage] types.
* Determines the message type based on the presence of specific fields:
* - "error" -> JSONRPCError
* - "result" -> JSONRPCResponse
* - "result" + "id" -> JSONRPCResponse
* - "result" -> JSONRPCEmptyMessage
* - "method" + "id" -> JSONRPCRequest
* - "method" -> JSONRPCNotification
*/
internal object JSONRPCMessagePolymorphicSerializer :
JsonContentPolymorphicSerializer<JSONRPCMessage>(JSONRPCMessage::class) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<JSONRPCMessage> {
val jsonObject = element.jsonObject
val jsonObj = element.jsonObject
return when {
"error" in jsonObject -> JSONRPCError.serializer()
"result" in jsonObject -> JSONRPCResponse.serializer()
"method" in jsonObject && "id" in jsonObject -> JSONRPCRequest.serializer()
"method" in jsonObject -> JSONRPCNotification.serializer()
jsonObject.isEmpty() || jsonObject.keys == setOf("jsonrpc") -> JSONRPCEmptyMessage.serializer()
else -> throw SerializationException("Invalid JSONRPCMessage type: ${jsonObject.keys}")
"error" in jsonObj -> JSONRPCError.serializer()
"result" in jsonObj && "id" in jsonObj -> JSONRPCResponse.serializer()
"result" in jsonObj && jsonObj["result"]?.jsonObject?.isEmpty() == true -> JSONRPCEmptyMessage.serializer()
"method" in jsonObj && "id" in jsonObj -> JSONRPCRequest.serializer()
"method" in jsonObj -> JSONRPCNotification.serializer()
jsonObj.isEmpty() || jsonObj.keys == setOf("jsonrpc") -> JSONRPCEmptyMessage.serializer()
else -> throw SerializationException("Invalid JSONRPCMessage type: ${jsonObj.keys}")
}
}
}

View file

@ -14,13 +14,14 @@ class CommonTypeTest {
@Test
fun `should have correct latest protocol version`() {
LATEST_PROTOCOL_VERSION shouldBe "2025-06-18"
LATEST_PROTOCOL_VERSION shouldBe "2025-11-25"
}
@Test
fun `should have correct supported protocol versions`() {
SUPPORTED_PROTOCOL_VERSIONS shouldContainExactlyInAnyOrder listOf(
LATEST_PROTOCOL_VERSION,
"2025-06-18",
"2025-03-26",
"2024-11-05",
)