Skip to content

Commit 7d7f118

Browse files
committed
add wasm server example
1 parent 2050b20 commit 7d7f118

File tree

7 files changed

+258
-29
lines changed

7 files changed

+258
-29
lines changed
Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,41 @@
1-
plugins {
2-
kotlin("jvm") version "2.1.0"
3-
kotlin("plugin.serialization") version "2.1.0"
4-
application
5-
}
1+
@file:OptIn(ExperimentalWasmDsl::class, ExperimentalKotlinGradlePluginApi::class)
62

7-
application {
8-
mainClass.set("MainKt")
9-
}
3+
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
4+
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
105

6+
plugins {
7+
kotlin("multiplatform") version "2.1.20"
8+
kotlin("plugin.serialization") version "2.1.20"
9+
}
1110

1211
group = "org.example"
1312
version = "0.1.0"
1413

15-
dependencies {
16-
implementation("io.modelcontextprotocol:kotlin-sdk:0.4.0")
17-
implementation("org.slf4j:slf4j-nop:2.0.9")
14+
repositories {
15+
mavenCentral()
1816
}
1917

20-
tasks.test {
21-
useJUnitPlatform()
22-
}
2318
kotlin {
24-
jvmToolchain(21)
19+
jvmToolchain(17)
20+
jvm {
21+
binaries {
22+
executable {
23+
mainClass.set("Main_jvmKt")
24+
}
25+
}
26+
}
27+
wasmJs {
28+
nodejs()
29+
binaries.executable()
30+
}
31+
32+
sourceSets {
33+
commonMain.dependencies {
34+
implementation("io.modelcontextprotocol:kotlin-sdk:0.4.0")
35+
}
36+
jvmMain.dependencies {
37+
implementation("org.slf4j:slf4j-nop:2.0.9")
38+
}
39+
wasmJsMain.dependencies {}
40+
}
2541
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
kotlin.code.style=official
2+
3+
kotlin.daemon.jvmargs=-Xmx2G

samples/kotlin-mcp-server/settings.gradle.kts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,3 @@ plugins {
22
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
33
}
44
rootProject.name = "kotlin-mcp-server"
5-
6-
dependencyResolutionManagement {
7-
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
8-
repositories {
9-
mavenCentral()
10-
maven("https://maven.pkg.jetbrains.space/public/p/kotlin-mcp-sdk/sdk")
11-
}
12-
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package shared
2+
3+
import io.ktor.http.HttpStatusCode
4+
import io.ktor.server.application.install
5+
import io.ktor.server.cio.CIO
6+
import io.ktor.server.engine.embeddedServer
7+
import io.ktor.server.response.respond
8+
import io.ktor.server.routing.post
9+
import io.ktor.server.routing.routing
10+
import io.ktor.server.sse.SSE
11+
import io.ktor.server.sse.sse
12+
import io.ktor.util.collections.ConcurrentMap
13+
import io.modelcontextprotocol.kotlin.sdk.CallToolResult
14+
import io.modelcontextprotocol.kotlin.sdk.GetPromptResult
15+
import io.modelcontextprotocol.kotlin.sdk.Implementation
16+
import io.modelcontextprotocol.kotlin.sdk.PromptArgument
17+
import io.modelcontextprotocol.kotlin.sdk.PromptMessage
18+
import io.modelcontextprotocol.kotlin.sdk.ReadResourceResult
19+
import io.modelcontextprotocol.kotlin.sdk.Role
20+
import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities
21+
import io.modelcontextprotocol.kotlin.sdk.TextContent
22+
import io.modelcontextprotocol.kotlin.sdk.TextResourceContents
23+
import io.modelcontextprotocol.kotlin.sdk.Tool
24+
import io.modelcontextprotocol.kotlin.sdk.server.Server
25+
import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions
26+
import io.modelcontextprotocol.kotlin.sdk.server.SseServerTransport
27+
import io.modelcontextprotocol.kotlin.sdk.server.mcp
28+
import kotlin.collections.set
29+
30+
fun configureServer(): Server {
31+
val server = Server(
32+
Implementation(
33+
name = "mcp-kotlin test server",
34+
version = "0.1.0"
35+
),
36+
ServerOptions(
37+
capabilities = ServerCapabilities(
38+
prompts = ServerCapabilities.Prompts(listChanged = true),
39+
resources = ServerCapabilities.Resources(subscribe = true, listChanged = true),
40+
tools = ServerCapabilities.Tools(listChanged = true),
41+
)
42+
)
43+
)
44+
45+
server.addPrompt(
46+
name = "Kotlin Developer",
47+
description = "Develop small kotlin applications",
48+
arguments = listOf(
49+
PromptArgument(
50+
name = "Project Name",
51+
description = "Project name for the new project",
52+
required = true
53+
)
54+
)
55+
) { request ->
56+
GetPromptResult(
57+
"Description for ${request.name}",
58+
messages = listOf(
59+
PromptMessage(
60+
role = Role.user,
61+
content = TextContent("Develop a kotlin project named <name>${request.arguments?.get("Project Name")}</name>")
62+
)
63+
)
64+
)
65+
}
66+
67+
// Add a tool
68+
server.addTool(
69+
name = "kotlin-sdk-tool",
70+
description = "A test tool",
71+
inputSchema = Tool.Input()
72+
) { request ->
73+
CallToolResult(
74+
content = listOf(TextContent("Hello, world!"))
75+
)
76+
}
77+
78+
// Add a resource
79+
server.addResource(
80+
uri = "https://search.com/",
81+
name = "Web Search",
82+
description = "Web search engine",
83+
mimeType = "text/html"
84+
) { request ->
85+
ReadResourceResult(
86+
contents = listOf(
87+
TextResourceContents("Placeholder content for ${request.uri}", request.uri, "text/html")
88+
)
89+
)
90+
}
91+
92+
return server
93+
}
94+
95+
suspend fun runSseMcpServerWithPlainConfiguration(port: Int): Unit {
96+
val servers = ConcurrentMap<String, Server>()
97+
println("Starting sse server on port $port. ")
98+
println("Use inspector to connect to the http://localhost:$port/sse")
99+
100+
embeddedServer(CIO, host = "0.0.0.0", port = port) {
101+
install(SSE)
102+
routing {
103+
sse("/sse") {
104+
val transport = SseServerTransport("/message", this)
105+
val server = configureServer()
106+
107+
// For SSE, you can also add prompts/tools/resources if needed:
108+
// server.addTool(...), server.addPrompt(...), server.addResource(...)
109+
110+
servers[transport.sessionId] = server
111+
112+
server.onClose {
113+
println("Server closed")
114+
servers.remove(transport.sessionId)
115+
}
116+
117+
server.connect(transport)
118+
}
119+
post("/message") {
120+
println("Received Message")
121+
val sessionId: String = call.request.queryParameters["sessionId"]!!
122+
val transport = servers[sessionId]?.transport as? SseServerTransport
123+
if (transport == null) {
124+
call.respond(HttpStatusCode.NotFound, "Session not found")
125+
return@post
126+
}
127+
128+
transport.handlePostMessage(call)
129+
}
130+
}
131+
}.startSuspend(wait = true)
132+
}
133+
134+
/**
135+
* Starts an SSE (Server Sent Events) MCP server using the Ktor framework and the specified port.
136+
*
137+
* The url can be accessed in the MCP inspector at [http://localhost:$port]
138+
*
139+
* @param port The port number on which the SSE MCP server will listen for client connections.
140+
* @return Unit This method does not return a value.
141+
*/
142+
suspend fun runSseMcpServerUsingKtorPlugin(port: Int): Unit {
143+
println("Starting sse server on port $port")
144+
println("Use inspector to connect to the http://localhost:$port/sse")
145+
146+
embeddedServer(CIO, host = "0.0.0.0", port = port) {
147+
mcp {
148+
return@mcp configureServer()
149+
}
150+
}.startSuspend(wait = true)
151+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport
2+
import kotlinx.coroutines.Job
3+
import kotlinx.coroutines.runBlocking
4+
import kotlinx.io.asSink
5+
import kotlinx.io.asSource
6+
import kotlinx.io.buffered
7+
import shared.configureServer
8+
import shared.runSseMcpServerUsingKtorPlugin
9+
import shared.runSseMcpServerWithPlainConfiguration
10+
11+
/**
12+
* Start sse-server mcp on port 3001.
13+
*
14+
* @param args
15+
* - "--stdio": Runs an MCP server using standard input/output.
16+
* - "--sse-server-ktor <port>": Runs an SSE MCP server using Ktor plugin (default if no argument is provided).
17+
* - "--sse-server <port>": Runs an SSE MCP server with a plain configuration.
18+
*/
19+
fun main(args: Array<String>): Unit = runBlocking {
20+
val command = args.firstOrNull() ?: "--sse-server-ktor"
21+
val port = args.getOrNull(1)?.toIntOrNull() ?: 3001
22+
when (command) {
23+
"--stdio" -> runMcpServerUsingStdio()
24+
"--sse-server-ktor" -> runSseMcpServerUsingKtorPlugin(port)
25+
"--sse-server" -> runSseMcpServerWithPlainConfiguration(port)
26+
else -> {
27+
System.err.println("Unknown command: $command")
28+
}
29+
}
30+
}
31+
32+
fun runMcpServerUsingStdio() {
33+
// Note: The server will handle listing prompts, tools, and resources automatically.
34+
// The handleListResourceTemplates will return empty as defined in the Server code.
35+
val server = configureServer()
36+
val transport = StdioServerTransport(
37+
inputStream = System.`in`.asSource().buffered(),
38+
outputStream = System.out.asSink().buffered()
39+
)
40+
41+
runBlocking {
42+
server.connect(transport)
43+
val done = Job()
44+
server.onClose {
45+
done.complete()
46+
}
47+
done.join()
48+
println("Server closed")
49+
}
50+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import shared.runSseMcpServerUsingKtorPlugin
2+
import shared.runSseMcpServerWithPlainConfiguration
3+
4+
/**
5+
* Start sse-server mcp on port 3001.
6+
*
7+
* @param args
8+
* - "--sse-server-ktor <port>": Runs an SSE MCP server using Ktor plugin (default if no argument is provided).
9+
* - "--sse-server <port>": Runs an SSE MCP server with a plain configuration.
10+
*/
11+
suspend fun main(args: Array<String>) {
12+
val command = args.firstOrNull() ?: "--sse-server-ktor"
13+
val port = args.getOrNull(1)?.toIntOrNull() ?: 3001
14+
when (command) {
15+
"--sse-server-ktor" -> runSseMcpServerUsingKtorPlugin(port)
16+
"--sse-server" -> runSseMcpServerWithPlainConfiguration(port)
17+
else -> {
18+
error("Unknown command: $command")
19+
}
20+
}
21+
}

samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,9 @@ fun `run mcp server`() {
6767
required = listOf("state")
6868
)
6969
) { request ->
70-
val state = request.arguments["state"]?.jsonPrimitive?.content
71-
if (state == null) {
72-
return@addTool CallToolResult(
73-
content = listOf(TextContent("The 'state' parameter is required."))
74-
)
75-
}
70+
val state = request.arguments["state"]?.jsonPrimitive?.content ?: return@addTool CallToolResult(
71+
content = listOf(TextContent("The 'state' parameter is required."))
72+
)
7673

7774
val alerts = httpClient.getAlerts(state)
7875

0 commit comments

Comments
 (0)