diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml index 96340d30..d546b166 100644 --- a/.github/workflows/gradle-publish.yml +++ b/.github/workflows/gradle-publish.yml @@ -13,7 +13,7 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: macos-latest environment: release permissions: diff --git a/.github/workflows/validate-pr.yml b/.github/workflows/validate-pr.yml index 885eb775..bfc4fc67 100644 --- a/.github/workflows/validate-pr.yml +++ b/.github/workflows/validate-pr.yml @@ -7,7 +7,7 @@ on: jobs: validate-pr: - runs-on: ubuntu-latest + runs-on: macos-latest name: Validate PR steps: - uses: actions/checkout@v4 diff --git a/.gitignore b/.gitignore index 067222bc..389cec6f 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ out/ ### Kotlin ### .kotlin +yarn.lock ### Eclipse ### .apt_generated diff --git a/README.md b/README.md index 4b0b7cb2..09bd1b47 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,19 @@ # MCP Kotlin SDK -Kotlin implementation of the [Model Context Protocol](https://modelcontextprotocol.io) (MCP), providing both client and server capabilities for integrating with LLM surfaces. +[![Kotlin Multiplatform](https://img.shields.io/badge/Kotlin-Multiplatform-blueviolet?logo=kotlin)](https://kotlinlang.org/docs/multiplatform.html) +[![Platforms](https://img.shields.io/badge/Platforms-JVM%20%7C%20Wasm%2FJS%20%7C%20Native%20(iOS%2FiOS%20Simulator)-blue)](https://kotlinlang.org/docs/multiplatform.html) +[![Maven Central](https://img.shields.io/maven-central/v/io.modelcontextprotocol/kotlin-sdk.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:io.modelcontextprotocol%20a:kotlin-sdk) +[![License](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) + +Kotlin Multiplatform implementation of the [Model Context Protocol](https://modelcontextprotocol.io) (MCP), +providing both client and server capabilities for integrating with LLM surfaces across various platforms. ## Overview -The Model Context Protocol allows applications to provide context for LLMs in a standardized way, separating the concerns of providing context from the actual LLM interaction. This Kotlin SDK implements the full MCP specification, making it easy to: +The Model Context Protocol allows applications to provide context for LLMs in a standardized way, +separating the concerns of providing context from the actual LLM interaction. +This SDK implements the MCP specification for Kotlin, +enabling you to build applications that can communicate using MCP on the JVM, WebAssembly and iOS. - Build MCP clients that can connect to any MCP server - Create MCP servers that expose resources, prompts and tools @@ -13,7 +22,7 @@ The Model Context Protocol allows applications to provide context for LLMs in a ## Samples -- [kotlin-mcp-server](./samples/kotlin-mcp-server): shows how to set up a Kotlin MCP server with different tools and other features. +- [kotlin-mcp-server](./samples/kotlin-mcp-server): demonstrates a multiplatform (JVM, Wasm) MCP server setup with various features and transports. - [weather-stdio-server](./samples/weather-stdio-server): shows how to build a Kotlin MCP server providing weather forecast and alerts using STDIO transport. - [kotlin-mcp-client](./samples/kotlin-mcp-client): demonstrates building an interactive Kotlin MCP client that connects to an MCP server via STDIO and integrates with Anthropic’s API. @@ -31,7 +40,8 @@ Add the dependency: ```kotlin dependencies { - implementation("io.modelcontextprotocol:kotlin-sdk:0.5.0") + // Use the badge above for the latest version + implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion") } ``` diff --git a/api/kotlin-sdk.api b/api/kotlin-sdk.api index a82600a8..96d59cd0 100644 --- a/api/kotlin-sdk.api +++ b/api/kotlin-sdk.api @@ -22,7 +22,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/AudioContent$$serializ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/AudioContent;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/AudioContent$Companion { @@ -53,7 +52,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/BlobResourceContents$$ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/BlobResourceContents;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/BlobResourceContents$Companion { @@ -86,7 +84,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/CallToolRequest$$seria public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/CallToolRequest;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/CallToolRequest$Companion { @@ -110,7 +107,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/CallToolResult$$serial public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/CallToolResult;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/CallToolResult$Companion { @@ -157,7 +153,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/CancelledNotification$ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/CancelledNotification;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/CancelledNotification$Companion { @@ -190,7 +185,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/ClientCapabilities$$se public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/ClientCapabilities$Companion { @@ -217,7 +211,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/ClientCapabilities$Roo public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities$Roots;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/ClientCapabilities$Roots$Companion { @@ -261,7 +254,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/CompatibilityCallToolR public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/CompatibilityCallToolResult;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/CompatibilityCallToolResult$Companion { @@ -294,7 +286,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/CompleteRequest$$seria public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/CompleteRequest;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/CompleteRequest$Argument { @@ -319,7 +310,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/CompleteRequest$Argume public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/CompleteRequest$Argument;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/CompleteRequest$Argument$Companion { @@ -353,7 +343,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/CompleteResult$$serial public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/CompleteResult;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/CompleteResult$Companion { @@ -376,7 +365,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/CompleteResult$Complet public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/CompleteResult$Completion;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/CompleteResult$Completion$Companion { @@ -421,7 +409,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/CreateMessageRequest$$ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/CreateMessageRequest;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/CreateMessageRequest$Companion { @@ -471,7 +458,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/CreateMessageResult$$s public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/CreateMessageResult;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/CreateMessageResult$Companion { @@ -494,7 +480,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/CustomMeta$$serializer public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/CustomMeta;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/CustomMeta$Companion { @@ -517,7 +502,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/CustomRequest$$seriali public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/CustomRequest;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/CustomRequest$Companion { @@ -546,7 +530,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/EmbeddedResource$$seri public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/EmbeddedResource;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/EmbeddedResource$Companion { @@ -575,7 +558,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/EmptyRequestResult$$se public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/EmptyRequestResult;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/EmptyRequestResult$Companion { @@ -630,7 +612,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/ErrorCode$Unknown$$ser public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/ErrorCode$Unknown;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/ErrorCode$Unknown$Companion { @@ -663,7 +644,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/GetPromptRequest$$seri public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/GetPromptRequest;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/GetPromptRequest$Companion { @@ -687,7 +667,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/GetPromptResult$$seria public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/GetPromptResult;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/GetPromptResult$Companion { @@ -718,7 +697,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/ImageContent$$serializ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/ImageContent;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/ImageContent$Companion { @@ -747,7 +725,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/Implementation$$serial public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/Implementation;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/Implementation$Companion { @@ -782,7 +759,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/InitializeRequest$$ser public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/InitializeRequest;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/InitializeRequest$Companion { @@ -816,7 +792,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/InitializeResult$$seri public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/InitializeResult;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/InitializeResult$Companion { @@ -837,7 +812,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/InitializedNotificatio public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/InitializedNotification;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/InitializedNotification$Companion { @@ -869,7 +843,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/JSONRPCError$$serializ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/JSONRPCError;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/JSONRPCError$Companion { @@ -909,7 +882,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/JSONRPCNotification$$s public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/JSONRPCNotification;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/JSONRPCNotification$Companion { @@ -943,7 +915,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/JSONRPCRequest$$serial public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/JSONRPCRequest;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/JSONRPCRequest$Companion { @@ -968,7 +939,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/JSONRPCResponse$$seria public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/JSONRPCResponse;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/JSONRPCResponse$Companion { @@ -1004,7 +974,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/ListPromptsRequest$$se public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/ListPromptsRequest;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/ListPromptsRequest$Companion { @@ -1028,7 +997,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/ListPromptsResult$$ser public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/ListPromptsResult;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/ListPromptsResult$Companion { @@ -1059,7 +1027,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/ListResourceTemplatesR public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/ListResourceTemplatesRequest;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/ListResourceTemplatesRequest$Companion { @@ -1083,7 +1050,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/ListResourceTemplatesR public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/ListResourceTemplatesResult;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/ListResourceTemplatesResult$Companion { @@ -1115,7 +1081,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/ListResourcesRequest$$ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/ListResourcesRequest;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/ListResourcesRequest$Companion { @@ -1139,7 +1104,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/ListResourcesResult$$s public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/ListResourcesResult;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/ListResourcesResult$Companion { @@ -1163,7 +1127,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/ListRootsRequest$$seri public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/ListRootsRequest;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/ListRootsRequest$Companion { @@ -1186,7 +1149,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/ListRootsResult$$seria public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/ListRootsResult;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/ListRootsResult$Companion { @@ -1218,7 +1180,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/ListToolsRequest$$seri public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/ListToolsRequest;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/ListToolsRequest$Companion { @@ -1242,7 +1203,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/ListToolsResult$$seria public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/ListToolsResult;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/ListToolsResult$Companion { @@ -1296,7 +1256,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/LoggingMessageNotifica public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification$Companion { @@ -1327,7 +1286,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/LoggingMessageNotifica public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification$SetLevelRequest;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification$SetLevelRequest$Companion { @@ -1371,7 +1329,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/Method$Custom$$seriali public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/Method$Custom;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/Method$Custom$Companion { @@ -1434,7 +1391,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/ModelHint$$serializer public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/ModelHint;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/ModelHint$Companion { @@ -1458,7 +1414,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/ModelPreferences$$seri public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/ModelPreferences;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/ModelPreferences$Companion { @@ -1507,7 +1462,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/PingRequest$$serialize public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/PingRequest;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/PingRequest$Companion { @@ -1532,7 +1486,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/Progress$$serializer : public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/Progress;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/Progress$Companion { @@ -1580,7 +1533,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/ProgressNotification$$ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/ProgressNotification;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/ProgressNotification$Companion { @@ -1603,7 +1555,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/Prompt$$serializer : k public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/Prompt;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/Prompt$Companion { @@ -1634,7 +1585,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/PromptArgument$$serial public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/PromptArgument;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/PromptArgument$Companion { @@ -1655,7 +1605,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/PromptListChangedNotif public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/PromptListChangedNotification;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/PromptListChangedNotification$Companion { @@ -1684,7 +1633,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/PromptMessage$$seriali public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/PromptMessage;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/PromptMessage$Companion { @@ -1730,7 +1678,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/PromptReference$$seria public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/PromptReference;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/PromptReference$Companion { @@ -1761,7 +1708,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/ReadResourceRequest$$s public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/ReadResourceRequest;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/ReadResourceRequest$Companion { @@ -1784,7 +1730,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/ReadResourceResult$$se public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/ReadResourceResult;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/ReadResourceResult$Companion { @@ -1837,7 +1782,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/RequestId$NumberId$$se public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/RequestId$NumberId;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/RequestId$NumberId$Companion { @@ -1864,7 +1808,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/RequestId$StringId$$se public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/RequestId$StringId;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/RequestId$StringId$Companion { @@ -1914,7 +1857,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/Resource$$serializer : public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/Resource;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/Resource$Companion { @@ -1945,7 +1887,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/ResourceListChangedNot public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/ResourceListChangedNotification;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/ResourceListChangedNotification$Companion { @@ -1974,7 +1915,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/ResourceReference$$ser public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/ResourceReference;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/ResourceReference$Companion { @@ -2007,7 +1947,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/ResourceTemplate$$seri public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/ResourceTemplate;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/ResourceTemplate$Companion { @@ -2038,7 +1977,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/ResourceUpdatedNotific public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/ResourceUpdatedNotification;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/ResourceUpdatedNotification$Companion { @@ -2080,7 +2018,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/Root$$serializer : kot public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/Root;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/Root$Companion { @@ -2101,7 +2038,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/RootsListChangedNotifi public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/RootsListChangedNotification;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/RootsListChangedNotification$Companion { @@ -2130,7 +2066,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/SamplingMessage$$seria public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/SamplingMessage;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/SamplingMessage$Companion { @@ -2169,7 +2104,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/ServerCapabilities$$se public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Companion { @@ -2196,7 +2130,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Pro public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Prompts;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Prompts$Companion { @@ -2225,7 +2158,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Res public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Resources;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Resources$Companion { @@ -2252,7 +2184,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Too public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Tools;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/ServerCapabilities$Tools$Companion { @@ -2328,7 +2259,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/StopReason$Other$$seri public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V public final fun serialize-AuY3eX0 (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/String;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/StopReason$Other$Companion { @@ -2368,7 +2298,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/SubscribeRequest$$seri public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/SubscribeRequest;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/SubscribeRequest$Companion { @@ -2399,7 +2328,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/TextContent$$serialize public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/TextContent;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/TextContent$Companion { @@ -2430,7 +2358,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/TextResourceContents$$ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/TextResourceContents;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/TextResourceContents$Companion { @@ -2461,7 +2388,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/Tool$$serializer : kot public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/Tool;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/Tool$Companion { @@ -2493,7 +2419,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/Tool$Input$$serializer public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/Tool$Input$Companion { @@ -2514,7 +2439,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/ToolListChangedNotific public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/ToolListChangedNotification;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/ToolListChangedNotification$Companion { @@ -2554,7 +2478,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/UnknownContent$$serial public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/UnknownContent;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/UnknownContent$Companion { @@ -2581,7 +2504,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/UnknownMethodRequestOr public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/UnknownMethodRequestOrNotification;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/UnknownMethodRequestOrNotification$Companion { @@ -2608,7 +2530,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/UnknownReference$$seri public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/UnknownReference;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/UnknownReference$Companion { @@ -2637,7 +2558,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/UnknownResourceContent public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/UnknownResourceContents;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/UnknownResourceContents$Companion { @@ -2668,7 +2588,6 @@ public synthetic class io/modelcontextprotocol/kotlin/sdk/UnsubscribeRequest$$se public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/UnsubscribeRequest;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class io/modelcontextprotocol/kotlin/sdk/UnsubscribeRequest$Companion { @@ -2834,7 +2753,7 @@ public class io/modelcontextprotocol/kotlin/sdk/server/Server : io/modelcontextp public static synthetic fun listRoots$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public fun onClose ()V public final fun onClose (Lkotlin/jvm/functions/Function0;)V - public final fun onInitalized (Lkotlin/jvm/functions/Function0;)V + public final fun onInitialized (Lkotlin/jvm/functions/Function0;)V public final fun ping (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun removePrompt (Ljava/lang/String;)Z public final fun removePrompts (Ljava/util/List;)I diff --git a/build.gradle.kts b/build.gradle.kts index 82cfd3b3..6a586511 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,9 +1,14 @@ -@file:OptIn(ExperimentalKotlinGradlePluginApi::class) +@file:OptIn(ExperimentalKotlinGradlePluginApi::class, ExperimentalWasmDsl::class) import org.jetbrains.dokka.gradle.engine.parameters.VisibilityModifier import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMetadataTarget +import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget import org.jreleaser.model.Active plugins { @@ -27,9 +32,11 @@ publishing { val javadocJar = configureEmptyJavadocArtifact() publications.withType(MavenPublication::class).all { + if (name.contains("jvm", ignoreCase = true)) { + artifact(javadocJar) + } pom.configureMavenCentralMetadata() signPublicationIfKeyPresent() - artifact(javadocJar) } repositories { @@ -53,10 +60,28 @@ jreleaser { active.set(Active.ALWAYS) mavenCentral { val ossrh by creating { - applyMavenCentralRules = true active.set(Active.ALWAYS) url.set("https://central.sonatype.com/api/v1/publisher") + applyMavenCentralRules = false + maxRetries = 240 stagingRepository(layout.buildDirectory.dir("staging-deploy").get().asFile.path) + // workaround: https://github.com/jreleaser/jreleaser/issues/1784 + kotlin.targets.forEach { target -> + if (target !is KotlinJvmTarget && target !is KotlinAndroidTarget && target !is KotlinMetadataTarget) { + val klibArtifactId = if (target.platformType == KotlinPlatformType.wasm) { + "${name}-wasm-${target.name.lowercase().substringAfter("wasm")}" + } else { + "${name}-${target.name.lowercase()}" + } + artifactOverride { + artifactId = klibArtifactId + jar = false + verifyPom = false + sourceJar = false + javadocJar = false + } + } + } } } } @@ -194,6 +219,24 @@ kotlin { } } + iosArm64() + iosX64() + iosSimulatorArm64() + + js(IR) { + nodejs { + testTask { + useMocha { + timeout = "30s" + } + } + } + } + + wasmJs { + nodejs() + } + explicitApi = ExplicitApiMode.Strict jvmToolchain(21) @@ -217,7 +260,6 @@ kotlin { implementation(libs.kotlin.test) implementation(libs.ktor.server.test.host) implementation(libs.kotlinx.coroutines.test) - implementation(libs.kotlinx.coroutines.debug) implementation(libs.kotest.assertions.json) } } diff --git a/gradle.properties b/gradle.properties index 9060ef0a..9b64f59a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,3 +5,5 @@ org.gradle.configuration-cache.parallel=true org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true + +kotlin.daemon.jvmargs=-Xmx2G \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 95153163..d32f90db 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,8 +5,8 @@ dokka = "2.0.0" # libraries version serialization = "1.7.3" -coroutines = "1.9.0" -ktor = "3.0.2" +coroutines = "1.10.2" +ktor = "3.1.2" mockk = "1.13.13" logging = "7.0.0" jreleaser = "1.17.0" @@ -28,7 +28,6 @@ ktor-server-cio = { group = "io.ktor", name = "ktor-server-cio", version.ref = " # Testing kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines" } -kotlinx-coroutines-debug = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-debug", version.ref = "coroutines" } ktor-server-test-host = { group = "io.ktor", name = "ktor-server-test-host", version.ref = "ktor" } mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" } slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" } diff --git a/samples/kotlin-mcp-server/README.md b/samples/kotlin-mcp-server/README.md index 65e01018..ee87ab76 100644 --- a/samples/kotlin-mcp-server/README.md +++ b/samples/kotlin-mcp-server/README.md @@ -1,13 +1,15 @@ # MCP Kotlin Server Sample -A sample implementation of an MCP (Model Communication Protocol) server in Kotlin that demonstrates different server configurations and transport methods. +A sample implementation of an MCP (Model Communication Protocol) server in Kotlin that demonstrates different server +configurations and transport methods for both JVM and WASM targets. ## Features - Multiple server operation modes: - - Standard I/O server - - SSE (Server-Sent Events) server with plain configuration - - SSE server using Ktor plugin + - Standard I/O server (JVM only) + - SSE (Server-Sent Events) server with plain configuration (JVM, WASM) + - SSE server using Ktor plugin (JVM, WASM) +- Multiplatform support - Built-in capabilities for: - Prompts management - Resources handling @@ -17,17 +19,30 @@ A sample implementation of an MCP (Model Communication Protocol) server in Kotli ### Running the Server -To run the server in SSE mode on the port 3001, run: +You can run the server on the JVM or using Kotlin/WASM on Node.js. + + +#### JVM: + +To run the server on the JVM (defaults to SSE mode with Ktor plugin on port 3001): + +```bash +./gradlew runJvm +``` + +#### WASM: + +To run the server using Kotlin/WASM on Node.js (defaults to SSE mode with Ktor plugin on port 3001): ```bash -./gradlew run +./gradlew wasmJsNodeDevelopmentRun ``` ### Connecting to the Server -For SSE servers (both plain and Ktor plugin versions): +For servers on JVM or WASM: 1. Start the server -2. Use the MCP inspector to connect to `http://localhost:/sse` +2. Use the [MCP inspector](https://modelcontextprotocol.io/docs/tools/inspector) to connect to `http://localhost:/sse` ## Server Capabilities @@ -42,3 +57,4 @@ The server is implemented using: - Kotlin coroutines for asynchronous operations - SSE for real-time communication - Standard I/O for command-line interface +- Common Kotlin code shared between JVM and WASM targets diff --git a/samples/kotlin-mcp-server/build.gradle.kts b/samples/kotlin-mcp-server/build.gradle.kts index fef174d7..b46fcb0b 100644 --- a/samples/kotlin-mcp-server/build.gradle.kts +++ b/samples/kotlin-mcp-server/build.gradle.kts @@ -1,35 +1,53 @@ -plugins { - kotlin("jvm") version "2.1.0" - kotlin("plugin.serialization") version "2.1.0" - application -} +@file:OptIn(ExperimentalWasmDsl::class, ExperimentalKotlinGradlePluginApi::class) -application { - mainClass.set("MainKt") -} +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl +plugins { + kotlin("multiplatform") version "2.1.20" + kotlin("plugin.serialization") version "2.1.20" +} group = "org.example" version = "0.1.0" -dependencies { - implementation("io.modelcontextprotocol:kotlin-sdk:0.5.0") - implementation("org.slf4j:slf4j-nop:2.0.9") +repositories { + mavenCentral() } -tasks.test { - useJUnitPlatform() -} -kotlin { - jvmToolchain(21) -} +val jvmMainClass = "Main_jvmKt" -tasks.jar { - manifest { - attributes["Main-Class"] = "MainKt" +kotlin { + jvmToolchain(17) + jvm { + binaries { + executable { + mainClass.set(jvmMainClass) + } + } + val jvmJar by tasks.getting(org.gradle.jvm.tasks.Jar::class) { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + doFirst { + manifest { + attributes["Main-Class"] = jvmMainClass + } + + from(configurations["jvmRuntimeClasspath"].map { if (it.isDirectory) it else zipTree(it) }) + } + } + } + wasmJs { + nodejs() + binaries.executable() } - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - - from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) }) + sourceSets { + commonMain.dependencies { + implementation("io.modelcontextprotocol:kotlin-sdk:0.5.0") + } + jvmMain.dependencies { + implementation("org.slf4j:slf4j-nop:2.0.9") + } + wasmJsMain.dependencies {} + } } diff --git a/samples/kotlin-mcp-server/gradle.properties b/samples/kotlin-mcp-server/gradle.properties index 7fc6f1ff..a39b7000 100644 --- a/samples/kotlin-mcp-server/gradle.properties +++ b/samples/kotlin-mcp-server/gradle.properties @@ -1 +1,3 @@ kotlin.code.style=official + +kotlin.daemon.jvmargs=-Xmx2G diff --git a/samples/kotlin-mcp-server/settings.gradle.kts b/samples/kotlin-mcp-server/settings.gradle.kts index 19d82e00..6efbc10a 100644 --- a/samples/kotlin-mcp-server/settings.gradle.kts +++ b/samples/kotlin-mcp-server/settings.gradle.kts @@ -2,11 +2,3 @@ plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" } rootProject.name = "kotlin-mcp-server" - -dependencyResolutionManagement { - repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) - repositories { - mavenCentral() - maven("https://maven.pkg.jetbrains.space/public/p/kotlin-mcp-sdk/sdk") - } -} diff --git a/samples/kotlin-mcp-server/src/main/kotlin/Main.kt b/samples/kotlin-mcp-server/src/commonMain/kotlin/server.kt similarity index 68% rename from samples/kotlin-mcp-server/src/main/kotlin/Main.kt rename to samples/kotlin-mcp-server/src/commonMain/kotlin/server.kt index a73b5b74..a544d0ea 100644 --- a/samples/kotlin-mcp-server/src/main/kotlin/Main.kt +++ b/samples/kotlin-mcp-server/src/commonMain/kotlin/server.kt @@ -1,50 +1,31 @@ -import io.ktor.http.* -import io.ktor.server.application.* -import io.ktor.server.cio.* -import io.ktor.server.engine.* -import io.ktor.server.response.* -import io.ktor.server.routing.* -import io.ktor.server.sse.* -import io.ktor.util.collections.* -import io.modelcontextprotocol.kotlin.sdk.* +package shared + +import io.ktor.http.HttpStatusCode +import io.ktor.server.application.install +import io.ktor.server.cio.CIO +import io.ktor.server.engine.embeddedServer +import io.ktor.server.response.respond +import io.ktor.server.routing.post +import io.ktor.server.routing.routing +import io.ktor.server.sse.SSE +import io.ktor.server.sse.sse +import io.ktor.util.collections.ConcurrentMap +import io.modelcontextprotocol.kotlin.sdk.CallToolResult import io.modelcontextprotocol.kotlin.sdk.GetPromptResult import io.modelcontextprotocol.kotlin.sdk.Implementation import io.modelcontextprotocol.kotlin.sdk.PromptArgument import io.modelcontextprotocol.kotlin.sdk.PromptMessage +import io.modelcontextprotocol.kotlin.sdk.ReadResourceResult import io.modelcontextprotocol.kotlin.sdk.Role import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.TextContent +import io.modelcontextprotocol.kotlin.sdk.TextResourceContents import io.modelcontextprotocol.kotlin.sdk.Tool import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions import io.modelcontextprotocol.kotlin.sdk.server.SseServerTransport -import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport import io.modelcontextprotocol.kotlin.sdk.server.mcp -import kotlinx.coroutines.Job -import kotlinx.coroutines.runBlocking -import kotlinx.io.asSink -import kotlinx.io.asSource -import kotlinx.io.buffered - -/** - * Start sse-server mcp on port 3001. - * - * @param args - * - "--stdio": Runs an MCP server using standard input/output. - * - "--sse-server-ktor ": Runs an SSE MCP server using Ktor plugin (default if no argument is provided). - * - "--sse-server ": Runs an SSE MCP server with a plain configuration. - */ -fun main(args: Array) { - val command = args.firstOrNull() ?: "--sse-server-ktor" - val port = args.getOrNull(1)?.toIntOrNull() ?: 3001 - when (command) { - "--stdio" -> runMcpServerUsingStdio() - "--sse-server-ktor" -> runSseMcpServerUsingKtorPlugin(port) - "--sse-server" -> runSseMcpServerWithPlainConfiguration(port) - else -> { - System.err.println("Unknown command: $command") - } - } -} +import kotlin.collections.set fun configureServer(): Server { val server = Server( @@ -111,27 +92,7 @@ fun configureServer(): Server { return server } -fun runMcpServerUsingStdio() { - // Note: The server will handle listing prompts, tools, and resources automatically. - // The handleListResourceTemplates will return empty as defined in the Server code. - val server = configureServer() - val transport = StdioServerTransport( - inputStream = System.`in`.asSource().buffered(), - outputStream = System.out.asSink().buffered() - ) - - runBlocking { - server.connect(transport) - val done = Job() - server.onClose { - done.complete() - } - done.join() - println("Server closed") - } -} - -fun runSseMcpServerWithPlainConfiguration(port: Int): Unit = runBlocking { +suspend fun runSseMcpServerWithPlainConfiguration(port: Int): Unit { val servers = ConcurrentMap() println("Starting sse server on port $port. ") println("Use inspector to connect to the http://localhost:$port/sse") @@ -167,7 +128,7 @@ fun runSseMcpServerWithPlainConfiguration(port: Int): Unit = runBlocking { transport.handlePostMessage(call) } } - }.start(wait = true) + }.startSuspend(wait = true) } /** @@ -178,7 +139,7 @@ fun runSseMcpServerWithPlainConfiguration(port: Int): Unit = runBlocking { * @param port The port number on which the SSE MCP server will listen for client connections. * @return Unit This method does not return a value. */ -fun runSseMcpServerUsingKtorPlugin(port: Int): Unit = runBlocking { +suspend fun runSseMcpServerUsingKtorPlugin(port: Int): Unit { println("Starting sse server on port $port") println("Use inspector to connect to the http://localhost:$port/sse") @@ -186,5 +147,5 @@ fun runSseMcpServerUsingKtorPlugin(port: Int): Unit = runBlocking { mcp { return@mcp configureServer() } - }.start(wait = true) + }.startSuspend(wait = true) } \ No newline at end of file diff --git a/samples/kotlin-mcp-server/src/jvmMain/kotlin/main.jvm.kt b/samples/kotlin-mcp-server/src/jvmMain/kotlin/main.jvm.kt new file mode 100644 index 00000000..509aa569 --- /dev/null +++ b/samples/kotlin-mcp-server/src/jvmMain/kotlin/main.jvm.kt @@ -0,0 +1,50 @@ +import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport +import kotlinx.coroutines.Job +import kotlinx.coroutines.runBlocking +import kotlinx.io.asSink +import kotlinx.io.asSource +import kotlinx.io.buffered +import shared.configureServer +import shared.runSseMcpServerUsingKtorPlugin +import shared.runSseMcpServerWithPlainConfiguration + +/** + * Start sse-server mcp on port 3001. + * + * @param args + * - "--stdio": Runs an MCP server using standard input/output. + * - "--sse-server-ktor ": Runs an SSE MCP server using Ktor plugin (default if no argument is provided). + * - "--sse-server ": Runs an SSE MCP server with a plain configuration. + */ +fun main(args: Array): Unit = runBlocking { + val command = args.firstOrNull() ?: "--sse-server-ktor" + val port = args.getOrNull(1)?.toIntOrNull() ?: 3001 + when (command) { + "--stdio" -> runMcpServerUsingStdio() + "--sse-server-ktor" -> runSseMcpServerUsingKtorPlugin(port) + "--sse-server" -> runSseMcpServerWithPlainConfiguration(port) + else -> { + System.err.println("Unknown command: $command") + } + } +} + +fun runMcpServerUsingStdio() { + // Note: The server will handle listing prompts, tools, and resources automatically. + // The handleListResourceTemplates will return empty as defined in the Server code. + val server = configureServer() + val transport = StdioServerTransport( + inputStream = System.`in`.asSource().buffered(), + outputStream = System.out.asSink().buffered() + ) + + runBlocking { + server.connect(transport) + val done = Job() + server.onClose { + done.complete() + } + done.join() + println("Server closed") + } +} \ No newline at end of file diff --git a/samples/kotlin-mcp-server/src/wasmJsMain/kotlin/main.wasmJs.kt b/samples/kotlin-mcp-server/src/wasmJsMain/kotlin/main.wasmJs.kt new file mode 100644 index 00000000..7aa12190 --- /dev/null +++ b/samples/kotlin-mcp-server/src/wasmJsMain/kotlin/main.wasmJs.kt @@ -0,0 +1,21 @@ +import shared.runSseMcpServerUsingKtorPlugin +import shared.runSseMcpServerWithPlainConfiguration + +/** + * Start sse-server mcp on port 3001. + * + * @param args + * - "--sse-server-ktor ": Runs an SSE MCP server using Ktor plugin (default if no argument is provided). + * - "--sse-server ": Runs an SSE MCP server with a plain configuration. + */ +suspend fun main(args: Array) { + val command = args.firstOrNull() ?: "--sse-server-ktor" + val port = args.getOrNull(1)?.toIntOrNull() ?: 3001 + when (command) { + "--sse-server-ktor" -> runSseMcpServerUsingKtorPlugin(port) + "--sse-server" -> runSseMcpServerWithPlainConfiguration(port) + else -> { + error("Unknown command: $command") + } + } +} \ No newline at end of file diff --git a/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt b/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt index 18fb37ad..6bdb84d9 100644 --- a/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt +++ b/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt @@ -67,12 +67,9 @@ fun `run mcp server`() { required = listOf("state") ) ) { request -> - val state = request.arguments["state"]?.jsonPrimitive?.content - if (state == null) { - return@addTool CallToolResult( - content = listOf(TextContent("The 'state' parameter is required.")) - ) - } + val state = request.arguments["state"]?.jsonPrimitive?.content ?: return@addTool CallToolResult( + content = listOf(TextContent("The 'state' parameter is required.")) + ) val alerts = httpClient.getAlerts(state) diff --git a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt index 1bd7372b..1b70daca 100644 --- a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt +++ b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/Client.kt @@ -1,7 +1,39 @@ package io.modelcontextprotocol.kotlin.sdk.client -import io.modelcontextprotocol.kotlin.sdk.* +import io.modelcontextprotocol.kotlin.sdk.CallToolRequest +import io.modelcontextprotocol.kotlin.sdk.CallToolResult +import io.modelcontextprotocol.kotlin.sdk.CallToolResultBase +import io.modelcontextprotocol.kotlin.sdk.ClientCapabilities +import io.modelcontextprotocol.kotlin.sdk.CompatibilityCallToolResult +import io.modelcontextprotocol.kotlin.sdk.CompleteRequest +import io.modelcontextprotocol.kotlin.sdk.CompleteResult +import io.modelcontextprotocol.kotlin.sdk.EmptyRequestResult +import io.modelcontextprotocol.kotlin.sdk.GetPromptRequest +import io.modelcontextprotocol.kotlin.sdk.GetPromptResult +import io.modelcontextprotocol.kotlin.sdk.Implementation +import io.modelcontextprotocol.kotlin.sdk.InitializeRequest +import io.modelcontextprotocol.kotlin.sdk.InitializeResult +import io.modelcontextprotocol.kotlin.sdk.InitializedNotification +import io.modelcontextprotocol.kotlin.sdk.LATEST_PROTOCOL_VERSION +import io.modelcontextprotocol.kotlin.sdk.ListPromptsRequest +import io.modelcontextprotocol.kotlin.sdk.ListPromptsResult +import io.modelcontextprotocol.kotlin.sdk.ListResourceTemplatesRequest +import io.modelcontextprotocol.kotlin.sdk.ListResourceTemplatesResult +import io.modelcontextprotocol.kotlin.sdk.ListResourcesRequest +import io.modelcontextprotocol.kotlin.sdk.ListResourcesResult +import io.modelcontextprotocol.kotlin.sdk.ListToolsRequest +import io.modelcontextprotocol.kotlin.sdk.ListToolsResult +import io.modelcontextprotocol.kotlin.sdk.LoggingLevel import io.modelcontextprotocol.kotlin.sdk.LoggingMessageNotification.SetLevelRequest +import io.modelcontextprotocol.kotlin.sdk.Method +import io.modelcontextprotocol.kotlin.sdk.PingRequest +import io.modelcontextprotocol.kotlin.sdk.ReadResourceRequest +import io.modelcontextprotocol.kotlin.sdk.ReadResourceResult +import io.modelcontextprotocol.kotlin.sdk.RootsListChangedNotification +import io.modelcontextprotocol.kotlin.sdk.SUPPORTED_PROTOCOL_VERSIONS +import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.SubscribeRequest +import io.modelcontextprotocol.kotlin.sdk.UnsubscribeRequest import io.modelcontextprotocol.kotlin.sdk.shared.Protocol import io.modelcontextprotocol.kotlin.sdk.shared.ProtocolOptions import io.modelcontextprotocol.kotlin.sdk.shared.RequestOptions @@ -27,7 +59,7 @@ public class ClientOptions( * An MCP client on top of a pluggable transport. * * The client automatically performs the initialization handshake with the server when [connect] is called. - * After initialization, [severCapabilities] and [serverVersion] provide details about the connected server. + * After initialization, [serverCapabilities] and [serverVersion] provide details about the connected server. * * You can extend this class with custom request/notification/result types if needed. * @@ -135,9 +167,7 @@ public open class Client( Method.Defined.ResourcesUnsubscribe, -> { val resCaps = serverCapabilities?.resources - if (resCaps == null) { - throw IllegalStateException("Server does not support resources (required for $method)") - } + ?: error("Server does not support resources (required for $method)") if (method == Method.Defined.ResourcesSubscribe && resCaps.subscribe != true) { throw IllegalStateException( diff --git a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt index 335e9a63..e7721ac1 100644 --- a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt +++ b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt @@ -1,14 +1,29 @@ package io.modelcontextprotocol.kotlin.sdk.client -import io.ktor.client.* -import io.ktor.client.plugins.sse.* -import io.ktor.client.request.* -import io.ktor.client.statement.* -import io.ktor.http.* +import io.ktor.client.HttpClient +import io.ktor.client.plugins.sse.ClientSSESession +import io.ktor.client.plugins.sse.sseSession +import io.ktor.client.request.HttpRequestBuilder +import io.ktor.client.request.post +import io.ktor.client.request.setBody +import io.ktor.client.statement.bodyAsText +import io.ktor.http.ContentType +import io.ktor.http.HttpHeaders +import io.ktor.http.Url +import io.ktor.http.append +import io.ktor.http.isSuccess import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport import io.modelcontextprotocol.kotlin.sdk.shared.McpJson -import kotlinx.coroutines.* +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.cancelAndJoin +import kotlinx.coroutines.launch import kotlinx.serialization.encodeToString import kotlin.concurrent.atomics.AtomicBoolean import kotlin.concurrent.atomics.ExperimentalAtomicApi @@ -44,7 +59,7 @@ public class SseClientTransport( } override suspend fun start() { - if (!initialized.compareAndSet(false, true)) { + if (!initialized.compareAndSet(expectedValue = false, newValue = true)) { error( "SSEClientTransport already started! " + "If using Client class, note that connect() calls start() automatically.", diff --git a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt index 77acc058..8ffbb752 100644 --- a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt +++ b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport.kt @@ -2,13 +2,25 @@ package io.modelcontextprotocol.kotlin.sdk.client import io.github.oshai.kotlinlogging.KotlinLogging import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.internal.IODispatcher import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport import io.modelcontextprotocol.kotlin.sdk.shared.ReadBuffer import io.modelcontextprotocol.kotlin.sdk.shared.serializeMessage -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.consumeEach -import kotlinx.io.* +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import kotlinx.io.Buffer +import kotlinx.io.Sink +import kotlinx.io.Source +import kotlinx.io.buffered +import kotlinx.io.readByteArray +import kotlinx.io.writeString import kotlin.concurrent.atomics.AtomicBoolean import kotlin.concurrent.atomics.ExperimentalAtomicApi import kotlin.coroutines.CoroutineContext @@ -28,7 +40,7 @@ public class StdioClientTransport( private val output: Sink ) : AbstractTransport() { private val logger = KotlinLogging.logger {} - private val ioCoroutineContext: CoroutineContext = Dispatchers.IO + private val ioCoroutineContext: CoroutineContext = IODispatcher private val scope by lazy { CoroutineScope(ioCoroutineContext + SupervisorJob()) } @@ -38,7 +50,7 @@ public class StdioClientTransport( private val readBuffer = ReadBuffer() override suspend fun start() { - if (!initialized.compareAndSet(false, true)) { + if (!initialized.compareAndSet(expectedValue = false, newValue = true)) { error("StdioClientTransport already started!") } @@ -100,7 +112,7 @@ public class StdioClientTransport( } override suspend fun close() { - if (!initialized.compareAndSet(true, false)) { + if (!initialized.compareAndSet(expectedValue = true, newValue = false)) { error("Transport is already closed") } job?.cancelAndJoin() diff --git a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketClientTransport.kt b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketClientTransport.kt index 0692557e..45719073 100644 --- a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketClientTransport.kt +++ b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketClientTransport.kt @@ -1,10 +1,11 @@ package io.modelcontextprotocol.kotlin.sdk.client -import io.ktor.client.* -import io.ktor.client.plugins.websocket.* -import io.ktor.client.request.* -import io.ktor.http.* -import io.ktor.websocket.* +import io.ktor.client.HttpClient +import io.ktor.client.plugins.websocket.webSocketSession +import io.ktor.client.request.HttpRequestBuilder +import io.ktor.client.request.header +import io.ktor.http.HttpHeaders +import io.ktor.websocket.WebSocketSession import io.modelcontextprotocol.kotlin.sdk.shared.MCP_SUBPROTOCOL import io.modelcontextprotocol.kotlin.sdk.shared.WebSocketMcpTransport import kotlin.properties.Delegates diff --git a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/internal/utils.kt b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/internal/utils.kt new file mode 100644 index 00000000..49436f93 --- /dev/null +++ b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/internal/utils.kt @@ -0,0 +1,5 @@ +package io.modelcontextprotocol.kotlin.sdk.internal + +import kotlinx.coroutines.CoroutineDispatcher + +internal expect val IODispatcher: CoroutineDispatcher \ No newline at end of file diff --git a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt index 572cd355..f3683497 100644 --- a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt +++ b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt @@ -1,12 +1,19 @@ package io.modelcontextprotocol.kotlin.sdk.server import io.github.oshai.kotlinlogging.KotlinLogging -import io.ktor.http.* -import io.ktor.server.application.* -import io.ktor.server.response.* -import io.ktor.server.routing.* -import io.ktor.server.sse.* -import io.ktor.util.collections.* +import io.ktor.http.HttpStatusCode +import io.ktor.server.application.Application +import io.ktor.server.application.install +import io.ktor.server.response.respond +import io.ktor.server.routing.Routing +import io.ktor.server.routing.RoutingContext +import io.ktor.server.routing.post +import io.ktor.server.routing.route +import io.ktor.server.routing.routing +import io.ktor.server.sse.SSE +import io.ktor.server.sse.ServerSSESession +import io.ktor.server.sse.sse +import io.ktor.util.collections.ConcurrentMap import io.ktor.utils.io.KtorDsl private val logger = KotlinLogging.logger {} diff --git a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt index 67ac4344..c5b59702 100644 --- a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt +++ b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt @@ -1,10 +1,13 @@ package io.modelcontextprotocol.kotlin.sdk.server -import io.ktor.http.* -import io.ktor.server.application.* -import io.ktor.server.request.* -import io.ktor.server.response.* -import io.ktor.server.sse.* +import io.ktor.http.ContentType +import io.ktor.http.HttpStatusCode +import io.ktor.http.encodeURLPath +import io.ktor.server.application.ApplicationCall +import io.ktor.server.request.contentType +import io.ktor.server.request.receiveText +import io.ktor.server.response.respondText +import io.ktor.server.sse.ServerSSESession import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport import io.modelcontextprotocol.kotlin.sdk.shared.McpJson @@ -41,8 +44,8 @@ public class SseServerTransport( * This should be called when a GET request is made to establish the SSE stream. */ override suspend fun start() { - if (!initialized.compareAndSet(false, true)) { - throw error("SSEServerTransport already started! If using Server class, note that connect() calls start() automatically.") + if (!initialized.compareAndSet(expectedValue = false, newValue = true)) { + error("SSEServerTransport already started! If using Server class, note that connect() calls start() automatically.") } // Send the endpoint event @@ -114,7 +117,7 @@ public class SseServerTransport( override suspend fun send(message: JSONRPCMessage) { if (!initialized.load()) { - throw error("Not connected") + error("Not connected") } session.send( diff --git a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt index ae622d28..be1b1f03 100644 --- a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt +++ b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt @@ -1,7 +1,45 @@ package io.modelcontextprotocol.kotlin.sdk.server import io.github.oshai.kotlinlogging.KotlinLogging -import io.modelcontextprotocol.kotlin.sdk.* +import io.modelcontextprotocol.kotlin.sdk.CallToolRequest +import io.modelcontextprotocol.kotlin.sdk.CallToolResult +import io.modelcontextprotocol.kotlin.sdk.ClientCapabilities +import io.modelcontextprotocol.kotlin.sdk.CreateMessageRequest +import io.modelcontextprotocol.kotlin.sdk.CreateMessageResult +import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject +import io.modelcontextprotocol.kotlin.sdk.EmptyRequestResult +import io.modelcontextprotocol.kotlin.sdk.GetPromptRequest +import io.modelcontextprotocol.kotlin.sdk.GetPromptResult +import io.modelcontextprotocol.kotlin.sdk.Implementation +import io.modelcontextprotocol.kotlin.sdk.InitializeRequest +import io.modelcontextprotocol.kotlin.sdk.InitializeResult +import io.modelcontextprotocol.kotlin.sdk.InitializedNotification +import io.modelcontextprotocol.kotlin.sdk.LATEST_PROTOCOL_VERSION +import io.modelcontextprotocol.kotlin.sdk.ListPromptsRequest +import io.modelcontextprotocol.kotlin.sdk.ListPromptsResult +import io.modelcontextprotocol.kotlin.sdk.ListResourceTemplatesRequest +import io.modelcontextprotocol.kotlin.sdk.ListResourceTemplatesResult +import io.modelcontextprotocol.kotlin.sdk.ListResourcesRequest +import io.modelcontextprotocol.kotlin.sdk.ListResourcesResult +import io.modelcontextprotocol.kotlin.sdk.ListRootsRequest +import io.modelcontextprotocol.kotlin.sdk.ListRootsResult +import io.modelcontextprotocol.kotlin.sdk.ListToolsRequest +import io.modelcontextprotocol.kotlin.sdk.ListToolsResult +import io.modelcontextprotocol.kotlin.sdk.LoggingMessageNotification +import io.modelcontextprotocol.kotlin.sdk.Method +import io.modelcontextprotocol.kotlin.sdk.PingRequest +import io.modelcontextprotocol.kotlin.sdk.Prompt +import io.modelcontextprotocol.kotlin.sdk.PromptArgument +import io.modelcontextprotocol.kotlin.sdk.PromptListChangedNotification +import io.modelcontextprotocol.kotlin.sdk.ReadResourceRequest +import io.modelcontextprotocol.kotlin.sdk.ReadResourceResult +import io.modelcontextprotocol.kotlin.sdk.Resource +import io.modelcontextprotocol.kotlin.sdk.ResourceListChangedNotification +import io.modelcontextprotocol.kotlin.sdk.ResourceUpdatedNotification +import io.modelcontextprotocol.kotlin.sdk.SUPPORTED_PROTOCOL_VERSIONS +import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.Tool +import io.modelcontextprotocol.kotlin.sdk.ToolListChangedNotification import io.modelcontextprotocol.kotlin.sdk.shared.Protocol import io.modelcontextprotocol.kotlin.sdk.shared.ProtocolOptions import io.modelcontextprotocol.kotlin.sdk.shared.RequestOptions @@ -105,7 +143,7 @@ public open class Server( /** * Registers a callback to be invoked when the server has completed initialization. */ - public fun onInitalized(block: () -> Unit) { + public fun onInitialized(block: () -> Unit) { val old = _onInitialized _onInitialized = { old() @@ -133,7 +171,7 @@ public open class Server( } /** - * Registers a single tool. This tool can then be called by the client. + * Registers a single tool. The client can then call this tool. * * @param name The name of the tool. * @param description A human-readable description of what the tool does. @@ -228,7 +266,7 @@ public open class Server( } /** - * Registers a single prompt. The prompt can then be retrieved by the client. + * Registers a single prompt. The client can then retrieve the prompt. * * @param prompt A [Prompt] object describing the prompt. * @param promptProvider A suspend function that returns the prompt content when requested by the client. @@ -335,7 +373,7 @@ public open class Server( } /** - * Registers a single resource. The resource content can then be read by the client. + * Registers a single resource. The client can then read the resource content. * * @param uri The URI of the resource. * @param name A human-readable name for the resource. diff --git a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport.kt b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport.kt index a7e371a2..c515ddac 100644 --- a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport.kt +++ b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StdioServerTransport.kt @@ -2,12 +2,25 @@ package io.modelcontextprotocol.kotlin.sdk.server import io.github.oshai.kotlinlogging.KotlinLogging import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.internal.IODispatcher import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport import io.modelcontextprotocol.kotlin.sdk.shared.ReadBuffer import io.modelcontextprotocol.kotlin.sdk.shared.serializeMessage -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.NonCancellable +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.channels.Channel -import kotlinx.io.* +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlinx.io.Buffer +import kotlinx.io.Sink +import kotlinx.io.Source +import kotlinx.io.buffered +import kotlinx.io.readByteArray +import kotlinx.io.writeString import kotlin.concurrent.atomics.AtomicBoolean import kotlin.concurrent.atomics.ExperimentalAtomicApi import kotlin.coroutines.CoroutineContext @@ -29,7 +42,7 @@ public class StdioServerTransport( private var readingJob: Job? = null private var sendingJob: Job? = null - private val coroutineContext: CoroutineContext = Dispatchers.IO + SupervisorJob() + private val coroutineContext: CoroutineContext = IODispatcher + SupervisorJob() private val scope = CoroutineScope(coroutineContext) private val readChannel = Channel(Channel.UNLIMITED) private val writeChannel = Channel(Channel.UNLIMITED) @@ -113,15 +126,26 @@ public class StdioServerTransport( override suspend fun close() { if (!initialized.compareAndSet(expectedValue = true, newValue = false)) return - // Cancel reading job and close channel - readingJob?.cancel() // ToDO("was cancel and join") - sendingJob?.cancel() + withContext(NonCancellable) { + writeChannel.close() + sendingJob?.cancelAndJoin() - readChannel.close() - writeChannel.close() - readBuffer.clear() + runCatching { + inputStream.close() + }.onFailure { logger.warn(it) { "Failed to close stdin" } } - _onClose.invoke() + readingJob?.cancel() + + readChannel.close() + readBuffer.clear() + + runCatching { + outputWriter.flush() + outputWriter.close() + }.onFailure { logger.warn(it) { "Failed to close stdout" } } + + _onClose.invoke() + } } override suspend fun send(message: JSONRPCMessage) { diff --git a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt index dcb491e1..9301749b 100644 --- a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt +++ b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt @@ -1,7 +1,8 @@ package io.modelcontextprotocol.kotlin.sdk.server -import io.ktor.server.routing.* -import io.ktor.server.websocket.* +import io.ktor.server.routing.Route +import io.ktor.server.websocket.WebSocketServerSession +import io.ktor.server.websocket.webSocket import io.modelcontextprotocol.kotlin.sdk.Implementation import io.modelcontextprotocol.kotlin.sdk.LIB_VERSION import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities diff --git a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpServerTransport.kt b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpServerTransport.kt index 7209f69c..45cb4df9 100644 --- a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpServerTransport.kt +++ b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpServerTransport.kt @@ -1,7 +1,7 @@ package io.modelcontextprotocol.kotlin.sdk.server -import io.ktor.http.* -import io.ktor.server.websocket.* +import io.ktor.http.HttpHeaders +import io.ktor.server.websocket.WebSocketServerSession import io.modelcontextprotocol.kotlin.sdk.shared.MCP_SUBPROTOCOL import io.modelcontextprotocol.kotlin.sdk.shared.WebSocketMcpTransport diff --git a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt index 256c6bd4..4cad7561 100644 --- a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt +++ b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt @@ -1,14 +1,35 @@ package io.modelcontextprotocol.kotlin.sdk.shared import io.github.oshai.kotlinlogging.KotlinLogging -import io.modelcontextprotocol.kotlin.sdk.* +import io.modelcontextprotocol.kotlin.sdk.CancelledNotification +import io.modelcontextprotocol.kotlin.sdk.EmptyRequestResult +import io.modelcontextprotocol.kotlin.sdk.ErrorCode +import io.modelcontextprotocol.kotlin.sdk.JSONRPCError +import io.modelcontextprotocol.kotlin.sdk.JSONRPCNotification +import io.modelcontextprotocol.kotlin.sdk.JSONRPCRequest +import io.modelcontextprotocol.kotlin.sdk.JSONRPCResponse +import io.modelcontextprotocol.kotlin.sdk.McpError +import io.modelcontextprotocol.kotlin.sdk.Method +import io.modelcontextprotocol.kotlin.sdk.Notification +import io.modelcontextprotocol.kotlin.sdk.PingRequest +import io.modelcontextprotocol.kotlin.sdk.Progress +import io.modelcontextprotocol.kotlin.sdk.ProgressNotification +import io.modelcontextprotocol.kotlin.sdk.Request +import io.modelcontextprotocol.kotlin.sdk.RequestId +import io.modelcontextprotocol.kotlin.sdk.RequestResult +import io.modelcontextprotocol.kotlin.sdk.fromJSON +import io.modelcontextprotocol.kotlin.sdk.toJSON import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Deferred import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.withTimeout import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.* +import kotlinx.serialization.json.ClassDiscriminatorMode +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.encodeToJsonElement import kotlinx.serialization.serializer import kotlin.reflect.KType import kotlin.reflect.typeOf @@ -47,7 +68,8 @@ public open class ProtocolOptions( * considered a logic error to mis-specify those. * * Currently, this defaults to false, for backwards compatibility with SDK versions - * that did not advertise capabilities correctly. In future, this will default to true. + * that did not advertise capabilities correctly. + * In the future, this will default to true. */ public var enforceStrictCapabilities: Boolean = false, @@ -118,7 +140,8 @@ public abstract class Protocol( /** * Callback for when an error occurs. * - * Note that errors are not necessarily fatal they are used for reporting any kind of exceptional condition out of band. + * Note that errors are not necessarily fatal they are used + * for reporting any kind of exceptional condition out of a band. */ public open fun onError(error: Throwable) {} diff --git a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBuffer.kt b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBuffer.kt index c901ed28..ddffaa99 100644 --- a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBuffer.kt +++ b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBuffer.kt @@ -1,6 +1,6 @@ package io.modelcontextprotocol.kotlin.sdk.shared -import io.ktor.utils.io.core.* +import io.ktor.utils.io.core.writeFully import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage import kotlinx.io.Buffer import kotlinx.io.indexOf diff --git a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Transport.kt b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Transport.kt index 514f3f26..ba460f94 100644 --- a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Transport.kt +++ b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Transport.kt @@ -4,7 +4,7 @@ import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage import kotlinx.coroutines.CompletableDeferred /** - * Describes the minimal contract for a MCP transport that a client or server can communicate over. + * Describes the minimal contract for MCP transport that a client or server can communicate over. */ public interface Transport { /** @@ -38,7 +38,7 @@ public interface Transport { * Callback for when an error occurs. * * Note that errors are not necessarily fatal; they are used for reporting any kind of - * exceptional condition out of band. + * exceptional condition out of a band. */ public fun onError(block: (Throwable) -> Unit) diff --git a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt index baf373a3..ff601ed3 100644 --- a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt +++ b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt @@ -1,9 +1,17 @@ package io.modelcontextprotocol.kotlin.sdk.shared -import io.ktor.websocket.* +import io.ktor.websocket.Frame +import io.ktor.websocket.WebSocketSession +import io.ktor.websocket.close +import io.ktor.websocket.readText import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.InternalCoroutinesApi +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.channels.ClosedReceiveChannelException +import kotlinx.coroutines.job +import kotlinx.coroutines.launch import kotlinx.serialization.encodeToString import kotlin.concurrent.atomics.AtomicBoolean import kotlin.concurrent.atomics.ExperimentalAtomicApi @@ -21,6 +29,7 @@ public abstract class WebSocketMcpTransport : AbstractTransport() { } private val initialized: AtomicBoolean = AtomicBoolean(false) + /** * The WebSocket session used for communication. */ @@ -32,7 +41,7 @@ public abstract class WebSocketMcpTransport : AbstractTransport() { protected abstract suspend fun initializeSession() override suspend fun start() { - if (!initialized.compareAndSet(false, true)) { + if (!initialized.compareAndSet(expectedValue = false, newValue = true)) { error( "WebSocketClientTransport already started! " + "If using Client class, note that connect() calls start() automatically.", diff --git a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt index d34e610d..c93fa941 100644 --- a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt +++ b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt @@ -13,6 +13,7 @@ import kotlinx.serialization.json.encodeToJsonElement import kotlin.concurrent.atomics.AtomicLong import kotlin.concurrent.atomics.ExperimentalAtomicApi import kotlin.concurrent.atomics.incrementAndFetch +import kotlin.jvm.JvmInline public const val LATEST_PROTOCOL_VERSION: String = "2024-11-05" @@ -184,7 +185,7 @@ internal fun JSONRPCNotification.fromJSON(): Notification { public sealed interface RequestResult : WithMeta /** - * An empty result for a request, containing optional metadata. + * An empty result for a request containing optional metadata. * * @param _meta Additional metadata for the response. Defaults to an empty JSON object. */ @@ -493,7 +494,8 @@ public class InitializedNotification : ClientNotification { /* Ping */ /** - * A ping, issued by either the server or the client, to check that the other party is still alive. The receiver must promptly respond, or else may be disconnected. + * A ping, issued by either the server or the client, to check that the other party is still alive. + * The receiver must promptly respond, or else it may be disconnected. */ @Serializable public class PingRequest : ServerRequest, ClientRequest { @@ -1492,6 +1494,7 @@ public class RootsListChangedNotification : ClientNotification { * @property message The error message. * @property data Additional error data as a JSON object. */ -public class McpError(public val code: Int, message: String, public val data: JsonObject = EmptyJsonObject) : Exception() { +public class McpError(public val code: Int, message: String, public val data: JsonObject = EmptyJsonObject) : + Exception() { override val message: String = "MCP error ${code}: $message" } diff --git a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.util.kt b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.util.kt index 8a4dcd25..67b6b9f3 100644 --- a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.util.kt +++ b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.util.kt @@ -10,7 +10,16 @@ import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.buildClassSerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder -import kotlinx.serialization.json.* +import kotlinx.serialization.json.JsonContentPolymorphicSerializer +import kotlinx.serialization.json.JsonDecoder +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonEncoder +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.long +import kotlinx.serialization.json.longOrNull private val logger = KotlinLogging.logger {} @@ -285,6 +294,7 @@ public class RequestIdSerializer : KSerializer { element.longOrNull != null -> RequestId.NumberId(element.long) else -> error("Invalid RequestId type") } + else -> error("Invalid RequestId format") } } diff --git a/src/jvmTest/kotlin/InMemoryTransport.kt b/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/InMemoryTransport.kt similarity index 96% rename from src/jvmTest/kotlin/InMemoryTransport.kt rename to src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/InMemoryTransport.kt index ed849e2b..cd17bfc7 100644 --- a/src/jvmTest/kotlin/InMemoryTransport.kt +++ b/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/InMemoryTransport.kt @@ -1,4 +1,5 @@ -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage +package io.modelcontextprotocol.kotlin.sdk + import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport /** diff --git a/src/commonTest/kotlin/ToolSerializationTest.kt b/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/ToolSerializationTest.kt similarity index 100% rename from src/commonTest/kotlin/ToolSerializationTest.kt rename to src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/ToolSerializationTest.kt diff --git a/src/jvmTest/kotlin/client/BaseTransportTest.kt b/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/BaseTransportTest.kt similarity index 97% rename from src/jvmTest/kotlin/client/BaseTransportTest.kt rename to src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/BaseTransportTest.kt index 1bd27f63..2c82ff72 100644 --- a/src/jvmTest/kotlin/client/BaseTransportTest.kt +++ b/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/BaseTransportTest.kt @@ -1,4 +1,4 @@ -package client +package io.modelcontextprotocol.kotlin.sdk.client import io.modelcontextprotocol.kotlin.sdk.InitializedNotification import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage diff --git a/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/InMemoryTransportTest.kt b/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/InMemoryTransportTest.kt new file mode 100644 index 00000000..6ab3feaf --- /dev/null +++ b/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/InMemoryTransportTest.kt @@ -0,0 +1,110 @@ +package io.modelcontextprotocol.kotlin.sdk.client + +import io.modelcontextprotocol.kotlin.sdk.InMemoryTransport +import io.modelcontextprotocol.kotlin.sdk.InitializedNotification +import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage +import io.modelcontextprotocol.kotlin.sdk.toJSON +import kotlinx.coroutines.test.runTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class InMemoryTransportTest { + private lateinit var clientTransport: InMemoryTransport + private lateinit var serverTransport: InMemoryTransport + + @BeforeTest + fun setUp() { + val (client, server) = InMemoryTransport.createLinkedPair() + clientTransport = client + serverTransport = server + } + + @Test + fun `should create linked pair`() { + assertNotNull(clientTransport) + assertNotNull(serverTransport) + } + + @Test + fun `should start without error`() = runTest { + clientTransport.start() + serverTransport.start() + // If no exception is thrown, the test passes + } + + @Test + fun `should send message from client to server`() = runTest { + val message = InitializedNotification() + + var receivedMessage: JSONRPCMessage? = null + serverTransport.onMessage { msg -> + receivedMessage = msg + } + + val rpcNotification = message.toJSON() + clientTransport.send(rpcNotification) + assertEquals(rpcNotification, receivedMessage) + } + + @Test + fun `should send message from server to client`() = runTest { + val message = InitializedNotification() + .toJSON() + + var receivedMessage: JSONRPCMessage? = null + clientTransport.onMessage { msg -> + receivedMessage = msg + } + + serverTransport.send(message) + assertEquals(message, receivedMessage) + } + + @Test + fun `should handle close`() = runTest { + var clientClosed = false + var serverClosed = false + + clientTransport.onClose { + clientClosed = true + } + + serverTransport.onClose { + serverClosed = true + } + + clientTransport.close() + assertTrue(clientClosed) + assertTrue(serverClosed) + } + + @Test + fun `should throw error when sending after close`() = runTest { + clientTransport.close() + + assertFailsWith { + clientTransport.send( + InitializedNotification().toJSON() + ) + } + } + + @Test + fun `should queue messages sent before start`() = runTest { + val message = InitializedNotification() + .toJSON() + + var receivedMessage: JSONRPCMessage? = null + serverTransport.onMessage { msg -> + receivedMessage = msg + } + + clientTransport.send(message) + serverTransport.start() + assertEquals(message, receivedMessage) + } +} diff --git a/src/jvmTest/kotlin/client/SseTransportTest.kt b/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt similarity index 71% rename from src/jvmTest/kotlin/client/SseTransportTest.kt rename to src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt index b056893c..fd4c3d69 100644 --- a/src/jvmTest/kotlin/client/SseTransportTest.kt +++ b/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt @@ -1,26 +1,25 @@ -package client +package io.modelcontextprotocol.kotlin.sdk.client -import io.ktor.client.* -import io.ktor.client.plugins.sse.* -import io.ktor.server.application.* -import io.ktor.server.cio.* -import io.ktor.server.engine.* -import io.ktor.server.routing.* +import io.ktor.client.HttpClient +import io.ktor.client.plugins.sse.SSE +import io.ktor.server.application.install +import io.ktor.server.cio.CIO +import io.ktor.server.engine.embeddedServer +import io.ktor.server.routing.post +import io.ktor.server.routing.routing import io.ktor.server.sse.sse import io.ktor.util.collections.ConcurrentMap -import kotlinx.coroutines.test.runTest -import io.modelcontextprotocol.kotlin.sdk.client.mcpSseTransport import io.modelcontextprotocol.kotlin.sdk.server.SseServerTransport import io.modelcontextprotocol.kotlin.sdk.server.mcpPostEndpoint import io.modelcontextprotocol.kotlin.sdk.server.mcpSseTransport -import org.junit.jupiter.api.Test - -private const val PORT = 8080 +import kotlinx.coroutines.test.runTest +import kotlin.test.Test class SseTransportTest : BaseTransportTest() { @Test fun `should start then close cleanly`() = runTest { - val server = embeddedServer(CIO, port = PORT) { + val port = 8080 + val server = embeddedServer(CIO, port = port) { install(io.ktor.server.sse.SSE) val transports = ConcurrentMap() routing { @@ -32,25 +31,26 @@ class SseTransportTest : BaseTransportTest() { mcpPostEndpoint(transports) } } - }.start(wait = false) + }.startSuspend(wait = false) val client = HttpClient { install(SSE) }.mcpSseTransport { url { host = "localhost" - port = PORT + this.port = port } } testClientOpenClose(client) - server.stop() + server.stopSuspend() } @Test fun `should read messages`() = runTest { - val server = embeddedServer(CIO, port = PORT) { + val port = 3003 + val server = embeddedServer(CIO, port = port) { install(io.ktor.server.sse.SSE) val transports = ConcurrentMap() routing { @@ -68,18 +68,18 @@ class SseTransportTest : BaseTransportTest() { mcpPostEndpoint(transports) } } - }.start(wait = false) + }.startSuspend(wait = false) val client = HttpClient { install(SSE) }.mcpSseTransport { url { host = "localhost" - port = PORT + this.port = port } } testClientRead(client) - server.stop() + server.stopSuspend() } } diff --git a/src/jvmTest/kotlin/client/TypesTest.kt b/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/TypesTest.kt similarity index 67% rename from src/jvmTest/kotlin/client/TypesTest.kt rename to src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/TypesTest.kt index f93b322e..1714ded6 100644 --- a/src/jvmTest/kotlin/client/TypesTest.kt +++ b/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/TypesTest.kt @@ -1,21 +1,23 @@ -package client +package io.modelcontextprotocol.kotlin.sdk.client import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage import io.modelcontextprotocol.kotlin.sdk.Request -import org.junit.jupiter.api.Test import io.modelcontextprotocol.kotlin.sdk.shared.McpJson +import kotlin.test.Test class TypesTest { @Test fun testRequestResult() { - val message = "{\"result\":{\"protocolVersion\":\"2024-11-05\",\"capabilities\":{\"tools\":{\"listChanged\":true},\"resources\":{}},\"serverInfo\":{\"name\":\"jetbrains/proxy\",\"version\":\"0.1.0\"}},\"jsonrpc\":\"2.0\",\"id\":1}" + val message = + "{\"result\":{\"protocolVersion\":\"2024-11-05\",\"capabilities\":{\"tools\":{\"listChanged\":true},\"resources\":{}},\"serverInfo\":{\"name\":\"jetbrains/proxy\",\"version\":\"0.1.0\"}},\"jsonrpc\":\"2.0\",\"id\":1}" McpJson.decodeFromString(message) } @Test fun testRequestError() { - val message = "{\"method\":\"initialize\", \"protocolVersion\":\"2024-11-05\",\"capabilities\":{\"experimental\":{},\"sampling\":{}},\"clientInfo\":{\"name\":\"test client\",\"version\":\"1.0\"},\"_meta\":{}}" + val message = + "{\"method\":\"initialize\", \"protocolVersion\":\"2024-11-05\",\"capabilities\":{\"experimental\":{},\"sampling\":{}},\"clientInfo\":{\"name\":\"test client\",\"version\":\"1.0\"},\"_meta\":{}}" McpJson.decodeFromString(message) } diff --git a/src/jvmTest/kotlin/client/WebSocketTransportTest.kt b/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketTransportTest.kt similarity index 77% rename from src/jvmTest/kotlin/client/WebSocketTransportTest.kt rename to src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketTransportTest.kt index 93f39591..57423b50 100644 --- a/src/jvmTest/kotlin/client/WebSocketTransportTest.kt +++ b/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketTransportTest.kt @@ -1,17 +1,16 @@ -package client +package io.modelcontextprotocol.kotlin.sdk.client -import io.ktor.server.testing.* -import io.ktor.server.websocket.* -import kotlinx.coroutines.CompletableDeferred -import io.modelcontextprotocol.kotlin.sdk.client.mcpWebSocketTransport -import org.junit.jupiter.api.Test +import io.ktor.server.testing.testApplication +import io.ktor.server.websocket.WebSockets import io.modelcontextprotocol.kotlin.sdk.server.mcpWebSocket import io.modelcontextprotocol.kotlin.sdk.server.mcpWebSocketTransport -import org.junit.jupiter.api.Disabled +import kotlinx.coroutines.CompletableDeferred +import kotlin.test.Ignore +import kotlin.test.Test class WebSocketTransportTest : BaseTransportTest() { @Test - @Disabled("Test disabled for investigation #17") + @Ignore // "Test disabled for investigation #17" fun `should start then close cleanly`() = testApplication { install(WebSockets) routing { @@ -26,7 +25,7 @@ class WebSocketTransportTest : BaseTransportTest() { } @Test - @Disabled("Test disabled for investigation #17") + @Ignore // "Test disabled for investigation #17" fun `should read messages`() = testApplication { val clientFinished = CompletableDeferred() diff --git a/src/jvmTest/kotlin/integration/SseIntegrationTest.kt b/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/SseIntegrationTest.kt similarity index 50% rename from src/jvmTest/kotlin/integration/SseIntegrationTest.kt rename to src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/SseIntegrationTest.kt index 741ea14a..fab2ee6e 100644 --- a/src/jvmTest/kotlin/integration/SseIntegrationTest.kt +++ b/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/SseIntegrationTest.kt @@ -1,9 +1,10 @@ -package integration +package io.modelcontextprotocol.kotlin.sdk.integration -import io.ktor.client.* -import io.ktor.client.plugins.sse.* -import io.ktor.server.cio.* -import io.ktor.server.engine.* +import io.ktor.client.HttpClient +import io.ktor.client.plugins.sse.SSE +import io.ktor.server.cio.CIOApplicationEngine +import io.ktor.server.engine.EmbeddedServer +import io.ktor.server.engine.embeddedServer import io.modelcontextprotocol.kotlin.sdk.Implementation import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.client.Client @@ -11,23 +12,33 @@ import io.modelcontextprotocol.kotlin.sdk.client.mcpSse import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions import io.modelcontextprotocol.kotlin.sdk.server.mcp -import kotlinx.coroutines.runBlocking -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertDoesNotThrow +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.withContext +import kotlin.test.Test +import kotlin.test.fail import io.ktor.client.engine.cio.CIO as ClientCIO import io.ktor.server.cio.CIO as ServerCIO class SseIntegrationTest { @Test - fun `client should be able to connect to sse server`() { // runTest will cause network timeout issues - runBlocking { - val serverEngine = initServer() - try { + fun `client should be able to connect to sse server`() = runTest { + val serverEngine = initServer() + try { + withContext(Dispatchers.Default) { assertDoesNotThrow { initClient() } - } finally { - // Make sure to stop the server - serverEngine.stop(1000, 2000) } + } finally { + // Make sure to stop the server + serverEngine.stopSuspend(1000, 2000) + } + } + + private inline fun assertDoesNotThrow(block: () -> T): T { + return try { + block() + } catch (e: Throwable) { + fail("Expected no exception, but got: $e") } } @@ -35,13 +46,13 @@ class SseIntegrationTest { return HttpClient(ClientCIO) { install(SSE) }.mcpSse("http://$URL:$PORT") } - private fun initServer(): EmbeddedServer { + private suspend fun initServer(): EmbeddedServer { val server = Server( Implementation(name = "sse-e2e-test", version = "1.0.0"), ServerOptions(capabilities = ServerCapabilities()), ) - return embeddedServer(ServerCIO, host = URL, port = PORT) { mcp { server } }.start(wait = false) + return embeddedServer(ServerCIO, host = URL, port = PORT) { mcp { server } }.startSuspend(wait = false) } companion object { diff --git a/src/jvmTest/kotlin/shared/ReadBufferTest.kt b/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBufferTest.kt similarity index 66% rename from src/jvmTest/kotlin/shared/ReadBufferTest.kt rename to src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBufferTest.kt index 850e2efb..6890aef6 100644 --- a/src/jvmTest/kotlin/shared/ReadBufferTest.kt +++ b/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBufferTest.kt @@ -1,21 +1,20 @@ -package shared +package io.modelcontextprotocol.kotlin.sdk.shared +import io.ktor.utils.io.charsets.Charsets +import io.ktor.utils.io.core.toByteArray import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage import io.modelcontextprotocol.kotlin.sdk.JSONRPCNotification import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json -import io.modelcontextprotocol.kotlin.sdk.shared.ReadBuffer -import io.modelcontextprotocol.kotlin.sdk.shared.serializeMessage -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNull -import org.junit.jupiter.api.Test -import java.nio.charset.StandardCharsets +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull class ReadBufferTest { private val testMessage: JSONRPCMessage = JSONRPCNotification(method = "foobar") - private val json = Json { - ignoreUnknownKeys = true + private val json = Json { + ignoreUnknownKeys = true encodeDefaults = true } @@ -29,14 +28,13 @@ class ReadBufferTest { fun `should only yield a message after a newline`() { val readBuffer = ReadBuffer() - // Append message without newline - val messageBytes = json.encodeToString(testMessage) - .toByteArray(StandardCharsets.UTF_8) + // Append message without a newline + val messageBytes = json.encodeToString(testMessage).encodeToByteArray() readBuffer.append(messageBytes) assertNull(readBuffer.readMessage()) - // Append newline and verify message is now available - readBuffer.append("\n".toByteArray(StandardCharsets.UTF_8)) + // Append a newline and verify message is now available + readBuffer.append("\n".encodeToByteArray()) assertEquals(testMessage, readBuffer.readMessage()) assertNull(readBuffer.readMessage()) } @@ -44,7 +42,7 @@ class ReadBufferTest { @Test fun `skip empty line`() { val readBuffer = ReadBuffer() - readBuffer.append("\n".toByteArray(StandardCharsets.UTF_8)) + readBuffer.append("\n".toByteArray()) assertNull(readBuffer.readMessage()) } diff --git a/src/iosMain/kotlin/io/modelcontextprotocol/kotlin/sdk/internal/utils.ios.kt b/src/iosMain/kotlin/io/modelcontextprotocol/kotlin/sdk/internal/utils.ios.kt new file mode 100644 index 00000000..17f6555d --- /dev/null +++ b/src/iosMain/kotlin/io/modelcontextprotocol/kotlin/sdk/internal/utils.ios.kt @@ -0,0 +1,8 @@ +package io.modelcontextprotocol.kotlin.sdk.internal + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO + +internal actual val IODispatcher: CoroutineDispatcher + get() = Dispatchers.IO \ No newline at end of file diff --git a/src/jsMain/kotlin/io/modelcontextprotocol/kotlin/sdk/internal/utils.js.kt b/src/jsMain/kotlin/io/modelcontextprotocol/kotlin/sdk/internal/utils.js.kt new file mode 100644 index 00000000..1ecad771 --- /dev/null +++ b/src/jsMain/kotlin/io/modelcontextprotocol/kotlin/sdk/internal/utils.js.kt @@ -0,0 +1,7 @@ +package io.modelcontextprotocol.kotlin.sdk.internal + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers + +internal actual val IODispatcher: CoroutineDispatcher + get() = Dispatchers.Default \ No newline at end of file diff --git a/src/jvmMain/java/io/modelcontextprotocol/kotlin/sdk/internal/utils.jvm.kt b/src/jvmMain/java/io/modelcontextprotocol/kotlin/sdk/internal/utils.jvm.kt new file mode 100644 index 00000000..2c44eec8 --- /dev/null +++ b/src/jvmMain/java/io/modelcontextprotocol/kotlin/sdk/internal/utils.jvm.kt @@ -0,0 +1,7 @@ +package io.modelcontextprotocol.kotlin.sdk.internal + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers + +internal actual val IODispatcher: CoroutineDispatcher + get() = Dispatchers.IO \ No newline at end of file diff --git a/src/jvmTest/kotlin/client/ClientTest.kt b/src/jvmTest/kotlin/client/ClientTest.kt index 4630a341..d63f27ed 100644 --- a/src/jvmTest/kotlin/client/ClientTest.kt +++ b/src/jvmTest/kotlin/client/ClientTest.kt @@ -5,9 +5,9 @@ import io.modelcontextprotocol.kotlin.sdk.CreateMessageRequest import io.modelcontextprotocol.kotlin.sdk.CreateMessageResult import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject import io.modelcontextprotocol.kotlin.sdk.Implementation -import InMemoryTransport import io.mockk.coEvery import io.mockk.spyk +import io.modelcontextprotocol.kotlin.sdk.InMemoryTransport import io.modelcontextprotocol.kotlin.sdk.InitializeRequest import io.modelcontextprotocol.kotlin.sdk.InitializeResult import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage @@ -486,10 +486,10 @@ class ClientTest { server.setRequestHandler(Method.Defined.ResourcesList) { _, extra -> // Simulate a delayed response - // Wait ~100ms unless cancelled + // Wait ~100ms unless canceled try { kotlinx.coroutines.withTimeout(100L) { - // Just delay here, if timeout is 0 on client side this won't return in time + // Just delay here, if timeout is 0 on the client side, this won't return in time kotlinx.coroutines.delay(100) } } catch (_: Exception) { diff --git a/src/jvmTest/kotlin/client/InMemoryTransportTest.kt b/src/jvmTest/kotlin/client/InMemoryTransportTest.kt deleted file mode 100644 index bfe01f60..00000000 --- a/src/jvmTest/kotlin/client/InMemoryTransportTest.kt +++ /dev/null @@ -1,122 +0,0 @@ -package client - -import InMemoryTransport -import io.modelcontextprotocol.kotlin.sdk.InitializedNotification -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage -import kotlinx.coroutines.runBlocking -import io.modelcontextprotocol.kotlin.sdk.toJSON -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertTrue - -class InMemoryTransportTest { - private lateinit var clientTransport: InMemoryTransport - private lateinit var serverTransport: InMemoryTransport - - @BeforeEach - fun setUp() { - val (client, server) = InMemoryTransport.createLinkedPair() - clientTransport = client - serverTransport = server - } - - @Test - fun `should create linked pair`() { - assertNotNull(clientTransport) - assertNotNull(serverTransport) - } - - @Test - fun `should start without error`() { - runBlocking { - clientTransport.start() - serverTransport.start() - // If no exception is thrown, the test passes - } - } - - @Test - fun `should send message from client to server`() { - runBlocking { - val message = InitializedNotification() - - var receivedMessage: JSONRPCMessage? = null - serverTransport.onMessage { msg -> - receivedMessage = msg - } - - val rpcNotification = message.toJSON() - clientTransport.send(rpcNotification) - assertEquals(rpcNotification, receivedMessage) - } - } - - @Test - fun `should send message from server to client`() { - runBlocking { - val message = InitializedNotification() - .toJSON() - - var receivedMessage: JSONRPCMessage? = null - clientTransport.onMessage { msg -> - receivedMessage = msg - } - - serverTransport.send(message) - assertEquals(message, receivedMessage) - } - } - - @Test - fun `should handle close`() { - runBlocking { - var clientClosed = false - var serverClosed = false - - clientTransport.onClose { - clientClosed = true - } - - serverTransport.onClose { - serverClosed = true - } - - clientTransport.close() - assertTrue(clientClosed) - assertTrue(serverClosed) - } - } - - @Test - fun `should throw error when sending after close`() { - runBlocking { - clientTransport.close() - - assertThrows { - clientTransport.send( - InitializedNotification().toJSON() - ) - } - } - } - - @Test - fun `should queue messages sent before start`() { - runBlocking { - val message = InitializedNotification() - .toJSON() - - var receivedMessage: JSONRPCMessage? = null - serverTransport.onMessage { msg -> - receivedMessage = msg - } - - clientTransport.send(message) - serverTransport.start() - assertEquals(message, receivedMessage) - } - } -} diff --git a/src/jvmTest/kotlin/client/StdioClientTransportTest.kt b/src/jvmTest/kotlin/client/StdioClientTransportTest.kt index 8663324c..15defaed 100644 --- a/src/jvmTest/kotlin/client/StdioClientTransportTest.kt +++ b/src/jvmTest/kotlin/client/StdioClientTransportTest.kt @@ -1,5 +1,6 @@ package client +import io.modelcontextprotocol.kotlin.sdk.client.BaseTransportTest import kotlinx.coroutines.test.runTest import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport import kotlinx.io.asSink diff --git a/src/jvmTest/kotlin/server/ServerTest.kt b/src/jvmTest/kotlin/server/ServerTest.kt index 10294b43..35e07741 100644 --- a/src/jvmTest/kotlin/server/ServerTest.kt +++ b/src/jvmTest/kotlin/server/ServerTest.kt @@ -1,35 +1,22 @@ package server -import io.modelcontextprotocol.kotlin.sdk.CallToolRequest import io.modelcontextprotocol.kotlin.sdk.CallToolResult -import io.modelcontextprotocol.kotlin.sdk.ClientCapabilities -import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject -import io.modelcontextprotocol.kotlin.sdk.GetPromptRequest import io.modelcontextprotocol.kotlin.sdk.GetPromptResult import io.modelcontextprotocol.kotlin.sdk.Implementation +import io.modelcontextprotocol.kotlin.sdk.InMemoryTransport import io.modelcontextprotocol.kotlin.sdk.Method import io.modelcontextprotocol.kotlin.sdk.Prompt import io.modelcontextprotocol.kotlin.sdk.PromptListChangedNotification -import io.modelcontextprotocol.kotlin.sdk.ReadResourceRequest import io.modelcontextprotocol.kotlin.sdk.ReadResourceResult -import io.modelcontextprotocol.kotlin.sdk.Resource import io.modelcontextprotocol.kotlin.sdk.ResourceListChangedNotification import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.TextContent +import io.modelcontextprotocol.kotlin.sdk.TextResourceContents import io.modelcontextprotocol.kotlin.sdk.Tool import io.modelcontextprotocol.kotlin.sdk.ToolListChangedNotification import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.client.ClientOptions -import io.modelcontextprotocol.kotlin.sdk.server.RegisteredPrompt -import io.modelcontextprotocol.kotlin.sdk.server.RegisteredResource -import io.modelcontextprotocol.kotlin.sdk.server.RegisteredTool import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions -import InMemoryTransport -import io.modelcontextprotocol.kotlin.sdk.PromptMessage -import io.modelcontextprotocol.kotlin.sdk.PromptMessageContent -import io.modelcontextprotocol.kotlin.sdk.Role -import io.modelcontextprotocol.kotlin.sdk.TextContent -import io.modelcontextprotocol.kotlin.sdk.TextResourceContents import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest diff --git a/src/jvmTest/kotlin/server/StdioServerTransportTest.kt b/src/jvmTest/kotlin/server/StdioServerTransportTest.kt index 521e0509..8c865aa2 100644 --- a/src/jvmTest/kotlin/server/StdioServerTransportTest.kt +++ b/src/jvmTest/kotlin/server/StdioServerTransportTest.kt @@ -89,7 +89,7 @@ class StdioServerTransportTest { val message = PingRequest().toJSON() - // Push message before the server started + // Push a message before the server started val serialized = serializeMessage(message) inputWriter.write(serialized) inputWriter.flush() diff --git a/src/wasmJsMain/kotlin/io/modelcontextprotocol/kotlin/sdk/internal/utils.wasmJs.kt b/src/wasmJsMain/kotlin/io/modelcontextprotocol/kotlin/sdk/internal/utils.wasmJs.kt new file mode 100644 index 00000000..1ecad771 --- /dev/null +++ b/src/wasmJsMain/kotlin/io/modelcontextprotocol/kotlin/sdk/internal/utils.wasmJs.kt @@ -0,0 +1,7 @@ +package io.modelcontextprotocol.kotlin.sdk.internal + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers + +internal actual val IODispatcher: CoroutineDispatcher + get() = Dispatchers.Default \ No newline at end of file