diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 99ac3b85..539db2dd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -44,6 +44,7 @@ jobs: --rerun-tasks \ clean \ ktlintCheck \ + knit \ build \ -x :conformance-test:test \ koverLog koverHtmlReport \ diff --git a/README.md b/README.md index 7250499e..761afcbc 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ # MCP Kotlin SDK - [![Maven Central](https://img.shields.io/maven-central/v/io.modelcontextprotocol/kotlin-sdk.svg?label=Maven%20Central)](https://central.sonatype.com/artifact/io.modelcontextprotocol/kotlin-sdk) [![Build](https://github.com/modelcontextprotocol/kotlin-sdk/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/modelcontextprotocol/kotlin-sdk/actions/workflows/build.yml) [![Kotlin](https://img.shields.io/badge/kotlin-2.2+-blueviolet.svg?logo=kotlin)](http://kotlinlang.org) @@ -8,100 +7,306 @@ [![JVM](https://img.shields.io/badge/JVM-11+-red.svg?logo=jvm)](http://java.com) [![License](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) -Kotlin Multiplatform implementation of the [Model Context Protocol](https://modelcontextprotocol.io) (MCP), -providing both client and server capabilities for integrating with LLM surfaces across various platforms. +Kotlin Multiplatform SDK for the [Model Context Protocol](https://modelcontextprotocol.io). +It enables Kotlin applications targeting JVM, Native, JS, and Wasm to implement MCP clients and servers using a +standardized protocol interface. + +## Table of Contents + + + +* [Overview](#overview) +* [Installation](#installation) + * [Artifacts](#artifacts) + * [Gradle setup (JVM)](#gradle-setup-jvm) + * [Multiplatform](#multiplatform) + * [Ktor dependencies](#ktor-dependencies) +* [Quickstart](#quickstart) + * [Creating a Client](#creating-a-client) + * [Creating a Server](#creating-a-server) +* [Core Concepts](#core-concepts) + * [MCP Primitives](#mcp-primitives) + * [Capabilities](#capabilities) + * [Server Capabilities](#server-capabilities) + * [Client Capabilities](#client-capabilities) + * [Server Features](#server-features) + * [Prompts](#prompts) + * [Resources](#resources) + * [Tools](#tools) + * [Completion](#completion) + * [Logging](#logging) + * [Pagination](#pagination) + * [Client Features](#client-features) + * [Roots](#roots) + * [Sampling](#sampling) +* [Transports](#transports) + * [STDIO Transport](#stdio-transport) + * [Streamable HTTP Transport](#streamable-http-transport) + * [SSE Transport](#sse-transport) + * [WebSocket Transport](#websocket-transport) +* [Connecting your server](#connecting-your-server) +* [Examples](#examples) +* [Documentation](#documentation) +* [Contributing](#contributing) +* [License](#license) + + ## Overview The Model Context Protocol allows applications to provide context for LLMs in a standardized way, separating the concerns of providing context from the actual LLM interaction. -This SDK implements the MCP specification for Kotlin, -enabling you to build applications that can communicate using MCP on the JVM, WebAssembly and iOS. +This Kotlin SDK implements the MCP specification, making it easy to: -- Build MCP clients that can connect to any MCP server -- Create MCP servers that expose resources, prompts and tools -- Use standard transports like stdio, SSE, and WebSocket -- Handle all MCP protocol messages and lifecycle events +- Build MCP **clients** that can connect to any MCP server +- Create MCP **servers** that expose resources, prompts, and tools +- Target **JVM, Native, JS, and Wasm** from a single codebase +- Use standard transports like **stdio**, **SSE**, **Streamable HTTP**, and **WebSocket** +- Handle MCP protocol messages and lifecycle events with coroutine-friendly APIs -## Samples +## Installation -- [kotlin-mcp-server](./samples/kotlin-mcp-server): demonstrates a multiplatform (JVM, Wasm) MCP server setup with various features and transports. -- [weather-stdio-server](./samples/weather-stdio-server): shows how to build a Kotlin MCP server providing weather forecast and alerts using STDIO transport. -- [kotlin-mcp-client](./samples/kotlin-mcp-client): demonstrates building an interactive Kotlin MCP client that connects to an MCP server via STDIO and integrates with Anthropic’s API. +### Artifacts -## Installation +- `io.modelcontextprotocol:kotlin-sdk` – umbrella SDK (client + server APIs) +- `io.modelcontextprotocol:kotlin-sdk-client` – client-only APIs +- `io.modelcontextprotocol:kotlin-sdk-server` – server-only APIs -Add the new repository to your build file: +### Gradle setup (JVM) + +Add the Maven Central repository and the SDK dependency: ```kotlin repositories { mavenCentral() } + +dependencies { + // See the badge above for the latest version + implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion") +} ``` -Add the dependency: +Use _kotlin-sdk-client_ or _kotlin-sdk-server_ if you only need one side of the API: ```kotlin dependencies { - // See the badge above for the latest version - implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion") + implementation("io.modelcontextprotocol:kotlin-sdk-client:$mcpVersion") + implementation("io.modelcontextprotocol:kotlin-sdk-server:$mcpVersion") +} +``` + +### Multiplatform + +In a Kotlin Multiplatform project you can add the SDK to commonMain: + +```kotlin +commonMain { + dependencies { + // Works as a common dependency as well as the platform one + implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion") + } } ``` -MCP SDK uses [Ktor](https://ktor.io/), but does not come with a specific engine dependency. -You should add [Ktor client](https://ktor.io/docs/client-dependencies.html#engine-dependency) -and/or [Ktor server](https://ktor.io/docs/client-dependencies.html#engine-dependency) dependency -to your project yourself, e.g.: + +### Ktor dependencies + +The Kotlin MCP SDK uses [Ktor](https://ktor.io/), but it does not add Ktor engine dependencies transitively. +You need to declare +Ktor [client](https://ktor.io/docs/client-dependencies.html#engine-dependency)/[server](https://ktor.io/docs/server-dependencies.html) +dependencies yourself (or reuse the ones already used in your project), +for example: + ```kotlin dependencies { - // for client: + // MCP client with Ktor implementation("io.ktor:ktor-client-cio:$ktorVersion") - // for server: + implementation("io.modelcontextprotocol:kotlin-sdk-client:$mcpVersion") + + // MCP server with Ktor implementation("io.ktor:ktor-server-netty:$ktorVersion") - + implementation("io.modelcontextprotocol:kotlin-sdk-server:$mcpVersion") } ``` -## Quick Start +## Quickstart + +Let's create a simple MCP client and server to demonstrate the basic usage of the Kotlin SDK. + + ### Creating a Client +Create an MCP client that connects to a server via Streamable HTTP transport and lists available tools: + ```kotlin +import io.ktor.client.HttpClient +import io.ktor.client.plugins.sse.SSE import io.modelcontextprotocol.kotlin.sdk.client.Client -import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport -import io.modelcontextprotocol.kotlin.sdk.Implementation +import io.modelcontextprotocol.kotlin.sdk.client.StreamableHttpClientTransport +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import kotlinx.coroutines.runBlocking -val client = Client( - clientInfo = Implementation( - name = "example-client", - version = "1.0.0" +fun main(args: Array) = runBlocking { + val url = args.firstOrNull() ?: "http://localhost:3000/mcp" + + val httpClient = HttpClient { install(SSE) } + + val client = Client( + clientInfo = Implementation( + name = "example-client", + version = "1.0.0" + ) ) -) -val transport = StdioClientTransport( - inputStream = processInputStream, - outputStream = processOutputStream -) + val transport = StreamableHttpClientTransport( + client = httpClient, + url = url + ) -// Connect to server -client.connect(transport) + // Connect to server + client.connect(transport) -// List available resources -val resources = client.listResources() + // List available tools + val tools = client.listTools().tools -// Read a specific resource -val resourceContent = client.readResource( - ReadResourceRequest(uri = "file:///example.txt") -) + println(tools) +} ``` + + ### Creating a Server +Create an MCP server that exposes a simple tool and runs on an embedded Ktor server with SSE transport: + ```kotlin +import io.ktor.server.cio.CIO +import io.ktor.server.engine.embeddedServer import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions -import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport -import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.server.mcp +import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult +import io.modelcontextprotocol.kotlin.sdk.types.Implementation +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put + + +fun main(args: Array) { + val port = args.firstOrNull()?.toIntOrNull() ?: 3000 + 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 = port) { + mcp { + mcpServer + } + }.start(wait = true) +} +``` + + + +You can run the server and then connect to it using the client or test with the MCP Inspector: + +```bash +npx -y @modelcontextprotocol/inspector +``` + +In the inspector UI, connect to `http://localhost:3000`. + +## Core Concepts + +### MCP Primitives + +The MCP protocol defines core primitives that enable communication between servers and clients: + +| Primitive | Server Role | Client Role | Description | +|---------------|---------------------------------------------------|----------------------------------------|---------------------------------------------------| +| **Prompts** | Provides prompt templates with optional arguments | Requests and uses prompts | Interactive templates for LLM interactions | +| **Resources** | Exposes data sources (files, API responses, etc.) | Reads and subscribes to resources | Contextual data for augmenting LLM context | +| **Tools** | Defines executable functions | Calls tools to perform actions | Functions the LLM can invoke to take actions | +| **Sampling** | Requests LLM completions from client | Executes LLM calls and returns results | Server-initiated LLM requests (reverse direction) | + +### Capabilities + +Capabilities define what features a server or client supports. They are declared during initialization and determine +what operations are available. + +#### Server Capabilities + +Servers declare their capabilities to inform clients what features they provide: + +| Capability | Feature Flags | Description | +|----------------|-------------------------------|------------------------------------------------------------| +| `prompts` | `listChanged` | Prompt template management and notifications | +| `resources` | `subscribe`
`listChanged` | Resource exposure, subscriptions, and update notifications | +| `tools` | `listChanged` | Tool discovery, execution, and list change notifications | +| `logging` | - | Server logging to client console | +| `completions` | - | Argument autocompletion suggestions | +| `experimental` | Custom properties | Non-standard experimental features | + +#### Client Capabilities + +Clients declare their capabilities to inform servers what features they support: + +| Capability | Feature Flags | Description | +|----------------|-------------------|-------------------------------------------------------------| +| `sampling` | - | Client can sample from an LLM (execute model requests) | +| `roots` | `listChanged` | Client exposes root directories and can notify of changes | +| `elicitation` | - | Client can display schema/form dialogs for structured input | +| `experimental` | Custom properties | Non-standard experimental features | + +### Server Features +The `Server` API lets you wire prompts, resources, and tools with only a few lines of Kotlin. Each feature is registered +up front and then resolved lazily when a client asks for it, so your handlers stay small and suspendable. + +#### Prompts + +Prompts are user-controlled templates that clients discover via `prompts/list` and fetch with `prompts/get` when a user +chooses one (think slash commands or saved flows). They’re best for repeatable, structured starters rather than ad-hoc +model calls. + + + +```kotlin val server = Server( serverInfo = Implementation( name = "example-server", @@ -109,97 +314,498 @@ val server = Server( ), options = ServerOptions( capabilities = ServerCapabilities( - resources = ServerCapabilities.Resources( - subscribe = true, - listChanged = true - ) - ) + prompts = ServerCapabilities.Prompts(listChanged = true), + ), ) -){ - "This server provides example resources and demonstrates MCP capabilities." +) + +server.addPrompt( + name = "code-review", + description = "Ask the model to review a diff", + arguments = listOf( + PromptArgument(name = "diff", description = "Unified diff", required = true), + ), +) { request -> + GetPromptResult( + description = "Quick code review helper", + messages = listOf( + PromptMessage( + role = Role.User, + content = TextContent(text = "Review this change:\n${request.arguments?.get("diff")}"), + ), + ), + ) +} +``` + + + + + +Use prompts for anything that deserves a template: bug triage questions, onboarding checklists, or saved searches. Set +`listChanged = true` only if your prompt catalog can change at runtime and your server will emit +`notifications/prompts/list_changed` when it does. + +#### Resources + +Resources are application-driven context that clients discover via `resources/list` or `resources/templates/list`, then +fetch with `resources/read`. Register each one with a stable URI and return a `ReadResourceResult` when asked—contents +can be text or binary blobs. + + + +```kotlin +val server = Server( + serverInfo = Implementation( + name = "example-server", + version = "1.0.0" + ), + options = ServerOptions( + capabilities = ServerCapabilities( + resources = ServerCapabilities.Resources(subscribe = true, listChanged = true), + ), + ) +) -// Add a resource server.addResource( - uri = "file:///example.txt", - name = "Example Resource", - description = "An example text file", - mimeType = "text/plain" + uri = "note://release/latest", + name = "Release notes", + description = "Last deployment summary", + mimeType = "text/markdown", ) { request -> ReadResourceResult( contents = listOf( TextResourceContents( - text = "This is the content of the example resource.", + text = "Ship 42 reached production successfully.", uri = request.uri, - mimeType = "text/plain" - ) - ) + mimeType = "text/markdown", + ), + ), ) } +``` + + + + + +Resources can be static text, generated JSON, or blobs—anything the client can surface to the user or inject into the +model context. Set `subscribe = true` if you emit `notifications/resources/updated` for changes to specific URIs, and +`listChanged = true` if you’ll send `notifications/resources/list_changed` when the catalog itself changes. + +#### Tools + +Tools are model-controlled capabilities the client exposes to the model. Clients discover them via `tools/list`, invoke +them with `tools/call`, and your handlers receive JSON arguments, can emit streaming logs or progress, and return a +`CallToolResult`. Keep a human in the loop for sensitive operations. + + + +```kotlin +val server = Server( + serverInfo = Implementation( + name = "example-server", + version = "1.0.0" + ), + options = ServerOptions( + capabilities = ServerCapabilities( + tools = ServerCapabilities.Tools(listChanged = true), + ), + ) +) -// Start server with stdio transport -val transport = StdioServerTransport() -server.connect(transport) +server.addTool( + name = "echo", + description = "Return whatever the user sent back to them", +) { request -> + val text = request.arguments?.get("text")?.jsonPrimitive?.content ?: "(empty)" + CallToolResult(content = listOf(TextContent(text = "Echo: $text"))) +} ``` -### Using SSE Transport + + + + +Register as many tools as you need—long-running jobs can report progress via the request context, and tools can also +trigger sampling (see below) when they need the client’s LLM. Set `listChanged = true` only if your tool catalog can +change at runtime and your server will emit `notifications/tools/list_changed` when it does. + +#### Completion + +Completion provides argument suggestions for prompts or resource templates. Declare the `completions` capability and +handle `completion/complete` requests to return up to 100 ranked values (include `total`/`hasMore` if you paginate). + + -Directly in Ktor's `Application`: ```kotlin -import io.ktor.server.application.* -import io.modelcontextprotocol.kotlin.sdk.server.mcp +val server = Server( + serverInfo = Implementation( + name = "example-server", + version = "1.0.0" + ), + options = ServerOptions( + capabilities = ServerCapabilities( + completions = ServerCapabilities.Completions, + ), + ) +) -fun Application.module() { - mcp { - Server( - serverInfo = Implementation( - name = "example-sse-server", - version = "1.0.0" - ), - options = ServerOptions( - capabilities = ServerCapabilities( - prompts = ServerCapabilities.Prompts(listChanged = null), - resources = ServerCapabilities.Resources(subscribe = null, listChanged = null) - ) - ), - ) { - "This SSE server provides prompts and resources via Server-Sent Events." - } - } +val session = server.createSession( + StdioServerTransport( + inputStream = System.`in`.asSource().buffered(), + outputStream = System.out.asSink().buffered() + ) +) + +session.setRequestHandler(Method.Defined.CompletionComplete) { request, _ -> + val options = listOf("kotlin", "compose", "coroutine") + val matches = options.filter { it.startsWith(request.argument.value.lowercase()) } + + CompleteResult( + completion = CompleteResult.Completion( + values = matches.take(3), + total = matches.size, + hasMore = matches.size > 3, + ), + ) } ``` -Inside a custom Ktor's `Route`: + + + + +Use `context.arguments` to refine suggestions for dependent fields (e.g., framework list filtered by chosen language). + +#### Logging + +Logging lets the server stream structured log notifications to the client using RFC 5424 levels (`debug` → `emergency`). +Declare the `logging` capability; clients can raise the minimum level with `logging/setLevel`, and the server emits +`notifications/message` with severity, optional logger name, and JSON data. + + + ```kotlin -import io.ktor.server.application.* -import io.ktor.server.sse.SSE -import io.modelcontextprotocol.kotlin.sdk.server.mcp +val server = Server( + serverInfo = Implementation("example-server", "1.0.0"), + options = ServerOptions( + capabilities = ServerCapabilities( + logging = ServerCapabilities.Logging, + ), + ) +) -fun Application.module() { - install(SSE) - - routing { - route("myRoute") { - mcp { - Server( - serverInfo = Implementation( - name = "example-sse-server", - version = "1.0.0" - ), - options = ServerOptions( - capabilities = ServerCapabilities( - prompts = ServerCapabilities.Prompts(listChanged = null), - resources = ServerCapabilities.Resources(subscribe = null, listChanged = null) - ) - ), - ) { - "Connect via SSE to interact with this MCP server." - } - } - } - } +val session = server.createSession( + StdioServerTransport( + inputStream = System.`in`.asSource().buffered(), + outputStream = System.out.asSink().buffered() + ) +) + +session.sendLoggingMessage( + LoggingMessageNotification( + LoggingMessageNotificationParams( + level = LoggingLevel.Info, + logger = "startup", + data = buildJsonObject { put("message", "Server started") }, + ), + ), +) +``` + + + + + +Keep logs free of sensitive data, and expect clients to surface them in their own UI. + +#### Pagination + +List operations return paginated results with an opaque `nextCursor`, clients echo that cursor to fetch the next page. +Supported list calls: `resources/list`, `resources/templates/list`, `prompts/list`, and `tools/list`. +Treat cursors as opaque—don’t parse or persist them across sessions. + + + +```kotlin +val server = Server( + serverInfo = Implementation("example-server", "1.0.0"), + options = ServerOptions( + capabilities = ServerCapabilities( + resources = ServerCapabilities.Resources(), + ), + ) +) + +val session = server.createSession( + StdioServerTransport( + inputStream = System.`in`.asSource().buffered(), + outputStream = System.out.asSink().buffered() + ) +) + +val resources = listOf( + Resource(uri = "note://1", name = "Note 1", description = "First"), + Resource(uri = "note://2", name = "Note 2", description = "Second"), + Resource(uri = "note://3", name = "Note 3", description = "Third"), +) +val pageSize = 2 + +session.setRequestHandler(Method.Defined.ResourcesList) { request, _ -> + val start = request.params?.cursor?.toIntOrNull() ?: 0 + val page = resources.drop(start).take(pageSize) + val next = if (start + page.size < resources.size) (start + page.size).toString() else null + + ListResourcesResult( + resources = page, + nextCursor = next, + ) +} +``` + + + + + +Include `nextCursor` only when more items remain an absent cursor ends pagination. + +### Client Features + +Clients advertise their capabilities (roots, sampling, elicitation, etc.) during initialization. After that they can +serve requests from the server while still initiating calls such as `listTools` or `callTool`. + +#### Roots + +Roots let the client declare where the server is allowed to operate. Declare the `roots` capability, respond to +`roots/list`, and emit `notifications/roots/list_changed` if you set `listChanged = true`. URIs **must** be `file://` +paths. + + + +```kotlin +val client = Client( + clientInfo = Implementation("demo-client", "1.0.0"), + options = ClientOptions( + capabilities = ClientCapabilities(roots = ClientCapabilities.Roots(listChanged = true)), + ), +) + +client.addRoot( + uri = "file:///Users/demo/projects", + name = "Projects", +) +client.sendRootsListChanged() ``` + + + + + +Call `addRoot`/`removeRoot` whenever your file system view changes, and use `sendRootsListChanged()` to notify the +server. Keep root lists user-controlled and revoke entries that are no longer authorized. + +#### Sampling + +Sampling lets the server ask the client to call its preferred LLM. Declare the `sampling` capability (and +`sampling.tools` if you allow tool-enabled sampling), and handle `sampling/createMessage`. Keep a human in the loop for +approvals. + + + +```kotlin +val client = Client( + clientInfo = Implementation("demo-client", "1.0.0"), + options = ClientOptions( + capabilities = ClientCapabilities( + sampling = buildJsonObject { putJsonObject("tools") { } }, // drop tools if you don't support tool use + ), + ), +) + +client.setRequestHandler(Method.Defined.SamplingCreateMessage) { request, _ -> + val content = request.messages.lastOrNull()?.content + val prompt = if (content is TextContent) content.text else "your topic" + CreateMessageResult( + model = "gpt-4o-mini", + role = Role.Assistant, + content = TextContent(text = "Here is a short note about $prompt"), + ) +} +``` + + + + + +Inside the handler you can pick any model/provider, require approvals, or reject the request. If you don’t support tool +use, omit `sampling.tools` from capabilities. + +[//]: # (TODO: add elicitation section) + +[//]: # (#### Elicitation) + +## Transports + +All transports share the same API surface, so you can change deployment style without touching business logic. Pick the +transport that best matches where the server runs. + +### STDIO Transport + +`StdioClientTransport` and `StdioServerTransport` tunnel MCP messages over stdin/stdout—perfect for editor plugins or +CLI tooling that spawns a helper process. No networking setup is required. + +### Streamable HTTP Transport + +`StreamableHttpClientTransport` and the Ktor `streamableHttpApp()` helpers expose MCP over a single HTTP endpoint with +optional JSON-only or SSE streaming responses. This is the recommended choice for remote deployments and integrates +nicely with proxies or service meshes. + +### SSE Transport + +Server-Sent Events remain available for backwards compatibility with older MCP clients. Use `SseServerTransport` or the +SSE Ktor plugin when you need drop-in compatibility, but prefer Streamable HTTP for new projects. + +### WebSocket Transport + +`WebSocketClientTransport` plus the matching server utilities provide full-duplex, low-latency connections—useful when +you expect lots of notifications or long-running sessions behind a reverse proxy that already terminates WebSockets. + +## Connecting your server + +1. Start a sample HTTP server on port 3000: + + ```bash + ./gradlew :samples:kotlin-mcp-server:run + ``` + +2. Connect with the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) or Claude Desktop/Code: + + ```bash + npx -y @modelcontextprotocol/inspector --connect http://localhost:3000 + # or + claude mcp add --transport http kotlin-mcp http://localhost:3000 + ``` + +3. In the Inspector, confirm prompts, tools, resources, completions, and logs show up. Iterate locally until you’re + ready to host the server wherever you prefer. + +## Examples + +| Scenario | Description | Example | +|--------------------------|-----------------------------------------------------------------|--------------------------------------------------------------------------| +| Streamable HTTP server | Full MCP server with prompts, resources, tools, completions | [samples/kotlin-mcp-server](./samples/kotlin-mcp-server) | +| STDIO weather server | Minimal STDIO transport server exposing weather info and alerts | [samples/weather-stdio-server](./samples/weather-stdio-server) | +| Interactive STDIO client | MCP client that connects over STDIO and pipes requests to LLMs | [samples/kotlin-mcp-client](./samples/kotlin-mcp-client) | +| Streamable HTTP client | MCP client demo in a runnable notebook | [samples/notebooks/McpClient.ipynb](./samples/notebooks/McpClient.ipynb) | + +## Documentation + +- [API Reference](https://modelcontextprotocol.github.io/kotlin-sdk/) +- [Model Context Protocol documentation](https://modelcontextprotocol.io) +- [MCP specification](https://modelcontextprotocol.io/specification/latest) + ## Contributing Please see the [contribution guide](CONTRIBUTING.md) and the [Code of conduct](CODE_OF_CONDUCT.md) before contributing. diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 00000000..6a306c45 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,2 @@ +## Knit-generated sources +src/ diff --git a/docs/build.gradle.kts b/docs/build.gradle.kts new file mode 100644 index 00000000..a4811277 --- /dev/null +++ b/docs/build.gradle.kts @@ -0,0 +1,27 @@ +plugins { + kotlin("jvm") + alias(libs.plugins.knit) +} + +dependencies { + implementation(project(":kotlin-sdk")) + implementation(libs.ktor.server.cio) +} + +ktlint { + filter { + exclude("src/") + } +} + +tasks.clean { + dependsOn("knitClean") + delete("src") +} + +knit { + rootDir = project.rootDir + files = files(project.rootDir.resolve("README.md")) + defaultLineSeparator = "\n" + siteRoot = "" // Disable site root validation +} diff --git a/docs/moduledoc.md b/docs/moduledoc.md index 508fef98..bcb68249 100644 --- a/docs/moduledoc.md +++ b/docs/moduledoc.md @@ -1,21 +1,17 @@ # MCP Kotlin SDK -Kotlin SDK for the Model Context Protocol (MCP). -This is a Kotlin Multiplatform library that helps you build MCP clients and servers that speak the same protocol and -share the same types. -The SDK focuses on clarity, small building blocks, and first‑class coroutine support. +Kotlin Multiplatform implementation of the Model Context Protocol (MCP). The SDK focuses on clear, explicit APIs, +small building blocks, and first-class coroutine support so clients and servers share the same well-typed messages and +transports. -Use the umbrella `kotlin-sdk` artifact when you want a single dependency that brings the core types plus both client and -server toolkits. If you only need one side, depend on `kotlin-sdk-client` or `kotlin-sdk-server` directly. - -Gradle (Kotlin DSL): +Use the umbrella `kotlin-sdk` artifact to bring in everything at once, or depend on the focused modules directly: ```kotlin dependencies { - // Convenience bundle with everything you need to start + // All-in-one bundle implementation("io.modelcontextprotocol:kotlin-sdk:") - // Or pick modules explicitly + // Or pick sides explicitly implementation("io.modelcontextprotocol:kotlin-sdk-client:") implementation("io.modelcontextprotocol:kotlin-sdk-server:") } @@ -25,79 +21,73 @@ dependencies { ## Module kotlin-sdk-core -Foundational, platform‑agnostic pieces: +Foundation shared by both sides: -- Protocol data model and JSON serialization (kotlinx.serialization) -- Request/response and notification types used by both sides of MCP -- Coroutine‑friendly protocol engine and utilities -- Transport abstractions shared by client and server +- Protocol data model for MCP requests, results, notifications, capabilities, and content types. +- `McpJson` (kotlinx.serialization) with MCP-friendly defaults plus helpers for converting native values to JSON. +- Transport abstractions (`Transport`, `AbstractTransport`, `WebSocketMcpTransport`) and streaming `ReadBuffer`. +- `Protocol` base class that handles JSON-RPC framing, correlation, progress tokens, and capability assertions. -You typically do not use `core` directly in application code; it is pulled in by the client/server modules. Use it -explicitly if you only need the protocol types or plan to implement a custom transport. +Use `core` when you need the raw types or want to author a custom transport. Public APIs are explicit to keep the shared +surface stable across platforms. --- ## Module kotlin-sdk-client -High‑level client API for connecting to an MCP server and invoking its tools, prompts, and resources. Ships with several -transports: +High-level client for connecting to MCP servers and invoking their features: -- WebSocketClientTransport – low latency, full‑duplex -- SSEClientTransport – Server‑Sent Events over HTTP -- StdioClientTransport – CLI‑friendly stdio bridge -- StreamableHttpClientTransport – simple HTTP streaming +- `Client` runtime (or `mcpClient` helper) performs the MCP handshake and exposes `serverCapabilities`, + `serverVersion`, and `serverInstructions`. +- Typed operations for tools, prompts, resources, completion, logging, roots, sampling, and elicitation with capability + enforcement. +- Transports: `StdioClientTransport`, `SSEClientTransport`, `WebSocketClientTransport`, and `StreamableHttpClientTransport`, + plus Ktor client extensions for quick wiring. -A minimal client: +Minimal WebSocket client: ```kotlin -val client = Client( - clientInfo = Implementation(name = "sample-client", version = "1.0.0") +val client = mcpClient( + clientInfo = Implementation("sample-client", "1.0.0"), + clientOptions = ClientOptions(ClientCapabilities(tools = ClientCapabilities.Tools())), + transport = WebSocketClientTransport("ws://localhost:8080/mcp") ) -client.connect(WebSocketClientTransport("ws://localhost:8080/mcp")) - val tools = client.listTools() -val result = client.callTool( - name = "echo", - arguments = mapOf("text" to "Hello, MCP!") -) +val result = client.callTool("echo", mapOf("text" to "Hello, MCP!")) +println(result.content) ``` --- ## Module kotlin-sdk-server -Lightweight server toolkit for hosting MCP tools, prompts, and resources. It provides a small, composable API and -ready‑to‑use transports: +Server toolkit for exposing MCP tools, prompts, and resources: -- StdioServerTransport – integrates well with CLIs and editors -- SSE/WebSocket helpers for Ktor – easy HTTP deployment +- `Server` runtime coordinates sessions, initialization flow, and capability enforcement with registries for tools, + prompts, resources, and templates. +- Transports: `StdioServerTransport` for CLI/editor bridges; Ktor extensions (`mcp` for SSE + POST back-channel and + `mcpWebSocket` for WebSocket) for HTTP hosting. +- Built-in notifications for list changes and resource subscriptions when capabilities enable them. -Register tools and run over stdio: +Minimal Ktor SSE server: ```kotlin - -val server = Server( - serverInfo = Implementation(name = "sample-server", version = "1.0.0"), - options = ServerOptions(ServerCapabilities()) -) - -server.addTool( - name = "echo", - description = "Echoes the provided text" -) { request -> - // Build and return a CallToolResult from request.arguments - // (see CallToolResult and related types in kotlin-sdk-core) - /* ... */ +fun Application.module() { + mcp { + Server( + serverInfo = Implementation("sample-server", "1.0.0"), + options = ServerOptions(ServerCapabilities( + tools = ServerCapabilities.Tools(listChanged = true), + resources = ServerCapabilities.Resources(listChanged = true, subscribe = true), + )), + ) { + addTool(name = "echo", description = "Echo text back") { request -> + CallToolResult(content = listOf(TextContent("You said: ${request.params.arguments["text"]}"))) + } + } + } } - -// Bridge the protocol over stdio -val transport = StdioServerTransport( - inputStream = kotlinx.io.files.Path("/dev/stdin").source(), - outputStream = kotlinx.io.files.Path("/dev/stdout").sink() -) -// Start transport and wire it with the server using provided helpers in the SDK. ``` -For HTTP deployments, use the Ktor extensions included in the module to expose an MCP WebSocket or SSE endpoint with a -few lines of code. +Pick the module that matches your role, or use the umbrella artifact to get both sides with the shared core. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d5a19ee6..9c9bb194 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,6 +4,7 @@ kotlin = "2.2.21" dokka = "2.1.0" atomicfu = "0.29.0" ktlint = "14.0.1" +knit = "0.5.0" kover = "0.9.3" netty = "4.2.7.Final" mavenPublish = "0.35.0" @@ -22,7 +23,7 @@ mockk = "1.14.7" mokksy = "0.6.2" serialization = "1.9.0" slf4j = "2.0.17" -junit="6.0.1" +junit = "6.0.1" [libraries] # Plugins @@ -72,4 +73,5 @@ ktor-server-cio = { group = "io.ktor", name = "ktor-server-cio", version.ref = " kotlinx-binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binaryCompatibilityValidatorPlugin" } kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" } +knit = { id = "org.jetbrains.kotlinx.knit", version.ref = "knit" } openapi-generator = { id = "org.openapi.generator", version.ref = "openapi-generator" } diff --git a/knit.properties b/knit.properties new file mode 100644 index 00000000..1c740970 --- /dev/null +++ b/knit.properties @@ -0,0 +1,3 @@ +# Knit configuration +knit.dir=docs/src/main/kotlin/ +knit.package=io.modelcontextprotocol.kotlin.sdk.examples diff --git a/kotlin-sdk-client/Module.md b/kotlin-sdk-client/Module.md index 7154911e..259a8657 100644 --- a/kotlin-sdk-client/Module.md +++ b/kotlin-sdk-client/Module.md @@ -1,10 +1,74 @@ -# Module kotlin-sdk-client +# kotlin-sdk-client -The Model Context Protocol (MCP) Kotlin SDK client module provides a multiplatform implementation for connecting to and -interacting with MCP servers. +`kotlin-sdk-client` is the multiplatform MCP client for Kotlin. It handles the MCP handshake, enforces capabilities, and +gives you typed APIs to call tools, prompts, resources, completions, and logging endpoints on MCP servers. It reuses +protocol types from `kotlin-sdk-core` and ships with transports for CLI and Ktor-based networking. -## Features +## What the module provides -- Cross-platform support for JVM, WASM, JS, and Native -- Built-in support for multiple transport protocols (stdio, SSE, WebSocket) -- Type-safe API for resource management and server interactions +- **Client runtime**: The `Client` class (or `mcpClient` helper) wraps initialization, capability checks, and + request/notification plumbing. After connecting, it exposes `serverCapabilities`, `serverVersion`, and + `serverInstructions`. +- **Typed operations**: Convenience functions like `listTools`, `callTool`, `listResources`, `readResource`, + `listPrompts`, `complete`, `setLoggingLevel`, and subscription helpers for resources. +- **Capability enforcement**: Methods fail fast if the server does not advertise the needed capability; optional + strictness mirrors the protocol options used by the server SDK. +- **Transports**: Ready-to-use implementations for STDIO (CLI interoperability), Ktor SSE (`SSEClientTransport` with + POST back-channel), Ktor WebSocket, and Streamable HTTP for environments that prefer request/response streaming. +- **Ktor client integration**: Extension helpers wire MCP over Ktor client engines with minimal setup for SSE, + Streamable HTTP, or WebSocket. + +## Typical client setup + +1. Declare client capabilities with `ClientOptions` (e.g., tools, prompts, resources, logging, roots, sampling, + elicitation). +2. Create a transport suitable for your environment. +3. Connect using `mcpClient` or instantiate `Client` and call `connect(transport)`. +4. Use typed APIs to interact with the server; results and errors use the shared MCP types. + +### Minimal STDIO client + +```kotlin +val client = mcpClient( + clientInfo = Implementation(name = "demo-client", version = "1.0.0"), + clientOptions = ClientOptions( + capabilities = ClientCapabilities( + tools = ClientCapabilities.Tools(), + resources = ClientCapabilities.Resources(subscribe = true), + ) + ), + transport = StdioClientTransport(System.`in`.source(), System.out.sink()) +) + +val tools = client.listTools() +val result = client.callTool("hello", mapOf("name" to "Kotlin")) +println(result.content) +``` + +### Ktor-based transports + +- **SSE** (streaming GET + POST back-channel): create `SSEClientTransport(baseUrl, httpClient)`; sessions are keyed by + `sessionId` managed by the SDK. +- **WebSocket**: use `WebSocketClientTransport(url, httpClient)` to get bidirectional messaging in one connection. +- **Streamable HTTP**: `StreamableHttpClientTransport` enables MCP over streaming HTTP where WebSockets are unavailable. + +## Feature usage highlights + +- **Tools**: `callTool` accepts raw maps or `CallToolRequest`; metadata keys are validated per MCP rules. +- **Prompts & completion**: `getPrompt`, `listPrompts`, and `complete` wrap common prompt flows. +- **Resources**: `listResources`, `listResourceTemplates`, `readResource`, and `subscribeResource` cover discovery, + templating, and updates (when the server allows subscriptions). +- **Roots**: If enabled, register local roots with `addRoot`/`addRoots`; the client responds to `RootsList` requests + from the server. +- **Notifications**: Built-in handlers cover initialization, cancellation, progress, and roots list changes; you can + register more handlers via the underlying `Protocol` API. + +## Diagnostics and lifecycle + +- Capability assertions prevent calling endpoints the server does not expose; errors surface with clear messages. +- Logging uses `KotlinLogging`; enable DEBUG to inspect transport and handshake behavior. +- `close()` tears down the transport and cancels in-flight requests. + +Use this module whenever you need an MCP-aware Kotlin client—whether embedding in a CLI over STDIO or talking to remote +servers via Ktor SSE/WebSocket/streamable HTTP transports. Public APIs are explicit so downstream users can rely on +stable, well-typed surface areas across platforms. diff --git a/kotlin-sdk-core/Module.md b/kotlin-sdk-core/Module.md index 696360a6..b41cdc81 100644 --- a/kotlin-sdk-core/Module.md +++ b/kotlin-sdk-core/Module.md @@ -1,5 +1,47 @@ -# Module kotlin-sdk-core +# kotlin-sdk-core -The MCP Kotlin SDK Core module provides fundamental functionality for interacting with Model Context Protocol (MCP). -This module serves as the foundation for building MCP-enabled applications in Kotlin. +`kotlin-sdk-core` is the foundation of the MCP Kotlin SDK. It contains protocol message types, JSON handling, and +transport abstractions used by both client and server modules. No platform-specific code lives here; everything is +designed for Kotlin Multiplatform with explicit API mode enabled. +## What the module provides + +- **Protocol model**: Complete MCP data classes for requests, results, notifications, capabilities, tools, prompts, + resources, logging, completion, sampling, elicitation, and roots. DSL helpers (`*.dsl.kt`) let you build messages + fluently without repeating boilerplate. +- **JSON utilities**: A shared `McpJson` configuration (kotlinx.serialization) with MCP-friendly settings (ignore + unknown keys, no class discriminator). Helpers for converting maps to `JsonElement`, `EmptyJsonObject`, and + encoding/decoding JSON-RPC envelopes. +- **Transport abstractions**: `Transport` and `AbstractTransport` define the message pipeline, callbacks, and error + handling. `WebSocketMcpTransport` adds a shared WebSocket implementation for both client and server sides, and + `ReadBuffer` handles streaming JSON-RPC framing. +- **Protocol engine**: The `Protocol` base class manages request/response correlation, notifications, progress tokens, + and capability assertions. Higher-level modules extend it to become `Client` and `Server`. +- **Errors and safety**: Common exception types (`McpException`, parsing errors) plus capability enforcement hooks + ensure callers cannot use endpoints the peer does not advertise. + +## Typical usage + +- Import MCP types to define capabilities and message payloads for your own transports or custom integrations. +- Extend `Protocol` if you need a bespoke peer role while reusing correlation logic and JSON-RPC helpers. +- Use the DSL builders to construct well-typed requests (tools, prompts, resources, logging, completion) instead of + crafting raw JSON. + +### Example: building a tool call request + +```kotlin +val request = CallToolRequest { + name = "summarize" + arguments = mapOf("text" to "Hello MCP") +} +``` + +### Example: parsing a JSON-RPC message + +```kotlin +val message: JSONRPCMessage = McpJson.decodeFromString(jsonString) +``` + +Use this module when you need the raw building blocks of MCP—types, JSON config, and transport base classes—whether to +embed in another runtime, author new transports, or contribute higher-level features in the client/server modules. The +APIs are explicit to keep the shared surface stable for downstream users. diff --git a/kotlin-sdk-server/Module.md b/kotlin-sdk-server/Module.md index 94ae71ef..187335fc 100644 --- a/kotlin-sdk-server/Module.md +++ b/kotlin-sdk-server/Module.md @@ -1,14 +1,122 @@ -# Module kotlin-sdk-server +# kotlin-sdk-server -The server module provides core server-side functionality for the Model Context Protocol (MCP) Kotlin SDK. -This module includes essential parts for writing MCP server applications. +`kotlin-sdk-server` contains the server-side building blocks for Model Context Protocol (MCP) applications written in +Kotlin Multiplatform. It pairs the protocol types from `kotlin-sdk-core` with transport implementations and utilities +that make it straightforward to expose tools, prompts, and resources to MCP clients. -## Features +## What the module provides -* Server-side request handling -* Model context management -* Protocol implementation -* Integration utilities +- **Server runtime**: The `Server` class coordinates sessions, initialization flow, and capability enforcement. It + tracks active sessions and cleans them up when transports close. +- **Feature registries**: Helpers to register and remove tools, prompts, resources, and resource templates. Registry + listeners emit list-changed notifications when the capability is enabled. +- **Transports**: Ready-to-use transports for STDIO (CLI-style hosting), Ktor Server-Sent Events (SSE) with POST + back-channel, and Ktor WebSocket. All share the common `Transport` abstraction from the SDK. +- **Ktor integration**: Extension functions (`Routing.mcp`, `Routing.mcpWebSocket`, and `Application` variants) that + plug MCP into an existing Ktor server with minimal boilerplate. +- **Notifications**: Built-in support for resource subscription, resource-updated events, and feature list change + notifications when capabilities are set accordingly. -For detailed usage instructions and examples, please refer to -the [documentation](https://github.com/modelcontextprotocol/kotlin-sdk/blob/main/README.md). +## Typical server setup + +1. Declare capabilities your server supports using `ServerOptions`. Capabilities drive which handlers are installed and + whether list-change notifications are emitted. +2. Register features: tools, prompts, resources, and resource templates. Handlers run in suspending context and can be + removed or replaced at runtime. +3. Attach a transport and open a session via `createSession(transport)`. Multiple sessions can be active simultaneously. +4. Close the server to tear down transports and unsubscribe sessions. + +### Minimal STDIO server + +```kotlin +val server = Server( + serverInfo = Implementation(name = "demo-server", version = "1.0.0"), + options = ServerOptions( + capabilities = ServerCapabilities( + tools = ServerCapabilities.Tools(listChanged = true) + ) + ) +) { + addTool( + name = "hello", + description = "Greets the caller by name" + ) { request -> + val name = request.params.arguments["name"]?.jsonPrimitive?.content ?: "there" + CallToolResult( + content = listOf(TextContent("Hello, $name!")), + isError = false, + ) + } +} + +val transport = StdioServerTransport(System.`in`.source(), System.out.sink()) +server.createSession(transport) +``` + +### Hosting with Ktor (SSE) + +```kotlin +fun Application.module() { + mcp { + Server( + serverInfo = Implementation("ktor-sse", "1.0.0"), + options = ServerOptions( + ServerCapabilities( + prompts = ServerCapabilities.Prompts(listChanged = true), + resources = ServerCapabilities.Resources(listChanged = true, subscribe = true), + ) + ) + ) { + addPrompt( + name = "welcome", + description = "Explain how to use the server" + ) { + GetPromptResult( + description = "Welcome to the MCP server", + messages = listOf(CreateMessageResult.Message(TextContent("Try listing tools first"))) + ) + } + } + } +} +``` + +The SSE route exposes a streaming GET endpoint and a POST endpoint for client messages. Session affinity is maintained +by a `sessionId` query parameter managed by the SDK. + +### Hosting with Ktor (WebSocket) + +```kotlin +fun Application.module() { + mcpWebSocket { + Server( + serverInfo = Implementation("ktor-ws", "1.0.0"), + options = ServerOptions(ServerCapabilities(tools = ServerCapabilities.Tools())) + ) + } +} +``` + +`mcpWebSocket` installs Ktor WebSockets and wires message handling over a single bidirectional connection. + +## Feature registration details + +- **Tools**: `addTool` accepts a `Tool` descriptor or inline parameters plus a suspend handler. `removeTool` and + `addTools/removeTools` support dynamic updates. +- **Prompts**: `addPrompt` wires a provider that returns `GetPromptResult` and optional arguments metadata. +- **Resources**: `addResource` exposes static or generated content via `ReadResourceResult`; `addResources` + batch-registers. Templates and roots are also supported for structured discovery. +- **Instructions**: Provide static text or a function via `instructionsProvider` to send per-session usage guidance + during initialization. + +## Lifecycle and diagnostics + +- Sessions emit callbacks via `onConnect` and `onClose`. Deprecated `onInitialized` exists for backward compatibility; + prefer session-level hooks. +- The server enforces declared capabilities; attempting to register an unsupported feature throws with a descriptive + message. +- Logging uses `KotlinLogging`; avoid logging secrets. To inspect traffic, run the server with DEBUG level enabled. + +Use this module whenever you need to expose MCP features from a Kotlin service—whether embedding in a CLI via STDIO, +serving from Ktor over SSE/WebSocket, or composing transports for custom environments. Public APIs are explicit to keep +server-facing contracts stable for consumers. diff --git a/settings.gradle.kts b/settings.gradle.kts index d7ec54f8..cad1322f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -23,5 +23,6 @@ include( ":kotlin-sdk-server", ":kotlin-sdk", ":kotlin-sdk-test", + ":docs", ":conformance-test", )