From 1b255a2baf957f68423751b05fabded1399ca510 Mon Sep 17 00:00:00 2001 From: Ilia Brovarnik Date: Mon, 16 Feb 2026 22:42:35 +0100 Subject: [PATCH] Add $defs to ToolSchema and tests (#526) Add $defs support in ToolSchema and update tests. Motivation and Context Fix parsing when tool schemas include $defs. How Has This Been Tested? `./gradlew :kotlin-sdk-core:jvmTest --tests "io.modelcontextprotocol.kotlin.sdk.types.ToolsTest"` Breaking Changes No. Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update Checklist - [x] I have read the MCP Documentation - [x] My code follows the repository's style guidelines - [x] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed Additional context N/A --- kotlin-sdk-core/api/kotlin-sdk-core.api | 10 +- .../kotlin/sdk/types/tools.kt | 8 +- .../kotlin/sdk/types/ToolsTest.kt | 112 ++++++++++++++++++ 3 files changed, 125 insertions(+), 5 deletions(-) diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index a5b581e..0607df5 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -4525,13 +4525,15 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ToolListChangedNotif public final class io/modelcontextprotocol/kotlin/sdk/types/ToolSchema { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema$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 fun (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)V + public synthetic fun (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;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/types/ToolSchema; - public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema; + public final fun component3 ()Lkotlinx/serialization/json/JsonObject; + public final fun copy (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema; + public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;Lkotlinx/serialization/json/JsonObject;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema; public fun equals (Ljava/lang/Object;)Z + public final fun getDefs ()Lkotlinx/serialization/json/JsonObject; public final fun getProperties ()Lkotlinx/serialization/json/JsonObject; public final fun getRequired ()Ljava/util/List; public final fun getType ()Ljava/lang/String; diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt index 767e724..b7aade5 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt @@ -78,9 +78,15 @@ public data class Tool( * @property type Always "object" for tool schemas. * @property properties Optional map of property names to their schema definitions. * @property required Optional list of property names that are required. + * @property defs Optional schema definitions available to references in [properties]. Serialized as `$defs`. */ @Serializable -public data class ToolSchema(val properties: JsonObject? = null, val required: List? = null) { +public data class ToolSchema( + val properties: JsonObject? = null, + val required: List? = null, + @SerialName("\$defs") + val defs: JsonObject? = null, +) { @OptIn(ExperimentalSerializationApi::class) @EncodeDefault val type: String = "object" diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ToolsTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ToolsTest.kt index a662e44..447df4f 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ToolsTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/ToolsTest.kt @@ -152,6 +152,57 @@ class ToolsTest { assertEquals("language", tool.meta?.get("category")?.jsonPrimitive?.content) } + @Test + fun `should serialize ToolSchema with defs`() { + val tool = toolWithDefs() + + val json = McpJson.encodeToString(tool) + + json shouldEqualJson toolWithDefsJson() + } + + @Test + fun `should deserialize ToolSchema with defs`() { + val json = $$""" + { + "name": "create-page", + "inputSchema": { + "type": "object", + "$defs": { + "parentRequest": { + "type": "object", + "properties": { + "page_id": { + "type": "string" + } + } + } + }, + "properties": { + "parent": { + "$ref": "#/$defs/parentRequest" + } + }, + "required": ["parent"] + } + } + """.trimIndent() + + val tool = McpJson.decodeFromString(json) + + val schema = tool.inputSchema + val defs = schema.defs + assertNotNull(defs) + val parentRequest = defs["parentRequest"]?.jsonObject + assertNotNull(parentRequest) + assertEquals("object", parentRequest["type"]?.jsonPrimitive?.content) + assertEquals( + $$"#/$defs/parentRequest", + schema.properties?.get("parent")?.jsonObject?.get($$"$ref")?.jsonPrimitive?.content, + ) + assertEquals(listOf("parent"), schema.required) + } + @Test fun `should serialize CallToolRequest with arguments`() { val request = CallToolRequest( @@ -520,4 +571,65 @@ class ToolsTest { } } """.trimIndent() + + private fun toolWithDefs(): Tool = Tool( + name = "create-page", + inputSchema = toolSchemaWithDefs(), + ) + + private fun toolSchemaWithDefs(): ToolSchema = ToolSchema( + properties = buildJsonObject { + put( + "parent", + buildJsonObject { + put($$"$ref", $$"#/$defs/parentRequest") + }, + ) + }, + required = listOf("parent"), + defs = buildJsonObject { + put( + "parentRequest", + buildJsonObject { + put("type", "object") + put( + "properties", + buildJsonObject { + put( + "page_id", + buildJsonObject { + put("type", "string") + }, + ) + }, + ) + }, + ) + }, + ) + + private fun toolWithDefsJson(): String = $$""" + { + "name": "create-page", + "inputSchema": { + "type": "object", + "$defs": { + "parentRequest": { + "type": "object", + "properties": { + "page_id": { + "type": "string" + } + } + } + }, + "properties": { + "parent": { + "$ref": "#/$defs/parentRequest" + } + }, + "required": ["parent"] + } + } + """.trimIndent() }