Deprecation cycle (#498)

- increase the deprecation level
- remove tests that use deprecated objects with deprecation level =
error

## Motivation and Context
deprecation lifecycle

## How Has This Been Tested?
-

## Breaking Changes
-

## Types of changes
- [ ] 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](https://modelcontextprotocol.io)
- [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
This commit is contained in:
Pavel Gorgulov 2026-02-04 20:04:11 +01:00 • committed by GitHub
commit 92823b987f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 224 additions and 8255 deletions

View file

@ -55,7 +55,7 @@ public typealias ProgressCallback = (Progress) -> Unit
@Deprecated(
message = "Use McpJson instead",
replaceWith = ReplaceWith("McpJson", "io.modelcontextprotocol.kotlin.sdk.types.McpJson"),
level = DeprecationLevel.WARNING,
level = DeprecationLevel.ERROR,
)
public val McpJson: Json by lazy {
Json {

View file

@ -7,7 +7,7 @@ import kotlinx.serialization.json.JsonObject
@Deprecated(
message = "Use `io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject` instead",
replaceWith = ReplaceWith("EmptyJsonObject", "io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject"),
level = DeprecationLevel.WARNING,
level = DeprecationLevel.ERROR,
)
public val EmptyJsonObject: JsonObject = io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject
@ -17,8 +17,9 @@ public val EmptyJsonObject: JsonObject = io.modelcontextprotocol.kotlin.sdk.type
@Deprecated(
message = "Use `CallToolResult.success` instead",
replaceWith = ReplaceWith("CallToolResult.Companion.success"),
level = DeprecationLevel.WARNING,
level = DeprecationLevel.ERROR,
)
@Suppress("DEPRECATION_ERROR")
public fun CallToolResult.ok(
content: String,
meta: JsonObject = EmptyJsonObject,
@ -31,8 +32,9 @@ public fun CallToolResult.ok(
@Deprecated(
message = "Use `CallToolResult.error` instead",
replaceWith = ReplaceWith("CallToolResult.Companion.error"),
level = DeprecationLevel.WARNING,
level = DeprecationLevel.ERROR,
)
@Suppress("DEPRECATION_ERROR")
public fun CallToolResult.error(
content: String,
meta: JsonObject = EmptyJsonObject,

View file

@ -33,6 +33,7 @@ public sealed interface WithMeta {
@Deprecated(
message = "Use 'meta' instead.",
replaceWith = ReplaceWith("meta"),
level = DeprecationLevel.ERROR,
)
@Suppress("PropertyName", "VariableNaming")
public val _meta: JsonObject

View file

@ -43,7 +43,7 @@ public data class CompleteRequest(override val params: CompleteRequestParams) :
@Deprecated(
message = "Will be removed in a future release.",
replaceWith = ReplaceWith(expression = "CompleteRequest(CompleteRequestParams(argument, ref, context, meta))"),
level = DeprecationLevel.WARNING,
level = DeprecationLevel.ERROR,
)
public constructor(
argument: Argument,

View file

@ -116,6 +116,7 @@ public data class GetPromptRequest(override val params: GetPromptRequestParams)
@Deprecated(
message = "Use constructor with GetPromptRequestParams instead",
replaceWith = ReplaceWith("GetPromptRequest(GetPromptRequestParams(name, arguments, meta))"),
level = DeprecationLevel.ERROR,
)
public constructor(
name: String,
@ -217,7 +218,7 @@ public data class ListPromptsRequest(override val params: PaginatedRequestParams
@Deprecated(
message = "Use constructor with ListPromptsParams instead",
replaceWith = ReplaceWith("ListPromptsRequest(ListPromptsParams(name, arguments, meta))"),
level = DeprecationLevel.WARNING,
level = DeprecationLevel.ERROR,
)
public constructor(
cursor: String?,

View file

@ -205,7 +205,7 @@ public data class ListResourcesRequest(override val params: PaginatedRequestPara
@Deprecated(
message = "Use the constructor with PaginatedRequestParams property instead",
replaceWith = ReplaceWith("ReadResourceRequest(PaginatedRequestParams(cursor, meta))"),
level = DeprecationLevel.WARNING,
level = DeprecationLevel.ERROR,
)
public constructor(
cursor: String?,
@ -265,7 +265,7 @@ public data class ReadResourceRequest(override val params: ReadResourceRequestPa
@Deprecated(
message = "Use the constructor with ReadResourceRequestParams property instead",
replaceWith = ReplaceWith("ReadResourceRequest(ReadResourceRequestParams(uri, meta))"),
level = DeprecationLevel.WARNING,
level = DeprecationLevel.ERROR,
)
public constructor(
uri: String,
@ -341,7 +341,7 @@ public data class SubscribeRequest(override val params: SubscribeRequestParams)
@Deprecated(
message = "Use the constructor with SubscribeRequestParams property instead",
replaceWith = ReplaceWith("ReadResourceRequest(SubscribeRequestParams(uri, meta))"),
level = DeprecationLevel.WARNING,
level = DeprecationLevel.ERROR,
)
public constructor(
uri: String,
@ -447,7 +447,7 @@ public data class ListResourceTemplatesRequest(override val params: PaginatedReq
@Deprecated(
message = "Use the constructor with PaginatedRequestParams property instead",
replaceWith = ReplaceWith("ListResourceTemplatesRequest(PaginatedRequestParams(cursor, meta))"),
level = DeprecationLevel.WARNING,
level = DeprecationLevel.ERROR,
)
public constructor(
cursor: String?,

View file

@ -66,7 +66,7 @@ public data class ListRootsRequest(override val params: BaseRequestParams? = nul
@Deprecated(
message = "Use the constructor with BaseRequestParams property instead",
replaceWith = ReplaceWith("ListRootsRequest(BaseRequestParams(meta))"),
level = DeprecationLevel.WARNING,
level = DeprecationLevel.ERROR,
)
public constructor(
meta: RequestMeta?,

View file

@ -240,7 +240,7 @@ public data class ListToolsRequest(override val params: PaginatedRequestParams?
@Deprecated(
message = "Use the constructor with BaseRequestParams property instead",
replaceWith = ReplaceWith("ListToolsRequest(BaseRequestParams(meta))"),
level = DeprecationLevel.WARNING,
level = DeprecationLevel.ERROR,
)
public constructor(
cursor: String?,

View file

@ -1,33 +0,0 @@
package io.modelcontextprotocol.kotlin.sdk
import io.kotest.assertions.json.shouldEqualJson
import io.modelcontextprotocol.kotlin.sdk.shared.McpJson
import kotlin.test.Test
import kotlin.test.assertEquals
class OldSchemaAudioContentSerializationTest {
private val audioContentJson = """
{
"data": "base64-encoded-audio-data",
"mimeType": "audio/wav",
"type": "audio"
}
""".trimIndent()
private val audioContent = AudioContent(
data = "base64-encoded-audio-data",
mimeType = "audio/wav",
)
@Test
fun `should serialize audio content`() {
McpJson.encodeToString(audioContent) shouldEqualJson audioContentJson
}
@Test
fun `should deserialize audio content`() {
val content = McpJson.decodeFromString<AudioContent>(audioContentJson)
assertEquals(expected = audioContent, actual = content)
}
}

View file

@ -1,63 +0,0 @@
package io.modelcontextprotocol.kotlin.sdk
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class OldSchemaCallToolResultUtilsTest {
@Test
fun testOkWithOnlyText() {
val content = "TextMessage"
val result = CallToolResult.ok(content)
assertEquals(1, result.content.size)
assertEquals(content, (result.content[0] as TextContent).text)
assertFalse(result.isError == true)
assertEquals(EmptyJsonObject, result.meta)
}
@Test
fun testOkWithMeta() {
val content = "TextMessageWithMeta"
val meta = buildJsonObject {
put("key1", JsonPrimitive("value1"))
put("key2", JsonPrimitive(42))
}
val result = CallToolResult.ok(content, meta)
assertEquals(1, result.content.size)
assertEquals(content, (result.content[0] as TextContent).text)
assertFalse(result.isError == true)
assertEquals(meta, result.meta)
}
@Test
fun testErrorWithOnlyText() {
val content = "ErrorMessage"
val result = CallToolResult.error(content)
assertEquals(1, result.content.size)
assertEquals(content, (result.content[0] as TextContent).text)
assertTrue(result.isError == true)
assertEquals(EmptyJsonObject, result.meta)
}
@Test
fun testErrorWithMeta() {
val content = "ErrorMessageWithMeta"
val meta = buildJsonObject {
put("errorCode", JsonPrimitive(404))
put("errorDetail", JsonPrimitive("资源未找到"))
}
val result = CallToolResult.error(content, meta)
assertEquals(1, result.content.size)
assertEquals(content, (result.content[0] as TextContent).text)
assertTrue(result.isError == true)
assertEquals(meta, result.meta)
}
}

View file

@ -1,329 +0,0 @@
package io.modelcontextprotocol.kotlin.sdk
import io.modelcontextprotocol.kotlin.sdk.shared.McpJson
import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult
import io.modelcontextprotocol.kotlin.sdk.types.CompleteResult
import io.modelcontextprotocol.kotlin.sdk.types.ElicitResult
import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertIs
class OldSchemaRequestSerializerTest {
// Client Result Tests
@Test
fun `should deserialize CreateMessageResult polymorphically`() {
val json = """{
"model": "test-model",
"role": "assistant",
"content": {
"type": "text",
"text": "Hello"
},
"stopReason": "endTurn"
}"""
val decoded = McpJson.decodeFromString<RequestResult>(json)
assertIs<CreateMessageResult>(decoded)
assertEquals("test-model", decoded.model)
assertEquals(Role.assistant, decoded.role)
assertEquals(StopReason.EndTurn, decoded.stopReason)
}
@Test
fun `should deserialize ListRootsResult polymorphically`() {
val json = """{
"roots": [
{
"uri": "file:///test",
"name": "Test Root"
}
]
}"""
val decoded = McpJson.decodeFromString<RequestResult>(json)
assertIs<ListRootsResult>(decoded)
assertEquals(1, decoded.roots.size)
assertEquals("file:///test", decoded.roots[0].uri)
assertEquals("Test Root", decoded.roots[0].name)
}
@Test
fun `should deserialize CreateElicitationResult polymorphically`() {
val json = """{
"action": "accept",
"content": {
"timezone": "Europe/Amsterdam"
}
}"""
val decoded = McpJson.decodeFromString<RequestResult>(json)
assertIs<ElicitResult>(decoded)
assertEquals(CreateElicitationResult.Action.accept, decoded.action)
}
// Server Result Tests
@Test
fun `should deserialize ListToolsResult polymorphically`() {
val json = """{
"tools": [
{
"name": "test-tool",
"description": "A test tool",
"inputSchema": {
"type": "object",
"properties": {}
},
"_meta": {}
}
]
}"""
val decoded = McpJson.decodeFromString<RequestResult>(json)
assertIs<ListToolsResult>(decoded)
assertEquals(1, decoded.tools.size)
assertEquals("test-tool", decoded.tools[0].name)
assertEquals("A test tool", decoded.tools[0].description)
}
@Test
fun `should deserialize ListResourcesResult polymorphically`() {
val json = """{
"resources": [
{
"uri": "file:///test.txt",
"name": "test.txt",
"mimeType": "text/plain"
}
]
}"""
val decoded = McpJson.decodeFromString<RequestResult>(json)
assertIs<ListResourcesResult>(decoded)
assertEquals(1, decoded.resources.size)
assertEquals("file:///test.txt", decoded.resources[0].uri)
assertEquals("test.txt", decoded.resources[0].name)
assertEquals("text/plain", decoded.resources[0].mimeType)
}
@Test
fun `should deserialize ListResourceTemplatesResult polymorphically`() {
val json = """{
"resourceTemplates": [
{
"uriTemplate": "file:///templates/{name}",
"name": "template",
"mimeType": "text/plain"
}
]
}"""
val decoded = McpJson.decodeFromString<RequestResult>(json)
assertIs<ListResourceTemplatesResult>(decoded)
assertEquals(1, decoded.resourceTemplates.size)
assertEquals("file:///templates/{name}", decoded.resourceTemplates[0].uriTemplate)
assertEquals("template", decoded.resourceTemplates[0].name)
assertEquals("text/plain", decoded.resourceTemplates[0].mimeType)
}
@Test
fun `should deserialize ListPromptsResult polymorphically`() {
val json = """{
"prompts": [
{
"name": "test-prompt",
"description": "A test prompt"
}
]
}"""
val decoded = McpJson.decodeFromString<RequestResult>(json)
assertIs<ListPromptsResult>(decoded)
assertEquals(1, decoded.prompts.size)
assertEquals("test-prompt", decoded.prompts[0].name)
assertEquals("A test prompt", decoded.prompts[0].description)
}
@Test
fun `should deserialize InitializeResult polymorphically`() {
val json = """{
"capabilities": {
"logging": {},
"prompts": {
"listChanged": true
},
"resources": {
"subscribe": true,
"listChanged": true
},
"tools": {
"listChanged": true
}
},
"protocolVersion": "2024-11-05",
"serverInfo": {
"name": "Test Server",
"version": "1.0.0"
}
}"""
val decoded = McpJson.decodeFromString<RequestResult>(json)
assertIs<InitializeResult>(decoded)
assertEquals("2024-11-05", decoded.protocolVersion)
assertEquals("Test Server", decoded.serverInfo.name)
assertEquals("1.0.0", decoded.serverInfo.version)
}
@Test
fun `should deserialize GetPromptResult polymorphically`() {
val json = """{
"description": "A test prompt",
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "Hello"
}
}
]
}"""
val decoded = McpJson.decodeFromString<RequestResult>(json)
assertIs<GetPromptResult>(decoded)
assertEquals("A test prompt", decoded.description)
assertEquals(1, decoded.messages.size)
assertEquals(Role.user, decoded.messages[0].role)
}
@Test
fun `should deserialize CompleteResult polymorphically`() {
val json = """{
"completion": {
"values": ["option1", "option2"],
"total": 2,
"hasMore": false
}
}"""
val decoded = McpJson.decodeFromString<RequestResult>(json)
assertIs<CompleteResult>(decoded)
assertEquals(2, decoded.completion.values.size)
assertEquals("option1", decoded.completion.values[0])
assertEquals("option2", decoded.completion.values[1])
assertEquals(2, decoded.completion.total)
assertEquals(false, decoded.completion.hasMore)
}
@Test
fun `should deserialize ReadResourceResult polymorphically`() {
val json = """{
"contents": [
{
"uri": "file:///test.txt",
"mimeType": "text/plain",
"text": "Hello World"
}
]
}"""
val decoded = McpJson.decodeFromString<RequestResult>(json)
assertIs<ReadResourceResult>(decoded)
assertEquals(1, decoded.contents.size)
assertIs<TextResourceContents>(decoded.contents[0])
val textContent = decoded.contents[0] as TextResourceContents
assertEquals("file:///test.txt", textContent.uri)
assertEquals("text/plain", textContent.mimeType)
assertEquals("Hello World", textContent.text)
}
@Test
fun `should deserialize CallToolResult polymorphically`() {
val json = """{
"content": [
{
"type": "text",
"text": "Tool result"
}
],
"isError": false
}"""
val decoded = McpJson.decodeFromString<RequestResult>(json)
assertIs<CallToolResult>(decoded)
assertEquals(1, decoded.content.size)
assertIs<TextContent>(decoded.content[0])
assertEquals("Tool result", (decoded.content[0] as TextContent).text)
assertEquals(false, decoded.isError)
}
@Test
fun `should deserialize CompatibilityCallToolResult polymorphically`() {
val json = """{
"toolResult": {"result": "Legacy tool result"},
"content": [],
"isError": false
}"""
val decoded = McpJson.decodeFromString<CompatibilityCallToolResult>(json)
assertIs<CompatibilityCallToolResult>(decoded)
assertEquals(buildJsonObject { put("result", "Legacy tool result") }, decoded.toolResult)
}
// Fallback Test
@Test
fun `should deserialize EmptyRequestResult for unknown result type`() {
val json = """{"unknownField": "value"}"""
assertFailsWith<SerializationException> {
McpJson.decodeFromString<RequestResult>(json)
}
}
@Test
fun `should handle empty JSON object`() {
val json = """{}"""
val decoded = McpJson.decodeFromString<RequestResult>(json)
assertIs<EmptyRequestResult>(decoded)
}
// Priority Test - Client results should take precedence over server results
@Test
fun `should prioritize client results over server results when both match`() {
// This JSON could potentially match both CreateMessageResult (client) and CallToolResult (server)
// but CreateMessageResult should be selected first due to the order
val json = """{
"model": "test-model",
"role": "assistant",
"content": {
"type": "text",
"text": "Test message"
},
"stopReason": "endTurn"
}"""
val decoded = McpJson.decodeFromString<RequestResult>(json)
assertIs<CreateMessageResult>(decoded)
assertEquals("test-model", decoded.model)
}
}

View file

@ -1,507 +0,0 @@
package io.modelcontextprotocol.kotlin.sdk
import io.kotest.assertions.json.shouldEqualJson
import io.modelcontextprotocol.kotlin.sdk.shared.McpJson
import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
import kotlin.test.Test
import kotlin.test.assertEquals
class OldSchemaToolSerializationTest {
// see https://docs.anthropic.com/en/docs/build-with-claude/tool-use
/* language=json */
private val getWeatherToolJson = """
{
"name": "get_weather",
"title": "Get weather",
"description": "Get the current weather in a given location",
"inputSchema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA"
}
},
"required": ["location"]
},
"outputSchema": {
"type": "object",
"properties": {
"temperature": {
"type": "number",
"description": "Temperature in celsius"
},
"conditions": {
"type": "string",
"description": "Weather conditions description"
},
"humidity": {
"type": "number",
"description": "Humidity percentage"
}
},
"required": ["temperature", "conditions", "humidity"]
},
"_meta": {
"_for_test_only": true
}
}
""".trimIndent()
val getWeatherTool = Tool(
name = "get_weather",
title = "Get weather",
description = "Get the current weather in a given location",
annotations = null,
inputSchema = Tool.Input(
properties = buildJsonObject {
put(
"location",
buildJsonObject {
put("type", JsonPrimitive("string"))
put("description", JsonPrimitive("The city and state, e.g. San Francisco, CA"))
},
)
},
required = listOf("location"),
),
outputSchema = Tool.Output(
properties = buildJsonObject {
put(
"temperature",
buildJsonObject {
put("type", JsonPrimitive("number"))
put("description", JsonPrimitive("Temperature in celsius"))
},
)
put(
"conditions",
buildJsonObject {
put("type", JsonPrimitive("string"))
put("description", JsonPrimitive("Weather conditions description"))
},
)
put(
"humidity",
buildJsonObject {
put("type", JsonPrimitive("number"))
put("description", JsonPrimitive("Humidity percentage"))
},
)
},
required = listOf("temperature", "conditions", "humidity"),
),
_meta = buildJsonObject {
put("_for_test_only", JsonPrimitive(true))
},
)
//region Serialize
@Test
fun `should serialize get_weather tool`() {
McpJson.encodeToString(getWeatherTool) shouldEqualJson getWeatherToolJson
}
@Test
fun `should always serialize default value`() {
val json = Json(from = McpJson) {
encodeDefaults = false
}
json.encodeToString(getWeatherTool) shouldEqualJson getWeatherToolJson
}
@Test
fun `should serialize get_weather tool without optional properties`() {
val weatherTool = createWeatherTool(name = "get_weather")
val expectedJson = createWeatherToolJson(name = "get_weather")
val actualJson = McpJson.encodeToString(weatherTool)
actualJson shouldEqualJson expectedJson
}
@Test
fun `should serialize get_weather tool with title optional property specified`() {
val weatherTool = createWeatherTool(name = "get_weather", title = "Get weather")
val expectedJson = createWeatherToolJson(name = "get_weather", title = "Get weather")
val actualJson = McpJson.encodeToString(weatherTool)
actualJson shouldEqualJson expectedJson
}
@Test
fun `should serialize get_weather tool with outputSchema optional property specified`() {
val weatherTool = createWeatherTool(
name = "get_weather",
outputSchema = Tool.Output(
properties = buildJsonObject {
put(
"temperature",
buildJsonObject {
put("type", JsonPrimitive("number"))
put("description", JsonPrimitive("Temperature in celsius"))
},
)
put(
"conditions",
buildJsonObject {
put("type", JsonPrimitive("string"))
put("description", JsonPrimitive("Weather conditions description"))
},
)
put(
"humidity",
buildJsonObject {
put("type", JsonPrimitive("number"))
put("description", JsonPrimitive("Humidity percentage"))
},
)
},
required = listOf("temperature", "conditions", "humidity"),
),
)
val expectedJson =
createWeatherToolJson(
name = "get_weather",
outputSchema = """
{
"type": "object",
"properties": {
"temperature": {
"type": "number",
"description": "Temperature in celsius"
},
"conditions": {
"type": "string",
"description": "Weather conditions description"
},
"humidity": {
"type": "number",
"description": "Humidity percentage"
}
},
"required": ["temperature", "conditions", "humidity"]
}
""".trimIndent(),
)
val actualJson = McpJson.encodeToString(weatherTool)
actualJson shouldEqualJson expectedJson
}
@Test
fun `should serialize get_weather tool with all properties specified`() {
val weatherTool = createWeatherTool(
name = "get_weather",
title = "Get weather",
outputSchema = Tool.Output(
properties = buildJsonObject {
put(
"temperature",
buildJsonObject {
put("type", JsonPrimitive("number"))
put("description", JsonPrimitive("Temperature in celsius"))
},
)
put(
"conditions",
buildJsonObject {
put("type", JsonPrimitive("string"))
put("description", JsonPrimitive("Weather conditions description"))
},
)
put(
"humidity",
buildJsonObject {
put("type", JsonPrimitive("number"))
put("description", JsonPrimitive("Humidity percentage"))
},
)
},
required = listOf("temperature", "conditions", "humidity"),
),
)
val expectedJson = createWeatherToolJson(
name = "get_weather",
title = "Get weather",
outputSchema = """
{
"type": "object",
"properties": {
"temperature": {
"type": "number",
"description": "Temperature in celsius"
},
"conditions": {
"type": "string",
"description": "Weather conditions description"
},
"humidity": {
"type": "number",
"description": "Humidity percentage"
}
},
"required": ["temperature", "conditions", "humidity"]
}
""".trimIndent(),
)
val actualJson = McpJson.encodeToString(weatherTool)
actualJson shouldEqualJson expectedJson
}
//endregion Serialize
//region Deserialize
@Test
fun `should deserialize get_weather tool`() {
val actualTool = McpJson.decodeFromString<io.modelcontextprotocol.kotlin.sdk.types.Tool>(getWeatherToolJson)
assertEquals(expected = getWeatherTool, actual = actualTool)
}
@Test
fun `should deserialize get_weather tool without optional properties`() {
val toolJson = createWeatherToolJson(name = "get_weather")
val expectedTool = createWeatherTool(name = "get_weather")
val actualTool = McpJson.decodeFromString<io.modelcontextprotocol.kotlin.sdk.types.Tool>(toolJson)
assertEquals(expected = expectedTool, actual = actualTool)
}
@Test
fun `should deserialize get_weather tool with title properties specified`() {
val toolJson = createWeatherToolJson(name = "get_weather", title = "Get weather")
val expectedTool = createWeatherTool(name = "get_weather", title = "Get weather")
val actualTool = McpJson.decodeFromString<io.modelcontextprotocol.kotlin.sdk.types.Tool>(toolJson)
assertEquals(expected = expectedTool, actual = actualTool)
}
@Test
fun `should deserialize get_weather tool with outputSchema optional property specified`() {
val toolJson =
createWeatherToolJson(
name = "get_weather",
outputSchema = """
{
"type": "object",
"properties": {
"temperature": {
"type": "number",
"description": "Temperature in celsius"
},
"conditions": {
"type": "string",
"description": "Weather conditions description"
},
"humidity": {
"type": "number",
"description": "Humidity percentage"
}
},
"required": ["temperature", "conditions", "humidity"]
}
""".trimIndent(),
)
val expectedTool = createWeatherTool(
name = "get_weather",
outputSchema = Tool.Output(
properties = buildJsonObject {
put(
"temperature",
buildJsonObject {
put("type", JsonPrimitive("number"))
put("description", JsonPrimitive("Temperature in celsius"))
},
)
put(
"conditions",
buildJsonObject {
put("type", JsonPrimitive("string"))
put("description", JsonPrimitive("Weather conditions description"))
},
)
put(
"humidity",
buildJsonObject {
put("type", JsonPrimitive("number"))
put("description", JsonPrimitive("Humidity percentage"))
},
)
},
required = listOf("temperature", "conditions", "humidity"),
),
)
val actualTool = McpJson.decodeFromString<io.modelcontextprotocol.kotlin.sdk.types.Tool>(toolJson)
assertEquals(expected = expectedTool, actual = actualTool)
}
@Test
fun `should deserialize get_weather tool with all properties specified`() {
val toolJson = createWeatherToolJson(
name = "get_weather",
title = "Get weather",
outputSchema = """
{
"type": "object",
"properties": {
"temperature": {
"type": "number",
"description": "Temperature in celsius"
},
"conditions": {
"type": "string",
"description": "Weather conditions description"
},
"humidity": {
"type": "number",
"description": "Humidity percentage"
}
},
"required": ["temperature", "conditions", "humidity"]
}
""".trimIndent(),
)
val expectedTool = createWeatherTool(
name = "get_weather",
title = "Get weather",
outputSchema = Tool.Output(
properties = buildJsonObject {
put(
"temperature",
buildJsonObject {
put("type", JsonPrimitive("number"))
put("description", JsonPrimitive("Temperature in celsius"))
},
)
put(
"conditions",
buildJsonObject {
put("type", JsonPrimitive("string"))
put("description", JsonPrimitive("Weather conditions description"))
},
)
put(
"humidity",
buildJsonObject {
put("type", JsonPrimitive("number"))
put("description", JsonPrimitive("Humidity percentage"))
},
)
},
required = listOf("temperature", "conditions", "humidity"),
),
)
val actualTool = McpJson.decodeFromString<io.modelcontextprotocol.kotlin.sdk.types.Tool>(toolJson)
assertEquals(expected = expectedTool, actual = actualTool)
}
//endregion Deserialize
//region Private Methods
private fun createWeatherToolJson(
name: String = "get_weather",
title: String? = null,
outputSchema: String? = null,
@Suppress("LocalVariableName") _meta: String? = null,
): String {
val stringBuilder = StringBuilder()
stringBuilder
.appendLine("{")
.append(" \"name\": \"$name\"")
if (title != null) {
stringBuilder
.appendLine(",")
.append(" \"title\": \"$title\"")
}
stringBuilder
.appendLine(",")
.append(" \"description\": \"Get the current weather in a given location\"")
.appendLine(",")
.append(
"""
"inputSchema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA"
}
},
"required": ["location"]
}
""".trimIndent(),
)
if (outputSchema != null) {
stringBuilder
.appendLine(",")
.append(
"""
"outputSchema": $outputSchema
""".trimIndent(),
)
}
stringBuilder
.appendLine(",")
.append(
"""
"_meta": ${_meta ?: "{}"}
""".trimIndent(),
)
stringBuilder
.appendLine()
.appendLine("}")
return stringBuilder.toString().trimIndent()
}
private fun createWeatherTool(
name: String = "get_weather",
title: String? = null,
outputSchema: ToolSchema? = null,
@Suppress("LocalVariableName") _meta: JsonObject? = null,
): io.modelcontextprotocol.kotlin.sdk.types.Tool = Tool(
name = name,
title = title,
description = "Get the current weather in a given location",
annotations = null,
inputSchema = Tool.Input(
properties = buildJsonObject {
put(
"location",
buildJsonObject {
put("type", JsonPrimitive("string"))
put("description", JsonPrimitive("The city and state, e.g. San Francisco, CA"))
},
)
},
required = listOf("location"),
),
outputSchema = outputSchema,
_meta = _meta ?: EmptyJsonObject,
)
//endregion Private Methods
}

View file

@ -1,450 +0,0 @@
package io.modelcontextprotocol.kotlin.sdk
import io.modelcontextprotocol.kotlin.sdk.shared.McpJson
import io.modelcontextprotocol.kotlin.sdk.types.CompleteRequestParams
import io.modelcontextprotocol.kotlin.sdk.types.ContentTypes
import io.modelcontextprotocol.kotlin.sdk.types.ReferenceType
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
import kotlin.time.ExperimentalTime
class OldSchemaTypesTest {
@Test
fun `should have correct latest protocol version`() {
assertNotEquals("", LATEST_PROTOCOL_VERSION)
assertEquals("2025-06-18", LATEST_PROTOCOL_VERSION)
}
@Test
fun `should have correct supported protocol versions`() {
assertIs<List<String>>(SUPPORTED_PROTOCOL_VERSIONS)
assertTrue(SUPPORTED_PROTOCOL_VERSIONS.contains(LATEST_PROTOCOL_VERSION))
assertTrue(SUPPORTED_PROTOCOL_VERSIONS.contains("2024-11-05"))
assertEquals(3, SUPPORTED_PROTOCOL_VERSIONS.size)
}
@Test
fun `should validate JSONRPC version constant`() {
assertEquals("2.0", JSONRPC_VERSION)
}
// Reference Tests
@Test
fun `should validate ResourceTemplateReference`() {
val resourceRef = ResourceTemplateReference(uri = "file:///path/to/file.txt")
assertEquals(ReferenceType.ResourceTemplate, resourceRef.type)
assertEquals("file:///path/to/file.txt", resourceRef.uri)
}
@Test
fun `should serialize and deserialize ResourceTemplateReference correctly`() {
val resourceRef = ResourceTemplateReference(uri = "https://example.com/resource")
val json = McpJson.encodeToString<Reference>(resourceRef)
val decoded = McpJson.decodeFromString<Reference>(json)
assertIs<ResourceTemplateReference>(decoded)
assertEquals(ReferenceType.ResourceTemplate, decoded.type)
assertEquals("https://example.com/resource", decoded.uri)
}
@Test
fun `should validate PromptReference`() {
val promptRef = PromptReference(name = "greeting")
assertEquals(ReferenceType.Prompt, promptRef.type)
assertEquals("greeting", promptRef.name)
}
@Test
fun `should serialize and deserialize PromptReference correctly`() {
val promptRef = PromptReference(name = "test-prompt")
val json = McpJson.encodeToString<Reference>(promptRef)
val decoded = McpJson.decodeFromString<Reference>(json)
assertIs<PromptReference>(decoded)
assertEquals(ReferenceType.Prompt, decoded.type)
assertEquals("test-prompt", decoded.name)
}
// PromptMessageContent Tests
@Test
fun `should validate text content`() {
val textContent = TextContent(text = "Hello, world!")
assertEquals(ContentTypes.TEXT, textContent.type)
assertEquals("Hello, world!", textContent.text)
}
@Test
fun `should serialize and deserialize text content correctly`() {
val textContent = TextContent(text = "Test message")
val json = McpJson.encodeToString<PromptMessageContent>(textContent)
val decoded = McpJson.decodeFromString<PromptMessageContent>(json)
assertIs<TextContent>(decoded)
assertEquals(ContentTypes.TEXT, decoded.type)
assertEquals("Test message", decoded.text)
}
@Test
fun `should validate image content`() {
val imageContent = ImageContent(
data = "aGVsbG8=", // base64 encoded "hello"
mimeType = "image/png",
)
assertEquals(ContentTypes.IMAGE, imageContent.type)
assertEquals("aGVsbG8=", imageContent.data)
assertEquals("image/png", imageContent.mimeType)
}
@Test
fun `should serialize and deserialize image content correctly`() {
val imageContent = ImageContent(
data = "dGVzdA==", // base64 encoded "test"
mimeType = "image/jpeg",
)
val json = McpJson.encodeToString<PromptMessageContent>(imageContent)
val decoded = McpJson.decodeFromString<PromptMessageContent>(json)
assertIs<ImageContent>(decoded)
assertEquals(ContentTypes.IMAGE, decoded.type)
assertEquals("dGVzdA==", decoded.data)
assertEquals("image/jpeg", decoded.mimeType)
}
@Test
fun `should validate audio content`() {
val audioContent = AudioContent(
data = "aGVsbG8=", // base64 encoded "hello"
mimeType = "audio/mp3",
)
assertEquals(ContentTypes.AUDIO, audioContent.type)
assertEquals("aGVsbG8=", audioContent.data)
assertEquals("audio/mp3", audioContent.mimeType)
}
@Test
fun `should serialize and deserialize audio content correctly`() {
val audioContent = AudioContent(
data = "YXVkaW8=", // base64 encoded "audio"
mimeType = "audio/wav",
)
val json = McpJson.encodeToString<PromptMessageContent>(audioContent)
val decoded = McpJson.decodeFromString<PromptMessageContent>(json)
assertIs<AudioContent>(decoded)
assertEquals(ContentTypes.AUDIO, decoded.type)
assertEquals("YXVkaW8=", decoded.data)
assertEquals("audio/wav", decoded.mimeType)
}
@Test
fun `should validate embedded resource content`() {
val resource = TextResourceContents(
text = "File contents",
uri = "file:///path/to/file.txt",
mimeType = "text/plain",
)
val embeddedResource = EmbeddedResource(resource = resource)
assertEquals(ContentTypes.EMBEDDED_RESOURCE, embeddedResource.type)
assertEquals(resource, embeddedResource.resource)
}
@Test
fun `should serialize and deserialize embedded resource content correctly`() {
val resource = BlobResourceContents(
blob = "YmluYXJ5ZGF0YQ==",
uri = "file:///path/to/binary.dat",
mimeType = "application/octet-stream",
)
val embeddedResource = EmbeddedResource(resource = resource)
val json = McpJson.encodeToString<PromptMessageContent>(embeddedResource)
val decoded = McpJson.decodeFromString<PromptMessageContent>(json)
assertIs<EmbeddedResource>(decoded)
assertEquals(ContentTypes.EMBEDDED_RESOURCE, decoded.type)
assertIs<BlobResourceContents>(decoded.resource)
val decodedBlob = decoded.resource
assertEquals("YmluYXJ5ZGF0YQ==", decodedBlob.blob)
assertEquals("file:///path/to/binary.dat", decodedBlob.uri)
assertEquals("application/octet-stream", decodedBlob.mimeType)
}
// PromptMessage Tests
@Test
fun `should validate prompt message with text content`() {
val textContent = TextContent(text = "Hello, assistant!")
val promptMessage = PromptMessage(
role = Role.user,
content = textContent,
)
assertEquals(Role.user, promptMessage.role)
assertEquals(textContent, promptMessage.content)
assertEquals(ContentTypes.TEXT, promptMessage.content.type)
}
@Test
fun `should validate prompt message with embedded resource`() {
val resource = TextResourceContents(
text = "Primary application entry point",
uri = "file:///project/src/main.rs",
mimeType = "text/x-rust",
)
val embeddedResource = EmbeddedResource(resource = resource)
val promptMessage = PromptMessage(
role = Role.assistant,
content = embeddedResource,
)
assertEquals(Role.assistant, promptMessage.role)
assertEquals(ContentTypes.EMBEDDED_RESOURCE, promptMessage.content.type)
val content = promptMessage.content as EmbeddedResource
val textResource = content.resource as TextResourceContents
assertEquals("Primary application entry point", textResource.text)
assertEquals("file:///project/src/main.rs", textResource.uri)
assertEquals("text/x-rust", textResource.mimeType)
}
@OptIn(ExperimentalTime::class)
@Test
fun `should serialize and deserialize annotations correctly`() {
val annotations = Annotations(
audience = listOf(Role.assistant),
lastModified = "2025-06-18T00:00:00Z",
priority = 0.5,
)
val json = McpJson.encodeToString(annotations)
val decoded = McpJson.decodeFromString<Annotations>(json)
assertEquals(listOf(Role.assistant), decoded.audience)
assertEquals("2025-06-18T00:00:00Z", decoded.lastModified)
assertEquals(0.5, decoded.priority)
}
@Test
fun `should serialize and deserialize prompt message correctly`() {
val imageContent = ImageContent(
data = "aW1hZ2VkYXRh", // base64 encoded "imagedata"
mimeType = "image/png",
)
val promptMessage = PromptMessage(
role = Role.assistant,
content = imageContent,
)
val json = McpJson.encodeToString(promptMessage)
val decoded = McpJson.decodeFromString<PromptMessage>(json)
assertEquals(Role.assistant, decoded.role)
assertIs<ImageContent>(decoded.content)
val decodedContent = decoded.content
assertEquals("aW1hZ2VkYXRh", decodedContent.data)
assertEquals("image/png", decodedContent.mimeType)
}
// CallToolResult Tests
@Test
fun `should validate tool result with multiple content types`() {
val toolResult = CallToolResult(
content = listOf(
TextContent(text = "Found the following files:"),
EmbeddedResource(
resource = TextResourceContents(
text = "fn main() {}",
uri = "file:///project/src/main.rs",
mimeType = "text/x-rust",
),
),
EmbeddedResource(
resource = TextResourceContents(
text = "pub mod lib;",
uri = "file:///project/src/lib.rs",
mimeType = "text/x-rust",
),
),
),
)
assertEquals(3, toolResult.content.size)
assertEquals(ContentTypes.TEXT, toolResult.content[0].type)
assertEquals(ContentTypes.EMBEDDED_RESOURCE, toolResult.content[1].type)
assertEquals(ContentTypes.EMBEDDED_RESOURCE, toolResult.content[2].type)
assertEquals(null, toolResult.isError)
}
@Test
fun `should validate empty content array with default`() {
val toolResult = CallToolResult(content = emptyList())
assertEquals(0, toolResult.content.size)
assertEquals(null, toolResult.isError)
}
@Test
fun `should serialize and deserialize CallToolResult correctly`() {
val toolResult = CallToolResult(
content = listOf(
TextContent(text = "Operation completed"),
ImageContent(data = "aW1hZ2U=", mimeType = "image/png"),
),
isError = false,
)
val json = McpJson.encodeToString(toolResult)
val decoded = McpJson.decodeFromString<io.modelcontextprotocol.kotlin.sdk.types.CallToolResult>(json)
assertEquals(2, decoded.content.size)
assertIs<TextContent>(decoded.content[0])
assertIs<ImageContent>(decoded.content[1])
assertEquals(false, decoded.isError)
}
// CompleteRequest Tests
@Test
fun `should validate CompleteRequest with prompt reference`() {
val request = CompleteRequest(
CompleteRequestParams(
ref = PromptReference(name = "greeting"),
argument = CompleteRequest.Argument(name = "name", value = "A"),
),
)
assertEquals("completion/complete", request.method.value)
assertIs<PromptReference>(request.params.ref)
val promptRef = request.params.ref
assertEquals("greeting", promptRef.name)
assertEquals("name", request.params.argument.name)
assertEquals("A", request.params.argument.value)
}
@Test
fun `should validate CompleteRequest with resource reference`() {
val request = CompleteRequest(
CompleteRequestParams(
ref = ResourceTemplateReference(uri = "github://repos/{owner}/{repo}"),
argument = CompleteRequest.Argument(name = "repo", value = "t"),
),
)
assertEquals("completion/complete", request.method.value)
assertIs<ResourceTemplateReference>(request.params.ref)
val resourceRef = request.params.ref
assertEquals("github://repos/{owner}/{repo}", resourceRef.uri)
assertEquals("repo", request.params.argument.name)
assertEquals("t", request.params.argument.value)
}
@Test
fun `should serialize and deserialize CompleteRequest correctly`() {
val request = CompleteRequest(
CompleteRequestParams(
ref = PromptReference(name = "test"),
argument = CompleteRequest.Argument(name = "arg", value = ""),
),
)
val json = McpJson.encodeToString(request)
val decoded = McpJson.decodeFromString<io.modelcontextprotocol.kotlin.sdk.types.CompleteRequest>(json)
assertEquals("completion/complete", decoded.method.value)
assertIs<PromptReference>(decoded.params.ref)
val promptRef = decoded.params.ref
assertEquals("test", promptRef.name)
assertEquals("arg", decoded.params.argument.name)
assertEquals("", decoded.params.argument.value)
}
@Test
fun `should validate CompleteRequest with complex URIs`() {
val request = CompleteRequest(
CompleteRequestParams(
ref = ResourceTemplateReference(uri = "api://v1/{tenant}/{resource}/{id}"),
argument = CompleteRequest.Argument(name = "id", value = "123"),
),
)
val resourceRef = request.params.ref as ResourceTemplateReference
assertEquals("api://v1/{tenant}/{resource}/{id}", resourceRef.uri)
assertEquals("id", request.params.argument.name)
assertEquals("123", request.params.argument.value)
}
// InitializeResult Tests
@Test
fun `should create InitializeResult with default instructions`() {
val serverInfo = Implementation(name = "test-server", version = "1.0.0")
val result = InitializeResult(
capabilities = ServerCapabilities(),
serverInfo = serverInfo,
)
assertEquals(LATEST_PROTOCOL_VERSION, result.protocolVersion)
assertEquals(serverInfo, result.serverInfo)
assertEquals(null, result.instructions)
}
@Test
fun `should create InitializeResult with custom instructions`() {
val serverInfo = Implementation(name = "test-server", version = "1.0.0")
val instructions = "Use this server to perform calculations. Call the 'add' tool to add numbers."
val result = InitializeResult(
capabilities = ServerCapabilities(),
serverInfo = serverInfo,
instructions = instructions,
)
assertEquals(LATEST_PROTOCOL_VERSION, result.protocolVersion)
assertEquals(serverInfo, result.serverInfo)
assertEquals(instructions, result.instructions)
}
@Test
fun `should serialize and deserialize InitializeResult with instructions`() {
val serverInfo = Implementation(name = "test-server", version = "1.0.0")
val instructions = "This server provides file system access. Use the 'read' tool to read files."
val result = InitializeResult(
capabilities = ServerCapabilities(),
serverInfo = serverInfo,
instructions = instructions,
)
val json = McpJson.encodeToString(result)
val decoded = McpJson.decodeFromString<InitializeResult>(json)
assertEquals(LATEST_PROTOCOL_VERSION, decoded.protocolVersion)
assertEquals(serverInfo, decoded.serverInfo)
assertEquals(instructions, decoded.instructions)
}
@Test
fun `should serialize and deserialize InitializeResult without instructions`() {
val serverInfo = Implementation(name = "test-server", version = "1.0.0")
val result = InitializeResult(
capabilities = ServerCapabilities(),
serverInfo = serverInfo,
)
val json = McpJson.encodeToString(result)
val decoded = McpJson.decodeFromString<InitializeResult>(json)
assertEquals(LATEST_PROTOCOL_VERSION, decoded.protocolVersion)
assertEquals(serverInfo, decoded.serverInfo)
assertEquals(null, decoded.instructions)
}
}

View file

@ -1,71 +0,0 @@
package io.modelcontextprotocol.kotlin.sdk.models
import io.kotest.matchers.shouldBe
import io.modelcontextprotocol.kotlin.sdk.ProgressNotification
import io.modelcontextprotocol.kotlin.sdk.RequestId
import io.modelcontextprotocol.kotlin.sdk.shared.McpJson
import kotlin.test.Test
class OldSchemaProgressNotificationsTest {
/**
* https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/progress#progress-flow
*/
@Test
fun `Read ProgressNotifications with string token`() {
//language=json
val json = """
{
"jsonrpc": "2.0",
"method": "notifications/progress",
"params": {
"progressToken": "abc123",
"progress": 50,
"total": 100,
"message": "Reticulating splines..."
}
}
""".trimIndent()
val result = McpJson.decodeFromString<io.modelcontextprotocol.kotlin.sdk.types.ProgressNotification>(json)
result shouldBe ProgressNotification(
params = ProgressNotification.Params(
progressToken = RequestId.StringId("abc123"),
progress = 50.0,
message = "Reticulating splines...",
total = 100.0,
),
)
}
/**
* https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/progress#progress-flow
*/
@Test
fun `Read ProgressNotifications with integer token`() {
//language=json
val json = """
{
"jsonrpc": "2.0",
"method": "notifications/progress",
"params": {
"progressToken": 100500,
"progress": 50,
"total": 100,
"message": "Reticulating splines..."
}
}
""".trimIndent()
val result = McpJson.decodeFromString<io.modelcontextprotocol.kotlin.sdk.types.ProgressNotification>(json)
result shouldBe ProgressNotification(
params = ProgressNotification.Params(
progressToken = RequestId.NumberId(100500),
progress = 50.0,
message = "Reticulating splines...",
total = 100.0,
),
)
}
}

View file

@ -1,62 +0,0 @@
package io.modelcontextprotocol.kotlin.sdk.shared
import io.ktor.utils.io.charsets.Charsets
import io.ktor.utils.io.core.toByteArray
import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage
import io.modelcontextprotocol.kotlin.sdk.JSONRPCNotification
import kotlinx.serialization.json.Json
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull
class OldSchemaReadBufferTest {
private val testMessage: JSONRPCMessage = JSONRPCNotification(method = "foobar")
private val json = Json {
ignoreUnknownKeys = true
encodeDefaults = true
}
@Test
fun `should have no messages after initialization`() {
val readBuffer = ReadBuffer()
assertNull(readBuffer.readMessage())
}
@Test
fun `should only yield a message after a newline`() {
val readBuffer = ReadBuffer()
// Append message without a newline
val messageBytes = json.encodeToString(testMessage).encodeToByteArray()
readBuffer.append(messageBytes)
assertNull(readBuffer.readMessage())
// Append a newline and verify message is now available
readBuffer.append("\n".encodeToByteArray())
assertEquals(testMessage, readBuffer.readMessage())
assertNull(readBuffer.readMessage())
}
@Test
fun `skip empty line`() {
val readBuffer = ReadBuffer()
readBuffer.append("\n".toByteArray())
assertNull(readBuffer.readMessage())
}
@Test
fun `should be reusable after clearing`() {
val readBuffer = ReadBuffer()
readBuffer.append("foobar".toByteArray(Charsets.UTF_8))
readBuffer.clear()
assertNull(readBuffer.readMessage())
val messageJson = serializeMessage(testMessage)
readBuffer.append(messageJson.toByteArray(Charsets.UTF_8))
readBuffer.append("\n".toByteArray(Charsets.UTF_8))
val message = readBuffer.readMessage()
assertEquals(testMessage, message)
}
}