文章目录一、项目概述二、飞书应用创建与配置三、依赖配置四、核心代码实现五、完整配置清单六、测试与验证七、生产环境部署建议八、总结一、项目概述本文基于现有的MCP Client Demo项目(可以参考之前的博文Spring AI MCP Client 实战)详细介绍了如何将其与飞书群机器人集成实现飞书消息 → LLM 智能决策 → MCP 工具调用 → 结果回复的完整链路。1.1 核心设计理念工具选择由大模型LLM自主决策而非硬编码的命令匹配。Spring AI 的 ChatClient 已内置工具调用能力LLM 会根据工具描述自动选择调用哪个工具。1.2 整体架构1.3 数据流向1.4 集成目标在现有架构基础上增加飞书机器人模块实现功能说明事件接收接收飞书群聊 机器人消息 个人单聊消息智能决策LLM 根据工具描述自动选择调用哪个工具工具执行调用本地/MCP Server 工具结果回复将执行结果发回飞书群或单聊1.5 效果展示p2pgroup二、飞书应用创建与配置2.1 创建飞书应用登录 飞书开放平台点击「创建企业自建应用」填写应用名称如 “MCP 智能助手”和描述创建完成后在「凭证与基础信息」页面获取App ID和App Secret2.2 事件接收模式选型WebSocket vs Webhook飞书 SDK 支持两种事件接收模式对比如下对比项WebSocket 长连接Webhook 回调网络要求无需公网 IP/域名需要公网可访问的 HTTPS 地址部署复杂度低开箱即用高需配置域名、SSL、反向代理适用场景开发调试、内网部署、NAT 后服务生产环境、高可用集群实时性高服务端主动推送高飞书主动回调可靠性依赖长连接稳定性需处理断线重连依赖公网稳定性飞书有重试机制扩展性单实例受限可配合负载均衡多实例部署防火墙需允许出站 WebSocket需允许入站 HTTPS推荐方案开发/测试环境使用 WebSocket零配置即可接收事件生产环境使用 Webhook配合域名和负载均衡2.3 配置应用权限在飞书开放平台的应用管理页面进入「权限管理」添加以下权限权限名称权限标识用途必须获取与发送单聊、群组消息im:message发送消息到群/单聊是读取消息im:message:readonly接收消息事件是获取群组信息im:chat:readonly获取群信息否获取用户信息contact:user.id:readonly识别消息发送者否2.4 配置事件订阅WebSocket 模式开发环境进入「事件订阅」页面选择「使用长连接接收事件」添加事件im.message.receive_v1接收消息勾选以下子事件取群组中其他机器人和用户当前机器人的消息读取用户发给机器人的单聊消息无需配置回调地址Webhook 模式生产环境进入「事件订阅」页面选择「将事件发送至开发者服务器」配置请求地址https://your-domain.com/feishu/event/callback记录Encrypt Key和Verification Token添加事件im.message.receive_v1接收消息勾选以下子事件取群组中其他机器人和用户当前机器人的消息读取用户发给机器人的单聊消息飞书会发送验证请求需确保服务已启动并能正确响应2.5 发布应用与添加到群在「版本管理与发布」中创建版本并提交审核审核通过后将机器人添加到目标飞书群打开目标群 → 群设置 → 群机器人 → 添加机器人选择刚创建的应用机器人2.6 配置飞书机器人能力在应用管理页面进入「应用能力」→「机器人」开启机器人能力配置机器人名称和头像设置机器人描述三、依赖配置3.1 添加飞书 SDK 依赖在pom.xml中添加飞书官方 SDKdependencygroupIdcom.larksuite.oapi/groupIdartifactIdoapi-sdk/artifactIdversion2.4.4/version/dependency3.2 配置飞书应用信息在application.yml中添加飞书相关配置# 飞书配置 feishu:app-id: {FEISHU_APP_SECRET}event-mode: websocket # 开发环境用 websocket生产用 webhook# Webhook 模式配置生产环境encrypt-key: {FEISHU_VERIFICATION_TOKEN:}callback-path: /feishu/event/callbacklogging:level:io.modelcontextprotocol: debugcom.example.mcpclient: debug四、核心代码实现创建FeishuMessage.java封装飞书消息package com.example.mcpclient.feishu.model;import com.lark.oapi.service.im.v1.model.P2MessageReceiveV1;import lombok.Data;Datapublic class FeishuMessage {private String messageId;private String chatId;private String chatType; // p2p 单聊, group 群聊private String userId;private String text;private boolean mentionBot;public static FeishuMessage from(P2MessageReceiveV1 event) {FeishuMessage msg new FeishuMessage();msg.setMessageId(event.getEvent().getMessage().getMessageId());msg.setChatId(event.getEvent().getMessage().getChatId());msg.setChatType(event.getEvent().getMessage().getChatType());msg.setUserId(event.getEvent().getSender().getSenderId().getOpenId());// 解析消息内容String content event.getEvent().getMessage().getContent();msg.setText(parseTextContent(content));// 判断是否 了机器人群聊需要 单聊不需要msg.setMentionBot(isMentionBot(event));return msg;}private static String parseTextContent(String content) {try {com.fasterxml.jackson.databind.ObjectMapper mapper new com.fasterxml.jackson.databind.ObjectMapper();var node mapper.readTree(content);return node.get(text).asText();} catch (Exception e) {return content;}}private static boolean isMentionBot(P2MessageReceiveV1 event) {var mentions event.getEvent().getMessage().getMentions();return mentions ! null mentions.length 0;}public String getTextWithoutMention() {if (!mentionBot) return text;return text.replaceAll(\\S\\s*, ).trim();}public boolean isP2P() {return p2p.equals(chatType);}}4.2 飞书事件监听器创建FeishuEventListener.java处理飞书消息事件package com.example.mcpclient.feishu.listener;import com.example.mcpclient.feishu.executor.McpExecutor;import com.example.mcpclient.feishu.model.FeishuMessage;import com.lark.oapi.event.EventDispatcher;import com.lark.oapi.service.im.ImService;import com.lark.oapi.service.im.v1.model.P2MessageReceiveV1;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;ConfigurationRequiredArgsConstructorSlf4jpublic class FeishuEventListener {private final McpExecutor mcpExecutor;Value({feishu.encrypt-key:})private String encryptKey;Beanpublic EventDispatcher eventDispatcher() {return EventDispatcher.newBuilder(verificationToken, encryptKey).onP2MessageReceiveV1(new ImService.P2MessageReceiveV1Handler() {Overridepublic void handle(P2MessageReceiveV1 event) {handleMessage(event);}}).build();}private void handleMessage(P2MessageReceiveV1 event) {try {FeishuMessage msg FeishuMessage.from(event);// 单聊消息直接处理群聊消息需要 机器人才处理if (!msg.isP2P() !msg.isMentionBot()) {log.debug(群聊消息未 机器人忽略: {}, msg.getText());return;}log.info(收到飞书消息 - chatType: {}, chatId: {}, userId: {}, text: {},msg.getChatType(), msg.getChatId(), msg.getUserId(), msg.getText());// 直接将用户消息发给 LLM由 LLM 自主决策工具调用String userText msg.getTextWithoutMention();mcpExecutor.execute(msg.getChatId(), msg.getUserId(), userText);} catch (Exception e) {log.error(处理飞书消息异常, e);}}}4.3 MCP 执行器创建McpExecutor.java协调工具调用流程package com.example.mcpclient.feishu.executor;import com.example.mcpclient.feishu.sender.FeishuMessageSender;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.ai.chat.client.ChatClient;import org.springframework.scheduling.annotation.Async;import org.springframework.stereotype.Component;ComponentRequiredArgsConstructorSlf4jpublic class McpExecutor {private final ChatClient chatClient;private final FeishuMessageSender messageSender;Asyncpublic void execute(String chatId, String userId, String userMessage) {long startTime System.currentTimeMillis();try {log.info(开始处理消息 - chatId: {}, userId: {}, message: {},chatId, userId, userMessage);// 直接调用 ChatClientLLM 会自动决策是否调用工具String result chatClient.prompt().user(userMessage).call().content();// 发送结果回飞书messageSender.sendTextMessage(chatId, result);log.info(消息处理完成 - chatId: {}, 耗时: {}ms,chatId, System.currentTimeMillis() - startTime);} catch (Exception e) {log.error(消息处理失败 - chatId: {}, chatId, e);messageSender.sendTextMessage(chatId, 抱歉服务暂时出现问题请稍后重试。);}}}4.4 飞书消息发送器创建FeishuMessageSender.java封装消息发送package com.example.mcpclient.feishu.sender;import com.lark.oapi.Client;import com.lark.oapi.service.im.v1.enums.CreateMessageReceiveIdTypeEnum;import com.lark.oapi.service.im.v1.enums.MsgTypeEnum;import com.lark.oapi.service.im.v1.model.*;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Component;ComponentRequiredArgsConstructorSlf4jpublic class FeishuMessageSender {private final Client feishuClient;public void sendTextMessage(String chatId, String text) {try {String safeText escapeJson(text);String truncatedText truncateToLimit(safeText, 7900);String content String.format({\text\:\%s\}, truncatedText);CreateMessageReq req CreateMessageReq.newBuilder().receiveIdType(CreateMessageReceiveIdTypeEnum.CHAT_ID).createMessageReqBody(CreateMessageReqBody.newBuilder().receiveId(chatId).msgType(MsgTypeEnum.MSG_TYPE_TEXT.getValue()).content(content).build()).build();var resp feishuClient.im().message().create(req);if (resp.success()) {log.info(消息发送成功 - chatId: {}, messageId: {},chatId, resp.getData().getMessageId());} else {log.error(消息发送失败 - code: {}, msg: {},resp.getCode(), resp.getMsg());}} catch (Exception e) {log.error(发送飞书消息异常, e);}}private String escapeJson(String text) {return text.replace(\\, \\\\).replace(\, \\\).replace(\n, \\n).replace(\r, \\r).replace(\t, \\t);}private String truncateToLimit(String text, int limit) {return text.length() limit ? text : text.substring(0, limit - 10) \n...内容已截断;}}4.5 飞书客户端配置创建FeishuClientConfig.java配置飞书客户端package com.example.mcpclient.feishu.config;import com.lark.oapi.Client;import com.lark.oapi.event.EventDispatcher;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Value;import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;ConfigurationRequiredArgsConstructorSlf4jpublic class FeishuClientConfig {Value({feishu.app-secret})private String appSecret;Beanpublic Client feishuClient() {return Client.newBuilder(appId, appSecret).build();}Bean(initMethod start)ConditionalOnProperty(name feishu.event-mode, havingValue websocket)public FeishuWebSocketClient feishuWebSocketClient(EventDispatcher eventDispatcher) {log.info(初始化飞书 WebSocket 客户端);return new FeishuWebSocketClient(appId, appSecret, eventDispatcher);}}4.6 WebSocket 客户端实现飞书 SDK 内置了com.lark.oapi.ws.Client封装了 WSS 握手鉴权、心跳保活和断线重连package com.example.mcpclient.feishu.config;import com.lark.oapi.event.EventDispatcher;import com.lark.oapi.ws.Client;import lombok.extern.slf4j.Slf4j;Slf4jpublic class FeishuWebSocketClient {private final Client wsClient;public FeishuWebSocketClient(String appId, String appSecret, EventDispatcher eventDispatcher) {this.wsClient new Client.Builder(appId, appSecret).eventHandler(eventDispatcher).autoReconnect(true).build();}public void start() {log.info(飞书 WebSocket 客户端启动发起 WSS 连接...);wsClient.start();}}关键点wsClient.start()会发起 WSS 握手鉴权SDK 内部自动处理心跳和断线重连。如果不调用start()WebSocket 连接不会建立事件也无法接收。4.7 Webhook 回调控制器生产环境当使用 Webhook 模式时需要创建回调控制器接收飞书事件推送package com.example.mcpclient.feishu.controller;import com.fasterxml.jackson.databind.ObjectMapper;import com.lark.oapi.core.request.EventReq;import com.lark.oapi.event.EventDispatcher;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Value;import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;import org.springframework.web.bind.annotation.*;import java.util.Map;RestControllerConditionalOnProperty(name feishu.event-mode, havingValue webhook)RequiredArgsConstructorSlf4jpublic class FeishuWebhookController {private final EventDispatcher eventDispatcher;private final ObjectMapper objectMapper new ObjectMapper();Value({feishu.callback-path:/feishu/event/callback})public MapString, String handleEvent(RequestBody String body) {log.debug(收到飞书 Webhook 回调: {}, body);try {// 处理 URL 验证挑战var node objectMapper.readTree(body);if (node.has(type) url_verification.equals(node.get(type).asText())) {return Map.of(challenge, node.get(challenge).asText());}// 使用 EventDispatcher 解析并分发事件EventReq eventReq new EventReq();eventReq.setBody(body.getBytes());eventReq.setHttpPath(callbackPath);eventDispatcher.parseReq(eventReq);} catch (Exception e) {log.error(事件处理失败, e);}return Map.of(code, 0);}}4.8 启用异步支持在启动类中添加EnableAsync注解package com.example.mcpclient;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.scheduling.annotation.EnableAsync;SpringBootApplicationEnableAsyncpublic class McpClientApplication {public static void main(String[] args) {SpringApplication.run(McpClientApplication.class, args);}}五、完整配置清单5.1 application.yml 最终配置feishu:app-id: ${FEISHU_APP_ID}app-secret: ${FEISHU_APP_SECRET}event-mode: websocketencrypt-key: ${FEISHU_ENCRYPT_KEY:}verification-token: ${FEISHU_VERIFICATION_TOKEN:}callback-path: /feishu/event/callbacklogging:level:io.modelcontextprotocol: debugcom.example.mcpclient: debugcom.lark.oapi: debug5.2 环境变量说明变量名说明示例FEISHU_APP_ID飞书应用 IDcli_xxxxxFEISHU_APP_SECRET飞书应用密钥xxxxxxFEISHU_ENCRYPT_KEY消息加密密钥Webhook 模式xxxxxxFEISHU_VERIFICATION_TOKEN事件校验令牌Webhook 模式xxxxxxMINIMAX_API_KEYMiniMax API 密钥sk-xxxxx六、测试与验证6.1 测试流程启动 MCP Server确保http://localhost:8080/mcp可访问启动本服务mvn spring-boot:run在飞书群中 机器人机器人 查询北京今天的天气机器人 帮我查一下订单 ORDER-123456机器人 你好6.2 预期结果用户消息LLM 决策预期回复机器人 查询北京今天的天气自动调用local_weather_query(city北京)北京当前天气信息机器人 帮我查一下订单 ORDER-123456自动调用 MCP Server 的订单查询工具订单详情机器人 你好不调用工具直接回复AI 闲聊回复七、生产部署建议7.1 切换到 Webhook 模式修改配置feishu: event-mode: webhook callback-path: /feishu/event/callback7.2 配置 Nginx 反向代理server { listen 80; server_name your-domain.com; location /feishu/event/callback { proxy_pass http://localhost:8081; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }7.3 配置飞书事件订阅在飞书开放平台配置事件订阅地址https://your-domain.com/feishu/event/callback加密密钥与配置文件一致校验令牌与配置文件一致八、总结本文完成了从 0 到 1 的飞书机器人 MCP Client 集成核心流程如下飞书消息 → FeishuEventListener → McpExecutor→ ChatClientLLM 自主决策工具调用→ 工具执行→ FeishuMessageSender → 飞书群回复8.1 项目结构src/main/java/com/example/mcpclient/└── feishu/ # 飞书模块新增├── config/│ ├── FeishuClientConfig.java│ └── FeishuWebSocketClient.java├── controller/│ └── FeishuWebhookController.java # Webhook 回调生产环境├── listener/│ └── FeishuEventListener.java├── executor/│ └── McpExecutor.java├── sender/│ └── FeishuMessageSender.java└── model/└── FeishuMessage.java8.2 核心优势智能决策LLM 根据工具描述自动选择调用哪个工具无需硬编码命令匹配低延迟直接桥接模式端到端延迟 5s高可用支持 WebSocket/Webhook 双模式易扩展通过配置即可新增 MCP Server 连接LLM 自动发现新工具优雅降级完善的异常处理和友好提示