diff --git a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaClientTest.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaClientTest.kt deleted file mode 100644 index f9ef4f3..0000000 --- a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaClientTest.kt +++ /dev/null @@ -1,1042 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.client - -import io.modelcontextprotocol.kotlin.sdk.ClientCapabilities -import io.modelcontextprotocol.kotlin.sdk.CreateElicitationRequest -import io.modelcontextprotocol.kotlin.sdk.CreateElicitationResult -import io.modelcontextprotocol.kotlin.sdk.CreateMessageResult -import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.InitializeRequest -import io.modelcontextprotocol.kotlin.sdk.InitializeResult -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage -import io.modelcontextprotocol.kotlin.sdk.JSONRPCRequest -import io.modelcontextprotocol.kotlin.sdk.JSONRPCResponse -import io.modelcontextprotocol.kotlin.sdk.LATEST_PROTOCOL_VERSION -import io.modelcontextprotocol.kotlin.sdk.ListResourcesRequest -import io.modelcontextprotocol.kotlin.sdk.ListResourcesResult -import io.modelcontextprotocol.kotlin.sdk.ListRootsRequest -import io.modelcontextprotocol.kotlin.sdk.ListToolsRequest -import io.modelcontextprotocol.kotlin.sdk.ListToolsResult -import io.modelcontextprotocol.kotlin.sdk.LoggingLevel -import io.modelcontextprotocol.kotlin.sdk.LoggingMessageNotification -import io.modelcontextprotocol.kotlin.sdk.Method -import io.modelcontextprotocol.kotlin.sdk.Role -import io.modelcontextprotocol.kotlin.sdk.Root -import io.modelcontextprotocol.kotlin.sdk.SUPPORTED_PROTOCOL_VERSIONS -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities -import io.modelcontextprotocol.kotlin.sdk.TextContent -import io.modelcontextprotocol.kotlin.sdk.Tool -import io.modelcontextprotocol.kotlin.sdk.server.Server -import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions -import io.modelcontextprotocol.kotlin.sdk.server.ServerSession -import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport -import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport -import io.modelcontextprotocol.kotlin.sdk.shared.TransportSendOptions -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.TimeoutCancellationException -import kotlinx.coroutines.cancel -import kotlinx.coroutines.delay -import kotlinx.coroutines.joinAll -import kotlinx.coroutines.launch -import kotlinx.coroutines.test.runTest -import kotlinx.coroutines.withTimeout -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.put -import kotlinx.serialization.json.putJsonObject -import kotlin.coroutines.cancellation.CancellationException -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertIs -import kotlin.test.assertTrue -import kotlin.test.fail - -class OldSchemaClientTest { - @Test - fun `should initialize with matching protocol version`() = runTest { - var initialised = false - val clientTransport = object : AbstractTransport() { - override suspend fun start() {} - - override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { - if (message !is JSONRPCRequest) return - initialised = true - val result = InitializeResult( - protocolVersion = LATEST_PROTOCOL_VERSION, - capabilities = ServerCapabilities(), - serverInfo = Implementation( - name = "test", - version = "1.0", - ), - ) - - val response = JSONRPCResponse( - id = message.id, - result = result, - ) - - _onMessage.invoke(response) - } - - override suspend fun close() { - } - } - - val client = Client( - clientInfo = Implementation( - name = "test client", - version = "1.0", - ), - options = ClientOptions( - capabilities = ClientCapabilities( - sampling = EmptyJsonObject, - ), - ), - ) - - client.connect(clientTransport) - assertTrue(initialised) - } - - @Test - fun `should initialize with supported older protocol version`() = runTest { - val oldVersion = SUPPORTED_PROTOCOL_VERSIONS[1] - val clientTransport = object : AbstractTransport() { - override suspend fun start() {} - - override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { - if (message !is JSONRPCRequest) return - check(message.method == Method.Defined.Initialize.value) - - val result = InitializeResult( - protocolVersion = oldVersion, - capabilities = ServerCapabilities(), - serverInfo = Implementation( - name = "test", - version = "1.0", - ), - ) - - val response = JSONRPCResponse( - id = message.id, - result = result, - ) - _onMessage.invoke(response) - } - - override suspend fun close() { - } - } - - val client = Client( - clientInfo = Implementation( - name = "test client", - version = "1.0", - ), - options = ClientOptions( - capabilities = ClientCapabilities( - sampling = EmptyJsonObject, - ), - ), - ) - - client.connect(clientTransport) - assertEquals( - Implementation("test", "1.0"), - client.serverVersion, - ) - } - - @Test - fun `should reject unsupported protocol version`() = runTest { - var closed = false - val clientTransport = object : AbstractTransport() { - override suspend fun start() {} - - override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { - if (message !is JSONRPCRequest) return - check(message.method == Method.Defined.Initialize.value) - - val result = InitializeResult( - protocolVersion = "invalid-version", - capabilities = ServerCapabilities(), - serverInfo = Implementation( - name = "test", - version = "1.0", - ), - ) - - val response = JSONRPCResponse( - id = message.id, - result = result, - ) - - _onMessage.invoke(response) - } - - override suspend fun close() { - closed = true - } - } - - val client = Client( - clientInfo = Implementation( - name = "test client", - version = "1.0", - ), - options = ClientOptions(), - ) - - assertFailsWith("Server's protocol version is not supported: invalid-version") { - client.connect(clientTransport) - } - - assertTrue(closed) - } - - @Test - fun `should reject due to non cancellation exception`() = runTest { - var closed = false - val failingTransport = object : AbstractTransport() { - override suspend fun start() {} - - override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { - if (message !is JSONRPCRequest) return - check(message.method == Method.Defined.Initialize.value) - throw IllegalStateException("Test error") - } - - override suspend fun close() { - closed = true - } - } - - val client = Client( - clientInfo = Implementation( - name = "test client", - version = "1.0", - ), - options = ClientOptions(), - ) - - val exception = assertFailsWith { - client.connect(failingTransport) - } - - assertEquals("Error connecting to transport: Test error", exception.message) - - assertTrue(closed) - } - - @Test - fun `should respect server capabilities`() = runTest { - val serverOptions = ServerOptions( - capabilities = ServerCapabilities( - resources = ServerCapabilities.Resources(null, null), - tools = ServerCapabilities.Tools(null), - ), - ) - val server = Server( - Implementation(name = "test server", version = "1.0"), - serverOptions, - ) - - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - - val client = Client( - clientInfo = Implementation(name = "test client", version = "1.0"), - options = ClientOptions( - capabilities = ClientCapabilities(sampling = EmptyJsonObject), - ), - ) - - val serverSessionResult = CompletableDeferred() - - listOf( - launch { - client.connect(clientTransport) - }, - launch { - serverSessionResult.complete(server.createSession(serverTransport)) - }, - ).joinAll() - - val serverSession = serverSessionResult.await() - serverSession.setRequestHandler(Method.Defined.Initialize) { _, _ -> - InitializeResult( - protocolVersion = LATEST_PROTOCOL_VERSION, - capabilities = ServerCapabilities( - resources = ServerCapabilities.Resources(null, null), - tools = ServerCapabilities.Tools(null), - ), - serverInfo = Implementation(name = "test", version = "1.0"), - ) - } - - serverSession.setRequestHandler(Method.Defined.ResourcesList) { _, _ -> - ListResourcesResult(resources = emptyList(), nextCursor = null) - } - - serverSession.setRequestHandler(Method.Defined.ToolsList) { _, _ -> - ListToolsResult(tools = emptyList(), nextCursor = null) - } - // Server supports resources and tools, but not prompts - val caps = client.serverCapabilities - assertEquals(ServerCapabilities.Resources(null, null), caps?.resources) - assertEquals(ServerCapabilities.Tools(null), caps?.tools) - assertTrue(caps?.prompts == null) // or check that prompts are absent - - // These should not throw - client.listResources() - client.listTools() - - // This should fail because prompts are not supported - val ex = assertFailsWith { - client.listPrompts() - } - assertTrue(ex.message?.contains("Server does not support prompts") == true) - } - - @Test - fun `should respect client notification capabilities`() = runTest { - val server = Server( - Implementation(name = "test server", version = "1.0"), - ServerOptions(capabilities = ServerCapabilities()), - ) - - val client = Client( - clientInfo = Implementation(name = "test client", version = "1.0"), - options = ClientOptions( - capabilities = ClientCapabilities( - roots = ClientCapabilities.Roots(listChanged = true), - ), - ), - ) - - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - - listOf( - launch { - client.connect(clientTransport) - println("Client connected") - }, - launch { - server.createSession(serverTransport) - println("Server connected") - }, - ).joinAll() - - // This should not throw because the client supports roots.listChanged - client.sendRootsListChanged() - - // Create a new client without the roots.listChanged capability - val clientWithoutCapability = Client( - clientInfo = Implementation(name = "test client without capability", version = "1.0"), - options = ClientOptions( - capabilities = ClientCapabilities(), - // enforceStrictCapabilities = true // TODO() - ), - ) - - clientWithoutCapability.connect(clientTransport) - // Using the same transport pair might not be realistic - in a real scenario you'd create another pair. - // Adjust if necessary. - - // This should fail - val ex = assertFailsWith { - clientWithoutCapability.sendRootsListChanged() - } - assertTrue(ex.message?.startsWith("Client does not support") == true) - } - - @Test - fun `should respect server notification capabilities`() = runTest { - val server = Server( - Implementation(name = "test server", version = "1.0"), - ServerOptions( - capabilities = ServerCapabilities( - logging = EmptyJsonObject, - resources = ServerCapabilities.Resources(listChanged = true, subscribe = null), - ), - ), - ) - - val client = Client( - clientInfo = Implementation(name = "test client", version = "1.0"), - options = ClientOptions( - capabilities = ClientCapabilities(), - ), - ) - - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - - val serverSessionResult = CompletableDeferred() - - listOf( - launch { - client.connect(clientTransport) - println("Client connected") - }, - launch { - serverSessionResult.complete(server.createSession(serverTransport)) - println("Server connected") - }, - ).joinAll() - - val serverSession = serverSessionResult.await() - // These should not throw - val jsonObject = buildJsonObject { - put("name", "John") - put("age", 30) - put("isStudent", false) - } - serverSession.sendLoggingMessage( - LoggingMessageNotification( - params = LoggingMessageNotification.Params( - level = LoggingLevel.info, - data = jsonObject, - ), - ), - ) - serverSession.sendResourceListChanged() - - // This should fail because the server doesn't have the tools capability - val ex = assertFailsWith { - serverSession.sendToolListChanged() - } - assertTrue(ex.message?.contains("Server does not support notifying of tool list changes") == true) - } - - @Test - fun `should handle client cancelling a request`() = runTest { - val server = Server( - Implementation(name = "test server", version = "1.0"), - ServerOptions( - capabilities = ServerCapabilities( - resources = ServerCapabilities.Resources( - listChanged = null, - subscribe = null, - ), - ), - ), - ) - - val def = CompletableDeferred() - val defTimeOut = CompletableDeferred() - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - - val client = Client( - clientInfo = Implementation(name = "test client", version = "1.0"), - options = ClientOptions(capabilities = ClientCapabilities()), - ) - - val serverSessionResult = CompletableDeferred() - - listOf( - launch { - client.connect(clientTransport) - println("Client connected") - }, - launch { - serverSessionResult.complete(server.createSession(serverTransport)) - println("Server connected") - }, - ).joinAll() - - val serverSession = serverSessionResult.await() - - serverSession.setRequestHandler(Method.Defined.ResourcesList) { _, _ -> - // Simulate delay - def.complete(Unit) - try { - delay(1000) - } catch (e: CancellationException) { - defTimeOut.complete(Unit) - throw e - } - ListResourcesResult(resources = emptyList()) - fail("Shouldn't have been called") - } - - val defCancel = CompletableDeferred() - val job = launch { - try { - client.listResources() - } catch (e: CancellationException) { - defCancel.complete(Unit) - assertEquals("Cancelled by test", e.message) - } - } - def.await() - runCatching { job.cancel("Cancelled by test") } - defCancel.await() - defTimeOut.await() - } - - @Test - fun `should handle request timeout`() = runTest { - val server = Server( - Implementation(name = "test server", version = "1.0"), - ServerOptions( - capabilities = ServerCapabilities( - resources = ServerCapabilities.Resources( - listChanged = null, - subscribe = null, - ), - ), - ), - ) - - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - val client = Client( - clientInfo = Implementation(name = "test client", version = "1.0"), - options = ClientOptions(capabilities = ClientCapabilities()), - ) - - val serverSessionResult = CompletableDeferred() - - listOf( - launch { - client.connect(clientTransport) - println("Client connected") - }, - launch { - serverSessionResult.complete(server.createSession(serverTransport)) - println("Server connected") - }, - ).joinAll() - - val serverSession = serverSessionResult.await() - serverSession.setRequestHandler(Method.Defined.ResourcesList) { _, _ -> - // Simulate a delayed response - // Wait ~100ms unless canceled - try { - withTimeout(100L) { - // Just delay here, if timeout is 0 on the client side, this won't return in time - delay(100) - } - } catch (_: Exception) { - // If aborted, just rethrow or return early - } - ListResourcesResult(resources = emptyList()) - } - - // Request with 1 msec timeout should fail immediately - val ex = assertFailsWith { - withTimeout(1) { - client.listResources() - } - } - assertTrue(ex is TimeoutCancellationException) - } - - @Test - fun `should only allow setRequestHandler for declared capabilities`() = runTest { - val client = Client( - clientInfo = Implementation( - name = "test client", - version = "1.0", - ), - options = ClientOptions( - capabilities = ClientCapabilities( - sampling = EmptyJsonObject, - ), - ), - ) - - client.setRequestHandler( - Method.Defined.SamplingCreateMessage, - ) { - _, - _, - -> - CreateMessageResult( - model = "test-model", - role = Role.assistant, - content = TextContent( - text = "Test response", - ), - ) - } - - assertFailsWith("Client does not support roots capability (required for RootsList)") { - client.setRequestHandler(Method.Defined.RootsList) { _, _ -> null } - } - } - - @Test - fun `JSONRPCRequest with ToolsList method and default params returns list of tools`() = runTest { - val serverOptions = ServerOptions( - capabilities = ServerCapabilities( - tools = ServerCapabilities.Tools(null), - ), - ) - val server = Server( - Implementation(name = "test server", version = "1.0"), - serverOptions, - ) - - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - - val client = Client( - clientInfo = Implementation(name = "test client", version = "1.0"), - options = ClientOptions( - capabilities = ClientCapabilities(sampling = EmptyJsonObject), - ), - ) - - var receivedMessage: JSONRPCMessage? = null - clientTransport.onMessage { msg -> - receivedMessage = msg - } - - val serverSessionResult = CompletableDeferred() - - listOf( - launch { - client.connect(clientTransport) - println("Client connected") - }, - launch { - serverSessionResult.complete(server.createSession(serverTransport)) - println("Server connected") - }, - ).joinAll() - - val serverSession = serverSessionResult.await() - - serverSession.setRequestHandler(Method.Defined.Initialize) { _, _ -> - InitializeResult( - protocolVersion = LATEST_PROTOCOL_VERSION, - capabilities = ServerCapabilities( - resources = ServerCapabilities.Resources(null, null), - tools = ServerCapabilities.Tools(null), - ), - serverInfo = Implementation(name = "test", version = "1.0"), - ) - } - - val serverListToolsResult = ListToolsResult( - tools = listOf( - Tool( - name = "testTool", - title = "testTool title", - description = "testTool description", - annotations = null, - inputSchema = Tool.Input(), - outputSchema = null, - ), - ), - nextCursor = null, - ) - - serverSession.setRequestHandler(Method.Defined.ToolsList) { _, _ -> - serverListToolsResult - } - - val serverCapabilities = client.serverCapabilities - assertEquals(ServerCapabilities.Tools(null), serverCapabilities?.tools) - - val request = JSONRPCRequest( - method = Method.Defined.ToolsList.value, - ) - clientTransport.send(request) - - assertIs(receivedMessage) - val receivedAsResponse = receivedMessage as JSONRPCResponse - assertEquals(request.id, receivedAsResponse.id) - assertEquals(request.jsonrpc, receivedAsResponse.jsonrpc) - assertEquals(serverListToolsResult, receivedAsResponse.result) - } - - @Test - fun `listRoots returns list of roots`() = runTest { - val client = Client( - Implementation(name = "test client", version = "1.0"), - ClientOptions( - capabilities = ClientCapabilities( - roots = ClientCapabilities.Roots(null), - ), - ), - ) - - val clientRoots = listOf( - Root(uri = "file:///test-root", name = "testRoot"), - ) - - client.addRoots(clientRoots) - - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - - val server = Server( - serverInfo = Implementation(name = "test server", version = "1.0"), - options = ServerOptions( - capabilities = ServerCapabilities(), - ), - ) - - val serverSessionResult = CompletableDeferred() - - listOf( - launch { - client.connect(clientTransport) - println("Client connected") - }, - launch { - serverSessionResult.complete(server.createSession(serverTransport)) - println("Server connected") - }, - ).joinAll() - - val serverSession = serverSessionResult.await() - - val clientCapabilities = serverSession.clientCapabilities - assertEquals(ClientCapabilities.Roots(null), clientCapabilities?.roots) - - val listRootsResult = serverSession.listRoots() - - assertEquals(listRootsResult.roots, clientRoots) - } - - @Test - fun `addRoot should throw when roots capability is not supported`() = runTest { - val client = Client( - Implementation(name = "test client", version = "1.0"), - ClientOptions( - capabilities = ClientCapabilities(), - ), - ) - - // Verify that adding a root throws an exception - val exception = assertFailsWith { - client.addRoot(uri = "file:///test-root1", name = "testRoot1") - } - assertEquals("Client does not support roots capability.", exception.message) - } - - @Test - fun `removeRoot should throw when roots capability is not supported`() = runTest { - val client = Client( - Implementation(name = "test client", version = "1.0"), - ClientOptions( - capabilities = ClientCapabilities(), - ), - ) - - // Verify that removing a root throws an exception - val exception = assertFailsWith { - client.removeRoot(uri = "file:///test-root1") - } - assertEquals("Client does not support roots capability.", exception.message) - } - - @Test - fun `removeRoot should remove a root`() = runTest { - val client = Client( - Implementation(name = "test client", version = "1.0"), - ClientOptions( - capabilities = ClientCapabilities( - roots = ClientCapabilities.Roots(null), - ), - ), - ) - - // Add some roots - client.addRoots( - listOf( - Root(uri = "file:///test-root1", name = "testRoot1"), - Root(uri = "file:///test-root2", name = "testRoot2"), - ), - ) - - // Remove a root - val result = client.removeRoot("file:///test-root1") - - // Verify the root was removed - assertTrue(result, "Root should be removed successfully") - } - - @Test - fun `removeRoots should remove multiple roots`() = runTest { - val client = Client( - Implementation(name = "test client", version = "1.0"), - ClientOptions( - capabilities = ClientCapabilities( - roots = ClientCapabilities.Roots(null), - ), - ), - ) - - // Add some roots - client.addRoots( - listOf( - Root(uri = "file:///test-root1", name = "testRoot1"), - Root(uri = "file:///test-root2", name = "testRoot2"), - ), - ) - - // Remove multiple roots - val result = client.removeRoots( - listOf("file:///test-root1", "file:///test-root2"), - ) - - // Verify the root was removed - assertEquals(2, result, "Both roots should be removed") - } - - @Test - fun `sendRootsListChanged should notify server`() = runTest { - val client = Client( - Implementation(name = "test client", version = "1.0"), - ClientOptions( - capabilities = ClientCapabilities( - roots = ClientCapabilities.Roots(listChanged = true), - ), - ), - ) - - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - - val server = Server( - serverInfo = Implementation(name = "test server", version = "1.0"), - options = ServerOptions( - capabilities = ServerCapabilities(), - ), - ) - - // Track notifications - var rootListChangedNotificationReceived = false - - val serverSessionResult = CompletableDeferred() - - listOf( - launch { - client.connect(clientTransport) - println("Client connected") - }, - launch { - serverSessionResult.complete(server.createSession(serverTransport)) - println("Server connected") - }, - ).joinAll() - - val serverSession = serverSessionResult.await() - serverSession.setNotificationHandler( - Method.Defined.NotificationsRootsListChanged, - ) { - rootListChangedNotificationReceived = true - CompletableDeferred(Unit) - } - - client.sendRootsListChanged() - - assertTrue( - rootListChangedNotificationReceived, - "Notification should be sent when sendRootsListChanged is called", - ) - } - - @Test - fun `should reject server elicitation when elicitation capability is not supported`() = runTest { - val client = Client( - Implementation(name = "test client", version = "1.0"), - ClientOptions( - capabilities = ClientCapabilities(), - ), - ) - - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - - val server = Server( - serverInfo = Implementation(name = "test server", version = "1.0"), - options = ServerOptions( - capabilities = ServerCapabilities(), - ), - ) - - val serverSessionResult = CompletableDeferred() - - listOf( - launch { - client.connect(clientTransport) - println("Client connected") - }, - launch { - serverSessionResult.complete(server.createSession(serverTransport)) - println("Server connected") - }, - ).joinAll() - - val serverSession = serverSessionResult.await() - - // Verify that creating an elicitation throws an exception - val exception = assertFailsWith { - serverSession.createElicitation( - message = "Please provide your GitHub username", - requestedSchema = CreateElicitationRequest.RequestedSchema( - properties = buildJsonObject { - putJsonObject("name") { - put("type", "string") - } - }, - required = listOf("name"), - ), - ) - } - assertEquals( - "Client does not support elicitation (required for elicitation/create)", - exception.message, - ) - } - - @Test - fun `should handle logging setLevel request`() = runTest { - val server = Server( - Implementation(name = "test server", version = "1.0"), - ServerOptions( - capabilities = ServerCapabilities( - logging = EmptyJsonObject, - ), - ), - ) - - val client = Client( - clientInfo = Implementation(name = "test client", version = "1.0"), - options = ClientOptions( - capabilities = ClientCapabilities(), - ), - ) - - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - - val receivedMessages = mutableListOf() - client.setNotificationHandler( - Method.Defined.NotificationsMessage, - ) { notification -> - receivedMessages.add(notification) - CompletableDeferred(Unit) - } - - val serverSessionResult = CompletableDeferred() - - listOf( - launch { - client.connect(clientTransport) - println("Client connected") - }, - launch { - serverSessionResult.complete(server.connect(serverTransport)) - println("Server connected") - }, - ).joinAll() - - val serverSession = serverSessionResult.await() - - // Set logging level to warning - val minLevel = LoggingLevel.warning - val result = client.setLoggingLevel(minLevel) - assertEquals(null, result.meta) - - // Send messages of different levels - val testMessages = listOf( - LoggingLevel.debug to "Debug - should be filtered", - LoggingLevel.info to "Info - should be filtered", - LoggingLevel.warning to "Warning - should pass", - LoggingLevel.error to "Error - should pass", - ) - - testMessages.forEach { (level, message) -> - serverSession.sendLoggingMessage( - LoggingMessageNotification( - params = LoggingMessageNotification.Params( - level = level, - data = buildJsonObject { put("message", message) }, - ), - ), - ) - } - - delay(100) - - // Only warning and error should be received - assertEquals(2, receivedMessages.size, "Should receive only 2 messages (warning and error)") - - // Verify all received messages have severity >= minLevel - receivedMessages.forEach { message -> - val messageSeverity = message.params.level.ordinal - assertTrue( - messageSeverity >= minLevel.ordinal, - "Received message with level ${message.params.level} should have severity >= $minLevel", - ) - } - } - - @Test - fun `should handle server elicitation`() = runTest { - val client = Client( - Implementation(name = "test client", version = "1.0"), - ClientOptions( - capabilities = ClientCapabilities( - elicitation = EmptyJsonObject, - ), - ), - ) - - val elicitationMessage = "Please provide your GitHub username" - val requestedSchema = CreateElicitationRequest.RequestedSchema( - properties = buildJsonObject { - putJsonObject("name") { - put("type", "string") - } - }, - required = listOf("name"), - ) - - val elicitationResultAction = CreateElicitationResult.Action.accept - val elicitationResultContent = buildJsonObject { - put("name", "octocat") - } - - client.setElicitationHandler { request -> - assertEquals(elicitationMessage, request.params.message) - assertEquals(requestedSchema, request.params.requestedSchema) - - CreateElicitationResult( - action = elicitationResultAction, - content = elicitationResultContent, - ) - } - - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - - val server = Server( - serverInfo = Implementation(name = "test server", version = "1.0"), - options = ServerOptions( - capabilities = ServerCapabilities(), - ), - ) - - val serverSessionResult = CompletableDeferred() - - listOf( - launch { - client.connect(clientTransport) - println("Client connected") - }, - launch { - serverSessionResult.complete(server.createSession(serverTransport)) - println("Server connected") - }, - ).joinAll() - - val serverSession = serverSessionResult.await() - - val result = serverSession.createElicitation( - message = elicitationMessage, - requestedSchema = requestedSchema, - ) - - assertEquals(elicitationResultAction, result.action) - assertEquals(elicitationResultContent, result.content) - } -} diff --git a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaSseTransportTest.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaSseTransportTest.kt deleted file mode 100644 index 3c2d75a..0000000 --- a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaSseTransportTest.kt +++ /dev/null @@ -1,123 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.client - -import io.ktor.client.HttpClient -import io.ktor.server.application.install -import io.ktor.server.cio.CIO -import io.ktor.server.engine.EmbeddedServer -import io.ktor.server.engine.embeddedServer -import io.ktor.server.routing.routing -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities -import io.modelcontextprotocol.kotlin.sdk.server.Server -import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions -import io.modelcontextprotocol.kotlin.sdk.server.mcp -import io.modelcontextprotocol.kotlin.sdk.shared.BaseTransportTest -import kotlinx.coroutines.test.runTest -import kotlin.test.BeforeTest -import kotlin.test.Ignore -import kotlin.test.Test -import io.ktor.client.plugins.sse.SSE as ClientSSE -import io.ktor.server.sse.SSE as ServerSSE - -class OldSchemaSseTransportTest : BaseTransportTest() { - - private suspend fun EmbeddedServer<*, *>.actualPort() = engine.resolvedConnectors().first().port - - private lateinit var mcpServer: Server - - @BeforeTest - fun setUp() { - mcpServer = Server( - serverInfo = Implementation( - name = "test-server", - version = "1.0", - ), - options = ServerOptions(ServerCapabilities()), - ) - } - - @Test - @Ignore // Ignored because it doesn’t work with wasm/js in Ktor 3.2.3 - fun `should start then close cleanly`() = runTest { - val server = embeddedServer(CIO, port = 0) { - install(ServerSSE) - routing { - mcp { mcpServer } - } - }.startSuspend(wait = false) - - val actualPort = server.actualPort() - - val transport = HttpClient { - install(ClientSSE) - }.mcpSseTransport { - url { - host = "localhost" - this.port = actualPort - } - } - - try { - testTransportRead(transport) - } finally { - server.stopSuspend() - } - } - - @Ignore - @Test - fun `should read messages`() = runTest { - val server = embeddedServer(CIO, port = 0) { - install(ServerSSE) - routing { - mcp { mcpServer } - } - }.startSuspend(wait = false) - - val actualPort = server.actualPort() - - val transport = HttpClient { - install(ClientSSE) - }.mcpSseTransport { - url { - host = "localhost" - this.port = actualPort - } - } - - try { - testTransportRead(transport) - } finally { - server.stopSuspend() - } - } - - @Ignore - @Test - fun `test sse path not root path`() = runTest { - val server = embeddedServer(CIO, port = 0) { - install(ServerSSE) - routing { - mcp { mcpServer } - } - }.startSuspend(wait = false) - - val actualPort = server.actualPort() - - val transport = HttpClient { - install(ClientSSE) - }.mcpSseTransport { - url { - host = "localhost" - this.port = actualPort - pathSegments = listOf("sse") - } - } - - try { - testTransportRead(transport) - } finally { - server.stopSuspend() - } - } -} diff --git a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/OldSchemaInMemoryTransportTest.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/OldSchemaInMemoryTransportTest.kt deleted file mode 100644 index 5f31bc1..0000000 --- a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/OldSchemaInMemoryTransportTest.kt +++ /dev/null @@ -1,110 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration - -import io.modelcontextprotocol.kotlin.sdk.InitializedNotification -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage -import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport -import io.modelcontextprotocol.kotlin.sdk.toJSON -import kotlinx.coroutines.test.runTest -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertNotNull -import kotlin.test.assertTrue - -class OldSchemaInMemoryTransportTest { - private lateinit var clientTransport: InMemoryTransport - private lateinit var serverTransport: InMemoryTransport - - @BeforeTest - fun setUp() { - val (client, server) = InMemoryTransport.createLinkedPair() - clientTransport = client - serverTransport = server - } - - @Test - fun `should create linked pair`() { - assertNotNull(clientTransport) - assertNotNull(serverTransport) - } - - @Test - fun `should start without error`() = runTest { - clientTransport.start() - serverTransport.start() - // If no exception is thrown, the test passes - } - - @Test - fun `should send message from client to server`() = runTest { - val message = InitializedNotification() - - var receivedMessage: JSONRPCMessage? = null - serverTransport.onMessage { msg -> - receivedMessage = msg - } - - val rpcNotification = message.toJSON() - clientTransport.send(rpcNotification) - assertEquals(rpcNotification, receivedMessage) - } - - @Test - fun `should send message from server to client`() = runTest { - val message = InitializedNotification() - .toJSON() - - var receivedMessage: JSONRPCMessage? = null - clientTransport.onMessage { msg -> - receivedMessage = msg - } - - serverTransport.send(message) - assertEquals(message, receivedMessage) - } - - @Test - fun `should handle close`() = runTest { - var clientClosed = false - var serverClosed = false - - clientTransport.onClose { - clientClosed = true - } - - serverTransport.onClose { - serverClosed = true - } - - clientTransport.close() - assertTrue(clientClosed) - assertTrue(serverClosed) - } - - @Test - fun `should throw error when sending after close`() = runTest { - clientTransport.close() - - assertFailsWith { - clientTransport.send( - InitializedNotification().toJSON(), - ) - } - } - - @Test - fun `should queue messages sent before start`() = runTest { - val message = InitializedNotification() - .toJSON() - - var receivedMessage: JSONRPCMessage? = null - serverTransport.onMessage { msg -> - receivedMessage = msg - } - - clientTransport.send(message) - serverTransport.start() - assertEquals(message, receivedMessage) - } -} diff --git a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaBaseTransportTest.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaBaseTransportTest.kt deleted file mode 100644 index d2d0402..0000000 --- a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaBaseTransportTest.kt +++ /dev/null @@ -1,63 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.shared - -import io.modelcontextprotocol.kotlin.sdk.InitializedNotification -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage -import io.modelcontextprotocol.kotlin.sdk.PingRequest -import io.modelcontextprotocol.kotlin.sdk.toJSON -import kotlinx.coroutines.CompletableDeferred -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue -import kotlin.test.fail - -abstract class OldSchemaBaseTransportTest { - - protected suspend fun testTransportOpenClose(transport: Transport) { - transport.onError { error -> - fail("Unexpected error: $error") - } - - var didClose = false - transport.onClose { didClose = true } - - transport.start() - assertFalse(didClose, "Transport should not be closed immediately after start") - - transport.close() - assertTrue(didClose, "Transport should be closed after close() call") - } - - protected suspend fun testTransportRead(transport: Transport) { - transport.onError { error -> - error.printStackTrace() - fail("Unexpected error: $error") - } - - val messages = listOf( - PingRequest().toJSON(), - InitializedNotification().toJSON(), - ) - - val readMessages = mutableListOf() - val finished = CompletableDeferred() - - transport.onMessage { message -> - readMessages.add(message) - if (message == messages.last()) { - finished.complete(Unit) - } - } - - transport.start() - - for (message in messages) { - transport.send(message) - } - - finished.await() - - assertEquals(messages, readMessages, "Assert messages received") - - transport.close() - } -} diff --git a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaInMemoryTransport.kt b/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaInMemoryTransport.kt deleted file mode 100644 index 9a0d4d3..0000000 --- a/integration-test/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaInMemoryTransport.kt +++ /dev/null @@ -1,47 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.shared - -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage - -/** - * In-memory transport for creating clients and servers that talk to each other within the same process. - */ -class OldSchemaInMemoryTransport : AbstractTransport() { - private var otherTransport: OldSchemaInMemoryTransport? = null - private val messageQueue: MutableList = mutableListOf() - - /** - * Creates a pair of linked in-memory transports that can communicate with each other. - * One should be passed to a Client and one to a Server. - */ - companion object { - fun createLinkedPair(): Pair { - val clientTransport = OldSchemaInMemoryTransport() - val serverTransport = OldSchemaInMemoryTransport() - clientTransport.otherTransport = serverTransport - serverTransport.otherTransport = clientTransport - return Pair(clientTransport, serverTransport) - } - } - - override suspend fun start() { - // Process any messages that were queued before start was called - while (messageQueue.isNotEmpty()) { - messageQueue.removeFirstOrNull()?.let { message -> - _onMessage.invoke(message) // todo? - } - } - } - - override suspend fun close() { - val other = otherTransport - otherTransport = null - other?.close() - _onClose.invoke() - } - - override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { - val other = otherTransport ?: throw IllegalStateException("Not connected") - - other._onMessage.invoke(message) - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractPromptIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractPromptIntegrationTest.kt deleted file mode 100644 index 1c7c130..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractPromptIntegrationTest.kt +++ /dev/null @@ -1,698 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.kotlin - -import io.modelcontextprotocol.kotlin.sdk.GetPromptRequest -import io.modelcontextprotocol.kotlin.sdk.GetPromptResult -import io.modelcontextprotocol.kotlin.sdk.PromptArgument -import io.modelcontextprotocol.kotlin.sdk.PromptMessage -import io.modelcontextprotocol.kotlin.sdk.Role -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities -import io.modelcontextprotocol.kotlin.sdk.TextContent -import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequestParams -import io.modelcontextprotocol.kotlin.sdk.types.McpException -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertTrue - -abstract class OldSchemaAbstractPromptIntegrationTest : OldSchemaKotlinTestBase() { - - private val basicPromptName = "basic-prompt" - private val basicPromptDescription = "A basic prompt for testing" - - private val complexPromptName = "multimodal-prompt" - private val complexPromptDescription = "A prompt with multiple content types" - private val conversationPromptName = "conversation" - private val conversationPromptDescription = "A prompt with multiple messages and roles" - private val strictPromptName = "strict-prompt" - private val strictPromptDescription = "A prompt with required arguments" - - private val largePromptName = "large-prompt" - private val largePromptDescription = "A very large prompt for testing" - private val largePromptContent = "X".repeat(100_000) // 100KB of data - - private val specialCharsPromptName = "special-chars-prompt" - private val specialCharsPromptDescription = "A prompt with special characters" - private val specialCharsContent = "!@#$%^&*()_+{}|:\"<>?~`-=[]\\;',./\n\t" - - override fun configureServerCapabilities(): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities = - ServerCapabilities( - prompts = ServerCapabilities.Prompts( - listChanged = true, - ), - ) - - override fun configureServer() { - // basic prompt with a name parameter - server.addPrompt( - name = basicPromptName, - description = basicPromptDescription, - arguments = listOf( - PromptArgument( - name = "name", - description = "The name to greet", - required = true, - ), - ), - ) { request -> - val name = request.params.arguments?.get("name") ?: "World" - - GetPromptResult( - description = basicPromptDescription, - messages = listOf( - PromptMessage( - role = Role.user, - content = TextContent(text = "Hello, $name!"), - ), - PromptMessage( - role = Role.assistant, - content = TextContent(text = "Greetings, $name! How can I assist you today?"), - ), - ), - ) - } - - // special chars prompt - server.addPrompt( - name = specialCharsPromptName, - description = specialCharsPromptDescription, - arguments = listOf( - PromptArgument( - name = "special", - description = "Special characters to include", - required = false, - ), - ), - ) { request -> - val special = request.params.arguments?.get("special") ?: specialCharsContent - - GetPromptResult( - description = specialCharsPromptDescription, - messages = listOf( - PromptMessage( - role = Role.user, - content = TextContent(text = "Special characters: $special"), - ), - PromptMessage( - role = Role.assistant, - content = TextContent(text = "Received special characters: $special"), - ), - ), - ) - } - - // very large prompt - server.addPrompt( - name = largePromptName, - description = largePromptDescription, - arguments = listOf( - PromptArgument( - name = "size", - description = "Size multiplier", - required = false, - ), - ), - ) { request -> - val size = request.params.arguments?.get("size")?.toIntOrNull() ?: 1 - val content = largePromptContent.repeat(size) - - GetPromptResult( - description = largePromptDescription, - messages = listOf( - PromptMessage( - role = Role.user, - content = TextContent(text = "Generate a large response"), - ), - PromptMessage( - role = Role.assistant, - content = TextContent(text = content), - ), - ), - ) - } - - // complext prompt - server.addPrompt( - name = complexPromptName, - description = complexPromptDescription, - arguments = listOf( - PromptArgument(name = "arg1", description = "Argument 1", required = true), - PromptArgument(name = "arg2", description = "Argument 2", required = true), - PromptArgument(name = "arg3", description = "Argument 3", required = true), - PromptArgument(name = "arg4", description = "Argument 4", required = false), - PromptArgument(name = "arg5", description = "Argument 5", required = false), - PromptArgument(name = "arg6", description = "Argument 6", required = false), - PromptArgument(name = "arg7", description = "Argument 7", required = false), - PromptArgument(name = "arg8", description = "Argument 8", required = false), - PromptArgument(name = "arg9", description = "Argument 9", required = false), - PromptArgument(name = "arg10", description = "Argument 10", required = false), - ), - ) { request -> - // validate required arguments - val requiredArgs = listOf("arg1", "arg2", "arg3") - for (argName in requiredArgs) { - if (request.params.arguments?.get(argName) == null) { - throw IllegalArgumentException("Missing required argument: $argName") - } - } - - val args = mutableMapOf() - for (i in 1..10) { - val argName = "arg$i" - val argValue = request.params.arguments?.get(argName) - if (argValue != null) { - args[argName] = argValue - } - } - - GetPromptResult( - description = complexPromptDescription, - messages = listOf( - PromptMessage( - role = Role.user, - content = TextContent( - text = "Arguments: ${ - args.entries.joinToString { - "${it.key}=${it.value}" - } - }", - ), - ), - PromptMessage( - role = Role.assistant, - content = TextContent(text = "Received ${args.size} arguments"), - ), - ), - ) - } - - // prompt with multiple messages and roles - server.addPrompt( - name = conversationPromptName, - description = conversationPromptDescription, - arguments = listOf( - PromptArgument( - name = "topic", - description = "The topic of the conversation", - required = false, - ), - ), - ) { request -> - val topic = request.params.arguments?.get("topic") ?: "weather" - - GetPromptResult( - description = conversationPromptDescription, - messages = listOf( - PromptMessage( - role = Role.user, - content = TextContent(text = "Let's talk about the $topic."), - ), - PromptMessage( - role = Role.assistant, - content = TextContent( - text = "Sure, I'd love to discuss the $topic. What would you like to know?", - ), - ), - PromptMessage( - role = Role.user, - content = TextContent(text = "What's your opinion on the $topic?"), - ), - PromptMessage( - role = Role.assistant, - content = TextContent( - text = "As an AI, I don't have personal opinions," + - " but I can provide information about $topic.", - ), - ), - PromptMessage( - role = Role.user, - content = TextContent(text = "That's helpful, thank you!"), - ), - PromptMessage( - role = Role.assistant, - content = TextContent( - text = "You're welcome! Let me know if you have more questions about $topic.", - ), - ), - ), - ) - } - - // prompt with strict required arguments - server.addPrompt( - name = strictPromptName, - description = strictPromptDescription, - arguments = listOf( - PromptArgument( - name = "requiredArg1", - description = "First required argument", - required = true, - ), - PromptArgument( - name = "requiredArg2", - description = "Second required argument", - required = true, - ), - PromptArgument( - name = "optionalArg", - description = "Optional argument", - required = false, - ), - ), - ) { request -> - val args = request.params.arguments ?: emptyMap() - val arg1 = args["requiredArg1"] ?: throw IllegalArgumentException( - "Missing required argument: requiredArg1", - ) - val arg2 = args["requiredArg2"] ?: throw IllegalArgumentException( - "Missing required argument: requiredArg2", - ) - val optArg = args["optionalArg"] ?: "default" - - GetPromptResult( - description = strictPromptDescription, - messages = listOf( - PromptMessage( - role = Role.user, - content = TextContent(text = "Required arguments: $arg1, $arg2. Optional: $optArg"), - ), - PromptMessage( - role = Role.assistant, - content = TextContent(text = "I received your arguments: $arg1, $arg2, and $optArg"), - ), - ), - ) - } - } - - @Test - fun testListPrompts() = runBlocking(Dispatchers.IO) { - val result = client.listPrompts() - - assertNotNull(result, "List prompts result should not be null") - assertTrue(result.prompts.isNotEmpty(), "Prompts list should not be empty") - - val testPrompt = result.prompts.find { it.name == basicPromptName } - assertNotNull(testPrompt, "Test prompt should be in the list") - assertEquals( - basicPromptDescription, - testPrompt.description, - "Prompt description should match", - ) - - val arguments = testPrompt.arguments ?: error("Prompt arguments should not be null") - assertTrue(arguments.isNotEmpty(), "Prompt arguments should not be empty") - - val nameArg = arguments.find { it.name == "name" } - assertNotNull(nameArg, "Name argument should be in the list") - assertEquals( - "The name to greet", - nameArg.description, - "Argument description should match", - ) - assertEquals(true, nameArg.required, "Argument required flag should match") - } - - @Test - fun testGetPrompt() = runBlocking(Dispatchers.IO) { - val testName = "Alice" - val result = client.getPrompt( - GetPromptRequest( - GetPromptRequestParams( - name = basicPromptName, - arguments = mapOf("name" to testName), - ), - ), - ) - - assertNotNull(result, "Get prompt result should not be null") - assertEquals( - basicPromptDescription, - result.description, - "Prompt description should match", - ) - - assertTrue(result.messages.isNotEmpty(), "Prompt messages should not be empty") - assertEquals(2, result.messages.size, "Prompt should have 2 messages") - - val userMessage = result.messages.find { it.role == Role.user } - assertNotNull(userMessage, "User message should be in the list") - val userContent = userMessage.content as? TextContent - assertNotNull(userContent, "User message content should be TextContent") - assertNotNull(userContent.text, "User message text should not be null") - assertEquals( - "Hello, $testName!", - userContent.text, - "User message content should match", - ) - - val assistantMessage = result.messages.find { it.role == Role.assistant } - assertNotNull(assistantMessage, "Assistant message should be in the list") - val assistantContent = assistantMessage.content as? TextContent - assertNotNull(assistantContent, "Assistant message content should be TextContent") - assertNotNull(assistantContent.text, "Assistant message text should not be null") - assertEquals( - "Greetings, $testName! How can I assist you today?", - assistantContent.text, - "Assistant message content should match", - ) - } - - @Test - fun testMissingRequiredArguments() = runBlocking(Dispatchers.IO) { - val promptsList = client.listPrompts() - assertNotNull(promptsList, "Prompts list should not be null") - val strictPrompt = promptsList.prompts.find { it.name == strictPromptName } - assertNotNull(strictPrompt, "Strict prompt should be in the list") - - val argsDef = strictPrompt.arguments ?: error("Prompt arguments should not be null") - val requiredArgs = argsDef.filter { it.required == true } - assertEquals( - 2, - requiredArgs.size, - "Strict prompt should have 2 required arguments", - ) - - // test missing required arg - val exception = assertThrows { - runBlocking { - client.getPrompt( - GetPromptRequest( - GetPromptRequestParams( - name = strictPromptName, - arguments = mapOf("requiredArg1" to "value1"), - ), - ), - ) - } - } - - assertEquals( - true, - exception.message?.contains("requiredArg2"), - "Exception should mention the missing argument", - ) - - // test with no args - val exception2 = assertThrows { - runBlocking { - client.getPrompt( - GetPromptRequest( - GetPromptRequestParams( - name = strictPromptName, - arguments = emptyMap(), - ), - ), - ) - } - } - - assertEquals( - exception2.message?.contains("requiredArg"), - true, - "Exception should mention a missing required argument", - ) - - // test with all required args - val result = client.getPrompt( - GetPromptRequest( - GetPromptRequestParams( - name = strictPromptName, - arguments = mapOf( - "requiredArg1" to "value1", - "requiredArg2" to "value2", - ), - ), - ), - ) - - assertNotNull(result, "Get prompt result should not be null") - assertEquals(2, result.messages.size, "Prompt should have 2 messages") - - val userMessage = result.messages.find { it.role == Role.user } - assertNotNull(userMessage, "User message should be in the list") - val userContent = userMessage.content as? TextContent - assertNotNull(userContent, "User message content should be TextContent") - val userText = requireNotNull(userContent.text) - assertTrue(userText.contains("value1"), "Message should contain first argument") - assertTrue(userText.contains("value2"), "Message should contain second argument") - } - - @Test - fun testMultipleMessagesAndRoles() = runBlocking(Dispatchers.IO) { - val topic = "climate change" - val result = client.getPrompt( - GetPromptRequest( - GetPromptRequestParams( - name = conversationPromptName, - arguments = mapOf("topic" to topic), - ), - ), - ) - - assertNotNull(result, "Get prompt result should not be null") - assertEquals( - conversationPromptDescription, - result.description, - "Prompt description should match", - ) - - assertTrue(result.messages.isNotEmpty(), "Prompt messages should not be empty") - assertEquals(6, result.messages.size, "Prompt should have 6 messages") - - val userMessages = result.messages.filter { it.role == Role.user } - val assistantMessages = result.messages.filter { it.role == Role.assistant } - - assertEquals(3, userMessages.size, "Should have 3 user messages") - assertEquals(3, assistantMessages.size, "Should have 3 assistant messages") - - for (i in 0 until result.messages.size) { - val expectedRole = if (i % 2 == 0) Role.user else Role.assistant - assertEquals( - expectedRole, - result.messages[i].role, - "Message $i should have role $expectedRole", - ) - } - - for (message in result.messages) { - val content = message.content as? TextContent - assertNotNull(content, "Message content should be TextContent") - val text = requireNotNull(content.text) - - // Either the message contains the topic or it's a generic conversation message - val containsTopic = text.contains(topic) - val isGenericMessage = text.contains("thank you") || text.contains("welcome") - - assertTrue( - containsTopic || isGenericMessage, - "Message should either contain the topic or be a generic conversation message", - ) - } - } - - @Test - fun testBasicPrompt() = runBlocking(Dispatchers.IO) { - val testName = "Alice" - val result = client.getPrompt( - GetPromptRequest( - GetPromptRequestParams( - name = basicPromptName, - arguments = mapOf("name" to testName), - ), - ), - ) - - assertNotNull(result, "Get prompt result should not be null") - assertEquals(basicPromptDescription, result.description, "Prompt description should match") - - assertEquals(2, result.messages.size, "Prompt should have 2 messages") - - val userMessage = result.messages.find { it.role == Role.user } - assertNotNull(userMessage, "User message should be in the list") - val userContent = userMessage.content as? TextContent - assertNotNull(userContent, "User message content should be TextContent") - assertEquals("Hello, $testName!", userContent.text, "User message content should match") - - val assistantMessage = result.messages.find { it.role == Role.assistant } - assertNotNull(assistantMessage, "Assistant message should be in the list") - val assistantContent = assistantMessage.content as? TextContent - assertNotNull(assistantContent, "Assistant message content should be TextContent") - assertEquals( - "Greetings, $testName! How can I assist you today?", - assistantContent.text, - "Assistant message content should match", - ) - } - - @Test - fun testComplexPromptWithManyArguments() = runBlocking(Dispatchers.IO) { - val arguments = (1..10).associate { i -> "arg$i" to "value$i" } - - val result = client.getPrompt( - GetPromptRequest( - GetPromptRequestParams( - name = complexPromptName, - arguments = arguments, - ), - ), - ) - - assertNotNull(result, "Get prompt result should not be null") - assertEquals(complexPromptDescription, result.description, "Prompt description should match") - - assertEquals(2, result.messages.size, "Prompt should have 2 messages") - - val userMessage = result.messages.find { it.role == Role.user } - assertNotNull(userMessage, "User message should be in the list") - val userContent = userMessage.content as? TextContent - assertNotNull(userContent, "User message content should be TextContent") - - // verify all arguments - val text = userContent.text ?: "" - for (i in 1..10) { - assertTrue(text.contains("arg$i=value$i"), "Message should contain arg$i=value$i") - } - - val assistantMessage = result.messages.find { it.role == Role.assistant } - assertNotNull(assistantMessage, "Assistant message should be in the list") - val assistantContent = assistantMessage.content as? TextContent - assertNotNull(assistantContent, "Assistant message content should be TextContent") - assertEquals( - "Received 10 arguments", - assistantContent.text, - "Assistant message should indicate 10 arguments", - ) - } - - @Test - fun testLargePrompt() = runBlocking(Dispatchers.IO) { - val result = client.getPrompt( - GetPromptRequest( - GetPromptRequestParams( - name = largePromptName, - arguments = mapOf("size" to "1"), - ), - ), - ) - - assertNotNull(result, "Get prompt result should not be null") - assertEquals(largePromptDescription, result.description, "Prompt description should match") - - assertEquals(2, result.messages.size, "Prompt should have 2 messages") - - val assistantMessage = result.messages.find { it.role == Role.assistant } - assertNotNull(assistantMessage, "Assistant message should be in the list") - val assistantContent = assistantMessage.content as? TextContent - assertNotNull(assistantContent, "Assistant message content should be TextContent") - val text = assistantContent.text ?: "" - assertEquals(100_000, text.length, "Assistant message should be 100KB in size") - } - - @Test - fun testSpecialCharacters() = runBlocking(Dispatchers.IO) { - val result = client.getPrompt( - GetPromptRequest( - GetPromptRequestParams( - name = specialCharsPromptName, - arguments = mapOf("special" to specialCharsContent), - ), - ), - ) - - assertNotNull(result, "Get prompt result should not be null") - assertEquals(specialCharsPromptDescription, result.description, "Prompt description should match") - - assertEquals(2, result.messages.size, "Prompt should have 2 messages") - - val userMessage = result.messages.find { it.role == Role.user } - assertNotNull(userMessage, "User message should be in the list") - val userContent = userMessage.content as? TextContent - assertNotNull(userContent, "User message content should be TextContent") - val userText = userContent.text ?: "" - assertTrue(userText.contains(specialCharsContent), "User message should contain special characters") - - val assistantMessage = result.messages.find { it.role == Role.assistant } - assertNotNull(assistantMessage, "Assistant message should be in the list") - val assistantContent = assistantMessage.content as? TextContent - assertNotNull(assistantContent, "Assistant message content should be TextContent") - val assistantText = assistantContent.text ?: "" - assertTrue( - assistantText.contains(specialCharsContent), - "Assistant message should contain special characters", - ) - } - - @Test - fun testConcurrentPromptRequests() = runTest { - val concurrentCount = 10 - val results = mutableListOf() - - runBlocking { - repeat(concurrentCount) { index -> - launch { - val promptName = when (index % 4) { - 0 -> basicPromptName - 1 -> complexPromptName - 2 -> largePromptName - else -> specialCharsPromptName - } - - val arguments = when (promptName) { - basicPromptName -> mapOf("name" to "User$index") - complexPromptName -> mapOf("arg1" to "v1", "arg2" to "v2", "arg3" to "v3") - largePromptName -> mapOf("size" to "1") - else -> mapOf("special" to "!@#$%^&*()") - } - - val result = client.getPrompt( - GetPromptRequest( - GetPromptRequestParams( - name = promptName, - arguments = arguments, - ), - ), - ) - - synchronized(results) { - results.add(result) - } - } - } - } - - assertEquals(concurrentCount, results.size, "All concurrent operations should complete") - - results.forEach { result -> - assertNotNull(result, "Result should not be null") - assertTrue(result.messages.isNotEmpty(), "Result messages should not be empty") - } - } - - @Test - fun testNonExistentPrompt() = runTest { - val nonExistentPromptName = "non-existent-prompt" - - val exception = assertThrows { - runBlocking { - client.getPrompt( - GetPromptRequest( - GetPromptRequestParams( - name = nonExistentPromptName, - arguments = mapOf("name" to "Test"), - ), - ), - ) - } - } - - val msg = exception.message ?: "" - val expectedMessage = "MCP error -32603: Prompt not found: non-existent-prompt" - - assertEquals(expectedMessage, msg, "Unexpected error message for non-existent prompt") - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractResourceIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractResourceIntegrationTest.kt deleted file mode 100644 index 7cc5f10..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractResourceIntegrationTest.kt +++ /dev/null @@ -1,319 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.kotlin - -import io.modelcontextprotocol.kotlin.sdk.BlobResourceContents -import io.modelcontextprotocol.kotlin.sdk.ReadResourceRequest -import io.modelcontextprotocol.kotlin.sdk.ReadResourceResult -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities -import io.modelcontextprotocol.kotlin.sdk.SubscribeRequest -import io.modelcontextprotocol.kotlin.sdk.TextResourceContents -import io.modelcontextprotocol.kotlin.sdk.UnsubscribeRequest -import io.modelcontextprotocol.kotlin.sdk.types.McpException -import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequestParams -import io.modelcontextprotocol.kotlin.sdk.types.SubscribeRequestParams -import io.modelcontextprotocol.kotlin.sdk.types.UnsubscribeRequestParams -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertNull -import org.junit.jupiter.api.assertThrows -import java.util.concurrent.atomic.AtomicBoolean -import kotlin.test.Ignore -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertTrue - -abstract class OldSchemaAbstractResourceIntegrationTest : OldSchemaKotlinTestBase() { - - private val testResourceUri = "test://example.txt" - private val testResourceName = "Test Resource" - private val testResourceDescription = "A test resource for integration testing" - private val testResourceContent = "This is the content of the test resource." - - private val binaryResourceUri = "test://image.png" - private val binaryResourceName = "Binary Resource" - private val binaryResourceDescription = "A binary resource for testing" - private val binaryResourceContent = - "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==" - - private val largeResourceUri = "test://large.txt" - private val largeResourceName = "Large Resource" - private val largeResourceDescription = "A large text resource for testing" - private val largeResourceContent = "X".repeat(100_000) // 100KB of data - - private val dynamicResourceUri = "test://dynamic.txt" - private val dynamicResourceName = "Dynamic Resource" - private val dynamicResourceContent = AtomicBoolean(false) - - override fun configureServerCapabilities(): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities = - ServerCapabilities( - resources = ServerCapabilities.Resources( - subscribe = true, - listChanged = true, - ), - ) - - override fun configureServer() { - server.addResource( - uri = testResourceUri, - name = testResourceName, - description = testResourceDescription, - mimeType = "text/plain", - ) { request -> - ReadResourceResult( - contents = listOf( - TextResourceContents( - text = testResourceContent, - uri = request.params.uri, - mimeType = "text/plain", - ), - ), - ) - } - - server.addResource( - uri = testResourceUri, - name = testResourceName, - description = testResourceDescription, - mimeType = "text/plain", - ) { request -> - ReadResourceResult( - contents = listOf( - TextResourceContents( - text = testResourceContent, - uri = request.params.uri, - mimeType = "text/plain", - ), - ), - ) - } - - server.addResource( - uri = binaryResourceUri, - name = binaryResourceName, - description = binaryResourceDescription, - mimeType = "image/png", - ) { request -> - ReadResourceResult( - contents = listOf( - BlobResourceContents( - blob = binaryResourceContent, - uri = request.params.uri, - mimeType = "image/png", - ), - ), - ) - } - - server.addResource( - uri = largeResourceUri, - name = largeResourceName, - description = largeResourceDescription, - mimeType = "text/plain", - ) { request -> - ReadResourceResult( - contents = listOf( - TextResourceContents( - text = largeResourceContent, - uri = request.params.uri, - mimeType = "text/plain", - ), - ), - ) - } - - server.addResource( - uri = dynamicResourceUri, - name = dynamicResourceName, - description = "A resource that can be updated", - mimeType = "text/plain", - ) { request -> - ReadResourceResult( - contents = listOf( - TextResourceContents( - text = if (dynamicResourceContent.get()) "Updated content" else "Original content", - uri = request.params.uri, - mimeType = "text/plain", - ), - ), - ) - } - } - - @Test - fun testListResources() = runBlocking(Dispatchers.IO) { - val result = client.listResources() - - assertNotNull(result, "List resources result should not be null") - assertNull(actual = result.meta, "List resources meta should be null") - assertTrue(result.resources.isNotEmpty(), "Resources list should not be empty") - - val testResource = result.resources.find { it.uri == testResourceUri } - assertNotNull(testResource, "Test resource should be in the list") - assertEquals(testResourceName, testResource.name, "Resource name should match") - assertEquals(testResourceDescription, testResource.description, "Resource description should match") - } - - @Test - fun testReadResource() = runBlocking(Dispatchers.IO) { - val result = client.readResource(ReadResourceRequest(ReadResourceRequestParams(uri = testResourceUri))) - - assertNotNull(result, "Read resource result should not be null") - assertNull(actual = result.meta, "Read resource result meta should be null") - assertTrue(result.contents.isNotEmpty(), "Resource contents should not be empty") - - val content = result.contents.firstOrNull() as? TextResourceContents - assertNotNull(content, "Resource content should be TextResourceContents") - assertEquals(testResourceContent, content.text, "Resource content should match") - } - - @Ignore("Blocked by https://github.com/modelcontextprotocol/kotlin-sdk/issues/249") - @Test - fun testSubscribeAndUnsubscribe() { - runBlocking(Dispatchers.IO) { - val subscribeResult = - client.subscribeResource(SubscribeRequest(SubscribeRequestParams(uri = testResourceUri))) - assertNotNull(subscribeResult, "Subscribe result should not be null") - - val unsubscribeResult = - client.unsubscribeResource(UnsubscribeRequest(UnsubscribeRequestParams(uri = testResourceUri))) - assertNotNull(unsubscribeResult, "Unsubscribe result should not be null") - } - } - - @Test - fun testBinaryResource() = runBlocking(Dispatchers.IO) { - val result = client.readResource(ReadResourceRequest(ReadResourceRequestParams(uri = binaryResourceUri))) - - assertNotNull(result, "Read resource result should not be null") - assertTrue(result.contents.isNotEmpty(), "Resource contents should not be empty") - - val content = result.contents.firstOrNull() as? BlobResourceContents - assertNotNull(content, "Resource content should be BlobResourceContents") - assertEquals(binaryResourceContent, content.blob, "Binary resource content should match") - assertEquals("image/png", content.mimeType, "MIME type should match") - } - - @Test - fun testLargeResource() = runBlocking(Dispatchers.IO) { - val result = client.readResource(ReadResourceRequest(ReadResourceRequestParams(uri = largeResourceUri))) - - assertNotNull(result, "Read resource result should not be null") - assertTrue(result.contents.isNotEmpty(), "Resource contents should not be empty") - - val content = result.contents.firstOrNull() as? TextResourceContents - assertNotNull(content, "Resource content should be TextResourceContents") - assertEquals(100_000, content.text.length, "Large resource content length should match") - assertEquals("X".repeat(100_000), content.text, "Large resource content should match") - } - - @Test - fun testInvalidResourceUri() = runTest { - val invalidUri = "test://nonexistent.txt" - - val exception = assertThrows { - runBlocking { - client.readResource(ReadResourceRequest(ReadResourceRequestParams(uri = invalidUri))) - } - } - - val msg = exception.message ?: "" - val expectedMessage = - "MCP error -32603: Resource not found: test://nonexistent.txt" - - assertEquals(expectedMessage, msg, "Unexpected error message for invalid resource URI") - } - - @Test - fun testDynamicResource() = runBlocking(Dispatchers.IO) { - val initialResult = client.readResource( - ReadResourceRequest(ReadResourceRequestParams(uri = dynamicResourceUri)), - ) - assertNotNull(initialResult, "Initial read result should not be null") - val initialContent = (initialResult.contents.firstOrNull() as? TextResourceContents)?.text - assertEquals("Original content", initialContent, "Initial content should match") - - // update resource - dynamicResourceContent.set(true) - - val updatedResult = client.readResource( - ReadResourceRequest(ReadResourceRequestParams(uri = dynamicResourceUri)), - ) - assertNotNull(updatedResult, "Updated read result should not be null") - val updatedContent = (updatedResult.contents.firstOrNull() as? TextResourceContents)?.text - assertEquals("Updated content", updatedContent, "Updated content should match") - } - - @Test - fun testResourceAddAndRemove() = runBlocking(Dispatchers.IO) { - val initialList = client.listResources() - assertNotNull(initialList, "Initial list result should not be null") - val initialCount = initialList.resources.size - - val newResourceUri = "test://new-resource.txt" - server.addResource( - uri = newResourceUri, - name = "New Resource", - description = "A newly added resource", - mimeType = "text/plain", - ) { request -> - ReadResourceResult( - contents = listOf( - TextResourceContents( - text = "New resource content", - uri = request.params.uri, - mimeType = "text/plain", - ), - ), - ) - } - - val updatedList = client.listResources() - assertNotNull(updatedList, "Updated list result should not be null") - val updatedCount = updatedList.resources.size - - assertEquals(initialCount + 1, updatedCount, "Resource count should increase by 1") - val newResource = updatedList.resources.find { it.uri == newResourceUri } - assertNotNull(newResource, "New resource should be in the list") - - server.removeResource(newResourceUri) - - val finalList = client.listResources() - assertNotNull(finalList, "Final list result should not be null") - val finalCount = finalList.resources.size - - assertEquals(initialCount, finalCount, "Resource count should return to initial value") - val removedResource = finalList.resources.find { it.uri == newResourceUri } - assertEquals(null, removedResource, "Resource should be removed from the list") - } - - @Test - fun testConcurrentResourceOperations() = runTest { - val concurrentCount = 10 - val results = mutableListOf() - - runBlocking { - repeat(concurrentCount) { index -> - launch { - val uri = when (index % 3) { - 0 -> testResourceUri - 1 -> binaryResourceUri - else -> largeResourceUri - } - - val result = client.readResource(ReadResourceRequest(ReadResourceRequestParams(uri = uri))) - synchronized(results) { - results.add(result) - } - } - } - } - - assertEquals(concurrentCount, results.size, "All concurrent operations should complete") - results.forEach { result -> - assertNotNull(result, "Result should not be null") - assertTrue(result.contents.isNotEmpty(), "Result contents should not be empty") - } - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractToolIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractToolIntegrationTest.kt deleted file mode 100644 index cd12d79..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaAbstractToolIntegrationTest.kt +++ /dev/null @@ -1,820 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.kotlin - -import io.kotest.assertions.json.shouldEqualJson -import io.modelcontextprotocol.kotlin.sdk.CallToolRequest -import io.modelcontextprotocol.kotlin.sdk.CallToolResult -import io.modelcontextprotocol.kotlin.sdk.ImageContent -import io.modelcontextprotocol.kotlin.sdk.PromptMessageContent -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities -import io.modelcontextprotocol.kotlin.sdk.TextContent -import io.modelcontextprotocol.kotlin.sdk.Tool -import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequestParams -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.runTest -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.add -import kotlinx.serialization.json.buildJsonArray -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.put -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertNull -import java.text.DecimalFormat -import java.text.DecimalFormatSymbols -import java.util.Locale -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertTrue - -abstract class OldSchemaAbstractToolIntegrationTest : OldSchemaKotlinTestBase() { - private val testToolName = "echo" - private val testToolDescription = "A simple echo tool that returns the input text" - private val complexToolName = "calculator" - private val complexToolDescription = "A calculator tool that performs operations on numbers" - private val errorToolName = "error-tool" - private val errorToolDescription = "A tool that demonstrates error handling" - private val multiContentToolName = "multi-content" - private val multiContentToolDescription = "A tool that returns multiple content types" - - private val basicToolName = "basic-tool" - private val basicToolDescription = "A basic tool for testing" - - private val largeToolName = "large-tool" - private val largeToolDescription = "A tool that returns a large response" - private val largeToolContent = "X".repeat(100_000) // 100KB of data - - private val slowToolName = "slow-tool" - private val slowToolDescription = "A tool that takes time to respond" - - private val specialCharsToolName = "special-chars-tool" - private val specialCharsToolDescription = "A tool that handles special characters" - private val specialCharsContent = "!@#$%^&*()_+{}|:\"<>?~`-=[]\\;',./\n\t" - - override fun configureServerCapabilities(): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities = - ServerCapabilities( - tools = ServerCapabilities.Tools( - listChanged = true, - ), - ) - - override fun configureServer() { - setupEchoTool() - setupCalculatorTool() - setupErrorHandlingTool() - setupMultiContentTool() - } - - private fun setupEchoTool() { - server.addTool( - name = testToolName, - description = testToolDescription, - inputSchema = Tool.Input( - properties = buildJsonObject { - put( - "text", - buildJsonObject { - put("type", "string") - put("description", "The text to echo back") - }, - ) - }, - required = listOf("text"), - ), - ) { request -> - val text = (request.params.arguments?.get("text") as? JsonPrimitive)?.content ?: "No text provided" - - CallToolResult( - content = listOf(TextContent(text = "Echo: $text")), - structuredContent = buildJsonObject { - put("result", text) - }, - ) - } - } - - private fun setupCalculatorTool() { - server.addTool( - name = basicToolName, - description = basicToolDescription, - inputSchema = Tool.Input( - properties = buildJsonObject { - put( - "text", - buildJsonObject { - put("type", "string") - put("description", "The text to echo back") - }, - ) - }, - required = listOf("text"), - ), - ) { request -> - val text = (request.params.arguments?.get("text") as? JsonPrimitive)?.content ?: "No text provided" - - CallToolResult( - content = listOf(TextContent(text = "Echo: $text")), - structuredContent = buildJsonObject { - put("result", text) - }, - ) - } - - server.addTool( - name = specialCharsToolName, - description = specialCharsToolDescription, - inputSchema = Tool.Input( - properties = buildJsonObject { - put( - "special", - buildJsonObject { - put("type", "string") - put("description", "Special characters to process") - }, - ) - }, - ), - ) { request -> - val special = (request.params.arguments?.get("special") as? JsonPrimitive)?.content ?: specialCharsContent - - CallToolResult( - content = listOf(TextContent(text = "Received special characters: $special")), - structuredContent = buildJsonObject { - put("special", special) - put("length", special.length) - }, - ) - } - - server.addTool( - name = slowToolName, - description = slowToolDescription, - inputSchema = Tool.Input( - properties = buildJsonObject { - put( - "delay", - buildJsonObject { - put("type", "integer") - put("description", "Delay in milliseconds") - }, - ) - }, - ), - ) { request -> - val delay = (request.params.arguments?.get("delay") as? JsonPrimitive)?.content?.toIntOrNull() ?: 1000 - - // simulate slow operation - runBlocking { - delay(delay.toLong()) - } - - CallToolResult( - content = listOf(TextContent(text = "Completed after ${delay}ms delay")), - structuredContent = buildJsonObject { - put("delay", delay) - }, - ) - } - - server.addTool( - name = largeToolName, - description = largeToolDescription, - inputSchema = Tool.Input( - properties = buildJsonObject { - put( - "size", - buildJsonObject { - put("type", "integer") - put("description", "Size multiplier") - }, - ) - }, - ), - ) { request -> - val size = (request.params.arguments?.get("size") as? JsonPrimitive)?.content?.toIntOrNull() ?: 1 - val content = largeToolContent.take(largeToolContent.length.coerceAtMost(size * 1000)) - - CallToolResult( - content = listOf(TextContent(text = content)), - structuredContent = buildJsonObject { - put("size", content.length) - }, - ) - } - - server.addTool( - name = complexToolName, - description = complexToolDescription, - inputSchema = Tool.Input( - properties = buildJsonObject { - put( - "operation", - buildJsonObject { - put("type", "string") - put("description", "The operation to perform (add, subtract, multiply, divide)") - put( - "enum", - buildJsonArray { - add("add") - add("subtract") - add("multiply") - add("divide") - }, - ) - }, - ) - put( - "a", - buildJsonObject { - put("type", "number") - put("description", "First operand") - }, - ) - put( - "b", - buildJsonObject { - put("type", "number") - put("description", "Second operand") - }, - ) - put( - "precision", - buildJsonObject { - put("type", "integer") - put("description", "Number of decimal places (optional)") - put("default", 2) - }, - ) - put( - "showSteps", - buildJsonObject { - put("type", "boolean") - put("description", "Whether to show calculation steps") - put("default", false) - }, - ) - put( - "tags", - buildJsonObject { - put("type", "array") - put("description", "Optional tags for the calculation") - put( - "items", - buildJsonObject { - put("type", "string") - }, - ) - }, - ) - }, - required = listOf("operation", "a", "b"), - ), - ) { request -> - val operation = (request.params.arguments?.get("operation") as? JsonPrimitive)?.content ?: "add" - val a = (request.params.arguments?.get("a") as? JsonPrimitive)?.content?.toDoubleOrNull() ?: 0.0 - val b = (request.params.arguments?.get("b") as? JsonPrimitive)?.content?.toDoubleOrNull() ?: 0.0 - val precision = (request.params.arguments?.get("precision") as? JsonPrimitive)?.content?.toIntOrNull() ?: 2 - val showSteps = - (request.params.arguments?.get("showSteps") as? JsonPrimitive)?.content?.toBoolean() ?: false - val tags = (request.params.arguments?.get("tags") as? JsonArray)?.mapNotNull { - (it as? JsonPrimitive)?.content - } ?: emptyList() - - val result = when (operation) { - "add" -> a + b - "subtract" -> a - b - "multiply" -> a * b - "divide" -> if (b != 0.0) a / b else Double.POSITIVE_INFINITY - else -> 0.0 - } - - val pattern = if (precision > 0) "0." + "0".repeat(precision) else "0" - val symbols = DecimalFormatSymbols(Locale.US).apply { decimalSeparator = '.' } - val df = DecimalFormat(pattern, symbols).apply { isGroupingUsed = false } - val formattedResult = df.format(result) - - val textContent = if (showSteps) { - "Operation: $operation\nA: $a\nB: $b\nResult: $formattedResult\nTags: ${ - tags.joinToString(", ") - }" - } else { - "Result: $formattedResult" - } - - CallToolResult( - content = listOf(TextContent(text = textContent)), - structuredContent = buildJsonObject { - put("operation", operation) - put("a", a) - put("b", b) - put("result", result) - put("formattedResult", formattedResult) - put("precision", precision) - put("tags", buildJsonArray { tags.forEach { add(it) } }) - }, - ) - } - } - - private fun setupErrorHandlingTool() { - server.addTool( - name = errorToolName, - description = errorToolDescription, - inputSchema = Tool.Input( - properties = buildJsonObject { - put( - "errorType", - buildJsonObject { - put("type", "string") - put("description", "Type of error to simulate (none, exception, error)") - put( - "enum", - buildJsonArray { - add("none") - add("exception") - add("error") - }, - ) - }, - ) - put( - "message", - buildJsonObject { - put("type", "string") - put("description", "Custom error message") - put("default", "An error occurred") - }, - ) - }, - required = listOf("errorType"), - ), - ) { request -> - val errorType = (request.params.arguments?.get("errorType") as? JsonPrimitive)?.content ?: "none" - val message = (request.params.arguments?.get("message") as? JsonPrimitive)?.content ?: "An error occurred" - - when (errorType) { - "exception" -> throw IllegalArgumentException(message) - - "error" -> CallToolResult( - content = listOf(TextContent(text = "Error: $message")), - structuredContent = buildJsonObject { - put("error", true) - put("message", message) - }, - ) - - else -> CallToolResult( - content = listOf(TextContent(text = "No error occurred")), - structuredContent = buildJsonObject { - put("error", false) - put("message", "Success") - }, - ) - } - } - } - - private fun setupMultiContentTool() { - server.addTool( - name = multiContentToolName, - description = multiContentToolDescription, - inputSchema = Tool.Input( - properties = buildJsonObject { - put( - "text", - buildJsonObject { - put("type", "string") - put("description", "Text to include in the response") - }, - ) - put( - "includeImage", - buildJsonObject { - put("type", "boolean") - put("description", "Whether to include an image in the response") - put("default", true) - }, - ) - }, - required = listOf("text"), - ), - ) { request -> - val text = (request.params.arguments?.get("text") as? JsonPrimitive)?.content ?: "Default text" - val includeImage = - (request.params.arguments?.get("includeImage") as? JsonPrimitive)?.content?.toBoolean() ?: true - - val content = mutableListOf( - TextContent(text = "Text content: $text"), - ) - - if (includeImage) { - content.add( - ImageContent( - data = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==", - mimeType = "image/png", - ), - ) - } - - CallToolResult( - content = content, - structuredContent = buildJsonObject { - put("text", text) - put("includeImage", includeImage) - }, - ) - } - } - - @Test - fun testListTools(): Unit = runBlocking(Dispatchers.IO) { - val result = client.listTools() - - assertNotNull(result, "List tools result should not be null") - assertNull(actual = result.meta, "List tools result meta should be null") - assertTrue(result.tools.isNotEmpty(), "Tools list should not be empty") - - val testTool = result.tools.find { it.name == testToolName } - - assertNotNull(testTool, "Test tool should be in the list") - assertEquals( - testToolDescription, - testTool.description, - "Tool description should match", - ) - } - - @Test - fun testCallTool(): Unit = runBlocking(Dispatchers.IO) { - val testText = "Hello, world!" - val arguments = mapOf("text" to testText) - - val result = client.callTool(testToolName, arguments) - assertNull(actual = result.meta, "CallTool result meta should be null") - - val actualContent = result.structuredContent.toString() - val expectedContent = """ - {"result":"Hello, world!"} - """.trimIndent() - - actualContent shouldEqualJson expectedContent - } - - @Test - fun testComplexInputSchemaTool(): Unit = runBlocking(Dispatchers.IO) { - val toolsList = client.listTools() - assertNotNull(toolsList, "Tools list should not be null") - val calculatorTool = toolsList.tools.find { it.name == complexToolName } - assertNotNull(calculatorTool, "Calculator tool should be in the list") - - val arguments = mapOf( - "operation" to "multiply", - "a" to 5.5, - "b" to 2.0, - "precision" to 3, - "showSteps" to true, - "tags" to listOf("test", "calculator", "integration"), - ) - - val result = client.callTool( - complexToolName, - arguments, - ) - - val actualContent = result.structuredContent.toString() - val expectedContent = """ - { - "operation" : "multiply", - "a" : 5.5, - "b" : 2.0, - "result" : 11.0, - "formattedResult" : "11.000", - "precision" : 3, - "tags" : ["test", "calculator", "integration"] - } - """.trimIndent() - - actualContent shouldEqualJson expectedContent - } - - @Test - fun testToolErrorHandling(): Unit = runBlocking(Dispatchers.IO) { - val successArgs = mapOf("errorType" to "none") - val successResult = client.callTool(errorToolName, successArgs) - - val actualContent = successResult?.structuredContent.toString() - val expectedContent = """ - { - "error" : false, - "message" : "Success" - } - """.trimIndent() - - actualContent shouldEqualJson expectedContent - - val errorArgs = mapOf( - "errorType" to "error", - "message" to "Custom error message", - ) - val errorResult = client.callTool( - errorToolName, - errorArgs, - ) - - val actualError = errorResult.structuredContent.toString() - val expectedError = """ - { - "error" : true, - "message" : "Custom error message" - } - """.trimIndent() - - actualError shouldEqualJson expectedError - - val exceptionArgs = mapOf( - "errorType" to "exception", - "message" to "My exception message", - ) - - val exceptionResult = client.callTool( - errorToolName, - exceptionArgs, - ) - - assertTrue(exceptionResult.isError ?: false, "isError should be true for exception") - - val exceptionContent = exceptionResult.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(exceptionContent, "Error content should be present in the result") - - val exceptionText = exceptionContent.text ?: "" - assertTrue( - exceptionText.contains("Error executing tool") && exceptionText.contains("My exception message"), - "Error message should contain the exception details", - ) - } - - @Test - fun testMultiContentTool(): Unit = runBlocking(Dispatchers.IO) { - val testText = "Test multi-content" - val arguments = mapOf( - "text" to testText, - "includeImage" to true, - ) - - val result = client.callTool( - multiContentToolName, - arguments, - ) - - assertEquals( - 2, - result.content.size, - "Tool result should have 2 content items", - ) - - val textContent = result.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Result should contain TextContent") - assertNotNull(textContent.text, "Text content should not be null") - assertEquals( - "Text content: $testText", - textContent.text, - "Text content should match", - ) - - val imageContent = result.content.firstOrNull { it is ImageContent } as? ImageContent - assertNotNull(imageContent, "Result should contain ImageContent") - assertEquals("image/png", imageContent.mimeType, "Image MIME type should match") - assertTrue(imageContent.data.isNotEmpty(), "Image data should not be empty") - - val actualContent = result.structuredContent.toString() - val expectedContent = """ - { - "text" : "Test multi-content", - "includeImage" : true - } - """.trimIndent() - - actualContent shouldEqualJson expectedContent - - val textOnlyArgs = mapOf( - "text" to testText, - "includeImage" to false, - ) - - val textOnlyResult = client.callTool( - multiContentToolName, - textOnlyArgs, - ) - - assertEquals( - 1, - textOnlyResult.content.size, - "Text-only result should have 1 content item", - ) - } - - @Test - fun testComplexNestedSchema(): Unit = runBlocking(Dispatchers.IO) { - val userJson = buildJsonObject { - put("name", JsonPrimitive("John Galt")) - put("age", JsonPrimitive(30)) - put( - "address", - buildJsonObject { - put("street", JsonPrimitive("123 Main St")) - put("city", JsonPrimitive("New York")) - put("country", JsonPrimitive("USA")) - }, - ) - } - - val optionsJson = buildJsonArray { - add(JsonPrimitive("option1")) - add(JsonPrimitive("option2")) - add(JsonPrimitive("option3")) - } - - val arguments = buildJsonObject { - put("user", userJson) - put("options", optionsJson) - } - - val result = client.callTool( - CallToolRequest( - CallToolRequestParams( - name = complexToolName, - arguments = arguments, - ), - ), - ) - - val actualContent = result.structuredContent.toString() - val expectedContent = """ - { - "operation": "add", - "a": 0.0, - "b": 0.0, - "result": 0.0, - "formattedResult": "0.00", - "precision": 2, - "tags": [] - } - """.trimIndent() - - actualContent shouldEqualJson expectedContent - } - - @Test - fun testLargeResponse(): Unit = runBlocking(Dispatchers.IO) { - val size = 10 - val arguments = mapOf("size" to size) - - val result = client.callTool( - largeToolName, - arguments, - ) - - val content = result.content.firstOrNull() as TextContent - assertNotNull(content, "Tool result content should be TextContent") - - val actualContent = result.structuredContent.toString() - val expectedContent = """ - { - "size" : 10000 - } - """.trimIndent() - - actualContent shouldEqualJson expectedContent - } - - @Test - fun testSlowTool(): Unit = runBlocking(Dispatchers.IO) { - val delay = 500 - val arguments = mapOf("delay" to delay) - - val startTime = System.currentTimeMillis() - val result = client.callTool(slowToolName, arguments) - val endTime = System.currentTimeMillis() - - val content = result.content.firstOrNull() as? TextContent - assertNotNull(content, "Tool result content should be TextContent") - - assertTrue(endTime - startTime >= delay, "Tool should take at least the specified delay") - - val actualContent = result.structuredContent.toString() - val expectedContent = """ - { - "delay" : 500 - } - """.trimIndent() - - actualContent shouldEqualJson expectedContent - } - - @Test - fun testSpecialCharacters() { - runBlocking(Dispatchers.IO) { - val arguments = mapOf("special" to specialCharsContent) - - val result = client.callTool( - specialCharsToolName, - arguments, - ) - - val content = result.content.firstOrNull() as? TextContent - assertNotNull(content, "Tool result content should be TextContent") - val text = content.text ?: "" - - assertTrue(text.contains(specialCharsContent), "Result should contain the special characters") - - val actualContent = result.structuredContent.toString() - val expectedContent = """ - { - "special" : "!@#$%^&*()_+{}|:\"<>?~`-=[]\\;',./\n\t", - "length" : 34 - } - """.trimIndent() - - actualContent shouldEqualJson expectedContent - } - } - - @Test - fun testConcurrentToolCalls() = runTest { - val concurrentCount = 10 - val results = mutableListOf() - - runBlocking { - repeat(concurrentCount) { index -> - launch { - val toolName = when (index % 5) { - 0 -> basicToolName - 1 -> complexToolName - 2 -> largeToolName - 3 -> slowToolName - else -> specialCharsToolName - } - - val arguments = when (toolName) { - basicToolName -> mapOf("text" to "Concurrent call $index") - - complexToolName -> mapOf( - "user" to mapOf( - "name" to "User $index", - "age" to 20 + index, - "address" to mapOf( - "street" to "Street $index", - "city" to "City $index", - "country" to "Country $index", - ), - ), - ) - - largeToolName -> mapOf("size" to 1) - - slowToolName -> mapOf("delay" to 100) - - else -> mapOf("special" to "!@#$%^&*()") - } - - val result = client.callTool(toolName, arguments) - - synchronized(results) { - results.add(result) - } - } - } - } - - assertEquals(concurrentCount, results.size, "All concurrent operations should complete") - results.forEach { result -> - assertNotNull(result, "Result should not be null") - assertTrue(result.content.isNotEmpty(), "Result content should not be empty") - } - } - - @Test - fun testNonExistentTool() = runTest { - val nonExistentToolName = "non-existent-tool" - val arguments = mapOf("text" to "Test") - - val result = runBlocking { - client.callTool(nonExistentToolName, arguments) - } - - assertNotNull(result, "Tool call result should not be null") - val callResult = result - assertTrue(callResult.isError ?: false, "isError should be true for non-existent tool") - - val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Error content should be present in the result") - - val errorText = textContent.text ?: "" - assertTrue( - errorText.contains("non-existent-tool") && errorText.contains("not found"), - "Error message should indicate the tool was not found", - ) - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaKotlinTestBase.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaKotlinTestBase.kt deleted file mode 100644 index 241172e..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/OldSchemaKotlinTestBase.kt +++ /dev/null @@ -1,184 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.kotlin - -import io.ktor.client.HttpClient -import io.ktor.client.engine.cio.CIO -import io.ktor.client.plugins.sse.SSE -import io.ktor.server.application.install -import io.ktor.server.engine.EmbeddedServer -import io.ktor.server.engine.embeddedServer -import io.ktor.server.routing.routing -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.client.SseClientTransport -import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport -import io.modelcontextprotocol.kotlin.sdk.server.Server -import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions -import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport -import io.modelcontextprotocol.kotlin.sdk.server.mcp -import io.modelcontextprotocol.kotlin.test.utils.Retry -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withTimeout -import kotlinx.io.Sink -import kotlinx.io.Source -import kotlinx.io.asSink -import kotlinx.io.asSource -import kotlinx.io.buffered -import org.awaitility.kotlin.await -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.BeforeEach -import java.io.PipedInputStream -import java.io.PipedOutputStream -import kotlin.time.Duration.Companion.seconds -import io.ktor.server.cio.CIO as ServerCIO -import io.ktor.server.sse.SSE as ServerSSE - -@Retry(times = 3) -abstract class OldSchemaKotlinTestBase { - - protected val host = "localhost" - protected var port: Int = 0 - - protected lateinit var server: Server - protected lateinit var client: Client - protected lateinit var serverEngine: EmbeddedServer<*, *> - - // Transport selection - protected enum class TransportKind { SSE, STDIO } - protected open val transportKind: TransportKind = TransportKind.STDIO - - // STDIO-specific fields - private var stdioServerTransport: StdioServerTransport? = null - private var stdioClientInput: Source? = null - private var stdioClientOutput: Sink? = null - - protected abstract fun configureServerCapabilities(): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities - protected abstract fun configureServer() - - @BeforeEach - fun setUp() { - setupServer() - if (transportKind == TransportKind.SSE) { - await - .ignoreExceptions() - .until { - port = runBlocking { serverEngine.engine.resolvedConnectors().first().port } - port != 0 - } - } - runBlocking { - setupClient() - } - } - - protected suspend fun setupClient() { - when (transportKind) { - TransportKind.SSE -> { - val transport = SseClientTransport( - HttpClient(CIO) { - install(SSE) - }, - "http://$host:$port", - ) - client = Client( - Implementation("test", "1.0"), - ) - client.connect(transport) - } - - TransportKind.STDIO -> { - val input = checkNotNull(stdioClientInput) { "STDIO client input not initialized" } - val output = checkNotNull(stdioClientOutput) { "STDIO client output not initialized" } - val transport = StdioClientTransport( - input = input, - output = output, - ) - client = Client( - Implementation("test", "1.0"), - ) - client.connect(transport) - } - } - } - - protected fun setupServer() { - val capabilities = configureServerCapabilities() - - server = Server( - Implementation(name = "test-server", version = "1.0"), - ServerOptions(capabilities = capabilities), - ) - - configureServer() - - if (transportKind == TransportKind.SSE) { - serverEngine = embeddedServer(ServerCIO, host = host, port = port) { - install(ServerSSE) - routing { - mcp { server } - } - }.start(wait = false) - } else { - // Create in-memory stdio pipes: client->server and server->client - val clientToServerOut = PipedOutputStream() - val clientToServerIn = PipedInputStream(clientToServerOut) - - val serverToClientOut = PipedOutputStream() - val serverToClientIn = PipedInputStream(serverToClientOut) - - // Server transport reads from client and writes to client - val serverTransport = StdioServerTransport( - inputStream = clientToServerIn.asSource().buffered(), - outputStream = serverToClientOut.asSink().buffered(), - ) - stdioServerTransport = serverTransport - - // Prepare client-side streams for later client initialization - stdioClientInput = serverToClientIn.asSource().buffered() - stdioClientOutput = clientToServerOut.asSink().buffered() - - // Start server transport by connecting the server - runBlocking { - server.createSession(serverTransport) - } - } - } - - @AfterEach - fun tearDown() { - // close client - if (::client.isInitialized) { - try { - runBlocking { - withTimeout(3.seconds) { - client.close() - } - } - } catch (e: Exception) { - println("Warning: Error during client close: ${e.message}") - } - } - - // stop server - if (transportKind == TransportKind.SSE) { - if (::serverEngine.isInitialized) { - try { - serverEngine.stop(500, 1000) - } catch (e: Exception) { - println("Warning: Error during server stop: ${e.message}") - } - } - } else { - stdioServerTransport?.let { - try { - runBlocking { it.close() } - } catch (e: Exception) { - println("Warning: Error during stdio server stop: ${e.message}") - } finally { - stdioServerTransport = null - stdioClientInput = null - stdioClientOutput = null - } - } - } - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaPromptIntegrationTestSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaPromptIntegrationTestSse.kt deleted file mode 100644 index 63d5865..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaPromptIntegrationTestSse.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.sse - -import io.modelcontextprotocol.kotlin.sdk.integration.kotlin.OldSchemaAbstractPromptIntegrationTest - -class OldSchemaPromptIntegrationTestSse : OldSchemaAbstractPromptIntegrationTest() { - override val transportKind: TransportKind = TransportKind.SSE -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaResourceIntegrationTestSse b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaResourceIntegrationTestSse deleted file mode 100644 index 6837753..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaResourceIntegrationTestSse +++ /dev/null @@ -1,7 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.sse - -import io.modelcontextprotocol.kotlin.sdk.integration.kotlin.OldSchemaAbstractResourceIntegrationTest - -class OldSchemaResourceIntegrationTestSse : OldSchemaAbstractResourceIntegrationTest() { - override val transportKind: TransportKind = TransportKind.SSE -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaToolIntegrationTestSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaToolIntegrationTestSse.kt deleted file mode 100644 index bd39d77..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/sse/OldSchemaToolIntegrationTestSse.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.sse - -import io.modelcontextprotocol.kotlin.sdk.integration.kotlin.OldSchemaAbstractToolIntegrationTest - -class OldSchemaToolIntegrationTestSse : OldSchemaAbstractToolIntegrationTest() { - override val transportKind: TransportKind = TransportKind.SSE -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaPromptIntegrationTestStdio.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaPromptIntegrationTestStdio.kt deleted file mode 100644 index 68d03eb..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaPromptIntegrationTestStdio.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.stdio - -import io.modelcontextprotocol.kotlin.sdk.integration.kotlin.OldSchemaAbstractPromptIntegrationTest - -class OldSchemaPromptIntegrationTestStdio : OldSchemaAbstractPromptIntegrationTest() { - override val transportKind: TransportKind = TransportKind.STDIO -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaResourceIntegrationTestStdio.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaResourceIntegrationTestStdio.kt deleted file mode 100644 index 4b07760..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaResourceIntegrationTestStdio.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.stdio - -import io.modelcontextprotocol.kotlin.sdk.integration.kotlin.OldSchemaAbstractResourceIntegrationTest - -class OldSchemaResourceIntegrationTestStdio : OldSchemaAbstractResourceIntegrationTest() { - override val transportKind: TransportKind = TransportKind.STDIO -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaToolIntegrationTestStdio.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaToolIntegrationTestStdio.kt deleted file mode 100644 index 046eebf..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/stdio/OldSchemaToolIntegrationTestStdio.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.stdio - -import io.modelcontextprotocol.kotlin.sdk.integration.kotlin.OldSchemaAbstractToolIntegrationTest - -class OldSchemaToolIntegrationTestStdio : OldSchemaAbstractToolIntegrationTest() { - override val transportKind: TransportKind = TransportKind.STDIO -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/OldSchemaWebSocketIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/OldSchemaWebSocketIntegrationTest.kt deleted file mode 100644 index 312e3be..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/websocket/OldSchemaWebSocketIntegrationTest.kt +++ /dev/null @@ -1,193 +0,0 @@ -@file:Suppress("DEPRECATION") - -package io.modelcontextprotocol.kotlin.sdk.integration.kotlin.websocket - -import io.ktor.client.HttpClient -import io.ktor.server.application.install -import io.ktor.server.cio.CIOApplicationEngine -import io.ktor.server.engine.EmbeddedServer -import io.ktor.server.engine.embeddedServer -import io.ktor.server.routing.routing -import io.modelcontextprotocol.kotlin.sdk.GetPromptRequest -import io.modelcontextprotocol.kotlin.sdk.GetPromptResult -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.PromptArgument -import io.modelcontextprotocol.kotlin.sdk.PromptMessage -import io.modelcontextprotocol.kotlin.sdk.Role -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities -import io.modelcontextprotocol.kotlin.sdk.TextContent -import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.client.mcpWebSocketTransport -import io.modelcontextprotocol.kotlin.sdk.server.Server -import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions -import io.modelcontextprotocol.kotlin.sdk.server.mcpWebSocket -import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequestParams -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.runBlocking -import kotlin.test.Test -import kotlin.test.assertTrue -import io.ktor.client.engine.cio.CIO as ClientCIO -import io.ktor.client.plugins.websocket.WebSockets as ClientWebSocket -import io.ktor.server.cio.CIO as ServerCIO -import io.ktor.server.websocket.WebSockets as ServerWebSockets - -class OldSchemaWebSocketIntegrationTest { - - @Test - fun `client should be able to connect to websocket server 2`() = runBlocking(Dispatchers.IO) { - var server: EmbeddedServer? = null - var client: Client? = null - - try { - server = initServer() - val port = server.engine.resolvedConnectors().first().port - client = initClient(serverPort = port) - } finally { - client?.close() - server?.stop(1000, 2000) - } - } - - /** - * Test Case #1: One opened connection, a client gets a prompt - * - * 1. Open WebSocket from Client A. - * 2. Send a POST request from Client A to POST /prompts/get. - * 3. Observe that Client A receives a response related to it. - */ - @Test - fun `single websocket connection`() = runBlocking(Dispatchers.IO) { - var server: EmbeddedServer? = null - var client: Client? = null - - try { - server = initServer() - val port = server.engine.resolvedConnectors().first().port - client = initClient("Client A", port) - - val promptA = getPrompt(client, "Client A") - assertTrue { "Client A" in promptA } - } finally { - client?.close() - server?.stop(1000, 2000) - } - } - - /** - * Test Case #1: Two open connections, each client gets a client-specific prompt - * - * 1. Open WebSocket connection #1 from Client A and note the sessionId= value. - * 2. Open WebSocket connection #2 from Client B and note the sessionId= value. - * 3. Send a POST request to POST /message with the corresponding sessionId#1. - * 4. Observe that Client B (connection #2) receives a response related to sessionId#1. - */ - @Test - fun `multiple websocket connections`() = runBlocking(Dispatchers.IO) { - var server: EmbeddedServer? = null - var clientA: Client? = null - var clientB: Client? = null - - try { - server = initServer() - val port = server.engine.resolvedConnectors().first().port - clientA = initClient("Client A", port) - clientB = initClient("Client B", port) - - // Step 3: Send a prompt request from Client A - val promptA = getPrompt(clientA, "Client A") - // Step 4: Send a prompt request from Client B - val promptB = getPrompt(clientB, "Client B") - - assertTrue { "Client A" in promptA } - assertTrue { "Client B" in promptB } - } finally { - clientA?.close() - clientB?.close() - server?.stop(1000, 2000) - } - } - - private suspend fun initClient(name: String = "", serverPort: Int): Client { - val client = Client( - Implementation(name = name, version = "1.0.0"), - ) - - val httpClient = HttpClient(ClientCIO) { - install(ClientWebSocket) - } - - // Create a transport wrapper that captures the session ID and received messages - val transport = httpClient.mcpWebSocketTransport { - url { - host = URL - port = serverPort - } - } - - client.connect(transport) - - return client - } - - private fun initServer(): EmbeddedServer { - val server = Server( - Implementation(name = "websocket-server", version = "1.0.0"), - ServerOptions( - capabilities = ServerCapabilities(prompts = ServerCapabilities.Prompts(listChanged = true)), - ), - ) - - server.addPrompt( - name = "prompt", - description = "Prompt description", - arguments = listOf( - PromptArgument( - name = "client", - description = "Client name who requested a prompt", - required = true, - ), - ), - ) { request -> - GetPromptResult( - description = "Prompt for ${request.params.name}", - messages = listOf( - PromptMessage( - role = Role.user, - content = TextContent("Prompt for client ${request.params.arguments?.get("client")}"), - ), - ), - ) - } - - val ktorServer = embeddedServer(ServerCIO, host = URL, port = PORT) { - install(ServerWebSockets) - routing { - mcpWebSocket(block = { server }) - } - } - - return ktorServer.start(wait = false) - } - - /** - * Retrieves a prompt result using the provided client and client name. - */ - private suspend fun getPrompt(client: Client, clientName: String): String { - val response = client.getPrompt( - GetPromptRequest( - GetPromptRequestParams( - "prompt", - arguments = mapOf("client" to clientName), - ), - ), - ) - - return (response.messages.first().content as? TextContent)?.text - ?: error("Failed to receive prompt for Client $clientName") - } - - companion object { - private const val PORT = 0 - private const val URL = "127.0.0.1" - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaAbstractSseIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaAbstractSseIntegrationTest.kt deleted file mode 100644 index 82d51b9..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaAbstractSseIntegrationTest.kt +++ /dev/null @@ -1,105 +0,0 @@ -@file:Suppress("DEPRECATION") - -package io.modelcontextprotocol.kotlin.sdk.integration.sse - -import io.ktor.client.HttpClient -import io.ktor.client.plugins.sse.SSE -import io.ktor.server.application.install -import io.ktor.server.cio.CIOApplicationEngine -import io.ktor.server.engine.EmbeddedServer -import io.ktor.server.engine.embeddedServer -import io.ktor.server.routing.routing -import io.modelcontextprotocol.kotlin.sdk.GetPromptResult -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.PromptArgument -import io.modelcontextprotocol.kotlin.sdk.PromptMessage -import io.modelcontextprotocol.kotlin.sdk.Role -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities -import io.modelcontextprotocol.kotlin.sdk.TextContent -import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.client.mcpSseTransport -import io.modelcontextprotocol.kotlin.sdk.server.Server -import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions -import io.modelcontextprotocol.kotlin.sdk.server.mcp -import io.ktor.client.engine.cio.CIO as ClientCIO -import io.ktor.server.cio.CIO as ServerCIO -import io.ktor.server.sse.SSE as ServerSSE - -open class OldSchemaAbstractSseIntegrationTest { - - suspend fun initTestClient(serverPort: Int, name: String? = null): Client { - val client = Client( - Implementation(name = name ?: DEFAULT_CLIENT_NAME, version = VERSION), - ) - - val httpClient = HttpClient(ClientCIO) { - install(SSE) - } - - // Create a transport wrapper that captures the session ID and received messages - val transport = httpClient.mcpSseTransport { - url { - host = URL - port = serverPort - } - } - - client.connect(transport) - - return client - } - - suspend fun initTestServer( - name: String? = null, - ): EmbeddedServer { - val server = Server( - Implementation(name = name ?: DEFAULT_SERVER_NAME, version = VERSION), - ServerOptions( - capabilities = ServerCapabilities(prompts = ServerCapabilities.Prompts(listChanged = true)), - ), - ) { - addPrompt( - name = "prompt", - description = "Prompt description", - arguments = listOf( - PromptArgument( - name = "client", - description = "Client name who requested a prompt", - required = true, - ), - ), - ) { request -> - GetPromptResult( - description = "Prompt for ${request.params.name}", - messages = listOf( - PromptMessage( - role = Role.user, - content = TextContent("Prompt for client ${request.params.arguments?.get("client")}"), - ), - ), - ) - } - } - - val ktorServer = embeddedServer( - ServerCIO, - host = URL, - port = PORT, - ) { - install(ServerSSE) - routing { - mcp { server } - } - } - - return ktorServer.startSuspend(wait = false) - } - - companion object { - private const val DEFAULT_CLIENT_NAME = "sse-test-client" - private const val DEFAULT_SERVER_NAME = "sse-test-server" - private const val VERSION = "1.0.0" - private const val URL = "127.0.0.1" - private const val PORT = 0 - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaSseIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaSseIntegrationTest.kt deleted file mode 100644 index 97313bd..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/sse/OldSchemaSseIntegrationTest.kt +++ /dev/null @@ -1,109 +0,0 @@ -@file:Suppress("DEPRECATION") - -package io.modelcontextprotocol.kotlin.sdk.integration.sse - -import io.ktor.server.cio.CIOApplicationEngine -import io.ktor.server.engine.EmbeddedServer -import io.modelcontextprotocol.kotlin.sdk.GetPromptRequest -import io.modelcontextprotocol.kotlin.sdk.TextContent -import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequestParams -import io.modelcontextprotocol.kotlin.test.utils.actualPort -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.runBlocking -import kotlin.test.Test -import kotlin.test.assertTrue - -class OldSchemaSseIntegrationTest : OldSchemaAbstractSseIntegrationTest() { - - @Test - fun `client should be able to connect to sse server`() = runBlocking(Dispatchers.IO) { - var server: EmbeddedServer? = null - var client: Client? = null - - try { - server = initTestServer() - val port = server.actualPort() - client = initTestClient(serverPort = port) - } finally { - client?.close() - server?.stopSuspend(1000, 2000) - } - } - - /** - * Test Case #1: One opened connection, a client gets a prompt - * - * 1. Open SSE from Client A. - * 2. Send a POST request from Client A to POST /prompts/get. - * 3. Observe that Client A receives a response related to it. - */ - @Test - fun `single sse connection`() = runBlocking(Dispatchers.IO) { - var server: EmbeddedServer? = null - var client: Client? = null - try { - server = initTestServer() - val port = server.actualPort() - client = initTestClient(port, "Client A") - - val promptA = getPrompt(client, "Client A") - assertTrue { "Client A" in promptA } - } finally { - client?.close() - server?.stopSuspend(1000, 2000) - } - } - - /** - * Test Case #1: Two open connections, each client gets a client-specific prompt - * - * 1. Open SSE connection #1 from Client A and note the sessionId= value. - * 2. Open SSE connection #2 from Client B and note the sessionId= value. - * 3. Send a POST request to POST /message with the corresponding sessionId#1. - * 4. Observe that Client B (connection #2) receives a response related to sessionId#1. - */ - @Test - fun `multiple sse connections`() = runBlocking(Dispatchers.IO) { - var server: EmbeddedServer? = null - var clientA: Client? = null - var clientB: Client? = null - - try { - server = initTestServer() - val port = server.engine.resolvedConnectors().first().port - - clientA = initTestClient(port, "Client A") - clientB = initTestClient(port, "Client B") - - // Step 3: Send a prompt request from Client A - val promptA = getPrompt(clientA, "Client A") - // Step 4: Send a prompt request from Client B - val promptB = getPrompt(clientB, "Client B") - - assertTrue { "Client A" in promptA } - assertTrue { "Client B" in promptB } - } finally { - clientA?.close() - clientB?.close() - server?.stopSuspend(1000, 2000) - } - } - - /** - * Retrieves a prompt result using the provided client and client name. - */ - private suspend fun getPrompt(client: Client, clientName: String): String { - val response = client.getPrompt( - GetPromptRequest( - GetPromptRequestParams( - "prompt", - arguments = mapOf("client" to clientName), - ), - ), - ) - - return (response.messages.first().content as? TextContent)?.text - ?: error("Failed to receive prompt for Client $clientName") - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/OldSchemaAbstractKotlinClientTsServerTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/OldSchemaAbstractKotlinClientTsServerTest.kt deleted file mode 100644 index 5d0406c..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/OldSchemaAbstractKotlinClientTsServerTest.kt +++ /dev/null @@ -1,77 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.typescript - -import io.modelcontextprotocol.kotlin.sdk.TextContent -import io.modelcontextprotocol.kotlin.sdk.client.Client -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.runBlocking -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.Timeout -import java.util.concurrent.TimeUnit -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertTrue - -abstract class OldSchemaAbstractKotlinClientTsServerTest : TsTestBase() { - protected abstract suspend fun useClient(block: suspend (Client) -> T): T - - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun connectsAndPings() = runBlocking(Dispatchers.IO) { - useClient { client -> - assertNotNull(client, "Client should be initialized") - val ping = client.ping() - assertNotNull(ping, "Ping result should not be null") - val serverImpl = client.serverVersion - assertNotNull(serverImpl, "Server implementation should not be null") - println("Connected to TypeScript server: ${serverImpl.name} v${serverImpl.version}") - } - } - - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun listsTools() = runBlocking(Dispatchers.IO) { - useClient { client -> - val result = client.listTools() - assertNotNull(result, "Tools list should not be null") - assertTrue(result.tools.isNotEmpty(), "Tools list should not be empty") - val toolNames = result.tools.map { it.name } - assertTrue("greet" in toolNames, "Greet tool should be available") - assertTrue("multi-greet" in toolNames, "Multi-greet tool should be available") - // Some tests also check collect-user-info; keep base minimal and non-breaking - } - } - - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun callGreet() = runBlocking(Dispatchers.IO) { - useClient { client -> - val testName = "TestUser" - val arguments = mapOf("name" to testName) - val result = client.callTool("greet", arguments) - assertNotNull(result, "Tool call result should not be null") - val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult - val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Text content should be present in the result") - assertEquals("Hello, $testName!", textContent.text) - } - } - - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun multipleClients() = runBlocking(Dispatchers.IO) { - useClient { client1 -> - useClient { client2 -> - val tools1 = client1.listTools() - val tools2 = client2.listTools() - assertTrue(tools1.tools.isNotEmpty(), "Tools list for first client should not be empty") - assertTrue(tools2.tools.isNotEmpty(), "Tools list for second client should not be empty") - val toolNames1 = tools1.tools.map { it.name } - val toolNames2 = tools2.tools.map { it.name } - assertTrue("greet" in toolNames1, "Greet tool should be available to first client") - assertTrue("multi-greet" in toolNames1, "Multi-greet tool should be available to first client") - assertTrue("greet" in toolNames2, "Greet tool should be available to second client") - assertTrue("multi-greet" in toolNames2, "Multi-greet tool should be available to second client") - } - } - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerEdgeCasesTestSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerEdgeCasesTestSse.kt deleted file mode 100644 index f2f6009..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerEdgeCasesTestSse.kt +++ /dev/null @@ -1,200 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse - -import io.modelcontextprotocol.kotlin.sdk.TextContent -import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TsTestBase -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withTimeout -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.Timeout -import java.util.concurrent.TimeUnit -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertTrue -import kotlin.time.Duration.Companion.seconds - -class OldSchemaKotlinClientTsServerEdgeCasesTestSse : TsTestBase() { - - override val transportKind = TransportKind.SSE - - private val host = "localhost" - private val serverUrl: String by lazy { getSharedSseUrl() } - - private lateinit var client: Client - - @AfterEach - fun tearDown() { - if (::client.isInitialized) { - try { - runBlocking { - withTimeout(3.seconds) { - client.close() - } - } - } catch (e: Exception) { - println("Warning: Error during client close: ${e.message}") - } - } - } - - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun testNonExistentTool(): Unit = runBlocking(Dispatchers.IO) { - withClient(serverUrl) { client -> - val nonExistentToolName = "non-existent-tool" - val arguments = mapOf("name" to "TestUser") - - val result = client.callTool(nonExistentToolName, arguments) - assertNotNull(result, "Tool call result should not be null") - - val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult - assertTrue(callResult.isError ?: false, "isError should be true for non-existent tool") - - val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Error content should be present in the result") - - val errorText = textContent.text ?: "" - assertTrue( - errorText.contains("non-existent-tool") && errorText.contains("not found"), - "Error message should indicate the tool was not found", - ) - } - } - - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun testSpecialCharactersInArguments(): Unit = runBlocking(Dispatchers.IO) { - withClient(serverUrl) { client -> - val specialChars = "!@#$%^&*()_+{}[]|\\:;\"'<>,.?/" - val arguments = mapOf("name" to specialChars) - - val result = client.callTool("greet", arguments) - assertNotNull(result, "Tool call result should not be null") - - val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult - val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Text content should be present in the result") - - val text = textContent.text ?: "" - assertTrue( - text.contains(specialChars), - "Tool response should contain the special characters", - ) - } - } - - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun testLargePayload(): Unit = runBlocking(Dispatchers.IO) { - withClient(serverUrl) { client -> - val largeName = "A".repeat(10 * 1024) - val arguments = mapOf("name" to largeName) - - val result = client.callTool("greet", arguments) - assertNotNull(result, "Tool call result should not be null") - - val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult - val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Text content should be present in the result") - - val text = textContent.text ?: "" - assertTrue( - text.contains("Hello,") && text.contains("A"), - "Tool response should contain the greeting with the large name", - ) - } - } - - @Test - @Timeout(60, unit = TimeUnit.SECONDS) - fun testConcurrentRequests(): Unit = runBlocking(Dispatchers.IO) { - withClient(serverUrl) { client -> - val concurrentCount = 5 - val responses = coroutineScope { - val results = (1..concurrentCount).map { i -> - async { - val name = "ConcurrentClient$i" - val arguments = mapOf("name" to name) - - val result = client.callTool("greet", arguments) - assertNotNull(result, "Tool call result should not be null for client $i") - - val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult - val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Text content should be present for client $i") - - textContent.text ?: "" - } - } - results.awaitAll() - } - - for (i in 1..concurrentCount) { - val expectedName = "ConcurrentClient$i" - val matchingResponses = responses.filter { it.contains("Hello, $expectedName!") } - assertEquals( - 1, - matchingResponses.size, - "Should have exactly one response for $expectedName", - ) - } - } - } - - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun testInvalidArguments(): Unit = runBlocking(Dispatchers.IO) { - withClient(serverUrl) { client -> - val invalidArguments = mapOf( - "name" to JsonObject(mapOf("nested" to JsonPrimitive("value"))), - ) - - val result = client.callTool("greet", invalidArguments) - assertNotNull(result, "Tool call result should not be null") - - val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult - assertTrue(callResult.isError ?: false, "isError should be true for invalid arguments") - - val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Error content should be present in the result") - - val errorText = textContent.text ?: "" - assertTrue( - errorText.contains("Invalid arguments") && errorText.contains("greet"), - "Error message should indicate invalid arguments for tool greet", - ) - } - } - - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun testMultipleToolCalls(): Unit = runBlocking(Dispatchers.IO) { - withClient(serverUrl) { client -> - repeat(10) { i -> - val name = "SequentialClient$i" - val arguments = mapOf("name" to name) - - val result = client.callTool("greet", arguments) - assertNotNull(result, "Tool call result should not be null for call $i") - - val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult - val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Text content should be present for call $i") - - assertEquals( - "Hello, $name!", - textContent.text, - "Tool response should contain the greeting with the provided name", - ) - } - } - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerTestSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerTestSse.kt deleted file mode 100644 index f9415e1..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinClientTsServerTestSse.kt +++ /dev/null @@ -1,25 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse - -import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.OldSchemaAbstractKotlinClientTsServerTest -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind -import kotlinx.coroutines.withTimeout -import kotlin.time.Duration.Companion.seconds - -class OldSchemaKotlinClientTsServerTestSse : OldSchemaAbstractKotlinClientTsServerTest() { - - override val transportKind = TransportKind.SSE - - private val host = "localhost" - private val serverUrl: String by lazy { getSharedSseUrl() } - - override suspend fun useClient(block: suspend (Client) -> T): T = withClient(serverUrl) { client -> - try { - withTimeout(20.seconds) { block(client) } - } finally { - try { - withTimeout(3.seconds) { client.close() } - } catch (_: Exception) {} - } - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinServerForTsClientSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinServerForTsClientSse.kt deleted file mode 100644 index 08dcad7..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/OldSchemaKotlinServerForTsClientSse.kt +++ /dev/null @@ -1,466 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.typescript.sse - -import io.github.oshai.kotlinlogging.KotlinLogging -import io.ktor.http.ContentType -import io.ktor.http.HttpStatusCode -import io.ktor.server.application.ApplicationCall -import io.ktor.server.cio.CIO -import io.ktor.server.engine.EmbeddedServer -import io.ktor.server.engine.embeddedServer -import io.ktor.server.request.header -import io.ktor.server.request.receiveText -import io.ktor.server.response.header -import io.ktor.server.response.respond -import io.ktor.server.response.respondText -import io.ktor.server.response.respondTextWriter -import io.ktor.server.routing.delete -import io.ktor.server.routing.get -import io.ktor.server.routing.post -import io.ktor.server.routing.routing -import io.modelcontextprotocol.kotlin.sdk.CallToolResult -import io.modelcontextprotocol.kotlin.sdk.ErrorCode -import io.modelcontextprotocol.kotlin.sdk.GetPromptResult -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.JSONRPCError -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage -import io.modelcontextprotocol.kotlin.sdk.JSONRPCNotification -import io.modelcontextprotocol.kotlin.sdk.JSONRPCRequest -import io.modelcontextprotocol.kotlin.sdk.JSONRPCResponse -import io.modelcontextprotocol.kotlin.sdk.PromptArgument -import io.modelcontextprotocol.kotlin.sdk.PromptMessage -import io.modelcontextprotocol.kotlin.sdk.ReadResourceResult -import io.modelcontextprotocol.kotlin.sdk.RequestId -import io.modelcontextprotocol.kotlin.sdk.Role -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities -import io.modelcontextprotocol.kotlin.sdk.TextContent -import io.modelcontextprotocol.kotlin.sdk.TextResourceContents -import io.modelcontextprotocol.kotlin.sdk.Tool -import io.modelcontextprotocol.kotlin.sdk.server.Server -import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions -import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport -import io.modelcontextprotocol.kotlin.sdk.shared.McpJson -import io.modelcontextprotocol.kotlin.sdk.shared.TransportSendOptions -import io.modelcontextprotocol.kotlin.sdk.types.RPCError -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withTimeoutOrNull -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonNull -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.contentOrNull -import kotlinx.serialization.json.decodeFromJsonElement -import kotlinx.serialization.json.jsonPrimitive -import java.util.UUID -import java.util.concurrent.ConcurrentHashMap - -private val logger = KotlinLogging.logger {} - -class OldSchemaKotlinServerForTsClient { - private val serverTransports = ConcurrentHashMap() - private val jsonFormat = Json { ignoreUnknownKeys = true } - private var server: EmbeddedServer<*, *>? = null - - fun start(port: Int = 3000) { - logger.info { "Starting HTTP server on port $port" } - - server = embeddedServer(CIO, port = port) { - routing { - get("/mcp") { - val sessionId = call.request.header("mcp-session-id") - if (sessionId == null) { - call.respond(HttpStatusCode.BadRequest, "Missing mcp-session-id header") - return@get - } - val transport = serverTransports[sessionId] - if (transport == null) { - call.respond(HttpStatusCode.BadRequest, "Invalid mcp-session-id") - return@get - } - transport.stream(call) - } - - post("/mcp") { - val sessionId = call.request.header("mcp-session-id") - val requestBody = call.receiveText() - - logger.debug { "Received request with sessionId: $sessionId" } - logger.trace { "Request body: $requestBody" } - - val jsonElement = try { - jsonFormat.parseToJsonElement(requestBody) - } catch (e: Exception) { - logger.error(e) { "Failed to parse request body as JSON" } - call.respond( - HttpStatusCode.BadRequest, - jsonFormat.encodeToString( - JsonObject.serializer(), - JsonObject( - mapOf( - "jsonrpc" to JsonPrimitive("2.0"), - "error" to JsonObject( - mapOf( - "code" to JsonPrimitive(-32700), - "message" to JsonPrimitive("Parse error: ${e.message}"), - ), - ), - "id" to JsonNull, - ), - ), - ), - ) - return@post - } - - if (sessionId != null && serverTransports.containsKey(sessionId)) { - logger.debug { "Using existing transport for session: $sessionId" } - val transport = serverTransports[sessionId]!! - transport.handleRequest(call, jsonElement) - } else { - if (isInitializeRequest(jsonElement)) { - val newSessionId = UUID.randomUUID().toString() - logger.info { "Creating new session with ID: $newSessionId" } - - val transport = OldSchemaHttpServerTransport(newSessionId) - - serverTransports[newSessionId] = transport - - val mcpServer = createMcpServer() - - call.response.header("mcp-session-id", newSessionId) - - val serverThread = Thread { - runBlocking { - mcpServer.createSession(transport) - } - } - serverThread.start() - - Thread.sleep(500) - - transport.handleRequest(call, jsonElement) - } else { - logger.warn { "Invalid request: no session ID or not an initialization request" } - call.respond( - HttpStatusCode.BadRequest, - jsonFormat.encodeToString( - JsonObject.serializer(), - JsonObject( - mapOf( - "jsonrpc" to JsonPrimitive("2.0"), - "error" to JsonObject( - mapOf( - "code" to JsonPrimitive(-32000), - "message" to - JsonPrimitive("Bad Request: No valid session ID provided"), - ), - ), - "id" to JsonNull, - ), - ), - ), - ) - } - } - } - - delete("/mcp") { - val sessionId = call.request.header("mcp-session-id") - if (sessionId != null && serverTransports.containsKey(sessionId)) { - logger.info { "Terminating session: $sessionId" } - val transport = serverTransports[sessionId]!! - serverTransports.remove(sessionId) - runBlocking { - transport.close() - } - call.respond(HttpStatusCode.OK) - } else { - logger.warn { "Invalid session termination request: $sessionId" } - call.respond(HttpStatusCode.BadRequest, "Invalid or missing session ID") - } - } - } - } - - server?.start(wait = false) - } - - fun stop() { - logger.info { "Stopping HTTP server" } - server?.stop(500, 1000) - server = null - } - - fun createMcpServer(): Server { - val server = Server( - Implementation( - name = "kotlin-http-server", - version = "1.0.0", - ), - ServerOptions( - capabilities = ServerCapabilities( - prompts = ServerCapabilities.Prompts(listChanged = true), - resources = ServerCapabilities.Resources(subscribe = true, listChanged = true), - tools = ServerCapabilities.Tools(listChanged = true), - ), - ), - ) - - server.addTool( - name = "greet", - description = "A simple greeting tool", - inputSchema = Tool.Input( - properties = buildJsonObject { - put( - "name", - buildJsonObject { - put("type", JsonPrimitive("string")) - put("description", JsonPrimitive("Name to greet")) - }, - ) - }, - required = listOf("name"), - ), - ) { request -> - val name = (request.params.arguments?.get("name") as? JsonPrimitive)?.content ?: "World" - CallToolResult( - content = listOf(TextContent("Hello, $name!")), - structuredContent = buildJsonObject { - put("greeting", JsonPrimitive("Hello, $name!")) - }, - ) - } - - server.addTool( - name = "multi-greet", - description = "A greeting tool that sends multiple notifications", - inputSchema = Tool.Input( - properties = buildJsonObject { - put( - "name", - buildJsonObject { - put("type", JsonPrimitive("string")) - put("description", JsonPrimitive("Name to greet")) - }, - ) - }, - required = listOf("name"), - ), - ) { request -> - val name = (request.params.arguments?.get("name") as? JsonPrimitive)?.content ?: "World" - - CallToolResult( - content = listOf(TextContent("Multiple greetings sent to $name!")), - structuredContent = buildJsonObject { - put("greeting", JsonPrimitive("Multiple greetings sent to $name!")) - put("notificationCount", JsonPrimitive(3)) - }, - ) - } - - server.addPrompt( - name = "greeting-template", - description = "A simple greeting prompt template", - arguments = listOf( - PromptArgument( - name = "name", - description = "Name to include in greeting", - required = true, - ), - ), - ) { request -> - GetPromptResult( - description = "Greeting for ${request.params.name}", - messages = listOf( - PromptMessage( - role = Role.user, - content = TextContent( - "Please greet ${request.params.arguments?.get("name") ?: "someone"} in a friendly manner.", - ), - ), - ), - ) - } - - server.addResource( - uri = "https://example.com/greetings/default", - name = "Default Greeting", - description = "A simple greeting resource", - mimeType = "text/plain", - ) { request -> - ReadResourceResult( - contents = listOf( - TextResourceContents("Hello, world!", request.params.uri, "text/plain"), - ), - ) - } - - return server - } - - private fun isInitializeRequest(json: JsonElement): Boolean { - if (json !is JsonObject) return false - - val method = json["method"]?.jsonPrimitive?.contentOrNull - return method == "initialize" - } -} - -class OldSchemaHttpServerTransport(private val sessionId: String) : AbstractTransport() { - private val logger = KotlinLogging.logger {} - private val pendingResponses = ConcurrentHashMap>() - private val messageQueue = Channel(Channel.UNLIMITED) - - suspend fun stream(call: ApplicationCall) { - logger.debug { "Starting SSE stream for session: $sessionId" } - call.response.header("Cache-Control", "no-cache") - call.response.header("Connection", "keep-alive") - call.respondTextWriter(ContentType.Text.EventStream) { - try { - while (true) { - val result = messageQueue.receiveCatching() - val msg = result.getOrNull() ?: break - val json = McpJson.encodeToString(msg) - write("event: message\n") - write("data: ") - write(json) - write("\n\n") - flush() - } - } catch (e: Exception) { - logger.warn(e) { "SSE stream terminated for session: $sessionId" } - } finally { - logger.debug { "SSE stream closed for session: $sessionId" } - } - } - } - - suspend fun handleRequest(call: ApplicationCall, requestBody: JsonElement) { - try { - logger.info { "Handling request body: $requestBody" } - val message = McpJson.decodeFromJsonElement(requestBody) - logger.info { "Decoded message: $message" } - - if (message is JSONRPCRequest) { - val id = message.id.toString() - logger.info { "Received request with ID: $id, method: ${message.method}" } - val responseDeferred = CompletableDeferred() - pendingResponses[id] = responseDeferred - logger.info { "Created deferred response for ID: $id" } - - logger.info { "Invoking onMessage handler" } - _onMessage.invoke(message) - logger.info { "onMessage handler completed" } - - try { - val response = withTimeoutOrNull(10000) { - responseDeferred.await() - } - - if (response != null) { - val jsonResponse = McpJson.encodeToString(response) - call.respondText(jsonResponse, ContentType.Application.Json) - } else { - logger.warn { "Timeout waiting for response to request ID: $id" } - call.respondText( - McpJson.encodeToString( - JSONRPCError( - id = message.id, - error = RPCError( - code = ErrorCode.Defined.RequestTimeout, - message = "Request timed out", - ), - ), - ), - ContentType.Application.Json, - ) - } - } catch (_: CancellationException) { - logger.warn { "Request cancelled for ID: $id" } - pendingResponses.remove(id) - if (!call.response.isCommitted) { - call.respondText( - McpJson.encodeToString( - JSONRPCError( - id = message.id, - error = RPCError( - code = ErrorCode.Defined.ConnectionClosed, - message = "Request cancelled", - ), - ), - ), - ContentType.Application.Json, - HttpStatusCode.ServiceUnavailable, - ) - } - } - } else { - call.respondText("", ContentType.Application.Json, HttpStatusCode.Accepted) - } - } catch (e: Exception) { - logger.error(e) { "Error handling request" } - if (!call.response.isCommitted) { - try { - val errorResponse = JSONRPCError( - id = RequestId.NumberId(0), - error = RPCError( - code = ErrorCode.Defined.InternalError, - message = "Internal server error: ${e.message}", - ), - ) - - call.respondText( - McpJson.encodeToString(errorResponse), - ContentType.Application.Json, - HttpStatusCode.InternalServerError, - ) - } catch (responseEx: Exception) { - logger.error(responseEx) { "Failed to send error response" } - } - } - } - } - - override suspend fun start() { - logger.debug { "Starting HTTP server transport for session: $sessionId" } - } - - override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { - logger.info { "Sending message: $message" } - - if (message is JSONRPCResponse) { - val id = message.id.toString() - logger.info { "Sending response for request ID: $id" } - val deferred = pendingResponses.remove(id) - if (deferred != null) { - logger.info { "Found pending response for ID: $id, completing deferred" } - deferred.complete(message) - return - } else { - logger.warn { "No pending response found for ID: $id" } - } - } else if (message is JSONRPCRequest) { - logger.info { "Sending request with ID: ${message.id}" } - } else if (message is JSONRPCNotification) { - logger.info { "Sending notification: ${message.method}" } - } - - logger.info { "Queueing message for next client request" } - messageQueue.send(message) - } - - override suspend fun close() { - logger.debug { "Closing HTTP server transport for session: $sessionId" } - messageQueue.close() - _onClose.invoke() - } -} - -fun main() { - val server = OldSchemaKotlinServerForTsClient() - server.start() -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerEdgeCasesTestStdio.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerEdgeCasesTestStdio.kt deleted file mode 100644 index 0ed8676..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerEdgeCasesTestStdio.kt +++ /dev/null @@ -1,177 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.typescript.stdio - -import io.modelcontextprotocol.kotlin.sdk.TextContent -import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TsTestBase -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.runBlocking -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.Timeout -import java.util.concurrent.TimeUnit -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertTrue - -class OldSchemaKotlinClientTsServerEdgeCasesTestStdio : TsTestBase() { - - override val transportKind = TransportKind.STDIO - - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun testNonExistentToolOverStdio(): Unit = runBlocking(Dispatchers.IO) { - withClientStdio { client: Client, _ -> - val nonExistentToolName = "non-existent-tool" - val arguments = mapOf("name" to "TestUser") - - val result = client.callTool(nonExistentToolName, arguments) - assertNotNull(result, "Tool call result should not be null") - - val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult - assertTrue(callResult.isError ?: false, "isError should be true for non-existent tool") - - val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Error content should be present in the result") - - val errorText = textContent.text ?: "" - assertTrue( - errorText.contains("non-existent-tool") && errorText.contains("not found"), - "Error message should indicate the tool was not found", - ) - } - } - - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun testSpecialCharactersInArgumentsOverStdio(): Unit = runBlocking(Dispatchers.IO) { - withClientStdio { client: Client, _ -> - val specialChars = "!@#$%^&*()_+{}[]|\\:;\"'<>.,?/" - val arguments = mapOf("name" to specialChars) - - val result = client.callTool("greet", arguments) - assertNotNull(result, "Tool call result should not be null") - - val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult - val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Text content should be present in the result") - - val text = textContent.text ?: "" - assertTrue( - text.contains(specialChars), - "Tool response should contain the special characters", - ) - } - } - - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun testLargePayloadOverStdio(): Unit = runBlocking(Dispatchers.IO) { - withClientStdio { client: Client, _ -> - val largeName = "A".repeat(10 * 1024) - val arguments = mapOf("name" to largeName) - - val result = client.callTool("greet", arguments) - assertNotNull(result, "Tool call result should not be null") - - val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult - val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Text content should be present in the result") - - val text = textContent.text ?: "" - assertTrue( - text.contains("Hello,") && text.contains("A"), - "Tool response should contain the greeting with the large name", - ) - } - } - - @Test - @Timeout(60, unit = TimeUnit.SECONDS) - fun testConcurrentRequestsOverStdio(): Unit = runBlocking(Dispatchers.IO) { - withClientStdio { client: Client, _ -> - val concurrentCount = 5 - val responses = coroutineScope { - val results = (1..concurrentCount).map { i -> - async { - val name = "ConcurrentClient$i" - val arguments = mapOf("name" to name) - - val result = client.callTool("greet", arguments) - assertNotNull(result, "Tool call result should not be null for client $i") - - val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult - val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Text content should be present for client $i") - - textContent.text ?: "" - } - } - results.awaitAll() - } - - for (i in 1..concurrentCount) { - val expectedName = "ConcurrentClient$i" - val matchingResponses = responses.filter { it.contains("Hello, $expectedName!") } - assertEquals( - 1, - matchingResponses.size, - "Should have exactly one response for $expectedName", - ) - } - } - } - - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun testInvalidArgumentsOverStdio(): Unit = runBlocking(Dispatchers.IO) { - withClientStdio { client: Client, _ -> - val invalidArguments = mapOf( - "name" to JsonObject(mapOf("nested" to JsonPrimitive("value"))), - ) - - val result = client.callTool("greet", invalidArguments) - assertNotNull(result, "Tool call result should not be null") - - val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult - assertTrue(callResult.isError ?: false, "isError should be true for invalid arguments") - - val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Error content should be present in the result") - - val errorText = textContent.text ?: "" - assertTrue( - errorText.contains("Invalid arguments") && errorText.contains("greet"), - "Error message should indicate invalid arguments for tool greet", - ) - } - } - - @Test - @Timeout(30, unit = TimeUnit.SECONDS) - fun testMultipleToolCallsOverStdio(): Unit = runBlocking(Dispatchers.IO) { - withClientStdio { client: Client, _ -> - repeat(10) { i -> - val name = "SequentialClient$i" - val arguments = mapOf("name" to name) - - val result = client.callTool("greet", arguments) - assertNotNull(result, "Tool call result should not be null for call $i") - - val callResult = result as io.modelcontextprotocol.kotlin.sdk.types.CallToolResult - val textContent = callResult.content.firstOrNull { it is TextContent } as? TextContent - assertNotNull(textContent, "Text content should be present for call $i") - - assertEquals( - "Hello, $name!", - textContent.text, - "Tool response should contain the greeting with the provided name", - ) - } - } - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerTestStdio.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerTestStdio.kt deleted file mode 100644 index e87a5c8..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/stdio/OldSchemaKotlinClientTsServerTestStdio.kt +++ /dev/null @@ -1,23 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.integration.typescript.stdio - -import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.OldSchemaAbstractKotlinClientTsServerTest -import io.modelcontextprotocol.kotlin.sdk.integration.typescript.TransportKind - -class OldSchemaKotlinClientTsServerTestStdio : OldSchemaAbstractKotlinClientTsServerTest() { - - override val transportKind = TransportKind.STDIO - - override suspend fun useClient(block: suspend (Client) -> T): T = withClientStdio { client, proc -> - try { - block(client) - } finally { - try { - client.close() - } catch (_: Exception) {} - try { - stopProcess(proc, name = "TypeScript stdio server") - } catch (_: Exception) {} - } - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaAbstractServerFeaturesTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaAbstractServerFeaturesTest.kt deleted file mode 100644 index d74c0b0..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaAbstractServerFeaturesTest.kt +++ /dev/null @@ -1,43 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.server - -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import org.junit.jupiter.api.BeforeEach - -abstract class OldSchemaAbstractServerFeaturesTest { - - protected lateinit var server: Server - protected lateinit var client: Client - - abstract fun getServerCapabilities(): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities - - protected open fun getServerInstructionsProvider(): (() -> String)? = null - - @BeforeEach - fun setUp() { - val serverOptions = ServerOptions( - capabilities = getServerCapabilities(), - ) - - server = Server( - serverInfo = Implementation(name = "test server", version = "1.0"), - options = serverOptions, - instructionsProvider = getServerInstructionsProvider(), - ) - - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - - client = Client( - clientInfo = Implementation(name = "test client", version = "1.0"), - ) - - runBlocking { - // Connect client and server - launch { client.connect(clientTransport) } - launch { server.createSession(serverTransport) } - } - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerInstructionsTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerInstructionsTest.kt deleted file mode 100644 index 26bb7bf..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerInstructionsTest.kt +++ /dev/null @@ -1,68 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.server - -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities -import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertNull -import kotlin.test.assertEquals - -class OldSchemaServerInstructionsTest { - - @Test - fun `Server constructor should accept instructions provider parameter`() = runTest { - val serverInfo = Implementation(name = "test server", version = "1.0") - val serverOptions = ServerOptions(capabilities = ServerCapabilities()) - val instructions = "This is a test server. Use it for testing purposes only." - - val server = Server(serverInfo, serverOptions, { instructions }) - - // The instructions should be stored internally and used in handleInitialize - // We can't directly access the private field, but we can test it through initialization - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - val client = Client(clientInfo = Implementation(name = "test client", version = "1.0")) - - server.createSession(serverTransport) - client.connect(clientTransport) - - assertEquals(instructions, client.serverInstructions) - } - - @Test - fun `Server constructor should accept instructions parameter`() = runTest { - val serverInfo = Implementation(name = "test server", version = "1.0") - val serverOptions = ServerOptions(capabilities = ServerCapabilities()) - val instructions = "This is a test server. Use it for testing purposes only." - - val server = Server(serverInfo, serverOptions, instructions) - - // The instructions should be stored internally and used in handleInitialize - // We can't directly access the private field, but we can test it through initialization - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - val client = Client(clientInfo = Implementation(name = "test client", version = "1.0")) - - server.createSession(serverTransport) - client.connect(clientTransport) - - assertEquals(instructions, client.serverInstructions) - } - - @Test - fun `Server constructor should work without instructions parameter`() = runTest { - val serverInfo = Implementation(name = "test server", version = "1.0") - val serverOptions = ServerOptions(capabilities = ServerCapabilities()) - - // Test that server works when instructions parameter is omitted (defaults to null) - val server = Server(serverInfo, serverOptions) - - val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair() - val client = Client(clientInfo = Implementation(name = "test client", version = "1.0")) - - server.createSession(serverTransport) - client.connect(clientTransport) - - assertNull(client.serverInstructions) - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerPromptsTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerPromptsTest.kt deleted file mode 100644 index 1721bec..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerPromptsTest.kt +++ /dev/null @@ -1,213 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.server - -import io.kotest.assertions.throwables.shouldThrow -import io.kotest.matchers.collections.shouldBeEmpty -import io.kotest.matchers.collections.shouldContain -import io.kotest.matchers.collections.shouldContainExactly -import io.kotest.matchers.collections.shouldHaveSize -import io.kotest.matchers.nulls.shouldNotBeNull -import io.kotest.matchers.shouldBe -import io.kotest.matchers.throwable.shouldHaveMessage -import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject -import io.modelcontextprotocol.kotlin.sdk.GetPromptRequest -import io.modelcontextprotocol.kotlin.sdk.GetPromptResult -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.Method -import io.modelcontextprotocol.kotlin.sdk.Prompt -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities -import io.modelcontextprotocol.kotlin.sdk.types.McpException -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -class OldSchemaServerPromptsTest : OldSchemaAbstractServerFeaturesTest() { - - override fun getServerCapabilities(): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities = - ServerCapabilities( - prompts = ServerCapabilities.Prompts(false), - ) - - @Test - fun `Should list no prompts by default`() = runTest { - client.listPrompts() shouldNotBeNull { - prompts.shouldBeEmpty() - } - } - - @Test - fun `Should add a prompt`() = runTest { - // Add a prompt - val testPrompt = Prompt( - name = "test-prompt-with-custom-handler", - description = "Test Prompt", - arguments = null, - ) - val expectedPromptResult = GetPromptResult( - description = "Test prompt description", - messages = listOf(), - ) - - server.addPrompt(testPrompt) { - expectedPromptResult - } - - client.getPrompt( - GetPromptRequest( - name = "test-prompt-with-custom-handler", - arguments = null, - ), - ) shouldBe expectedPromptResult - - client.listPrompts() shouldNotBeNull { - prompts shouldContainExactly listOf(testPrompt) - nextCursor shouldBe null - _meta shouldBe EmptyJsonObject - } - } - - @Test - fun `Should remove a prompt`() = runTest { - // given - val testPrompt = Prompt( - name = "test-prompt-to-remove", - description = "Test Prompt", - arguments = null, - ) - server.addPrompt(testPrompt) { - GetPromptResult( - description = "Test prompt description", - messages = listOf(), - ) - } - - client.listPrompts() shouldNotBeNull { - prompts shouldContain testPrompt - } - - // when - val result = server.removePrompt(testPrompt.name) - - // then - assertTrue(result, "Prompt should be removed successfully") - val mcpException = shouldThrow { - client.getPrompt( - GetPromptRequest( - name = testPrompt.name, - arguments = null, - ), - ) - } - mcpException shouldHaveMessage "MCP error -32603: Prompt not found: ${testPrompt.name}" - - client.listPrompts() shouldNotBeNull { - prompts.firstOrNull { it.name == testPrompt.name } shouldBe null - } - } - - @Test - fun `Should remove multiple prompts and send notification`() = runTest { - // Add prompts - val testPrompt1 = Prompt("test-prompt-1", "Test Prompt 1", null) - val testPrompt2 = Prompt("test-prompt-2", "Test Prompt 2", null) - server.addPrompt(testPrompt1) { - GetPromptResult( - description = "Test prompt description 1", - messages = listOf(), - ) - } - server.addPrompt(testPrompt2) { - GetPromptResult( - description = "Test prompt description 2", - messages = listOf(), - ) - } - - client.listPrompts() shouldNotBeNull { - prompts shouldHaveSize 2 - } - // Remove the prompts - val result = server.removePrompts(listOf(testPrompt1.name, testPrompt2.name)) - - // Verify the prompts were removed - assertEquals(2, result, "Both prompts should be removed") - client.listPrompts() shouldNotBeNull { - prompts.shouldBeEmpty() - } - } - - @Test - fun `removePrompt should return false when prompt does not exist`() = runTest { - // Track notifications - var promptListChangedNotificationReceived = false - client.setNotificationHandler( - Method.Defined.NotificationsPromptsListChanged, - ) { - promptListChangedNotificationReceived = true - CompletableDeferred(Unit) - } - - // Try to remove a non-existent prompt - val result = server.removePrompt("non-existent-prompt") - - // Verify the result - assertFalse(result, "Removing non-existent prompt should return false") - assertFalse(promptListChangedNotificationReceived, "No notification should be sent when prompt doesn't exist") - } - - @Nested - inner class NoPromptsCapabilitiesTests { - // Create server without prompts capability - val serverWithoutPrompts = Server( - Implementation(name = "test server", version = "1.0"), - ServerOptions( - capabilities = ServerCapabilities(), - ), - ) - - @Test - fun `RemovePrompt should throw when prompts capability is not supported`() = runTest { - // Verify that removing a prompt throws an exception - val exception = assertThrows { - serverWithoutPrompts.removePrompt("test-prompt") - } - assertEquals("Server does not support prompts capability.", exception.message) - } - - @Test - fun `Remove Prompts should throw when prompts capability is not supported`() = runTest { - // Verify that removing a prompt throws an exception - val exception = assertThrows { - serverWithoutPrompts.removePrompts(emptyList()) - } - assertEquals("Server does not support prompts capability.", exception.message) - } - - @Test - fun `Add Prompt should throw when prompts capability is not supported`() = runTest { - // Verify that removing a prompt throws an exception - val exception = assertThrows { - serverWithoutPrompts.addPrompt(name = "test-prompt") { - GetPromptResult( - description = "Test prompt description", - messages = listOf(), - ) - } - } - assertEquals("Server does not support prompts capability.", exception.message) - } - - @Test - fun `Add Prompts should throw when prompts capability is not supported`() = runTest { - // Verify that removing a prompt throws an exception - val exception = assertThrows { - serverWithoutPrompts.addPrompts(emptyList()) - } - assertEquals("Server does not support prompts capability.", exception.message) - } - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerResourcesTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerResourcesTest.kt deleted file mode 100644 index 90cb9a0..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerResourcesTest.kt +++ /dev/null @@ -1,135 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.server - -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.Method -import io.modelcontextprotocol.kotlin.sdk.ReadResourceResult -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities -import io.modelcontextprotocol.kotlin.sdk.TextResourceContents -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -class OldSchemaServerResourcesTest : OldSchemaAbstractServerFeaturesTest() { - - override fun getServerCapabilities(): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities = - ServerCapabilities( - resources = ServerCapabilities.Resources(null, null), - ) - - @Test - fun `removeResource should remove a resource and send notification`() = runTest { - // Add a resource - val testResourceUri = "test://resource" - server.addResource( - uri = testResourceUri, - name = "Test Resource", - description = "A test resource", - mimeType = "text/plain", - ) { - ReadResourceResult( - contents = listOf( - TextResourceContents( - text = "Test resource content", - uri = testResourceUri, - mimeType = "text/plain", - ), - ), - ) - } - - // Remove the resource - val result = server.removeResource(testResourceUri) - - // Verify the resource was removed - assertTrue(result, "Resource should be removed successfully") - } - - @Test - fun `removeResources should remove multiple resources and send notification`() = runTest { - // Add resources - val testResourceUri1 = "test://resource1" - val testResourceUri2 = "test://resource2" - server.addResource( - uri = testResourceUri1, - name = "Test Resource 1", - description = "A test resource 1", - mimeType = "text/plain", - ) { - ReadResourceResult( - contents = listOf( - TextResourceContents( - text = "Test resource content 1", - uri = testResourceUri1, - mimeType = "text/plain", - ), - ), - ) - } - server.addResource( - uri = testResourceUri2, - name = "Test Resource 2", - description = "A test resource 2", - mimeType = "text/plain", - ) { - ReadResourceResult( - contents = listOf( - TextResourceContents( - text = "Test resource content 2", - uri = testResourceUri2, - mimeType = "text/plain", - ), - ), - ) - } - - // Remove the resources - val result = server.removeResources(listOf(testResourceUri1, testResourceUri2)) - - // Verify the resources were removed - assertEquals(2, result, "Both resources should be removed") - } - - @Test - fun `removeResource should return false when resource does not exist`() = runTest { - // Track notifications - var resourceListChangedNotificationReceived = false - client.setNotificationHandler( - Method.Defined.NotificationsResourcesListChanged, - ) { - resourceListChangedNotificationReceived = true - CompletableDeferred(Unit) - } - - // Try to remove a non-existent resource - val result = server.removeResource("non-existent-resource") - - // Verify the result - assertFalse(result, "Removing non-existent resource should return false") - assertFalse( - resourceListChangedNotificationReceived, - "No notification should be sent when resource doesn't exist", - ) - } - - @Test - fun `removeResource should throw when resources capability is not supported`() = runTest { - // Create server without resources capability - val serverOptions = ServerOptions( - capabilities = ServerCapabilities(), - ) - val server = Server( - Implementation(name = "test server", version = "1.0"), - serverOptions, - ) - - // Verify that removing a resource throws an exception - val exception = assertThrows { - server.removeResource("test://resource") - } - assertEquals("Server does not support resources capability.", exception.message) - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerToolsTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerToolsTest.kt deleted file mode 100644 index b956e2c..0000000 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaServerToolsTest.kt +++ /dev/null @@ -1,91 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.server - -import io.modelcontextprotocol.kotlin.sdk.CallToolResult -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.Method -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities -import io.modelcontextprotocol.kotlin.sdk.TextContent -import io.modelcontextprotocol.kotlin.sdk.Tool -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -class OldSchemaServerToolsTest : OldSchemaAbstractServerFeaturesTest() { - - override fun getServerCapabilities(): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities = - ServerCapabilities( - tools = ServerCapabilities.Tools(null), - ) - - @Test - fun `removeTool should remove a tool`() = runTest { - // Add a tool - server.addTool("test-tool", "Test Tool", Tool.Input()) { - CallToolResult(listOf(TextContent("Test result"))) - } - - // Remove the tool - val result = server.removeTool("test-tool") - - // Verify the tool was removed - assertTrue(result, "Tool should be removed successfully") - } - - @Test - fun `removeTool should return false when tool does not exist`() = runTest { - // Track notifications - var toolListChangedNotificationReceived = false - client.setNotificationHandler( - Method.Defined.NotificationsToolsListChanged, - ) { - toolListChangedNotificationReceived = true - CompletableDeferred(Unit) - } - - // Try to remove a non-existent tool - val result = server.removeTool("non-existent-tool") - - // Verify the result - assertFalse(result, "Removing non-existent tool should return false") - assertFalse(toolListChangedNotificationReceived, "No notification should be sent when tool doesn't exist") - } - - @Test - fun `removeTool should throw when tools capability is not supported`() = runTest { - // Create server without tools capability - val serverOptions = ServerOptions( - capabilities = ServerCapabilities(), - ) - val server = Server( - Implementation(name = "test server", version = "1.0"), - serverOptions, - ) - - // Verify that removing a tool throws an exception - val exception = assertThrows { - server.removeTool("test-tool") - } - assertEquals("Server does not support tools capability.", exception.message) - } - - @Test - fun `removeTools should remove multiple tools`() = runTest { - // Add tools - server.addTool("test-tool-1", "Test Tool 1") { - CallToolResult(listOf(TextContent("Test result 1"))) - } - server.addTool("test-tool-2", "Test Tool 2") { - CallToolResult(listOf(TextContent("Test result 2"))) - } - - // Remove the tools - val result = server.removeTools(listOf("test-tool-1", "test-tool-2")) - - // Verify the tools were removed - assertEquals(2, result, "Both tools should be removed") - } -} diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerInstructionsTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerInstructionsTest.kt index 53d0fd1..c289b53 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerInstructionsTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/ServerInstructionsTest.kt @@ -1,12 +1,12 @@ package io.modelcontextprotocol.kotlin.sdk.server import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.client.ClientOptions import io.modelcontextprotocol.kotlin.sdk.client.mcpClient import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertNull diff --git a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt index 0f1353b..4ffb970 100644 --- a/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt +++ b/kotlin-sdk-client/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SSEClientTransport.kt @@ -34,9 +34,6 @@ import kotlin.concurrent.atomics.AtomicBoolean import kotlin.concurrent.atomics.ExperimentalAtomicApi import kotlin.time.Duration -@Deprecated("Use SseClientTransport instead", ReplaceWith("SseClientTransport"), DeprecationLevel.ERROR) -public typealias SSEClientTransport = SseClientTransport - /** * Client transport for SSE: this will connect to a server using Server-Sent Events for receiving * messages and make separate POST requests for sending messages. diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaClientMetaParameterTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaClientMetaParameterTest.kt deleted file mode 100644 index b541176..0000000 --- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaClientMetaParameterTest.kt +++ /dev/null @@ -1,275 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.client - -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.JSONRPCRequest -import kotlinx.coroutines.test.runTest -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.boolean -import kotlinx.serialization.json.int -import kotlinx.serialization.json.jsonPrimitive -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertContains -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertTrue - -/** - * Comprehensive test suite for MCP Client meta parameter functionality - * - * Tests cover: - * - Meta key validation according to MCP specification - * - JSON type conversion for various data types - * - Error handling for invalid meta keys - * - Integration with callTool method - */ -class OldSchemaClientMetaParameterTest { - - private lateinit var client: Client - private lateinit var mockTransport: OldSchemaMockTransport - private val clientInfo = Implementation("test-client", "1.0.0") - - @BeforeTest - fun setup() = runTest { - mockTransport = OldSchemaMockTransport() - client = Client(clientInfo = clientInfo) - mockTransport.setupInitializationResponse() - client.connect(mockTransport) - } - - @Test - fun `should accept valid meta keys without throwing exception`() = runTest { - val validMeta = buildMap { - put("simple-key", "value1") - put("api.example.com/version", "1.0") - put("com.company.app/setting", "enabled") - put("retry_count", 3) - put("user.preference", true) - put("valid123", "alphanumeric") - put("multi.dot.name", "multiple-dots") - put("under_score", "underscore") - put("hyphen-dash", "hyphen") - put("org.apache.kafka/consumer-config", "complex-valid-prefix") - } - - val result = runCatching { - client.callTool("test-tool", mapOf("arg" to "value"), validMeta) - } - - assertTrue(result.isSuccess, "Valid meta keys should not cause exceptions") - mockTransport.lastJsonRpcRequest()?.let { request -> - val params = request.params as JsonObject - assertTrue(params.containsKey("_meta"), "Request should contain _meta field") - val metaField = params["_meta"] as JsonObject - - // Verify all meta keys are present - assertEquals(validMeta.size, metaField.size, "All meta keys should be included") - - // Verify specific key-value pairs - assertEquals("value1", metaField["simple-key"]?.jsonPrimitive?.content) - assertEquals("1.0", metaField["api.example.com/version"]?.jsonPrimitive?.content) - assertEquals("enabled", metaField["com.company.app/setting"]?.jsonPrimitive?.content) - assertEquals(3, metaField["retry_count"]?.jsonPrimitive?.int) - assertEquals(true, metaField["user.preference"]?.jsonPrimitive?.boolean) - assertEquals("alphanumeric", metaField["valid123"]?.jsonPrimitive?.content) - assertEquals("multiple-dots", metaField["multi.dot.name"]?.jsonPrimitive?.content) - assertEquals("underscore", metaField["under_score"]?.jsonPrimitive?.content) - assertEquals("hyphen", metaField["hyphen-dash"]?.jsonPrimitive?.content) - assertEquals("complex-valid-prefix", metaField["org.apache.kafka/consumer-config"]?.jsonPrimitive?.content) - } - } - - @Test - fun `should accept edge case valid prefixes and names`() = runTest { - val edgeCaseValidMeta = buildMap { - put("a/", "single-char-prefix-empty-name") // empty name is allowed - put("a1-b2/test", "alphanumeric-hyphen-prefix") - put("long.domain.name.here/config", "long-prefix") - put("x/a", "minimal-valid-key") - put("test123", "alphanumeric-name-only") - } - - val result = runCatching { - client.callTool("test-tool", emptyMap(), edgeCaseValidMeta) - } - - assertTrue(result.isSuccess, "Edge case valid meta keys should be accepted") - } - - @Test - fun `should reject mcp reserved prefix`() = runTest { - val invalidMeta = mapOf("mcp/internal" to "value") - - val exception = assertFailsWith { - client.callTool("test-tool", emptyMap(), invalidMeta) - } - - assertContains( - charSequence = exception.message ?: "", - other = "Invalid _meta key", - ) - } - - @Test - fun `should reject modelcontextprotocol reserved prefix`() = runTest { - val invalidMeta = mapOf("modelcontextprotocol/config" to "value") - - val exception = assertFailsWith { - client.callTool("test-tool", emptyMap(), invalidMeta) - } - - assertContains( - charSequence = exception.message ?: "", - other = "Invalid _meta key", - ) - } - - @Test - fun `should reject nested reserved prefixes`() = runTest { - val invalidKeys = listOf( - "api.mcp.io/setting", - "com.modelcontextprotocol.test/value", - "example.mcp/data", - "subdomain.mcp.com/config", - "app.modelcontextprotocol.dev/setting", - "test.mcp/value", - "service.modelcontextprotocol/data", - ) - - invalidKeys.forEach { key -> - val exception = assertFailsWith( - message = "Should reject nested reserved key: $key", - ) { - client.callTool("test-tool", emptyMap(), mapOf(key to "value")) - } - assertContains( - charSequence = exception.message ?: "", - other = "Invalid _meta key", - ) - } - } - - @Test - fun `should reject case-insensitive reserved prefixes`() = runTest { - val invalidKeys = listOf( - "MCP/internal", - "Mcp/config", - "mCp/setting", - "MODELCONTEXTPROTOCOL/data", - "ModelContextProtocol/value", - "modelContextProtocol/test", - ) - - invalidKeys.forEach { key -> - val exception = assertFailsWith( - message = "Should reject case-insensitive reserved key: $key", - ) { - client.callTool("test-tool", emptyMap(), mapOf(key to "value")) - } - assertContains( - charSequence = exception.message ?: "", - other = "Invalid _meta key", - ) - } - } - - @Test - fun `should reject invalid key formats`() = runTest { - val invalidKeys = listOf( - "", // empty key - not allowed at key level - "/invalid", // starts with slash - "-invalid", // starts with hyphen - ".invalid", // starts with dot - "in valid", // contains space - "api../test", // consecutive dots - "api./test", // label ends with dot - ) - - invalidKeys.forEach { key -> - assertFailsWith( - message = "Should reject invalid key format: '$key'", - ) { - client.callTool("test-tool", emptyMap(), mapOf(key to "value")) - } - } - } - - @Test - fun `should convert various data types to JSON correctly`() = runTest { - val complexMeta = createComplexMetaData() - - val result = runCatching { - client.callTool( - "test-tool", - emptyMap(), - complexMeta, - ) - } - - assertTrue(result.isSuccess, "Complex data type conversion should not throw exceptions") - - mockTransport.lastJsonRpcRequest()?.let { request -> - assertEquals("tools/call", request.method) - val params = request.params as JsonObject - assertTrue(params.containsKey("_meta"), "Request should contain _meta field") - } - } - - @Test - fun `should handle nested map structures correctly`() = runTest { - val nestedMeta = buildNestedConfiguration() - - val result = runCatching { - client.callTool("test-tool", emptyMap(), nestedMeta) - } - - assertTrue(result.isSuccess) - - mockTransport.lastJsonRpcRequest()?.let { request -> - val params = request.params as JsonObject - val metaField = params["_meta"] as JsonObject - assertTrue(metaField.containsKey("config")) - } - } - - @Test - fun `should include empty meta object when meta parameter not provided`() = runTest { - client.callTool("test-tool", mapOf("arg" to "value")) - - mockTransport.lastJsonRpcRequest()?.let { request -> - val params = request.params as JsonObject - val metaField = params["_meta"] as JsonObject - assertTrue(metaField.isEmpty(), "Meta field should be empty when not provided") - } - } - - private fun createComplexMetaData(): Map = buildMap { - put("string", "text") - put("number", 42) - put("boolean", true) - put("null_value", null) - put("list", listOf(1, 2, 3)) - put("map", mapOf("nested" to "value")) - put("enum", "STRING") - put("int_array", intArrayOf(1, 2, 3)) - } - - private fun buildNestedConfiguration(): Map = buildMap { - put( - "config", - buildMap { - put( - "database", - buildMap { - put("host", "localhost") - put("port", 5432) - }, - ) - put("features", listOf("feature1", "feature2")) - }, - ) - } -} - -suspend fun OldSchemaMockTransport.lastJsonRpcRequest(): JSONRPCRequest? = - getSentMessages().lastOrNull() as? JSONRPCRequest diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaMockTransport.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaMockTransport.kt deleted file mode 100644 index 1a0db70..0000000 --- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaMockTransport.kt +++ /dev/null @@ -1,95 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.client - -import io.modelcontextprotocol.kotlin.sdk.CallToolResult -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.InitializeResult -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage -import io.modelcontextprotocol.kotlin.sdk.JSONRPCRequest -import io.modelcontextprotocol.kotlin.sdk.JSONRPCResponse -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities -import io.modelcontextprotocol.kotlin.sdk.shared.Transport -import io.modelcontextprotocol.kotlin.sdk.shared.TransportSendOptions -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock - -class OldSchemaMockTransport : Transport { - private val _sentMessages = mutableListOf() - private val _receivedMessages = mutableListOf() - private val mutex = Mutex() - - suspend fun getSentMessages() = mutex.withLock { _sentMessages.toList() } - suspend fun getReceivedMessages() = mutex.withLock { _receivedMessages.toList() } - - private var onMessageBlock: (suspend (JSONRPCMessage) -> Unit)? = null - private var onCloseBlock: (() -> Unit)? = null - private var onErrorBlock: ((Throwable) -> Unit)? = null - - override suspend fun start() = Unit - - override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) { - mutex.withLock { - _sentMessages += message - } - - // Auto-respond to initialization and tool calls - when (message) { - is JSONRPCRequest -> { - when (message.method) { - "initialize" -> { - val initResponse = JSONRPCResponse( - id = message.id, - result = InitializeResult( - protocolVersion = "2024-11-05", - capabilities = ServerCapabilities( - tools = ServerCapabilities.Tools(listChanged = null), - ), - serverInfo = Implementation("mock-server", "1.0.0"), - ), - ) - onMessageBlock?.invoke(initResponse) - } - - "tools/call" -> { - val toolResponse = JSONRPCResponse( - id = message.id, - result = CallToolResult( - content = listOf(), - isError = false, - ), - ) - onMessageBlock?.invoke(toolResponse) - } - } - } - - else -> { - // Handle other message types if needed - } - } - } - - override suspend fun close() { - onCloseBlock?.invoke() - } - - override fun onMessage(block: suspend (JSONRPCMessage) -> Unit) { - onMessageBlock = { message -> - mutex.withLock { - _receivedMessages += message - } - block(message) - } - } - - override fun onClose(block: () -> Unit) { - onCloseBlock = block - } - - override fun onError(block: (Throwable) -> Unit) { - onErrorBlock = block - } - - fun setupInitializationResponse() { - // This method helps set up the mock for proper initialization - } -} diff --git a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaStreamableHttpClientTransportTest.kt b/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaStreamableHttpClientTransportTest.kt deleted file mode 100644 index c5be21e..0000000 --- a/kotlin-sdk-client/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldSchemaStreamableHttpClientTransportTest.kt +++ /dev/null @@ -1,422 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.client - -import io.ktor.client.HttpClient -import io.ktor.client.engine.mock.MockEngine -import io.ktor.client.engine.mock.MockRequestHandler -import io.ktor.client.engine.mock.respond -import io.ktor.client.plugins.sse.SSE -import io.ktor.http.ContentType -import io.ktor.http.HttpHeaders -import io.ktor.http.HttpMethod -import io.ktor.http.HttpStatusCode -import io.ktor.http.content.TextContent -import io.ktor.http.headersOf -import io.ktor.utils.io.ByteReadChannel -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage -import io.modelcontextprotocol.kotlin.sdk.JSONRPCNotification -import io.modelcontextprotocol.kotlin.sdk.JSONRPCRequest -import io.modelcontextprotocol.kotlin.sdk.RequestId -import io.modelcontextprotocol.kotlin.sdk.shared.McpJson -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay -import kotlinx.coroutines.test.runTest -import kotlinx.coroutines.withContext -import kotlinx.coroutines.withTimeout -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.buildJsonObject -import kotlin.test.Ignore -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertNull -import kotlin.test.assertTrue -import kotlin.time.Duration.Companion.seconds - -class OldSchemaStreamableHttpClientTransportTest { - - private fun createTransport(handler: MockRequestHandler): StreamableHttpClientTransport { - val mockEngine = MockEngine(handler) - val httpClient = HttpClient(mockEngine) { - install(SSE) { - reconnectionTime = 1.seconds - } - } - - return StreamableHttpClientTransport(httpClient, url = "http://localhost:8080/mcp") - } - - @Test - fun testSendJsonRpcMessage() = runTest { - val message = JSONRPCRequest( - id = RequestId.StringId("test-id"), - method = "test", - params = buildJsonObject { }, - ) - - val transport = createTransport { request -> - assertEquals(HttpMethod.Post, request.method) - assertEquals("http://localhost:8080/mcp", request.url.toString()) - assertEquals(ContentType.Application.Json, request.body.contentType) - - val body = (request.body as TextContent).text - val decodedMessage = McpJson.decodeFromString(body) - assertEquals(message, decodedMessage) - - respond( - content = "", - status = HttpStatusCode.Accepted, - ) - } - - transport.start() - transport.send(message) - transport.close() - } - - @Test - fun testStoreSessionId() = runTest { - val initMessage = JSONRPCRequest( - id = RequestId.StringId("test-id"), - method = "initialize", - params = buildJsonObject { - put( - "clientInfo", - buildJsonObject { - put("name", JsonPrimitive("test-client")) - put("version", JsonPrimitive("1.0")) - }, - ) - put("protocolVersion", JsonPrimitive("2025-06-18")) - }, - ) - - val transport = createTransport { request -> - when (val msg = McpJson.decodeFromString((request.body as TextContent).text)) { - is JSONRPCRequest if msg.method == "initialize" -> respond( - content = "", - status = HttpStatusCode.OK, - headers = headersOf("mcp-session-id", "test-session-id"), - ) - - is JSONRPCNotification if msg.method == "test" -> { - assertEquals("test-session-id", request.headers["mcp-session-id"]) - respond( - content = "", - status = HttpStatusCode.Accepted, - ) - } - - else -> error("Unexpected message: $msg") - } - } - - transport.start() - transport.send(initMessage) - - assertEquals("test-session-id", transport.sessionId) - - transport.send(JSONRPCNotification(method = "test")) - - transport.close() - } - - @Test - fun testTerminateSession() = runTest { -// transport.sessionId = "test-session-id" - - val transport = createTransport { request -> - assertEquals(HttpMethod.Delete, request.method) - assertEquals("test-session-id", request.headers["mcp-session-id"]) - respond( - content = "", - status = HttpStatusCode.OK, - ) - } - - transport.start() - transport.terminateSession() - - assertNull(transport.sessionId) - transport.close() - } - - @Test - fun testTerminateSessionHandle405() = runTest { -// transport.sessionId = "test-session-id" - - val transport = createTransport { request -> - assertEquals(HttpMethod.Delete, request.method) - respond( - content = "", - status = HttpStatusCode.MethodNotAllowed, - ) - } - - transport.start() - // Should not throw for 405 - transport.terminateSession() - - // Session ID should still be cleared - assertNull(transport.sessionId) - transport.close() - } - - @Test - fun testProtocolVersionHeader() = runTest { - val transport = createTransport { request -> - assertEquals("2025-06-18", request.headers["mcp-protocol-version"]) - respond( - content = "", - status = HttpStatusCode.Accepted, - ) - } - transport.protocolVersion = "2025-06-18" - - transport.start() - transport.send(JSONRPCNotification(method = "test")) - transport.close() - } - - // Engine doesn't support SSECapability: https://youtrack.jetbrains.com/issue/KTOR-8177/MockEngine-Add-SSE-support - @Ignore - @Test - fun testNotificationSchemaE2E() = runTest { - val receivedMessages = mutableListOf() - var sseStarted = false - - val transport = createTransport { request -> - when (request.method) { - HttpMethod.Post if request.body.toString().contains("notifications/initialized") -> { - respond( - content = "", - status = HttpStatusCode.Accepted, - headers = headersOf("mcp-session-id", "notification-test-session"), - ) - } - - // Handle SSE connection - HttpMethod.Get -> { - sseStarted = true - val sseContent = buildString { - // Server sends various notifications - appendLine("event: message") - appendLine("id: 1") - appendLine( - """data: {"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"upload-123","progress":50,"total":100}}""", - ) - appendLine() - - appendLine("event: message") - appendLine("id: 2") - appendLine("""data: {"jsonrpc":"2.0","method":"notifications/resources/list_changed"}""") - appendLine() - - appendLine("event: message") - appendLine("id: 3") - appendLine("""data: {"jsonrpc":"2.0","method":"notifications/tools/list_changed"}""") - appendLine() - } - respond( - content = ByteReadChannel(sseContent), - status = HttpStatusCode.OK, - headers = headersOf( - HttpHeaders.ContentType, - ContentType.Text.EventStream.toString(), - ), - ) - } - - // Handle regular notifications - HttpMethod.Post -> { - respond( - content = "", - status = HttpStatusCode.Accepted, - ) - } - - else -> respond("", HttpStatusCode.OK) - } - } - - transport.onMessage { message -> - receivedMessages.add(message) - } - - transport.start() - - // Test 1: Send initialized notification to trigger SSE - val initializedNotification = JSONRPCNotification( - method = "notifications/initialized", - params = buildJsonObject { - put("protocolVersion", JsonPrimitive("1.0")) - put( - "capabilities", - buildJsonObject { - put("tools", JsonPrimitive(true)) - put("resources", JsonPrimitive(true)) - }, - ) - }, - ) - - transport.send(initializedNotification) - - // Verify SSE was triggered - assertTrue(sseStarted, "SSE should start after initialized notification") - - // Test 2: Verify received notifications - assertEquals(3, receivedMessages.size) - assertTrue(receivedMessages.all { it is JSONRPCNotification }) - - val notifications = receivedMessages.filterIsInstance() - - // Verify progress notification - val progressNotif = notifications[0] - assertEquals("notifications/progress", progressNotif.method) - val progressParams = progressNotif.params as JsonObject - assertEquals("upload-123", (progressParams["progressToken"] as JsonPrimitive).content) - assertEquals(50, (progressParams["progress"] as JsonPrimitive).content.toInt()) - - // Verify list changed notifications - assertEquals("notifications/resources/list_changed", notifications[1].method) - assertEquals("notifications/tools/list_changed", notifications[2].method) - - // Test 3: Send various client notifications - val clientNotifications = listOf( - JSONRPCNotification( - method = "notifications/progress", - params = buildJsonObject { - put("progressToken", JsonPrimitive("download-456")) - put("progress", JsonPrimitive(75)) - }, - ), - JSONRPCNotification( - method = "notifications/cancelled", - params = buildJsonObject { - put("requestId", JsonPrimitive("req-789")) - put("reason", JsonPrimitive("user_cancelled")) - }, - ), - JSONRPCNotification( - method = "notifications/message", - params = buildJsonObject { - put("level", JsonPrimitive("info")) - put("message", JsonPrimitive("Operation completed")) - put( - "data", - buildJsonObject { - put("duration", JsonPrimitive(1234)) - }, - ) - }, - ), - ) - - // Send all client notifications - clientNotifications.forEach { notification -> - transport.send(notification) - } - - // Verify session ID is maintained - assertEquals("notification-test-session", transport.sessionId) - transport.close() - } - - // Engine doesn't support SSECapability: https://youtrack.jetbrains.com/issue/KTOR-8177/MockEngine-Add-SSE-support - @Ignore - @Test - fun testNotificationWithResumptionToken() = runTest { - var resumptionTokenReceived: String? = null - var lastEventIdSent: String? = null - - val transport = createTransport { request -> - // Capture Last-Event-ID header - lastEventIdSent = request.headers["Last-Event-ID"] - - when (request.method) { - HttpMethod.Get -> { - val sseContent = buildString { - appendLine("event: message") - appendLine("id: resume-100") - appendLine( - """data: {"jsonrpc":"2.0","method":"notifications/resumed","params":{"fromToken":"$lastEventIdSent"}}""", - ) - appendLine() - } - respond( - content = ByteReadChannel(sseContent), - status = HttpStatusCode.OK, - headers = headersOf( - HttpHeaders.ContentType, - ContentType.Text.EventStream.toString(), - ), - ) - } - - else -> respond("", HttpStatusCode.Accepted) - } - } - - transport.start() - - // Send notification with resumption token - transport.send( - message = JSONRPCNotification( - method = "notifications/test", - params = buildJsonObject { - put("data", JsonPrimitive("test-data")) - }, - ), - resumptionToken = "previous-token-99", - onResumptionToken = { token -> - resumptionTokenReceived = token - }, - ) - - // Wait for response - delay(1.seconds) - - // Verify resumption token was sent in header - assertEquals("previous-token-99", lastEventIdSent) - - // Verify new resumption token was received - assertEquals("resume-100", resumptionTokenReceived) - transport.close() - } - - @Test - fun testClientConnectWithInvalidJson() = runTest { - // Transport under test: respond with invalid JSON for the initialize request - val transport = createTransport { _ -> - respond( - "this is not valid json", - status = HttpStatusCode.OK, - headers = headersOf(HttpHeaders.ContentType, ContentType.Application.Json.toString()), - ) - } - - val client = Client( - clientInfo = Implementation( - name = "test-client", - version = "1.0", - ), - ) - - try { - // Real time-keeping is needed; otherwise Protocol will always throw TimeoutCancellationException in tests - assertFailsWith( - message = "Expected client.connect to fail on invalid JSON response", - ) { - withContext(Dispatchers.Default.limitedParallelism(1)) { - withTimeout(5.seconds) { - client.connect(transport) - } - } - } - } finally { - transport.close() - } - } -} diff --git a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldStreamableHttpClientTest.kt b/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldStreamableHttpClientTest.kt deleted file mode 100644 index d61d349..0000000 --- a/kotlin-sdk-client/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/OldStreamableHttpClientTest.kt +++ /dev/null @@ -1,218 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.client - -import io.kotest.matchers.collections.shouldContain -import io.ktor.http.ContentType -import io.ktor.http.HttpMethod -import io.ktor.http.HttpStatusCode -import io.ktor.sse.ServerSentEvent -import io.modelcontextprotocol.kotlin.sdk.ClientCapabilities -import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject -import io.modelcontextprotocol.kotlin.sdk.Implementation -import io.modelcontextprotocol.kotlin.sdk.Tool -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.runBlocking -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.put -import kotlinx.serialization.json.putJsonObject -import org.junit.jupiter.api.TestInstance -import kotlin.test.Test -import kotlin.time.Duration.Companion.milliseconds -import kotlin.time.Duration.Companion.seconds -import kotlin.uuid.ExperimentalUuidApi -import kotlin.uuid.Uuid - -/** - * Integration tests for the `StreamableHttpClientTransport` implementation - * using the [Mokksy](https://mokksy.dev) library - * to simulate Streaming HTTP with server-sent events (SSE). - * @author Konstantin Pavlov - */ -@OptIn(ExperimentalUuidApi::class) -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -internal class OldStreamableHttpClientTest : AbstractStreamableHttpClientTest() { - - @Test - fun `test streamableHttpClient`() = runBlocking { - val client = Client( - clientInfo = Implementation( - name = "client1", - version = "1.0.0", - ), - options = ClientOptions( - capabilities = ClientCapabilities(), - ), - ) - - val sessionId = Uuid.random().toString() - - mockMcp.onInitialize( - clientName = "client1", - sessionId = sessionId, - ) - - mockMcp.handleJSONRPCRequest( - jsonRpcMethod = "notifications/initialized", - expectedSessionId = sessionId, - sessionId = sessionId, - statusCode = HttpStatusCode.Accepted, - ) - - mockMcp.handleSubscribeWithGet(sessionId) { - flow { - delay(500.milliseconds) - emit( - ServerSentEvent( - event = "message", - id = "1", - data = @Suppress("MaxLineLength") - //language=json - """{"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"upload-123","progress":50,"total":100}}""", - ), - ) - delay(200.milliseconds) - emit( - ServerSentEvent( - data = @Suppress("MaxLineLength") - //language=json - """{"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"upload-123","progress":50,"total":100}}""", - ), - ) - } - } - - // TODO: how to get notifications via Client API? - - mockMcp.handleWithResult( - jsonRpcMethod = "tools/list", - sessionId = sessionId, - // language=json - result = """ - { - "tools": [ - { - "name": "get_weather", - "title": "Weather Information Provider", - "description": "Get current weather information for a location", - "inputSchema": { - "type": "object", - "properties": { - "location": { - "type": "string", - "description": "City name or zip code" - } - }, - "required": ["location"] - }, - "outputSchema": { - "type": "object", - "properties": { - "temperature": { - "type": "number", - "description": "Temperature, Celsius" - } - }, - "required": ["temperature"] - }, - "_meta": {} - } - ] - } - """.trimIndent(), - ) - - connect(client) - - val listToolsResult = client.listTools() - - listToolsResult.tools shouldContain Tool( - name = "get_weather", - title = "Weather Information Provider", - description = "Get current weather information for a location", - inputSchema = Tool.Input( - properties = buildJsonObject { - putJsonObject("location") { - put("type", "string") - put("description", "City name or zip code") - } - }, - required = listOf("location"), - ), - outputSchema = Tool.Output( - properties = buildJsonObject { - putJsonObject("temperature") { - put("type", "number") - put("description", "Temperature, Celsius") - } - }, - required = listOf("temperature"), - ), - annotations = null, - _meta = EmptyJsonObject, - ) - - mockMcp.mockUnsubscribeRequest(sessionId = sessionId) - - client.close() - } - - @Test - fun `handle MethodNotAllowed`() = runBlocking { - checkSupportNonStreamingResponse( - ContentType.Text.EventStream, - HttpStatusCode.MethodNotAllowed, - ) - } - - @Test - fun `handle non-streaming response`() = runBlocking { - checkSupportNonStreamingResponse( - ContentType.Application.Json, - HttpStatusCode.OK, - ) - } - - private suspend fun checkSupportNonStreamingResponse(contentType: ContentType, statusCode: HttpStatusCode) { - val sessionId = "SID_${Uuid.random().toHexString()}" - val clientName = "client-${Uuid.random().toHexString()}" - val client = Client( - clientInfo = Implementation(name = clientName, version = "1.0.0"), - options = ClientOptions( - capabilities = ClientCapabilities(), - ), - ) - - mockMcp.onInitialize(clientName = clientName, sessionId = sessionId) - - mockMcp.handleJSONRPCRequest( - jsonRpcMethod = "notifications/initialized", - expectedSessionId = sessionId, - sessionId = sessionId, - statusCode = HttpStatusCode.Accepted, - ) - - mockMcp.onSubscribe( - httpMethod = HttpMethod.Get, - sessionId = sessionId, - ) respondsWith { - headers += MCP_SESSION_ID_HEADER to sessionId - body = null - httpStatus = statusCode - this.contentType = contentType - } - - mockMcp.handleWithResult(jsonRpcMethod = "ping", sessionId = sessionId) { - buildJsonObject {} - } - - mockMcp.mockUnsubscribeRequest(sessionId = sessionId) - - connect(client) - - delay(1.seconds) - - client.ping() // connection is still alive - - client.close() - } -} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt index 358b565..4c0cc0d 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Protocol.kt @@ -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 { diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt index 23b0e45..3447f18 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.kt @@ -18,7 +18,7 @@ import kotlin.concurrent.atomics.ExperimentalAtomicApi "LATEST_PROTOCOL_VERSION", "io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public const val LATEST_PROTOCOL_VERSION: String = io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION @@ -28,7 +28,7 @@ public const val LATEST_PROTOCOL_VERSION: String = io.modelcontextprotocol.kotli "SUPPORTED_PROTOCOL_VERSIONS", "io.modelcontextprotocol.kotlin.sdk.types.SUPPORTED_PROTOCOL_VERSIONS", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val SUPPORTED_PROTOCOL_VERSIONS: List = io.modelcontextprotocol.kotlin.sdk.types.SUPPORTED_PROTOCOL_VERSIONS @@ -36,7 +36,7 @@ public val SUPPORTED_PROTOCOL_VERSIONS: List = @Deprecated( message = "Use `JSONRPC_VERSION` instead", replaceWith = ReplaceWith("JSONRPC_VERSION", "io.modelcontextprotocol.kotlin.sdk.types.JSONRPC_VERSION"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public const val JSONRPC_VERSION: String = io.modelcontextprotocol.kotlin.sdk.types.JSONRPC_VERSION @@ -44,7 +44,7 @@ public const val JSONRPC_VERSION: String = io.modelcontextprotocol.kotlin.sdk.ty @Deprecated( message = "Use `REQUEST_MESSAGE_ID` instead", replaceWith = ReplaceWith("REQUEST_MESSAGE_ID", "io.modelcontextprotocol.kotlin.sdk.types.REQUEST_MESSAGE_ID"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) private val REQUEST_MESSAGE_ID: AtomicLong = AtomicLong(0L) @@ -55,7 +55,7 @@ private val REQUEST_MESSAGE_ID: AtomicLong = AtomicLong(0L) @Deprecated( message = "Use `ProgressToken` instead", replaceWith = ReplaceWith("ProgressToken", "io.modelcontextprotocol.kotlin.sdk.types.ProgressToken"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ProgressToken = io.modelcontextprotocol.kotlin.sdk.types.ProgressToken @@ -65,7 +65,7 @@ public typealias ProgressToken = io.modelcontextprotocol.kotlin.sdk.types.Progre @Deprecated( message = "This alias will be removed. Use String directly instead.", replaceWith = ReplaceWith("String"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias Cursor = String @@ -75,7 +75,7 @@ public typealias Cursor = String @Deprecated( message = "Use `WithMeta` instead", replaceWith = ReplaceWith("WithMeta", "io.modelcontextprotocol.kotlin.sdk.types.WithMeta"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias WithMeta = io.modelcontextprotocol.kotlin.sdk.types.WithMeta @@ -87,7 +87,7 @@ public typealias WithMeta = io.modelcontextprotocol.kotlin.sdk.types.WithMeta @Deprecated( message = "This class will be removed. Use `WithMeta` instead", replaceWith = ReplaceWith("WithMeta", "io.modelcontextprotocol.kotlin.sdk.types.WithMeta"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias CustomMeta = io.modelcontextprotocol.kotlin.sdk.types.WithMeta @@ -97,7 +97,7 @@ public typealias CustomMeta = io.modelcontextprotocol.kotlin.sdk.types.WithMeta @Deprecated( message = "Use `Method` instead", replaceWith = ReplaceWith("Method", "io.modelcontextprotocol.kotlin.sdk.types.Method"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public sealed interface Method { public val value: String @@ -109,7 +109,7 @@ public sealed interface Method { @Deprecated( message = "Use `Method.Custom` instead", replaceWith = ReplaceWith("Method.Custom", "io.modelcontextprotocol.kotlin.sdk.types.Method.Custom"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Custom(value: String): io.modelcontextprotocol.kotlin.sdk.types.Method.Custom = io.modelcontextprotocol.kotlin.sdk.types.Method.Custom(value) @@ -122,7 +122,7 @@ public sealed interface Method { "Method.Defined.Initialize", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.Initialize", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val Initialize: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.Initialize @@ -133,7 +133,7 @@ public sealed interface Method { "Method.Defined.Ping", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.Ping", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val Ping: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.Ping @@ -144,7 +144,7 @@ public sealed interface Method { "Method.Defined.ResourcesList", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesList", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val ResourcesList: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesList @@ -155,7 +155,7 @@ public sealed interface Method { "Method.Defined.ResourcesTemplatesList", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesTemplatesList", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val ResourcesTemplatesList: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesTemplatesList @@ -166,7 +166,7 @@ public sealed interface Method { "Method.Defined.ResourcesRead", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesRead", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val ResourcesRead: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesRead @@ -177,7 +177,7 @@ public sealed interface Method { "Method.Defined.ResourcesSubscribe", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesSubscribe", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val ResourcesSubscribe: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesSubscribe @@ -188,7 +188,7 @@ public sealed interface Method { "Method.Defined.ResourcesUnsubscribe", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesUnsubscribe", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val ResourcesUnsubscribe: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ResourcesUnsubscribe @@ -199,7 +199,7 @@ public sealed interface Method { "Method.Defined.PromptsList", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.PromptsList", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val PromptsList: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.PromptsList @@ -210,7 +210,7 @@ public sealed interface Method { "Method.Defined.PromptsGet", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.PromptsGet", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val PromptsGet: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.PromptsGet @@ -221,7 +221,7 @@ public sealed interface Method { "Method.Defined.NotificationsCancelled", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsCancelled", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val NotificationsCancelled: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsCancelled @@ -232,7 +232,7 @@ public sealed interface Method { "Method.Defined.NotificationsInitialized", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsInitialized", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val NotificationsInitialized: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsInitialized @@ -243,7 +243,7 @@ public sealed interface Method { "Method.Defined.NotificationsProgress", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsProgress", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val NotificationsProgress: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsProgress @@ -254,7 +254,7 @@ public sealed interface Method { "Method.Defined.NotificationsMessage", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsMessage", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val NotificationsMessage: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsMessage @@ -265,7 +265,7 @@ public sealed interface Method { "Method.Defined.NotificationsResourcesUpdated", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsResourcesUpdated", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val NotificationsResourcesUpdated: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsResourcesUpdated @@ -276,7 +276,7 @@ public sealed interface Method { "Method.Defined.NotificationsResourcesListChanged", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsResourcesListChanged", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val NotificationsResourcesListChanged: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsResourcesListChanged @@ -287,7 +287,7 @@ public sealed interface Method { "Method.Defined.NotificationsToolsListChanged", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsToolsListChanged", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val NotificationsToolsListChanged: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsToolsListChanged @@ -298,7 +298,7 @@ public sealed interface Method { "Method.Defined.NotificationsRootsListChanged", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsRootsListChanged", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val NotificationsRootsListChanged: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsRootsListChanged @@ -309,7 +309,7 @@ public sealed interface Method { "Method.Defined.NotificationsPromptsListChanged", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsPromptsListChanged", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val NotificationsPromptsListChanged: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.NotificationsPromptsListChanged @@ -320,7 +320,7 @@ public sealed interface Method { "Method.Defined.ToolsList", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ToolsList", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val ToolsList: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ToolsList @@ -331,7 +331,7 @@ public sealed interface Method { "Method.Defined.ToolsCall", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ToolsCall", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val ToolsCall: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ToolsCall @@ -342,7 +342,7 @@ public sealed interface Method { "Method.Defined.LoggingSetLevel", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.LoggingSetLevel", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val LoggingSetLevel: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.LoggingSetLevel @@ -353,7 +353,7 @@ public sealed interface Method { "Method.Defined.SamplingCreateMessage", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.SamplingCreateMessage", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val SamplingCreateMessage: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.SamplingCreateMessage @@ -364,7 +364,7 @@ public sealed interface Method { "Method.Defined.CompletionComplete", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.CompletionComplete", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val CompletionComplete: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.CompletionComplete @@ -375,7 +375,7 @@ public sealed interface Method { "Method.Defined.RootsList", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.RootsList", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val RootsList: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.RootsList @@ -386,7 +386,7 @@ public sealed interface Method { "Method.Defined.ElicitationCreate", "io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ElicitationCreate", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val ElicitationCreate: io.modelcontextprotocol.kotlin.sdk.types.Method.Defined = io.modelcontextprotocol.kotlin.sdk.types.Method.Defined.ElicitationCreate @@ -399,7 +399,7 @@ public sealed interface Method { @Deprecated( message = "Use `Request` instead", replaceWith = ReplaceWith("Request", "io.modelcontextprotocol.kotlin.sdk.types.Request"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias Request = io.modelcontextprotocol.kotlin.sdk.types.Request @@ -411,8 +411,9 @@ public typealias Request = io.modelcontextprotocol.kotlin.sdk.types.Request @Deprecated( message = "Use `toJSON` instead", replaceWith = ReplaceWith("toJSON", "io.modelcontextprotocol.kotlin.sdk.types.toJSON"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) +@Suppress("DEPRECATION_ERROR") public fun Request.toJSON(): JSONRPCRequest = this.toJSON() /** @@ -423,8 +424,9 @@ public fun Request.toJSON(): JSONRPCRequest = this.toJSON() @Deprecated( message = "Use `fromJSON` instead", replaceWith = ReplaceWith("fromJSON", "io.modelcontextprotocol.kotlin.sdk.types.fromJSON"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) +@Suppress("DEPRECATION_ERROR") internal fun JSONRPCRequest.fromJSON(): Request = this.fromJSON() /** @@ -435,7 +437,7 @@ internal fun JSONRPCRequest.fromJSON(): Request = this.fromJSON() @Deprecated( message = "Use `CustomRequest` instead", replaceWith = ReplaceWith("CustomRequest", "io.modelcontextprotocol.kotlin.sdk.types.CustomRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias CustomRequest = io.modelcontextprotocol.kotlin.sdk.types.CustomRequest @@ -445,7 +447,7 @@ public typealias CustomRequest = io.modelcontextprotocol.kotlin.sdk.types.Custom @Deprecated( message = "Use `Notification` instead", replaceWith = ReplaceWith("Notification", "io.modelcontextprotocol.kotlin.sdk.types.Notification"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias Notification = io.modelcontextprotocol.kotlin.sdk.types.Notification @@ -457,8 +459,9 @@ public typealias Notification = io.modelcontextprotocol.kotlin.sdk.types.Notific @Deprecated( message = "Use `toJSON` instead", replaceWith = ReplaceWith("toJSON", "io.modelcontextprotocol.kotlin.sdk.types.toJSON"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) +@Suppress("DEPRECATION_ERROR") public fun Notification.toJSON(): JSONRPCNotification = this.toJSON() /** @@ -469,8 +472,9 @@ public fun Notification.toJSON(): JSONRPCNotification = this.toJSON() @Deprecated( message = "Use `fromJSON` instead", replaceWith = ReplaceWith("toJSON", "io.modelcontextprotocol.kotlin.sdk.types.fromJSON"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) +@Suppress("DEPRECATION_ERROR") internal fun JSONRPCNotification.fromJSON(): Notification = this.fromJSON() /** @@ -479,7 +483,7 @@ internal fun JSONRPCNotification.fromJSON(): Notification = this.fromJSON() @Deprecated( message = "Use `RequestResult` instead", replaceWith = ReplaceWith("RequestResult", "io.modelcontextprotocol.kotlin.sdk.types.RequestResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias RequestResult = io.modelcontextprotocol.kotlin.sdk.types.RequestResult @@ -491,7 +495,7 @@ public typealias RequestResult = io.modelcontextprotocol.kotlin.sdk.types.Reques @Deprecated( message = "Use `EmptyResult` instead", replaceWith = ReplaceWith("EmptyResult", "io.modelcontextprotocol.kotlin.sdk.types.EmptyResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias EmptyRequestResult = io.modelcontextprotocol.kotlin.sdk.types.EmptyResult @@ -501,13 +505,13 @@ public typealias EmptyRequestResult = io.modelcontextprotocol.kotlin.sdk.types.E @Deprecated( message = "Use `RequestId` instead", replaceWith = ReplaceWith("RequestId", "io.modelcontextprotocol.kotlin.sdk.types.RequestId"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object RequestId { @Deprecated( message = "Use `RequestId.StringId` instead", replaceWith = ReplaceWith("RequestId.StringId", "io.modelcontextprotocol.kotlin.sdk.types.RequestId.StringId"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun StringId(value: String): io.modelcontextprotocol.kotlin.sdk.types.RequestId.StringId = io.modelcontextprotocol.kotlin.sdk.types.RequestId.StringId(value) @@ -515,7 +519,7 @@ public object RequestId { @Deprecated( message = "Use `RequestId.NumberId` instead", replaceWith = ReplaceWith("RequestId.NumberId", "io.modelcontextprotocol.kotlin.sdk.types.RequestId.NumberId"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun NumberId(value: Long): io.modelcontextprotocol.kotlin.sdk.types.RequestId.NumberId = io.modelcontextprotocol.kotlin.sdk.types.RequestId.NumberId(value) @@ -527,7 +531,7 @@ public object RequestId { @Deprecated( message = "Use `JSONRPCMessage` instead", replaceWith = ReplaceWith("JSONRPCMessage", "io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias JSONRPCMessage = io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage @@ -537,7 +541,7 @@ public typealias JSONRPCMessage = io.modelcontextprotocol.kotlin.sdk.types.JSONR @Deprecated( message = "Use `JSONRPCRequest` instead", replaceWith = ReplaceWith("JSONRPCRequest", "io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias JSONRPCRequest = io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest @@ -547,7 +551,7 @@ public typealias JSONRPCRequest = io.modelcontextprotocol.kotlin.sdk.types.JSONR @Deprecated( message = "Use `JSONRPCNotification` instead", replaceWith = ReplaceWith("JSONRPCNotification", "io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias JSONRPCNotification = io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification @@ -557,7 +561,7 @@ public typealias JSONRPCNotification = io.modelcontextprotocol.kotlin.sdk.types. @Deprecated( message = "Use `JSONRPCResponse` instead", replaceWith = ReplaceWith("JSONRPCResponse", "io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias JSONRPCResponse = io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse @@ -567,7 +571,7 @@ public typealias JSONRPCResponse = io.modelcontextprotocol.kotlin.sdk.types.JSON @Deprecated( message = "Use `RPCError` instead", replaceWith = ReplaceWith("RPCError", "io.modelcontextprotocol.kotlin.sdk.types.RPCError"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public sealed interface ErrorCode { public val code: Int @@ -579,7 +583,7 @@ public sealed interface ErrorCode { "RPCError.ErrorCode.CONNECTION_CLOSED", "io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.CONNECTION_CLOSED", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val ConnectionClosed: Int = io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.CONNECTION_CLOSED @@ -589,7 +593,7 @@ public sealed interface ErrorCode { "RPCError.ErrorCode.REQUEST_TIMEOUT", "io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.REQUEST_TIMEOUT", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val RequestTimeout: Int = io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.REQUEST_TIMEOUT @@ -599,7 +603,7 @@ public sealed interface ErrorCode { "RPCError.ErrorCode.PARSE_ERROR", "io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.PARSE_ERROR", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val ParseError: Int = io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.PARSE_ERROR @@ -609,7 +613,7 @@ public sealed interface ErrorCode { "RPCError.ErrorCode.INVALID_REQUEST", "io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.INVALID_REQUEST", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val InvalidRequest: Int = io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.INVALID_REQUEST @@ -619,7 +623,7 @@ public sealed interface ErrorCode { "RPCError.ErrorCode.METHOD_NOT_FOUND", "io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.METHOD_NOT_FOUND", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val MethodNotFound: Int = io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.METHOD_NOT_FOUND @@ -629,7 +633,7 @@ public sealed interface ErrorCode { "RPCError.ErrorCode.INVALID_PARAMS", "io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.INVALID_PARAMS", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val InvalidParams: Int = io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.INVALID_PARAMS @@ -639,7 +643,7 @@ public sealed interface ErrorCode { "RPCError.ErrorCode.INTERNAL_ERROR", "io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.INTERNAL_ERROR", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val InternalError: Int = io.modelcontextprotocol.kotlin.sdk.types.RPCError.ErrorCode.INTERNAL_ERROR } @@ -647,9 +651,10 @@ public sealed interface ErrorCode { @Deprecated( message = "Use `RPCError` instead", replaceWith = ReplaceWith("RPCError", "io.modelcontextprotocol.kotlin.sdk.types.RPCError"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) @Serializable + @Suppress("DEPRECATION_ERROR") public data class Unknown(override val code: Int) : ErrorCode } @@ -659,7 +664,7 @@ public sealed interface ErrorCode { @Deprecated( message = "Use `JSONRPCError` instead", replaceWith = ReplaceWith("JSONRPCError", "io.modelcontextprotocol.kotlin.sdk.types.JSONRPCError"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias JSONRPCError = io.modelcontextprotocol.kotlin.sdk.types.JSONRPCError @@ -669,7 +674,7 @@ public typealias JSONRPCError = io.modelcontextprotocol.kotlin.sdk.types.JSONRPC @Deprecated( message = "Use `NotificationParams` instead", replaceWith = ReplaceWith("NotificationParams", "io.modelcontextprotocol.kotlin.sdk.types.NotificationParams"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias NotificationParams = io.modelcontextprotocol.kotlin.sdk.types.NotificationParams @@ -681,7 +686,7 @@ public typealias NotificationParams = io.modelcontextprotocol.kotlin.sdk.types.N "CancelledNotification", "io.modelcontextprotocol.kotlin.sdk.types.CancelledNotification", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object CancelledNotification { @Deprecated( @@ -690,7 +695,7 @@ public object CancelledNotification { "CancelledNotificationParams", "io.modelcontextprotocol.kotlin.sdk.types.CancelledNotificationParams", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Params( requestId: io.modelcontextprotocol.kotlin.sdk.types.RequestId, @@ -715,7 +720,7 @@ public object CancelledNotification { "CancelledNotification", "io.modelcontextprotocol.kotlin.sdk.types.CancelledNotification", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun CancelledNotification( params: io.modelcontextprotocol.kotlin.sdk.types.CancelledNotificationParams, @@ -730,14 +735,14 @@ public fun CancelledNotification( @Deprecated( message = "Use `Implementation` instead", replaceWith = ReplaceWith("Implementation", "io.modelcontextprotocol.kotlin.sdk.types.Implementation"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias Implementation = io.modelcontextprotocol.kotlin.sdk.types.Implementation @Deprecated( message = "Use `ClientCapabilities` instead", replaceWith = ReplaceWith("ClientCapabilities", "io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object ClientCapabilities { @Deprecated( @@ -746,7 +751,7 @@ public object ClientCapabilities { "ClientCapabilities.Roots", "io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities.Roots", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Roots(listChanged: Boolean? = null): io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities.Roots = io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities.Roots(listChanged) @@ -760,7 +765,7 @@ public object ClientCapabilities { @Deprecated( message = "Use `ClientCapabilities` instead", replaceWith = ReplaceWith("ClientCapabilities", "io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun ClientCapabilities( sampling: JsonObject? = null, @@ -776,7 +781,7 @@ public fun ClientCapabilities( @Deprecated( message = "Use `ClientRequest` instead", replaceWith = ReplaceWith("ClientRequest", "io.modelcontextprotocol.kotlin.sdk.types.ClientRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ClientRequest = io.modelcontextprotocol.kotlin.sdk.types.ClientRequest @@ -786,7 +791,7 @@ public typealias ClientRequest = io.modelcontextprotocol.kotlin.sdk.types.Client @Deprecated( message = "Use `ClientNotification` instead", replaceWith = ReplaceWith("ClientNotification", "io.modelcontextprotocol.kotlin.sdk.types.ClientNotification"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ClientNotification = io.modelcontextprotocol.kotlin.sdk.types.ClientNotification @@ -796,7 +801,7 @@ public typealias ClientNotification = io.modelcontextprotocol.kotlin.sdk.types.C @Deprecated( message = "Use `ClientResult` instead", replaceWith = ReplaceWith("ClientResult", "io.modelcontextprotocol.kotlin.sdk.types.ClientResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ClientResult = io.modelcontextprotocol.kotlin.sdk.types.ClientResult @@ -806,7 +811,7 @@ public typealias ClientResult = io.modelcontextprotocol.kotlin.sdk.types.ClientR @Deprecated( message = "Use `ServerRequest` instead", replaceWith = ReplaceWith("ServerRequest", "io.modelcontextprotocol.kotlin.sdk.types.ServerRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ServerRequest = io.modelcontextprotocol.kotlin.sdk.types.ServerRequest @@ -816,7 +821,7 @@ public typealias ServerRequest = io.modelcontextprotocol.kotlin.sdk.types.Server @Deprecated( message = "Use `ServerNotification` instead", replaceWith = ReplaceWith("ServerNotification", "io.modelcontextprotocol.kotlin.sdk.types.ServerNotification"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ServerNotification = io.modelcontextprotocol.kotlin.sdk.types.ServerNotification @@ -826,7 +831,7 @@ public typealias ServerNotification = io.modelcontextprotocol.kotlin.sdk.types.S @Deprecated( message = "Use `ServerResult` instead", replaceWith = ReplaceWith("ServerResult", "io.modelcontextprotocol.kotlin.sdk.types.ServerResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ServerResult = io.modelcontextprotocol.kotlin.sdk.types.ServerResult @@ -838,8 +843,9 @@ public typealias ServerResult = io.modelcontextprotocol.kotlin.sdk.types.ServerR @Serializable @Deprecated( message = "This class will be removed", - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) +@Suppress("DEPRECATION_ERROR") public data class UnknownMethodRequestOrNotification(val method: Method, val params: NotificationParams? = null) /** @@ -848,14 +854,14 @@ public data class UnknownMethodRequestOrNotification(val method: Method, val par @Deprecated( message = "Use `InitializeRequest` instead", replaceWith = ReplaceWith("InitializeRequest", "io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias InitializeRequest = io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest @Deprecated( message = "Use `ServerCapabilities` instead", replaceWith = ReplaceWith("ServerCapabilities", "io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object ServerCapabilities { @@ -865,7 +871,7 @@ public object ServerCapabilities { "ServerCapabilities.Prompts", "io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Prompts", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Prompts( listChanged: Boolean? = null, @@ -878,7 +884,7 @@ public object ServerCapabilities { "ServerCapabilities.Resources", "io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Resources", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Resources( listChanged: Boolean? = null, @@ -892,7 +898,7 @@ public object ServerCapabilities { "ServerCapabilities.Tools", "io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Tools", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Tools(listChanged: Boolean? = null): io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Tools = io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Tools(listChanged) @@ -910,7 +916,7 @@ public object ServerCapabilities { @Deprecated( message = "Use `ServerCapabilities` instead", replaceWith = ReplaceWith("ServerCapabilities", "io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun ServerCapabilities( tools: io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities.Tools? = null, @@ -935,7 +941,7 @@ public fun ServerCapabilities( @Deprecated( message = "Use `InitializeResult` instead", replaceWith = ReplaceWith("InitializeResult", "io.modelcontextprotocol.kotlin.sdk.types.InitializeResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias InitializeResult = io.modelcontextprotocol.kotlin.sdk.types.InitializeResult @@ -945,7 +951,7 @@ public typealias InitializeResult = io.modelcontextprotocol.kotlin.sdk.types.Ini "InitializedNotification", "io.modelcontextprotocol.kotlin.sdk.types.InitializedNotification", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object InitializedNotification { @Deprecated( @@ -954,8 +960,9 @@ public object InitializedNotification { "BaseNotificationParams", "io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) + @Suppress("DEPRECATION_ERROR") public fun Params( meta: JsonObject = EmptyJsonObject, ): io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams = @@ -971,7 +978,7 @@ public object InitializedNotification { "InitializedNotification", "io.modelcontextprotocol.kotlin.sdk.types.InitializedNotification", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun InitializedNotification( params: io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams? = null, @@ -987,7 +994,7 @@ public fun InitializedNotification( @Deprecated( message = "Use `PingRequest` instead", replaceWith = ReplaceWith("PingRequest", "io.modelcontextprotocol.kotlin.sdk.types.PingRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias PingRequest = io.modelcontextprotocol.kotlin.sdk.types.PingRequest @@ -997,7 +1004,7 @@ public typealias PingRequest = io.modelcontextprotocol.kotlin.sdk.types.PingRequ @Serializable @Deprecated( message = "This interface will be removed", - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public sealed interface ProgressBase { /** @@ -1027,14 +1034,14 @@ public sealed interface ProgressBase { @Deprecated( message = "Use `Progress` instead", replaceWith = ReplaceWith("Progress", "io.modelcontextprotocol.kotlin.sdk.types.Progress"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias Progress = io.modelcontextprotocol.kotlin.sdk.types.Progress @Deprecated( message = "Use `ProgressNotification` instead", replaceWith = ReplaceWith("ProgressNotification", "io.modelcontextprotocol.kotlin.sdk.types.ProgressNotification"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object ProgressNotification { @Deprecated( @@ -1043,7 +1050,7 @@ public object ProgressNotification { "ProgressNotificationParams", "io.modelcontextprotocol.kotlin.sdk.types.ProgressNotificationParams", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Params( progressToken: io.modelcontextprotocol.kotlin.sdk.types.ProgressToken, @@ -1067,7 +1074,7 @@ public object ProgressNotification { @Deprecated( message = "Use `ProgressNotification` instead", replaceWith = ReplaceWith("ProgressNotification", "io.modelcontextprotocol.kotlin.sdk.types.ProgressNotification"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun ProgressNotification( params: io.modelcontextprotocol.kotlin.sdk.types.ProgressNotificationParams, @@ -1082,7 +1089,7 @@ public fun ProgressNotification( @Deprecated( message = "Use `PaginatedRequest` instead", replaceWith = ReplaceWith("PaginatedRequest", "io.modelcontextprotocol.kotlin.sdk.types.PaginatedRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias PaginatedRequest = io.modelcontextprotocol.kotlin.sdk.types.PaginatedRequest @@ -1092,7 +1099,7 @@ public typealias PaginatedRequest = io.modelcontextprotocol.kotlin.sdk.types.Pag @Deprecated( message = "Use `PaginatedResult` instead", replaceWith = ReplaceWith("PaginatedResult", "io.modelcontextprotocol.kotlin.sdk.types.PaginatedResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias PaginatedResult = io.modelcontextprotocol.kotlin.sdk.types.PaginatedResult @@ -1104,7 +1111,7 @@ public typealias PaginatedResult = io.modelcontextprotocol.kotlin.sdk.types.Pagi @Deprecated( message = "Use `ResourceContents` instead", replaceWith = ReplaceWith("ResourceContents", "io.modelcontextprotocol.kotlin.sdk.types.ResourceContents"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ResourceContents = io.modelcontextprotocol.kotlin.sdk.types.ResourceContents @@ -1116,7 +1123,7 @@ public typealias ResourceContents = io.modelcontextprotocol.kotlin.sdk.types.Res @Deprecated( message = "Use `TextResourceContents` instead", replaceWith = ReplaceWith("TextResourceContents", "io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias TextResourceContents = io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents @@ -1128,7 +1135,7 @@ public typealias TextResourceContents = io.modelcontextprotocol.kotlin.sdk.types @Deprecated( message = "Use `BlobResourceContents` instead", replaceWith = ReplaceWith("BlobResourceContents", "io.modelcontextprotocol.kotlin.sdk.types.BlobResourceContents"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias BlobResourceContents = io.modelcontextprotocol.kotlin.sdk.types.BlobResourceContents @@ -1141,7 +1148,7 @@ public typealias BlobResourceContents = io.modelcontextprotocol.kotlin.sdk.types "UnknownResourceContents", "io.modelcontextprotocol.kotlin.sdk.types.UnknownResourceContents", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias UnknownResourceContents = io.modelcontextprotocol.kotlin.sdk.types.UnknownResourceContents @@ -1151,7 +1158,7 @@ public typealias UnknownResourceContents = io.modelcontextprotocol.kotlin.sdk.ty @Deprecated( message = "Use `Resource` instead", replaceWith = ReplaceWith("Resource", "io.modelcontextprotocol.kotlin.sdk.types.Resource"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias Resource = io.modelcontextprotocol.kotlin.sdk.types.Resource @@ -1161,7 +1168,7 @@ public typealias Resource = io.modelcontextprotocol.kotlin.sdk.types.Resource @Deprecated( message = "Use `ResourceTemplate` instead", replaceWith = ReplaceWith("ResourceTemplate", "io.modelcontextprotocol.kotlin.sdk.types.ResourceTemplate"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ResourceTemplate = io.modelcontextprotocol.kotlin.sdk.types.ResourceTemplate @@ -1171,7 +1178,7 @@ public typealias ResourceTemplate = io.modelcontextprotocol.kotlin.sdk.types.Res @Deprecated( message = "Use `ListResourcesRequest` instead", replaceWith = ReplaceWith("ListResourcesRequest", "io.modelcontextprotocol.kotlin.sdk.types.ListResourcesRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ListResourcesRequest = io.modelcontextprotocol.kotlin.sdk.types.ListResourcesRequest @@ -1181,7 +1188,7 @@ public typealias ListResourcesRequest = io.modelcontextprotocol.kotlin.sdk.types @Deprecated( message = "Use `ListResourcesResult` instead", replaceWith = ReplaceWith("ListResourcesResult", "io.modelcontextprotocol.kotlin.sdk.types.ListResourcesResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ListResourcesResult = io.modelcontextprotocol.kotlin.sdk.types.ListResourcesResult @@ -1194,7 +1201,7 @@ public typealias ListResourcesResult = io.modelcontextprotocol.kotlin.sdk.types. "ListResourceTemplatesRequest", "io.modelcontextprotocol.kotlin.sdk.types.ListResourceTemplatesRequest", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ListResourceTemplatesRequest = io.modelcontextprotocol.kotlin.sdk.types.ListResourceTemplatesRequest @@ -1207,7 +1214,7 @@ public typealias ListResourceTemplatesRequest = io.modelcontextprotocol.kotlin.s "ListResourceTemplatesResult", "io.modelcontextprotocol.kotlin.sdk.types.ListResourceTemplatesResult", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ListResourceTemplatesResult = io.modelcontextprotocol.kotlin.sdk.types.ListResourceTemplatesResult @@ -1217,7 +1224,7 @@ public typealias ListResourceTemplatesResult = io.modelcontextprotocol.kotlin.sd @Deprecated( message = "Use `ReadResourceRequest` instead", replaceWith = ReplaceWith("ReadResourceRequest", "io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ReadResourceRequest = io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest @@ -1227,7 +1234,7 @@ public typealias ReadResourceRequest = io.modelcontextprotocol.kotlin.sdk.types. @Deprecated( message = "Use `ReadResourceResult` instead", replaceWith = ReplaceWith("ReadResourceResult", "io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ReadResourceResult = io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult @@ -1237,7 +1244,7 @@ public typealias ReadResourceResult = io.modelcontextprotocol.kotlin.sdk.types.R "ResourceListChangedNotification", "io.modelcontextprotocol.kotlin.sdk.types.ResourceListChangedNotification", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object ResourceListChangedNotification { @Deprecated( @@ -1246,7 +1253,7 @@ public object ResourceListChangedNotification { "BaseNotificationParams", "io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Params(meta: JsonObject? = null): io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams = io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams(meta) @@ -1263,7 +1270,7 @@ public object ResourceListChangedNotification { "ResourceListChangedNotification", "io.modelcontextprotocol.kotlin.sdk.types.ResourceListChangedNotification", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun ResourceListChangedNotification( params: io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams? = null, @@ -1276,7 +1283,7 @@ public fun ResourceListChangedNotification( @Deprecated( message = "Use `SubscribeRequest` instead", replaceWith = ReplaceWith("SubscribeRequest", "io.modelcontextprotocol.kotlin.sdk.types.SubscribeRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias SubscribeRequest = io.modelcontextprotocol.kotlin.sdk.types.SubscribeRequest @@ -1286,7 +1293,7 @@ public typealias SubscribeRequest = io.modelcontextprotocol.kotlin.sdk.types.Sub @Deprecated( message = "Use `UnsubscribeRequest` instead", replaceWith = ReplaceWith("UnsubscribeRequest", "io.modelcontextprotocol.kotlin.sdk.types.UnsubscribeRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias UnsubscribeRequest = io.modelcontextprotocol.kotlin.sdk.types.UnsubscribeRequest @@ -1296,7 +1303,7 @@ public typealias UnsubscribeRequest = io.modelcontextprotocol.kotlin.sdk.types.U "ResourceUpdatedNotification", "io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotification", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object ResourceUpdatedNotification { @Deprecated( @@ -1305,7 +1312,7 @@ public object ResourceUpdatedNotification { "ResourceUpdatedNotificationParams", "io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotificationParams", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Params( uri: String, @@ -1323,7 +1330,7 @@ public object ResourceUpdatedNotification { "ResourceUpdatedNotification", "io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotification", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun ResourceUpdatedNotification( params: io.modelcontextprotocol.kotlin.sdk.types.ResourceUpdatedNotificationParams, @@ -1338,7 +1345,7 @@ public fun ResourceUpdatedNotification( @Deprecated( message = "Use `PromptArgument` instead", replaceWith = ReplaceWith("PromptArgument", "io.modelcontextprotocol.kotlin.sdk.types.PromptArgument"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias PromptArgument = io.modelcontextprotocol.kotlin.sdk.types.PromptArgument @@ -1348,7 +1355,7 @@ public typealias PromptArgument = io.modelcontextprotocol.kotlin.sdk.types.Promp @Deprecated( message = "Use `Prompt` instead", replaceWith = ReplaceWith("Prompt", "io.modelcontextprotocol.kotlin.sdk.types.Prompt"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias Prompt = io.modelcontextprotocol.kotlin.sdk.types.Prompt @@ -1358,7 +1365,7 @@ public typealias Prompt = io.modelcontextprotocol.kotlin.sdk.types.Prompt @Deprecated( message = "Use `ListPromptsRequest` instead", replaceWith = ReplaceWith("ListPromptsRequest", "io.modelcontextprotocol.kotlin.sdk.types.ListPromptsRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ListPromptsRequest = io.modelcontextprotocol.kotlin.sdk.types.ListPromptsRequest @@ -1368,7 +1375,7 @@ public typealias ListPromptsRequest = io.modelcontextprotocol.kotlin.sdk.types.L @Deprecated( message = "Use `ListPromptsResult` instead", replaceWith = ReplaceWith("ListPromptsResult", "io.modelcontextprotocol.kotlin.sdk.types.ListPromptsResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ListPromptsResult = io.modelcontextprotocol.kotlin.sdk.types.ListPromptsResult @@ -1378,7 +1385,7 @@ public typealias ListPromptsResult = io.modelcontextprotocol.kotlin.sdk.types.Li @Deprecated( message = "Use `GetPromptRequest` instead", replaceWith = ReplaceWith("GetPromptRequest", "io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias GetPromptRequest = io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest @@ -1388,7 +1395,7 @@ public typealias GetPromptRequest = io.modelcontextprotocol.kotlin.sdk.types.Get @Deprecated( message = "Use `ContentBlock` instead", replaceWith = ReplaceWith("ContentBlock"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias PromptMessageContent = io.modelcontextprotocol.kotlin.sdk.types.ContentBlock @@ -1398,7 +1405,7 @@ public typealias PromptMessageContent = io.modelcontextprotocol.kotlin.sdk.types @Deprecated( message = "Use `MediaContent` instead", replaceWith = ReplaceWith("MediaContent"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias PromptMessageContentMultimodal = io.modelcontextprotocol.kotlin.sdk.types.MediaContent @@ -1408,7 +1415,7 @@ public typealias PromptMessageContentMultimodal = io.modelcontextprotocol.kotlin @Deprecated( message = "Use `TextContent` instead", replaceWith = ReplaceWith("TextContent", "io.modelcontextprotocol.kotlin.sdk.types.TextContent"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias TextContent = io.modelcontextprotocol.kotlin.sdk.types.TextContent @@ -1418,7 +1425,7 @@ public typealias TextContent = io.modelcontextprotocol.kotlin.sdk.types.TextCont @Deprecated( message = "Use `ImageContent` instead", replaceWith = ReplaceWith("ImageContent", "io.modelcontextprotocol.kotlin.sdk.types.ImageContent"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ImageContent = io.modelcontextprotocol.kotlin.sdk.types.ImageContent @@ -1428,7 +1435,7 @@ public typealias ImageContent = io.modelcontextprotocol.kotlin.sdk.types.ImageCo @Deprecated( message = "Use `AudioContent` instead", replaceWith = ReplaceWith("AudioContent", "io.modelcontextprotocol.kotlin.sdk.types.AudioContent"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias AudioContent = io.modelcontextprotocol.kotlin.sdk.types.AudioContent @@ -1439,7 +1446,7 @@ public typealias AudioContent = io.modelcontextprotocol.kotlin.sdk.types.AudioCo @Deprecated( message = "This class will be removed.", replaceWith = ReplaceWith("MediaContent"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public data class UnknownContent(val type: String) @@ -1449,7 +1456,7 @@ public data class UnknownContent(val type: String) @Deprecated( message = "Use `EmbeddedResource` instead", replaceWith = ReplaceWith("EmbeddedResource", "io.modelcontextprotocol.kotlin.sdk.types.EmbeddedResource"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias EmbeddedResource = io.modelcontextprotocol.kotlin.sdk.types.EmbeddedResource @@ -1459,20 +1466,20 @@ public typealias EmbeddedResource = io.modelcontextprotocol.kotlin.sdk.types.Emb @Deprecated( message = "Use `Role` instead", replaceWith = ReplaceWith("Role", "io.modelcontextprotocol.kotlin.sdk.types.Role"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object Role { @Deprecated( message = "Use `Role.User` instead", replaceWith = ReplaceWith("Role.User", "io.modelcontextprotocol.kotlin.sdk.types.Role"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val user: io.modelcontextprotocol.kotlin.sdk.types.Role = io.modelcontextprotocol.kotlin.sdk.types.Role.User @Deprecated( message = "Use `Role.Assistant` instead", replaceWith = ReplaceWith("Role.Assistant", "io.modelcontextprotocol.kotlin.sdk.types.Role"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val assistant: io.modelcontextprotocol.kotlin.sdk.types.Role = io.modelcontextprotocol.kotlin.sdk.types.Role.Assistant @@ -1485,7 +1492,7 @@ public object Role { @Deprecated( message = "Use `Annotations` instead", replaceWith = ReplaceWith("Annotations", "io.modelcontextprotocol.kotlin.sdk.types.Annotations"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias Annotations = io.modelcontextprotocol.kotlin.sdk.types.Annotations @@ -1495,7 +1502,7 @@ public typealias Annotations = io.modelcontextprotocol.kotlin.sdk.types.Annotati @Deprecated( message = "Use `PromptMessage` instead", replaceWith = ReplaceWith("PromptMessage", "io.modelcontextprotocol.kotlin.sdk.types.PromptMessage"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias PromptMessage = io.modelcontextprotocol.kotlin.sdk.types.PromptMessage @@ -1505,7 +1512,7 @@ public typealias PromptMessage = io.modelcontextprotocol.kotlin.sdk.types.Prompt @Deprecated( message = "Use `GetPromptResult` instead", replaceWith = ReplaceWith("GetPromptResult", "io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias GetPromptResult = io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult @@ -1515,7 +1522,7 @@ public typealias GetPromptResult = io.modelcontextprotocol.kotlin.sdk.types.GetP "PromptListChangedNotification", "io.modelcontextprotocol.kotlin.sdk.types.PromptListChangedNotification", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object PromptListChangedNotification { @Deprecated( @@ -1524,7 +1531,7 @@ public object PromptListChangedNotification { "BaseNotificationParams", "io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Params(meta: JsonObject? = null): io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams = io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams(meta) @@ -1540,7 +1547,7 @@ public object PromptListChangedNotification { "PromptListChangedNotification", "io.modelcontextprotocol.kotlin.sdk.types.PromptListChangedNotification", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun PromptListChangedNotification( params: io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams, @@ -1562,20 +1569,20 @@ public fun PromptListChangedNotification( @Deprecated( message = "Use `ToolAnnotations` instead", replaceWith = ReplaceWith("ToolAnnotations", "io.modelcontextprotocol.kotlin.sdk.types.ToolAnnotations"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ToolAnnotations = io.modelcontextprotocol.kotlin.sdk.types.ToolAnnotations @Deprecated( message = "Use `Tool` instead", replaceWith = ReplaceWith("Tool", "io.modelcontextprotocol.kotlin.sdk.types.Tool"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object Tool { @Deprecated( message = "Use `ToolSchema` instead", replaceWith = ReplaceWith("ToolSchema", "io.modelcontextprotocol.kotlin.sdk.types.ToolSchema"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Input( properties: JsonObject? = null, @@ -1586,7 +1593,7 @@ public object Tool { @Deprecated( message = "Use `ToolSchema` instead", replaceWith = ReplaceWith("ToolSchema", "io.modelcontextprotocol.kotlin.sdk.types.ToolSchema"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Output( properties: JsonObject? = null, @@ -1601,7 +1608,7 @@ public object Tool { @Deprecated( message = "Use `Tool` instead", replaceWith = ReplaceWith("Tool", "io.modelcontextprotocol.kotlin.sdk.types.Tool"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Tool( name: String, @@ -1629,7 +1636,7 @@ public fun Tool( @Deprecated( message = "Use `ListToolsRequest` instead", replaceWith = ReplaceWith("ListToolsRequest", "io.modelcontextprotocol.kotlin.sdk.types.ListToolsRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ListToolsRequest = io.modelcontextprotocol.kotlin.sdk.types.ListToolsRequest @@ -1639,7 +1646,7 @@ public typealias ListToolsRequest = io.modelcontextprotocol.kotlin.sdk.types.Lis @Deprecated( message = "Use `ListToolsResult` instead", replaceWith = ReplaceWith("ListToolsResult", "io.modelcontextprotocol.kotlin.sdk.types.ListToolsResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ListToolsResult = io.modelcontextprotocol.kotlin.sdk.types.ListToolsResult @@ -1650,8 +1657,9 @@ public typealias ListToolsResult = io.modelcontextprotocol.kotlin.sdk.types.List @Deprecated( message = "This interface will be removed.", replaceWith = ReplaceWith("CallToolResult", "io.modelcontextprotocol.kotlin.sdk.types.CallToolResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) +@Suppress("DEPRECATION_ERROR") public sealed interface CallToolResultBase { public val content: List public val structuredContent: JsonObject? @@ -1661,7 +1669,7 @@ public sealed interface CallToolResultBase { @Deprecated( message = "Use `CallToolResult` instead", replaceWith = ReplaceWith("CallToolResult", "io.modelcontextprotocol.kotlin.sdk.types.CallToolResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object CallToolResult @@ -1671,7 +1679,7 @@ public object CallToolResult @Deprecated( message = "Use `CallToolResult` instead", replaceWith = ReplaceWith("CallToolResult", "io.modelcontextprotocol.kotlin.sdk.types.CallToolResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun CallToolResult( content: List, @@ -1688,8 +1696,9 @@ public fun CallToolResult( @Deprecated( message = "This class will be removed.", replaceWith = ReplaceWith("ToolCallResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) +@Suppress("DEPRECATION_ERROR") public class CompatibilityCallToolResult( public val content: List, public val structuredContent: JsonObject? = null, @@ -1704,7 +1713,7 @@ public class CompatibilityCallToolResult( @Deprecated( message = "Use `CallToolRequest` instead", replaceWith = ReplaceWith("CallToolRequest", "io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias CallToolRequest = io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest @@ -1714,7 +1723,7 @@ public typealias CallToolRequest = io.modelcontextprotocol.kotlin.sdk.types.Call "ToolListChangedNotification", "io.modelcontextprotocol.kotlin.sdk.types.ToolListChangedNotification", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object ToolListChangedNotification { @Deprecated( @@ -1723,7 +1732,7 @@ public object ToolListChangedNotification { "BaseNotificationParams", "io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Params(meta: JsonObject? = null): io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams = io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams(meta) @@ -1739,7 +1748,7 @@ public object ToolListChangedNotification { "ToolListChangedNotification", "io.modelcontextprotocol.kotlin.sdk.types.ToolListChangedNotification", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun ToolListChangedNotification( params: io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams, @@ -1754,13 +1763,13 @@ public fun ToolListChangedNotification( @Deprecated( message = "Use `LoggingLevel` instead", replaceWith = ReplaceWith("LoggingLevel", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object LoggingLevel { @Deprecated( message = "Use `LoggingLevel.Debug` instead", replaceWith = ReplaceWith("LoggingLevel.Debug", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val debug: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel = io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel.Debug @@ -1768,7 +1777,7 @@ public object LoggingLevel { @Deprecated( message = "Use `LoggingLevel.Info` instead", replaceWith = ReplaceWith("LoggingLevel.Info", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val info: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel = io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel.Info @@ -1776,7 +1785,7 @@ public object LoggingLevel { @Deprecated( message = "Use `LoggingLevel.Notice` instead", replaceWith = ReplaceWith("LoggingLevel.Notice", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val notice: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel = io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel.Notice @@ -1784,7 +1793,7 @@ public object LoggingLevel { @Deprecated( message = "Use `LoggingLevel.Warning` instead", replaceWith = ReplaceWith("LoggingLevel.Warning", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val warning: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel = io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel.Warning @@ -1792,7 +1801,7 @@ public object LoggingLevel { @Deprecated( message = "Use `LoggingLevel.Error` instead", replaceWith = ReplaceWith("LoggingLevel.Error", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val error: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel = io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel.Error @@ -1800,7 +1809,7 @@ public object LoggingLevel { @Deprecated( message = "Use `LoggingLevel.Critical` instead", replaceWith = ReplaceWith("LoggingLevel.Critical", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val critical: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel = io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel.Critical @@ -1808,7 +1817,7 @@ public object LoggingLevel { @Deprecated( message = "Use `LoggingLevel.Alert` instead", replaceWith = ReplaceWith("LoggingLevel.Alert", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val alert: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel = io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel.Alert @@ -1816,7 +1825,7 @@ public object LoggingLevel { @Deprecated( message = "Use `LoggingLevel.Emergency` instead", replaceWith = ReplaceWith("LoggingLevel.Emergency", "io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val emergency: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel = io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel.Emergency @@ -1828,7 +1837,7 @@ public object LoggingLevel { "LoggingMessageNotification", "io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotification", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object LoggingMessageNotification { @Deprecated( @@ -1837,7 +1846,7 @@ public object LoggingMessageNotification { "LoggingMessageNotificationParams", "io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotificationParams", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Params( level: io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel, @@ -1853,7 +1862,7 @@ public object LoggingMessageNotification { "SetLevelRequest", "io.modelcontextprotocol.kotlin.sdk.types.SetLevelRequest", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun SetLevelRequest( params: io.modelcontextprotocol.kotlin.sdk.types.SetLevelRequestParams, @@ -1872,7 +1881,7 @@ public object LoggingMessageNotification { "LoggingMessageNotification", "io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotification", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun LoggingMessageNotification( params: io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotificationParams, @@ -1887,7 +1896,7 @@ public fun LoggingMessageNotification( @Deprecated( message = "Use `ModelHint` instead", replaceWith = ReplaceWith("ModelHint", "io.modelcontextprotocol.kotlin.sdk.types.ModelHint"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ModelHint = io.modelcontextprotocol.kotlin.sdk.types.ModelHint @@ -1897,7 +1906,7 @@ public typealias ModelHint = io.modelcontextprotocol.kotlin.sdk.types.ModelHint @Deprecated( message = "Use `ModelPreferences` instead", replaceWith = ReplaceWith("ModelPreferences", "io.modelcontextprotocol.kotlin.sdk.types.ModelPreferences"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ModelPreferences = io.modelcontextprotocol.kotlin.sdk.types.ModelPreferences @@ -1907,14 +1916,14 @@ public typealias ModelPreferences = io.modelcontextprotocol.kotlin.sdk.types.Mod @Deprecated( message = "Use `SamplingMessage` instead", replaceWith = ReplaceWith("SamplingMessage", "io.modelcontextprotocol.kotlin.sdk.types.SamplingMessage"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias SamplingMessage = io.modelcontextprotocol.kotlin.sdk.types.SamplingMessage @Deprecated( message = "Use `CreateMessageRequest` instead", replaceWith = ReplaceWith("CreateMessageRequest", "io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object CreateMessageRequest { public val IncludeContext: io.modelcontextprotocol.kotlin.sdk.types.IncludeContext.Companion = @@ -1930,7 +1939,7 @@ public object CreateMessageRequest { @Deprecated( message = "Use `CreateMessageRequest` instead", replaceWith = ReplaceWith("CreateMessageRequest", "io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun CreateMessageRequest( params: io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequestParams, @@ -1940,7 +1949,7 @@ public fun CreateMessageRequest( @Deprecated( message = "Use `StopReason` instead", replaceWith = ReplaceWith("StopReason", "io.modelcontextprotocol.kotlin.sdk.types.StopReason"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias StopReason = io.modelcontextprotocol.kotlin.sdk.types.StopReason @@ -1952,7 +1961,7 @@ public typealias StopReason = io.modelcontextprotocol.kotlin.sdk.types.StopReaso @Deprecated( message = "Use `CreateMessageResult` instead", replaceWith = ReplaceWith("CreateMessageResult", "io.modelcontextprotocol.kotlin.sdk.types.CreateMessageResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias CreateMessageResult = io.modelcontextprotocol.kotlin.sdk.types.CreateMessageResult @@ -1960,7 +1969,7 @@ public typealias CreateMessageResult = io.modelcontextprotocol.kotlin.sdk.types. @Deprecated( message = "Use `Reference` instead", replaceWith = ReplaceWith("Reference", "io.modelcontextprotocol.kotlin.sdk.types.Reference"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias Reference = io.modelcontextprotocol.kotlin.sdk.types.Reference @@ -1973,7 +1982,7 @@ public typealias Reference = io.modelcontextprotocol.kotlin.sdk.types.Reference "ResourceTemplateReference", "io.modelcontextprotocol.kotlin.sdk.types.ResourceTemplateReference", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ResourceTemplateReference = io.modelcontextprotocol.kotlin.sdk.types.ResourceTemplateReference @@ -1983,7 +1992,7 @@ public typealias ResourceTemplateReference = io.modelcontextprotocol.kotlin.sdk. @Deprecated( message = "Use `PromptReference` instead", replaceWith = ReplaceWith("PromptReference", "io.modelcontextprotocol.kotlin.sdk.types.PromptReference"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias PromptReference = io.modelcontextprotocol.kotlin.sdk.types.PromptReference @@ -1993,14 +2002,14 @@ public typealias PromptReference = io.modelcontextprotocol.kotlin.sdk.types.Prom @Serializable @Deprecated( message = "This class will be removed.", - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public data class UnknownReference(val type: String) @Deprecated( message = "Use `CompleteRequest` instead", replaceWith = ReplaceWith("CompleteRequest", "io.modelcontextprotocol.kotlin.sdk.types.CompleteRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object CompleteRequest { @Deprecated( @@ -2009,7 +2018,7 @@ public object CompleteRequest { "CompleteRequestParams.Argument", "io.modelcontextprotocol.kotlin.sdk.types.CompleteRequestParams.Argument", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Argument( name: String, @@ -2024,7 +2033,7 @@ public object CompleteRequest { @Deprecated( message = "Use `CompleteRequest` instead", replaceWith = ReplaceWith("CompleteRequest", "io.modelcontextprotocol.kotlin.sdk.types.CompleteRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun CompleteRequest( params: io.modelcontextprotocol.kotlin.sdk.types.CompleteRequestParams, @@ -2034,7 +2043,7 @@ public fun CompleteRequest( @Deprecated( message = "Use `CompleteResult` instead", replaceWith = ReplaceWith("CompleteResult", "io.modelcontextprotocol.kotlin.sdk.types.CompleteResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object CompleteResult { @Deprecated( @@ -2043,7 +2052,7 @@ public object CompleteResult { "CompleteResult.Completion", "io.modelcontextprotocol.kotlin.sdk.types.CompleteResult.Completion", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Completion( values: List, @@ -2059,7 +2068,7 @@ public object CompleteResult { @Deprecated( message = "Use `CompleteResult` instead", replaceWith = ReplaceWith("CompleteResult", "io.modelcontextprotocol.kotlin.sdk.types.CompleteResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun CompleteResult( completion: io.modelcontextprotocol.kotlin.sdk.types.CompleteResult.Completion, @@ -2075,7 +2084,7 @@ public fun CompleteResult( @Deprecated( message = "Use `Root` instead", replaceWith = ReplaceWith("Root", "io.modelcontextprotocol.kotlin.sdk.types.Root"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias Root = io.modelcontextprotocol.kotlin.sdk.types.Root @@ -2085,7 +2094,7 @@ public typealias Root = io.modelcontextprotocol.kotlin.sdk.types.Root @Deprecated( message = "Use `ListRootsRequest` instead", replaceWith = ReplaceWith("ListRootsRequest", "io.modelcontextprotocol.kotlin.sdk.types.ListRootsRequest"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ListRootsRequest = io.modelcontextprotocol.kotlin.sdk.types.ListRootsRequest @@ -2095,7 +2104,7 @@ public typealias ListRootsRequest = io.modelcontextprotocol.kotlin.sdk.types.Lis @Deprecated( message = "Use `ListRootsResult` instead", replaceWith = ReplaceWith("ListRootsResult", "io.modelcontextprotocol.kotlin.sdk.types.ListRootsResult"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias ListRootsResult = io.modelcontextprotocol.kotlin.sdk.types.ListRootsResult @@ -2105,7 +2114,7 @@ public typealias ListRootsResult = io.modelcontextprotocol.kotlin.sdk.types.List "RootsListChangedNotification", "io.modelcontextprotocol.kotlin.sdk.types.RootsListChangedNotification", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object RootsListChangedNotification { @Deprecated( @@ -2114,7 +2123,7 @@ public object RootsListChangedNotification { "BaseNotificationParams", "io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun Params(meta: JsonObject? = null): io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams = io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams(meta) @@ -2129,7 +2138,7 @@ public object RootsListChangedNotification { "RootsListChangedNotification", "io.modelcontextprotocol.kotlin.sdk.types.RootsListChangedNotification", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun RootsListChangedNotification( params: io.modelcontextprotocol.kotlin.sdk.types.BaseNotificationParams? = null, @@ -2142,7 +2151,7 @@ public fun RootsListChangedNotification( "ElicitRequest", "io.modelcontextprotocol.kotlin.sdk.types.ElicitRequest", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object CreateElicitationRequest { public fun RequestedSchema( @@ -2161,7 +2170,7 @@ public object CreateElicitationRequest { "ElicitRequest", "io.modelcontextprotocol.kotlin.sdk.types.ElicitRequest", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun CreateElicitationRequest( params: io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestParams, @@ -2174,7 +2183,7 @@ public fun CreateElicitationRequest( "ElicitResult", "io.modelcontextprotocol.kotlin.sdk.types.ElicitResult", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object CreateElicitationResult { @Deprecated( @@ -2183,7 +2192,7 @@ public object CreateElicitationResult { "ElicitResult.Action", "io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public object Action { @Deprecated( @@ -2192,7 +2201,7 @@ public object CreateElicitationResult { "ElicitResult.Action.Accept", "io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val accept: io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action = io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action.Accept @@ -2203,7 +2212,7 @@ public object CreateElicitationResult { "ElicitResult.Action.Decline", "io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val decline: io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action = io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action.Decline @@ -2214,7 +2223,7 @@ public object CreateElicitationResult { "ElicitResult.Action.Cancel", "io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public val cancel: io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action = io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action.Cancel @@ -2230,7 +2239,7 @@ public object CreateElicitationResult { "ElicitResult", "io.modelcontextprotocol.kotlin.sdk.types.ElicitResult", ), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public fun CreateElicitationResult( action: io.modelcontextprotocol.kotlin.sdk.types.ElicitResult.Action, @@ -2249,6 +2258,6 @@ public fun CreateElicitationResult( @Deprecated( message = "Use `McpException` instead", replaceWith = ReplaceWith("McpException"), - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, ) public typealias McpError = io.modelcontextprotocol.kotlin.sdk.types.McpException diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.util.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.util.kt index 305efe7..3e0abe1 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.util.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types.util.kt @@ -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, diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt index e22dab7..5e39d1f 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/common.kt @@ -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 diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.kt index 59f558c..5ca41c6 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.kt @@ -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, diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.kt index 68a892f..08a7a9f 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.kt @@ -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?, diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.kt index ba9533e..8782db1 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.kt @@ -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?, diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.kt index cc08739..9e7dbae 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.kt @@ -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?, diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt index 6cba208..767e724 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt @@ -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?, diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaAudioContentSerializationTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaAudioContentSerializationTest.kt deleted file mode 100644 index 738539f..0000000 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaAudioContentSerializationTest.kt +++ /dev/null @@ -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(audioContentJson) - assertEquals(expected = audioContent, actual = content) - } -} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaCallToolResultUtilsTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaCallToolResultUtilsTest.kt deleted file mode 100644 index b7bac6d..0000000 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaCallToolResultUtilsTest.kt +++ /dev/null @@ -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) - } -} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaRequestSerializerTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaRequestSerializerTest.kt deleted file mode 100644 index 2360b5b..0000000 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaRequestSerializerTest.kt +++ /dev/null @@ -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(json) - - assertIs(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(json) - - assertIs(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(json) - - assertIs(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(json) - - assertIs(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(json) - - assertIs(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(json) - - assertIs(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(json) - - assertIs(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(json) - - assertIs(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(json) - - assertIs(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(json) - - assertIs(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(json) - - assertIs(decoded) - assertEquals(1, decoded.contents.size) - assertIs(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(json) - - assertIs(decoded) - assertEquals(1, decoded.content.size) - assertIs(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(json) - - assertIs(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 { - McpJson.decodeFromString(json) - } - } - - @Test - fun `should handle empty JSON object`() { - val json = """{}""" - - val decoded = McpJson.decodeFromString(json) - - assertIs(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(json) - - assertIs(decoded) - assertEquals("test-model", decoded.model) - } -} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaToolSerializationTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaToolSerializationTest.kt deleted file mode 100644 index 7e96dcf..0000000 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaToolSerializationTest.kt +++ /dev/null @@ -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(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(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(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(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(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 -} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaTypesTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaTypesTest.kt deleted file mode 100644 index f8ad0be..0000000 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/OldSchemaTypesTest.kt +++ /dev/null @@ -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>(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(resourceRef) - val decoded = McpJson.decodeFromString(json) - - assertIs(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(promptRef) - val decoded = McpJson.decodeFromString(json) - - assertIs(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(textContent) - val decoded = McpJson.decodeFromString(json) - - assertIs(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(imageContent) - val decoded = McpJson.decodeFromString(json) - - assertIs(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(audioContent) - val decoded = McpJson.decodeFromString(json) - - assertIs(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(embeddedResource) - val decoded = McpJson.decodeFromString(json) - - assertIs(decoded) - assertEquals(ContentTypes.EMBEDDED_RESOURCE, decoded.type) - assertIs(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(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(json) - - assertEquals(Role.assistant, decoded.role) - assertIs(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(json) - - assertEquals(2, decoded.content.size) - assertIs(decoded.content[0]) - assertIs(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(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(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(json) - - assertEquals("completion/complete", decoded.method.value) - assertIs(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(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(json) - - assertEquals(LATEST_PROTOCOL_VERSION, decoded.protocolVersion) - assertEquals(serverInfo, decoded.serverInfo) - assertEquals(null, decoded.instructions) - } -} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/models/OldSchemaProgressNotificationsTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/models/OldSchemaProgressNotificationsTest.kt deleted file mode 100644 index 29bc12d..0000000 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/models/OldSchemaProgressNotificationsTest.kt +++ /dev/null @@ -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(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(json) - result shouldBe ProgressNotification( - params = ProgressNotification.Params( - progressToken = RequestId.NumberId(100500), - progress = 50.0, - message = "Reticulating splines...", - total = 100.0, - ), - ) - } -} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaReadBufferTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaReadBufferTest.kt deleted file mode 100644 index 0311c13..0000000 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/OldSchemaReadBufferTest.kt +++ /dev/null @@ -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) - } -} diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt index 1b33f39..bb436bf 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt @@ -22,9 +22,6 @@ import kotlin.uuid.Uuid internal const val SESSION_ID_PARAM = "sessionId" -@Deprecated("Use SseServerTransport instead", ReplaceWith("SseServerTransport"), DeprecationLevel.ERROR) -public typealias SSEServerTransport = SseServerTransport - /** * Server transport for SSE: this will send messages over an SSE connection and receive messages from HTTP POST requests. * diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt index 2f80bf8..1b035b4 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt @@ -173,7 +173,7 @@ public open class Server( @Deprecated( "Use createSession(transport) instead.", ReplaceWith("createSession(transport)"), - DeprecationLevel.WARNING, + DeprecationLevel.ERROR, ) public suspend fun connect(transport: Transport): ServerSession = createSession(transport) @@ -266,7 +266,7 @@ public open class Server( @Deprecated( "Initialization moved to ServerSession, use ServerSession.onInitialized instead.", ReplaceWith("ServerSession.onInitialized"), - DeprecationLevel.WARNING, + DeprecationLevel.ERROR, ) public fun onInitialized(block: () -> Unit) { val old = _onInitialized diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaStdioServerTransportTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaStdioServerTransportTest.kt deleted file mode 100644 index 32e25ba..0000000 --- a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/OldSchemaStdioServerTransportTest.kt +++ /dev/null @@ -1,140 +0,0 @@ -package io.modelcontextprotocol.kotlin.sdk.server - -import io.modelcontextprotocol.kotlin.sdk.InitializedNotification -import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage -import io.modelcontextprotocol.kotlin.sdk.PingRequest -import io.modelcontextprotocol.kotlin.sdk.shared.ReadBuffer -import io.modelcontextprotocol.kotlin.sdk.shared.serializeMessage -import io.modelcontextprotocol.kotlin.sdk.toJSON -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.runTest -import kotlinx.io.Sink -import kotlinx.io.Source -import kotlinx.io.asSink -import kotlinx.io.asSource -import kotlinx.io.buffered -import java.io.ByteArrayOutputStream -import java.io.PipedInputStream -import java.io.PipedOutputStream -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -class OldSchemaStdioServerTransportTest { - private lateinit var input: PipedInputStream - private lateinit var inputWriter: PipedOutputStream - private lateinit var outputBuffer: ReadBuffer - private lateinit var output: ByteArrayOutputStream - - // We'll store the wrapped streams that meet the constructor requirements - private lateinit var bufferedInput: Source - private lateinit var printOutput: Sink - - @BeforeTest - fun setUp() { - // Simulate an input stream that we can push data into using inputWriter. - input = PipedInputStream() - inputWriter = PipedOutputStream(input) - - outputBuffer = ReadBuffer() - - // A custom ByteArrayOutputStream that appends all written data into outputBuffer - output = object : ByteArrayOutputStream() { - override fun write(b: ByteArray, off: Int, len: Int) { - super.write(b, off, len) - outputBuffer.append(b.copyOfRange(off, off + len)) - } - } - - bufferedInput = input.asSource().buffered() - - printOutput = output.asSink().buffered() - } - - @Test - fun `should start then close cleanly`() { - runBlocking { - val server = StdioServerTransport(bufferedInput, printOutput) - server.onError { error -> - throw error - } - - var didClose = false - server.onClose { - didClose = true - } - - server.start() - assertFalse(didClose, "Should not have closed yet") - - server.close() - assertTrue(didClose, "Should have closed after calling close()") - } - } - - @Test - fun `should not read until started`() = runTest { - val server = StdioServerTransport(bufferedInput, printOutput) - server.onError { error -> - throw error - } - - var didRead = false - val readMessage = CompletableDeferred() - - server.onMessage { message -> - didRead = true - readMessage.complete(message) - } - - val message = PingRequest().toJSON() - - // Push a message before the server started - val serialized = serializeMessage(message) - inputWriter.write(serialized) - inputWriter.flush() - - assertFalse(didRead, "Should not have read message before start") - - server.start() - val received = readMessage.await() - assertEquals(message, received) - } - - @Test - fun `should read multiple messages`() = runTest { - val server = StdioServerTransport(bufferedInput, printOutput) - server.onError { error -> - throw error - } - - val messages = listOf( - PingRequest().toJSON(), - InitializedNotification().toJSON(), - ) - - val readMessages = mutableListOf() - val finished = CompletableDeferred() - - server.onMessage { message -> - readMessages.add(message) - if (message == messages[1]) { - finished.complete(Unit) - } - } - - // Push both messages before starting the server - for (m in messages) { - inputWriter.write(serializeMessage(m)) - } - inputWriter.flush() - - server.start() - finished.await() - - assertEquals(messages, readMessages) - } -}