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:
parent
6f1c120c27
commit
92823b987f
16 changed files with 224 additions and 8255 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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?,
|
||||
|
|
|
|||
|
|
@ -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?,
|
||||
|
|
|
|||
|
|
@ -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?,
|
||||
|
|
|
|||
|
|
@ -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?,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue