Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
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.launch
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.BeforeEach

abstract class AbstractServerFeaturesTest {

protected lateinit var server: Server
protected lateinit var client: Client

abstract fun getServerCapabilities(): 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) }
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
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 ServerInstructionsTest {

@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)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package io.modelcontextprotocol.kotlin.sdk.server

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.PromptListChangedNotification
import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities
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 ServerPromptsTest : AbstractServerFeaturesTest() {

override fun getServerCapabilities(): ServerCapabilities = ServerCapabilities(
prompts = ServerCapabilities.Prompts(false),
)

@Test
fun `removePrompt should remove a prompt`() = runTest {
// Add a prompt
val testPrompt = Prompt("test-prompt", "Test Prompt", null)
server.addPrompt(testPrompt) {
GetPromptResult(
description = "Test prompt description",
messages = listOf(),
)
}

// Remove the prompt
val result = server.removePrompt(testPrompt.name)

// Verify the prompt was removed
assertTrue(result, "Prompt should be removed successfully")
}

@Test
fun `removePrompts 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(),
)
}

// 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")
}

@Test
fun `removePrompt should return false when prompt does not exist`() = runTest {
// Track notifications
var promptListChangedNotificationReceived = false
client.setNotificationHandler<PromptListChangedNotification>(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")
}

@Test
fun `removePrompt should throw when prompts capability is not supported`() = runTest {
// Create server without prompts capability
val serverOptions = ServerOptions(
capabilities = ServerCapabilities(),
)
val server = Server(
Implementation(name = "test server", version = "1.0"),
serverOptions,
)

// Verify that removing a prompt throws an exception
val exception = assertThrows<IllegalStateException> {
server.removePrompt("test-prompt")
}
assertEquals("Server does not support prompts capability.", exception.message)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
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.ResourceListChangedNotification
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 ServerResourcesTest : AbstractServerFeaturesTest() {

override fun getServerCapabilities(): 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<ResourceListChangedNotification>(
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<IllegalStateException> {
server.removeResource("test://resource")
}
assertEquals("Server does not support resources capability.", exception.message)
}
}
Loading
Loading