diff --git a/api/kotlin-sdk.api b/api/kotlin-sdk.api index 6b711675..e04795bb 100644 --- a/api/kotlin-sdk.api +++ b/api/kotlin-sdk.api @@ -95,9 +95,10 @@ public final class io/modelcontextprotocol/kotlin/sdk/CallToolRequest$Companion public final class io/modelcontextprotocol/kotlin/sdk/CallToolResult : io/modelcontextprotocol/kotlin/sdk/CallToolResultBase { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/CallToolResult$Companion; - public fun (Ljava/util/List;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Ljava/util/List;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun getContent ()Ljava/util/List; + public fun getStructuredContent ()Lkotlinx/serialization/json/JsonObject; public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun isError ()Ljava/lang/Boolean; } @@ -120,6 +121,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/CallToolResult$Companion { public abstract interface class io/modelcontextprotocol/kotlin/sdk/CallToolResultBase : io/modelcontextprotocol/kotlin/sdk/ServerResult { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/CallToolResultBase$Companion; public abstract fun getContent ()Ljava/util/List; + public abstract fun getStructuredContent ()Lkotlinx/serialization/json/JsonObject; public fun isError ()Ljava/lang/Boolean; } @@ -247,9 +249,10 @@ public final class io/modelcontextprotocol/kotlin/sdk/ClientResult$Companion { public final class io/modelcontextprotocol/kotlin/sdk/CompatibilityCallToolResult : io/modelcontextprotocol/kotlin/sdk/CallToolResultBase { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/CompatibilityCallToolResult$Companion; - public fun (Ljava/util/List;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)V - public synthetic fun (Ljava/util/List;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun getContent ()Ljava/util/List; + public fun getStructuredContent ()Lkotlinx/serialization/json/JsonObject; public final fun getToolResult ()Lkotlinx/serialization/json/JsonObject; public fun get_meta ()Lkotlinx/serialization/json/JsonObject; public fun isError ()Ljava/lang/Boolean; @@ -2553,18 +2556,20 @@ public final class io/modelcontextprotocol/kotlin/sdk/TextResourceContents$Compa public final class io/modelcontextprotocol/kotlin/sdk/Tool { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/Tool$Companion; - public fun (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;)V + public fun (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 final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/Tool$Input; - public final fun component4 ()Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;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;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/Tool; + public final fun component4 ()Lio/modelcontextprotocol/kotlin/sdk/Tool$Output; + public final fun component5 ()Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations; + public final fun copy (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;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 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 fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -2616,6 +2621,38 @@ public final class io/modelcontextprotocol/kotlin/sdk/Tool$Input$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/Tool$Output { + public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/Tool$Output$Companion; + public fun ()V + public fun (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;)V + public synthetic fun (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lkotlinx/serialization/json/JsonObject; + public final fun component2 ()Ljava/util/List; + public final fun copy (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;)Lio/modelcontextprotocol/kotlin/sdk/Tool$Output; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/Tool$Output; + public fun equals (Ljava/lang/Object;)Z + public final fun getProperties ()Lkotlinx/serialization/json/JsonObject; + public final fun getRequired ()Ljava/util/List; + public final fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class io/modelcontextprotocol/kotlin/sdk/Tool$Output$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/Tool$Output$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/Tool$Output; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;)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$Output$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class io/modelcontextprotocol/kotlin/sdk/ToolAnnotations { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations$Companion; public fun (Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;)V @@ -2977,8 +3014,9 @@ public class io/modelcontextprotocol/kotlin/sdk/server/Server : io/modelcontextp public final fun addResource (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function2;)V 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 (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;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;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)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;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;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V public final fun addTools (Ljava/util/List;)V protected fun assertCapabilityForMethod (Lio/modelcontextprotocol/kotlin/sdk/Method;)V protected fun assertNotificationCapability (Lio/modelcontextprotocol/kotlin/sdk/Method;)V 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 6f3a7c78..ecfaeba1 100644 --- a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt +++ b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt @@ -186,12 +186,30 @@ public open class Server( _onClose() } + /** + * Registers a single tool. The client can then call this tool. + * + * @param tool A [Tool] object describing the tool. + * @param handler A suspend function that handles executing the tool when called by the client. + * @throws IllegalStateException If the server does not support tools. + */ + public fun addTool(tool: Tool, handler: suspend (CallToolRequest) -> CallToolResult) { + if (capabilities.tools == null) { + logger.error { "Failed to add tool '${tool.name}': Server does not support tools capability" } + throw IllegalStateException("Server does not support tools capability. Enable it in ServerOptions.") + } + logger.info { "Registering tool: ${tool.name}" } + _tools.update { current -> current.put(tool.name, RegisteredTool(tool, handler)) } + } + /** * 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. * @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 handler A suspend function that handles executing the tool when called by the client. * @throws IllegalStateException If the server does not support tools. */ @@ -199,17 +217,12 @@ public open class Server( name: String, description: String, inputSchema: Tool.Input = Tool.Input(), + outputSchema: Tool.Output? = null, toolAnnotations: ToolAnnotations? = null, handler: suspend (CallToolRequest) -> CallToolResult ) { - if (capabilities.tools == null) { - logger.error { "Failed to add tool '$name': Server does not support tools capability" } - throw IllegalStateException("Server does not support tools capability. Enable it in ServerOptions.") - } - logger.info { "Registering tool: $name" } - _tools.update { current -> - current.put(name, RegisteredTool(Tool(name, description, inputSchema, toolAnnotations), handler)) - } + val tool = Tool(name, description, inputSchema, outputSchema, toolAnnotations) + addTool(tool, handler) } /** diff --git a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt index f84233e8..893a8d63 100644 --- a/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt +++ b/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt @@ -1105,7 +1105,10 @@ public data class Tool( * A JSON object defining the expected parameters for the tool. */ val inputSchema: Input, - + /** + * An optional JSON object defining the expected output schema for the tool. + */ + val outputSchema: Output?, /** * Optional additional tool information. */ @@ -1120,6 +1123,16 @@ public data class Tool( @EncodeDefault val type: String = "object" } + + @Serializable + public data class Output( + val properties: JsonObject = EmptyJsonObject, + val required: List? = null, + ) { + @OptIn(ExperimentalSerializationApi::class) + @EncodeDefault + val type: String = "object" + } } /** @@ -1149,6 +1162,7 @@ public class ListToolsResult( @Serializable public sealed interface CallToolResultBase : ServerResult { public val content: List + public val structuredContent: JsonObject? public val isError: Boolean? get() = false } @@ -1158,6 +1172,7 @@ public sealed interface CallToolResultBase : ServerResult { @Serializable public class CallToolResult( override val content: List, + override val structuredContent: JsonObject? = null, override val isError: Boolean? = false, override val _meta: JsonObject = EmptyJsonObject, ) : CallToolResultBase @@ -1168,6 +1183,7 @@ public class CallToolResult( @Serializable public class CompatibilityCallToolResult( override val content: List, + override val structuredContent: JsonObject? = null, override val isError: Boolean? = false, override val _meta: JsonObject = EmptyJsonObject, public val toolResult: JsonObject = EmptyJsonObject, diff --git a/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/ToolSerializationTest.kt b/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/ToolSerializationTest.kt index 3d53a18d..6e2ac447 100644 --- a/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/ToolSerializationTest.kt +++ b/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/ToolSerializationTest.kt @@ -25,6 +25,24 @@ class ToolSerializationTest { } }, "required": ["location"] + }, + "outputSchema": { + "type": "object", + "properties": { + "temperature": { + "type": "number", + "description": "Temperature in celsius" + }, + "conditions": { + "type": "string", + "description": "Weather conditions description" + }, + "humidity": { + "type": "number", + "description": "Humidity percentage" + } + }, + "required": ["temperature", "conditions", "humidity"] } } """.trimIndent() @@ -41,6 +59,23 @@ class ToolSerializationTest { }) }, required = listOf("location") + ), + outputSchema = Tool.Output( + properties = buildJsonObject { + put("temperature", buildJsonObject { + put("type", JsonPrimitive("number")) + put("description", JsonPrimitive("Temperature in celsius")) + }) + put("conditions", buildJsonObject { + put("type", JsonPrimitive("string")) + put("description", JsonPrimitive("Weather conditions description")) + }) + put("humidity", buildJsonObject { + put("type", JsonPrimitive("number")) + put("description", JsonPrimitive("Humidity percentage")) + }) + }, + required = listOf("temperature", "conditions", "humidity") ) ) diff --git a/src/jvmTest/kotlin/client/ClientTest.kt b/src/jvmTest/kotlin/client/ClientTest.kt index 20f89114..9e2ea055 100644 --- a/src/jvmTest/kotlin/client/ClientTest.kt +++ b/src/jvmTest/kotlin/client/ClientTest.kt @@ -587,7 +587,8 @@ class ClientTest { name = "testTool", description = "testTool description", annotations = null, - inputSchema = Tool.Input() + inputSchema = Tool.Input(), + outputSchema = null ) ), nextCursor = null )