diff --git a/acp-model/src/commonMain/kotlin/com/agentclientprotocol/model/Requests.kt b/acp-model/src/commonMain/kotlin/com/agentclientprotocol/model/Requests.kt index 8d2650a..5339ac8 100644 --- a/acp-model/src/commonMain/kotlin/com/agentclientprotocol/model/Requests.kt +++ b/acp-model/src/commonMain/kotlin/com/agentclientprotocol/model/Requests.kt @@ -6,6 +6,7 @@ package com.agentclientprotocol.model import com.agentclientprotocol.annotations.UnstableApi import com.agentclientprotocol.rpc.RequestId import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.Polymorphic import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonClassDiscriminator @@ -53,7 +54,7 @@ public data class HttpHeader( @Serializable public sealed class McpServer { public abstract val name: String - + /** * Stdio transport configuration * @@ -67,7 +68,7 @@ public sealed class McpServer { val args: List, val env: List ) : McpServer() - + /** * HTTP transport configuration * @@ -80,7 +81,7 @@ public sealed class McpServer { val url: String, val headers: List ) : McpServer() - + /** * SSE transport configuration * @@ -192,7 +193,7 @@ public data class AuthenticateRequest( @Serializable public data class NewSessionRequest( val cwd: String, - val mcpServers: List, + val mcpServers: List<@Polymorphic McpServer>, override val _meta: JsonElement? = null ) : AcpRequest @@ -207,7 +208,7 @@ public data class NewSessionRequest( public data class LoadSessionRequest( override val sessionId: SessionId, val cwd: String, - val mcpServers: List, + val mcpServers: List<@Polymorphic McpServer>, override val _meta: JsonElement? = null ) : AcpRequest, AcpWithSessionId @@ -534,4 +535,4 @@ public class CancelRequestNotification( public val requestId: RequestId, public val message: String?, override val _meta: JsonElement? = null, -) : AcpNotification \ No newline at end of file +) : AcpNotification diff --git a/acp-model/src/commonMain/kotlin/com/agentclientprotocol/rpc/JsonRpc.kt b/acp-model/src/commonMain/kotlin/com/agentclientprotocol/rpc/JsonRpc.kt index 24d0755..527c105 100644 --- a/acp-model/src/commonMain/kotlin/com/agentclientprotocol/rpc/JsonRpc.kt +++ b/acp-model/src/commonMain/kotlin/com/agentclientprotocol/rpc/JsonRpc.kt @@ -3,6 +3,7 @@ package com.agentclientprotocol.rpc import com.agentclientprotocol.model.AvailableCommandInput +import com.agentclientprotocol.model.McpServer import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable @@ -178,6 +179,12 @@ private val acpSerializersModule = SerializersModule { subclass(AvailableCommandInput.Unstructured::class, AvailableCommandInput.Unstructured.serializer()) defaultDeserializer { AvailableCommandInput.Unstructured.serializer() } } + polymorphic(McpServer::class) { + subclass(McpServer.Stdio::class, McpServer.Stdio.serializer()) + subclass(McpServer.Http::class, McpServer.Http.serializer()) + subclass(McpServer.Sse::class, McpServer.Sse.serializer()) + defaultDeserializer { McpServer.Stdio.serializer() } + } } @OptIn(ExperimentalSerializationApi::class) @@ -223,4 +230,4 @@ public fun decodeJsonRpcMessage(jsonString: String): JsonRpcMessage { hasMethod -> ACPJson.decodeFromJsonElement(JsonRpcNotification.serializer(), element) else -> error("Unable to determine JsonRpcMessage type from JSON structure") } -} \ No newline at end of file +} diff --git a/acp-model/src/commonTest/kotlin/com/agentclientprotocol/model/McpServerSerializerTest.kt b/acp-model/src/commonTest/kotlin/com/agentclientprotocol/model/McpServerSerializerTest.kt new file mode 100644 index 0000000..71fca11 --- /dev/null +++ b/acp-model/src/commonTest/kotlin/com/agentclientprotocol/model/McpServerSerializerTest.kt @@ -0,0 +1,91 @@ +package com.agentclientprotocol.model + +import com.agentclientprotocol.rpc.ACPJson +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class McpServerSerializerTest { + @Test + fun `decodes stdio transport without discriminator`() { + val payload = """ + { + "name": "filesystem", + "command": "/path/to/mcp-server", + "args": ["--stdio"], + "env": [{"name": "TOKEN", "value": "secret"}] + } + """.trimIndent() + + val server = ACPJson.decodeFromString(McpServer.serializer(), payload) + assertTrue(server is McpServer.Stdio) + assertEquals("filesystem", server.name) + assertEquals("/path/to/mcp-server", server.command) + assertEquals(listOf("--stdio"), server.args) + assertEquals("TOKEN", server.env.first().name) + assertEquals("secret", server.env.first().value) + } + + @Test + fun `decodes http transport with type discriminator`() { + val payload = """ + { + "type": "http", + "name": "api-server", + "url": "https://api.example.com/mcp", + "headers": [{"name": "Authorization", "value": "Bearer token123"}] + } + """.trimIndent() + + val server = ACPJson.decodeFromString(McpServer.serializer(), payload) + assertTrue(server is McpServer.Http) + assertEquals("api-server", server.name) + assertEquals("https://api.example.com/mcp", server.url) + assertEquals("Authorization", server.headers.first().name) + assertEquals("Bearer token123", server.headers.first().value) + } + + @Test + fun `decodes sse transport when type is sse`() { + val payload = """ + { + "type": "sse", + "name": "event-stream", + "url": "https://events.example.com/mcp", + "headers": [{"name": "X-API-Key", "value": "apikey456"}] + } + """.trimIndent() + + val server = ACPJson.decodeFromString(McpServer.serializer(), payload) + assertTrue(server is McpServer.Sse) + assertEquals("event-stream", server.name) + assertEquals("https://events.example.com/mcp", server.url) + assertEquals("X-API-Key", server.headers.first().name) + assertEquals("apikey456", server.headers.first().value) + } + + @Test + fun `encodes discriminator when serializing`() { + val server = McpServer.Sse( + name = "event-stream", + url = "https://events.example.com/mcp", + headers = listOf(HttpHeader("X-API-Key", "apikey456")) + ) + + val encoded = ACPJson.encodeToString(McpServer.serializer(), server) + assertTrue(encoded.contains("\"type\":\"sse\"")) + } + + @Test + fun `encodes stdio discriminator when serializing`() { + val server = McpServer.Stdio( + name = "filesystem", + command = "/path/to/mcp-server", + args = listOf("--stdio"), + env = listOf(EnvVariable("TOKEN", "secret")) + ) + + val encoded = ACPJson.encodeToString(McpServer.serializer(), server) + assertTrue(encoded.contains("\"type\":\"stdio\"")) + } +}