
1. 从本地 Markdown 到 CSDN 草稿MCP 服务自动发送文章的真实痛点如果你写过一段时间技术博客大概率经历过这种循环本地用 Typora 或 VS Code 写完一篇 Markdown手动打开 CSDN 创作中心复制粘贴调整格式补标签选分类最后点发布。单篇还好一旦要同步三五篇旧文或者想把 AI 生成的初稿批量推上去这套动作就变成了纯体力活。我试过用脚本直接调 CSDN 的接口能跑通但很快遇到第二个问题Key 太散了。CSDN 的 Cookie 是一套模型调用是另一套如果还想接 Claude Code 或者别的 Agent 工具又是第三套鉴权。每个工具都要单独配一遍 Base URL、API Key、Model ID改一个地方要翻好几个配置文件。这时候 MCPModel Context Protocol的价值就出来了——它把「工具能力」标准化成服务端客户端只需要连一个入口就能调用发布文章、生成摘要、润色正文这些动作。这篇要解决的核心场景是用 Spring Boot 搭一个 MCP 服务通过 Java 调用 API把本地 Markdown 文章自动推送到 CSDN。同时用 TaoToken 的统一 Key 把模型调用和工具调用的鉴权收敛到一处避免多工具 Key 分散、配置繁琐的问题。适合有 Java 基础、想把自己的内容发布链路自动化的开发者也适合正在研究 MCP 服务端怎么落地的人。整条链路拆开看是三段Spring Boot 提供 MCP 服务端接口Java 侧负责 Markdown 转 HTML 和组装请求TaoToken 提供统一的模型与 API 入口。下面按可复现的顺序一步步来每一步都给到能直接抄的配置和代码。2. TaoToken 统一 Key 前置把模型与工具鉴权收敛到一个入口在动手写 MCP 服务之前先把鉴权这层理清楚。传统做法是每个外部服务配一套凭证CSDN 用 Cookie模型调用用某个平台的 KeyAgent 工具再配一套。问题在于一旦你要在 MCP 服务里同时做「生成文章摘要」和「发布到 CSDN」服务端就得持有多个凭证配置项散落在 application.yml、环境变量、甚至硬编码里。TaoToken 在这里扮演的是统一入口的角色。它提供兼容 OpenAI 风格的 API 地址模型对话、Coding Plan、API Keys 管理都在同一个控制台里。对 MCP 服务端来说你只需要记住一个 Base URL 和一个 Key模型调用走这个入口工具链的鉴权也在这里统一管理。官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 注意 API 地址不带 UTM 参数配置的时候别把查询串带进去。具体到操作先去控制台创建一个 API Key。控制台地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 登录后在 API Keys 页面新建一个复制出来形如sk-xxxxxxxx的字符串。这个 Key 后面会同时用在两处一是 MCP 服务端调用模型生成摘要二是作为统一凭证管理其他工具调用。如果你还没决定用哪个模型可以先去模型对话页面 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 试一下确认模型 ID 再写进配置。这里有个容易踩的坑很多人把 Base URL 写成https://taotoken.net/api/带尾斜杠然后在代码里又拼一次路径结果变成双斜杠导致 404。正确写法是 Base URL 只到/api具体路径由客户端库拼接。另外Key 不要提交到 Git用环境变量注入Spring Boot 里用${TAOTOKEN_API_KEY}占位。对于长期做编码和 Agent 的场景可以考虑 Coding Plan地址是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 它更适合高频调用和工具链集成。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 配置细节以文档为准。把 Key 准备好之后MCP 服务端的鉴权层就简化成「读一个环境变量」。这一步做完后面 Spring Boot 里所有需要模型能力的地方都复用同一个 Key不用再为每个工具单独配。3. Spring Boot MCP 服务端可复制配置application.yml 与 settings 片段这一节给到能直接落地的配置。项目基于 Spring Boot 3.4.xJDK 17Maven 3.6。核心依赖包括spring-ai-mcp-server-spring-boot-starter、Retrofit、OkHttp、commonmark。先在pom.xml里加上这些依赖版本按你项目实际管理这里给的是参考版本。dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-mcp-server-spring-boot-starter/artifactId version${spring-ai.version}/version /dependency dependency groupIdcom.squareup.retrofit2/groupId artifactIdretrofit/artifactId version2.9.0/version /dependency dependency groupIdcom.squareup.retrofit2/groupId artifactIdconverter-jackson/artifactId version2.9.0/version /dependency dependency groupIdcom.squareup.okhttp3/groupId artifactIdokhttp/artifactId version4.12.0/version /dependency dependency groupIdorg.commonmark/groupId artifactIdcommonmark/artifactId version0.21.0/version /dependency然后是application.yml。这里把 TaoToken 的 Base URL、Key、Model ID 三件套写全同时配置 CSDN 接口的超时参数。注意 Key 用环境变量占位不要写死。spring: application: name: mcp-service-article-message main: banner-mode: off web-application-type: none taotoken: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} model-id: ${TAOTOKEN_MODEL_ID:gpt-4o-mini} csdn: api: base-url: https://bizapi.csdn.net/ connect-timeout: 30 read-timeout: 30 write-timeout: 30 logging: pattern: console: %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n file: name: data/log/${spring.application.name}.log如果你用的是 Claude Code 或者 Cline 这类客户端MCP 服务端的连接配置通常是一个 JSON 片段。以 Claude Code 的 MCP 配置为例路径一般在~/.claude/settings.json或项目级.mcp.json写法如下{ mcpServers: { article-publisher: { command: java, args: [-jar, /path/to/mcp-service-article-message.jar], env: { TAOTOKEN_API_KEY: sk-你的Key, TAOTOKEN_MODEL_ID: gpt-4o-mini } } } }这里 Base URL、Key、Model ID 三件套都齐了Base URL 在application.yml的taotoken.base-urlKey 通过环境变量注入Model ID 在taotoken.model-id。如果你用 Codex 的auth.json结构类似把 Key 放在对应字段即可。Cline 的 MCP 配置也是 JSON字段名可能略有差异核心是 command、args、env 三块。配置写完先别急着跑检查两点一是TAOTOKEN_API_KEY环境变量在当前 shell 里能echo出来二是TAOTOKEN_MODEL_ID是控制台里真实存在的模型 ID。这两点确认了服务启动时就不会因为鉴权失败卡住。4. Java 侧核心实现与一次真实发送 CSDN 文章的验证配置就绪后写核心代码。整体分四块Markdown 转 HTML、CSDN 请求 DTO、Retrofit 接口定义、MCP 工具回调。先看 Markdown 转换CSDN 的接口要求内容是 HTML所以本地 Markdown 必须先转。Component public class MarkdownConverter { private final Parser parser Parser.builder().build(); private final HtmlRenderer renderer HtmlRenderer.builder().build(); public String convertToHtml(String markdown) { if (markdown null || markdown.isEmpty()) { return ; } Node document parser.parse(markdown); return renderer.render(document); } }接着是请求 DTO字段名要和 CSDN 接口对齐用 Jackson 注解映射。Data Builder public class ArticleRequestDTO { JsonProperty(article_id) private String articleId; private String title; private String description; private String content; private String tags; private String categories; private String type; private Integer status; JsonProperty(read_type) private String readType; }Retrofit 接口定义注意 Header 里要带 Cookie这是 CSDN 的身份凭证。public interface ICSDNService { Headers({ accept: application/json, text/plain, */*, content-type: application/json; }) POST(/blog-console-api/v1/postedit/saveArticle) CallArticleResponseDTO saveArticleV1( Body ArticleRequestDTO request, Header(Cookie) String cookieValue); }然后是 MCP 工具回调的注册。Spring AI 的 MCP starter 提供了MethodToolCallbackProvider把带有工具注解的方法暴露出去。Bean public ToolCallbackProvider csdnTools(CSDNArticleService articleService) { return MethodToolCallbackProvider.builder() .toolObjects(articleService) .build(); }CSDNArticleService里封装发布逻辑同时调用 TaoToken 生成摘要。这里用 Spring 的RestClient调 TaoToken 的兼容接口Base URL 从配置读。Service Slf4j public class CSDNArticleService { Value(${taotoken.base-url}) private String taotokenBaseUrl; Value(${taotoken.api-key}) private String taotokenApiKey; Value(${taotoken.model-id}) private String modelId; Resource private ICSDNService csdnService; Resource private MarkdownConverter markdownConverter; public String publishToCSDN(String markdown, String cookie) throws IOException { String htmlContent markdownConverter.convertToHtml(markdown); String summary generateSummary(markdown); ArticleRequestDTO request ArticleRequestDTO.builder() .title(MCP服务自动发送CSDN文章实战) .description(summary) .content(htmlContent) .tags(Java,Spring Boot,MCP) .type(original) .status(0) .readType(public) .build(); ResponseArticleResponseDTO response csdnService.saveArticleV1(request, cookie).execute(); if (response.isSuccessful() response.body() ! null response.body().getCode() 200) { String url response.body().getData().getUrl(); log.info(发布成功文章地址{}, url); return url; } log.error(发布失败HTTP状态码{}, response.code()); return null; } private String generateSummary(String markdown) { RestClient client RestClient.builder() .baseUrl(taotokenBaseUrl) .defaultHeader(Authorization, Bearer taotokenApiKey) .build(); String prompt 用一句话总结以下技术文章不超过80字\n markdown; return client.post() .uri(/v1/chat/completions) .body(Map.of( model, modelId, messages, List.of(Map.of(role, user, content, prompt)) )) .retrieve() .body(Map.class) .toString(); } }验证环节准备一篇本地 Markdown比如demo.md内容随意但要有标题和正文。然后从浏览器登录 CSDN 创作中心打开开发者工具在 Network 里找任意一个bizapi.csdn.net的请求复制请求头里的 Cookie 值。注意 Cookie 有时效性过期了要重新取。写一个测试类或者直接用CommandLineRunner触发SpringBootTest class PublishTest { Resource private CSDNArticleService articleService; Test void testPublish() throws IOException { String markdown Files.readString(Path.of(demo.md)); String cookie System.getenv(CSDN_COOKIE); String url articleService.publishToCSDN(markdown, cookie); assertNotNull(url); System.out.println(文章已发布 url); } }跑通后控制台会打印文章地址打开就是 CSDN 草稿或已发布状态。实测下来从本地 Markdown 到 CSDN 草稿整个链路在 3 秒左右完成摘要由 TaoToken 生成正文格式由 commonmark 转换Cookie 只在这一处使用。5. 本篇常见错排查401、local proxy failed、reading choices 与 OAuth自动化链路跑不通报错通常集中在几个地方。下面按真实遇到的错误对照排查。401 Unauthorized。这个最常见分两种。一种是 TaoToken 侧返回 401说明TAOTOKEN_API_KEY没读到或者 Key 失效。检查环境变量是否在当前运行环境可见Spring Boot 启动日志里搜taotoken.api-key看是否解析成空。另一种是 CSDN 侧返回 401说明 Cookie 过期或格式不对。Cookie 要完整复制包括UserToken、UserInfo这些字段少一个都可能鉴权失败。注意 Cookie 不要带换行复制后检查一下。local proxy failed。这个报错通常出现在客户端连 MCP 服务端的时候比如 Claude Code 启动 MCP 进程失败。原因可能是command路径不对或者args里的 jar 路径是相对路径。改成绝对路径先手动在终端跑一遍java -jar /path/to/xxx.jar确认能启动再写进配置。如果手动能跑、客户端报 proxy failed检查客户端的 MCP 配置 JSON 是否有语法错误比如多了逗号。reading choices 相关报错。这个一般出现在解析模型返回的时候。TaoToken 的兼容接口返回结构是 OpenAI 风格choices数组里取message.content。如果你直接body.toString()或者按别的结构解析就会报 reading choices 失败。正确做法是定义响应 DTO用 Jackson 反序列化取choices[0].message.content。另外注意有些模型返回的content可能是 null要做空值判断。OAuth 相关报错。如果你在 MCP 客户端里配置了 OAuth 流程但服务端是本地进程模式可能会报 OAuth 不适用。本地 MCP 服务端一般用环境变量传 Key不需要走 OAuth。检查客户端配置里是否误开了 OAuth 选项关掉即可。如果确实需要 OAuth那是远程 MCP 服务端的场景本地 jar 模式不涉及。CSDN 接口返回非 200 但 HTTP 是 200。这种情况是业务层错误response.body().getCode()不等于 200。常见原因是文章内容为空、标题超长、标签格式不对。CSDN 的标签用逗号分隔不要带空格。分类字段如果填了不存在的分类 ID也会失败。建议先把status设为 0草稿确认能创建成功再改成发布。Markdown 转 HTML 后格式错乱。commonmark 默认不处理表格和任务列表如果你的文章里有这些需要加扩展。引入commonmark-ext-gfm-tables和commonmark-ext-task-list-items在Parser.builder()里注册扩展。否则表格会变成纯文本CSDN 编辑器里显示很难看。排查顺序建议先确认 TaoToken Key 能单独调通模型对话再确认 CSDN Cookie 能单独调通发布接口最后把两者串起来。分步验证比一上来跑全链路更容易定位问题。6. 把链路固定下来从手动发布到 MCP 工具调用的日常用法链路跑通之后日常用法可以更省事。你不需要每次都写测试类而是把发布能力注册成 MCP 工具在支持 MCP 的客户端里直接调用。比如在 Claude Code 里配置好 MCP 服务端后直接说「把 demo.md 发布到 CSDN」客户端会调用服务端暴露的工具方法传入文件路径服务端读文件、转 HTML、生成摘要、调 CSDN 接口返回文章地址。这里的关键是把工具方法的入参设计好。建议入参只暴露filePath和可选的titleCookie 从服务端环境变量读不要每次传。这样客户端调用时不用关心鉴权细节体验更顺。工具方法的注解用 Spring AI 的Tool描述写清楚客户端才能正确理解什么时候调用它。对于长期做内容分发的场景可以把多个平台的发布能力都注册成 MCP 工具统一走 TaoToken 的 Key 管理。这样新增一个平台只需要加一个工具方法不用改客户端的鉴权配置。Coding Plan 适合这种高频、多工具的调用模式地址是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。最后给一个实用技巧把 CSDN Cookie 的刷新也做成半自动。Cookie 过期时接口返回 401服务端捕获后记录日志并返回明确提示你在客户端看到提示后手动更新环境变量重启服务即可。不要尝试自动登录 CSDN 获取 Cookie那涉及验证码和风控不稳定也不合规。手动更新一次 Cookie 能用挺久配合 MCP 的调用体验整体效率比纯手动发布高很多。如果你在配置过程中卡在某个报错先去接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 对照参数再去 API Keys 页面 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 确认 Key 状态。模型侧的问题用模型对话页面 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 单独验证能快速区分是模型调用问题还是 CSDN 接口问题。