主题
Kotlin SDK
Model Context Protocol (MCP) 的 Kotlin SDK 提供了构建 MCP 服务器和客户端的现代 Kotlin 解决方案,支持协程、类型安全和函数式编程特性。
安装
Gradle (Kotlin DSL)
kotlin
dependencies {
implementation("io.modelcontextprotocol:mcp-sdk-kotlin:1.0.0")
// 协程支持
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
// JSON 序列化
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
}
Gradle (Groovy)
groovy
dependencies {
implementation 'io.modelcontextprotocol:mcp-sdk-kotlin:1.0.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3'
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0'
}
Maven
xml
<dependency>
<groupId>io.modelcontextprotocol</groupId>
<artifactId>mcp-sdk-kotlin</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
<version>1.7.3</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-serialization-json</artifactId>
<version>1.6.0</version>
</dependency>
快速开始
创建 MCP 服务器
kotlin
import io.mcp.sdk.server.*
import io.mcp.sdk.tools.*
import io.mcp.sdk.schema.*
import kotlinx.coroutines.*
import kotlinx.serialization.json.*
suspend fun main() {
// 创建服务器
val server = McpServer {
name = "my-kotlin-server"
version = "1.0.0"
description = "Kotlin MCP 服务器示例"
}
// 注册工具
server.registerTool<EchoTool>()
// 启动服务器
server.start()
}
@Serializable
data class EchoRequest(val message: String)
@Serializable
data class EchoResponse(val echo: String)
class EchoTool : Tool<EchoRequest, EchoResponse> {
override val name = "echo"
override val description = "回显输入的消息"
override suspend fun call(request: EchoRequest): ToolResult<EchoResponse> {
return ToolResult.success(EchoResponse(echo = request.message))
}
}
创建 MCP 客户端
kotlin
import io.mcp.sdk.client.*
import io.mcp.sdk.transport.*
import kotlinx.coroutines.*
suspend fun main() {
// 创建传输层
val transport = StdioTransport()
// 创建客户端
val client = McpClient {
transport = transport
connectTimeout = 10.seconds
requestTimeout = 30.seconds
}
try {
// 连接到服务器
client.connect()
// 列出可用工具
val tools = client.listTools()
println("可用工具: ${tools.joinToString { it.name }}")
// 调用工具
val result = client.callTool<EchoResponse>("echo") {
"message" to "Hello from Kotlin!"
}
println("工具调用结果: ${result.echo}")
} finally {
client.close()
}
}
核心功能
服务器功能
工具注册
kotlin
// 计算器工具
@Serializable
data class CalculatorRequest(
val operation: Operation,
val a: Double,
val b: Double
)
@Serializable
enum class Operation {
ADD, SUBTRACT, MULTIPLY, DIVIDE
}
@Serializable
data class CalculatorResponse(val result: Double)
class CalculatorTool : Tool<CalculatorRequest, CalculatorResponse> {
override val name = "calculator"
override val description = "执行基本数学运算"
override suspend fun call(request: CalculatorRequest): ToolResult<CalculatorResponse> {
val result = when (request.operation) {
Operation.ADD -> request.a + request.b
Operation.SUBTRACT -> request.a - request.b
Operation.MULTIPLY -> request.a * request.b
Operation.DIVIDE -> {
if (request.b == 0.0) {
return ToolResult.error("除数不能为零")
}
request.a / request.b
}
}
return ToolResult.success(CalculatorResponse(result))
}
}
// 注册工具
server.registerTool<CalculatorTool>()
// 或者注册实例
server.registerTool(CalculatorTool())
资源管理
kotlin
import io.mcp.sdk.resources.*
import java.io.File
import java.nio.file.Path
import kotlin.io.path.*
class FileResourceProvider(private val basePath: Path) : ResourceProvider {
override suspend fun getResource(uri: ResourceUri): Resource {
val filePath = basePath / uri.path.removePrefix("/")
if (!filePath.exists()) {
throw ResourceNotFoundException("资源不存在: $uri")
}
val content = filePath.readText()
return Resource(
uri = uri,
mimeType = "text/plain",
content = content
)
}
override suspend fun listResources(): List<ResourceUri> {
return basePath.walk()
.filter { it.isRegularFile() }
.map { file ->
val relativePath = basePath.relativize(file)
ResourceUri("file:///$relativePath")
}
.toList()
}
}
// 注册资源提供者
server.registerResourceProvider(FileResourceProvider(Path.of("/path/to/files")))
提示模板
kotlin
import io.mcp.sdk.prompts.*
@Serializable
data class CodeReviewRequest(
val code: String,
val language: String = "unknown"
)
class CodeReviewPromptProvider : PromptProvider {
override suspend fun getPrompt(name: String, arguments: JsonObject): Prompt {
return when (name) {
"code-review" -> createCodeReviewPrompt(arguments)
else -> throw PromptNotFoundException("未知提示: $name")
}
}
override suspend fun listPrompts(): List<String> {
return listOf("code-review")
}
private suspend fun createCodeReviewPrompt(arguments: JsonObject): Prompt {
val code = arguments["code"]?.jsonPrimitive?.content
?: throw IllegalArgumentException("缺少 code 参数")
val language = arguments["language"]?.jsonPrimitive?.content ?: "unknown"
val content = """
请审查以下 $language 代码并提供改进建议:
```$language
$code
```
""".trimIndent()
return Prompt(
name = "code-review",
description = "代码审查提示",
content = content
)
}
}
// 注册提示提供者
server.registerPromptProvider(CodeReviewPromptProvider())
客户端功能
连接管理
kotlin
// 自动重连客户端
val client = McpClient {
transport = transport
autoReconnect = true
maxReconnectAttempts = 5
reconnectDelay = 2.seconds
}
// 连接状态监听
client.onConnected {
println("已连接到服务器")
}
client.onDisconnected {
println("与服务器断开连接")
}
client.onError { exception ->
println("连接错误: ${exception.message}")
}
并发操作
kotlin
// 并发调用多个工具
val results = coroutineScope {
listOf(
async { client.callTool<String>("tool1", args1) },
async { client.callTool<String>("tool2", args2) },
async { client.callTool<String>("tool3", args3) }
).awaitAll()
}
results.forEachIndexed { index, result ->
println("结果 ${index + 1}: $result")
}
流式操作
kotlin
// 流式工具调用
client.callToolStream<String>("streaming-tool", args)
.collect { chunk ->
print(chunk)
}
传输协议
Stdio 传输
kotlin
val transport = StdioTransport {
command = listOf("python", "server.py")
workingDirectory = Path.of("/path/to/server")
environment = mapOf("ENV_VAR" to "value")
}
WebSocket 传输
kotlin
val transport = WebSocketTransport {
uri = "ws://localhost:8080/mcp"
headers = mapOf("Authorization" to "Bearer token")
connectTimeout = 10.seconds
}
HTTP SSE 传输
kotlin
val transport = SseTransport {
uri = "http://localhost:8080/mcp"
headers = mapOf("Authorization" to "Bearer token")
readTimeout = 30.seconds
}
高级特性
依赖注入支持 (Koin)
kotlin
import org.koin.core.module.dsl.singleOf
import org.koin.dsl.module
import org.koin.core.context.startKoin
val mcpModule = module {
// 注册 MCP 服务器
single {
McpServer {
name = "my-server"
version = "1.0.0"
}
}
// 注册工具
singleOf(::DatabaseTool)
singleOf(::EmailTool)
// 注册资源提供者
singleOf(::FileResourceProvider)
// 注册其他服务
singleOf(::EmailService)
singleOf(::DatabaseService)
}
suspend fun main() {
startKoin {
modules(mcpModule)
}
val server = get<McpServer>()
server.start()
}
// 带依赖注入的工具
class DatabaseTool(
private val databaseService: DatabaseService
) : Tool<DatabaseQuery, DatabaseResult> {
override val name = "database-query"
override val description = "执行数据库查询"
override suspend fun call(request: DatabaseQuery): ToolResult<DatabaseResult> {
return try {
val result = databaseService.executeQuery(request.sql)
ToolResult.success(DatabaseResult(result))
} catch (e: Exception) {
ToolResult.error("查询执行失败: ${e.message}")
}
}
}
中间件支持
kotlin
interface Middleware {
suspend fun handle(request: Request, next: suspend (Request) -> Response): Response
}
class LoggingMiddleware : Middleware {
override suspend fun handle(request: Request, next: suspend (Request) -> Response): Response {
println("处理请求: ${request.method}")
val startTime = System.currentTimeMillis()
return try {
val response = next(request)
val duration = System.currentTimeMillis() - startTime
println("请求完成,耗时: ${duration}ms")
response
} catch (e: Exception) {
val duration = System.currentTimeMillis() - startTime
println("请求处理失败,耗时: ${duration}ms")
throw e
}
}
}
// 注册中间件
server.use(LoggingMiddleware())
server.use(AuthenticationMiddleware())
server.use(RateLimitingMiddleware())
配置管理
kotlin
import kotlinx.serialization.Serializable
@Serializable
data class McpConfig(
val server: ServerConfig = ServerConfig(),
val logging: LoggingConfig = LoggingConfig(),
val transport: TransportConfig = TransportConfig()
)
@Serializable
data class ServerConfig(
val name: String = "",
val version: String = "",
val description: String = "",
val maxConnections: Int = 100,
val requestTimeoutSeconds: Int = 30
)
@Serializable
data class LoggingConfig(
val level: String = "INFO",
val file: String? = null
)
@Serializable
data class TransportConfig(
val type: String = "stdio",
val bufferSize: Int = 8192
)
// 加载配置
val config = Json.decodeFromString<McpConfig>(
File("config.json").readText()
)
// 使用配置创建服务器
val server = McpServer {
name = config.server.name
version = config.server.version
description = config.server.description
maxConnections = config.server.maxConnections
requestTimeout = config.server.requestTimeoutSeconds.seconds
}
类型安全的工具调用
kotlin
// 定义工具接口
interface WeatherTool {
suspend fun getCurrentWeather(location: String): WeatherResponse
suspend fun getForecast(location: String, days: Int): ForecastResponse
}
@Serializable
data class WeatherResponse(
val temperature: Double,
val humidity: Int,
val description: String
)
@Serializable
data class ForecastResponse(
val location: String,
val forecasts: List<DailyForecast>
)
@Serializable
data class DailyForecast(
val date: String,
val temperature: TemperatureRange,
val description: String
)
@Serializable
data class TemperatureRange(
val min: Double,
val max: Double
)
// 实现工具
class WeatherToolImpl : WeatherTool {
override suspend fun getCurrentWeather(location: String): WeatherResponse {
// 实现获取当前天气的逻辑
return WeatherResponse(
temperature = 25.0,
humidity = 60,
description = "晴朗"
)
}
override suspend fun getForecast(location: String, days: Int): ForecastResponse {
// 实现获取天气预报的逻辑
return ForecastResponse(
location = location,
forecasts = emptyList()
)
}
}
// 客户端类型安全调用
val weatherTool = client.createProxy<WeatherTool>()
val weather = weatherTool.getCurrentWeather("北京")
println("当前温度: ${weather.temperature}°C")
测试
单元测试
kotlin
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class EchoToolTest {
@Test
fun `call with valid message returns echo`() = runTest {
// Arrange
val tool = EchoTool()
val request = EchoRequest("test")
// Act
val result = tool.call(request)
// Assert
assertTrue(result.isSuccess)
assertEquals("test", result.data?.echo)
}
@Test
fun `calculator tool performs addition correctly`() = runTest {
// Arrange
val tool = CalculatorTool()
val request = CalculatorRequest(Operation.ADD, 2.0, 3.0)
// Act
val result = tool.call(request)
// Assert
assertTrue(result.isSuccess)
assertEquals(5.0, result.data?.result)
}
}
集成测试
kotlin
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
class McpIntegrationTest {
@Test
fun `server client integration test`() = runTest {
// 启动测试服务器
val server = McpServer {
name = "test-server"
}
server.registerTool<EchoTool>()
val serverJob = launch { server.start() }
try {
// 创建客户端
val transport = StdioTransport()
val client = McpClient { transport = transport }
client.connect()
// 测试工具调用
val result = client.callTool<EchoResponse>("echo") {
"message" to "test"
}
assertEquals("test", result.echo)
} finally {
serverJob.cancel()
}
}
}
部署
Spring Boot 集成
kotlin
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@SpringBootApplication
class McpServerApplication
fun main(args: Array<String>) {
runApplication<McpServerApplication>(*args)
}
@Configuration
class McpConfiguration {
@Bean
fun mcpServer(): McpServer {
return McpServer {
name = "spring-mcp-server"
version = "1.0.0"
}
}
@Bean
fun echoTool(): EchoTool = EchoTool()
}
Docker 部署
dockerfile
FROM openjdk:17-jdk-slim
WORKDIR /app
COPY build/libs/mcp-server.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
Kubernetes 部署
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: my-mcp-server:latest
ports:
- containerPort: 8080
env:
- name: MCP_SERVER_NAME
value: "kubernetes-mcp-server"
---
apiVersion: v1
kind: Service
metadata:
name: mcp-server-service
spec:
selector:
app: mcp-server
ports:
- port: 80
targetPort: 8080
type: LoadBalancer