Skip to content

Commit 12f686f

Browse files
authored
feat(server): add support for tool structured content and output schema (#146)
Fixes #139
1 parent 675cc73 commit 12f686f

File tree

5 files changed

+123
-20
lines changed

5 files changed

+123
-20
lines changed

api/kotlin-sdk.api

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,10 @@ public final class io/modelcontextprotocol/kotlin/sdk/CallToolRequest$Companion
9595

9696
public final class io/modelcontextprotocol/kotlin/sdk/CallToolResult : io/modelcontextprotocol/kotlin/sdk/CallToolResultBase {
9797
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/CallToolResult$Companion;
98-
public fun <init> (Ljava/util/List;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;)V
99-
public synthetic fun <init> (Ljava/util/List;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
98+
public fun <init> (Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;)V
99+
public synthetic fun <init> (Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
100100
public fun getContent ()Ljava/util/List;
101+
public fun getStructuredContent ()Lkotlinx/serialization/json/JsonObject;
101102
public fun get_meta ()Lkotlinx/serialization/json/JsonObject;
102103
public fun isError ()Ljava/lang/Boolean;
103104
}
@@ -120,6 +121,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/CallToolResult$Companion {
120121
public abstract interface class io/modelcontextprotocol/kotlin/sdk/CallToolResultBase : io/modelcontextprotocol/kotlin/sdk/ServerResult {
121122
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/CallToolResultBase$Companion;
122123
public abstract fun getContent ()Ljava/util/List;
124+
public abstract fun getStructuredContent ()Lkotlinx/serialization/json/JsonObject;
123125
public fun isError ()Ljava/lang/Boolean;
124126
}
125127

@@ -247,9 +249,10 @@ public final class io/modelcontextprotocol/kotlin/sdk/ClientResult$Companion {
247249

248250
public final class io/modelcontextprotocol/kotlin/sdk/CompatibilityCallToolResult : io/modelcontextprotocol/kotlin/sdk/CallToolResultBase {
249251
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/CompatibilityCallToolResult$Companion;
250-
public fun <init> (Ljava/util/List;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)V
251-
public synthetic fun <init> (Ljava/util/List;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
252+
public fun <init> (Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)V
253+
public synthetic fun <init> (Ljava/util/List;Lkotlinx/serialization/json/JsonObject;Ljava/lang/Boolean;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
252254
public fun getContent ()Ljava/util/List;
255+
public fun getStructuredContent ()Lkotlinx/serialization/json/JsonObject;
253256
public final fun getToolResult ()Lkotlinx/serialization/json/JsonObject;
254257
public fun get_meta ()Lkotlinx/serialization/json/JsonObject;
255258
public fun isError ()Ljava/lang/Boolean;
@@ -2553,18 +2556,20 @@ public final class io/modelcontextprotocol/kotlin/sdk/TextResourceContents$Compa
25532556

25542557
public final class io/modelcontextprotocol/kotlin/sdk/Tool {
25552558
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/Tool$Companion;
2556-
public fun <init> (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;)V
2559+
public fun <init> (Ljava/lang/String;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;)V
25572560
public final fun component1 ()Ljava/lang/String;
25582561
public final fun component2 ()Ljava/lang/String;
25592562
public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;
2560-
public final fun component4 ()Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;
2561-
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;
2562-
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;
2563+
public final fun component4 ()Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;
2564+
public final fun component5 ()Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;
2565+
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;
2566+
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;
25632567
public fun equals (Ljava/lang/Object;)Z
25642568
public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations;
25652569
public final fun getDescription ()Ljava/lang/String;
25662570
public final fun getInputSchema ()Lio/modelcontextprotocol/kotlin/sdk/Tool$Input;
25672571
public final fun getName ()Ljava/lang/String;
2572+
public final fun getOutputSchema ()Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;
25682573
public fun hashCode ()I
25692574
public fun toString ()Ljava/lang/String;
25702575
}
@@ -2616,6 +2621,38 @@ public final class io/modelcontextprotocol/kotlin/sdk/Tool$Input$Companion {
26162621
public final fun serializer ()Lkotlinx/serialization/KSerializer;
26172622
}
26182623

2624+
public final class io/modelcontextprotocol/kotlin/sdk/Tool$Output {
2625+
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/Tool$Output$Companion;
2626+
public fun <init> ()V
2627+
public fun <init> (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;)V
2628+
public synthetic fun <init> (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
2629+
public final fun component1 ()Lkotlinx/serialization/json/JsonObject;
2630+
public final fun component2 ()Ljava/util/List;
2631+
public final fun copy (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;)Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;
2632+
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;
2633+
public fun equals (Ljava/lang/Object;)Z
2634+
public final fun getProperties ()Lkotlinx/serialization/json/JsonObject;
2635+
public final fun getRequired ()Ljava/util/List;
2636+
public final fun getType ()Ljava/lang/String;
2637+
public fun hashCode ()I
2638+
public fun toString ()Ljava/lang/String;
2639+
}
2640+
2641+
public final synthetic class io/modelcontextprotocol/kotlin/sdk/Tool$Output$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
2642+
public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/Tool$Output$$serializer;
2643+
public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
2644+
public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;
2645+
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
2646+
public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
2647+
public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/Tool$Output;)V
2648+
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
2649+
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
2650+
}
2651+
2652+
public final class io/modelcontextprotocol/kotlin/sdk/Tool$Output$Companion {
2653+
public final fun serializer ()Lkotlinx/serialization/KSerializer;
2654+
}
2655+
26192656
public final class io/modelcontextprotocol/kotlin/sdk/ToolAnnotations {
26202657
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/ToolAnnotations$Companion;
26212658
public fun <init> (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
29773014
public final fun addResource (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function2;)V
29783015
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
29793016
public final fun addResources (Ljava/util/List;)V
2980-
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
2981-
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
3017+
public final fun addTool (Lio/modelcontextprotocol/kotlin/sdk/Tool;Lkotlin/jvm/functions/Function2;)V
3018+
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
3019+
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
29823020
public final fun addTools (Ljava/util/List;)V
29833021
protected fun assertCapabilityForMethod (Lio/modelcontextprotocol/kotlin/sdk/Method;)V
29843022
protected fun assertNotificationCapability (Lio/modelcontextprotocol/kotlin/sdk/Method;)V

src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -186,30 +186,43 @@ public open class Server(
186186
_onClose()
187187
}
188188

189+
/**
190+
* Registers a single tool. The client can then call this tool.
191+
*
192+
* @param tool A [Tool] object describing the tool.
193+
* @param handler A suspend function that handles executing the tool when called by the client.
194+
* @throws IllegalStateException If the server does not support tools.
195+
*/
196+
public fun addTool(tool: Tool, handler: suspend (CallToolRequest) -> CallToolResult) {
197+
if (capabilities.tools == null) {
198+
logger.error { "Failed to add tool '${tool.name}': Server does not support tools capability" }
199+
throw IllegalStateException("Server does not support tools capability. Enable it in ServerOptions.")
200+
}
201+
logger.info { "Registering tool: ${tool.name}" }
202+
_tools.update { current -> current.put(tool.name, RegisteredTool(tool, handler)) }
203+
}
204+
189205
/**
190206
* Registers a single tool. The client can then call this tool.
191207
*
192208
* @param name The name of the tool.
193209
* @param description A human-readable description of what the tool does.
194210
* @param inputSchema The expected input schema for the tool.
211+
* @param outputSchema The optional expected output schema for the tool.
212+
* @param toolAnnotations Optional additional tool information.
195213
* @param handler A suspend function that handles executing the tool when called by the client.
196214
* @throws IllegalStateException If the server does not support tools.
197215
*/
198216
public fun addTool(
199217
name: String,
200218
description: String,
201219
inputSchema: Tool.Input = Tool.Input(),
220+
outputSchema: Tool.Output? = null,
202221
toolAnnotations: ToolAnnotations? = null,
203222
handler: suspend (CallToolRequest) -> CallToolResult
204223
) {
205-
if (capabilities.tools == null) {
206-
logger.error { "Failed to add tool '$name': Server does not support tools capability" }
207-
throw IllegalStateException("Server does not support tools capability. Enable it in ServerOptions.")
208-
}
209-
logger.info { "Registering tool: $name" }
210-
_tools.update { current ->
211-
current.put(name, RegisteredTool(Tool(name, description, inputSchema, toolAnnotations), handler))
212-
}
224+
val tool = Tool(name, description, inputSchema, outputSchema, toolAnnotations)
225+
addTool(tool, handler)
213226
}
214227

215228
/**

src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1105,7 +1105,10 @@ public data class Tool(
11051105
* A JSON object defining the expected parameters for the tool.
11061106
*/
11071107
val inputSchema: Input,
1108-
1108+
/**
1109+
* An optional JSON object defining the expected output schema for the tool.
1110+
*/
1111+
val outputSchema: Output?,
11091112
/**
11101113
* Optional additional tool information.
11111114
*/
@@ -1120,6 +1123,16 @@ public data class Tool(
11201123
@EncodeDefault
11211124
val type: String = "object"
11221125
}
1126+
1127+
@Serializable
1128+
public data class Output(
1129+
val properties: JsonObject = EmptyJsonObject,
1130+
val required: List<String>? = null,
1131+
) {
1132+
@OptIn(ExperimentalSerializationApi::class)
1133+
@EncodeDefault
1134+
val type: String = "object"
1135+
}
11231136
}
11241137

11251138
/**
@@ -1149,6 +1162,7 @@ public class ListToolsResult(
11491162
@Serializable
11501163
public sealed interface CallToolResultBase : ServerResult {
11511164
public val content: List<PromptMessageContent>
1165+
public val structuredContent: JsonObject?
11521166
public val isError: Boolean? get() = false
11531167
}
11541168

@@ -1158,6 +1172,7 @@ public sealed interface CallToolResultBase : ServerResult {
11581172
@Serializable
11591173
public class CallToolResult(
11601174
override val content: List<PromptMessageContent>,
1175+
override val structuredContent: JsonObject? = null,
11611176
override val isError: Boolean? = false,
11621177
override val _meta: JsonObject = EmptyJsonObject,
11631178
) : CallToolResultBase
@@ -1168,6 +1183,7 @@ public class CallToolResult(
11681183
@Serializable
11691184
public class CompatibilityCallToolResult(
11701185
override val content: List<PromptMessageContent>,
1186+
override val structuredContent: JsonObject? = null,
11711187
override val isError: Boolean? = false,
11721188
override val _meta: JsonObject = EmptyJsonObject,
11731189
public val toolResult: JsonObject = EmptyJsonObject,

src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/ToolSerializationTest.kt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,24 @@ class ToolSerializationTest {
2525
}
2626
},
2727
"required": ["location"]
28+
},
29+
"outputSchema": {
30+
"type": "object",
31+
"properties": {
32+
"temperature": {
33+
"type": "number",
34+
"description": "Temperature in celsius"
35+
},
36+
"conditions": {
37+
"type": "string",
38+
"description": "Weather conditions description"
39+
},
40+
"humidity": {
41+
"type": "number",
42+
"description": "Humidity percentage"
43+
}
44+
},
45+
"required": ["temperature", "conditions", "humidity"]
2846
}
2947
}
3048
""".trimIndent()
@@ -41,6 +59,23 @@ class ToolSerializationTest {
4159
})
4260
},
4361
required = listOf("location")
62+
),
63+
outputSchema = Tool.Output(
64+
properties = buildJsonObject {
65+
put("temperature", buildJsonObject {
66+
put("type", JsonPrimitive("number"))
67+
put("description", JsonPrimitive("Temperature in celsius"))
68+
})
69+
put("conditions", buildJsonObject {
70+
put("type", JsonPrimitive("string"))
71+
put("description", JsonPrimitive("Weather conditions description"))
72+
})
73+
put("humidity", buildJsonObject {
74+
put("type", JsonPrimitive("number"))
75+
put("description", JsonPrimitive("Humidity percentage"))
76+
})
77+
},
78+
required = listOf("temperature", "conditions", "humidity")
4479
)
4580
)
4681

src/jvmTest/kotlin/client/ClientTest.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -587,7 +587,8 @@ class ClientTest {
587587
name = "testTool",
588588
description = "testTool description",
589589
annotations = null,
590-
inputSchema = Tool.Input()
590+
inputSchema = Tool.Input(),
591+
outputSchema = null
591592
)
592593
), nextCursor = null
593594
)

0 commit comments

Comments
 (0)