Model Context Protocol (MCP) for Kotlin Multiplatform using the official modelcontextprotocol/kotlin-sdk (maintained with JetBrains). Covers building an MCP server that exposes tools/resources/prompts to an LLM client (Claude, Claude Code, MCP Inspector), building an MCP client that connects to an external MCP server from a Kotlin app, transport selection (STDIO, Streamable HTTP, SSE, WebSocket, ChannelTransport for tests), and Ktor wiring since the SDK does not bundle a Ktor engine transitiv...
Scanned 9/2/2026
Install to Claude Code
npx -y skills add ronjunevaldoz/kmp-agent-skills --skill kmp-mcp-sdk --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Kmp Mcp Sdk?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/ronjunevaldoz-kmp-mcp-sdk)More formats (shields.io, HTML) on the badges page.
---
name: kmp-mcp-sdk
description: >
Model Context Protocol (MCP) for Kotlin Multiplatform using the official
modelcontextprotocol/kotlin-sdk (maintained with JetBrains). Covers building
an MCP server that exposes tools/resources/prompts to an LLM client (Claude,
Claude Code, MCP Inspector), building an MCP client that connects to an
external MCP server from a Kotlin app, transport selection (STDIO,
Streamable HTTP, SSE, WebSocket, ChannelTransport for tests), and Ktor
wiring since the SDK does not bundle a Ktor engine transitively. Targets
JVM, Native, JS, and Wasm from commonMain.
license: Apache-2.0
metadata:
author: kmp-agent-skills
last-updated: '2026-08-23'
keywords:
- MCP
- Model Context Protocol
- kotlin-sdk
- modelcontextprotocol
- MCP server
- MCP client
- MCP tool
- MCP resource
- MCP prompt
- StreamableHttp
- StdioServerTransport
- Claude tool
- LLM tool
- agent tool
---
## When to Use This Skill
Use when you need to:
- Expose Kotlin/KMP functionality as tools, resources, or prompts an LLM client
(Claude Desktop, Claude Code, MCP Inspector) can call
- Build an MCP client that connects a Kotlin app to an external MCP server
- Choose a transport (stdio for CLI/editor-spawned tooling, Streamable HTTP for
a remote/hosted server, WebSocket for long-lived full-duplex sessions)
- Wire the SDK's server/client APIs onto a Ktor engine (the SDK doesn't pull
one in transitively)
**Trigger keywords:** MCP, Model Context Protocol, kotlin-sdk, modelcontextprotocol,
MCP server, MCP client, MCP tool, MCP resource, MCP prompt, expose tool to Claude,
Streamable HTTP, StdioServerTransport, WebSocket transport, agent tool, LLM tool.
**Freshness rule:** the SDK ships frequently (0.15.0 as of 2026-08-23) and the MCP
spec itself is still evolving (elicitation, completions are newer additions) —
recheck the [Maven Central badge](https://central.sonatype.com/artifact/io.modelcontextprotocol/kotlin-sdk)
and the real README before pinning a version or relying on a capability flag
this skill doesn't cover.
---
## Recommendation First
**Default to the server-only artifact + Streamable HTTP transport** for the common
case — exposing Kotlin functionality as tools an LLM client calls. Use STDIO
instead only when the process is spawned directly by the client (an editor
plugin, a CLI helper) with no networking involved. Pull in the client artifact
only if the Kotlin app itself needs to call *out* to another MCP server —
that's a different role from serving tools, don't default to the umbrella
`kotlin-sdk` artifact if only one side is ever used.
Why Streamable HTTP over SSE for a new project: the README states it directly —
"Prefer Streamable HTTP for new projects." SSE exists for backwards
compatibility with older MCP clients, not as the current default.
---
## Installation
Three artifacts — pick based on which side you're building:
```kotlin
dependencies {
// Both sides
implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion")
// Server only
implementation("io.modelcontextprotocol:kotlin-sdk-server:$mcpVersion")
// Client only
implementation("io.modelcontextprotocol:kotlin-sdk-client:$mcpVersion")
// In-process client/server testing, no networking — see Testing below
testImplementation("io.modelcontextprotocol:kotlin-sdk-testing:$mcpVersion")
}
```
**The SDK uses Ktor but does not add a Ktor engine transitively** — declare it
yourself:
```kotlin
dependencies {
// Server, hosting on CIO
implementation("io.ktor:ktor-server-cio:$ktorVersion")
implementation("io.modelcontextprotocol:kotlin-sdk-server:$mcpVersion")
// Client
implementation("io.ktor:ktor-client-cio:$ktorVersion")
implementation("io.modelcontextprotocol:kotlin-sdk-client:$mcpVersion")
}
```
In a KMP project, add it to `commonMain` — it works as both the common and
platform dependency:
```kotlin
commonMain {
dependencies {
implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion")
}
}
```
---
## Quickstart — Server Exposing a Tool
```kotlin
val mcpServer = Server(
serverInfo = Implementation(name = "example-server", version = "1.0.0"),
options = ServerOptions(
capabilities = ServerCapabilities(tools = ServerCapabilities.Tools(listChanged = true)),
)
)
mcpServer.addTool(
name = "example-tool",
description = "An example tool",
inputSchema = ToolSchema(
properties = buildJsonObject { put("input", buildJsonObject { put("type", "string") }) }
)
) { request ->
CallToolResult(content = listOf(TextContent("Hello, world!")))
}
embeddedServer(CIO, host = "127.0.0.1", port = 3000) {
mcpStreamableHttp { mcpServer }
}.start(wait = true)
```
**Don't `install(ContentNegotiation)` yourself** — `mcpStreamableHttp {}` (and
`mcp {}` for SSE) install it automatically with `McpJson`; a second install
logs a warning.
Verify with the MCP Inspector before wiring a real client:
```bash
npx -y @modelcontextprotocol/inspector
# connect to http://localhost:3000/mcp
```
## Quickstart — Client
```kotlin
val httpClient = HttpClient { install(SSE) }
val client = Client(clientInfo = Implementation(name = "example-client", version = "1.0.0"))
val transport = StreamableHttpClientTransport(client = httpClient, url = "http://localhost:3000/mcp")
client.connect(transport)
val tools = client.listTools().tools
```
---
## MCP Primitives
| Primitive | Server Role | Client Role |
|---|---|---|
| **Tools** | `addTool` — model-controlled functions the LLM invokes | `listTools()`/`callTool()` |
| **Resources** | `addResource` — application-driven context (files, API data) at a stable URI | `listResources()`/`readResource()` |
| **Prompts** | `addPrompt` — user-controlled templates (think slash commands) | `listPrompts()`/`getPrompt()` |
| **Sampling** | requests an LLM completion *from the client* — reverse direction | executes the model call, returns the result |
Set a capability's `listChanged = true` only if that catalog can change at
runtime and the server actually emits the matching `notifications/*/list_changed`
— declaring it without emitting the notification is a real client-facing lie
about what the server does.
---
## Transport Selection
| Transport | Use when | Notes |
|---|---|---|
| **STDIO** (`StdioServerTransport`/`StdioClientTransport`) | Process is spawned directly by the client (editor plugin, CLI helper) | No networking setup; tunnels over stdin/stdout |
| **Streamable HTTP** (`mcpStreamableHttp`/`StreamableHttpClientTransport`) | Remote/hosted server — the recommended default for new projects | Single endpoint, default path `/mcp`, optional JSON-only or SSE streaming |
| **SSE** (`mcp {}` / `Application.mcp {}`) | Backwards compatibility with older MCP clients only | Not the default for new projects — Streamable HTTP supersedes it |
| **WebSocket** (`WebSocketClientTransport`) | Long-lived, full-duplex sessions behind a proxy that already terminates WebSockets | Heavier setup than Streamable HTTP; only reach for it when notification volume genuinely needs it |
| **ChannelTransport** | Tests / local development | Coroutine-channel-based, no network — full-duplex client↔server without spinning up a real server |
**Browser-based clients (MCP Inspector) need CORS** — install Ktor's CORS
plugin allowing/exposing `Mcp-Session-Id` and `Mcp-Protocol-Version` headers;
the README documents the exact block, don't guess at the header names.
---
## Connecting a Real Client
```bash
# MCP Inspector
npx -y @modelcontextprotocol/inspector --connect http://localhost:3000
# Claude Code
claude mcp add --transport http kotlin-mcp http://localhost:3000
```
---
## Testing — `ChannelTransport` (no real networking)
`ChannelTransport.createLinkedPair()` returns a connected `(clientTransport,
serverTransport)` pair over Kotlin coroutine channels — connect both
concurrently, then exercise the client as if it were talking to a real server.
Real pattern, verified against the SDK's own test suite:
```kotlin
@OptIn(ExperimentalMcpApi::class)
class MyServerTest {
@Test
fun `server registers the tool with the correct schema`() = runTest {
val (clientTransport, serverTransport) = ChannelTransport.createLinkedPair()
val client = Client(
clientInfo = Implementation(name = "test-client", version = "1.0"),
options = ClientOptions(),
)
joinAll(
launch { client.connect(clientTransport) },
launch { myServer.createSession(serverTransport) },
)
val tools = client.listTools().tools
assertEquals(1, tools.size)
client.close()
}
}
```
`ChannelTransport` lives in a separate `kotlin-sdk-testing` artifact and is
marked `@ExperimentalMcpApi` — the `@OptIn` is required, not optional
boilerplate. This is the standard way to test tool/resource/prompt
registration without ever binding a real port.
---
## Common Anti-Patterns
- installing `ContentNegotiation` manually alongside `mcpStreamableHttp {}`/`mcp {}`
— both already install it with `McpJson`; a second install just logs a warning
for nothing
- pulling in the umbrella `kotlin-sdk` artifact when only one side (server or
client) is ever used — pick `kotlin-sdk-server`/`kotlin-sdk-client` instead
- assuming a Ktor engine ships transitively — it doesn't; declare
`ktor-server-cio`/`ktor-client-cio` (or your engine of choice) explicitly
- defaulting to SSE for a new server — Streamable HTTP is the current
recommendation; SSE exists only for older-client compatibility
- setting `listChanged = true` on a capability without ever emitting the
matching `notifications/*/list_changed` — the flag is a promise to the client,
not decoration
- testing against a real HTTP server when `ChannelTransport` already gives a
full-duplex client↔server pair with no networking
---
## Related Skills
- `kmp-network-layer` — the general Ktor client setup this SDK's client-side
transports build on
- `kmp-kotlin-rpc` — a different Kotlin-to-Kotlin RPC mechanism; use MCP
specifically when the peer is an LLM client, not another Kotlin service
- `kmp-ktor-auth-service` — if the MCP server needs authenticated access
- `kmp-feature-scaffold` — the module this SDK's server/client code slots into
---
## Output Style
When asked to add MCP support, respond in this order:
1. server or client (or both) — confirm which role is actually needed
2. the artifact(s) + Ktor engine dependency
3. transport choice with a one-line reason (Transport Selection table)
4. the minimal `addTool`/`addResource`/`addPrompt` registration for the actual ask
5. how to verify (`npx @modelcontextprotocol/inspector`)
---
## Changelog
| Date | Change |
|---|---|
| 2026-08-23 | Initial release. User asked whether this collection covers `modelcontextprotocol/kotlin-sdk` — confirmed a real, zero-coverage gap (no MCP mention anywhere in the repo). Verified real and official (maintained with JetBrains, 1,441 stars, actively pushed, Apache-2.0-equivalent, KMP-native across JVM/Native/JS/Wasm) via `gh api` against the real repo and README before writing. Covers server/client artifact selection, the undocumented "no transitive Ktor engine" gotcha, transport selection (STDIO/Streamable HTTP/SSE/WebSocket/ChannelTransport), and the `ContentNegotiation` double-install warning — all pulled from the real README, not assumed. |
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!