MCP Kotlin SDK: A Multiplatform Client and Server Stack for the Model Context Protocol
The official Kotlin SDK for Model Context Protocol servers and clients. Maintained in collaboration with JetBrains
At a glance
- What is it?
- The official Kotlin implementation of MCP ships client and server APIs for JVM, Native, JS and Wasm, with stdio, SSE, Streamable HTTP and WebSocket transports. It is a protocol library, not a framework, and it expects you to bring your own Ktor engine.
- Who is it for?
- Adopt it if you are writing Kotlin and want one codebase to expose tools, prompts and resources over stdio or Streamable HTTP, or if you need an MCP client inside a JVM service. Do not adopt it if you want a batteries-included server with routing, auth and process supervision, because this library stops at the protocol boundary.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Kotlin, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The gap this fills: MCP without leaving Kotlin
MCP defines a wire format and a lifecycle for exposing context to language models. Implementing that by hand means writing JSON-RPC framing, capability negotiation, session handling and four different transports before you expose a single tool. The Kotlin SDK exists so that step is a dependency rather than a project. The README frames the goal as separating the concern of providing context from the LLM interaction itself, and the API surface follows that: you register prompts, resources and tools, and the library handles the protocol conversation.
The audience is narrow and identifiable. Kotlin Multiplatform teams that already ship to JVM, Native, JS and Wasm and want the same server or client logic on each target. Android and backend JVM teams that want to call an MCP server from existing coroutine code. JetBrains is credited as a collaborator on the repository, which is consistent with the Kotlin-first design rather than a port of an existing implementation.
What it is not: a hosted gateway, a tool registry, or a runtime that manages server processes. Those decisions stay with you.
Three artifacts, and why the split matters
The SDK publishes three coordinates. io.modelcontextprotocol:kotlin-sdk carries both client and server APIs. kotlin-sdk-client and kotlin-sdk-server carry one side each. The README presents the umbrella artifact as the default and the split ones as an option when you only need one direction.
That split is worth taking seriously on constrained targets. A Wasm or JS client bundle that pulls in server-side session machinery pays for code it will never execute, and the README explicitly notes the umbrella artifact works as a common dependency in commonMain. If your shared module only ever connects outward, depending on kotlin-sdk-client keeps the server half out of the graph entirely. The same logic runs in reverse for a JVM service that only exposes tools.
The repository layout reinforces the separation: samples/simple-streamable-server is a full working project referenced from the server quickstart, which suggests sample projects are maintained per transport rather than as one monolith.
The mechanism: Client, Transport, and a connect call
The data flow is visible in the client quickstart. You construct an Implementation holding a name and version, pass it to Client as clientInfo, build a transport, then call client.connect(transport). From that point the client object exposes typed calls such as client.listTools(), which returns an object whose tools property you read. The README's own example prints that list.
Two design choices stand out. First, the client is transport-agnostic by construction: the transport is an argument to connect rather than a constructor parameter, so the same Client instance shape works against stdio, SSE, Streamable HTTP or WebSocket. Second, the API is coroutine-native. The quickstart wraps main in runBlocking and the README describes the message and lifecycle handling as coroutine-friendly, which is the natural shape for Kotlin but does mean every entry point is a suspend function.
On the server side the shape mirrors it. You build a Server with ServerOptions and a ServerCapabilities value, register handlers, and mount it with an mcpStreamableHttp extension on an embedded Ktor server. Tool results come back as CallToolResult carrying TextContent, and tool inputs are described with ToolSchema built from kotlinx.serialization JSON. Schema construction is therefore your responsibility, not something the library infers from a Kotlin function signature.
Getting it running: Gradle, Ktor engines, and the version variable
The install path is standard Maven Central. Add mavenCentral() to repositories, then implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion") to dependencies. The README leaves $mcpVersion as a variable you supply, and points at the Maven Central badge rather than naming a version in the text. The most recent release listed is 0.15.0, dated 2026-07-28, following 0.14.0 and 0.13.0 at roughly monthly intervals.
The step that catches people is Ktor. The README states plainly that the SDK uses Ktor but does not add Ktor engine dependencies transitively, so you declare them yourself. For a client that means something like implementation("io.ktor:ktor-client-cio:$ktorVersion") alongside kotlin-sdk-client. For a server, implementation("io.ktor:ktor-server-netty:$ktorVersion") alongside kotlin-sdk-server. The client quickstart also installs the SSE plugin on the HttpClient before constructing StreamableHttpClientTransport, which is a detail easy to miss when copying only the transport block.
Toolchain floors are stated in the badges: Kotlin 2.2 or newer, JVM 11 or newer. The multiplatform targets named are JVM, Wasm/JS and Native.
Transports and testing: stdio, HTTP, SSE, WebSocket, and an in-memory channel
The README documents five transports. STDIO for local process integration, Streamable HTTP as the current HTTP path, SSE for the older event-stream style, WebSocket, and ChannelTransport, which the table of contents labels as being for testing.
ChannelTransport is the interesting one and the README gives it the least space. An in-memory transport means you can exercise client and server logic in a unit test without binding a port or spawning a subprocess, which is the difference between a test suite that runs in milliseconds and one that leaks file handles. If you are writing an MCP server, that transport is the reason to structure your handlers so they do not assume a network context.
Having both SSE and Streamable HTTP in the same release is a maintenance signal. SSE is the older mechanism; Streamable HTTP is what the quickstarts use. New code should follow the quickstarts. The presence of the SSE transport is for talking to servers that have not moved.
Where it stops: no process management, no schema inference, no stability promise
The library implements the protocol. It does not solve the operational problems around it. There is nothing in the material about spawning and supervising stdio child processes, about authentication on the HTTP transport, about rate limiting, or about persistence of sessions across restarts. A production MCP server built on this SDK will need those layers written by hand or supplied by Ktor plugins you choose.
Schema authoring is manual. ToolSchema comes from kotlinx.serialization builders, so a tool's input contract is JSON you write, not a reflection over a Kotlin data class. That is explicit and predictable, and it also means nothing checks that your schema matches what your handler actually reads.
Version numbering is the third constraint. The releases listed are 0.13.0, 0.14.0 and 0.15.0, all below 1.0, arriving about once a month. Pre-1.0 with that cadence means you should expect source-level changes between minor versions and read the release notes before bumping. Pinning an exact version is the safer default than a dynamic range.
Finally, the repository's licence field is reported as NOASSERTION even though the README badge and the LICENSE reference point at Apache 2.0. That mismatch is worth resolving yourself before you rely on the terms, particularly if you redistribute the artifacts. This is not legal advice; read the LICENSE file in the repository.
The honest alternative: the official TypeScript SDK
The comparison that matters most is the official TypeScript SDK for MCP. It solves the same protocol problem and covers the same primitives: tools, resources, prompts, sampling, roots and the standard transports. The difference is not feature coverage, it is the runtime and the deployment shape.
TypeScript runs on Node, so a server is a process you start and a client is a process that connects. The Kotlin SDK compiles to JVM bytecode, native binaries, JS and Wasm. If your server needs to ship as a native binary with no runtime dependency, or if it needs to run inside an existing JVM application next to code that already holds your domain model, the Kotlin SDK is the only one of the two that reaches there. If your team is a JavaScript team and the MCP server is a thin wrapper around an HTTP API, the TypeScript SDK gets you there with fewer moving parts, because you are not also choosing a Ktor engine and a Kotlin toolchain version.
The second alternative is writing the protocol yourself against the MCP specification. That is defensible only if you need a transport or a capability the SDK does not cover, and the material does not indicate which those would be. For the four transports listed, the SDK is the shorter path.
Who should take the dependency
Take it if you are already in Kotlin. The multiplatform targets, the coroutine-shaped API and the client/server artifact split all assume a Kotlin codebase, and the cost of the dependency is one line in a Gradle file plus an explicit Ktor engine. Teams building a JVM service that needs to expose internal tools to an MCP client get the server API and an embedded Ktor mount point without writing JSON-RPC framing. Teams building a desktop or mobile client get the client API and a transport that fits whatever channel they already have.
Do not take it if you want a complete server product. There is no routing, no auth, no process supervision and no schema generation here, and the pre-1.0 release cadence means the surface can move under you between minor versions. If your MCP server is a thin shim over an existing HTTP service and your team writes JavaScript, the TypeScript SDK avoids an entire second toolchain.
Before you commit, check three concrete things. Confirm your Kotlin version is at least 2.2 and your JVM target at least 11, both from the README badges. Add your Ktor engine dependency explicitly and build once to confirm it resolves, since the SDK will not pull it in for you. Then open the repository's LICENSE file and reconcile it against the NOASSERTION licence field reported for the project, because the README badge alone is not a reliable statement of terms.
Editorial conclusion
Adopt it if you are writing Kotlin and want one codebase to expose tools, prompts and resources over stdio or Streamable HTTP, or if you need an MCP client inside a JVM service. Do not adopt it if you want a batteries-included server with routing, auth and process supervision, because this library stops at the protocol boundary. Before committing, verify three things in your own build: that your Kotlin toolchain is 2.2 or newer, that you have declared a Ktor engine explicitly rather than assuming it arrives transitively, and that the transport you need is present in the version you pin, since the release cadence between 0.13.0 and 0.15.0 is roughly monthly and the API surface has not settled.
Community notes