{"kind":"AgentDefinition","metadata":{"namespace":"community","name":"kotlin-mcp-server","version":"0.1.0"},"spec":{"agents_md":"---\ndescription: 'Best practices and patterns for building Model Context Protocol (MCP) servers in Kotlin using the official io.modelcontextprotocol:kotlin-sdk library.'\napplyTo: \"**/*.kt, **/*.kts, **/build.gradle.kts, **/settings.gradle.kts\"\n---\n\n# Kotlin MCP Server Development Guidelines\n\nWhen building MCP servers in Kotlin, follow these best practices and patterns using the official Kotlin SDK.\n\n## Server Setup\n\nCreate an MCP server using the `Server` class:\n\n```kotlin\nimport io.modelcontextprotocol.kotlin.sdk.server.Server\nimport io.modelcontextprotocol.kotlin.sdk.server.ServerOptions\nimport io.modelcontextprotocol.kotlin.sdk.Implementation\nimport io.modelcontextprotocol.kotlin.sdk.ServerCapabilities\n\nval server = Server(\n    serverInfo = Implementation(\n        name = \"my-server\",\n        version = \"1.0.0\"\n    ),\n    options = ServerOptions(\n        capabilities = ServerCapabilities(\n            tools = ServerCapabilities.Tools(),\n            resources = ServerCapabilities.Resources(\n                subscribe = true,\n                listChanged = true\n            ),\n            prompts = ServerCapabilities.Prompts(listChanged = true)\n        )\n    )\n) {\n    \"Server description goes here\"\n}\n```\n\n## Adding Tools\n\nUse `server.addTool()` to register tools with typed request/response handling:\n\n```kotlin\nimport io.modelcontextprotocol.kotlin.sdk.CallToolRequest\nimport io.modelcontextprotocol.kotlin.sdk.CallToolResult\nimport io.modelcontextprotocol.kotlin.sdk.TextContent\n\nserver.addTool(\n    name = \"search\",\n    description = \"Search for information\",\n    inputSchema = buildJsonObject {\n        put(\"type\", \"object\")\n        putJsonObject(\"properties\") {\n            putJsonObject(\"query\") {\n                put(\"type\", \"string\")\n                put(\"description\", \"The search query\")\n            }\n            putJsonObject(\"limit\") {\n                put(\"type\", \"integer\")\n                put(\"description\", \"Maximum results to return\")\n            }\n        }\n        putJsonArray(\"required\") {\n            add(\"query\")\n        }\n    }\n) { request: CallToolRequest -\u003e\n    val query = request.params.arguments[\"query\"] as? String\n        ?: throw IllegalArgumentException(\"query is required\")\n    val limit = (request.params.arguments[\"limit\"] as? Number)?.toInt() ?: 10\n    \n    // Perform search\n    val results = performSearch(query, limit)\n    \n    CallToolResult(\n        content = listOf(\n            TextContent(\n                text = results.joinToString(\"\\n\")\n            )\n        )\n    )\n}\n```\n\n## Adding Resources\n\nUse `server.addResource()` to provide accessible data:\n\n```kotlin\nimport io.modelcontextprotocol.kotlin.sdk.ReadResourceRequest\nimport io.modelcontextprotocol.kotlin.sdk.ReadResourceResult\nimport io.modelcontextprotocol.kotlin.sdk.TextResourceContents\n\nserver.addResource(\n    uri = \"file:///data/example.txt\",\n    name = \"Example Data\",\n    description = \"Example resource data\",\n    mimeType = \"text/plain\"\n) { request: ReadResourceRequest -\u003e\n    val content = loadResourceContent(request.uri)\n    \n    ReadResourceResult(\n        contents = listOf(\n            TextResourceContents(\n                text = content,\n                uri = request.uri,\n                mimeType = \"text/plain\"\n            )\n        )\n    )\n}\n```\n\n## Adding Prompts\n\nUse `server.addPrompt()` for reusable prompt templates:\n\n```kotlin\nimport io.modelcontextprotocol.kotlin.sdk.GetPromptRequest\nimport io.modelcontextprotocol.kotlin.sdk.GetPromptResult\nimport io.modelcontextprotocol.kotlin.sdk.PromptMessage\nimport io.modelcontextprotocol.kotlin.sdk.Role\n\nserver.addPrompt(\n    name = \"analyze\",\n    description = \"Analyze a topic\",\n    arguments = listOf(\n        PromptArgument(\n            name = \"topic\",\n            description = \"The topic to analyze\",\n            required = true\n        )\n    )\n) { request: GetPromptRequest -\u003e\n    val topic = request.params.arguments?.get(\"topic\") as? String\n        ?: throw IllegalArgumentException(\"topic is required\")\n    \n    GetPromptResult(\n        description = \"Analyze the given topic\",\n        messages = listOf(\n            PromptMessage(\n                role = Role.User,\n                content = TextContent(\n                    text = \"Analyze this topic: $topic\"\n                )\n            )\n        )\n    )\n}\n```\n\n## Transport Configuration\n\n### Stdio Transport\n\nFor communication over stdin/stdout:\n\n```kotlin\nimport io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport\n\nsuspend fun main() {\n    val transport = StdioServerTransport()\n    server.connect(transport)\n}\n```\n\n### SSE Transport with Ktor\n\nFor HTTP-based communication using Server-Sent Events:\n\n```kotlin\nimport io.ktor.server.application.*\nimport io.ktor.server.engine.*\nimport io.ktor.server.netty.*\nimport io.modelcontextprotocol.kotlin.sdk.server.mcp\n\nfun main() {\n    embeddedServer(Netty, port = 8080) {\n        mcp {\n            Server(\n                serverInfo = Implementation(\n                    name = \"sse-server\",\n                    version = \"1.0.0\"\n                ),\n                options = ServerOptions(\n                    capabilities = ServerCapabilities(\n                        tools = ServerCapabilities.Tools()\n                    )\n                )\n            ) {\n                \"SSE-based MCP server\"\n            }\n        }\n    }.start(wait = true)\n}\n```\n\n## Coroutine Usage\n\nAll MCP operations are suspending functions. Use Kotlin coroutines properly:\n\n```kotlin\nimport kotlinx.coroutines.coroutineScope\nimport kotlinx.coroutines.async\n\nserver.addTool(\n    name = \"parallel-search\",\n    description = \"Search multiple sources in parallel\"\n) { request -\u003e\n    coroutineScope {\n        val source1 = async { searchSource1(query) }\n        val source2 = async { searchSource2(query) }\n        \n        val results = source1.await() + source2.await()\n        \n        CallToolResult(\n            content = listOf(TextContent(text = results.joinToString(\"\\n\")))\n        )\n    }\n}\n```\n\n## Error Handling\n\nUse Kotlin's exception handling and provide meaningful error messages:\n\n```kotlin\nserver.addTool(\n    name = \"validate-input\",\n    description = \"Process validated input\"\n) { request -\u003e\n    try {\n        val input = request.params.arguments[\"input\"] as? String\n            ?: throw IllegalArgumentException(\"input is required\")\n        \n        require(input.isNotBlank()) { \"input cannot be blank\" }\n        \n        val result = processInput(input)\n        \n        CallToolResult(\n            content = listOf(TextContent(text = result))\n        )\n    } catch (e: IllegalArgumentException) {\n        CallToolResult(\n            isError = true,\n            content = listOf(TextContent(text = \"Validation error: ${e.message}\"))\n        )\n    }\n}\n```\n\n## JSON Schema with kotlinx.serialization\n\nUse kotlinx.serialization for type-safe JSON schemas:\n\n```kotlin\nimport kotlinx.serialization.Serializable\nimport kotlinx.serialization.json.*\n\n@Serializable\ndata class SearchInput(\n    val query: String,\n    val limit: Int = 10,\n    val filters: List\u003cString\u003e = emptyList()\n)\n\nfun createToolSchema(): JsonObject = buildJsonObject {\n    put(\"type\", \"object\")\n    putJsonObject(\"properties\") {\n        putJsonObject(\"query\") {\n            put(\"type\", \"string\")\n            put(\"description\", \"Search query\")\n        }\n        putJsonObject(\"limit\") {\n            put(\"type\", \"integer\")\n            put(\"default\", 10)\n        }\n        putJsonObject(\"filters\") {\n            put(\"type\", \"array\")\n            putJsonObject(\"items\") {\n                put(\"type\", \"string\")\n            }\n        }\n    }\n    putJsonArray(\"required\") {\n        add(\"query\")\n    }\n}\n```\n\n## Gradle Configuration\n\nSet up your `build.gradle.kts` properly:\n\n```kotlin\nplugins {\n    kotlin(\"jvm\") version \"2.1.0\"\n    kotlin(\"plugin.serialization\") version \"2.1.0\"\n}\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    implementation(\"io.modelcontextprotocol:kotlin-sdk:0.7.2\")\n    \n    // For client transport\n    implementation(\"io.ktor:ktor-client-cio:3.0.0\")\n    \n    // For server transport\n    implementation(\"io.ktor:ktor-server-netty:3.0.0\")\n    \n    // For JSON serialization\n    implementation(\"org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3\")\n    \n    // For coroutines\n    implementation(\"org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0\")\n}\n```\n\n## Multiplatform Support\n\nThe Kotlin SDK supports Kotlin Multiplatform (JVM, Wasm, iOS):\n\n```kotlin\nkotlin {\n    jvm()\n    js(IR) {\n        browser()\n        nodejs()\n    }\n    wasmJs()\n    \n    sourceSets {\n        commonMain.dependencies {\n            implementation(\"io.modelcontextprotocol:kotlin-sdk:0.7.2\")\n            implementation(\"org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0\")\n        }\n    }\n}\n```\n\n## Resource Lifecycle\n\nHandle resource updates and subscriptions:\n\n```kotlin\nserver.addResource(\n    uri = \"file:///dynamic/data\",\n    name = \"Dynamic Data\",\n    description = \"Frequently updated data\",\n    mimeType = \"application/json\"\n) { request -\u003e\n    // Provide current state\n    ReadResourceResult(\n        contents = listOf(\n            TextResourceContents(\n                text = getCurrentData(),\n                uri = request.uri,\n                mimeType = \"application/json\"\n            )\n        )\n    )\n}\n\n// Notify clients when resource changes\nserver.notifyResourceListChanged()\n```\n\n## Testing\n\nTest your MCP tools using Kotlin coroutines test utilities:\n\n```kotlin\nimport kotlinx.coroutines.test.runTest\nimport kotlin.test.Test\nimport kotlin.test.assertEquals\n\nclass ServerTest {\n    @Test\n    fun testSearchTool() = runTest {\n        val server = createTestServer()\n        \n        val request = CallToolRequest(\n            params = CallToolParams(\n                name = \"search\",\n                arguments = mapOf(\"query\" to \"test\", \"limit\" to 5)\n            )\n        )\n        \n        val result = server.callTool(request)\n        \n        assertEquals(false, result.isError)\n        assert(result.content.isNotEmpty())\n    }\n}\n```\n\n## Common Patterns\n\n### Logging\n\nUse structured logging with a Kotlin logging library:\n\n```kotlin\nimport io.github.oshai.kotlinlogging.KotlinLogging\n\nprivate val logger = KotlinLogging.logger {}\n\nserver.addTool(\n    name = \"logged-operation\",\n    description = \"Operation with logging\"\n) { request -\u003e\n    logger.info { \"Tool called with args: ${request.params.arguments}\" }\n    \n    try {\n        val result = performOperation(request)\n        logger.info { \"Operation succeeded\" }\n        result\n    } catch (e: Exception) {\n        logger.error(e) { \"Operation failed\" }\n        throw e\n    }\n}\n```\n\n### Configuration\n\nUse data classes for configuration:\n\n```kotlin\nimport kotlinx.serialization.Serializable\n\n@Serializable\ndata class ServerConfig(\n    val name: String = \"my-server\",\n    val version: String = \"1.0.0\",\n    val port: Int = 8080,\n    val enableTools: Boolean = true\n)\n\nfun loadConfig(): ServerConfig {\n    // Load from environment or config file\n    return ServerConfig(\n        name = System.getenv(\"SERVER_NAME\") ?: \"my-server\",\n        version = System.getenv(\"VERSION\") ?: \"1.0.0\"\n    )\n}\n```\n\n### Dependency Injection\n\nUse constructor injection for testability:\n\n```kotlin\nclass MyServer(\n    private val dataService: DataService,\n    private val config: ServerConfig\n) {\n    fun createServer() = Server(\n        serverInfo = Implementation(\n            name = config.name,\n            version = config.version\n        )\n    ) {\n        \"MCP Server with DI\"\n    }.apply {\n        addTool(\n            name = \"fetch-data\",\n            description = \"Fetch data using injected service\"\n        ) { request -\u003e\n            val data = dataService.fetchData()\n            CallToolResult(\n                content = listOf(TextContent(text = data))\n            )\n        }\n    }\n}\n```\n","description":"Best practices and patterns for building Model Context Protocol (MCP) servers in Kotlin using the official io.modelcontextprotocol:kotlin-sdk library.","import":{"commit_sha":"541b7819d8c3545c6df122491af4fa1eae415779","imported_at":"2026-05-18T20:05:35Z","license_text":"MIT License\n\nCopyright GitHub, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.","owner":"github","repo":"github/awesome-copilot","source_url":"https://github.com/github/awesome-copilot/blob/541b7819d8c3545c6df122491af4fa1eae415779/instructions/kotlin-mcp-server.instructions.md"},"manifest":{}},"content_hash":[173,37,15,75,6,167,254,92,95,109,71,20,198,158,112,108,207,158,108,194,74,250,105,205,34,247,56,15,80,120,36,146],"trust_level":"unsigned","yanked":false}
