news 2026/10/2 6:51:33

Spring-AI 结合自定义 mcp server 实现飞书智能机器人:把 endpoint 改到 TaoToken

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring-AI 结合自定义 mcp server 实现飞书智能机器人:把 endpoint 改到 TaoToken

1. 飞书机器人接大模型,为什么卡在 endpoint 这一层

飞书智能机器人这个场景,真正难的不是把消息收进来,而是让机器人“会思考”。飞书开放平台的长连接(WebSocket)负责把用户消息推给你的服务,你的服务再把消息交给大模型,拿到回复后用 messageId 回帖。链路本身不复杂,但一旦落到 Spring-AI 上,很多人会卡在两个地方:一是自定义 mcp server 怎么注册进 ChatClient,二是大模型的 endpoint 到底写在哪、怎么改。

我见过太多项目把 api-key 和 base-url 硬编码在 Java 代码里,换一个模型供应商就要重新打包。更麻烦的是,Spring-AI 默认的模型 starter 往往只认官方地址,你想走统一 Key/API 通道,就得知道它到底读哪个配置项。这篇就围绕“把 endpoint 改到 TaoToken”这件事,把 Spring-AI + 自定义 mcp server + 飞书机器人这条链路完整跑一遍。

适合谁看:已经会用 Spring Boot 写接口、想给飞书机器人加“工具调用”能力的后端同学;或者手上有个 mcp server,想把它接进 Spring-AI 生态的开发者。核心检索词就三个:Spring-AI、mcp server、飞书智能机器人。读完你能拿到可复制的 application.yml、mcp server 注册代码,以及飞书侧验证机器人是否真的在回复的动作。

先说清楚整体结构。工程分两个模块:mcp-server 负责暴露工具(比如查天气、查订单),mcp-client 负责跑 Spring-AI、连飞书长连接、调大模型。mcp-client 通过java -jar或 stdio 方式拉起 mcp-server,Spring-AI 用SyncMcpToolCallbackProvider把工具注册进 ChatClient。大模型这一层,我们把 endpoint 指向 TaoToken 的统一通道,这样 Key 和地址只维护一份。

为什么值得这么做?因为 mcp 的价值在于“工具和模型解耦”。工具定义写在 mcp-server 里,模型换供应商不影响工具;而 endpoint 统一之后,模型换供应商也不影响业务代码。飞书机器人只是最外层的入口,它不关心你背后用的是哪家模型,只关心有没有拿到回复。

2. TaoToken 前置:统一 Key 与 endpoint 的接入准备

在动手改配置之前,先把 TaoToken 这一层准备好。你可以把它理解成一个“模型调用的统一入口”:不管底层接的是哪家模型,你的 Spring-AI 只需要认一个 Base URL 和一把 Key。官网入口是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 地址是 https://taotoken.net/api ,注意 API 地址后面不加 UTM 参数。

第一步是拿 Key。登录后进控制台,在 API Keys 页面创建一个新 Key。建议按项目命名,比如feishu-bot-dev,方便后面排查是哪个应用在调用。创建完立刻复制保存,页面刷新后通常不再完整显示。控制台地址走这个 deep link:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite 。

第二步是确认模型 ID。不同模型在 TaoToken 上的 Model ID 写法可能不一样,别凭记忆写。可以在模型对话页面先手动发一条消息验证,页面地址:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite 。在模型列表里选一个你打算用的,比如通用的对话模型,记下它的准确 ID。这个 ID 后面要写进 application.yml 的model字段。

第三步是理解 Spring-AI 的配置读取逻辑。Spring-AI 的 OpenAI starter 默认读spring.ai.openai.base-url和spring.ai.openai.api-key。如果你用的是别的 starter(比如 zhipuai),配置前缀会不同。关键点是:base-url 要指向 TaoToken 的 API 地址,而不是模型厂商的官方地址。这样你的请求先到 TaoToken,再由它转发到具体模型。

这里有个容易踩的坑:base-url 结尾要不要带/v1?取决于 starter 的实现。OpenAI 兼容的 starter 通常会在 base-url 后面自动拼/v1/chat/completions,所以 base-url 写到https://taotoken.net/api即可,不要自己再加/v1,否则会变成/api/v1/v1/...。如果你不确定,先用 curl 测一下,后面第 4 节会给验证命令。

另外提醒一句:Key 不要提交到 Git。用环境变量或者本地application-local.yml覆盖,生产环境走配置中心。这是基本安全习惯,和用哪家服务无关。

3. 可复制配置:application.yml 与 mcp server 注册代码

这一节是全文的核心,直接给可复制的片段。先看 mcp-client 的application.yml。注意路径和字段名要和你的工程一致,我按 OpenAI 兼容 starter 来写:

server: port: 8080 spring: ai: openai: # 指向 TaoToken 统一 API 地址,不要带 /v1 base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: your-model-id temperature: 0.7 mcp: client: enabled: true name: feishu-mcp-client version: 1.0.0 type: SYNC request-timeout: 30s stdio: connections: weather-server: command: java args: - -jar - ./mcp-server/target/mcp-server-0.0.1-SNAPSHOT.jar lark: app-id: ${LARK_APP_ID} app-secret: ${LARK_APP_SECRET}

几个关键点解释一下。base-url写https://taotoken.net/api,api-key用环境变量注入,避免明文。model填你在模型对话页面确认过的 ID。spring.ai.mcp.client.stdio.connections下面注册了一个叫weather-server的 stdio 连接,command 是java,args 是-jar加 jar 包路径。Spring-AI 启动时会自动拉起这个子进程,并通过 stdio 和它通信。

然后是 mcp server 侧的工具定义。用@Tool注解暴露方法,Spring-AI 会自动扫描:

import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.ToolParam; import org.springframework.stereotype.Service; @Service public class WeatherService { @Tool(name = "getWeather", description = "查询指定城市的天气") public WeatherResult getWeather(@ToolParam(description = "请求参数") WeatherRequest req) { // 真实场景这里调内部天气接口 return new WeatherResult(req.city(), "SUNNY", "25C", "mcp:getWeather"); } }

接着是 mcp-client 里把工具注册进 ChatClient 的配置类。这一步决定了模型能不能“看到”工具:

import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.mcp.SyncMcpToolCallbackProvider; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import io.modelcontextprotocol.client.McpSyncClient; import java.util.List; @Configuration public class AiConfig { @Bean public ChatClient chatClient(ChatClient.Builder builder, List<McpSyncClient> mcpSyncClients) { return builder .defaultSystem("你是一个AI助手,必须调用工具 kings-spring-ai-mcp-tools 下的方法,如果工具不可用,就明确说明无法调用工具,不要编造。") .defaultToolCallbacks( SyncMcpToolCallbackProvider.builder() .mcpClients(mcpSyncClients) .build() ) .build(); } }

注意defaultToolCallbacks接收的是SyncMcpToolCallbackProvider,它会把所有McpSyncClient里的工具都注册进去。defaultSystem里明确要求模型调用工具,否则有些模型会偷懒直接编答案。这段配置和前面的 yml 是配套的:yml 负责建立 stdio 连接,配置类负责把连接里的工具挂到 ChatClient 上。

最后是飞书长连接监听器,负责收消息、异步调 botService:

import com.lark.oapi.event.EventDispatcher; import com.lark.oapi.service.im.ImService; import com.lark.oapi.service.im.v1.model.P2MessageReceiveV1; import com.lark.oapi.ws.Client; import jakarta.annotation.Resource; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; @Component public class LarkWsListener implements CommandLineRunner { @Resource private LarkBotService botService; @Resource private Client.Builder larkWsBuilder; @Override public void run(String... args) { EventDispatcher handler = EventDispatcher.newBuilder("", "") .onP2MessageReceiveV1(new ImService.P2MessageReceiveV1Handler() { @Override public void handle(P2MessageReceiveV1 event) { String messageId = event.getEvent().getMessage().getMessageId(); String contentJson = event.getEvent().getMessage().getContent(); String userText = LarkMsgParser.extractText(contentJson); ThreadUtil.execAsync(() -> botService.onUserMessage(messageId, userText)); } }) .build(); Client wsClient = larkWsBuilder.eventHandler(handler).build(); wsClient.start(); } }

botService.onUserMessage里就是调chatClient.prompt().user(userText).call().content(),拿到结果后用 messageId 回帖。整条链路到这里就闭环了:飞书推消息 → 监听器解析 → ChatClient 带工具调模型 → 模型决定是否调 mcp 工具 → 回复回帖。

4. 验证请求:从 curl 到飞书实测

配置写完别急着启动整个工程,先分层验证。第一层验证 TaoToken 的 endpoint 通不通。用 curl 直接打 chat completions:

curl -X POST https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "your-model-id", "messages": [{"role": "user", "content": "你好"}] }'

如果返回里有choices[0].message.content,说明 Key 和 endpoint 没问题。如果返回 401,检查 Key 是否复制完整、有没有多余空格。如果返回 404,大概率是 base-url 拼错了,注意/api后面由 starter 自动补/v1,curl 里要手动写全。

第二层验证 mcp server 能不能被拉起。单独跑一下 jar 包,看它是否正常启动、是否在 stdio 上等待输入。如果 jar 包启动就报错,先解决 mcp-server 自己的问题,别急着调 client。

第三层验证 Spring-AI 启动日志。启动 mcp-client,观察控制台有没有类似Connected to MCP server或者工具注册的日志。如果看到工具数量为 0,说明 stdio 连接没建立成功,回去检查 yml 里的 command 和 args 路径。

第四层才是飞书实测。启动成功后,飞书应用里应该能看到机器人上线。在飞书里给机器人发一条消息,比如“北京天气怎么样”。预期行为是:机器人先回一个“正在查询”或者直接回结果,日志里能看到MCP Tool getWeather called。如果机器人没反应,先看长连接有没有连上,日志里通常会有connected to wss://msg-frontier.feishu.cn/这样的字样。

实测下来,最容易出问题的是飞书应用的事件订阅配置。你需要在飞书开放平台把长连接模式打开,并且订阅im.message.receive_v1事件。如果事件没订阅,消息根本不会推给你的服务。另外机器人要能被拉进群或者私聊,权限里要开im:message相关权限。

验证成功的标志很明确:飞书里发消息,机器人回复内容里包含工具返回的数据(比如mcp:getWeather这个标记),同时后端日志有工具调用记录。两个都对上,说明 Spring-AI + mcp server + 飞书这条链路通了。

5. 本篇常见错排查:401、local proxy failed、reading choices

排障这块我按真实报错来写,都是这条链路上高频出现的。

401 Unauthorized。这个最直接,Key 不对或者没带上。检查三处:环境变量TAOTOKEN_API_KEY有没有真正注入到进程;yml 里api-key的${}占位符有没有被正确解析;curl 测试时 Header 是不是Bearer加空格加 Key。如果 Key 是对的还报 401,确认一下是不是把 Key 用在了错误的 endpoint 上。

local proxy failed / connection refused。这个通常出现在 mcp client 拉 mcp server 的时候。stdio 模式下,Spring-AI 会 fork 一个子进程跑java -jar,如果 jar 路径不对、Java 不在 PATH 里,就会报连接失败。排查方法:把 yml 里的 command 和 args 复制出来,在终端手动执行一遍,看能不能跑起来。另外注意相对路径是相对于 mcp-client 的工作目录,不是相对于 yml 文件。

reading choices 相关报错。比如Cannot read field "choices" because response is null或者解析响应时 NPE。这多半是模型返回了非预期结构,常见原因是 base-url 写错导致返回了 HTML 错误页,或者 model ID 不存在导致返回错误 JSON。先用 curl 确认返回体结构,再对照 starter 期望的格式。如果 curl 正常但代码报错,检查是不是 starter 版本和 API 格式不匹配。

OAuth / token 相关报错。如果你用的是需要 OAuth 的模型通道,可能会遇到 token 过期。TaoToken 的 Key 是长期有效的 API Key,不涉及 OAuth 刷新,所以这类报错一般出现在飞书侧的应用凭证上。检查lark.app-id和lark.app-secret是否正确,以及飞书应用是否开启了对应的权限。

工具不生效,模型直接编答案。这个不是报错,但很常见。原因是defaultSystem没写清楚,或者工具没注册进去。先确认启动日志里工具数量大于 0,再确认 system prompt 里明确要求调用工具。有些模型对工具调用的触发比较保守,可以在 prompt 里加一句“涉及天气、订单等实时信息时必须调用工具”。

飞书消息重复回复。长连接模式下如果服务重启,可能会重复消费事件。飞书事件本身有去重机制,但你的业务侧最好也按 messageId 做幂等。简单做法是用一个本地缓存记录已处理的 messageId,处理前先查一下。

排查顺序建议:先 curl 验 endpoint,再单独跑 mcp server,再看 Spring-AI 启动日志,最后才看飞书。从内到外,别一上来就怀疑飞书配置。

6. 把 Key 和 endpoint 收口,后续换模型不再改代码

走到这里,整条链路已经能跑通了。回头看,真正让这个方案可维护的,是把 endpoint 和 Key 收口到了配置层。mcp server 负责工具,Spring-AI 负责编排,飞书负责入口,TaoToken 负责模型通道。四层各司其职,换任何一层都不需要动其他层的代码。

如果你打算长期跑这个机器人,建议把 Coding Plan 用起来,地址是 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite ,适合需要持续调用、做 Agent 类应用的场景。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,里面有各语言 starter 的配置示例。API Keys 管理页面还是 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite ,建议按环境建不同的 Key。

最后留一个实用技巧:在botService.onUserMessage里把用户原始消息、模型回复、工具调用记录打一条结构化日志。飞书机器人出问题时,这条日志能帮你快速定位是消息没收到、模型没回、还是工具没调。比在飞书里反复发消息试要高效得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 6:51:08

动态规划进阶:力扣32、62、64三道经典题吃透DP核心

刷力扣的人应该都清楚&#xff0c;动态规划是绕不过去的一座山。而“力扣32.最长有效括号、62.不同路径、64.最小路径和”这三道题&#xff0c;恰好构成了一条很典型的DP进阶线&#xff1a;从一维状态设计到二维状态设计&#xff0c;从计数类问题到最优值问题&#xff0c;从基础…

作者头像 李华
网站建设 2026/10/2 6:51:02

十大开源的Cursor AI替代方案:用TaoToken统一Key接入实测

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/2 6:50:25

OpenClaw 下载安装教程:用 TaoToken 统一 Key 打通 config.toml 配置骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/2 6:48:03

WPF Style自定义标题栏与无边框窗口:WindowChrome原理与避坑指南

简介&#xff1a;面向 WPF 开发者的样式级自定义标题栏实现方案&#xff0c;解决无边框窗口下标题栏样式统一、按钮事件与拖动逻辑难以在 Style 中复用的痛点。资源共 15 个文件&#xff0c;以 cs 和 xaml 源码为主&#xff0c;另有 sln 工程、config 配置、resx 资源等文件&am…

作者头像 李华
网站建设 2026/10/2 6:47:45

YOLOv8校园安全监控系统开发实战:从训练到部署全流程

简介&#xff1a;面向计算机视觉与人工智能方向的毕业设计及课程设计开发者&#xff0c;这份基于YOLOv8的校园安全监控系统资源提供了从源码、完整数据集到可视化界面与部署教程的一站式方案。代码经个人毕业设计实测运行通过&#xff0c;可生成核心指标曲线、混淆矩阵、F1分数…

作者头像 李华