Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ internal class StreamableHttpClientTest : AbstractStreamableHttpClientTest() {
}
},
"required": ["temperature"]
}
},
"_meta": {}
}
]
}
Expand All @@ -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") {
Expand All @@ -136,7 +137,7 @@ internal class StreamableHttpClientTest : AbstractStreamableHttpClientTest() {
}
},
required = listOf("location"),
),
)
outputSchema = Tool.Output(
properties = buildJsonObject {
putJsonObject("temperature") {
Expand All @@ -145,9 +146,8 @@ internal class StreamableHttpClientTest : AbstractStreamableHttpClientTest() {
}
},
required = listOf("temperature"),
),
annotations = null,
)
)
}

mockMcp.mockUnsubscribeRequest(sessionId = sessionId)

Expand Down
31 changes: 27 additions & 4 deletions kotlin-sdk-core/api/kotlin-sdk-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -2889,24 +2889,27 @@ 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 <init> (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 <init> (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 <init> (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;
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 fun get_meta ()Lkotlinx/serialization/json/JsonObject;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
Expand All @@ -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 <init> ()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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>? = null) {
@OptIn(ExperimentalSerializationApi::class)
Expand All @@ -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()
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ class RequestSerializerTest {
"inputSchema": {
"type": "object",
"properties": {}
}
},
"_meta": {}
}
]
}"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -44,6 +45,9 @@ class ToolSerializationTest {
}
},
"required": ["temperature", "conditions", "humidity"]
},
"_meta": {
"_for_test_only": true
}
}
""".trimIndent()
Expand Down Expand Up @@ -91,6 +95,9 @@ class ToolSerializationTest {
},
required = listOf("temperature", "conditions", "humidity"),
),
_meta = buildJsonObject {
put("_for_test_only", JsonPrimitive(true))
},
)

//region Serialize
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -453,6 +461,14 @@ class ToolSerializationTest {
)
}

stringBuilder
.appendLine(",")
.append(
"""
"_meta": ${_meta ?: "{}"}
""".trimIndent(),
)

stringBuilder
.appendLine()
.appendLine("}")
Expand All @@ -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",
Expand All @@ -480,9 +497,10 @@ class ToolSerializationTest {
)
},
required = listOf("location"),
),
outputSchema = outputSchema,
)
)
outputSchema?.let { this.outputSchema = it }
_meta?.let { this._meta = it }
}

//endregion Private Methods
}
4 changes: 2 additions & 2 deletions kotlin-sdk-server/api/kotlin-sdk-server.api
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}

Expand Down Expand Up @@ -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.
*/
Expand All @@ -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)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down