ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Spring AI + MCP 实战:构建自动发帖 Agent 的完整指南

Spring AI + MCP 实战:构建自动发帖 Agent 的完整指南 Spring AI 加 MCP 这套组合我大概在半年前就开始折腾了。最初最直观的感受是资料少、版本乱、API 一天一个样。但等我把整条链路真正跑通并落地了一个“自动发帖工具”之后再看这套技术栈确实是 Java 生态里做 Agent 应用的最短路径。这篇文章不聊虚的直接把项目从需求拆解到代码实现再到踩坑记录完整复盘一遍。如果你正打算用 Spring AI 做点什么真正能落地的 Agent 应用这篇内容大概率能帮你少走不少弯路。1. 项目到底要解决什么问题1.1 自动发帖需求的真实场景先交代一下项目背景。我平时要维护多个技术社区账号高频产出文章之后需要手动复制粘贴到各个平台还要针对平台风格调整标题和摘要。这套流程非常机械但极其耗时。于是我想做一个工具输入一个选题AI 自动生成适合不同平台风格的文章然后通过工具直接发布到目标社区最后把发布结果回传给我。这类工具的价值在于“内容分发的自动化”它的核心难点不在于“调用大模型写文章”而在于如何让 AI 产出不同平台风格的文案掘金、知乎、CSDN 的风格侧重完全不一样。如何让 AI 在完成写作后主动调用外部发布接口而不是仅输出一份“你应该这样发帖”的文本建议。如何管理多个平台的登录态和发布频率保证整个流程稳定可控。如果只用纯 ChatGPT 式聊天界面这些问题一个都解决不了。这也是为什么我最终选择了 Spring AI MCP 这套方案。1.2 为什么是 Spring AI而不是 LangChain 或 Python 系方案坦白说Python 生态里做 Agent 的工具链已经非常成熟LangChain、LlamaIndex、CrewAI 都有大量现成案例。但我依然选了 Spring AI理由很现实第一我所在的团队后端技术栈是 Java / Spring Boot引入 Python 服务意味着要维护一套跨语言调用体系成本远高于在现有项目里加一个依赖。第二自动发帖工具要对接的是内容平台的开放接口这些接口调用、鉴权、频率控制、重试机制都属于典型的后端工程问题。Java 生态在这块有非常成熟的方案比如 Spring Retry、Resilience4j、Quartz 任务调度。第三Spring AI 从 2025 年开始明显加速迭代MCP 内置支持的成熟度逐渐赶上 Python 生态。更重要的是它直接把 MCP 的客户端和服务端能力打包成了 Spring Boot Starter只需要加依赖写配置就能用不需要自己维护 WebSocket、SSE 连接那一套复杂状态。1.3 这套方案的技术亮点整个项目里我认为最核心的技术亮点有四个基于 Spring AI 的 ChatModel 抽象统一对接多个模型厂商不锁死在某一家的 API 上。基于 MCP 协议把“发布文章”这个动作封装成标准工具让模型在对话过程中自主触发。基于 Tool 注解 ToolCallbackProvider实现一套代码同时服务 MCP Client 和 MCP Server 两种角色。设计了一个平台适配层让同一篇内容可以按不同平台规则重新组装和发布。2. 技术底座Spring AI 与 MCP 是如何协同工作的2.1 Spring AI 的核心抽象理解了 Spring AI 是怎么组织代码的后面所有操作都会清爽很多。它的核心概念可以用四个组件讲明白ChatModel统一的大模型调用入口。无论是通义千问、DeepSeek 还是 OpenAI在代码层面都实现同一个接口切换模型时只改配置不碰业务代码。ChatClient面向开发者的流式 API支持 System Prompt、User Message、工具调用、顾问链等组合式调用。ToolCallback / ToolCallbackProvider工具回调抽象。开发者把业务方法打上 Tool 注解Spring AI 自动转换为模型可识别的 function calling 格式。Advisor类似拦截器链。可以在请求前后做统一处理比如记录日志、注入公共上下文、重试降级。自动发帖工具最关键的链路是模型在生成内容的过程中“意识到”需要发布文章然后发起一次工具调用。Spring AI 怎么感知这次调用呢它通过 ToolCallbackProvider 把本地方法序列化成 JSON Schema 发给模型模型在合适的时机返回一个 function call 请求Spring AI 再反序列化参数并执行对应方法。2.2 MCP 到底是个什么东西MCP 的全称是 Model Context Protocol模型上下文协议。它的目标是统一 AI 应用与外部工具、数据源之间的接入方式。有人说它是“AI 应用的 USB-C 接口”这个类比很贴切。没有 MCP 之前想让大模型调用一个新工具你要自己写一套 JSON Schema、自己管理传输、自己处理上下文注入。有了 MCP 之后所有这些都被标准化了。MCP 协议里有三种角色Host宿主程序就是你的 Agent 应用负责管理连接和上下文。Client运行在 Host 内部的连接器负责与 Server 通信。Server暴露能力的一方可以提供三类资源Tools工具、Resources资源、Prompts提示词模板。服务端与客户端的传输方式主要有两种本地 stdio 和远程 HTTP/SSE。Spring AI 的 MCP Starter 把这两种传输方式的客户端和服务端都封装好了。2.3 为什么自动发帖必须要 MCP你可能会问不用 MCP我自己用 WebClient 调平台接口然后硬编码在 Agent 逻辑里不行吗当然行但工程上会很痛苦。比如今天要在 Agent 里加一个“查询发布状态”的工具传统做法是改 Agent 的代码、重新调整 function calling 的 JSON Schema、维护上下文注入逻辑。但用 MCP 之后“查询发布状态”可以做成一个独立的 MCP ServerAgent 通过配置挂载这个 Server就能动态发现工具并调用。当工具数量增多、团队协作时这种解耦的价值会被迅速放大。另一个理由是模型上下文的组织。MCP 协议允许 Server 向客户端暴露 Resources也就是一些可供模型读取的数据。在自动发帖场景里平台规则、排版规范、历史爆款标题都可以作为 Resources 注入系统提示词不需要写死在 Prompt 里。2.4 工具调用的闭环从意图到执行大模型本身并不具备调用外部系统的能力它只是预测文本。工具调用之所以有效是因为模型在训练阶段接触了大量 function calling 的样本当对话上下文里出现工具描述时模型会尝试输出结构化的 function call。完整闭环如下用户给 Agent 下达指令写一篇关于 MCP 的文章并发布到掘金。Agent 将系统提示词、历史消息、工具 Schema 一起发送给大模型。模型生成文章内容后判断需要调用 publishArticle 工具。模型返回一条工具调用请求包含意图中的关键字段标题、内容、标签、目标平台。Spring AI 解析请求找到对应的 Java 方法并执行实际调用掘金平台发布接口。发布结果作为函数响返回给模型模型继续生成一段“发布成功”的总结。这就是 Agent 能够“动手做事”而不是“纸上谈兵”的底层原理。3. 自动发帖工具的架构设计与模块划分3.1 总体架构与模块边界整个项目我划分成五个模块职责非常清晰模块职责web 层提供 REST 接口接收发帖任务请求返回任务状态agent 层承载 ChatModel 调用、Prompt 编排、工具绑定逻辑mcp-server 层实现 MCP Server暴露 publishArticle、queryPublishStatus 等工具platform-adapter 层封装不同内容平台的发布接口差异task-store 层记录发帖任务状态支持重试和幂等模块之间通过接口隔离MCP Server 层不直接依赖具体平台的 SDK而是调用 platform-adapter 层的统一接口。这样后续新增一个平台只需要写一个 adapter不用动 Agent 逻辑。3.2 平台适配层设计一套接口通吃多个平台内容平台的开放接口差异很大有的平台支持完整 Markdown有的只支持富文本有的要求标题必须在 50 字以内有的标签数量最多 5 个。我设计了一个 PlatformPublisher 接口public interface PlatformPublisher { String platformId(); PublishResult publish(ArticleDraft draft); PublishStatus queryStatus(String taskId); boolean validate(ArticleDraft draft); }每个平台实现一个类注册到 Spring 容器里。MCP Server 的 Tool 方法内部通过 platformId 找到对应实现类。这里有一个关键设计Tool 方法只做分发和结果组装不处理平台细节。这样模型传入的参数保持统一结构平台差异全部收敛在 adapter 层。3.3 Prompt 模板设计一个模型分身出演多个平台自动发帖不只是把同一篇文章发到多个平台。不同平台的读者调性不同掘金读者偏爱技术深度知乎读者喜欢背景分析和原理推导CSDN 读者看重可直接落地的步骤。如果都用同一篇正文分发效果会很差。我的做法是把 Prompt 设计成“平台风格注入”模式。系统提示词里定义一个变量{platformStyle}实际发送时从配置中心读取对应平台的风格描述组装到 System Prompt 中。比如掘金的风格描述是“开头用场景切入避免官方文档式的枯燥说明章节之间要有递进逻辑代码必须有完整注释。”为了让模型输出稳定的 Markdown 结构我还在 Prompt 里附加了输出格式约束要求标题必须高度概括、正文必须包含 H2 分段、代码必须标注语言类型。3.4 数据模型文章草稿与发布任务文章草稿的结构我尽量接近通用标准public class ArticleDraft { private String title; private String content; private ListString tags; private String summary; private String coverUrl; private MapString, Object extra; }发布任务则多一层状态public class PublishTask { private String taskId; private String platformId; private ArticleDraft draft; private PublishState state; // CREATED, PUBLISHING, SUCCESS, FAILED private String errorMsg; private LocalDateTime createdAt; }task-store 模块维护这张任务表所有发布操作先登记任务再异步执行。这样即使某个平台接口超时也可以根据任务状态做补偿重试。4. 实操从 0 到 1 搭建 Spring AI MCP 自动发帖项目4.1 工程初始化与依赖引入项目基于 Spring Boot 3.5建议 JDK 21 以上。Spring AI 的版本变化很快我踩过的坑是不同版本之间 API 差异很大所以先把依赖锁定。dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-alibaba/artifactId version2.0.5/version /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-mcp-client-webmvc-starter/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-mcp-server-webmvc-starter/artifactId /dependencyspring-ai-starter-alibaba 是阿里云提供的 Spring AI 实现它会自动配置 ChatModel同时兼容 DashScope 生态。如果你用的是其他厂商也可以换成官方 starter 并手动指定 base-url。配置文件核心部分spring: ai: dashscope: api-key: ${AI_API_KEY} chat: options: model: qwen-plus mcp: server: name: publish-tool-server version: 1.0.0 enabled: true启动项目后Spring AI 会自动扫描标有 Tool 或 RegisteredTool 注解的 Bean把它们注册到 MCP Server 的 Tool 列表里。4.2 实现 MCP Server把发帖能力暴露给 Agent这是一个典型的 Tool 方法实现。注意描述要写清楚因为大模型完全依赖这段描述来决定何时调用该工具。Component public class PublishTools { private final PlatformPublisherRegistry registry; public PublishTools(PlatformPublisherRegistry registry) { this.registry registry; } Tool(description 发布文章到指定的内容平台返回包含发布地址的结果) public PublishResult publishArticle( ToolParam(description 平台标识zhihu、juejin、csdn、cnblogs 之一) String platformId, ToolParam(description 文章标题) String title, ToolParam(description 文章正文Markdown 格式) String content, ToolParam(description 文章标签最多 5 个) ListString tags) { PlatformPublisher publisher registry.getPublisher(platformId); if (publisher null) { return PublishResult.failed(不支持的平台: platformId); } ArticleDraft draft ArticleDraft.builder() .title(title.trim()) .content(content) .tags(tags null ? List.of() : tags) .build(); return publisher.publish(draft); } Tool(description 查询文章在某平台的发布状态) public PublishStatus queryPublishStatus( ToolParam(description 发布任务 ID) String taskId, ToolParam(description 平台标识) String platformId) { return registry.getPublisher(platformId).queryStatus(taskId); } }这里有几个细节非常关键一是ToolParam的 description 一定要写清楚模型全靠这些描述来生成正确的 JSON 参数。如果描述含糊模型很可能会漏传 platformId 或 content。二是返回值结构必须稳定。我封装了一个 PublishResult 对象包含 code、message、publishUrl、taskId 四个字段序列化成 JSON 返回给模型。模型拿到 taskId 之后可以继续调用 queryPublishStatus 来确认最终状态。三是在发布前做参数校验。模型生成的文章标题偶尔会超长发布前先做长度校验避免平台接口直接报错。4.3 实现 MCP Client让 Agent 能感知并使用发帖工具如果你是让 Spring AI 应用作为 Agent 宿主同时通过 MCP 客户端连接外部的发帖 Server那还需要配置一个客户端。这里我提供两种做法。做法一本地直连把发布工具和 Agent 放在同一个 Spring Boot 应用里。这种模式下不需要显式配置 MCP Client直接注入 ToolCallbackProviderConfiguration public class AgentConfig { Bean public ToolCallbackProvider publishToolCallbackProvider( PublishTools publishTools) { return MethodToolCallbackProvider.builder() .toolObjects(publishTools) .build(); } }MethodToolCallbackProvider 会把 Tool 注解方法自动转换成 ToolCallback 列表。做法二远程 MCP 连接。Agent 应用作为 MCP Client连接独立部署的发帖 MCP ServerConfiguration public class McpClientConfig { Bean public ToolCallbackProvider remotePublishTools() { var transport HttpTransport.builder() .url(http://publish-mcp-server:8080/mcp) .build(); var mcpClient McpClientSync.builder(transport) .build(); return new McpToolCallbackProvider(mcpClient); } }这里说明一下本地直连模式适合发帖服务与 Agent 同进程部署的场景远程模式适合把发帖能力独立成公共服务、供多个 Agent 复用的场景。我一开始用的远程模式后来发现本地直连更稳减少了网络抖动这一个变量而且调试工具调用链也更方便。4.4 业务编排从选题到成稿再到发布这是整个项目里最有“Agent”味道的部分。我基于 ChatClient 的流式 API 构建了一个发布流程String systemPrompt 你是一名资深技术内容运营专家。你会收到一个主题关键词 需要按给定平台风格撰写一篇文章并使用发布工具完成实际发布。 步骤要求 1. 先用一句话确认你理解的平台风格方向。 2. 撰写文章标题要克制正文要有完整结构。 3. 发布前检查内容避免纯空洞的套话。 4. 调用 publishArticle 工具完成发布。 ; String response chatClient.prompt() .system(systemPrompt) .user(主题Spring AI MCP 实战目标平台juejin补充要求附带真实代码示例。) .tools(toolCallbackProvider) .call() .content();这里有个非常重要的设置一定要检查最终响应里是否真的发生了工具调用。因为大模型不是每次都会按预期调用工具它可能长篇大论分析完就不动手了。所以我在调用之后检查了响应中的 ToolCall 列表。ChatResponse resp chatClient.prompt() .system(systemPrompt) .user(userMsg) .tools(toolCallbackProvider) .call(); ListToolCall calls resp.getResult().getOutput().getToolCalls(); if (calls null || calls.isEmpty()) { // 说明模型没调工具需要二次提醒或重置上下文 }如果发现没有工具调用我会把上一步的响应作为历史消息再追加一条“请直接调用 publishArticle 工具完成发布”的 user message强制模型继续执行。实测下来这种补偿式的二次提醒比单纯修改 System Prompt 更可靠。4.5 多模型接入与效果对比项目落地之后我先后试过 qwen-plus、DeepSeek-V3 和 gpt-4o-mini。在工具调用的稳定性方面qwen-plus 的中文指令跟随率不错但偶尔会把平台标识写错DeepSeek-V3 在长文章生成上表现更好代码质量更高gpt-4o-mini 的工具调用非常规范但单价稍高。Spring AI 切换模型时只需要改配置文件。这一点是大厂框架的好处模型是用来试的架构不需要跟着模型走。实际操作中我会给不同平台分配不同模型。比如知乎的长文分析用 DeepSeek-V3掘金的代码教程用 qwen-plus因为它的中文技术术语更稳。这个策略可以写在平台 adapter 的配置里Agent 发起任务时自动选择。5. 运行链路一次自动发帖请求的完整旅程5.1 请求入口与上下文构造REST 接口长这样PostMapping(/publish) public ResponseEntityString createTask(RequestBody PublishRequest req) { // 构造会话消息 Message userMsg new UserMessage(主题 req.getTopic() 目标平台 req.getPlatformId() 平台风格 platformStyleService.getStyle(req.getPlatformId())); ChatResponse response chatClient.prompt() .system(systemPromptBlock(req.getPlatformId())) .messages(userMsg) .tools(publishToolCallbackProvider) .call(); return ResponseEntity.ok(response.getResult().getOutput().getText()); }这里比较关键的一点是平台风格不是放在 UserMessage 里而是放到 System Prompt 里。因为工具调用、内容输出都发生在上下文窗口内系统消息会始终占据高位权重模型更有可能遵循平台风格要求。5.2 模型调用中的多轮工具协商一次简单的发布流程实际可能产生两轮甚至三轮模型调用。第一轮模型可能在生成文章后返回一个 publishArticle 的 function callSpring AI 执行发布动作后把结果作为 function result 重新送入第二轮模型调用。第二轮模型拿到发布结果之后会生成最终反馈“文章已发布到掘金地址为 https://juejin.cn/post/xxx全文共 3200 字。”这第二轮调用是不需要代码干涉的Spring AI 内部自动处理 function result 的回填。这里有一个值得注意的现象如果发布接口返回的错误信息不够结构化模型会“理解错误”。比如平台接口返回了 401 未授权模型可能生成一句“文章发布失败但你的内容质量很高”这种毫无意义的回复。所以我在 PublishResult 里强制返回了 code 和 message并且 message 要尽量具体比如“登录态失效请重新授权掘金账号”。这样模型才能给出有意义的补救建议。5.3 异常路径与重试机制发布动作本身不是原子的可能平台侧已经创建了文章但响应超时了。所以我做了两层保障第一层是幂等设计。publishArticle 方法在调用平台接口前先按 title content 的 hash 生成一个业务幂等键存到 task-store 表里。如果这个键已经存在且状态为 SUCCESS则直接返回已存在的 publishUrl不再重复发帖。第二层是补偿重试。如果平台接口抛出 IOException我会把任务状态标记为 FAILED然后交给 Spring Retry 在延迟队列中重试。重试不超过三次间隔分别为 10 秒、60 秒、300 秒。这样处理之后自动发帖的失败率从我最初测试时的百分之十几降到了百分之三以内。6. 实战中踩过的坑与排查手册6.1 MCP Server 连接失败的排查思路我遇到过的最典型报错是启动时 MCP 客户端连接不上服务端应用直接启动失败。原因多半是传输协议配置错了。MCP 有 stdio 和 HTTP/SSE 两种传输方式。如果使用 HTTP 传输服务端必须保证额外的 servlet mapping 存在。Spring AI 的 webmvc starter 默认在/mcp路径下暴露端点。排查步骤建议按顺序来确认服务端控制台日志里出现了“MCP Server initialized successfully”字样。直接 curl 一下http://localhost:8080/mcp/sse看是否能正常返回 SSE 流。如果连接报 404检查 spring-ai-mcp-server-webmvc-starter 是否被正确引入有些版本需要显式设置spring.ai.mcp.server.enabledtrue。如果是 stdio 模式检查子进程路径是否配置正确Windows 和 Linux 的命令行参数有所不同。6.2 强制工具调用防止大模型“临阵脱逃”这是自动发帖场景里最折磨人的一个问题。模型可能在生成文章后直接输出“关于发布请参考平台文档”就是不调用工具。我尝试过两种解法。第一种是在 System Prompt 末尾加强制指令例如“你必须调用发布工具这是本任务的最后一步不要只给出建议。”第二种是设置 Spring AI 的 tool choice 策略。Spring AI 2.x 提供了FunctionCallingOptions可以指定toolChoice为某个具体工具名强制模型调用。var options FunctionCallingOptions.builder() .toolChoice(publishArticle) .build(); String response chatClient.prompt() .system(systemPrompt) .user(userMsg) .options(options) .tools(toolCallbackProvider) .call() .content();实测下来Tool Choice 策略的效果远好于 Prompt 指令。如果发现模型总是只输出文本不调工具优先检查 toolChoice 有没有生效。另外某些模型对 toolChoice 的支持不够好这时候就要靠我在 4.4 节里提到的二次提醒方案兜底。6.3 平台登录态与安全风控自动发帖涉及账号操作平台风控是绕不开的问题。我的经验是必须做发布前人工确认。具体做法是平台 adapter 在真正执行发布之前先创建一个审核任务推送给管理员确认。管理员在管理后台点“允许发布”任务才真正执行。这不是技术失败而是安全设计。MCP 工具是给 AI 执行的但 AI 的决策不可 100% 信赖涉及对外发布、写操作、资金操作的场景保留一个人工闸门是最好的兜底。另外要处理好凭据存储。发布平台的 token 不要硬编码在项目里建议放在配置中心或密钥管理服务里并在调用前做解密。6.4 频率限制与任务队列多个平台对开放接口都有频率限制短时间高频发帖容易被临时封禁。我在 task-store 模块加了一个简单滑动窗口同一个平台 ID 一分钟内最多发布两次超出的任务延迟到下一个窗口执行。另外所有发布操作都应该走异步线程池不要在 Tool 方法里搞同步阻塞。因为 Spring AI 在模型调用持锁期间执行工具方法如果发布耗时超过大模型底层的响应超时时间整个对话就断了。我做法是publishArticle 内快速记录任务并返回 taskId真正的平台调用放到 CompletableFuture 异步执行Agent 再通过 queryPublishStatus 轮询确认最终结果。6.5 Spring AI 版本迁移的兼容性问题Spring AI 版本迭代之快在 Java 生态里很难找到第二个。1.0.x 到 2.0.x 的迁移中最直观的变化是 MCP 配置类名和包名变了比如McpToolCallbackProvider的构造方式还有FunctionCallingOptions从实验包移动到正式包。我的建议是项目一开始就用 BOM 锁定版本尽量跟随相应版本的官方示例。不要直接照抄老博客的代码因为那可能适用于半年之前的旧版本。如果你在排查问题的时候发现网上资料和本地表现对不上先检查版本差异大概率是这个原因。7. 经验总结与后续扩展这个项目让我对 Spring AI 和 MCP 有了完全不同的理解。最开始我以为 MCP 只是一个“接口协议标准”但实际做下来发现它真正解决了 Agent 应用里最脏最累的活工具发现、工具调用、上下文注入。我不用再为每个平台写一套 JSON Schema也不用自己维护 function calling 的映射逻辑这块 Spring AI 内置的 MCP 支持已经做得足够好。想扩展的话有几个方向很值得继续做下去。一个是把整个 Agent 能力做成 MCP Server 暴露出去让其他 Agent 应用也能调用这个发帖服务相当于把内容分发能力变成一个公共服务。第二个是接入 MCP 的 Resources 能力把平台风格库、历史爆款数据做成可读取的资源让模型生成前先参考一下过去的经验。第三个是在 task-store 模块上叠加定时任务做成完全自动化的内容运营日历每天早上按计划选题、生成、排期发布。如果你正准备在 Java 生态里做 Agent 应用我的建议是从一个小而真实的需求入手比如这个自动发帖工具。MCP 的价值只有在你真正把工具挂到 Agent 上跑起来之后才能体会得到。希望大家绕开我踩过的那些坑早日跑通自己的第一版 Agent 应用。
返回列表