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
This commit is contained in:
Ilia Brovarnik 2026-02-16 22:42:35 +01:00 • committed by GitHub
commit 1b255a2baf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 125 additions and 5 deletions

View file

@ -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 <init> ()V
public fun <init> (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;)V
public synthetic fun <init> (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Lkotlinx/serialization/json/JsonObject;Ljava/util/List;Lkotlinx/serialization/json/JsonObject;)V
public synthetic fun <init> (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;

View file

@ -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<String>? = null) {
public data class ToolSchema(
val properties: JsonObject? = null,
val required: List<String>? = null,
@SerialName("\$defs")
val defs: JsonObject? = null,
) {
@OptIn(ExperimentalSerializationApi::class)
@EncodeDefault
val type: String = "object"

View file

@ -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<Tool>(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()
}