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

(cherry picked from commit 1b255a2baf)
This commit is contained in:
Ilia Brovarnik 2026-02-16 22:42:35 +01:00 • committed by Konstantin Pavlov
commit 6969176e6c
2 changed files with 125 additions and 5 deletions

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