UNPKG

agent-rules-kit

Version:

Bootstrap of **Cursor** rules (`.mdc`) and mirror documentation (`.md`) for AI agent-guided projects.

345 lines (279 loc) 10.1 kB
--- globs: <root>/**/*.kt,<root>/build.gradle.kts,<root>/build.gradle alwaysApply: false --- # MCP Kotlin SDK Implementation Guide ## Kotlin SDK Features The Kotlin SDK provides modern, concise, and type-safe MCP implementations with coroutines support and excellent null safety. ### Gradle Configuration ```kotlin // build.gradle.kts plugins { kotlin("jvm") version "1.9.10" kotlin("plugin.serialization") version "1.9.10" application } dependencies { implementation("io.modelcontextprotocol:mcp-kotlin-sdk:1.0.0") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0") implementation("io.ktor:ktor-server-core:2.3.4") implementation("io.ktor:ktor-server-netty:2.3.4") testImplementation("org.jetbrains.kotlin:kotlin-test") testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3") testImplementation("io.mockk:mockk:1.13.8") } ``` ### Basic Server Implementation ```kotlin import io.modelcontextprotocol.sdk.server.* import io.modelcontextprotocol.sdk.types.* import kotlinx.coroutines.* import kotlinx.serialization.Serializable @Serializable data class EchoArgs(val message: String) @Serializable data class QueryArgs(val query: String, val limit: Int = 100) class KotlinMCPServer : MCPServerHandler { private val resourceStorage = mutableMapOf<String, String>() init { resourceStorage["file://example.txt"] = "Kotlin MCP Server content" resourceStorage["file://data.json"] = """{"key": "value"}""" } override suspend fun listResources(): List<Resource> { return resourceStorage.keys.map { uri -> Resource( uri = uri, name = uri.substringAfterLast("/"), description = "Sample resource", mimeType = when { uri.endsWith(".json") -> "application/json" uri.endsWith(".txt") -> "text/plain" else -> "application/octet-stream" } ) } } override suspend fun readResource(uri: String): ResourceContent { val content = resourceStorage[uri] ?: throw ResourceNotFoundException("Resource not found: $uri") return ResourceContent( uri = uri, mimeType = determineMimeType(uri), text = content ) } override suspend fun listTools(): List<Tool> = listOf( Tool( name = "echo", description = "Echo back the input message", inputSchema = JsonSchema.obj { property("message", JsonSchema.string("Message to echo")) required("message") } ), Tool( name = "query", description = "Execute a database query", inputSchema = JsonSchema.obj { property("query", JsonSchema.string("SQL query")) property("limit", JsonSchema.number("Result limit", default = 100)) required("query") } ) ) override suspend fun callTool(name: String, arguments: Map<String, Any>): ToolResult { return when (name) { "echo" -> handleEcho(arguments) "query" -> handleQuery(arguments) else -> throw ToolNotFoundException("Unknown tool: $name") } } private suspend fun handleEcho(arguments: Map<String, Any>): ToolResult { val message = arguments["message"] as? String ?: throw IllegalArgumentException("Message parameter required") return ToolResult( content = listOf( TextContent( type = "text", text = "Echo: $message" ) ) ) } private suspend fun handleQuery(arguments: Map<String, Any>): ToolResult { val query = arguments["query"] as? String ?: throw IllegalArgumentException("Query parameter required") val limit = (arguments["limit"] as? Number)?.toInt() ?: 100 // Simulate async database operation delay(50) val result = mapOf( "query" to query, "limit" to limit, "rowCount" to (1..limit).random(), "executionTime" to "${(10..100).random()}ms" ) return ToolResult( content = listOf( TextContent( type = "text", text = "Query result: $result" ) ) ) } private fun determineMimeType(uri: String): String = when { uri.endsWith(".json") -> "application/json" uri.endsWith(".txt") -> "text/plain" else -> "application/octet-stream" } } // Server startup suspend fun main() { val server = MCPServer( name = "kotlin-mcp-server", version = "1.0.0", capabilities = ServerCapabilities( resources = ResourceCapabilities( subscribe = true, listChanged = true ), tools = ToolCapabilities( listChanged = true ) ), handler = KotlinMCPServer() ) val transport = StdioTransport() server.start(transport) println("Kotlin MCP Server running...") } ``` ### Client Implementation ```kotlin class KotlinMCPClient { private val client = MCPClient( name = "kotlin-mcp-client", version = "1.0.0", capabilities = ClientCapabilities() ) suspend fun connect(command: String, vararg args: String) { val transport = StdioTransport(command, *args) client.connect(transport) } suspend fun listResources(): List<Resource> = client.listResources() suspend fun readResource(uri: String): ResourceContent = client.readResource(uri) suspend fun listTools(): List<Tool> = client.listTools() suspend fun callTool(name: String, arguments: Map<String, Any>): ToolResult = client.callTool(name, arguments) suspend fun disconnect() = client.disconnect() } // Usage example suspend fun clientExample() { val client = KotlinMCPClient() try { client.connect("kotlin", "-jar", "server.jar") // List resources val resources = client.listResources() println("Resources: ${resources.map { it.name }}") // Call tools val echoResult = client.callTool("echo", mapOf("message" to "Hello Kotlin!")) println("Echo: ${echoResult.content.first().text}") } finally { client.disconnect() } } ``` ### Advanced Features with Coroutines ```kotlin class AdvancedKotlinMCPServer : MCPServerHandler { private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) private val resourceCache = mutableMapOf<String, Deferred<String>>() override suspend fun readResource(uri: String): ResourceContent { val content = resourceCache.getOrPut(uri) { scope.async { // Expensive async operation loadResourceAsync(uri) } }.await() return ResourceContent(uri = uri, text = content) } private suspend fun loadResourceAsync(uri: String): String { delay(100) // Simulate network/disk I/O return "Loaded content for $uri" } override suspend fun callTool(name: String, arguments: Map<String, Any>): ToolResult { return when (name) { "parallel-process" -> handleParallelProcessing(arguments) "streaming-data" -> handleStreamingData(arguments) else -> super.callTool(name, arguments) } } private suspend fun handleParallelProcessing(arguments: Map<String, Any>): ToolResult { val tasks = (arguments["tasks"] as? List<*>)?.filterIsInstance<String>() ?: throw IllegalArgumentException("Tasks parameter required") val results = withContext(Dispatchers.IO) { tasks.map { task -> async { processTask(task) } }.awaitAll() } return ToolResult( content = listOf( TextContent( type = "text", text = "Processed ${results.size} tasks: $results" ) ) ) } private suspend fun processTask(task: String): String { delay(50) // Simulate work return "Processed: $task" } private suspend fun handleStreamingData(arguments: Map<String, Any>): ToolResult { val count = (arguments["count"] as? Number)?.toInt() ?: 10 val results = mutableListOf<String>() // Simulate streaming data processing repeat(count) { i -> delay(10) results.add("Data chunk $i") } return ToolResult( content = listOf( TextContent( type = "text", text = "Streamed ${results.size} chunks" ) ) ) } } ``` ### Testing with Coroutines ```kotlin import kotlinx.coroutines.test.* import kotlin.test.* class KotlinMCPServerTest { @Test fun `should list resources`() = runTest { val server = KotlinMCPServer() val resources = server.listResources() assertTrue(resources.isNotEmpty()) assertTrue(resources.any { it.name == "example.txt" }) } @Test fun `should handle echo tool`() = runTest { val server = KotlinMCPServer() val result = server.callTool("echo", mapOf("message" to "test")) assertEquals("Echo: test", result.content.first().text) } @Test fun `should handle resource not found`() = runTest { val server = KotlinMCPServer() assertFailsWith<ResourceNotFoundException> { server.readResource("file://nonexistent.txt") } } } ``` This Kotlin SDK implementation leverages coroutines, null safety, and concise syntax for building efficient MCP applications.