samples: update mcp-kotlin to 0.10.0 across sample projects (#645)
## Update mcp-kotlin to 0.10.0 across sample projects ## How Has This Been Tested? Locally, added ServerIntegrationTest to simple-streamable-server ## Breaking Changes No ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] Documentation update - [x] Test update ## Checklist <!-- Go over all the following points, and put an `x` in all the boxes that apply. --> - [x] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [x] My code follows the repository's style guidelines - [x] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed
This commit is contained in:
parent
6e247f902d
commit
ad072325c9
6 changed files with 453 additions and 233 deletions
|
|
@ -2,7 +2,7 @@
|
||||||
anthropic = "2.18.0"
|
anthropic = "2.18.0"
|
||||||
kotlin = "2.2.21"
|
kotlin = "2.2.21"
|
||||||
ktor = "3.2.3"
|
ktor = "3.2.3"
|
||||||
mcp-kotlin = "0.9.0"
|
mcp-kotlin = "0.10.0"
|
||||||
shadow = "9.4.1"
|
shadow = "9.4.1"
|
||||||
slf4j = "2.0.17"
|
slf4j = "2.0.17"
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -22,6 +22,15 @@ dependencies {
|
||||||
implementation(libs.ktor.server.sse)
|
implementation(libs.ktor.server.sse)
|
||||||
implementation(libs.ktor.serialization.kotlinx.json)
|
implementation(libs.ktor.serialization.kotlinx.json)
|
||||||
implementation(libs.slf4j.simple)
|
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 {
|
kotlin {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
[versions]
|
[versions]
|
||||||
kotlin = "2.2.21"
|
kotlin = "2.2.21"
|
||||||
ktor = "3.2.3"
|
ktor = "3.2.3"
|
||||||
mcp-kotlin = "0.9.0"
|
junit = "6.0.3"
|
||||||
|
mcp-kotlin = "0.10.0"
|
||||||
slf4j = "2.0.17"
|
slf4j = "2.0.17"
|
||||||
shadow = "9.4.1"
|
shadow = "9.4.1"
|
||||||
|
|
||||||
|
|
@ -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-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json" }
|
||||||
ktor-server-auth = { group = "io.ktor", name = "ktor-server-auth" }
|
ktor-server-auth = { group = "io.ktor", name = "ktor-server-auth" }
|
||||||
ktor-server-sse = { group = "io.ktor", name = "ktor-server-sse" }
|
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" }
|
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" }
|
slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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…
Reference in a new issue