Fix kotlin-mcp-server example and update samples (#345)
- updated the sdk version in all examples - fixed `kotlin-mcp-server` example (#331) ## Motivation and Context Removed the WASM example, as it duplicates the JVM example and only adds unnecessary complexity ## How Has This Been Tested? Locally ## Breaking Changes No ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update ## Checklist - [x] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [x] My code follows the repository's style guidelines - [x] New and existing tests pass locally - [ ] I have added appropriate error handling - [x] I have added or updated documentation as needed
This commit is contained in:
parent
cae9130e37
commit
458a49a5fb
4 changed files with 115 additions and 144 deletions
|
|
@ -1,50 +0,0 @@
|
|||
import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.io.asSink
|
||||
import kotlinx.io.asSource
|
||||
import kotlinx.io.buffered
|
||||
import shared.configureServer
|
||||
import shared.runSseMcpServerUsingKtorPlugin
|
||||
import shared.runSseMcpServerWithPlainConfiguration
|
||||
|
||||
/**
|
||||
* Start sse-server mcp on port 3001.
|
||||
*
|
||||
* @param args
|
||||
* - "--stdio": Runs an MCP server using standard input/output.
|
||||
* - "--sse-server-ktor <port>": Runs an SSE MCP server using Ktor plugin (default if no argument is provided).
|
||||
* - "--sse-server <port>": Runs an SSE MCP server with a plain configuration.
|
||||
*/
|
||||
fun main(args: Array<String>): Unit = runBlocking {
|
||||
val command = args.firstOrNull() ?: "--sse-server-ktor"
|
||||
val port = args.getOrNull(1)?.toIntOrNull() ?: 3001
|
||||
when (command) {
|
||||
"--stdio" -> runMcpServerUsingStdio()
|
||||
"--sse-server-ktor" -> runSseMcpServerUsingKtorPlugin(port)
|
||||
"--sse-server" -> runSseMcpServerWithPlainConfiguration(port)
|
||||
else -> {
|
||||
System.err.println("Unknown command: $command")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun runMcpServerUsingStdio() {
|
||||
// Note: The server will handle listing prompts, tools, and resources automatically.
|
||||
// The handleListResourceTemplates will return empty as defined in the Server code.
|
||||
val server = configureServer()
|
||||
val transport = StdioServerTransport(
|
||||
inputStream = System.`in`.asSource().buffered(),
|
||||
outputStream = System.out.asSink().buffered()
|
||||
)
|
||||
|
||||
runBlocking {
|
||||
server.connect(transport)
|
||||
val done = Job()
|
||||
server.onClose {
|
||||
done.complete()
|
||||
}
|
||||
done.join()
|
||||
println("Server closed")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package io.modelcontextprotocol.sample.server
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
/**
|
||||
* Start sse-server mcp on port 3001.
|
||||
*
|
||||
* @param args
|
||||
* - "--stdio": Runs an MCP server using standard input/output.
|
||||
* - "--sse-server-ktor <port>": Runs an SSE MCP server using Ktor plugin (default if no argument is provided).
|
||||
* - "--sse-server <port>": Runs an SSE MCP server with a plain configuration.
|
||||
*/
|
||||
fun main(args: Array<String>): Unit = runBlocking {
|
||||
val command = args.firstOrNull() ?: "--sse-server-ktor"
|
||||
val port = args.getOrNull(1)?.toIntOrNull() ?: 3001
|
||||
when (command) {
|
||||
"--stdio" -> runMcpServerUsingStdio()
|
||||
"--sse-server-ktor" -> runSseMcpServerUsingKtorPlugin(port)
|
||||
"--sse-server" -> runSseMcpServerWithPlainConfiguration(port)
|
||||
else -> {
|
||||
error("Unknown command: $command")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
package io.modelcontextprotocol.sample.server
|
||||
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.server.application.install
|
||||
import io.ktor.server.cio.CIO
|
||||
import io.ktor.server.engine.embeddedServer
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.post
|
||||
import io.ktor.server.routing.routing
|
||||
import io.ktor.server.sse.SSE
|
||||
import io.ktor.server.sse.sse
|
||||
import io.ktor.util.collections.ConcurrentMap
|
||||
import io.modelcontextprotocol.kotlin.sdk.CallToolResult
|
||||
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.ReadResourceResult
|
||||
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.server.ServerSession
|
||||
import io.modelcontextprotocol.kotlin.sdk.server.SseServerTransport
|
||||
import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport
|
||||
import io.modelcontextprotocol.kotlin.sdk.server.mcp
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.io.asSink
|
||||
import kotlinx.io.asSource
|
||||
import kotlinx.io.buffered
|
||||
|
||||
fun configureServer(): Server {
|
||||
val server = Server(
|
||||
Implementation(
|
||||
name = "mcp-kotlin test server",
|
||||
version = "0.1.0",
|
||||
),
|
||||
ServerOptions(
|
||||
capabilities = ServerCapabilities(
|
||||
prompts = ServerCapabilities.Prompts(listChanged = true),
|
||||
resources = ServerCapabilities.Resources(subscribe = true, listChanged = true),
|
||||
tools = ServerCapabilities.Tools(listChanged = true),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
server.addPrompt(
|
||||
name = "Kotlin Developer",
|
||||
description = "Develop small kotlin applications",
|
||||
arguments = listOf(
|
||||
PromptArgument(
|
||||
name = "Project Name",
|
||||
description = "Project name for the new project",
|
||||
required = true,
|
||||
),
|
||||
),
|
||||
) { request ->
|
||||
GetPromptResult(
|
||||
"Description for ${request.name}",
|
||||
messages = listOf(
|
||||
PromptMessage(
|
||||
role = Role.user,
|
||||
content = TextContent(
|
||||
"Develop a kotlin project named <name>${request.arguments?.get("Project Name")}</name>",
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Add a tool
|
||||
server.addTool(
|
||||
name = "kotlin-sdk-tool",
|
||||
description = "A test tool",
|
||||
inputSchema = Tool.Input(),
|
||||
) { _ ->
|
||||
CallToolResult(
|
||||
content = listOf(TextContent("Hello, world!")),
|
||||
)
|
||||
}
|
||||
|
||||
// Add a resource
|
||||
server.addResource(
|
||||
uri = "https://search.com/",
|
||||
name = "Web Search",
|
||||
description = "Web search engine",
|
||||
mimeType = "text/html",
|
||||
) { request ->
|
||||
ReadResourceResult(
|
||||
contents = listOf(
|
||||
TextResourceContents("Placeholder content for ${request.uri}", request.uri, "text/html"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
fun runSseMcpServerWithPlainConfiguration(port: Int) {
|
||||
val serverSessions = ConcurrentMap<String, ServerSession>()
|
||||
println("Starting SSE server on port $port")
|
||||
println("Use inspector to connect to http://localhost:$port/sse")
|
||||
|
||||
val server = configureServer()
|
||||
|
||||
embeddedServer(CIO, host = "127.0.0.1", port = port) {
|
||||
install(SSE)
|
||||
routing {
|
||||
sse("/sse") {
|
||||
val transport = SseServerTransport("/message", this)
|
||||
val serverSession = server.connect(transport)
|
||||
serverSessions[transport.sessionId] = serverSession
|
||||
|
||||
serverSession.onClose {
|
||||
println("Server session closed for: ${transport.sessionId}")
|
||||
serverSessions.remove(transport.sessionId)
|
||||
}
|
||||
}
|
||||
post("/message") {
|
||||
val sessionId: String? = call.request.queryParameters["sessionId"]
|
||||
if (sessionId == null) {
|
||||
call.respond(HttpStatusCode.BadRequest, "Missing sessionId parameter")
|
||||
return@post
|
||||
}
|
||||
|
||||
val transport = serverSessions[sessionId]?.transport as? SseServerTransport
|
||||
if (transport == null) {
|
||||
call.respond(HttpStatusCode.NotFound, "Session not found")
|
||||
return@post
|
||||
}
|
||||
|
||||
transport.handlePostMessage(call)
|
||||
}
|
||||
}
|
||||
}.start(wait = true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an SSE (Server-Sent Events) MCP server using the Ktor plugin.
|
||||
*
|
||||
* This is the recommended approach for SSE servers as it simplifies configuration.
|
||||
* The URL can be accessed in the MCP inspector at http://localhost:[port]/sse
|
||||
*
|
||||
* @param port The port number on which the SSE MCP server will listen for client connections.
|
||||
*/
|
||||
fun runSseMcpServerUsingKtorPlugin(port: Int) {
|
||||
println("Starting SSE server on port $port")
|
||||
println("Use inspector to connect to http://localhost:$port/sse")
|
||||
|
||||
embeddedServer(CIO, host = "127.0.0.1", port = port) {
|
||||
mcp {
|
||||
return@mcp configureServer()
|
||||
}
|
||||
}.start(wait = true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an MCP server using Standard I/O transport.
|
||||
*
|
||||
* This mode is useful for process-based communication where the server
|
||||
* communicates via stdin/stdout with a parent process or client.
|
||||
*/
|
||||
fun runMcpServerUsingStdio() {
|
||||
val server = configureServer()
|
||||
val transport = StdioServerTransport(
|
||||
inputStream = System.`in`.asSource().buffered(),
|
||||
outputStream = System.out.asSink().buffered()
|
||||
)
|
||||
|
||||
runBlocking {
|
||||
server.connect(transport)
|
||||
val done = Job()
|
||||
server.onClose {
|
||||
done.complete()
|
||||
}
|
||||
done.join()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
# Level of logging for the ROOT logger: ERROR, WARN, INFO, DEBUG, TRACE (default is INFO)
|
||||
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=TRACE
|
||||
org.slf4j.simpleLogger.log.io.ktor=TRACE
|
||||
Loading…
Reference in a new issue