#162 ## Motivation and Context - update readme - add more snippets - add knit ## How Has This Been Tested? knit on CI ## Breaking Changes None ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [x] Documentation update ## Checklist - [x] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [x] My code follows the repository's style guidelines - [x] New and existing tests pass locally - [ ] I have added appropriate error handling - [x] I have added or updated documentation as needed --------- Co-authored-by: devcrocod <devcrocod@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
7eb2a93c46
commit
adc4150f1f
1 changed files with 1093 additions and 195 deletions
844
README.md
844
README.md
|
|
@ -1,6 +1,5 @@
|
|||
# MCP Kotlin SDK
|
||||
|
||||
|
||||
[](https://central.sonatype.com/artifact/io.modelcontextprotocol/kotlin-sdk)
|
||||
[](https://github.com/modelcontextprotocol/kotlin-sdk/actions/workflows/build.yml)
|
||||
[](https://github.com/modelcontextprotocol/kotlin-sdk/actions/workflows/conformance.yml)
|
||||
|
|
@ -9,100 +8,306 @@
|
|||
[](http://java.com)
|
||||
[](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
|
||||
|
||||
<!--- TOC -->
|
||||
|
||||
* [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)
|
||||
|
||||
<!--- END -->
|
||||
|
||||
## 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
|
||||
|
||||
## Samples
|
||||
|
||||
- [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.
|
||||
- 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
|
||||
|
||||
## Installation
|
||||
|
||||
Add the new repository to your build file:
|
||||
### Artifacts
|
||||
|
||||
- `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
|
||||
|
||||
### Gradle setup (JVM)
|
||||
|
||||
Add the Maven Central repository and the SDK dependency:
|
||||
|
||||
```kotlin
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
```
|
||||
|
||||
Add the dependency:
|
||||
|
||||
```kotlin
|
||||
dependencies {
|
||||
// See the badge above for the latest version
|
||||
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.:
|
||||
|
||||
Use _kotlin-sdk-client_ or _kotlin-sdk-server_ if you only need one side of the API:
|
||||
|
||||
```kotlin
|
||||
dependencies {
|
||||
// for client:
|
||||
implementation("io.ktor:ktor-client-cio:$ktorVersion")
|
||||
// for server:
|
||||
implementation("io.ktor:ktor-server-netty:$ktorVersion")
|
||||
|
||||
implementation("io.modelcontextprotocol:kotlin-sdk-client:$mcpVersion")
|
||||
implementation("io.modelcontextprotocol:kotlin-sdk-server:$mcpVersion")
|
||||
}
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
### 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")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 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 {
|
||||
// MCP client with Ktor
|
||||
implementation("io.ktor:ktor-client-cio:$ktorVersion")
|
||||
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")
|
||||
}
|
||||
```
|
||||
|
||||
## Quickstart
|
||||
|
||||
Let's create a simple MCP client and server to demonstrate the basic usage of the Kotlin SDK.
|
||||
|
||||
<!--- CLEAR -->
|
||||
|
||||
### 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<String>) = 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)
|
||||
}
|
||||
```
|
||||
|
||||
<!--- KNIT example-quickstart-client-01.kt -->
|
||||
|
||||
### 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<String>) {
|
||||
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)
|
||||
}
|
||||
```
|
||||
|
||||
<!--- KNIT example-quickstart-server-01.kt -->
|
||||
|
||||
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`<br/>`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.
|
||||
|
||||
<!--- INCLUDE
|
||||
import io.modelcontextprotocol.kotlin.sdk.server.Server
|
||||
import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.Implementation
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.PromptArgument
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.PromptMessage
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.Role
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.TextContent
|
||||
|
||||
fun main() {
|
||||
-->
|
||||
|
||||
```kotlin
|
||||
val server = Server(
|
||||
serverInfo = Implementation(
|
||||
name = "example-server",
|
||||
|
|
@ -110,97 +315,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")}"),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
<!--- SUFFIX
|
||||
}
|
||||
-->
|
||||
|
||||
<!--- KNIT example-server-prompts-01.kt -->
|
||||
|
||||
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.
|
||||
|
||||
<!--- INCLUDE
|
||||
import io.modelcontextprotocol.kotlin.sdk.server.Server
|
||||
import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.Implementation
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents
|
||||
|
||||
fun main() {
|
||||
-->
|
||||
|
||||
```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",
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Start server with stdio transport
|
||||
val transport = StdioServerTransport()
|
||||
server.connect(transport)
|
||||
```
|
||||
|
||||
### Using SSE Transport
|
||||
<!--- SUFFIX
|
||||
}
|
||||
-->
|
||||
|
||||
<!--- KNIT example-server-resources-01.kt -->
|
||||
|
||||
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.
|
||||
|
||||
<!--- INCLUDE
|
||||
import io.modelcontextprotocol.kotlin.sdk.server.Server
|
||||
import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions
|
||||
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 kotlinx.serialization.json.jsonPrimitive
|
||||
|
||||
fun main() {
|
||||
-->
|
||||
|
||||
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(
|
||||
tools = ServerCapabilities.Tools(listChanged = true),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
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."
|
||||
}
|
||||
}
|
||||
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")))
|
||||
}
|
||||
```
|
||||
|
||||
Inside a custom Ktor's `Route`:
|
||||
<!--- SUFFIX
|
||||
}
|
||||
-->
|
||||
|
||||
<!--- KNIT example-server-tools-01.kt -->
|
||||
|
||||
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).
|
||||
|
||||
<!--- INCLUDE
|
||||
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.types.CompleteRequest
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.CompleteResult
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.Implementation
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.Method
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities
|
||||
import kotlinx.io.asSink
|
||||
import kotlinx.io.asSource
|
||||
import kotlinx.io.buffered
|
||||
|
||||
suspend fun main() {
|
||||
-->
|
||||
|
||||
```kotlin
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.sse.SSE
|
||||
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() {
|
||||
install(SSE)
|
||||
val session = server.createSession(
|
||||
StdioServerTransport(
|
||||
inputStream = System.`in`.asSource().buffered(),
|
||||
outputStream = System.out.asSink().buffered()
|
||||
)
|
||||
)
|
||||
|
||||
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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
session.setRequestHandler<CompleteRequest>(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,
|
||||
),
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
<!--- SUFFIX
|
||||
}
|
||||
-->
|
||||
|
||||
<!--- KNIT example-server-util-completions-01.kt -->
|
||||
|
||||
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.
|
||||
|
||||
<!--- INCLUDE
|
||||
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.types.Implementation
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotification
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotificationParams
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities
|
||||
import kotlinx.io.asSink
|
||||
import kotlinx.io.asSource
|
||||
import kotlinx.io.buffered
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.put
|
||||
|
||||
suspend fun main() {
|
||||
-->
|
||||
|
||||
```kotlin
|
||||
val server = Server(
|
||||
serverInfo = Implementation("example-server", "1.0.0"),
|
||||
options = ServerOptions(
|
||||
capabilities = ServerCapabilities(
|
||||
logging = ServerCapabilities.Logging,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
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") },
|
||||
),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
<!--- SUFFIX
|
||||
}
|
||||
-->
|
||||
|
||||
<!--- KNIT example-server-util-logging-01.kt -->
|
||||
|
||||
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.
|
||||
|
||||
<!--- INCLUDE
|
||||
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.types.Implementation
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.ListResourcesRequest
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.ListResourcesResult
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.Method
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.Resource
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities
|
||||
import kotlinx.io.asSink
|
||||
import kotlinx.io.asSource
|
||||
import kotlinx.io.buffered
|
||||
|
||||
suspend fun main() {
|
||||
-->
|
||||
|
||||
```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<ListResourcesRequest>(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,
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
<!--- SUFFIX
|
||||
}
|
||||
-->
|
||||
|
||||
<!--- KNIT example-server-util-pagination-01.kt -->
|
||||
|
||||
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.
|
||||
|
||||
<!--- INCLUDE
|
||||
import io.modelcontextprotocol.kotlin.sdk.client.Client
|
||||
import io.modelcontextprotocol.kotlin.sdk.client.ClientOptions
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.Implementation
|
||||
|
||||
suspend fun main() {
|
||||
-->
|
||||
|
||||
```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()
|
||||
```
|
||||
|
||||
<!--- SUFFIX
|
||||
}
|
||||
-->
|
||||
|
||||
<!--- KNIT example-client-roots-01.kt -->
|
||||
|
||||
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.
|
||||
|
||||
<!--- INCLUDE
|
||||
import io.modelcontextprotocol.kotlin.sdk.client.Client
|
||||
import io.modelcontextprotocol.kotlin.sdk.client.ClientOptions
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageResult
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.Implementation
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.Method
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.Role
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.TextContent
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.putJsonObject
|
||||
|
||||
fun main() {
|
||||
-->
|
||||
|
||||
```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<CreateMessageRequest>(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"),
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
<!--- SUFFIX
|
||||
}
|
||||
-->
|
||||
|
||||
<!--- KNIT example-client-sampling-01.kt -->
|
||||
|
||||
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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue