diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 99ac3b85..52ca966f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -43,6 +43,7 @@ jobs: ./gradlew --no-daemon \ --rerun-tasks \ clean \ + knit \ ktlintCheck \ build \ -x :conformance-test:test \ diff --git a/README.md b/README.md index 7250499e..41f86922 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,202 +7,1571 @@ [![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 implementation of the [Model Context Protocol](https://modelcontextprotocol.io) (MCP), providing both client and server capabilities for integrating with LLM applications across JVM, JavaScript, and Native platforms. + +--- + +## Table of Contents + +- [What is MCP?](#what-is-mcp) +- [Why Use This SDK?](#why-use-this-sdk) +- [Requirements](#requirements) +- [Installation](#installation) +- [Quick Start](#quick-start) + - [Creating a Client](#creating-a-client) + - [Creating a Server](#creating-a-server) +- [Architecture](#architecture) +- [Transports](#transports) +- [Deploying to Claude Desktop](#deploying-to-claude-desktop) +- [API Reference](#api-reference) +- [Testing](#testing) +- [Production Deployment](#production-deployment) +- [Security](#security) +- [Troubleshooting](#troubleshooting) +- [Examples](#examples) +- [Contributing](#contributing) +- [License](#license) + +--- + +## What is MCP? + +The **Model Context Protocol (MCP)** is an open standard that enables AI applications to securely access data sources, +tools, and context in a standardized way. + +### Key Benefits + +- πŸ”„ **Interoperability**: Write tools once, use with Claude, GPT-4, Gemini, or any LLM +- 🎯 **Discovery**: Tools and resources are auto-discovered, not hard-coded +- πŸ” **Security**: Sandboxed execution with permission controls +- πŸ“¦ **Ecosystem**: Share servers with community, use 100+ existing integrations +- 🏒 **Enterprise**: Standardize AI context across your organization + +### MCP Concepts + +| Concept | Description | Example | +|---------|-------------|---------| +| **Server** | Provides tools, resources, or prompts | Database connector, file system access | +| **Client** | Consumes MCP capabilities | Claude Desktop, custom AI app | +| **Tool** | Function LLM can call | `get_weather(location)`, `send_email(to, body)` | +| **Resource** | Data LLM can read | File contents, database records, API responses | +| **Prompt** | Reusable prompt templates | Code review template, analysis framework | +| **Transport** | Communication channel | stdio, SSE, WebSocket | + +--- + +## Why Use This SDK? + +### Comparison with Other MCP SDKs + +| Feature | Kotlin SDK | TypeScript | Python | +|---------|------------|------------|---------| +| **Platforms** | JVM, JS, Native, Android, iOS | Node.js, Browser | CPython only | +| **Type Safety** | βœ… Compile-time | βœ… Compile-time | ⚠️ Runtime | +| **Async Model** | Coroutines | Promises | asyncio | +| **Mobile Support** | βœ… Full | ❌ No | ❌ No | +| **Interop** | Java, Kotlin, Scala | JavaScript, TypeScript | Python | + +**Choose Kotlin SDK when:** +- βœ… You have existing Kotlin/JVM infrastructure +- βœ… You need multiplatform deployment (Android, iOS, Desktop, Server) +- βœ… Type safety and compile-time checks are important +- βœ… You're building mobile AI applications +- βœ… You want coroutine-based async programming -## Overview +**Other official SDKs:** +- **TypeScript**: [@modelcontextprotocol/sdk](https://github.com/modelcontextprotocol/typescript-sdk) +- **Python**: [mcp](https://github.com/modelcontextprotocol/python-sdk) -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. +--- -- 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 +## Requirements -## Samples +### Minimum Versions -- [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. +- **Kotlin**: 2.0 or later +- **JVM**: 11 or later (for JVM targets) +- **Gradle**: 7.0+ or Maven 3.6+ + +### Supported Platforms + +| Platform | Support | Architectures | Notes | +|----------|---------|---------------|-------| +| **JVM** | βœ…| All | Java 11+ required | +| **Android** | βœ…| All | API 21+ (Lollipop) | +| **JavaScript** | βœ…| - | Node.js & Browser | +| **Wasm** | βœ…| wasm32 | Experimental | +| **iOS** | βœ…| arm64, x64 | iOS 14+ | +| **macOS** | βœ…| arm64, x64 | macOS 11+ | +| **Linux** | βœ…| x64, arm64 | glibc 2.27+ | +| **Windows** | βœ…| x64 | Windows 10+ | + +--- ## Installation -Add the new repository to your build file: +### Gradle (Kotlin DSL) ```kotlin repositories { mavenCentral() } + +dependencies { + // Core SDK (includes client + server) + implementation("io.modelcontextprotocol:kotlin-sdk:0.8.1") + + // Required: Choose a Ktor client engine + implementation("io.ktor:ktor-client-cio:3.0.0") + // OR implementation("io.ktor:ktor-client-okhttp:3.0.0") + // OR implementation("io.ktor:ktor-client-java:3.0.0") + + // Required for servers: Choose a Ktor server engine + implementation("io.ktor:ktor-server-netty:3.0.0") + // OR implementation("io.ktor:ktor-server-cio:3.0.0") +} ``` -Add the dependency: +
+Why separate Ktor engine dependencies? + +The SDK is engine-agnostic to give you flexibility: + +- **CIO**: Pure Kotlin, good default choice +- **OkHttp**: Battle-tested, extensive ecosystem +- **Java**: Uses Java's HttpClient (JDK 11+) +- **Netty**: High performance, production-grade + +See [Ktor engines documentation](https://ktor.io/docs/engines.html) for guidance. +
+ +### Gradle (Version Catalogs) + +
+Click to expand version catalog example + +In `gradle/libs.versions.toml`: +```properties +# Version definitions +mcp = "0.8.1" +ktor = "3.0.0" + +# Library definitions +mcp-sdk = { module = "io.modelcontextprotocol:kotlin-sdk", version.ref = "mcp" } +ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" } +ktor-server-netty = { module = "io.ktor:ktor-server-netty", version.ref = "ktor" } +``` +In `build.gradle.kts`: ```kotlin dependencies { - // See the badge above for the latest version - implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion") + implementation(libs.mcp.sdk) + implementation(libs.ktor.client.cio) + implementation(libs.ktor.server.netty) } ``` -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.: + +
+ +### Maven + +```xml + + + io.modelcontextprotocol + kotlin-sdk-jvm + 0.8.1 + + + io.ktor + ktor-client-cio-jvm + 3.0.0 + + +``` + +### Multiplatform Projects + ```kotlin -dependencies { - // for client: - implementation("io.ktor:ktor-client-cio:$ktorVersion") - // for server: - implementation("io.ktor:ktor-server-netty:$ktorVersion") - +kotlin { + jvm() + js(IR) { nodejs() } + iosArm64() + iosSimulatorArm64() + + sourceSets { + commonMain { + dependencies { + implementation("io.modelcontextprotocol:kotlin-sdk:0.8.1") + implementation("io.ktor:ktor-client-core:3.0.0") + } + } + + jvmMain { + dependencies { + implementation("io.ktor:ktor-client-cio:3.0.0") + } + } + + iosMain { + dependencies { + implementation("io.ktor:ktor-client-darwin:3.0.0") + } + } + } } ``` +--- + + + ## Quick Start ### Creating a Client -```kotlin +A client connects to MCP servers to discover and use tools, resources, and prompts. + + + +```kotlin +fun main() = runBlocking { + // 1. Create client + val client = Client( + clientInfo = Implementation( + name = "my-ai-app", + version = "1.0.0", + ), + ) -val client = Client( - clientInfo = Implementation( - name = "example-client", - version = "1.0.0" + // 2. Start MCP server process + val serverProcess = ProcessBuilder( + "npx", + "-y", + "@modelcontextprotocol/server-everything", + ).start() + + // 3. Connect via stdio transport + val transport = StdioClientTransport( + input = serverProcess.inputStream.asSource().buffered(), + output = serverProcess.outputStream.asSink().buffered(), ) -) -val transport = StdioClientTransport( - inputStream = processInputStream, - outputStream = processOutputStream -) + client.connect(transport) -// Connect to server -client.connect(transport) + // 4. Discover available tools + val tools = client.listTools() + println("Available tools: ${tools.tools.map { it.name }}") -// List available resources -val resources = client.listResources() + // 5. Call a tool + val result = client.callTool( + name = "get_weather", + arguments = mapOf("location" to "Tokyo"), + ) -// Read a specific resource -val resourceContent = client.readResource( - ReadResourceRequest(uri = "file:///example.txt") -) + println("Weather: ${result.content}") +} ``` + + ### Creating a Server -```kotlin +A server exposes tools, resources, or prompts to any MCP client. + + + +```kotlin +fun main() = runBlocking { + // 1. Create server with capabilities + val server = Server( + serverInfo = Implementation( + name = "my-tools-server", + version = "1.0.0", + ), + options = ServerOptions( + capabilities = ServerCapabilities( + tools = ServerCapabilities.Tools(listChanged = true), + resources = ServerCapabilities.Resources( + subscribe = true, + listChanged = true, + ), + ), + ), + ) { + "My MCP server providing useful tools and resources" + } -val server = Server( - serverInfo = Implementation( - name = "example-server", - version = "1.0.0" - ), - options = ServerOptions( - capabilities = ServerCapabilities( - resources = ServerCapabilities.Resources( - subscribe = true, - listChanged = true - ) + // 2. Add a tool + server.addTool( + name = "calculate", + description = "Performs basic calculations", + inputSchema = ToolSchema( + properties = buildJsonObject { + putJsonObject("operation") { + put("type", "string") + put( + "enum", + buildJsonArray { + add("add") + add("subtract") + add("multiply") + add("divide") + }, + ) + } + putJsonObject("a") { put("type", "number") } + putJsonObject("b") { put("type", "number") } + }, + required = listOf("operation", "a", "b"), + ), + ) { request -> + val args = request.arguments + ?: throw McpException(RPCError.ErrorCode.INVALID_PARAMS, "Missing arguments") + val op = args["operation"]?.jsonPrimitive?.content + val a = args["a"]?.jsonPrimitive?.double ?: 0.0 + val b = args["b"]?.jsonPrimitive?.double ?: 0.0 + + val result = when (op) { + "add" -> a + b + + "subtract" -> a - b + + "multiply" -> a * b + + "divide" -> if (b != 0.0) { + a / b + } else { + throw McpException(RPCError.ErrorCode.INVALID_PARAMS, "Division by zero") + } + + else -> throw McpException(RPCError.ErrorCode.INVALID_PARAMS, "Unknown operation: $op") + } + + CallToolResult(content = listOf(TextContent(result.toString()))) + } + + // 3. Add a resource + server.addResource( + uri = "config://app/settings", + name = "Application Settings", + description = "Current application configuration", + mimeType = "application/json", + ) { request -> + ReadResourceResult( + contents = listOf( + TextResourceContents( + text = """{"theme": "dark", "language": "en"}""", + uri = request.uri, + mimeType = "application/json", + ), + ), ) + } + + // 4. Start server with stdio transport + val transport = StdioServerTransport( + inputStream = System.`in`.asSource().buffered(), + outputStream = System.out.asSink().buffered(), ) -){ - "This server provides example resources and demonstrates MCP capabilities." + + server.createSession(transport) + + // Keep server running + kotlinx.coroutines.delay(Long.MAX_VALUE) } +``` -// Add a resource -server.addResource( - uri = "file:///example.txt", - name = "Example Resource", - description = "An example text file", - mimeType = "text/plain" -) { request -> - ReadResourceResult( - contents = listOf( - TextResourceContents( - text = "This is the content of the example resource.", - uri = request.uri, - mimeType = "text/plain" - ) - ) + + +--- + +## Architecture + +### Module Structure + +``` +kotlin-sdk (umbrella artifact) +β”œβ”€β”€ kotlin-sdk-core # Protocol types, base transports, JSON-RPC +β”œβ”€β”€ kotlin-sdk-client # Client implementation, client-specific transports +└── kotlin-sdk-server # Server implementation, server-specific transports +``` + +### When to Use Each Module + +| Module | Use When | Dependency | +|--------|----------|------------| +| `kotlin-sdk` | Building apps with both client + server (most common) | All modules | +| `kotlin-sdk-client` | Building pure MCP clients (AI apps) | Core + Client | +| `kotlin-sdk-server` | Building pure MCP servers (tool providers) | Core + Server | +| `kotlin-sdk-core` | Building custom transports or protocol extensions | Core only | + +### Architecture Patterns + +
+Pattern 1: Desktop Integration (Claude Desktop, Cursor, Zed) + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ AI Desktop App β”‚ (e.g., Claude Desktop) +β”‚ (MCP Client) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ stdio (subprocess) + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Your Server β”‚ (Kotlin JAR) +β”‚ (Stdio) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό + Your Services + (DB, APIs, etc) +``` + +**Use Case**: Personal productivity tools, local file access, IDE integrations + +**Deployment**: Package as executable JAR, configured in Claude Desktop settings +
+ +
+Pattern 2: Microservice Architecture + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” HTTP/SSE β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ AI Service β”‚ ◄─────────────► β”‚ MCP Server β”‚ +β”‚ (MCP Client) β”‚ or WebSocket β”‚ (Ktor) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό + Backend Services + (PostgreSQL, Redis, S3) +``` + +**Use Case**: Enterprise AI applications, multi-tenant SaaS, high availability + +**Deployment**: Kubernetes deployment with Ingress, Docker containers +
+ +
+Pattern 3: Embedded (Mobile) + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Mobile App β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ MCP Client β”‚ β”‚ +β”‚ β”‚ (in-process) β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ MCP Server β”‚ β”‚ +β”‚ β”‚ (embedded) β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ +β”‚ Local Storage/APIs β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +**Use Case**: Offline-first mobile apps, on-device AI + +**Deployment**: Kotlin Multiplatform shared library +
+ +--- + +## Transports + +MCP supports multiple transport mechanisms for different deployment scenarios. + +### Stdio Transport + +**Best for**: Desktop integration (Claude Desktop, Cursor, Zed), CLI tools + +```kotlin +// Server +fun main() = runBlocking { + val server = Server(...) + val transport = StdioServerTransport( + inputStream = System.`in`.asSource().buffered(), + outputStream = System.out.asSink().buffered() ) + server.createSession(transport) + kotlinx.coroutines.delay(Long.MAX_VALUE) } -// Start server with stdio transport -val transport = StdioServerTransport() -server.connect(transport) +// Client +val process = ProcessBuilder("java", "-jar", "server.jar").start() +val transport = StdioClientTransport( + input = process.inputStream.asSource().buffered(), + output = process.outputStream.asSink().buffered() +) +client.connect(transport) ``` -### Using SSE Transport +### SSE (Server-Sent Events) Transport + +**Best for**: Web-based servers, unidirectional streaming from server + +
+Simple SSE Server with Ktor Plugin -Directly in Ktor's `Application`: ```kotlin import io.ktor.server.application.* +import io.ktor.server.engine.* +import io.ktor.server.netty.* import io.modelcontextprotocol.kotlin.sdk.server.mcp -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) +fun main() { + embeddedServer(Netty, port = 8080) { + mcp { + Server( + serverInfo = Implementation("sse-server", "1.0.0"), + options = ServerOptions( + capabilities = ServerCapabilities( + tools = ServerCapabilities.Tools(listChanged = true) + ) ) - ), - ) { - "This SSE server provides prompts and resources via Server-Sent Events." + ) { "SSE MCP Server" } } - } + }.start(wait = true) } ``` -Inside a custom Ktor's `Route`: +Server will be available at `http://localhost:8080` +- SSE endpoint: `GET http://localhost:8080/sse` +- Message endpoint: `POST http://localhost:8080/message?sessionId={id}` +
+ +
+SSE Client + ```kotlin -import io.ktor.server.application.* +import io.ktor.client.* +import io.ktor.client.engine.cio.* +import io.modelcontextprotocol.kotlin.sdk.client.SseClientTransport + +val httpClient = HttpClient(CIO) +val transport = SseClientTransport( + client = httpClient, + urlString = "http://localhost:8080" +) +client.connect(transport) +``` +
+ +
+Custom SSE Routing + +```kotlin +import io.ktor.server.routing.* import io.ktor.server.sse.SSE -import io.modelcontextprotocol.kotlin.sdk.server.mcp fun Application.module() { install(SSE) routing { - route("myRoute") { + route("/custom-mcp") { mcp { Server( - serverInfo = Implementation( - name = "example-sse-server", - version = "1.0.0" - ), + serverInfo = Implementation("custom-sse", "1.0.0"), options = ServerOptions( capabilities = ServerCapabilities( - prompts = ServerCapabilities.Prompts(listChanged = null), - resources = ServerCapabilities.Resources(subscribe = null, listChanged = null) + tools = ServerCapabilities.Tools(listChanged = true) ) - ), - ) { - "Connect via SSE to interact with this MCP server." - } + ) + ) { "Custom path SSE server" } } } } } ``` -## Contributing +
-Please see the [contribution guide](CONTRIBUTING.md) and the [Code of conduct](CODE_OF_CONDUCT.md) before contributing. +### WebSocket Transport -## License +**Best for**: Bidirectional streaming, real-time applications -This project is licensed under the MIT Licenseβ€”see the [LICENSE](LICENSE) file for details. +```kotlin +// Server +import io.ktor.server.websocket.* + +fun Application.module() { + install(WebSockets) + + routing { + mcpWebSocket("/mcp") { + Server( + serverInfo = Implementation("ws-server", "1.0.0"), + options = ServerOptions( + capabilities = ServerCapabilities( + tools = ServerCapabilities.Tools(listChanged = true) + ) + ) + ) { "WebSocket MCP Server" } + } + } +} + +// Client +import io.ktor.client.plugins.websocket.* + +val httpClient = HttpClient(CIO) { + install(WebSockets) +} + +val transport = WebSocketClientTransport( + client = httpClient, + urlString = "ws://localhost:8080/mcp" +) +client.connect(transport) +``` + +### Custom Transports + +Implement `Transport` interface for custom communication channels: + +```kotlin +class CustomTransport : Transport { + override suspend fun start() { + // Initialize connection + } + + override suspend fun send(message: JSONRPCMessage) { + // Send message over your protocol + } + + override suspend fun close() { + // Cleanup resources + } +} +``` + +--- + +## Deploying to Claude Desktop + +[Claude Desktop](https://claude.ai/download) has built-in MCP support, making it the fastest way to use your server. + +### Step 1: Build Executable JAR + +```kotlin +// build.gradle.kts +plugins { + kotlin("jvm") version "2.0.0" + application +} + +application { + mainClass.set("com.example.MyServerKt") +} + +tasks.jar { + manifest { + attributes["Main-Class"] = "com.example.MyServerKt" + } + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + from(configurations.runtimeClasspath.get().map { + if (it.isDirectory) it else zipTree(it) + }) +} +``` + +Build the JAR: +```bash +./gradlew jar +# Output: build/libs/my-server.jar +``` + +### Step 2: Configure Claude Desktop + +Edit the configuration file: +- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` +- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` +- **Linux**: `~/.config/Claude/claude_desktop_config.json` + +```json +{ + "mcpServers": { + "my-kotlin-server": { + "command": "java", + "args": [ + "-jar", + "/absolute/path/to/build/libs/my-server.jar" + ], + "env": { + "API_KEY": "your-api-key-here", + "LOG_LEVEL": "INFO" + } + } + } +} +``` + +**Important**: Use absolute paths, not relative paths. + +### Step 3: Restart Claude Desktop + +Claude will automatically: +1. Start your server as a subprocess +2. Connect via stdio transport +3. Discover tools/resources/prompts +4. Make them available in conversations + +### Testing + +Ask Claude: +``` +What tools do you have available? +``` + +Claude should list your MCP tools. Try using one: +``` +Use my calculator tool to add 42 and 58 +``` + +### Troubleshooting Claude Desktop + +
+Server not starting? + +**Check server logs:** +- **macOS**: `~/Library/Logs/Claude/mcp-server-my-kotlin-server.log` +- **Windows**: `%APPDATA%\Claude\logs\mcp-server-my-kotlin-server.log` + +**Common issues:** +- JAR path must be absolute, not relative +- Java must be in PATH: `which java` (macOS/Linux) or `where java` (Windows) +- Verify JAR runs standalone: `java -jar /path/to/server.jar` +- Check JSON syntax in config file +
+ +
+Tools not appearing? + +- Server must write to stdout (not stderr) for stdio transport +- Verify capabilities are configured: `tools = ServerCapabilities.Tools(listChanged = true)` +- Check server logs for initialization errors +- Ensure `server.addTool(...)` is called before `createSession()` +
+ +
+Need to debug server? + +Add debug output to your server: +```kotlin +fun main() = runBlocking { + println("MCP Server starting...") // Visible in Claude logs + System.err.println("Debug: Server initialized") // Also visible + + val server = Server(...) + server.onRequest { request -> + System.err.println("Received: ${request.method}") + } + + // Rest of server setup +} +``` + +Or run server standalone to see output directly: +```bash +java -jar build/libs/server.jar +# Type JSON-RPC messages manually to test +``` +
+ +--- + +## API Reference + +### Client API + +The `Client` class provides methods to interact with MCP servers. + +#### Connection Management + +| Method | Description | +|--------|-------------| +| `Client(clientInfo: Implementation, options: ClientOptions = ClientOptions())` | Create a client instance | +| `suspend fun connect(transport: Transport)` | Connect to MCP server via transport | +| `suspend fun close()` | Close connection and cleanup resources | + +#### Resources + +| Method | Return Type | Description | +|--------|-------------|-------------| +| `listResources()` | `ListResourcesResult` | List all available resources | +| `readResource(request: ReadResourceRequest)` | `ReadResourceResult` | Read content of a specific resource | +| `subscribeResource(request: SubscribeRequest)` | `EmptyResult` | Subscribe to resource change notifications | +| `unsubscribeResource(request: UnsubscribeRequest)` | `EmptyResult` | Unsubscribe from resource notifications | + +#### Tools + +| Method | Return Type | Description | +|--------|-------------|-------------| +| `listTools()` | `ListToolsResult` | List all available tools | +| `callTool(name: String, arguments: Map, meta: Map = emptyMap())` | `CallToolResult` | Call a tool by name with arguments | + +#### Prompts + +| Method | Return Type | Description | +|--------|-------------|-------------| +| `listPrompts()` | `ListPromptsResult` | List all available prompts | +| `getPrompt(request: GetPromptRequest)` | `GetPromptResult` | Get a specific prompt with arguments | + +#### Other Operations + +| Method | Return Type | Description | +|--------|-------------|-------------| +| `complete(request: CompleteRequest)` | `CompleteResult` | Request argument/URI completions | +| `setLoggingLevel(level: LoggingLevel)` | `EmptyResult` | Set server logging level | +| `ping()` | `EmptyResult` | Ping server to check connectivity | + +### Server API + +The `Server` class provides methods to expose MCP capabilities. + +#### Initialization + +```kotlin +Server( + serverInfo: Implementation, + options: ServerOptions, + instructionsProvider: (() -> String)? = null, + block: Server.() -> Unit = {} +) +``` + +#### Resource Management + +| Method | Description | +|--------|-------------| +| `addResource(uri, name, description, mimeType, handler)` | Add a resource with fixed URI | +| `addResourceTemplate(uriTemplate, name, description, mimeType, handler)` | Add resource template with URI pattern | +| `suspend fun sendResourceUpdated(uri: String)` | Notify clients that resource changed | +| `suspend fun sendResourceListChanged()` | Notify clients that resource list changed | + +#### Tool Management + +| Method | Description | +|--------|-------------| +| `addTool(name, description, inputSchema, handler)` | Add a tool handler | +| `suspend fun sendToolListChanged()` | Notify clients that tool list changed | + +#### Prompt Management + +| Method | Description | +|--------|-------------| +| `addPrompt(name, description, arguments, handler)` | Add a prompt template handler | +| `suspend fun sendPromptListChanged()` | Notify clients that prompt list changed | + +#### Session Management + +| Method | Description | +|--------|-------------| +| `suspend fun createSession(transport: Transport): ServerSession` | Create new session with transport | +| `fun onClose(handler: () -> Unit)` | Register close handler | + +### Capabilities Configuration + +#### Client Capabilities + +```kotlin +val client = Client( + clientInfo = Implementation("my-client", "1.0.0"), + options = ClientOptions( + capabilities = ClientCapabilities( + roots = ClientCapabilities.Roots(listChanged = true), + sampling = ClientCapabilities.Sampling() + ) + ) +) +``` + +#### Server Capabilities + +```kotlin +val server = Server( + serverInfo = Implementation("my-server", "1.0.0"), + options = ServerOptions( + capabilities = ServerCapabilities( + resources = ServerCapabilities.Resources( + subscribe = true, + listChanged = true + ), + tools = ServerCapabilities.Tools( + listChanged = true + ), + prompts = ServerCapabilities.Prompts( + listChanged = true + ), + logging = ServerCapabilities.Logging() + ) + ) +) +``` + +--- + +## Testing + +### Unit Testing Servers + +```kotlin +import io.modelcontextprotocol.kotlin.sdk.test.InMemoryTransport +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +class MyServerTest { + @Test + fun `test calculator tool`() = runTest { + // Arrange + val server = Server( + serverInfo = Implementation("test-server", "1.0.0"), + options = ServerOptions( + capabilities = ServerCapabilities( + tools = ServerCapabilities.Tools(listChanged = true) + ) + ) + ) + + server.addTool("add", "Adds two numbers") { request -> + val a = request.arguments?.get("a")?.jsonPrimitive?.double ?: 0.0 + val b = request.arguments?.get("b")?.jsonPrimitive?.double ?: 0.0 + CallToolResult(content = listOf(TextContent((a + b).toString()))) + } + + val transport = InMemoryTransport() + server.createSession(transport.serverEnd) + + val client = Client(Implementation("test-client", "1.0.0")) + client.connect(transport.clientEnd) + + // Act + val result = client.callTool("add", mapOf("a" to 5, "b" to 3)) + + // Assert + val content = result.content.first() as TextContent + assertEquals("8.0", content.text) + } +} +``` + +### Test Utilities + +```kotlin +dependencies { + testImplementation("io.modelcontextprotocol:kotlin-sdk-test:0.8.1") +} +``` + +Available utilities: +- `InMemoryTransport` - Fast in-process transport for unit tests + +--- + +## Production Deployment + +### Error Handling + +```kotlin +server.addTool("divide", "Divides two numbers") { request -> + try { + val a = request.arguments?.get("a")?.jsonPrimitive?.double + ?: throw McpException.InvalidParams("Missing parameter 'a'") + val b = request.arguments?.get("b")?.jsonPrimitive?.double + ?: throw McpException.InvalidParams("Missing parameter 'b'") + + if (b == 0.0) { + throw McpException.InvalidParams("Cannot divide by zero") + } + + CallToolResult(content = listOf(TextContent((a / b).toString()))) + + } catch (e: McpException) { + throw e // Re-throw MCP protocol errors + } catch (e: Exception) { + logger.error(e) { "Tool execution failed" } + throw McpException.InternalError("Calculation failed: ${e.message}") + } +} +``` + +### Observability + +
+Structured Logging + +```kotlin +import io.github.oshai.kotlinlogging.KotlinLogging + +val logger = KotlinLogging.logger {} + +server.onInitialized { + logger.info { "Server initialized and ready" } +} + +server.addTool("example", "Example tool") { request -> + logger.info { "Tool called with args: ${request.arguments}" } + // Tool implementation + CallToolResult(content = listOf(TextContent("Result"))) +} +``` +
+ +
+Metrics with Micrometer + +```kotlin +import io.micrometer.core.instrument.MeterRegistry +import io.micrometer.prometheus.PrometheusConfig +import io.micrometer.prometheus.PrometheusMeterRegistry +import kotlin.system.measureTimeMillis + +val registry = PrometheusMeterRegistry(PrometheusConfig.DEFAULT) + +server.addTool("example", "Example tool") { request -> + val duration = measureTimeMillis { + // Tool implementation + } + + registry.counter("mcp.tools.calls", "tool", "example").increment() + registry.timer("mcp.tools.duration", "tool", "example") + .record(java.time.Duration.ofMillis(duration)) + + CallToolResult(content = listOf(TextContent("Result"))) +} +``` +
+ +
+Distributed Tracing + +```kotlin +import io.opentelemetry.api.GlobalOpenTelemetry + +val tracer = GlobalOpenTelemetry.getTracer("mcp-server") + +server.addTool("example", "Example tool") { request -> + val span = tracer.spanBuilder("mcp.tool.example") + .setAttribute("tool.name", "example") + .startSpan() + + try { + // Tool implementation + CallToolResult(content = listOf(TextContent("Result"))) + } finally { + span.end() + } +} +``` +
+ +### Deployment Patterns + +
+Docker Container + +```dockerfile +FROM eclipse-temurin:11-jre-alpine + +WORKDIR /app +COPY build/libs/server.jar server.jar + +EXPOSE 8080 + +ENTRYPOINT ["java", "-jar", "server.jar"] +``` + +```bash +docker build -t my-mcp-server . +docker run -p 8080:8080 -e API_KEY=secret my-mcp-server +``` +
+ +
+Kubernetes Deployment + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mcp-server +spec: + replicas: 3 + selector: + matchLabels: + app: mcp-server + template: + metadata: + labels: + app: mcp-server + spec: + containers: + - name: mcp-server + image: myorg/mcp-server:1.0.0 + ports: + - containerPort: 8080 + env: + - name: API_KEY + valueFrom: + secretKeyRef: + name: mcp-secrets + key: api-key + resources: + requests: + memory: "512Mi" + cpu: "500m" + limits: + memory: "1Gi" + cpu: "1000m" +--- +apiVersion: v1 +kind: Service +metadata: + name: mcp-server +spec: + selector: + app: mcp-server + ports: + - port: 80 + targetPort: 8080 + type: LoadBalancer +``` +
+ +--- + +## Security + +### Authentication + +```kotlin +// Custom authentication middleware +class AuthenticatedServer( + serverInfo: Implementation, + options: ServerOptions, + private val validateToken: (String) -> Boolean +) : Server(serverInfo, options) { + + init { + onRequest { request -> + val token = request.meta?.get("authorization")?.jsonPrimitive?.content + ?: throw McpException.InvalidParams("Missing authorization") + + if (!validateToken(token)) { + throw McpException.InvalidParams("Invalid authorization") + } + } + } +} + +// Client with authentication +val client = Client(Implementation("secure-client", "1.0.0")) +client.setDefaultMeta(mapOf( + "authorization" to System.getenv("MCP_TOKEN") +)) +``` + +### Tool Sandboxing + +```kotlin +// Restrict dangerous operations +val ALLOWED_COMMANDS = setOf("ls", "cat", "grep") + +server.addTool("execute", "Executes shell command") { request -> + val command = request.arguments?.get("command")?.jsonPrimitive?.content + ?: throw McpException.InvalidParams("Missing command") + + val parts = command.split(" ") + if (parts.first() !in ALLOWED_COMMANDS) { + throw McpException.InvalidParams("Command not allowed: ${parts.first()}") + } + + // Execute in restricted environment + val result = ProcessBuilder(parts).start().inputStream.bufferedReader().readText() + CallToolResult(content = listOf(TextContent(result))) +} +``` + +### TLS Configuration + +```kotlin +import io.ktor.network.tls.certificates.* +import java.io.File + +embeddedServer(Netty, port = 8080) { + // Production TLS + sslConnector( + keyStore = generateCertificate( + file = File("keystore.jks"), + keyAlias = "mcp-server", + keyPassword = "changeit", + jksPassword = "changeit" + ), + keyAlias = "mcp-server", + keyStorePassword = { "changeit".toCharArray() }, + privateKeyPassword = { "changeit".toCharArray() } + ) { + port = 8443 + } + + mcp { /* server config */ } +}.start(wait = true) +``` + +--- + +## Troubleshooting + +### Common Issues + +
+"Could not find Ktor engine" Error + +**Error:** +``` +java.lang.ClassNotFoundException: io.ktor.client.engine.cio.CIOEngine +``` + +**Solution:** +Add a Ktor client engine dependency: +```kotlin +dependencies { + implementation("io.ktor:ktor-client-cio:3.0.0") +} +``` +
+ +
+Transport Connection Failures + +**Symptoms:** `TimeoutException`, `ConnectionRefusedException` + +**Checklist:** +- βœ… Server process is running before client connects +- βœ… Correct transport type (stdio, SSE, WebSocket) +- βœ… Firewall allows connections (for network transports) +- βœ… Correct host/port in client configuration +- βœ… Server logs show successful startup + +**Debug stdio transport:** +```kotlin +// Server: Add debug output +fun main() = runBlocking { + System.err.println("Server starting...") + val server = Server(...) + System.err.println("Server ready") + // ... rest of setup +} +``` + +**Debug network transports:** +```bash +# Test SSE endpoint +curl http://localhost:8080/sse + +# Test WebSocket endpoint +wscat -c ws://localhost:8080/mcp +``` +
+ +
+Tools Not Discovered + +**Issue:** `client.listTools()` returns empty list + +**Solutions:** +1. Verify capabilities are configured: +```kotlin +ServerCapabilities( + tools = ServerCapabilities.Tools(listChanged = true) // Required +) +``` + +2. Ensure tools are added before creating session: +```kotlin +server.addTool(...) // BEFORE this: +server.createSession(transport) +``` + +3. Check tool registration: +```kotlin +server.addTool("my-tool", ...) { request -> + println("Tool called!") // Debug output + CallToolResult(...) +} +``` +
+ +
+Serialization Errors + +**Error:** +``` +kotlinx.serialization.SerializationException: Unexpected JSON token +``` + +**Common causes:** +- Invalid JSON in tool arguments +- Incorrect `inputSchema` type definitions +- Missing required fields in response objects + +**Solution:** +Validate input schema matches arguments: +```kotlin +server.addTool( + name = "example", + inputSchema = ToolSchema( + properties = buildJsonObject { + putJsonObject("name") { + put("type", "string") // Must match argument type + } + }, + required = listOf("name") // Mark required fields + ) +) { request -> + // Validate manually if needed + val name = request.arguments?.get("name")?.jsonPrimitive?.content + ?: throw McpException.InvalidParams("Missing required field: name") + + CallToolResult(...) +} +``` +
+ +### Getting Help + +- πŸ“š **Specification**: [spec.modelcontextprotocol.io](https://spec.modelcontextprotocol.io) +- πŸ’¬ **Discord**: [MCP Community](https://discord.gg/modelcontextprotocol) +- πŸ› **Issues**: [GitHub Issues](https://github.com/modelcontextprotocol/kotlin-sdk/issues) +- πŸ” **Inspector**: [MCP Inspector](https://github.com/modelcontextprotocol/inspector) - Debug tool for testing servers + +--- + +## Migration Guides + +### From Direct LLM APIs + +
+Migrating from Anthropic SDK + +**Before (Direct API):** +```kotlin +val anthropic = Anthropic(apiKey) +val response = anthropic.messages.create( + model = "claude-3-5-sonnet-20241022", + maxTokens = 1024, + tools = listOf( + Tool.builder() + .name("get_weather") + .description("Get weather") + .inputSchema(/* manual schema */) + .build() + ), + messages = listOf(/* messages */) +) + +// Manual tool execution +if (response.stopReason == StopReason.TOOL_USE) { + val toolCall = response.content.first { it.isToolUse() }.toolUse() + when (toolCall.name) { + "get_weather" -> { + val result = WeatherAPI.fetch(/* hard-coded */) + // Send back to API... + } + } +} +``` + +**After (MCP):** +```kotlin +// 1. Create reusable MCP server (once) +val mcpServer = Server(...) +mcpServer.addTool("get_weather", ...) { request -> + CallToolResult(content = listOf(TextContent(WeatherAPI.fetch(...)))) +} + +// 2. Connect MCP client +val mcpClient = Client(...) +mcpClient.connect(transport) + +// 3. Tools auto-discovered +val tools = mcpClient.listTools() // Discovers get_weather + +// 4. Use with Anthropic (or any LLM) +val anthropic = Anthropic(apiKey) +val response = anthropic.messages.create( + tools = tools.toAnthropicFormat(), // Auto-converted + // ... rest +) + +// 5. Protocol-handled execution +if (response.stopReason == StopReason.TOOL_USE) { + val toolCall = response.content.first { it.isToolUse() }.toolUse() + val result = mcpClient.callTool(toolCall.name, toolCall.input) +} +``` + +**Benefits:** +- βœ… Tools work with any LLM (Claude, GPT-4, Gemini) +- βœ… Reusable across applications +- βœ… Shareable with team/community +- βœ… Standardized discovery and execution +
+ +### From TypeScript SDK + +
+Key Differences + +| Concept | TypeScript | Kotlin | +|---------|-----------|--------| +| **Async Model** | `async/await` | `suspend fun` + coroutines | +| **Error Handling** | `try/catch` + Promises | `try/catch` + structured concurrency | +| **Types** | Interfaces | Data classes | +| **Null Safety** | `Type \| undefined` | `Type?` with null safety | +| **Imports** | `import { Client } from '@modelcontextprotocol/sdk'` | `import io.modelcontextprotocol.kotlin.sdk.client.Client` | + +**TypeScript:** +```typescript +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; + +const client = new Client({ + name: "my-client", + version: "1.0.0" +}); + +await client.connect(transport); +const tools = await client.listTools(); +``` + +**Kotlin:** +```kotlin +import io.modelcontextprotocol.kotlin.sdk.client.Client +import io.modelcontextprotocol.kotlin.sdk.types.Implementation + +val client = Client( + clientInfo = Implementation( + name = "my-client", + version = "1.0.0" + ) +) + +client.connect(transport) +val tools = client.listTools() +``` +
+ +--- + +## Examples + +### Complete Examples + +| Example | Description | Location | +|---------|-------------|----------| +| **Multiplatform Server** | MCP server with multiple transports (JVM, Wasm) | [kotlin-mcp-server](./samples/kotlin-mcp-server) | +| **Weather Server** | Real-world API integration with stdio transport | [weather-stdio-server](./samples/weather-stdio-server) | +| **AI Client** | Interactive client with Anthropic API integration | [kotlin-mcp-client](./samples/kotlin-mcp-client) | + +### Community Servers + +Explore pre-built MCP servers at [mcp.run](https://mcp.run): +- πŸ“ **File systems**: Google Drive, Dropbox, S3 +- πŸ—„οΈ **Databases**: PostgreSQL, MongoDB, Supabase +- πŸ” **Search**: Brave, Perplexity, Exa +- πŸ’» **Development**: GitHub, Linear, Sentry +- πŸ“Š **Data**: Snowflake, BigQuery, Airtable + +> **Note:** Community servers may be in TypeScript or Python. This SDK can connect to any MCP server regardless of implementation language. + +--- + +## Contributing + +We welcome contributions! Please see: + +- [Contributing Guide](CONTRIBUTING.md) - Development setup, testing, PR process +- [Code of Conduct](CODE_OF_CONDUCT.md) - Community guidelines +- [Architecture](ARCHITECTURE.md) - Codebase structure and design decisions + +### Quick Start for Contributors + +```bash +# Clone repository +git clone https://github.com/modelcontextprotocol/kotlin-sdk.git +cd kotlin-sdk + +# Build +./gradlew build + +# Run tests +./gradlew test + +# Run samples +./gradlew :samples:weather-stdio-server:run +``` + +--- + +## License + +This project is licensed under the MIT Licenseβ€”see the [LICENSE](LICENSE) file for details. + +### Third-Party Dependencies + +This SDK depends on: +- **Kotlin** (Apache 2.0) +- **Ktor** (Apache 2.0) +- **kotlinx-coroutines** (Apache 2.0) +- **kotlinx-serialization** (Apache 2.0) + +--- + +## Additional Resources + +- πŸ“– **MCP Specification**: [spec.modelcontextprotocol.io](https://spec.modelcontextprotocol.io) +- 🌐 **Official Website**: [modelcontextprotocol.io](https://modelcontextprotocol.io) +- πŸ“¦ **TypeScript SDK**: [@modelcontextprotocol/sdk](https://github.com/modelcontextprotocol/typescript-sdk) +- 🐍 **Python SDK**: [mcp](https://github.com/modelcontextprotocol/python-sdk) +- πŸ” **MCP Inspector**: [Debug tool](https://github.com/modelcontextprotocol/inspector) +- πŸ’¬ **Community Discord**: [Join here](https://discord.gg/modelcontextprotocol) + +--- + +

+ Built with ❀️ by the MCP community +

diff --git a/build.gradle.kts b/build.gradle.kts index f7f3ef4c..c42772a1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,6 @@ plugins { id("mcp.dokka") + id("knit-convention") alias(libs.plugins.ktlint) alias(libs.plugins.kover) } diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 84676a4f..2d61cad7 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -13,4 +13,5 @@ dependencies { implementation(libs.kotlinx.atomicfu.gradle) implementation(libs.dokka.gradle) implementation(libs.maven.publish) + implementation(libs.kotlinx.knit) } diff --git a/buildSrc/src/main/kotlin/knit-convention.gradle.kts b/buildSrc/src/main/kotlin/knit-convention.gradle.kts new file mode 100644 index 00000000..de6ab6d0 --- /dev/null +++ b/buildSrc/src/main/kotlin/knit-convention.gradle.kts @@ -0,0 +1,11 @@ +plugins { + id("org.jetbrains.kotlinx.knit") +} +knit { + rootDir = projectDir + files = fileTree(projectDir) { + include("README.md") + } + defaultLineSeparator = "\n" + siteRoot = "" // Disable site root validation +} 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..99b5f655 --- /dev/null +++ b/docs/build.gradle.kts @@ -0,0 +1,27 @@ +import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode + +plugins { + id("mcp.multiplatform") +} + +kotlin { + jvm() + + explicitApi = ExplicitApiMode.Disabled + + sourceSets { + jvmMain { + dependencies { + implementation(project(":kotlin-sdk")) + implementation(libs.kotlinx.coroutines.core) + implementation(libs.kotlinx.io.core) + implementation(libs.ktor.server.core) + implementation(libs.ktor.server.sse) + } + } + } +} + +tasks.clean { + delete("src") +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d5a19ee6..c421e532 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,13 +1,14 @@ [versions] # plugins version -kotlin = "2.2.21" -dokka = "2.1.0" atomicfu = "0.29.0" -ktlint = "14.0.1" +binaryCompatibilityValidatorPlugin = "0.18.1" +dokka = "2.1.0" +knit = "0.5.0" +kotlin = "2.2.21" kover = "0.9.3" -netty = "4.2.7.Final" +ktlint = "14.0.1" mavenPublish = "0.35.0" -binaryCompatibilityValidatorPlugin = "0.18.1" +netty = "4.2.7.Final" openapi-generator = "7.17.0" # libraries version @@ -30,6 +31,7 @@ dokka-gradle = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } kotlin-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" } kotlinx-atomicfu-gradle = { module = "org.jetbrains.kotlinx:atomicfu-gradle-plugin", version.ref = "atomicfu" } +kotlinx-knit = { module = "org.jetbrains.kotlinx:kotlinx-knit", version.ref = "knit" } maven-publish = { module = "com.vanniktech:gradle-maven-publish-plugin", version.ref = "mavenPublish" } # Kotlinx libraries diff --git a/knit.properties b/knit.properties new file mode 100644 index 00000000..f761ad73 --- /dev/null +++ b/knit.properties @@ -0,0 +1,3 @@ +# Knit configuration +knit.package = io.modelcontextprotocol.kotlin.sdk.examples +knit.dir = docs/src/jvmMain/kotlin 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", )