Fix serializer for CreateElicitationResult (#240)
* Fix serializer for CreateElicitationResult * Add tests
This commit is contained in:
parent
9bb851aba2
commit
b37a0f62d0
2 changed files with 325 additions and 0 deletions
|
|
@ -225,6 +225,7 @@ private fun selectClientResultDeserializer(element: JsonElement): Deserializatio
|
||||||
return when {
|
return when {
|
||||||
jsonObject.contains("model") -> CreateMessageResult.serializer()
|
jsonObject.contains("model") -> CreateMessageResult.serializer()
|
||||||
jsonObject.contains("roots") -> ListRootsResult.serializer()
|
jsonObject.contains("roots") -> ListRootsResult.serializer()
|
||||||
|
jsonObject.contains("action") -> CreateElicitationResult.serializer()
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,324 @@
|
||||||
|
package io.modelcontextprotocol.kotlin.sdk
|
||||||
|
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.shared.McpJson
|
||||||
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
|
import kotlinx.serialization.json.put
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertIs
|
||||||
|
|
||||||
|
class RequestSerializerTest {
|
||||||
|
|
||||||
|
// Client Result Tests
|
||||||
|
@Test
|
||||||
|
fun `should deserialize CreateMessageResult polymorphically`() {
|
||||||
|
val json = """{
|
||||||
|
"model": "test-model",
|
||||||
|
"role": "assistant",
|
||||||
|
"content": {
|
||||||
|
"type": "text",
|
||||||
|
"text": "Hello"
|
||||||
|
},
|
||||||
|
"stopReason": "endTurn"
|
||||||
|
}"""
|
||||||
|
|
||||||
|
val decoded = McpJson.decodeFromString<RequestResult>(json)
|
||||||
|
|
||||||
|
assertIs<CreateMessageResult>(decoded)
|
||||||
|
assertEquals("test-model", decoded.model)
|
||||||
|
assertEquals(Role.assistant, decoded.role)
|
||||||
|
assertEquals(StopReason.EndTurn, decoded.stopReason)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should deserialize ListRootsResult polymorphically`() {
|
||||||
|
val json = """{
|
||||||
|
"roots": [
|
||||||
|
{
|
||||||
|
"uri": "file:///test",
|
||||||
|
"name": "Test Root"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}"""
|
||||||
|
|
||||||
|
val decoded = McpJson.decodeFromString<RequestResult>(json)
|
||||||
|
|
||||||
|
assertIs<ListRootsResult>(decoded)
|
||||||
|
assertEquals(1, decoded.roots.size)
|
||||||
|
assertEquals("file:///test", decoded.roots[0].uri)
|
||||||
|
assertEquals("Test Root", decoded.roots[0].name)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should deserialize CreateElicitationResult polymorphically`() {
|
||||||
|
val json = """{
|
||||||
|
"action": "accept",
|
||||||
|
"content": {
|
||||||
|
"timezone": "Europe/Amsterdam"
|
||||||
|
}
|
||||||
|
}"""
|
||||||
|
|
||||||
|
val decoded = McpJson.decodeFromString<RequestResult>(json)
|
||||||
|
|
||||||
|
assertIs<CreateElicitationResult>(decoded)
|
||||||
|
assertEquals(CreateElicitationResult.Action.accept, decoded.action)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server Result Tests
|
||||||
|
@Test
|
||||||
|
fun `should deserialize ListToolsResult polymorphically`() {
|
||||||
|
val json = """{
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"name": "test-tool",
|
||||||
|
"description": "A test tool",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}"""
|
||||||
|
|
||||||
|
val decoded = McpJson.decodeFromString<RequestResult>(json)
|
||||||
|
|
||||||
|
assertIs<ListToolsResult>(decoded)
|
||||||
|
assertEquals(1, decoded.tools.size)
|
||||||
|
assertEquals("test-tool", decoded.tools[0].name)
|
||||||
|
assertEquals("A test tool", decoded.tools[0].description)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should deserialize ListResourcesResult polymorphically`() {
|
||||||
|
val json = """{
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"uri": "file:///test.txt",
|
||||||
|
"name": "test.txt",
|
||||||
|
"mimeType": "text/plain"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}"""
|
||||||
|
|
||||||
|
val decoded = McpJson.decodeFromString<RequestResult>(json)
|
||||||
|
|
||||||
|
assertIs<ListResourcesResult>(decoded)
|
||||||
|
assertEquals(1, decoded.resources.size)
|
||||||
|
assertEquals("file:///test.txt", decoded.resources[0].uri)
|
||||||
|
assertEquals("test.txt", decoded.resources[0].name)
|
||||||
|
assertEquals("text/plain", decoded.resources[0].mimeType)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should deserialize ListResourceTemplatesResult polymorphically`() {
|
||||||
|
val json = """{
|
||||||
|
"resourceTemplates": [
|
||||||
|
{
|
||||||
|
"uriTemplate": "file:///templates/{name}",
|
||||||
|
"name": "template",
|
||||||
|
"mimeType": "text/plain"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}"""
|
||||||
|
|
||||||
|
val decoded = McpJson.decodeFromString<RequestResult>(json)
|
||||||
|
|
||||||
|
assertIs<ListResourceTemplatesResult>(decoded)
|
||||||
|
assertEquals(1, decoded.resourceTemplates.size)
|
||||||
|
assertEquals("file:///templates/{name}", decoded.resourceTemplates[0].uriTemplate)
|
||||||
|
assertEquals("template", decoded.resourceTemplates[0].name)
|
||||||
|
assertEquals("text/plain", decoded.resourceTemplates[0].mimeType)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should deserialize ListPromptsResult polymorphically`() {
|
||||||
|
val json = """{
|
||||||
|
"prompts": [
|
||||||
|
{
|
||||||
|
"name": "test-prompt",
|
||||||
|
"description": "A test prompt"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}"""
|
||||||
|
|
||||||
|
val decoded = McpJson.decodeFromString<RequestResult>(json)
|
||||||
|
|
||||||
|
assertIs<ListPromptsResult>(decoded)
|
||||||
|
assertEquals(1, decoded.prompts.size)
|
||||||
|
assertEquals("test-prompt", decoded.prompts[0].name)
|
||||||
|
assertEquals("A test prompt", decoded.prompts[0].description)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should deserialize InitializeResult polymorphically`() {
|
||||||
|
val json = """{
|
||||||
|
"capabilities": {
|
||||||
|
"logging": {},
|
||||||
|
"prompts": {
|
||||||
|
"listChanged": true
|
||||||
|
},
|
||||||
|
"resources": {
|
||||||
|
"subscribe": true,
|
||||||
|
"listChanged": true
|
||||||
|
},
|
||||||
|
"tools": {
|
||||||
|
"listChanged": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"protocolVersion": "2024-11-05",
|
||||||
|
"serverInfo": {
|
||||||
|
"name": "Test Server",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
|
}"""
|
||||||
|
|
||||||
|
val decoded = McpJson.decodeFromString<RequestResult>(json)
|
||||||
|
|
||||||
|
assertIs<InitializeResult>(decoded)
|
||||||
|
assertEquals("2024-11-05", decoded.protocolVersion)
|
||||||
|
assertEquals("Test Server", decoded.serverInfo.name)
|
||||||
|
assertEquals("1.0.0", decoded.serverInfo.version)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should deserialize GetPromptResult polymorphically`() {
|
||||||
|
val json = """{
|
||||||
|
"description": "A test prompt",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": {
|
||||||
|
"type": "text",
|
||||||
|
"text": "Hello"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}"""
|
||||||
|
|
||||||
|
val decoded = McpJson.decodeFromString<RequestResult>(json)
|
||||||
|
|
||||||
|
assertIs<GetPromptResult>(decoded)
|
||||||
|
assertEquals("A test prompt", decoded.description)
|
||||||
|
assertEquals(1, decoded.messages.size)
|
||||||
|
assertEquals(Role.user, decoded.messages[0].role)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should deserialize CompleteResult polymorphically`() {
|
||||||
|
val json = """{
|
||||||
|
"completion": {
|
||||||
|
"values": ["option1", "option2"],
|
||||||
|
"total": 2,
|
||||||
|
"hasMore": false
|
||||||
|
}
|
||||||
|
}"""
|
||||||
|
|
||||||
|
val decoded = McpJson.decodeFromString<RequestResult>(json)
|
||||||
|
|
||||||
|
assertIs<CompleteResult>(decoded)
|
||||||
|
assertEquals(2, decoded.completion.values.size)
|
||||||
|
assertEquals("option1", decoded.completion.values[0])
|
||||||
|
assertEquals("option2", decoded.completion.values[1])
|
||||||
|
assertEquals(2, decoded.completion.total)
|
||||||
|
assertEquals(false, decoded.completion.hasMore)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should deserialize ReadResourceResult polymorphically`() {
|
||||||
|
val json = """{
|
||||||
|
"contents": [
|
||||||
|
{
|
||||||
|
"uri": "file:///test.txt",
|
||||||
|
"mimeType": "text/plain",
|
||||||
|
"text": "Hello World"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}"""
|
||||||
|
|
||||||
|
val decoded = McpJson.decodeFromString<RequestResult>(json)
|
||||||
|
|
||||||
|
assertIs<ReadResourceResult>(decoded)
|
||||||
|
assertEquals(1, decoded.contents.size)
|
||||||
|
assertIs<TextResourceContents>(decoded.contents[0])
|
||||||
|
val textContent = decoded.contents[0] as TextResourceContents
|
||||||
|
assertEquals("file:///test.txt", textContent.uri)
|
||||||
|
assertEquals("text/plain", textContent.mimeType)
|
||||||
|
assertEquals("Hello World", textContent.text)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should deserialize CallToolResult polymorphically`() {
|
||||||
|
val json = """{
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Tool result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isError": false
|
||||||
|
}"""
|
||||||
|
|
||||||
|
val decoded = McpJson.decodeFromString<RequestResult>(json)
|
||||||
|
|
||||||
|
assertIs<CallToolResult>(decoded)
|
||||||
|
assertEquals(1, decoded.content.size)
|
||||||
|
assertIs<TextContent>(decoded.content[0])
|
||||||
|
assertEquals("Tool result", (decoded.content[0] as TextContent).text)
|
||||||
|
assertEquals(false, decoded.isError)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should deserialize CompatibilityCallToolResult polymorphically`() {
|
||||||
|
val json = """{
|
||||||
|
"toolResult": {"result": "Legacy tool result"},
|
||||||
|
"content": [],
|
||||||
|
"isError": false
|
||||||
|
}"""
|
||||||
|
|
||||||
|
val decoded = McpJson.decodeFromString<RequestResult>(json)
|
||||||
|
|
||||||
|
assertIs<CompatibilityCallToolResult>(decoded)
|
||||||
|
assertEquals(buildJsonObject { put("result", "Legacy tool result") }, decoded.toolResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback Test
|
||||||
|
@Test
|
||||||
|
fun `should deserialize EmptyRequestResult for unknown result type`() {
|
||||||
|
val json = """{"unknownField": "value"}"""
|
||||||
|
|
||||||
|
val decoded = McpJson.decodeFromString<RequestResult>(json)
|
||||||
|
|
||||||
|
assertIs<EmptyRequestResult>(decoded)
|
||||||
|
assertEquals(EmptyJsonObject, decoded._meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should handle empty JSON object`() {
|
||||||
|
val json = """{}"""
|
||||||
|
|
||||||
|
val decoded = McpJson.decodeFromString<RequestResult>(json)
|
||||||
|
|
||||||
|
assertIs<EmptyRequestResult>(decoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Priority Test - Client results should take precedence over server results
|
||||||
|
@Test
|
||||||
|
fun `should prioritize client results over server results when both match`() {
|
||||||
|
// This JSON could potentially match both CreateMessageResult (client) and CallToolResult (server)
|
||||||
|
// but CreateMessageResult should be selected first due to the order
|
||||||
|
val json = """{
|
||||||
|
"model": "test-model",
|
||||||
|
"role": "assistant",
|
||||||
|
"content": {
|
||||||
|
"type": "text",
|
||||||
|
"text": "Test message"
|
||||||
|
},
|
||||||
|
"stopReason": "endTurn"
|
||||||
|
}"""
|
||||||
|
|
||||||
|
val decoded = McpJson.decodeFromString<RequestResult>(json)
|
||||||
|
|
||||||
|
assertIs<CreateMessageResult>(decoded)
|
||||||
|
assertEquals("test-model", decoded.model)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue