Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -53,7 +54,7 @@ public data class HttpHeader(
@Serializable
public sealed class McpServer {
public abstract val name: String

/**
* Stdio transport configuration
*
Expand All @@ -67,7 +68,7 @@ public sealed class McpServer {
val args: List<String>,
val env: List<EnvVariable>
) : McpServer()

/**
* HTTP transport configuration
*
Expand All @@ -80,7 +81,7 @@ public sealed class McpServer {
val url: String,
val headers: List<HttpHeader>
) : McpServer()

/**
* SSE transport configuration
*
Expand Down Expand Up @@ -192,7 +193,7 @@ public data class AuthenticateRequest(
@Serializable
public data class NewSessionRequest(
val cwd: String,
val mcpServers: List<McpServer>,
val mcpServers: List<@Polymorphic McpServer>,
override val _meta: JsonElement? = null
) : AcpRequest

Expand All @@ -207,7 +208,7 @@ public data class NewSessionRequest(
public data class LoadSessionRequest(
override val sessionId: SessionId,
val cwd: String,
val mcpServers: List<McpServer>,
val mcpServers: List<@Polymorphic McpServer>,
override val _meta: JsonElement? = null
) : AcpRequest, AcpWithSessionId

Expand Down Expand Up @@ -534,4 +535,4 @@ public class CancelRequestNotification(
public val requestId: RequestId,
public val message: String?,
override val _meta: JsonElement? = null,
) : AcpNotification
) : AcpNotification
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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")
}
}
}
Original file line number Diff line number Diff line change
@@ -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\""))
}
}
Loading