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
2 changes: 1 addition & 1 deletion samples/kotlin-mcp-client/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
anthropic = "2.18.0"
kotlin = "2.2.21"
ktor = "3.2.3"
mcp-kotlin = "0.9.0"
mcp-kotlin = "0.10.0"
shadow = "9.4.1"
slf4j = "2.0.17"

Expand Down
517 changes: 286 additions & 231 deletions samples/notebooks/McpClient.ipynb

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions samples/simple-streamable-server/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ dependencies {
implementation(libs.ktor.server.sse)
implementation(libs.ktor.serialization.kotlinx.json)
implementation(libs.slf4j.simple)

testImplementation(libs.mcp.kotlin.client)
testImplementation(libs.ktor.client.cio)
testImplementation(libs.junit.jupiter)
testImplementation(kotlin("test"))
}

tasks.test {
useJUnitPlatform()
}

kotlin {
Expand Down
6 changes: 5 additions & 1 deletion samples/simple-streamable-server/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
[versions]
kotlin = "2.2.21"
ktor = "3.2.3"
mcp-kotlin = "0.9.0"
junit = "6.0.3"
mcp-kotlin = "0.10.0"
slf4j = "2.0.17"
shadow = "9.4.1"

Expand All @@ -13,7 +14,10 @@ ktor-server-content-negotiation = { group = "io.ktor", name = "ktor-server-conte
ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json" }
ktor-server-auth = { group = "io.ktor", name = "ktor-server-auth" }
ktor-server-sse = { group = "io.ktor", name = "ktor-server-sse" }
mcp-kotlin-client = { group = "io.modelcontextprotocol", name = "kotlin-sdk-client", version.ref = "mcp-kotlin" }
mcp-kotlin-server = { group = "io.modelcontextprotocol", name = "kotlin-sdk-server", version.ref = "mcp-kotlin" }
ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio" }
junit-jupiter = { group = "org.junit.jupiter", name = "junit-jupiter", version.ref = "junit" }
slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" }

[plugins]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package io.modelcontextprotocol.sample.server

import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
import io.ktor.client.plugins.sse.SSE
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty
import io.modelcontextprotocol.kotlin.sdk.client.Client
import io.modelcontextprotocol.kotlin.sdk.client.mcpStreamableHttp
import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest
import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequestParams
import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest
import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequestParams
import io.modelcontextprotocol.kotlin.sdk.types.Role
import io.modelcontextprotocol.kotlin.sdk.types.TextContent
import io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlin.test.assertNotNull

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class ServerIntegrationTest {

private val server = embeddedServer(Netty, host = "127.0.0.1", port = 0) {
configureServer()
}

private val httpClient = HttpClient(CIO) {
install(SSE)
}

private lateinit var mcpClient: Client

@BeforeAll
suspend fun beforeAll() {
server.start(wait = false)
val port = server.engine.resolvedConnectors().single().port

mcpClient = httpClient.mcpStreamableHttp(
url = "http://127.0.0.1:$port/mcp",
)
}

@AfterAll
suspend fun afterAll() {
mcpClient.close()
httpClient.close()
server.stop(500, 1000)
}

@Test
fun `should negotiate server capabilities`() {
val capabilities = mcpClient.serverCapabilities
assertNotNull(capabilities)
assertNotNull(capabilities.tools)
assertNotNull(capabilities.prompts)
assertNotNull(capabilities.resources)
assertNotNull(capabilities.logging)
}

@Test
fun `should report server version`() {
val version = mcpClient.serverVersion
assertNotNull(version)
assertEquals("simple-streamable-http-server", version.name)
assertEquals("1.0.0", version.version)
}

@Test
suspend fun `should list tools`() {
val result = mcpClient.listTools()
val toolNames = result.tools.map { it.name }.sorted()
assertEquals(listOf("greet", "multi-greet"), toolNames)
}

@Test
suspend fun `should call greet tool`() {
val result = mcpClient.callTool(name = "greet", arguments = mapOf("name" to "Alice"))
val content = result.content.single()
assertIs<TextContent>(content)
assertEquals("Hello, Alice!", content.text)
}

@Test
suspend fun `should call greet tool with default name`() {
val result = mcpClient.callTool(name = "greet", arguments = emptyMap())
val content = result.content.single()
assertIs<TextContent>(content)
assertEquals("Hello, World!", content.text)
}

@Test
suspend fun `should call multi-greet tool with logging notifications`() {
val result = mcpClient.callTool(name = "multi-greet", arguments = mapOf("name" to "Bob"))
val content = result.content.single()
assertIs<TextContent>(content)
assertEquals("Good morning, Bob!", content.text)
}

@Test
suspend fun `should list prompts`() {
val result = mcpClient.listPrompts()
assertEquals(listOf("greeting-template"), result.prompts.map { it.name })
}

@Test
suspend fun `should get greeting template prompt`() {
val result = mcpClient.getPrompt(
GetPromptRequest(
GetPromptRequestParams(
name = "greeting-template",
arguments = mapOf("name" to "Charlie"),
),
),
)
val message = result.messages.single()
assertEquals(Role.User, message.role)
val content = message.content
assertIs<TextContent>(content)
assertEquals("Please greet Charlie in a friendly manner.", content.text)
}

@Test
suspend fun `should list resources`() {
val result = mcpClient.listResources()
assertEquals(listOf("Default Greeting"), result.resources.map { it.name })
}

@Test
suspend fun `should read default greeting resource`() {
val result = mcpClient.readResource(
ReadResourceRequest(
ReadResourceRequestParams(uri = "https://example.com/greetings/default"),
),
)
val content = result.contents.single()
assertIs<TextResourceContents>(content)
assertEquals("Hello, world!", content.text)
assertEquals("text/plain", content.mimeType)
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
org.slf4j.simpleLogger.defaultLogLevel=INFO
org.slf4j.simpleLogger.showThreadName=true
org.slf4j.simpleLogger.showDateTime=false

# Log level for specific packages or classes
org.slf4j.simpleLogger.log.io.modelcontextprotocol.sample.server=DEBUG
Loading