diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt index 1e5ca7ee..c6d18bcc 100644 --- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt +++ b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTest.kt @@ -113,7 +113,8 @@ internal class StreamableHttpClientTest : AbstractStreamableHttpClientTest() { } }, "required": ["temperature"] - } + }, + "_meta": {} } ] } @@ -124,10 +125,10 @@ internal class StreamableHttpClientTest : AbstractStreamableHttpClientTest() { val listToolsResult = client.listTools() - listToolsResult.tools shouldContain Tool( - name = "get_weather", - title = "Weather Information Provider", - description = "Get current weather information for a location", + listToolsResult.tools shouldContain Tool.build { + name = "get_weather" + title = "Weather Information Provider" + description = "Get current weather information for a location" inputSchema = Tool.Input( properties = buildJsonObject { putJsonObject("location") { @@ -136,7 +137,7 @@ internal class StreamableHttpClientTest : AbstractStreamableHttpClientTest() { } }, required = listOf("location"), - ), + ) outputSchema = Tool.Output( properties = buildJsonObject { putJsonObject("temperature") { @@ -145,9 +146,8 @@ internal class StreamableHttpClientTest : AbstractStreamableHttpClientTest() { } }, required = listOf("temperature"), - ), - annotations = null, - ) + ) + } mockMcp.mockUnsubscribeRequest(sessionId = sessionId) diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index d4d4b9d5..8789bcf8 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -2889,17 +2889,19 @@ public final class io/modelcontextprotocol/kotlin/sdk/TextResourceContents$Compa public final fun serializer ()Lkotlinx/serialization/KSerializer; } -public final class io/modelcontextprotocol/kotlin/sdk/Tool { +public final class io/modelcontextprotocol/kotlin/sdk/Tool : io/modelcontextprotocol/kotlin/sdk/WithMeta { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/Tool$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;)V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Ljava/lang/String; public final fun component4 ()Lio/modelcontextprotocol/kotlin/sdk/Tool$Input; public final fun component5 ()Lio/modelcontextprotocol/kotlin/sdk/Tool$Output; public final fun component6 ()Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;)Lio/modelcontextprotocol/kotlin/sdk/Tool; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/Tool;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/Tool; + public final fun component7 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/Tool; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/Tool;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/Tool; public fun equals (Ljava/lang/Object;)Z public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations; public final fun getDescription ()Ljava/lang/String; @@ -2907,6 +2909,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/Tool { public final fun getName ()Ljava/lang/String; public final fun getOutputSchema ()Lio/modelcontextprotocol/kotlin/sdk/Tool$Output; public final fun getTitle ()Ljava/lang/String; + public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -2922,7 +2925,27 @@ public final synthetic class io/modelcontextprotocol/kotlin/sdk/Tool$$serializer public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/Tool$Builder { + public fun ()V + public final fun build ()Lio/modelcontextprotocol/kotlin/sdk/Tool; + public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations; + public final fun getDescription ()Ljava/lang/String; + public final fun getInputSchema ()Lio/modelcontextprotocol/kotlin/sdk/Tool$Input; + public final fun getName ()Ljava/lang/String; + public final fun getOutputSchema ()Lio/modelcontextprotocol/kotlin/sdk/Tool$Output; + public final fun getTitle ()Ljava/lang/String; + public final fun get_meta ()Lkotlinx/serialization/json/JsonObject; + public final fun setAnnotations (Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;)V + public final fun setDescription (Ljava/lang/String;)V + public final fun setInputSchema (Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;)V + public final fun setName (Ljava/lang/String;)V + public final fun setOutputSchema (Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;)V + public final fun setTitle (Ljava/lang/String;)V + public final fun set_meta (Lkotlinx/serialization/json/JsonObject;)V +} + public final class io/modelcontextprotocol/kotlin/sdk/Tool$Companion { + public final fun build (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/Tool; public final fun serializer ()Lkotlinx/serialization/KSerializer; } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt index 927967af..8dd56657 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt @@ -1242,7 +1242,12 @@ public data class Tool( * Optional additional tool information. */ val annotations: ToolAnnotations?, -) { + + /** + * Optional metadata for the tool. + */ + override val _meta: JsonObject = EmptyJsonObject, +) : WithMeta { @Serializable public data class Input(val properties: JsonObject = EmptyJsonObject, val required: List? = null) { @OptIn(ExperimentalSerializationApi::class) @@ -1256,6 +1261,32 @@ public data class Tool( @EncodeDefault val type: String = "object" } + + public class Builder { + public var name: String? = null + public var title: String? = null + public var description: String? = null + public var inputSchema: Input = Input() + public var outputSchema: Output? = null + public var annotations: ToolAnnotations? = null + + @Suppress("PropertyName") + public var _meta: JsonObject = EmptyJsonObject + + public fun build(): Tool = Tool( + name = requireNotNull(name) { "Tool name is required" }, + title = title, + description = description, + inputSchema = inputSchema, + outputSchema = outputSchema, + annotations = annotations, + _meta = _meta, + ) + } + + public companion object { + public fun build(builder: Builder.() -> Unit): Tool = Builder().apply(builder).build() + } } /** diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/RequestSerializerTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/RequestSerializerTest.kt index 14f67794..96a74dc0 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/RequestSerializerTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/RequestSerializerTest.kt @@ -75,7 +75,8 @@ class RequestSerializerTest { "inputSchema": { "type": "object", "properties": {} - } + }, + "_meta": {} } ] }""" diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/ToolSerializationTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/ToolSerializationTest.kt index 0664351f..db828fe4 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/ToolSerializationTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/ToolSerializationTest.kt @@ -3,6 +3,7 @@ package io.modelcontextprotocol.kotlin.sdk import io.kotest.assertions.json.shouldEqualJson import io.modelcontextprotocol.kotlin.sdk.shared.McpJson import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.buildJsonObject import kotlin.test.Test @@ -44,6 +45,9 @@ class ToolSerializationTest { } }, "required": ["temperature", "conditions", "humidity"] + }, + "_meta": { + "_for_test_only": true } } """.trimIndent() @@ -91,6 +95,9 @@ class ToolSerializationTest { }, required = listOf("temperature", "conditions", "humidity"), ), + _meta = buildJsonObject { + put("_for_test_only", JsonPrimitive(true)) + }, ) //region Serialize @@ -411,6 +418,7 @@ class ToolSerializationTest { name: String = "get_weather", title: String? = null, outputSchema: String? = null, + @Suppress("LocalVariableName") _meta: String? = null, ): String { val stringBuilder = StringBuilder() @@ -453,6 +461,14 @@ class ToolSerializationTest { ) } + stringBuilder + .appendLine(",") + .append( + """ + "_meta": ${_meta ?: "{}"} + """.trimIndent(), + ) + stringBuilder .appendLine() .appendLine("}") @@ -464,12 +480,13 @@ class ToolSerializationTest { name: String = "get_weather", title: String? = null, outputSchema: Tool.Output? = null, - ): Tool = Tool( - name = name, - title = title, - description = "Get the current weather in a given location", - annotations = null, - inputSchema = Tool.Input( + @Suppress("LocalVariableName") _meta: JsonObject? = null, + ): Tool = Tool.build { + this.name = name + title?.let { this.title = it } + this.description = "Get the current weather in a given location" + this.annotations = null + this.inputSchema = Tool.Input( properties = buildJsonObject { put( "location", @@ -480,9 +497,10 @@ class ToolSerializationTest { ) }, required = listOf("location"), - ), - outputSchema = outputSchema, - ) + ) + outputSchema?.let { this.outputSchema = it } + _meta?.let { this._meta = it } + } //endregion Private Methods } diff --git a/kotlin-sdk-server/api/kotlin-sdk-server.api b/kotlin-sdk-server/api/kotlin-sdk-server.api index 8ee5af28..d84e116e 100644 --- a/kotlin-sdk-server/api/kotlin-sdk-server.api +++ b/kotlin-sdk-server/api/kotlin-sdk-server.api @@ -56,8 +56,8 @@ public class io/modelcontextprotocol/kotlin/sdk/server/Server { public static synthetic fun addResource$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V public final fun addResources (Ljava/util/List;)V public final fun addTool (Lio/modelcontextprotocol/kotlin/sdk/Tool;Lkotlin/jvm/functions/Function2;)V - public final fun addTool (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;Lkotlin/jvm/functions/Function2;)V - public static synthetic fun addTool$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V + public final fun addTool (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/functions/Function2;)V + public static synthetic fun addTool$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;Lkotlinx/serialization/json/JsonObject;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V public final fun addTools (Ljava/util/List;)V public final fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun connect (Lio/modelcontextprotocol/kotlin/sdk/shared/Transport;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt index 18283e47..db900895 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt @@ -32,6 +32,7 @@ import kotlinx.collections.immutable.minus import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.toPersistentSet +import kotlinx.serialization.json.JsonObject private val logger = KotlinLogging.logger {} @@ -217,6 +218,7 @@ public open class Server( * @param inputSchema The expected input schema for the tool. * @param outputSchema The optional expected output schema for the tool. * @param toolAnnotations Optional additional tool information. + * @param _meta Optional metadata as a [JsonObject]. * @param handler A suspend function that handles executing the tool when called by the client. * @throws IllegalStateException If the server does not support tools. */ @@ -227,9 +229,18 @@ public open class Server( title: String? = null, outputSchema: Tool.Output? = null, toolAnnotations: ToolAnnotations? = null, + @Suppress("LocalVariableName") _meta: JsonObject? = null, handler: suspend (CallToolRequest) -> CallToolResult, ) { - val tool = Tool(name, title, description, inputSchema, outputSchema, toolAnnotations) + val tool = Tool.build { + this.name = name + this.description = description + this.inputSchema = inputSchema + title?.let { this.title = it } + outputSchema?.let { this.outputSchema = it } + toolAnnotations?.let { this.annotations = it } + _meta?.let { this._meta = it } + } addTool(tool, handler) } diff --git a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt index d132ae5f..daa6cea3 100644 --- a/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt +++ b/kotlin-sdk-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/ClientTest.kt @@ -614,14 +614,14 @@ class ClientTest { val serverListToolsResult = ListToolsResult( tools = listOf( - Tool( - name = "testTool", - title = "testTool title", - description = "testTool description", - annotations = null, - inputSchema = Tool.Input(), - outputSchema = null, - ), + Tool.build { + name = "testTool" + title = "testTool title" + description = "testTool description" + annotations = null + inputSchema = Tool.Input() + outputSchema = null + }, ), nextCursor = null, )