Add ktlint (#213)

* Add ktlint

* Simplify `initClient` function and clean imports in SseIntegrationTest.
This commit is contained in:
Pavel Gorgulov 2025-08-05 15:10:44 +02:00 • committed by GitHub
commit 91cf1205af
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
40 changed files with 800 additions and 758 deletions

37
.editorconfig Normal file
View file

@ -0,0 +1,37 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
max_line_length = 120
[*.json]
indent_size = 2
[{*.yaml,*.yml}]
indent_size = 2
[*.{kt,kts}]
ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL
# Disable wildcard imports entirely
ij_kotlin_name_count_to_use_star_import = 2147483647
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
ij_kotlin_packages_to_use_import_on_demand = unset
ktlint_code_style = intellij_idea
ktlint_experimental = enabled
ktlint_standard_filename = disabled
ktlint_standard_no-empty-first-line-in-class-body = disabled
ktlint_class_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = 4
ktlint_function_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = 4
ktlint_standard_chain-method-continuation = disabled
ktlint_ignore_back_ticked_identifier = true
ktlint_standard_multiline-expression-wrapping = disabled
ktlint_standard_when-entry-bracing = disabled
[*/build/**/*]
ktlint = disabled

View file

@ -1,4 +1,12 @@
plugins {
alias(libs.plugins.ktlint)
}
allprojects {
group = "io.modelcontextprotocol"
version = "0.6.0"
}
}
subprojects {
apply(plugin = "org.jlleitschuh.gradle.ktlint")
}

View file

@ -3,6 +3,7 @@
kotlin = "2.2.0"
dokka = "2.0.0"
atomicfu = "0.29.0"
ktlint = "13.0.0"
# libraries version
serialization = "1.9.0"
@ -57,6 +58,7 @@ ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serializatio
[plugins]
kotlinx-binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binaryCompatibilityValidatorPlugin" }
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" }
# Samples
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }

View file

@ -11,9 +11,15 @@ plugins {
}
kotlin {
iosArm64(); iosX64(); iosSimulatorArm64()
watchosX64(); watchosArm64(); watchosSimulatorArm64()
tvosX64(); tvosArm64(); tvosSimulatorArm64()
iosArm64()
iosX64()
iosSimulatorArm64()
watchosX64()
watchosArm64()
watchosSimulatorArm64()
tvosX64()
tvosArm64()
tvosSimulatorArm64()
js {
browser()
nodejs()
@ -40,4 +46,4 @@ kotlin {
}
}
}
}
}

View file

@ -80,10 +80,8 @@ public class ClientOptions(
* @param clientInfo Information about the client implementation (name, version).
* @param options Configuration options for this client.
*/
public open class Client(
private val clientInfo: Implementation,
options: ClientOptions = ClientOptions(),
) : Protocol(options) {
public open class Client(private val clientInfo: Implementation, options: ClientOptions = ClientOptions()) :
Protocol(options) {
/**
* Retrieves the server's reported capabilities after the initialization process completes.
@ -144,13 +142,13 @@ public open class Client(
val message = InitializeRequest(
protocolVersion = LATEST_PROTOCOL_VERSION,
capabilities = capabilities,
clientInfo = clientInfo
clientInfo = clientInfo,
)
val result = request<InitializeResult>(message)
if (!SUPPORTED_PROTOCOL_VERSIONS.contains(result.protocolVersion)) {
throw IllegalStateException(
"Server's protocol version is not supported: ${result.protocolVersion}"
"Server's protocol version is not supported: ${result.protocolVersion}",
)
}
@ -165,11 +163,9 @@ public open class Client(
}
throw error
}
}
override fun assertCapabilityForMethod(method: Method) {
when (method) {
Method.Defined.LoggingSetLevel -> {
@ -181,7 +177,7 @@ public open class Client(
Method.Defined.PromptsGet,
Method.Defined.PromptsList,
Method.Defined.CompletionComplete,
-> {
-> {
if (serverCapabilities?.prompts == null) {
throw IllegalStateException("Server does not support prompts (required for $method)")
}
@ -192,20 +188,20 @@ public open class Client(
Method.Defined.ResourcesRead,
Method.Defined.ResourcesSubscribe,
Method.Defined.ResourcesUnsubscribe,
-> {
-> {
val resCaps = serverCapabilities?.resources
?: error("Server does not support resources (required for $method)")
if (method == Method.Defined.ResourcesSubscribe && resCaps.subscribe != true) {
throw IllegalStateException(
"Server does not support resource subscriptions (required for $method)"
"Server does not support resource subscriptions (required for $method)",
)
}
}
Method.Defined.ToolsCall,
Method.Defined.ToolsList,
-> {
-> {
if (serverCapabilities?.tools == null) {
throw IllegalStateException("Server does not support tools (required for $method)")
}
@ -213,7 +209,7 @@ public open class Client(
Method.Defined.Initialize,
Method.Defined.Ping,
-> {
-> {
// No specific capability required
}
@ -228,7 +224,7 @@ public open class Client(
Method.Defined.NotificationsRootsListChanged -> {
if (capabilities.roots?.listChanged != true) {
throw IllegalStateException(
"Client does not support roots list changed notifications (required for $method)"
"Client does not support roots list changed notifications (required for $method)",
)
}
}
@ -236,7 +232,7 @@ public open class Client(
Method.Defined.NotificationsInitialized,
Method.Defined.NotificationsCancelled,
Method.Defined.NotificationsProgress,
-> {
-> {
// Always allowed
}
@ -251,7 +247,7 @@ public open class Client(
Method.Defined.SamplingCreateMessage -> {
if (capabilities.sampling == null) {
throw IllegalStateException(
"Client does not support sampling capability (required for $method)"
"Client does not support sampling capability (required for $method)",
)
}
}
@ -259,7 +255,7 @@ public open class Client(
Method.Defined.RootsList -> {
if (capabilities.roots == null) {
throw IllegalStateException(
"Client does not support roots capability (required for $method)"
"Client does not support roots capability (required for $method)",
)
}
}
@ -267,7 +263,7 @@ public open class Client(
Method.Defined.ElicitationCreate -> {
if (capabilities.elicitation == null) {
throw IllegalStateException(
"Client does not support elicitation capability (required for $method)"
"Client does not support elicitation capability (required for $method)",
)
}
}
@ -280,16 +276,14 @@ public open class Client(
}
}
/**
* Sends a ping request to the server to check connectivity.
*
* @param options Optional request options.
* @throws IllegalStateException If the server does not support the ping method (unlikely).
*/
public suspend fun ping(options: RequestOptions? = null): EmptyRequestResult {
return request<EmptyRequestResult>(PingRequest(), options)
}
public suspend fun ping(options: RequestOptions? = null): EmptyRequestResult =
request<EmptyRequestResult>(PingRequest(), options)
/**
* Sends a completion request to the server, typically to generate or complete some content.
@ -299,9 +293,8 @@ public open class Client(
* @return The completion result returned by the server, or `null` if none.
* @throws IllegalStateException If the server does not support prompts or completion.
*/
public suspend fun complete(params: CompleteRequest, options: RequestOptions? = null): CompleteResult {
return request(params, options)
}
public suspend fun complete(params: CompleteRequest, options: RequestOptions? = null): CompleteResult =
request(params, options)
/**
* Sets the logging level on the server.
@ -310,9 +303,8 @@ public open class Client(
* @param options Optional request options.
* @throws IllegalStateException If the server does not support logging.
*/
public suspend fun setLoggingLevel(level: LoggingLevel, options: RequestOptions? = null): EmptyRequestResult {
return request<EmptyRequestResult>(SetLevelRequest(level), options)
}
public suspend fun setLoggingLevel(level: LoggingLevel, options: RequestOptions? = null): EmptyRequestResult =
request<EmptyRequestResult>(SetLevelRequest(level), options)
/**
* Retrieves a prompt by name from the server.
@ -322,9 +314,8 @@ public open class Client(
* @return The requested prompt details, or `null` if not found.
* @throws IllegalStateException If the server does not support prompts.
*/
public suspend fun getPrompt(request: GetPromptRequest, options: RequestOptions? = null): GetPromptResult {
return request(request, options)
}
public suspend fun getPrompt(request: GetPromptRequest, options: RequestOptions? = null): GetPromptResult =
request(request, options)
/**
* Lists all available prompts from the server.
@ -337,9 +328,7 @@ public open class Client(
public suspend fun listPrompts(
request: ListPromptsRequest = ListPromptsRequest(),
options: RequestOptions? = null,
): ListPromptsResult {
return request(request, options)
}
): ListPromptsResult = request(request, options)
/**
* Lists all available resources from the server.
@ -352,9 +341,7 @@ public open class Client(
public suspend fun listResources(
request: ListResourcesRequest = ListResourcesRequest(),
options: RequestOptions? = null,
): ListResourcesResult {
return request(request, options)
}
): ListResourcesResult = request(request, options)
/**
* Lists resource templates available on the server.
@ -367,9 +354,7 @@ public open class Client(
public suspend fun listResourceTemplates(
request: ListResourceTemplatesRequest,
options: RequestOptions? = null,
): ListResourceTemplatesResult {
return request(request, options)
}
): ListResourceTemplatesResult = request(request, options)
/**
* Reads a resource from the server by its URI.
@ -382,9 +367,7 @@ public open class Client(
public suspend fun readResource(
request: ReadResourceRequest,
options: RequestOptions? = null,
): ReadResourceResult {
return request(request, options)
}
): ReadResourceResult = request(request, options)
/**
* Subscribes to resource changes on the server.
@ -396,9 +379,7 @@ public open class Client(
public suspend fun subscribeResource(
request: SubscribeRequest,
options: RequestOptions? = null,
): EmptyRequestResult {
return request(request, options)
}
): EmptyRequestResult = request(request, options)
/**
* Unsubscribes from resource changes on the server.
@ -410,9 +391,7 @@ public open class Client(
public suspend fun unsubscribeResource(
request: UnsubscribeRequest,
options: RequestOptions? = null,
): EmptyRequestResult {
return request(request, options)
}
): EmptyRequestResult = request(request, options)
/**
* Calls a tool on the server by name, passing the specified arguments.
@ -443,7 +422,7 @@ public open class Client(
val request = CallToolRequest(
name = name,
arguments = JsonObject(jsonArguments)
arguments = JsonObject(jsonArguments),
)
return callTool(request, compatibility, options)
}
@ -461,12 +440,10 @@ public open class Client(
request: CallToolRequest,
compatibility: Boolean = false,
options: RequestOptions? = null,
): CallToolResultBase? {
return if (compatibility) {
request<CompatibilityCallToolResult>(request, options)
} else {
request<CallToolResult>(request, options)
}
): CallToolResultBase? = if (compatibility) {
request<CompatibilityCallToolResult>(request, options)
} else {
request<CallToolResult>(request, options)
}
/**
@ -480,9 +457,7 @@ public open class Client(
public suspend fun listTools(
request: ListToolsRequest = ListToolsRequest(),
options: RequestOptions? = null,
): ListToolsResult {
return request(request, options)
}
): ListToolsResult = request(request, options)
/**
* Registers a single root.
@ -491,10 +466,7 @@ public open class Client(
* @param name A human-readable name for the root.
* @throws IllegalStateException If the client does not support roots.
*/
public fun addRoot(
uri: String,
name: String,
) {
public fun addRoot(uri: String, name: String) {
if (capabilities.roots == null) {
logger.error { "Failed to add root '$name': Client does not support roots capability" }
throw IllegalStateException("Client does not support roots capability.")

View file

@ -12,7 +12,7 @@ import kotlin.time.Duration
*
* @param urlString Optional URL of the MCP server.
* @param reconnectionTime Optional duration to wait before attempting to reconnect.
* @param requestBuilder Optional lambda to configure the HTTP request.
* @param requestBuilder Optional lambda to configure the HTTP request.
* @return A [SSEClientTransport] configured for MCP communication.
*/
public fun HttpClient.mcpSseTransport(
@ -38,8 +38,8 @@ public suspend fun HttpClient.mcpSse(
val client = Client(
Implementation(
name = IMPLEMENTATION_NAME,
version = LIB_VERSION
)
version = LIB_VERSION,
),
)
client.connect(transport)
return client

View file

@ -139,6 +139,7 @@ public class SseClientTransport(
}
"endpoint" -> handleEndpoint(event.data.orEmpty())
else -> handleMessage(event.data.orEmpty())
}
}

View file

@ -35,10 +35,7 @@ import kotlin.coroutines.CoroutineContext
* @param output The output stream where messages are sent.
*/
@OptIn(ExperimentalAtomicApi::class)
public class StdioClientTransport(
private val input: Source,
private val output: Sink
) : AbstractTransport() {
public class StdioClientTransport(private val input: Source, private val output: Sink) : AbstractTransport() {
private val logger = KotlinLogging.logger {}
private val ioCoroutineContext: CoroutineContext = IODispatcher
private val scope by lazy {

View file

@ -50,10 +50,8 @@ private const val MCP_RESUMPTION_TOKEN_HEADER = "Last-Event-ID"
/**
* Error class for Streamable HTTP transport errors.
*/
public class StreamableHttpError(
public val code: Int? = null,
message: String? = null
) : Exception("Streamable HTTP error: $message")
public class StreamableHttpError(public val code: Int? = null, message: String? = null) :
Exception("Streamable HTTP error: $message")
/**
* Client transport for Streamable HTTP: this implements the MCP Streamable HTTP transport specification.
@ -102,15 +100,16 @@ public class StreamableHttpClientTransport(
public suspend fun send(
message: JSONRPCMessage,
resumptionToken: String?,
onResumptionToken: ((String) -> Unit)? = null
onResumptionToken: ((String) -> Unit)? = null,
) {
logger.debug { "Client sending message via POST to $url: ${McpJson.encodeToString(message)}" }
// If we have a resumption token, reconnect the SSE stream with it
resumptionToken?.let { token ->
startSseSession(
resumptionToken = token, onResumptionToken = onResumptionToken,
replayMessageId = if (message is JSONRPCRequest) message.id else null
resumptionToken = token,
onResumptionToken = onResumptionToken,
replayMessageId = if (message is JSONRPCRequest) message.id else null,
)
return
}
@ -147,8 +146,9 @@ public class StreamableHttpClientTransport(
}
ContentType.Text.EventStream -> handleInlineSse(
response, onResumptionToken = onResumptionToken,
replayMessageId = if (message is JSONRPCRequest) message.id else null
response,
onResumptionToken = onResumptionToken,
replayMessageId = if (message is JSONRPCRequest) message.id else null,
)
else -> {
@ -197,7 +197,7 @@ public class StreamableHttpClientTransport(
if (!response.status.isSuccess() && response.status != HttpStatusCode.MethodNotAllowed) {
val error = StreamableHttpError(
response.status.value,
"Failed to terminate session: ${response.status.description}"
"Failed to terminate session: ${response.status.description}",
)
logger.error(error) { "Failed to terminate session" }
_onError(error)
@ -212,7 +212,7 @@ public class StreamableHttpClientTransport(
private suspend fun startSseSession(
resumptionToken: String? = null,
replayMessageId: RequestId? = null,
onResumptionToken: ((String) -> Unit)? = null
onResumptionToken: ((String) -> Unit)? = null,
) {
sseSession?.cancel()
sseJob?.cancelAndJoin()
@ -254,7 +254,7 @@ public class StreamableHttpClientTransport(
private suspend fun collectSse(
session: ClientSSESession,
replayMessageId: RequestId?,
onResumptionToken: ((String) -> Unit)?
onResumptionToken: ((String) -> Unit)?,
) {
try {
session.incoming.collect { event ->
@ -290,7 +290,7 @@ public class StreamableHttpClientTransport(
private suspend fun handleInlineSse(
response: HttpResponse,
replayMessageId: RequestId?,
onResumptionToken: ((String) -> Unit)?
onResumptionToken: ((String) -> Unit)?,
) {
logger.trace { "Handling inline SSE from POST response" }
val channel = response.bodyAsChannel()

View file

@ -39,4 +39,4 @@ public suspend fun HttpClient.mcpStreamableHttp(
val client = Client(Implementation(name = IMPLEMENTATION_NAME, version = LIB_VERSION))
client.connect(transport)
return client
}
}

View file

@ -33,8 +33,8 @@ public suspend fun HttpClient.mcpWebSocket(
val client = Client(
Implementation(
name = IMPLEMENTATION_NAME,
version = LIB_VERSION
)
version = LIB_VERSION,
),
)
client.connect(transport)
return client

View file

@ -47,7 +47,7 @@ class StreamableHttpClientTransportTest {
val message = JSONRPCRequest(
id = RequestId.StringId("test-id"),
method = "test",
params = buildJsonObject { }
params = buildJsonObject { },
)
val transport = createTransport { request ->
@ -61,7 +61,7 @@ class StreamableHttpClientTransportTest {
respond(
content = "",
status = HttpStatusCode.Accepted
status = HttpStatusCode.Accepted,
)
}
@ -76,26 +76,30 @@ class StreamableHttpClientTransportTest {
id = RequestId.StringId("test-id"),
method = "initialize",
params = buildJsonObject {
put("clientInfo", buildJsonObject {
put("name", JsonPrimitive("test-client"))
put("version", JsonPrimitive("1.0"))
})
put(
"clientInfo",
buildJsonObject {
put("name", JsonPrimitive("test-client"))
put("version", JsonPrimitive("1.0"))
},
)
put("protocolVersion", JsonPrimitive("2025-06-18"))
}
},
)
val transport = createTransport { request ->
when (val msg = McpJson.decodeFromString<JSONRPCMessage>((request.body as TextContent).text)) {
is JSONRPCRequest if msg.method == "initialize" -> respond(
content = "", status = HttpStatusCode.OK,
headers = headersOf("mcp-session-id", "test-session-id")
content = "",
status = HttpStatusCode.OK,
headers = headersOf("mcp-session-id", "test-session-id"),
)
is JSONRPCNotification if msg.method == "test" -> {
assertEquals("test-session-id", request.headers["mcp-session-id"])
respond(
content = "",
status = HttpStatusCode.Accepted
status = HttpStatusCode.Accepted,
)
}
@ -122,7 +126,7 @@ class StreamableHttpClientTransportTest {
assertEquals("test-session-id", request.headers["mcp-session-id"])
respond(
content = "",
status = HttpStatusCode.OK
status = HttpStatusCode.OK,
)
}
@ -141,7 +145,7 @@ class StreamableHttpClientTransportTest {
assertEquals(HttpMethod.Delete, request.method)
respond(
content = "",
status = HttpStatusCode.MethodNotAllowed
status = HttpStatusCode.MethodNotAllowed,
)
}
@ -160,7 +164,7 @@ class StreamableHttpClientTransportTest {
assertEquals("2025-06-18", request.headers["mcp-protocol-version"])
respond(
content = "",
status = HttpStatusCode.Accepted
status = HttpStatusCode.Accepted,
)
}
transport.protocolVersion = "2025-06-18"
@ -170,7 +174,8 @@ class StreamableHttpClientTransportTest {
transport.close()
}
@Ignore //Engine doesn't support SSECapability: https://youtrack.jetbrains.com/issue/KTOR-8177/MockEngine-Add-SSE-support
// Engine doesn't support SSECapability: https://youtrack.jetbrains.com/issue/KTOR-8177/MockEngine-Add-SSE-support
@Ignore
@Test
fun testNotificationSchemaE2E() = runTest {
val receivedMessages = mutableListOf<JSONRPCMessage>()
@ -182,7 +187,7 @@ class StreamableHttpClientTransportTest {
respond(
content = "",
status = HttpStatusCode.Accepted,
headers = headersOf("mcp-session-id", "notification-test-session")
headers = headersOf("mcp-session-id", "notification-test-session"),
)
}
@ -193,7 +198,9 @@ class StreamableHttpClientTransportTest {
// Server sends various notifications
appendLine("event: message")
appendLine("id: 1")
appendLine("""data: {"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"upload-123","progress":50,"total":100}}""")
appendLine(
"""data: {"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"upload-123","progress":50,"total":100}}""",
)
appendLine()
appendLine("event: message")
@ -210,8 +217,9 @@ class StreamableHttpClientTransportTest {
content = ByteReadChannel(sseContent),
status = HttpStatusCode.OK,
headers = headersOf(
HttpHeaders.ContentType, ContentType.Text.EventStream.toString()
)
HttpHeaders.ContentType,
ContentType.Text.EventStream.toString(),
),
)
}
@ -219,7 +227,7 @@ class StreamableHttpClientTransportTest {
HttpMethod.Post -> {
respond(
content = "",
status = HttpStatusCode.Accepted
status = HttpStatusCode.Accepted,
)
}
@ -238,11 +246,14 @@ class StreamableHttpClientTransportTest {
method = "notifications/initialized",
params = buildJsonObject {
put("protocolVersion", JsonPrimitive("1.0"))
put("capabilities", buildJsonObject {
put("tools", JsonPrimitive(true))
put("resources", JsonPrimitive(true))
})
}
put(
"capabilities",
buildJsonObject {
put("tools", JsonPrimitive(true))
put("resources", JsonPrimitive(true))
},
)
},
)
transport.send(initializedNotification)
@ -274,25 +285,28 @@ class StreamableHttpClientTransportTest {
params = buildJsonObject {
put("progressToken", JsonPrimitive("download-456"))
put("progress", JsonPrimitive(75))
}
},
),
JSONRPCNotification(
method = "notifications/cancelled",
params = buildJsonObject {
put("requestId", JsonPrimitive("req-789"))
put("reason", JsonPrimitive("user_cancelled"))
}
},
),
JSONRPCNotification(
method = "notifications/message",
params = buildJsonObject {
put("level", JsonPrimitive("info"))
put("message", JsonPrimitive("Operation completed"))
put("data", buildJsonObject {
put("duration", JsonPrimitive(1234))
})
}
)
put(
"data",
buildJsonObject {
put("duration", JsonPrimitive(1234))
},
)
},
),
)
// Send all client notifications
@ -305,7 +319,8 @@ class StreamableHttpClientTransportTest {
transport.close()
}
@Ignore // Engine doesn't support SSECapability: https://youtrack.jetbrains.com/issue/KTOR-8177/MockEngine-Add-SSE-support
// Engine doesn't support SSECapability: https://youtrack.jetbrains.com/issue/KTOR-8177/MockEngine-Add-SSE-support
@Ignore
@Test
fun testNotificationWithResumptionToken() = runTest {
var resumptionTokenReceived: String? = null
@ -320,15 +335,18 @@ class StreamableHttpClientTransportTest {
val sseContent = buildString {
appendLine("event: message")
appendLine("id: resume-100")
appendLine("""data: {"jsonrpc":"2.0","method":"notifications/resumed","params":{"fromToken":"${lastEventIdSent}"}}""")
appendLine(
"""data: {"jsonrpc":"2.0","method":"notifications/resumed","params":{"fromToken":"$lastEventIdSent"}}""",
)
appendLine()
}
respond(
content = ByteReadChannel(sseContent),
status = HttpStatusCode.OK,
headers = headersOf(
HttpHeaders.ContentType, ContentType.Text.EventStream.toString()
)
HttpHeaders.ContentType,
ContentType.Text.EventStream.toString(),
),
)
}
@ -344,12 +362,12 @@ class StreamableHttpClientTransportTest {
method = "notifications/test",
params = buildJsonObject {
put("data", JsonPrimitive("test-data"))
}
},
),
resumptionToken = "previous-token-99",
onResumptionToken = { token ->
resumptionTokenReceived = token
}
},
)
// Wait for response

View file

@ -11,9 +11,15 @@ plugins {
}
kotlin {
iosArm64(); iosX64(); iosSimulatorArm64()
watchosX64(); watchosArm64(); watchosSimulatorArm64()
tvosX64(); tvosArm64(); tvosSimulatorArm64()
iosArm64()
iosX64()
iosSimulatorArm64()
watchosX64()
watchosArm64()
watchosSimulatorArm64()
tvosX64()
tvosArm64()
tvosSimulatorArm64()
js {
browser()
nodejs()
@ -42,4 +48,4 @@ kotlin {
}
}
}
}
}

View file

@ -109,7 +109,7 @@ public data class RequestOptions(
/**
* Extra data given to request handlers.
*/
public class RequestHandlerExtra()
public class RequestHandlerExtra
internal val COMPLETED = CompletableDeferred(Unit).also { it.complete(Unit) }
@ -117,15 +117,20 @@ internal val COMPLETED = CompletableDeferred(Unit).also { it.complete(Unit) }
* Implements MCP protocol framing on top of a pluggable transport, including
* features like request/response linking, notifications, and progress.
*/
public abstract class Protocol(
@PublishedApi internal val options: ProtocolOptions?,
) {
public abstract class Protocol(@PublishedApi internal val options: ProtocolOptions?) {
public var transport: Transport? = null
private set
private val _requestHandlers: AtomicRef<PersistentMap<String, suspend (JSONRPCRequest, RequestHandlerExtra) -> RequestResult?>> =
private val _requestHandlers:
AtomicRef<PersistentMap<String, suspend (JSONRPCRequest, RequestHandlerExtra) -> RequestResult?>> =
atomic(persistentMapOf())
public val requestHandlers: Map<String, suspend (request: JSONRPCRequest, extra: RequestHandlerExtra) -> RequestResult?>
public val requestHandlers: Map<
String,
suspend (
request: JSONRPCRequest,
extra: RequestHandlerExtra,
) -> RequestResult?,
>
get() = _requestHandlers.value
private val _notificationHandlers =
@ -133,7 +138,8 @@ public abstract class Protocol(
public val notificationHandlers: Map<String, suspend (notification: JSONRPCNotification) -> Unit>
get() = _notificationHandlers.value
private val _responseHandlers: AtomicRef<PersistentMap<RequestId, (response: JSONRPCResponse?, error: Exception?) -> Unit>> =
private val _responseHandlers:
AtomicRef<PersistentMap<RequestId, (response: JSONRPCResponse?, error: Exception?) -> Unit>> =
atomic(persistentMapOf())
public val responseHandlers: Map<RequestId, (response: JSONRPCResponse?, error: Exception?) -> Unit>
get() = _responseHandlers.value
@ -161,7 +167,9 @@ public abstract class Protocol(
/**
* A handler to invoke for any request types that do not have their own handler installed.
*/
public var fallbackRequestHandler: (suspend (request: JSONRPCRequest, extra: RequestHandlerExtra) -> RequestResult?)? =
public var fallbackRequestHandler: (
suspend (request: JSONRPCRequest, extra: RequestHandlerExtra) -> RequestResult?
)? =
null
/**
@ -251,8 +259,8 @@ public abstract class Protocol(
error = JSONRPCError(
ErrorCode.Defined.MethodNotFound,
message = "Server does not support ${request.method}",
)
)
),
),
)
} catch (cause: Throwable) {
LOGGER.error(cause) { "Error sending method not found response" }
@ -268,10 +276,9 @@ public abstract class Protocol(
transport?.send(
JSONRPCResponse(
id = request.id,
result = result
)
result = result,
),
)
} catch (cause: Throwable) {
LOGGER.error(cause) { "Error handling request: ${request.method} (id: ${request.id})" }
@ -281,19 +288,23 @@ public abstract class Protocol(
id = request.id,
error = JSONRPCError(
code = ErrorCode.Defined.InternalError,
message = cause.message ?: "Internal error"
)
)
message = cause.message ?: "Internal error",
),
),
)
} catch (sendError: Throwable) {
LOGGER.error(sendError) { "Failed to send error response for request: ${request.method} (id: ${request.id})" }
LOGGER.error(sendError) {
"Failed to send error response for request: ${request.method} (id: ${request.id})"
}
// Optionally implement fallback behavior here
}
}
}
private fun onProgress(notification: ProgressNotification) {
LOGGER.trace { "Received progress notification: token=${notification.params.progressToken}, progress=${notification.params.progress}/${notification.params.total}" }
LOGGER.trace {
"Received progress notification: token=${notification.params.progressToken}, progress=${notification.params.progress}/${notification.params.total}"
}
val progress = notification.params.progress
val total = notification.params.total
val message = notification.params.message
@ -378,10 +389,7 @@ public abstract class Protocol(
*
* Do not use this method to emit notifications! Use notification() instead.
*/
public suspend fun <T : RequestResult> request(
request: Request,
options: RequestOptions? = null,
): T {
public suspend fun <T : RequestResult> request(request: Request, options: RequestOptions? = null): T {
LOGGER.trace { "Sending request: ${request.method}" }
val result = CompletableDeferred<T>()
val transport = this@Protocol.transport ?: throw Error("Not connected")
@ -427,14 +435,14 @@ public abstract class Protocol(
val notification = CancelledNotification(
params = CancelledNotification.Params(
requestId = messageId,
reason = reason.message ?: "Unknown"
)
requestId = messageId,
reason = reason.message ?: "Unknown",
),
)
val serialized = JSONRPCNotification(
notification.method.value,
params = McpJson.encodeToJsonElement(notification)
params = McpJson.encodeToJsonElement(notification),
)
transport.send(serialized)
@ -454,7 +462,7 @@ public abstract class Protocol(
McpError(
ErrorCode.Defined.RequestTimeout.code,
"Request timed out",
JsonObject(mutableMapOf("timeout" to JsonPrimitive(timeout.inWholeMilliseconds)))
JsonObject(mutableMapOf("timeout" to JsonPrimitive(timeout.inWholeMilliseconds))),
),
)
result.cancel(cause)

View file

@ -20,6 +20,7 @@ public class ReadBuffer {
var lfIndex = buffer.indexOf('\n'.code.toByte())
val line = when (lfIndex) {
-1L -> return null
0L -> {
buffer.skip(1)
return null
@ -44,11 +45,6 @@ public class ReadBuffer {
}
}
internal fun deserializeMessage(line: String): JSONRPCMessage {
return McpJson.decodeFromString<JSONRPCMessage>(line)
}
public fun serializeMessage(message: JSONRPCMessage): String {
return McpJson.encodeToString(message) + "\n"
}
internal fun deserializeMessage(line: String): JSONRPCMessage = McpJson.decodeFromString<JSONRPCMessage>(line)
public fun serializeMessage(message: JSONRPCMessage): String = McpJson.encodeToString(message) + "\n"

View file

@ -43,7 +43,7 @@ public abstract class WebSocketMcpTransport : AbstractTransport() {
if (!initialized.compareAndSet(expectedValue = false, newValue = true)) {
error(
"WebSocketClientTransport already started! " +
"If using Client class, note that connect() calls start() automatically.",
"If using Client class, note that connect() calls start() automatically.",
)
}

View file

@ -168,12 +168,10 @@ public sealed interface Notification {
*
* @return The JSON-RPC notification representation.
*/
public fun Notification.toJSON(): JSONRPCNotification {
return JSONRPCNotification(
method = method.value,
params = McpJson.encodeToJsonElement(params),
)
}
public fun Notification.toJSON(): JSONRPCNotification = JSONRPCNotification(
method = method.value,
params = McpJson.encodeToJsonElement(params),
)
/**
* Decodes a JSON-RPC notification into a protocol-specific [Notification].
@ -200,9 +198,9 @@ public sealed interface RequestResult : WithMeta
* @param _meta Additional metadata for the response. Defaults to an empty JSON object.
*/
@Serializable
public data class EmptyRequestResult(
override val _meta: JsonObject = EmptyJsonObject,
) : ServerResult, ClientResult
public data class EmptyRequestResult(override val _meta: JsonObject = EmptyJsonObject) :
ServerResult,
ClientResult
/**
* A uniquely identifying ID for a request in JSON-RPC.
@ -282,7 +280,6 @@ public sealed interface ErrorCode {
MethodNotFound(-32601),
InvalidParams(-32602),
InternalError(-32603),
;
}
@Serializable
@ -293,11 +290,8 @@ public sealed interface ErrorCode {
* A response to a request that indicates an error occurred.
*/
@Serializable
public data class JSONRPCError(
val code: ErrorCode,
val message: String,
val data: JsonObject = EmptyJsonObject,
) : JSONRPCMessage
public data class JSONRPCError(val code: ErrorCode, val message: String, val data: JsonObject = EmptyJsonObject) :
JSONRPCMessage
/**
* Base interface for notification parameters with optional metadata.
@ -316,9 +310,9 @@ public sealed interface NotificationParams : WithMeta
* A client MUST NOT attempt to cancel its `initialize` request.
*/
@Serializable
public data class CancelledNotification(
override val params: Params,
) : ClientNotification, ServerNotification {
public data class CancelledNotification(override val params: Params) :
ClientNotification,
ServerNotification {
override val method: Method = Method.Defined.NotificationsCancelled
@Serializable
@ -342,10 +336,7 @@ public data class CancelledNotification(
* Describes the name and version of an MCP implementation.
*/
@Serializable
public data class Implementation(
val name: String,
val version: String,
)
public data class Implementation(val name: String, val version: String)
/**
* Capabilities a client may support.
@ -383,7 +374,7 @@ public data class ClientCapabilities(
/**
* Represents a request sent by the client.
*/
//@Serializable(with = ClientRequestPolymorphicSerializer::class)
// @Serializable(with = ClientRequestPolymorphicSerializer::class)
public interface ClientRequest : Request
/**
@ -401,7 +392,7 @@ public sealed interface ClientResult : RequestResult
/**
* Represents a request sent by the server.
*/
//@Serializable(with = ServerRequestPolymorphicSerializer::class)
// @Serializable(with = ServerRequestPolymorphicSerializer::class)
public sealed interface ServerRequest : Request
/**
@ -423,8 +414,12 @@ public sealed interface ServerResult : RequestResult
*/
@Serializable
public data class UnknownMethodRequestOrNotification(
override val method: Method, override val params: NotificationParams? = null,
) : ClientNotification, ClientRequest, ServerNotification, ServerRequest
override val method: Method,
override val params: NotificationParams? = null,
) : ClientNotification,
ClientRequest,
ServerNotification,
ServerRequest
/**
* This request is sent from the client to the server when it first connects, asking it to begin initialization.
@ -435,7 +430,8 @@ public data class InitializeRequest(
val capabilities: ClientCapabilities,
val clientInfo: Implementation,
override val _meta: JsonObject = EmptyJsonObject,
) : ClientRequest, WithMeta {
) : ClientRequest,
WithMeta {
override val method: Method = Method.Defined.Initialize
}
@ -521,15 +517,11 @@ public data class InitializeResult(
* This notification is sent from the client to the server after initialization has finished.
*/
@Serializable
public data class InitializedNotification(
override val params: Params = Params(),
) : ClientNotification {
public data class InitializedNotification(override val params: Params = Params()) : ClientNotification {
override val method: Method = Method.Defined.NotificationsInitialized
@Serializable
public data class Params(
override val _meta: JsonObject = EmptyJsonObject,
) : NotificationParams
public data class Params(override val _meta: JsonObject = EmptyJsonObject) : NotificationParams
}
/* Ping */
@ -538,7 +530,9 @@ public data class InitializedNotification(
* The receiver must promptly respond, or else it may be disconnected.
*/
@Serializable
public class PingRequest : ServerRequest, ClientRequest {
public class PingRequest :
ServerRequest,
ClientRequest {
override val method: Method = Method.Defined.Ping
}
@ -592,9 +586,9 @@ public open class Progress(
* An out-of-band notification used to inform the receiver of a progress update for a long-running request.
*/
@Serializable
public data class ProgressNotification(
override val params: Params,
) : ClientNotification, ServerNotification {
public data class ProgressNotification(override val params: Params) :
ClientNotification,
ServerNotification {
override val method: Method = Method.Defined.NotificationsProgress
@Serializable
@ -618,7 +612,8 @@ public data class ProgressNotification(
*/
override val message: String? = null,
override val _meta: JsonObject = EmptyJsonObject,
) : NotificationParams, ProgressBase
) : NotificationParams,
ProgressBase
}
/* Pagination */
@ -626,7 +621,9 @@ public data class ProgressNotification(
* Represents a request supporting pagination.
*/
@Serializable
public sealed interface PaginatedRequest : Request, WithMeta {
public sealed interface PaginatedRequest :
Request,
WithMeta {
/**
* The cursor indicating the pagination position.
*/
@ -669,11 +666,8 @@ public sealed interface ResourceContents {
* @property text The text of the item. This must only be set if the item can actually be represented as text (not binary data).
*/
@Serializable
public data class TextResourceContents(
val text: String,
override val uri: String,
override val mimeType: String?,
) : ResourceContents
public data class TextResourceContents(val text: String, override val uri: String, override val mimeType: String?) :
ResourceContents
/**
* Represents the binary contents of a resource encoded as a base64 string.
@ -681,20 +675,14 @@ public data class TextResourceContents(
* @property blob A base64-encoded string representing the binary data of the item.
*/
@Serializable
public data class BlobResourceContents(
val blob: String,
override val uri: String,
override val mimeType: String?,
) : ResourceContents
public data class BlobResourceContents(val blob: String, override val uri: String, override val mimeType: String?) :
ResourceContents
/**
* Represents resource contents with unknown or unspecified data.
*/
@Serializable
public data class UnknownResourceContents(
override val uri: String,
override val mimeType: String?,
) : ResourceContents
public data class UnknownResourceContents(override val uri: String, override val mimeType: String?) : ResourceContents
/**
* A known resource that the server is capable of reading.
@ -758,8 +746,9 @@ public data class ResourceTemplate(
@Serializable
public data class ListResourcesRequest(
override val cursor: Cursor? = null,
override val _meta: JsonObject = EmptyJsonObject
) : ClientRequest, PaginatedRequest {
override val _meta: JsonObject = EmptyJsonObject,
) : ClientRequest,
PaginatedRequest {
override val method: Method = Method.Defined.ResourcesList
}
@ -771,7 +760,8 @@ public class ListResourcesResult(
public val resources: List<Resource>,
override val nextCursor: Cursor? = null,
override val _meta: JsonObject = EmptyJsonObject,
) : ServerResult, PaginatedResult
) : ServerResult,
PaginatedResult
/**
* Sent from the client to request a list of resource templates the server has.
@ -779,8 +769,9 @@ public class ListResourcesResult(
@Serializable
public data class ListResourceTemplatesRequest(
override val cursor: Cursor?,
override val _meta: JsonObject = EmptyJsonObject
) : ClientRequest, PaginatedRequest {
override val _meta: JsonObject = EmptyJsonObject,
) : ClientRequest,
PaginatedRequest {
override val method: Method = Method.Defined.ResourcesTemplatesList
}
@ -792,16 +783,16 @@ public class ListResourceTemplatesResult(
public val resourceTemplates: List<ResourceTemplate>,
override val nextCursor: Cursor? = null,
override val _meta: JsonObject = EmptyJsonObject,
) : ServerResult, PaginatedResult
) : ServerResult,
PaginatedResult
/**
* Sent from the client to the server to read a specific resource URI.
*/
@Serializable
public data class ReadResourceRequest(
val uri: String,
override val _meta: JsonObject = EmptyJsonObject,
) : ClientRequest, WithMeta {
public data class ReadResourceRequest(val uri: String, override val _meta: JsonObject = EmptyJsonObject) :
ClientRequest,
WithMeta {
override val method: Method = Method.Defined.ResourcesRead
}
@ -820,15 +811,11 @@ public class ReadResourceResult(
* Servers may issue this without any previous subscription from the client.
*/
@Serializable
public data class ResourceListChangedNotification(
override val params: Params = Params(),
) : ServerNotification {
public data class ResourceListChangedNotification(override val params: Params = Params()) : ServerNotification {
override val method: Method = Method.Defined.NotificationsResourcesListChanged
@Serializable
public data class Params(
override val _meta: JsonObject = EmptyJsonObject,
) : NotificationParams
public data class Params(override val _meta: JsonObject = EmptyJsonObject) : NotificationParams
}
/**
@ -841,7 +828,8 @@ public data class SubscribeRequest(
*/
val uri: String,
override val _meta: JsonObject = EmptyJsonObject,
) : ClientRequest, WithMeta {
) : ClientRequest,
WithMeta {
override val method: Method = Method.Defined.ResourcesSubscribe
}
@ -855,7 +843,8 @@ public data class UnsubscribeRequest(
*/
val uri: String,
override val _meta: JsonObject = EmptyJsonObject,
) : ClientRequest, WithMeta {
) : ClientRequest,
WithMeta {
override val method: Method = Method.Defined.ResourcesUnsubscribe
}
@ -863,9 +852,7 @@ public data class UnsubscribeRequest(
* A notification from the server to the client, informing it that a resource has changed and may need to be read again. This should only be sent if the client previously sent a resources/subscribe request.
*/
@Serializable
public data class ResourceUpdatedNotification(
override val params: Params,
) : ServerNotification {
public data class ResourceUpdatedNotification(override val params: Params) : ServerNotification {
override val method: Method = Method.Defined.NotificationsResourcesUpdated
@Serializable
@ -923,8 +910,9 @@ public class Prompt(
@Serializable
public data class ListPromptsRequest(
override val cursor: Cursor? = null,
override val _meta: JsonObject = EmptyJsonObject
) : ClientRequest, PaginatedRequest {
override val _meta: JsonObject = EmptyJsonObject,
) : ClientRequest,
PaginatedRequest {
override val method: Method = Method.Defined.PromptsList
}
@ -936,7 +924,8 @@ public class ListPromptsResult(
public val prompts: List<Prompt>,
override val nextCursor: Cursor? = null,
override val _meta: JsonObject = EmptyJsonObject,
) : ServerResult, PaginatedResult
) : ServerResult,
PaginatedResult
/**
* Used by the client to get a prompt provided by the server.
@ -954,7 +943,8 @@ public data class GetPromptRequest(
val arguments: Map<String, String>?,
override val _meta: JsonObject = EmptyJsonObject,
) : ClientRequest, WithMeta {
) : ClientRequest,
WithMeta {
override val method: Method = Method.Defined.PromptsGet
}
@ -1033,22 +1023,17 @@ public data class AudioContent(
}
}
/**
* Unknown content provided to or from an LLM.
*/
@Serializable
public data class UnknownContent(
override val type: String,
) : PromptMessageContentMultimodal
public data class UnknownContent(override val type: String) : PromptMessageContentMultimodal
/**
* The contents of a resource, embedded into a prompt or tool call result.
*/
@Serializable
public data class EmbeddedResource(
val resource: ResourceContents,
) : PromptMessageContent {
public data class EmbeddedResource(val resource: ResourceContents) : PromptMessageContent {
override val type: String = TYPE
public companion object {
@ -1062,17 +1047,15 @@ public data class EmbeddedResource(
@Suppress("EnumEntryName")
@Serializable
public enum class Role {
user, assistant,
user,
assistant,
}
/**
* Describes a message returned as part of a prompt.
*/
@Serializable
public data class PromptMessage(
val role: Role,
val content: PromptMessageContent,
)
public data class PromptMessage(val role: Role, val content: PromptMessageContent)
/**
* The server's response to a prompts/get request from the client.
@ -1092,15 +1075,11 @@ public class GetPromptResult(
* Servers may issue this without any previous subscription from the client.
*/
@Serializable
public data class PromptListChangedNotification(
override val params: Params = Params(),
) : ServerNotification {
public data class PromptListChangedNotification(override val params: Params = Params()) : ServerNotification {
override val method: Method = Method.Defined.NotificationsPromptsListChanged
@Serializable
public data class Params(
override val _meta: JsonObject = EmptyJsonObject,
) : NotificationParams
public data class Params(override val _meta: JsonObject = EmptyJsonObject) : NotificationParams
}
/* Tools */
@ -1155,7 +1134,6 @@ public data class ToolAnnotations(
val openWorldHint: Boolean? = true,
)
/**
* Definition for a tool the client can call.
*/
@ -1187,20 +1165,14 @@ public data class Tool(
val annotations: ToolAnnotations?,
) {
@Serializable
public data class Input(
val properties: JsonObject = EmptyJsonObject,
val required: List<String>? = null,
) {
public data class Input(val properties: JsonObject = EmptyJsonObject, val required: List<String>? = null) {
@OptIn(ExperimentalSerializationApi::class)
@EncodeDefault
val type: String = "object"
}
@Serializable
public data class Output(
val properties: JsonObject = EmptyJsonObject,
val required: List<String>? = null,
) {
public data class Output(val properties: JsonObject = EmptyJsonObject, val required: List<String>? = null) {
@OptIn(ExperimentalSerializationApi::class)
@EncodeDefault
val type: String = "object"
@ -1213,8 +1185,9 @@ public data class Tool(
@Serializable
public data class ListToolsRequest(
override val cursor: Cursor? = null,
override val _meta: JsonObject = EmptyJsonObject
) : ClientRequest, PaginatedRequest {
override val _meta: JsonObject = EmptyJsonObject,
) : ClientRequest,
PaginatedRequest {
override val method: Method = Method.Defined.ToolsList
}
@ -1226,7 +1199,8 @@ public class ListToolsResult(
public val tools: List<Tool>,
override val nextCursor: Cursor?,
override val _meta: JsonObject = EmptyJsonObject,
) : ServerResult, PaginatedResult
) : ServerResult,
PaginatedResult
/**
* The server's response to a tool call.
@ -1269,7 +1243,8 @@ public data class CallToolRequest(
val name: String,
val arguments: JsonObject = EmptyJsonObject,
override val _meta: JsonObject = EmptyJsonObject,
) : ClientRequest, WithMeta {
) : ClientRequest,
WithMeta {
override val method: Method = Method.Defined.ToolsCall
}
@ -1278,15 +1253,11 @@ public data class CallToolRequest(
* Servers may issue this without any previous subscription from the client.
*/
@Serializable
public data class ToolListChangedNotification(
override val params: Params = Params(),
) : ServerNotification {
public data class ToolListChangedNotification(override val params: Params = Params()) : ServerNotification {
override val method: Method = Method.Defined.NotificationsToolsListChanged
@Serializable
public data class Params(
override val _meta: JsonObject = EmptyJsonObject,
) : NotificationParams
public data class Params(override val _meta: JsonObject = EmptyJsonObject) : NotificationParams
}
/* Logging */
@ -1304,7 +1275,6 @@ public enum class LoggingLevel {
critical,
alert,
emergency,
;
}
/**
@ -1313,9 +1283,7 @@ public enum class LoggingLevel {
* the server MAY decide which messages to send automatically.
*/
@Serializable
public data class LoggingMessageNotification(
override val params: Params,
) : ServerNotification {
public data class LoggingMessageNotification(override val params: Params) : ServerNotification {
override val method: Method = Method.Defined.NotificationsMessage
@Serializable
@ -1345,7 +1313,8 @@ public data class LoggingMessageNotification(
*/
val level: LoggingLevel,
override val _meta: JsonObject = EmptyJsonObject,
) : ClientRequest, WithMeta {
) : ClientRequest,
WithMeta {
override val method: Method = Method.Defined.LoggingSetLevel
}
}
@ -1404,10 +1373,7 @@ public class ModelPreferences(
* Describes a message issued to or received from an LLM API.
*/
@Serializable
public data class SamplingMessage(
val role: Role,
val content: PromptMessageContentMultimodal,
)
public data class SamplingMessage(val role: Role, val content: PromptMessageContentMultimodal)
/**
* A request from the server to sample an LLM via the client.
@ -1441,7 +1407,8 @@ public data class CreateMessageRequest(
*/
val modelPreferences: ModelPreferences?,
override val _meta: JsonObject = EmptyJsonObject,
) : ServerRequest, WithMeta {
) : ServerRequest,
WithMeta {
override val method: Method = Method.Defined.SamplingCreateMessage
@Serializable
@ -1536,9 +1503,7 @@ public data class PromptReference(
* Identifies a prompt.
*/
@Serializable
public data class UnknownReference(
override val type: String,
) : Reference
public data class UnknownReference(override val type: String) : Reference
/**
* A request from the client to the server to ask for completion options.
@ -1551,7 +1516,8 @@ public data class CompleteRequest(
*/
val argument: Argument,
override val _meta: JsonObject = EmptyJsonObject,
) : ClientRequest, WithMeta {
) : ClientRequest,
WithMeta {
override val method: Method = Method.Defined.CompletionComplete
@Serializable
@ -1571,10 +1537,8 @@ public data class CompleteRequest(
* The server's response to a completion/complete request
*/
@Serializable
public data class CompleteResult(
val completion: Completion,
override val _meta: JsonObject = EmptyJsonObject,
) : ServerResult {
public data class CompleteResult(val completion: Completion, override val _meta: JsonObject = EmptyJsonObject) :
ServerResult {
@Suppress("CanBeParameter")
@Serializable
public class Completion(
@ -1626,7 +1590,9 @@ public data class Root(
* Sent from the server to request a list of root URIs from the client.
*/
@Serializable
public class ListRootsRequest(override val _meta: JsonObject = EmptyJsonObject) : ServerRequest, WithMeta {
public class ListRootsRequest(override val _meta: JsonObject = EmptyJsonObject) :
ServerRequest,
WithMeta {
override val method: Method = Method.Defined.RootsList
}
@ -1634,24 +1600,18 @@ public class ListRootsRequest(override val _meta: JsonObject = EmptyJsonObject)
* The client's response to a roots/list request from the server.
*/
@Serializable
public class ListRootsResult(
public val roots: List<Root>,
override val _meta: JsonObject = EmptyJsonObject,
) : ClientResult
public class ListRootsResult(public val roots: List<Root>, override val _meta: JsonObject = EmptyJsonObject) :
ClientResult
/**
* A notification from the client to the server, informing it that the list of roots has changed.
*/
@Serializable
public data class RootsListChangedNotification(
override val params: Params = Params(),
) : ClientNotification {
public data class RootsListChangedNotification(override val params: Params = Params()) : ClientNotification {
override val method: Method = Method.Defined.NotificationsRootsListChanged
@Serializable
public data class Params(
override val _meta: JsonObject = EmptyJsonObject,
) : NotificationParams
public data class Params(override val _meta: JsonObject = EmptyJsonObject) : NotificationParams
}
/**
@ -1662,7 +1622,8 @@ public data class CreateElicitationRequest(
public val message: String,
public val requestedSchema: RequestedSchema,
override val _meta: JsonObject = EmptyJsonObject,
) : ServerRequest, WithMeta {
) : ServerRequest,
WithMeta {
override val method: Method = Method.Defined.ElicitationCreate
@Serializable
@ -1705,5 +1666,5 @@ public data class CreateElicitationResult(
*/
public class McpError(public val code: Int, message: String, public val data: JsonObject = EmptyJsonObject) :
Exception() {
override val message: String = "MCP error ${code}: $message"
override val message: String = "MCP error $code: $message"
}

View file

@ -73,38 +73,35 @@ internal object StopReasonSerializer : KSerializer<StopReason> {
}
internal object ReferencePolymorphicSerializer : JsonContentPolymorphicSerializer<Reference>(Reference::class) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<Reference> {
return when (element.jsonObject.getValue("type").jsonPrimitive.content) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<Reference> =
when (element.jsonObject.getValue("type").jsonPrimitive.content) {
ResourceReference.TYPE -> ResourceReference.serializer()
PromptReference.TYPE -> PromptReference.serializer()
else -> UnknownReference.serializer()
}
}
}
internal object PromptMessageContentPolymorphicSerializer :
JsonContentPolymorphicSerializer<PromptMessageContent>(PromptMessageContent::class) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<PromptMessageContent> {
return when (element.jsonObject.getValue("type").jsonPrimitive.content) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<PromptMessageContent> =
when (element.jsonObject.getValue("type").jsonPrimitive.content) {
ImageContent.TYPE -> ImageContent.serializer()
TextContent.TYPE -> TextContent.serializer()
EmbeddedResource.TYPE -> EmbeddedResource.serializer()
AudioContent.TYPE -> AudioContent.serializer()
else -> UnknownContent.serializer()
}
}
}
internal object PromptMessageContentMultimodalPolymorphicSerializer :
JsonContentPolymorphicSerializer<PromptMessageContentMultimodal>(PromptMessageContentMultimodal::class) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<PromptMessageContentMultimodal> {
return when (element.jsonObject.getValue("type").jsonPrimitive.content) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<PromptMessageContentMultimodal> =
when (element.jsonObject.getValue("type").jsonPrimitive.content) {
ImageContent.TYPE -> ImageContent.serializer()
TextContent.TYPE -> TextContent.serializer()
AudioContent.TYPE -> AudioContent.serializer()
else -> UnknownContent.serializer()
}
}
}
internal object ResourceContentsPolymorphicSerializer :
@ -125,54 +122,48 @@ internal fun selectRequestDeserializer(method: String): DeserializationStrategy<
return CustomRequest.serializer()
}
internal fun selectClientRequestDeserializer(method: String): DeserializationStrategy<ClientRequest>? {
return when (method) {
Method.Defined.Ping.value -> PingRequest.serializer()
Method.Defined.Initialize.value -> InitializeRequest.serializer()
Method.Defined.CompletionComplete.value -> CompleteRequest.serializer()
Method.Defined.LoggingSetLevel.value -> SetLevelRequest.serializer()
Method.Defined.PromptsGet.value -> GetPromptRequest.serializer()
Method.Defined.PromptsList.value -> ListPromptsRequest.serializer()
Method.Defined.ResourcesList.value -> ListResourcesRequest.serializer()
Method.Defined.ResourcesTemplatesList.value -> ListResourceTemplatesRequest.serializer()
Method.Defined.ResourcesRead.value -> ReadResourceRequest.serializer()
Method.Defined.ResourcesSubscribe.value -> SubscribeRequest.serializer()
Method.Defined.ResourcesUnsubscribe.value -> UnsubscribeRequest.serializer()
Method.Defined.ToolsCall.value -> CallToolRequest.serializer()
Method.Defined.ToolsList.value -> ListToolsRequest.serializer()
else -> null
}
internal fun selectClientRequestDeserializer(method: String): DeserializationStrategy<ClientRequest>? = when (method) {
Method.Defined.Ping.value -> PingRequest.serializer()
Method.Defined.Initialize.value -> InitializeRequest.serializer()
Method.Defined.CompletionComplete.value -> CompleteRequest.serializer()
Method.Defined.LoggingSetLevel.value -> SetLevelRequest.serializer()
Method.Defined.PromptsGet.value -> GetPromptRequest.serializer()
Method.Defined.PromptsList.value -> ListPromptsRequest.serializer()
Method.Defined.ResourcesList.value -> ListResourcesRequest.serializer()
Method.Defined.ResourcesTemplatesList.value -> ListResourceTemplatesRequest.serializer()
Method.Defined.ResourcesRead.value -> ReadResourceRequest.serializer()
Method.Defined.ResourcesSubscribe.value -> SubscribeRequest.serializer()
Method.Defined.ResourcesUnsubscribe.value -> UnsubscribeRequest.serializer()
Method.Defined.ToolsCall.value -> CallToolRequest.serializer()
Method.Defined.ToolsList.value -> ListToolsRequest.serializer()
else -> null
}
private fun selectClientNotificationDeserializer(element: JsonElement): DeserializationStrategy<ClientNotification>? {
return when (element.jsonObject.getValue("method").jsonPrimitive.content) {
private fun selectClientNotificationDeserializer(element: JsonElement): DeserializationStrategy<ClientNotification>? =
when (element.jsonObject.getValue("method").jsonPrimitive.content) {
Method.Defined.NotificationsCancelled.value -> CancelledNotification.serializer()
Method.Defined.NotificationsProgress.value -> ProgressNotification.serializer()
Method.Defined.NotificationsInitialized.value -> InitializedNotification.serializer()
Method.Defined.NotificationsRootsListChanged.value -> RootsListChangedNotification.serializer()
else -> null
}
}
internal object ClientNotificationPolymorphicSerializer :
JsonContentPolymorphicSerializer<ClientNotification>(ClientNotification::class) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<ClientNotification> {
return selectClientNotificationDeserializer(element)
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<ClientNotification> =
selectClientNotificationDeserializer(element)
?: UnknownMethodRequestOrNotification.serializer()
}
}
internal fun selectServerRequestDeserializer(method: String): DeserializationStrategy<ServerRequest>? {
return when (method) {
Method.Defined.Ping.value -> PingRequest.serializer()
Method.Defined.SamplingCreateMessage.value -> CreateMessageRequest.serializer()
Method.Defined.RootsList.value -> ListRootsRequest.serializer()
else -> null
}
internal fun selectServerRequestDeserializer(method: String): DeserializationStrategy<ServerRequest>? = when (method) {
Method.Defined.Ping.value -> PingRequest.serializer()
Method.Defined.SamplingCreateMessage.value -> CreateMessageRequest.serializer()
Method.Defined.RootsList.value -> ListRootsRequest.serializer()
else -> null
}
internal fun selectServerNotificationDeserializer(element: JsonElement): DeserializationStrategy<ServerNotification>? {
return when (element.jsonObject.getValue("method").jsonPrimitive.content) {
internal fun selectServerNotificationDeserializer(element: JsonElement): DeserializationStrategy<ServerNotification>? =
when (element.jsonObject.getValue("method").jsonPrimitive.content) {
Method.Defined.NotificationsCancelled.value -> CancelledNotification.serializer()
Method.Defined.NotificationsProgress.value -> ProgressNotification.serializer()
Method.Defined.NotificationsMessage.value -> LoggingMessageNotification.serializer()
@ -182,23 +173,20 @@ internal fun selectServerNotificationDeserializer(element: JsonElement): Deseria
Method.Defined.NotificationsPromptsListChanged.value -> PromptListChangedNotification.serializer()
else -> null
}
}
internal object ServerNotificationPolymorphicSerializer :
JsonContentPolymorphicSerializer<ServerNotification>(ServerNotification::class) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<ServerNotification> {
return selectServerNotificationDeserializer(element)
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<ServerNotification> =
selectServerNotificationDeserializer(element)
?: UnknownMethodRequestOrNotification.serializer()
}
}
internal object NotificationPolymorphicSerializer :
JsonContentPolymorphicSerializer<Notification>(Notification::class) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<Notification> {
return selectClientNotificationDeserializer(element)
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<Notification> =
selectClientNotificationDeserializer(element)
?: selectServerNotificationDeserializer(element)
?: UnknownMethodRequestOrNotification.serializer()
}
}
internal object RequestPolymorphicSerializer :
@ -243,27 +231,24 @@ private fun selectClientResultDeserializer(element: JsonElement): Deserializatio
internal object ServerResultPolymorphicSerializer :
JsonContentPolymorphicSerializer<ServerResult>(ServerResult::class) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<ServerResult> {
return selectServerResultDeserializer(element)
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<ServerResult> =
selectServerResultDeserializer(element)
?: EmptyRequestResult.serializer()
}
}
internal object ClientResultPolymorphicSerializer :
JsonContentPolymorphicSerializer<ClientResult>(ClientResult::class) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<ClientResult> {
return selectClientResultDeserializer(element)
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<ClientResult> =
selectClientResultDeserializer(element)
?: EmptyRequestResult.serializer()
}
}
internal object RequestResultPolymorphicSerializer :
JsonContentPolymorphicSerializer<RequestResult>(RequestResult::class) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<RequestResult> {
return selectClientResultDeserializer(element)
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<RequestResult> =
selectClientResultDeserializer(element)
?: selectServerResultDeserializer(element)
?: EmptyRequestResult.serializer()
}
}
internal object JSONRPCMessagePolymorphicSerializer :
@ -315,7 +300,7 @@ public fun CallToolResult.Companion.ok(content: String, meta: JsonObject = Empty
CallToolResult(
content = listOf(TextContent(content)),
isError = false,
_meta = meta
_meta = meta,
)
/**
@ -325,5 +310,5 @@ public fun CallToolResult.Companion.error(content: String, meta: JsonObject = Em
CallToolResult(
content = listOf(TextContent(content)),
isError = true,
_meta = meta
)
_meta = meta,
)

View file

@ -17,7 +17,7 @@ class AudioContentSerializationTest {
private val audioContent = AudioContent(
data = "base64-encoded-audio-data",
mimeType = "audio/wav"
mimeType = "audio/wav",
)
@Test
@ -30,4 +30,4 @@ class AudioContentSerializationTest {
val content = McpJson.decodeFromString<AudioContent>(audioContentJson)
assertEquals(expected = audioContent, actual = content)
}
}
}

View file

@ -61,4 +61,3 @@ class CallToolResultUtilsTest {
assertEquals(meta, result._meta)
}
}

Some files were not shown because too many files have changed in this diff Show more