Initial commit
This commit is contained in:
parent
71ae106311
commit
7b32c3e3da
1 changed files with 7267 additions and 5 deletions
297
samples/cli.kt
Normal file
297
samples/cli.kt
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
package org.jetbrains.kotlinx.mcp
|
||||
|
||||
import org.jetbrains.kotlinx.mcp.client.Client
|
||||
import org.jetbrains.kotlinx.mcp.client.StdioClientTransport
|
||||
import io.ktor.http.*
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.cio.*
|
||||
import io.ktor.server.engine.*
|
||||
import io.ktor.server.response.*
|
||||
import io.ktor.server.routing.*
|
||||
import io.ktor.server.sse.*
|
||||
import io.ktor.util.collections.*
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import org.jetbrains.kotlinx.mcp.server.MCP
|
||||
import org.jetbrains.kotlinx.mcp.server.SSEServerTransport
|
||||
import org.jetbrains.kotlinx.mcp.server.Server
|
||||
import org.jetbrains.kotlinx.mcp.server.ServerOptions
|
||||
import org.jetbrains.kotlinx.mcp.server.StdioServerTransport
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
if (args.isEmpty())
|
||||
return
|
||||
val first = args[0]
|
||||
when (first) {
|
||||
"--server" -> runServer()
|
||||
"--demo" -> runDemo()
|
||||
"--sse-server" -> {
|
||||
if (args.size < 2) {
|
||||
System.err.println("Missing port argument")
|
||||
return
|
||||
}
|
||||
val port = args[1].toIntOrNull()
|
||||
if (port == null) {
|
||||
System.err.println("Invalid port: ${args[1]}")
|
||||
return
|
||||
}
|
||||
runSseServer(port)
|
||||
}
|
||||
|
||||
else -> {
|
||||
System.err.println("Unknown argument: $first")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun runDemo() {
|
||||
val processBuilder = ProcessBuilder("npx", "-y", "@jetbrains/mcp-proxy")
|
||||
|
||||
var process: Process? = null
|
||||
try {
|
||||
process = processBuilder.start()
|
||||
|
||||
val client = Client(
|
||||
Implementation("test", "1.0"),
|
||||
)
|
||||
val transport = StdioClientTransport(process.inputStream, process.outputStream)
|
||||
runBlocking {
|
||||
client.connect(transport)
|
||||
|
||||
val serverCapabilities = client.getServerCapabilities()
|
||||
|
||||
// Resources capability check
|
||||
serverCapabilities?.resources?.let {
|
||||
try {
|
||||
val resources = client.listResources()
|
||||
System.err.println("Resources: ${resources?.resources?.joinToString { it.name }}")
|
||||
} catch (e: Exception) {
|
||||
System.err.println("Failed to list resources: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
// Tools capability check
|
||||
serverCapabilities?.tools?.let {
|
||||
try {
|
||||
val terminal = client.callTool("execute_terminal_command", mapOf("command" to "ls"))
|
||||
System.err.println(terminal?.content?.first())
|
||||
|
||||
val tools = client.listTools()
|
||||
System.err.println(tools?.tools?.joinToString(", ") { tool -> tool.name })
|
||||
|
||||
// tools?.tools?.reversed()?.find { it.name == "toggle_debugger_breakpoint" }?.let { org.jetbrains.kotlinx.mcp.callTool(client, it) }
|
||||
|
||||
tools?.tools?.reversed()?.forEachIndexed { i, tool ->
|
||||
System.err.println("$i out of ${tools.tools.size}: ${tool.name}")
|
||||
callTool(client, tool)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
System.err.println("Failed to list tools: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
// Prompts capability check
|
||||
serverCapabilities?.prompts?.let {
|
||||
try {
|
||||
val prompts = client.listPrompts()
|
||||
System.err.println("Prompts: ${prompts?.prompts?.joinToString { it.name }}")
|
||||
} catch (e: Exception) {
|
||||
System.err.println("Failed to list prompts: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
process?.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun callTool(client: Client, tool: Tool) {
|
||||
System.err.println(tool.inputSchema)
|
||||
|
||||
val map = fillSchema(tool.inputSchema)
|
||||
|
||||
System.err.println("calling: ${tool.name}: $map")
|
||||
val result = try {
|
||||
client.callTool(CallToolRequest(tool.name, map))
|
||||
} catch (e: Exception) {
|
||||
System.err.println("Failed to call tool ${tool.name}: ${e.message}")
|
||||
return
|
||||
}
|
||||
System.err.println("Result: ${result?.content?.first()}\n")
|
||||
}
|
||||
|
||||
private fun fillSchema(schema: Tool.Input): JsonObject {
|
||||
return buildJsonObject {
|
||||
schema.properties.forEach { name, elt ->
|
||||
val type = (elt.jsonObject["type"] as JsonPrimitive).content
|
||||
val value = when (type) {
|
||||
"string" -> JsonPrimitive("Hello")
|
||||
"number" -> JsonPrimitive(42)
|
||||
"boolean" -> JsonPrimitive(true)
|
||||
else -> {
|
||||
System.err.println("+".repeat(30) + " Unknown type: $type " + "+".repeat(30))
|
||||
JsonPrimitive("Unknown")
|
||||
}
|
||||
}
|
||||
put(name, value)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun runServer() {
|
||||
val def = CompletableDeferred<Unit>()
|
||||
|
||||
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),
|
||||
)
|
||||
),
|
||||
onCloseCallback = {
|
||||
def.complete(Unit)
|
||||
}
|
||||
)
|
||||
|
||||
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 = "Test org.jetbrains.kotlinx.mcp.Tool",
|
||||
description = "A test tool",
|
||||
inputSchema = Tool.Input()
|
||||
) { request ->
|
||||
CallToolResult(
|
||||
content = listOf(TextContent("Hello, world!"))
|
||||
)
|
||||
}
|
||||
|
||||
// Add a resource
|
||||
server.addResource(
|
||||
uri = "https://google.com/",
|
||||
name = "Google Search",
|
||||
description = "Web search engine",
|
||||
mimeType = "text/html"
|
||||
) { request ->
|
||||
ReadResourceResult(
|
||||
contents = listOf(
|
||||
TextResourceContents("Placeholder content for ${request.uri}", request.uri, "text/html")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Note: The server will handle listing prompts, tools, and resources automatically.
|
||||
// The handleListResourceTemplates will return empty as defined in the Server code.
|
||||
|
||||
val transport = StdioServerTransport()
|
||||
|
||||
val err = System.err
|
||||
|
||||
runBlocking {
|
||||
server.connect(transport)
|
||||
err.println("Server running on stdio")
|
||||
def.await()
|
||||
}
|
||||
|
||||
err.println("Server closed")
|
||||
}
|
||||
|
||||
fun runSseServer(port: Int): Unit = runBlocking {
|
||||
val servers = ConcurrentMap<String, Server>()
|
||||
|
||||
embeddedServer(CIO, host = "0.0.0.0", port = port) {
|
||||
install(SSE)
|
||||
routing {
|
||||
sse("/sse") {
|
||||
val transport = SSEServerTransport("/message", this)
|
||||
val options = ServerOptions(
|
||||
capabilities = ServerCapabilities(
|
||||
prompts = ServerCapabilities.Prompts(listChanged = null),
|
||||
resources = ServerCapabilities.Resources(subscribe = null, listChanged = null),
|
||||
)
|
||||
)
|
||||
val server = Server(
|
||||
Implementation(
|
||||
name = "mcp-kotlin test server",
|
||||
version = "0.1.0"
|
||||
),
|
||||
options
|
||||
)
|
||||
|
||||
// For SSE, you can also add prompts/tools/resources if needed:
|
||||
// server.addTool(...), server.addPrompt(...), server.addResource(...)
|
||||
|
||||
servers[transport.sessionId] = server
|
||||
|
||||
server.onCloseCallback = {
|
||||
println("Server closed")
|
||||
servers.remove(transport.sessionId)
|
||||
}
|
||||
|
||||
server.connect(transport)
|
||||
}
|
||||
post("/message") {
|
||||
println("Received Message")
|
||||
val sessionId: String = call.request.queryParameters["sessionId"]!!
|
||||
val transport = servers[sessionId]?.transport as? SSEServerTransport
|
||||
if (transport == null) {
|
||||
call.respond(HttpStatusCode.NotFound, "Session not found")
|
||||
return@post
|
||||
}
|
||||
|
||||
transport.handlePostMessage(call)
|
||||
}
|
||||
}
|
||||
}.start(wait = true)
|
||||
}
|
||||
|
||||
fun runSseServerKtor(port: Int): Unit = runBlocking {
|
||||
embeddedServer(CIO, host = "0.0.0.0", port = port) {
|
||||
MCP {
|
||||
val options = ServerOptions(
|
||||
capabilities = ServerCapabilities(
|
||||
prompts = ServerCapabilities.Prompts(listChanged = null),
|
||||
resources = ServerCapabilities.Resources(subscribe = null, listChanged = null),
|
||||
)
|
||||
)
|
||||
|
||||
Server(
|
||||
Implementation(
|
||||
name = "mcp-kotlin test server",
|
||||
version = "0.1.0"
|
||||
),
|
||||
options
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue