New MCP Schema types (#386)

Implement the last version of the mcp schema and complete restructuring
of the mcp schema implementation from a monolithic `types.kt` file into
separate files.

- #158 
- #185 
- #353 
- #298 
- #248 

## Motivation and Context
The previous implementation of the mcp schema was outdated and contained
several issues. In addition, it stored all mcp types in a single large
file, which made maintenance and further development more difficult

## How Has This Been Tested?
All tests passed

## Breaking Changes
Yes. The new types are now located in a separate package. Additionally,
many types have new parameters and modified constructors

## Types of changes
- [x] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [x] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update

## Checklist
- [x] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [x] My code follows the repository's style guidelines
- [x] New and existing tests pass locally
- [x] I have added appropriate error handling
- [x] I have added or updated documentation as needed

## Additional context
to adopt the new structure:
**Before:**
```kotlin
  import io.modelcontextprotocol.kotlin.sdk.InitializeRequest
  import io.modelcontextprotocol.kotlin.sdk.Resource
```

**After:**
```kotlin
  import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest
  import io.modelcontextprotocol.kotlin.sdk.types.Resource
```
This commit is contained in:
Pavel Gorgulov 2025-11-11 12:25:23 +01:00 • committed by GitHub
commit 21393e7ce3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 13048 additions and 4230 deletions

View file

@ -9,6 +9,7 @@ import com.anthropic.models.messages.Tool
import com.anthropic.models.messages.ToolUnion
import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.ObjectMapper
import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject
import io.modelcontextprotocol.kotlin.sdk.Implementation
import io.modelcontextprotocol.kotlin.sdk.TextContent
import io.modelcontextprotocol.kotlin.sdk.client.Client
@ -76,7 +77,7 @@ class MCPClient : AutoCloseable {
.inputSchema(
Tool.InputSchema.builder()
.type(JsonValue.from(tool.inputSchema.type))
.properties(tool.inputSchema.properties.toJsonValue())
.properties(tool.inputSchema.properties?.toJsonValue() ?: EmptyJsonObject.toJsonValue())
.putAdditionalProperty("required", JsonValue.from(tool.inputSchema.required))
.build(),
)
@ -135,9 +136,11 @@ class MCPClient : AutoCloseable {
"""
"type": "tool_result",
"tool_name": $toolName,
"result": ${result?.content?.joinToString("\n") {
(it as TextContent).text ?: ""
}}
"result": ${
result?.content?.joinToString("\n") {
(it as TextContent).text ?: ""
}
}
""".trimIndent(),
)
.build(),

View file

@ -64,7 +64,6 @@ fun configureServer(): Server {
),
) { request ->
GetPromptResult(
"Description for ${request.name}",
messages = listOf(
PromptMessage(
role = Role.user,
@ -73,6 +72,7 @@ fun configureServer(): Server {
),
),
),
description = "Description for ${request.name}",
)
}

View file

@ -2,7 +2,6 @@ import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject
import io.modelcontextprotocol.kotlin.sdk.TextContent
import io.modelcontextprotocol.kotlin.sdk.client.Client
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.TestInstance
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertIs
@ -39,6 +38,6 @@ class SseServerIntegrationTest {
assertIs<TextContent>(content, "Tool result should be a text content")
assertEquals(expected = "Hello, world!", actual = content.text)
assertEquals(expected = "text", actual = content.type)
assertEquals(expected = "text", actual = "${content.type}".lowercase())
}
}

View file

@ -82,7 +82,7 @@ fun runMcpServer() {
required = listOf("state"),
),
) { request ->
val state = request.arguments["state"]?.jsonPrimitive?.content ?: return@addTool CallToolResult(
val state = request.arguments?.get("state")?.jsonPrimitive?.content ?: return@addTool CallToolResult(
content = listOf(TextContent("The 'state' parameter is required.")),
)
@ -109,8 +109,8 @@ fun runMcpServer() {
required = listOf("latitude", "longitude"),
),
) { request ->
val latitude = request.arguments["latitude"]?.jsonPrimitive?.doubleOrNull
val longitude = request.arguments["longitude"]?.jsonPrimitive?.doubleOrNull
val latitude = request.arguments?.get("latitude")?.jsonPrimitive?.doubleOrNull
val longitude = request.arguments?.get("longitude")?.jsonPrimitive?.doubleOrNull
if (latitude == null || longitude == null) {
return@addTool CallToolResult(
content = listOf(TextContent("The 'latitude' and 'longitude' parameters are required.")),

View file

@ -1,6 +1,5 @@
package io.modelcontextprotocol.sample.client
import io.modelcontextprotocol.kotlin.sdk.CallToolRequest
import io.modelcontextprotocol.kotlin.sdk.Implementation
import io.modelcontextprotocol.kotlin.sdk.TextContent
import io.modelcontextprotocol.kotlin.sdk.client.Client
@ -9,9 +8,6 @@ import kotlinx.coroutines.runBlocking
import kotlinx.io.asSink
import kotlinx.io.asSource
import kotlinx.io.buffered
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
fun main(): Unit = runBlocking {
val process = ProcessBuilder("java", "-jar", "./build/libs/weather-stdio-server-0.1.0-all.jar")
@ -33,12 +29,10 @@ fun main(): Unit = runBlocking {
println("Available Tools = $toolsList")
val weatherForecastResult = client.callTool(
CallToolRequest(
name = "get_forecast",
arguments = buildJsonObject {
put("latitude", JsonPrimitive(38.5816))
put("longitude", JsonPrimitive(-121.4944))
},
name = "get_forecast",
arguments = mapOf(
"latitude" to 38.5816,
"longitude" to -121.4944,
),
)?.content?.map { if (it is TextContent) it.text else it.toString() }
@ -46,10 +40,8 @@ fun main(): Unit = runBlocking {
val alertResult =
client.callTool(
CallToolRequest(
name = "get_alerts",
arguments = JsonObject(mapOf("state" to JsonPrimitive("TX"))),
),
name = "get_alert",
arguments = mapOf("state" to "TX"),
)?.content?.map { if (it is TextContent) it.text else it.toString() }
println("Alert Response = $alertResult")