MCP协议详解与Kotlin服务端实践指南

April 18, 2025
114, 514 read
Technology

一、MCP协议核心解析

我是lenz,很高兴在这里分享我的第一篇文章。近期工作中接触到了MCP的学习机会,作为一名主要使用Kotlin的后端开发者,我计划撰写MCP相关的文章。本文基于我的实际开发经验和个人理解整理而成,如有不足之处欢迎指正交流。

参考资料:

1.1 协议定位

MCP(Model Context Protocol)作为开放协议,其核心价值在于标准化LLM的上下文交互方式。官方将其类比为"AI领域的USB-C接口"——正如USB-C统一了设备连接标准,MCP为AI模型提供了统一的数据源和工具接入规范。

1.2 技术本质

从实现层面看,MCP实质上是对LLM Function Call功能的标准化封装,其架构包含两个关键组件:

  • 服务端:实现标准化Function Call接口
  • 客户端:动态发现并调用服务端能力

1.3 架构类比

采用前后端分离架构的类比理解:

组件 类比角色 功能对应
MCP服务端 后端API 提供标准化能力接口
MCP客户端 前端应用 交互调度层
LLM 用户操作行为 动态触发功能调用

二、Kotlin服务端实现方案

2.1 实现选型建议

推荐技术组合:

  • 传输协议:SSE(Server-Sent Events)
  • 开发语言:Kotlin
  • 框架选择:Ktor

协议选型对比:

传输方式 启动方式 适用场景
STDIO 子进程模式(java -jar) 嵌入式部署
SSE 独立HTTP服务 云原生部署

2.2 环境配置清单

- JDK:≥17(推荐Amazon Corretto 17)
- 构建工具:Gradle 8.5+
- 关键依赖:
    io.modelcontextprotocol:kotlin-sdk:0.4.0
    kotlinx-serialization-json:1.7.x

版本适配警告:务必确保kotlin("jvm")插件与序列化插件版本严格匹配(推荐2.1.0),否则可能引发运行时异常。

2.3 服务端核心实现

启动配置示例:

fun runSseMcpServerUsingKtorPlugin(port: Int): Unit = runBlocking {
    println("Starting sse server on port $port")
    println("Use inspector to connect to the http://localhost:$port/sse")
                        
    embeddedServer(CIO, host = "0.0.0.0", port = port) {
        mcp {
            return@mcp configureServer()
        }
    }.start(wait = true)
}

能力注册模板:

fun configureServer(): Server {
    val server = Server(
        Implementation(
            name = "mcp-kotlin test server",
            version = "0.1.0"
        ),
        ServerOptions(
            capabilities = ServerCapabilities(
                prompts = ServerCapabilities.Prompts(listChanged = true),
                resources = ServerCapabilities.Resources(subscribe = true, listChanged = true),
                tools = ServerCapabilities.Tools(listChanged = true),
            )
        )
    )
                        
    // Add a tool
    server.addTool(
        name = "kotlin-sdk-tool",
        description = "A test tool",
        inputSchema = Tool.Input()
    ) { _ ->
        CallToolResult(
            content = listOf(TextContent("Hello, world!"))
        )
    }
                        
    return server
}

三、实战:业务API智能接入

以下业务API智能接入方案由温州大佬提供,敬礼!( ` ・´)7

3.1 三阶能力暴露方案

1.接口目录服务

// 1. 基础元工具:获取所有可用API列表
server.addTool(
    name = "listAllApis",
    description = "列出所有可用的API接口",
    inputSchema = Tool.Input()
) {
    try {
        // 模拟数据
        val responseBuilder = StringBuilder("可用API列表(apiId-Name-Description):\n\n")
        .append("用户查询:\n").append("  • getUserInfo-获取用户信息-根据用户名称获取用户信息\n")
        .append("可使用 getApiDetails 工具获取特定API的详细信息,其中source表示来源")
                        
        CallToolResult(content = listOf(TextContent(responseBuilder.toString())))
    } catch (e: Exception) {
        CallToolResult(content = listOf(TextContent("错误: 获取API列表失败 - ${e.message}")))
    }
}

2.详情查询服务

// 2. 基础元工具:获取特定API的详细信息
server.addTool(
    name = "getApiDetails",
    description = "获取特定API的详细信息和使用方法",
    inputSchema = Tool.Input(
        properties = buildJsonObject {
        putJsonObject("apiId") {
            put("type", "string")
            put("description", "API的唯一标识符")
        }
    },
    required = listOf("apiId")
    )
) { request ->
    val apiId = request.arguments["apiId"]?.jsonPrimitive?.content
    ?: return@addTool CallToolResult(content = listOf(TextContent("错误: 缺少参数 apiId")))

    // 模拟数据
    val api = "{\"id\":\"getUserInfo\",\"name\":\"获取用户信息\",\"description\":\"根据用户名称获取用户信息\",\"category\":\"用户查询\",\"params\":[{\"name\":\"username\",\"type\":\"String\",\"description\":\"用户名称\",\"required\":true,\"defaultValue\":\"\",\"source\":\"\"}]}"

    CallToolResult(content = listOf(TextContent(api)))
}

3.执行代理服务

// 3. 基础元工具:执行指定API
server.addTool(
    name = "executeApi",
    description = "执行指定的API,需要提供API的ID和参数",
    inputSchema = Tool.Input(
        properties = buildJsonObject {
            putJsonObject("apiId") {
                put("type", "string")
                put("description", "要执行的API的唯一标识符")
            }
            putJsonObject("params") {
                put("type", "object")
                put("description", "API的参数")
            }
        },
        required = listOf("apiId")
    )
) { request ->
    val apiId = request.arguments["apiId"]?.jsonPrimitive?.content
    val params = request.arguments["params"]?.let { jsonElement ->
        if (jsonElement is JsonPrimitive)
            jsonElement.jsonPrimitive.content
        else
            jsonElement.toString()
    } ?: "{}"
                        
    if (apiId == null) {
        return@addTool CallToolResult(content = listOf(TextContent("错误: 缺少参数 apiId")))
    }
    
    // 模拟调用接口响应数据
    val map = mutableMapOf("lenz" to "lenz是一名使用Kotlin的后端开发者")

    CallToolResult(content = listOf(TextContent(map[Gson().fromJson(params, Map::class.java).getOrDefault("username", "")])))
}

3.2 测试验证流程

1.环境准备

安装Cherry Studio(最新版)+ 有效API Key

2.服务配置

设置 → MCP服务器 → 新增:
- 类型:SSE
- 地址:http://localhost:3001/sse
Image preview
Figure 1: Cherry Studio配置MCP服务器示例

3.对话测试

用户:询问用户lenz的信息
→ LLM自动选择list_apis工具
→ 定位用户信息API
→ 调用执行代理获取结果
Image preview
Figure 2: Cherry Studio对话测试示例

总结

感谢您阅读本文!实现MCP服务器的核心逻辑并不复杂,但需要注意开发环境与依赖库版本的兼容性问题。我已将完整的业务API集成代码开源在GitHub仓库(kotlin-mcp-server),其中包含了可复用的配置方案和版本管理建议。

后续我将持续更新该系列文章,下一篇将重点讲解客户端实现的关键技术与实战演示。

Lenz

想下班

热知识,今晚八点第三批MB神高开抢。会赢吗?会赢的。

Comments

2 Comments

楚雨荨 Lau

April 18, 2025

是Kotlin,这才是真正的MCP!

An*l

April 18, 2025

博主,你觉得Java和Kotlin哪个更好用?

Lenz

April 18, 2025

我觉得Idea + Coplilot更好用。