前言
最近一直在用NodeJS弄MCP应用,注意到SDK提供了一个Kotlin的版本,尝试后发现居然是基于Ktor的,索性试了下用Kotlin来搭建MCP应用,实践后证明这是一个不错的选择,比如官方Koog也是配了Ktor,Ktor这种小体量web框架就很适合做这种中间层服务来使用。
MCP简介

简单来说就是基于MCP协议对传统的AI-Agent进行赋能,让AI-Agent和工具服务有一个约定的协议进行通讯,通过MCP告诉AI-Agent工具调用端点并提供服务上的工具信息进行注册。在AI对话时让AI优先去选择工具,拿到选择的工具后通过告知的操作端点对MCPServer服务进行调用,然后获取到结果后交给AI一并分析。达到数据不过时,准确性高,不训练AI的前提下定制化AI析数据推理
技术栈
- AI部署:Ollama部署一个
DeepseekR1-1.5B模型(个人电脑有限😅,有条件的建议7B以上) - MCPServer:
Kotlin,ktor-server,modelcontextprotocol-kotlin-sdk - AI-Agent(MCP-Client):
Kotlin,ktor-server,ktor-client,modelcontextprotocol-kotlin-sdk,
目标实现
我们本地部署的AI是不具备联网功能所以它无法获取当前时间,我们通过提供时间选择工具达到本地AI也能有当前时间感知的能力

实现MCP Server
MCPServer本身是不具备Web服务能力的,所以我们得依靠Ktor-Server去把他挂载到我们的Web服务上让他成为一个独立可被远端调用的服务
核心依赖
kotlin
plugins {
kotlin("jvm") version "2.2.0"
id("io.ktor.plugin") version "3.3.3"
}
val mcpVersion = "0.8.1"
implementation("io.modelcontextprotocol:kotlin-sdk:${mcpVersion}")
implementation("io.ktor:ktor-server-core-jvm")
implementation("io.ktor:ktor-server-netty-jvm")
implementation("io.ktor:ktor-serialization-jackson")
implementation("io.ktor:ktor-server-content-negotiation")
implementation("io.ktor:ktor-server-sse")
1.创建MCPSever的运行示例
kotlin
val mcpServer = Server(
Implementation(
name = "kotlin mcp server",
version = "1.0.0"
),
ServerOptions(
capabilities = ServerCapabilities(tools = ServerCapabilities.Tools(listChanged = true))
)
)
2.创建工具
创建运行实例以后我们就可以基于往服务容器中注册工具了
kotlin
//写一个扩展函数根据addTool函数的规范创建一个获取时间的工具
fun Server.registerCurrentTimeTool() {
addTool(
name = "get_current_time",
description = """
获取当前的日期和时间。用户可以使用格式模式(如 'yyyy-MM-dd HH:mm:ss')来指定返回格式。
""".trimIndent(),
inputSchema = ToolSchema(
properties = buildJsonObject {
putJsonObject("format_pattern") {
put("type", "string")
put("description", "用于格式化时间的模式,例如 'yyyy-MM-dd HH:mm:ss'。")
}
},
),
handler = { request ->
val userPattern: String? = request.arguments
?.get("format_pattern")
?.jsonPrimitive
?.contentOrNull
log.info { "请求参数: $userPattern" }
val formatPattern: String = userPattern ?: run {
log.warn { " 上游请求时间工具未提供参数,使用默认格式化" }
"yyyy年MM月dd日 HH:mm:ss (zzz)"
}
val formatter = DateTimeFormatter.ofPattern(formatPattern)
.withZone(ZoneId.systemDefault())
val currentTime = Instant.now()
val formattedTime = formatter.format(currentTime)
return@addTool CallToolResult(
content = listOf(TextContent(formattedTime))
)
}
)
}
3.创建Transport连接桥梁
MCPServer提供不同的连接使用方式,比如SSE,内部IO调用,所以我们得选用一个连接器来进行连接。由于我们是利用Ktor-server来挂载提供远端调用,所以我们使用SSE的Transport,并且SDK内置的SSETransport是基于Ktor实现的,我们可以直接把Ktor的Call交给SSETransport处理,非常的方便。
创建对应AI-Agent的Transport管理
kotlin
//AI-Agent的Transport会话管理
//考虑到可能会有多个AI-Agent进行SSE连接,把他们对应的Transport工具管理归属起来
object SessionManager {
private val sessions: ConcurrentHashMap<String, SseServerTransport> = ConcurrentHashMap()
fun addSession(transport: SseServerTransport) {
sessions[transport.sessionId] = transport
transport.onClose {
sessions.remove(transport.sessionId)
}
}
fun getTransport(sessionId: String): SseServerTransport? {
return sessions[sessionId]
}
//可自行扩展会话删除等完善会话管理,示例仅供流程运行参考
}
1. 创建MCPServer的SSE连接点
kotlin
//利用Ktor-Server提供AI-Agent连接的SSE请求
val postEndpoint = "post"
sse("mcp/sse") {
//创建与MCPServer的连接处理器,并且提供SSE请求用户调用端点,和sse的会话提供连接器使用响应
//这里的调用端点AI-Agent客户端的SDK会自动匹配到mcp/${postEndpoint}下
val transport = SseServerTransport(postEndpoint, this)
mcpServer.createSession(transport) //这玩意虽然是挂起的,但是不会一直挂起!!!还是会往下执行
SessionManager.addSession(transport)
while (isActive) { //避免sse{}执行完毕后导致的会话结束Transport无法使用会话响应
delay(100)
}
}
2. 创建MCPServer的操作端点
kotlin
val postEndpoint = "post"
//创建调用端点请求(是POST)
post("sse/${postEndpoint}") {
//查询AI-Agent的会话Id
val sessionId = call.queryParameters["sessionId"] ?: run {
call.respondText("没有sessionId参数,不知道你是哪一个SSE会话", status = HttpStatusCode.BadRequest)
return@post
}
//查询对应的Transport工具
val transport = SessionManager.getTransport(sessionId)
?: run {
call.respondText("无效会话Id,对应的会话已经关闭了", status = HttpStatusCode.Unauthorized)
return@post
}
try {
//把会话上下问交给 transport.handlePostMessage进行处理
//这里面会自动根据上游发来MCP内容约定去进行相关操作然后响应
//注意: 这里并不会进行响应需要的数据,他只会响应一个接受状态,实际上数据的响应是走的SSE
transport.handlePostMessage(call)
} catch (e: Exception) {
if (!call.response.isCommitted) {
call.respondText("MCP服务处理出错: ${e.message}", status = HttpStatusCode.InternalServerError)
}
}
}

完整的MCPServer路由
kotlin
import io.ktor.http.HttpStatusCode
import io.ktor.server.response.respondText
import io.ktor.server.routing.Route
import io.ktor.server.routing.post
import io.ktor.server.sse.sse
import io.modelcontextprotocol.kotlin.sdk.server.Server
import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions
import io.modelcontextprotocol.kotlin.sdk.server.SseServerTransport
import io.modelcontextprotocol.kotlin.sdk.types.Implementation
import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import java.util.concurrent.ConcurrentHashMap
/**
*
* @author setruth
* @date 2025/12/13
* @time 12:21
*/
object SessionManager {
private val sessions: ConcurrentHashMap<String, SseServerTransport> = ConcurrentHashMap()
fun addSession(transport: SseServerTransport) {
sessions[transport.sessionId] = transport
transport.onClose {
sessions.remove(transport.sessionId)
}
}
fun getTransport(sessionId: String): SseServerTransport? {
return sessions[sessionId]
}
}
fun Route.mcpServer(
path: String,
toolConfiguration: Server.() -> Unit,
) {
val mcpServer = Server(
Implementation(
name = "kotlin mcp server",
version = "1.0.0"
),
ServerOptions(
capabilities = ServerCapabilities(tools = ServerCapabilities.Tools(listChanged = true))
)
)
mcpServer.toolConfiguration()
val postEndpoint = "post"
sse("$path/sse") {
val transport = SseServerTransport(postEndpoint, this)
mcpServer.createSession(transport)
SessionManager.addSession(transport)
while (isActive) {
delay(100)
}
}
post("$path/${postEndpoint}") {
val sessionId = call.queryParameters["sessionId"] ?: run {
call.respondText("没有sessionId参数,不知道你是哪一个SSE会话", status = HttpStatusCode.BadRequest)
return@post
}
val transport = SessionManager.getTransport(sessionId)
?: run {
call.respondText("无效会话Id,对应的会话已经关闭了", status = HttpStatusCode.Unauthorized)
return@post
}
try {
transport.handlePostMessage(call)
} catch (e: Exception) {
if (!call.response.isCommitted) {
call.respondText("MCP服务处理出错: ${e.message}", status = HttpStatusCode.InternalServerError)
}
}
}
}
运行MCPServer
kotlin
const val MCP_PORT = 3000
fun main() {
embeddedServer(Netty, port = MCP_PORT, host = "127.0.0.1", module = Application::module)
.start(wait = true)
}
fun Application.module() {
install(ContentNegotiation) {
jackson()
}
install(SSE)
routing {
mcpServer("/mcp") { // 配置路由
registerCurrentTimeTool() //注册获取时间工具工具
}
}
}
MCPServer的连接测试
访问127.0.0.1:3000/mcp/sse后我们会获取到McpServer响应过来的endpoint事件,当AI-Agent(MCPClient)连接后就会拿到调用端点的信息也就是我们上面写的post,并且提供了会话Id信息。上游后面POST请求/mcp/post路径的时候就会带上会话Id的参数,我们就能拿到去提供对应的Transport工具去处理调用了。

实现AI-Agent(MCPClient)
与MCPServer交互的是MCPClient,但是一般来说MCPClient都是和AI-Agent处于一个服务中所以这里就直接整合起来了。然后Koog也有Ktor的SDK,利用Ktor服务结合Koog去代理AI就非常流畅了再加上MCPClient来使用。
核心依赖
kotlin
plugins {
kotlin("jvm") version "2.2.0"
id("io.ktor.plugin") version "3.3.3"
}
val mcpVersion = "0.8.1"
implementation("io.modelcontextprotocol:kotlin-sdk:${mcpVersion}")
implementation("io.ktor:ktor-server-core-jvm")
implementation("io.ktor:ktor-server-netty-jvm")
implementation("io.ktor:ktor-serialization-jackson")
implementation("io.ktor:ktor-server-content-negotiation")
implementation("io.ktor:ktor-server-sse")
implementation("io.ktor:ktor-server-cors")
implementation("io.ktor:ktor-server-config-yaml")
implementation("io.ktor:ktor-client-core")
implementation("io.ktor:ktor-client-cio")
implementation("ai.koog:koog-ktor:0.5.4")
1. 创建MCPClient
1.1 创建MCPClient
kotlin
private val mcpClient: Client = Client(clientInfo = Implementation(name = "kotlin mcp client", version = "1.0.0"))
1.2 创建连接器
我们是使用的SDK去创建MCPServer的Transport连接器,所以Client也是有对应的Transport去进行连接,避免通讯方式的不统一
kotlin
//利用Ktor-client去作为发送MCP请求的客户端
val mcpSeverReqClient = HttpClient(CIO) {
install(SSE)
install(Logging)
}
//由于MCPServer我们创建的是SSE的连接器,所以这边也用SSE的
val transport = SseClientTransport(
mcpSeverReqClient,
serverScriptPath, //MCPSerber的SSE请求地址路径
5.seconds,
)
1.3 获取连接MCPServer并获取工具信息存储
创建好MCPClient和Transport后我们就可以使用Client去连接Transport,让Transport去代理Client的所有操作去和MCPSevrer进行交互了
kotlin
mcpClient.connect(transport) // 挂起-> 获取与MCPServer的SSE连接
val toolsResult = mcpClient.listTools() // 挂起-> 获取工具列表
tools = toolsResult.tools.map { //处理一下过滤调一些无用的工具元数据避免干扰AI判断
LLMTool(
name = it.name,
description = it.description ?: "",
parameters = extractSimplifiedProperties(it), //自写的函数不是内部的
)
}
toolsFormatStr = """ //提供工具列表Prompt提示
## IV. 可用工具列表:${mapper.writeValueAsString(tools)}
""".trimIndent()
1.4 提供工具调用函数
kotlin
//当给AIPrompt进行规范后后只需要获取AI的回答去解析工具name和对应参数即可,利用MCPClient就能去调用对应的Tool
//如果需要扩展可以和AI约定更多信息比如带上不同的MCPServer的标签 就能实现调用不同MCPClient去获取对应的工具信息
suspend fun executeTool(name: String, input: Map<String, Any>): String {
val toolResult = mcpClient.callTool(name, input)
//这里只是演示Text的数据流响应时的处理,对应我们的get_current_time工具响应类型
val textContent = toolResult.content.find { it.type == ContentTypes.TEXT }
if (textContent == null) {
return "无内容"
}
return (textContent as TextContent).text
}
完整MCPClient
kotlin
private val log = KotlinLogging.logger { }
// 工具参数
data class LLMToolProperty(
val name: String,
val type: String,
val description: String,
)
// 工具定义
data class LLMTool(
val name: String,
val description: String,
val parameters: List<LLMToolProperty>,
)
/**
*
* @author setruth
* @date 2025/12/13
* @time 13:31
*/
class MCPClient() : AutoCloseable {
private val mcpClient: Client = Client(clientInfo = Implementation(name = "kotlin mcp client", version = "1.0.0"))
private val mcpSeverReqClient = HttpClient(CIO) {
install(SSE)
install(Logging)
}
private var tools: List<LLMTool> = listOf()
var toolsFormatStr = ""
suspend fun connectToServer(serverScriptPath: String) {
try {
val transport = SseClientTransport(
mcpSeverReqClient,
serverScriptPath,
5.seconds,
)
mcpClient.connect(transport)
val toolsResult = mcpClient.listTools()
tools = toolsResult.tools.map {
val tool= LLMTool(
name = it.name,
description = it.description ?: "",
parameters = extractSimplifiedProperties(it),
)
log.info { "工具: $tool" }
tool
}
toolsFormatStr = """
## IV. 可用工具列表:${mapper.writeValueAsString(tools)}
""".trimIndent()
} catch (e: Exception) {
log.error(e) { "MCP服务连接失败: ${e.message}" }
}
}
suspend fun executeTool(name: String, input: Map<String, Any>): String {
val toolResult = mcpClient.callTool(name, input)
val textContent = toolResult.content.find { it.type == ContentTypes.TEXT }
if (textContent == null) {
return "无内容"
}
return (textContent as TextContent).text
}
override fun close() {
runBlocking {
mcpClient.close()
}
}
private fun extractSimplifiedProperties(sdkTool: Tool): List<LLMToolProperty> {
val toolNode: JsonNode = try {
mapper.valueToTree(sdkTool)
} catch (e: Exception) {
return emptyList()
}
val propertiesNode = toolNode
.get("inputSchema")
?.get("properties")
if (propertiesNode == null || !propertiesNode.isObject) {
return emptyList()
}
val paramsList = mutableListOf<LLMToolProperty>()
propertiesNode.properties().forEach { (paramName, detailsNode) ->
val typeContentNode = detailsNode.get("type")
val type = typeContentNode?.get("content")?.asText() ?: typeContentNode?.asText() ?: "string"
val descriptionContentNode = detailsNode.get("description")
val description = descriptionContentNode?.get("content")?.asText() ?: descriptionContentNode?.asText()
?: "无描述"
paramsList.add(
LLMToolProperty(
name = paramName,
type = type.trim(),
description = description.trim()
)
)
}
return paramsList
}
}
2. 实现AI-Agent
2.1 系统Prompt定义
AI是否能选择和选择是否准确,在我们不训练AI的前提下就只能靠堆和调整Prompt提示达到准确效果
kotlin
const val SystemPromptBase = """你是一个强大的 AI 助手。你的回复必须严格遵循以下规则,回复内容必须是**纯净**的工具调用 JSON **或** **纯净**的自然语言文本,绝不允许混合。
## I. 工具调用模式:决策与输出 ( STRICT JSON MODE )
1. **触发条件:** 仅当用户的请求内容与**可用工具列表中的任一工具描述**直接相关时,才决定使用工具。
2. **可用性限制:** 你只允许使用当前上下文中提供的工具。**严禁**自行编造、猜测或调用任何不存在的工具或函数。
3. **最终格式(必须):** 如果你决定调用工具,你的回复**必须且只能**是一个完整的、**未经任何包裹**的 JSON 字符串。格式必须严格为:
{"name": "工具名", "input": {参数名1: 值1, 参数名2: 值2}}
⚠️ **参数注意:** `input` 的值必须是一个有效的 **JSON 对象(Map)**,包含工具所需的所有必填参数。
4. **纯净输出(核心):** 工具调用 JSON **前后**及**内部**,**绝不允许**包含以下任何内容:
* Markdown 标记(包括 ```、**、#)
* 任何解释、推理、思考过程(包括 <think> 标签)
* 额外的空行、换行符或多余的空格。
* 任何除 JSON 结构本身以外的文本。
## II. 纯文本模式:直接回复 ( PLAIN TEXT MODE )
5. **回复条件与意图:** 如果你不需要调用工具,或者用户的请求与任何可用工具均不相关,你**必须作为 AI 助手直接、完整地回答用户的问题或请求**。
6. **格式排除:** 在纯文本回复中,**严禁**包含任何 JSON 格式标记(花括号 {}、方括号 []、双引号 ")或任何可能被误识别为结构化数据的符号。回复必须是自然、有帮助的语言文本。
## III. 最终校验
7. **唯一性:** 最终的输出**只能**是 I 模式下的**纯净 JSON 字符串**,**或**是 II 模式下的**纯净文本**。不允许同时出现或混合。
"""
2.2 创建对话路由
kotlin
fun Route.appRoute() {
runBlocking {
log.info { "尝试连接 MCP 服务器..." }
mcpClient.connectToServer("http://127.0.0.1:3000/mcp/sse")
}
post("chat") {
val userInput = call.receive<UserQuery>()
call.respond(HttpStatusCode.OK, ResData(mcpProcess(userInput.query)))
}
}
2.3 创建对话流程(核心)
基于MCP使用工具的对话流程
初始请求与决策:
- 输入: 接收
用户询问和系统约束加工具描述。- 动作: 第一次询问 LLM 进行工具选择 。
工具选择解析:
- 结果: 解析 LLM 的回复,判断是直接回复,还是工具调用请求 。
工具执行
- 调用: 若 LLM 选择工具,使用
MCPClient进行远端工具调用。- 获取: 阻塞等待并接收工具结果 。
二次询问与结束
- 输入: 将工具结果作为对话历史的一部分。
- 动作: 第二次询问 LLM 进行最终推理生成回复响应给客户端。
kotlin
suspend fun RoutingContext.mcpProcess(input: String): String {
// 创建顶层第一次会话,带上工具描述和系统约束规则
val topPrompt = SystemPromptBase.trimIndent() + "\n\n" + mcpClient.toolsFormatStr
val aiSelectPrompt = prompt(UUID.randomUUID().toString()) {
system(topPrompt)
user(input)
}
// 获取工具选择结果
val messages = askAI(aiSelectPrompt).toText(true, "ai选择工具询问")
return when (val result = selectTool(messages)) {
is SelectResult.NoSelect -> { // AI没选择工具直接返回AI回复
log.info { "AI未选择工具,直接返回: $messages" }
result.message
}
is SelectResult.Select -> { // AI选择工具后进行工具调用
log.info { "AI已选择工具: ${result.name},参数: ${result.input}" }
//使用mcpClient进行远端工具调用
val toolRes = mcpClient.executeTool(result.name, result.input)
log.info { "工具返回: $toolRes" }
// 带工具配合历史对话让AI一并分析回复
val resultPrompt = prompt(aiSelectPrompt) {
system("使用的工具${result.name}已返回的数据时:${toolRes},基于这个工具给的数据去思考再次回复之前的询问,并且不要带上工具的任何信息")
}
askAI(resultPrompt).toText(true, "ai带上工具结果去思考")
}
}
}
完整AI-Agent
kotlin
data class ResData(val data: String)
data class UserQuery(val query: String)
sealed class SelectResult {
data class NoSelect(val message: String) : SelectResult()
data class Select(val name: String, val input: Map<String, Any>) : SelectResult()
}
private val log = KotlinLogging.logger { }
fun Route.appRoute() {
runBlocking {
log.info { "尝试连接 MCP 服务器..." }
mcpClient.connectToServer("http://127.0.0.1:3000/mcp/sse")
}
post("chat") {
val userInput = call.receive<UserQuery>()
call.respond(HttpStatusCode.OK, ResData(mcpProcess(userInput.query)))
}
}
suspend fun RoutingContext.mcpProcess(input: String): String {
// 创建顶层第一次会话,带上工具描述和系统约束规则
val topPrompt = SystemPromptBase.trimIndent() + "\n\n" + mcpClient.toolsFormatStr
val aiSelectPrompt = prompt(UUID.randomUUID().toString()) {
system(topPrompt)
user(input)
}
// 获取工具选择结果
val messages = askAI(aiSelectPrompt).toText(true, "ai选择工具询问")
return when (val result = selectTool(messages)) {
is SelectResult.NoSelect -> { // AI没选择工具直接返回AI回复
log.info { "AI未选择工具,直接返回: $messages" }
result.message
}
is SelectResult.Select -> { // AI选择工具后进行工具调用
log.info { "AI已选择工具: ${result.name},参数: ${result.input}" }
//使用mcpClient进行远端工具调用
val toolRes = mcpClient.executeTool(result.name, result.input)
log.info { "工具返回: $toolRes" }
// 带工具配合历史对话让AI一并分析回复
val resultPrompt = prompt(aiSelectPrompt) {
system("使用的工具${result.name}已返回的数据时:${toolRes},基于这个工具给的数据去思考再次回复之前的询问,并且不要带上工具的任何信息")
}
askAI(resultPrompt).toText(true, "ai带上工具结果去思考")
}
}
}
/**
* 基于和AI约定的`{"name": "工具名", "input": {参数名1: 值1, 参数名2: 值2}}`规则去解析AI是否选择了工具
*
* @param messages AI响应文本
*/
fun selectTool(messages: String): SelectResult {
if (!messages.startsWith("{") || !messages.endsWith("}")) {
return SelectResult.NoSelect(messages)
}
try {
val toolCall = mapper.readValue<SelectResult.Select>(messages)
if (toolCall.name.isBlank()) {
return SelectResult.NoSelect(messages)
}
return toolCall
} catch (e: Exception) {
log.error(e) { "解析工具调用失败,原始信息:${messages}" }
return SelectResult.NoSelect(messages)
}
}
/**
* 将消息响应列表转换为压缩后的文本内容
*
* @param showRawThink 是否显示AI原始思考内容,默认为false
* @param step 步骤标识符,用于日志输出,默认为空字符串
* @return 清理并压缩后的文本内容
*/
private fun List<Message.Response>.toText(showRawThink: Boolean = false, step: String = ""): String {
// 合并所有响应内容
val mergeText = joinToString(separator = "") { it.content }
if (showRawThink) {
log.info { "$step AI原始思考:${mergeText}" }
}
// 移除<think>标签及其包含的内容
val thinkRegex = Regex("""<think>[\s\S]*?</think>""")
val cleanedContent = mergeText.replace(thinkRegex, "")
// 压缩连续的空白字符为单个空格
val whitespaceRegex = Regex("""\s+""")
val compressedContent = cleanedContent.replace(whitespaceRegex, " ")
return compressedContent.trim()
}
/**
* 向AI模型发送询问并获取响应结果
*
* @param prompt 要发送给AI模型的提示信息
* @return AI模型返回的消息响应列表
*/
private suspend fun RoutingContext.askAI(prompt: Prompt): List<Message.Response> {
try {
//Koog的模型收录并部署很完整,有一些得自己去定义 特别是Ollama中的
return llm().execute(prompt, DEEPSEEK_R1_1_5B)
} catch (e: Exception) {
log.error(e) { "LLM执行失败" }
throw e
}
}
创建AI-Agent服务
yaml配置
选择KOOG的KtorSDK以后只需要配置代理方和地址即可,如果有密钥需要写入apikey
yaml
koog:
ollama:
enable: true
baseUrl: http://localhost:11434
ktor:
application:
modules:
- com.setruth.mcp.client.MainKt.module
deployment:
port: 3001
主程序
kotlin
val mapper = jacksonObjectMapper()
val mcpClient = MCPClient()
/**
*
* @author setruth
* @date 2025/12/13
* @time 13:27
*/
fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)
fun Application.module() {
monitor.subscribe(ApplicationStopping) { application ->
mcpClient.close()
}
install(ContentNegotiation) {
jackson()
}
install(Koog)
install(SSE)
install(CORS){
anyMethod()
anyHost()
allowCredentials = true
allowNonSimpleContentTypes = true
}
routing {
staticResources("/", "static")
appRoute()
}
}
AI-Agent运行测试
运行后我们看到获取到了工具,接下来连接我们的localhost:3000/chat去通过AI-Agent和AI对话看是否能正确选择工具

实践结果
当询问AI名字的时候AI没有选择工具直接回复我们了✔️
当我们问时间的时候AI选择了我们的获取时间工具拿到了时间去进行分析回复✔️
自此我们利用MCP服务对AI进行了能力增强,并且是基于我们给的数据来进行回复回答,保证了准确性也避免了幻觉问题

注意事项
AI推理选择工具很大一定程度上得看AI本身,小模型本身推理能力是非常差劲的,很难按照你的规范来完整响应给你,而且对上下文的分析也有限,我拿1.5B当时都调了好一会Prompt,拿7B虽然推理能力好点,但是对于这种长Prompt的约束AI很多时候也比较死脑筋,当我换在线API比如Deepseek后,很完美的达到了预计效果。所以当调Prompt死活效果不对的情况下建议试试换个大点的模型,推荐先拿在线AI接入来进行Prompt调整,再根据小点的模型逐步精细化,并且大模型也能理解更多的工具 而不只是简单的时间获取。