尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

【Java开发MCP】CSDN发帖MCP开发与使用:从Stdio到SpringAI的完整落地

【Java开发MCP】CSDN发帖MCP开发与使用:从Stdio到SpringAI的完整落地 1. 为什么 Java 开发者需要一个 CSDN 发帖 MCP先说清楚这个东西是什么。MCP 全称 Model Context Protocol你可以把它理解成一套“AI 和外部系统之间的插座标准”。以前你想让大模型帮你发一篇文章到 CSDN得自己写一堆胶水代码把模型的输出解析出来、拼成 HTTP 请求、处理 Cookie、再手动触发。MCP 出现之后这件事变成了你写一个符合协议的工具服务模型自己决定什么时候调用、传什么参数调用完把结果拿回去继续推理。那为什么是 Java因为大量后端同学的主力技术栈就是 Java尤其是 Spring 生态。你不可能为了玩一个 MCP 就把整个工程换成 Python。Spring AI 从 1.0.0-M6 开始对 MCP 的支持已经比较完整了spring-ai-mcp-server-spring-boot-starter这个 starter 能让你用几个注解就把一个普通 Spring Bean 变成模型可调用的工具。这篇要做的就是基于 Spring AI 构建一个 CSDN 发帖 MCP Server走 Stdio 通信模式本地跑通然后集成到 Spring AI 的客户端里验证一次真实发帖。适合谁看有 Java 基础、用过 Spring Boot、想搞清楚 MCP 到底怎么落地的人。如果你只是想调个 API 发文章那用 Postman 就够了不需要 MCP。MCP 的价值在于“让模型自主编排工具”比如你后面可以做一个 Agent让它先查资料、再写草稿、再调发帖工具全程不用你插手。Stdio 模式又是怎么回事MCP 有两种常见通信方式SSE走 HTTP适合远程服务和 Stdio走标准输入输出适合本地进程。Stdio 的好处是简单、无网络依赖、启动快客户端直接java -jar拉起你的进程通过 stdin/stdout 交换 JSON-RPC 消息。缺点是一个进程只能服务一个客户端。对于本地开发和个人使用Stdio 是最省事的选择。我试过把整个链路拆成“先写一个能跑的 HTTP 调用再把它包装成 MCP 工具”这样排错会容易很多。因为 MCP 层出问题时你很难判断是协议问题还是业务问题先把业务跑通再套协议壳是更稳的路径。下面就从环境准备开始一步步来。2. 环境准备与 CSDN 接口抓包Cookie 和 saveArticle 怎么拿开发环境这块不复杂但版本要对齐否则 Spring AI 的 starter 会拉不起来。JDK 17 是硬性要求Spring Boot 3.4.3Spring AI 用 1.0.0-M6。Maven 3.6 以上。这些版本组合我实测能跑通别自己乱升M6 和后面的 RC 版本 API 有差异。CSDN 账号方面你需要一个已实名认证、开通了博客的账号。没认证的话发帖接口会返回权限错误。这一步没什么技术含量但绕不过去。关键在抓包。打开浏览器登录 CSDN进入创作中心用 Markdown 编辑器随便写一篇测试文章按 F12 打开开发者工具切到 Network 面板勾选 Preserve log然后点“发布”或“保存草稿”。你会看到一个请求POST https://bizapi.csdn.net/blog-console-api/v3/mdeditor/saveArticle这个就是我们要的接口。点开它看 Request Headers重点抓两样东西Cookie 和那几个x-ca-*签名头。Cookie 是身份凭证x-ca-key、x-ca-nonce、x-ca-signature是 CSDN 的网关签名。这里有个坑x-ca-nonce和x-ca-signature是每次请求动态生成的理论上你复用抓到的旧值也能用一段时间但不保证长期有效。如果后面发帖返回签名错误就得重新抓一次。把整个请求右键 Copy as cURL导入到 Apifox 或 Postman 里你能更清楚地看到请求体结构。请求体是 JSON字段包括title、markdowncontent、contentHTML、tags、categories、readType、type、pubStatus等。readType控制可见性private是仅自己可见public是公开。pubStatus设成draft就是存草稿设成publish才是正式发布。建议第一次测试用privatedraft避免误发。这里要提醒一句Cookie 属于敏感信息别硬编码进代码提交到 Git。用环境变量注入后面配置文件里我会写成${CSDN_API_COOKIE}的形式。抓包完成后你手上应该有三样东西完整的接口 URL、一份可用的 Cookie、一份请求体字段清单。有了这些就可以开始写 Java 代码了。下一节先把 Maven 依赖和配置文件搭好。3. 可复制的 Spring AI MCP Server 配置pom.xml 与 application.yml这一节给你能直接抄的配置。先看pom.xml的关键部分。父工程用 Spring Boot 3.4.3属性里声明 Spring AI 版本 1.0.0-M6。properties maven.compiler.source17/maven.compiler.source maven.compiler.target17/maven.compiler.target project.build.sourceEncodingUTF-8/project.build.sourceEncoding spring-ai.version1.0.0-M6/spring-ai.version spring-boot.version3.4.3/spring-boot.version /properties parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.4.3/version relativePath/ /parent依赖里最核心的是 MCP Server starter它负责把 Stdio 通信、JSON-RPC 解析、工具注册这些脏活全包了dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-mcp-server-spring-boot-starter/artifactId /dependencyHTTP 客户端我用 Retrofit比 RestTemplate 写起来干净接口式声明很适合这种固定 APIdependency groupIdcom.squareup.retrofit2/groupId artifactIdretrofit/artifactId version2.9.0/version /dependency dependency groupIdcom.squareup.retrofit2/groupId artifactIdconverter-jackson/artifactId version2.9.0/version /dependencyMarkdown 转 HTML 用 Flexmark因为 CSDN 的content字段要的是 HTML而模型生成的一般是 Markdowndependency groupIdcom.vladsch.flexmark/groupId artifactIdflexmark-all/artifactId version0.64.8/version /dependency别忘了dependencyManagement里导入 Spring AI BOM否则 starter 版本对不上dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version${spring-ai.version}/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement然后是application.yml。Stdio 模式最关键的两行是web-application-type: none和banner-mode: off。前者告诉 Spring 不要启动内嵌 Tomcat后者避免 banner 输出污染 stdout——记住Stdio 模式下 stdout 是协议通道任何多余输出都会破坏 JSON-RPC 消息。server: servlet: encoding: charset: UTF-8 force: true enabled: true spring: application: name: mcp-server-csdn ai: mcp: server: name: ${spring.application.name} version: 1.0.0 main: banner-mode: off web-application-type: none csdn: api: categories: ${CSDN_API_CATEGORIES} cookie: ${CSDN_API_COOKIE} logging: pattern: console: file: name: data/log/${spring.application.name}.log注意日志配置我把 console 的 pattern 留空了因为 Stdio 模式下控制台输出会干扰协议。日志全部写到文件里排查问题时去看data/log/mcp-server-csdn.log。这个细节很多人会踩坑启动后客户端一直报解析错误八成就是有日志打到了 stdout。配置类CSDNApiProperties用ConfigurationProperties(prefix csdn.api)把这两个值读进来ConfigurationProperties(prefix csdn.api) Component public class CSDNApiProperties { private String cookie; private String categories; // getter/setter 省略 }到这里配置骨架就搭好了。下一节写业务代码Retrofit 接口、DTO、Markdown 转换、以及最关键的Tool注解方法。4. 工具注册与发帖调用从 Retrofit 接口到 Tool 方法先定义 Retrofit 接口。请求头这块要完整模拟浏览器尤其是x-ca-*那几个签名头缺一个都可能被网关拒掉public interface ICSDNService { Headers({ accept: */*, content-type: application/json, origin: https://editor.csdn.net, referer: https://editor.csdn.net/, user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36, x-ca-key: 203803574, x-ca-nonce: a70ca99e-8bfa-46d1-8d12-363c72707ebe, x-ca-signature: NGLzlIyvH7BuQgGJrgfGOzao0SVpzdTs4aTcw3hio6Y, x-ca-signature-headers: x-ca-key,x-ca-nonce }) POST(/blog-console-api/v3/mdeditor/saveArticle) CallArticleResponseDTO saveArticle( Body ArticleRequestDTO request, Header(Cookie) String cookieValue ); }请求 DTO 字段比较多但大部分可以给默认值。核心是title、markdowncontent、content、tags、categories、readType、pubStatusData public class ArticleRequestDTO { private String title; private String markdowncontent; private String content; private String readType private; private String level 0; private String tags; private Integer status 0; private String categories 测试; private String type original; private Boolean authorized_status true; private String Description; private String source pc_mdeditor; private String pubStatus draft; private Integer is_new 1; // 其余字段给默认值即可 }Markdown 转 HTML 的工具类静态初始化解析器和渲染器避免每次调用都重建public class MarkdownConverter { private static final Parser parser; private static final HtmlRenderer renderer; static { MutableDataSet options new MutableDataSet(); parser Parser.builder(options).build(); renderer HtmlRenderer.builder(options).build(); } public static String convertToHtml(String markdown) { if (markdown null || markdown.trim().isEmpty()) { return ; } return renderer.render(parser.parse(markdown)); } }现在到最关键的一步用Tool注解把方法暴露给模型。Spring AI 的MethodToolCallbackProvider会扫描带Tool的 Bean 方法自动生成 JSON Schema 描述模型看到的就是这些描述。Slf4j Service public class CSDNArticleService { Resource private ICSDNPort port; Tool(description 发布文章到CSDN需要提供标题、Markdown内容、标签和简述) public ArticleFunctionResponse saveArticle(ArticleFunctionRequest request) throws IOException { log.info(CSDN发帖标题:{} 标签:{}, request.getTitle(), request.getTags()); return port.writeArticle(request); } }请求参数类ArticleFunctionRequest用 Jackson 注解描述每个字段这些描述会变成模型看到的参数说明Data JsonInclude(JsonInclude.Include.NON_NULL) public class ArticleFunctionRequest { JsonProperty(required true, value title) JsonPropertyDescription(文章标题) private String title; JsonProperty(required true, value markdowncontent) JsonPropertyDescription(文章内容Markdown格式) private String markdowncontent; JsonProperty(required true, value tags) JsonPropertyDescription(文章标签英文逗号隔开) private String tags; JsonProperty(required true, value Description) JsonPropertyDescription(文章简述) private String Description; JsonProperty(required false, value readType) JsonPropertyDescription(可见性private 或 public默认 private) private String readType private; public String getContent() { return MarkdownConverter.convertToHtml(markdowncontent); } }主启动类里注册 Retrofit Bean 和 ToolCallbackProviderSpringBootApplication public class McpServerApplication { public static void main(String[] args) { SpringApplication.run(McpServerApplication.class, args); } Bean public ICSDNService csdnService() { Retrofit retrofit new Retrofit.Builder() .baseUrl(https://bizapi.csdn.net/) .addConverterFactory(JacksonConverterFactory.create()) .build(); return retrofit.create(ICSDNService.class); } Bean public ToolCallbackProvider csdnTools(CSDNArticleService service) { return MethodToolCallbackProvider.builder() .toolObjects(service) .build(); } }这里有个容易忽略的点MethodToolCallbackProvider注册的是整个对象它会扫描对象里所有Tool方法。如果你一个 Service 里有多个工具方法都会被注册进去。工具名默认是方法名所以saveArticle就是模型看到的工具名。代码写完后mvn clean package打包得到mcp-server-csdn-app.jar。注意spring-boot-maven-plugin的mainClass要指向McpServerApplication否则打出来的 jar 没有主清单java -jar会报 no main manifest attribute。5. 本地 Stdio 启动与发帖验证mcp-servers-config.json 配置打包完成后先别急着集成到客户端单独测一下 jar 能不能起来。直接命令行跑java -Dspring.ai.mcp.server.stdiotrue \ -Dfile.encodingUTF-8 \ -jar target/mcp-server-csdn-app.jar如果进程挂住不动、没有报错退出说明 Stdio 服务正常启动了它在等 stdin 输入。按 CtrlC 退出即可。如果看到 Spring banner 或者 Tomcat 启动日志说明web-application-type: none没生效回去检查 yml。接下来配置客户端。Spring AI 的 MCP 客户端通过一个 JSON 文件描述要启动哪些 Server文件名通常叫mcp-servers-config.json{ mcpServers: { mcp-server-csdn: { command: java, args: [ -Dspring.ai.mcp.server.stdiotrue, -Dfile.encodingUTF-8, -Dconsole.encodingUTF-8, -Dstdin.encodingUTF-8, -Dstdout.encodingUTF-8, -Dstderr.encodingUTF-8, -jar, D:\\code\\JAVA\\mcp-server-csdn\\stdio\\mcp-server-csdn-app.jar ], env: { CSDN_API_CATEGORIES: Java, CSDN_API_COOKIE: 你的真实Cookie } } } }这里三件套必须齐全command是启动命令args是参数env是环境变量。Base URL 在代码里写死了https://bizapi.csdn.net/Key 就是 CookieModel ID 在 MCP 场景下对应的是工具名saveArticle。这三个概念在 MCP 里和普通 API 调用不太一样但对应关系要清楚。编码参数那一堆-Dxxx.encodingUTF-8不是凑数的。Windows 默认 GBK中文标题和内容传过去会乱码加上这些参数强制 UTF-8。Linux/Mac 上可以省略但加上无害。配置好后在 Spring AI 客户端里问一句“有哪些工具可以使用”正常会返回类似您可以使用以下工具 1. functions.saveArticle: 用于将文章发布到CSDN 2. multi_tool_use.parallel: 用于同时运行多个工具看到saveArticle就说明工具注册成功了。然后让它发一篇测试文章比如“帮我发一篇标题为‘MCP测试’的草稿到CSDN内容是 hello world标签 Java”。模型会调用saveArticle传入参数你的 Server 收到请求后走 Retrofit 发到 CSDN返回文章 URL 和 ID。验证成功的标志日志文件里出现请求CSDN发帖的 req/res 记录且 response 的code是 200data.url有值。去 CSDN 创作中心的草稿箱能看到那篇文章。第一次建议用readTypeprivatepubStatusdraft确认无误后再改成公开。6. 常见报错排查401、local proxy failed 与 reading choices这一节列几个真实会撞上的错误以及怎么定位。401 Unauthorized 或 code 非 200九成是 Cookie 失效。CSDN 的 Cookie 有效期不长隔天可能就过期。重新抓一次更新env里的CSDN_API_COOKIE。另外检查 Cookie 有没有被 shell 转义里面有分号和空格JSON 里要完整保留。local proxy failed / connection refused客户端报这个通常是 Server 进程没起来。先手动java -jar跑一遍看有没有异常堆栈。常见原因是 jar 路径写错、JDK 版本不对低于 17 会报 UnsupportedClassVersionError、或者web-application-type没设成 none 导致端口冲突。reading choices 相关解析错误这个报错一般出现在客户端侧意思是它从 stdout 读到的不是合法 JSON-RPC 消息。根因是 Server 往 stdout 打了非协议内容。检查三处banner-mode: off有没有生效、日志有没有配到 console、有没有System.out.println残留。我踩过的坑就是在测试类里留了个System.out.println打包后忘了删客户端一直解析失败。OAuth / 签名错误如果返回x-ca-signature相关错误说明 CSDN 的网关签名校验没过。x-ca-nonce和x-ca-signature是抓包时的快照可能已过期。重新抓一次请求把这两个头更新到Headers里。长期方案是研究签名算法动态生成但个人使用重新抓包更省事。中文乱码标题或内容变成问号。确认-Dfile.encodingUTF-8等参数都加上了且application.yml里server.servlet.encoding.force: true。另外 Flexmark 转换时如果源字符串编码不对也会乱码确保读入的 Markdown 是 UTF-8。工具没被识别客户端问“有哪些工具”时看不到saveArticle。检查Tool注解有没有加、ToolCallbackProviderBean 有没有注册、MethodToolCallbackProvider.builder().toolObjects()传的对象对不对。还有一个隐蔽问题如果方法抛异常且没被捕获工具注册阶段可能静默失败加日志确认。排查顺序建议先手动跑 jar 确认进程正常再用一个最简单的 HTTP 测试类直接调 Retrofit 接口确认业务通最后才走 MCP 客户端。分层定位比一上来就调 MCP 快得多。7. 接入 TaoToken 与后续扩展工具跑通之后如果你想让模型侧更稳定地调用可以把模型接入层换成 TaoToken。它的 API 地址是https://taotoken.net/api兼容 OpenAI 风格的接口Spring AI 里配置base-url和api-key就能用。模型对话调试可以去 模型对话 页面直接试确认工具调用返回正常。如果你打算长期做编码类 Agent比如让模型自动写文章、自动发帖、自动回评论那用 Coding Plan 会更划算额度按编码场景优化过。API Key 在 API Keys 页面生成接入细节看 接入文档。控制台在 Console。扩展方向有几个。一是把saveArticle拆成saveDraft和publishArticle两个工具让模型自己决定存草稿还是直接发。二是加一个queryArticle工具支持按标题查已发文章。三是把 Cookie 换成动态刷新避免频繁抓包。四是把 Stdio 换成 SSE 模式这样多个客户端能共享一个 Server 实例。最后留个实用技巧把mcp-servers-config.json里的 jar 路径和 Cookie 用环境变量管理别写死在文件里。团队协作时每个人本地配自己的 Cookie配置文件可以提交到 Git敏感信息走.env或系统环境变量。这样既方便又安全。
返回列表