1. 存量 Spring Boot 项目接入 MCP 的真实痛点
很多团队手里已经有一套跑了两三年的 Spring Boot 业务系统,Controller 里堆着商品查询、订单创建、客户管理这些接口,前端、定时任务、内部调用方都在用。现在想让 AI Agent 直接调用这些能力,第一反应往往是「重写一套工具层」——把每个 Service 方法再包一层 Function Calling 描述,或者干脆另起一个 Python 服务做桥接。这条路我走过,代价是接口一改就要同步维护两份定义,时间一长必然漂移。
Model Context Protocol(简称 MCP)解决的正是这个问题。它是一套让 AI 客户端发现并调用外部工具的开放协议,Claude Desktop、CherryStudio、Cline 这类客户端都支持。你只要把现有 Spring Boot 项目暴露成一个 MCP 服务,AI 就能像调用内置工具一样调用你的query_products、create_order。关键在于:不需要动原有 Controller,也不需要改业务逻辑,新增一个类即可。
这篇面向的是「已有 Spring Boot 存量项目、想快速接入 MCP 协议」的场景。我会从现有 Controller 出发,给出可复制的配置类代码、Maven 依赖坐标、启动验证步骤,并说明如何把服务端点统一改到 TaoToken 通道,最后用一次真实的工具调用确认 MCP 服务能被客户端发现。适合谁:手上有 Spring Boot 项目、想让 AI Agent 调用自己业务接口、又不想大改架构的后端同学。读完你能拿到一个能跑起来的最小闭环,而不是一堆概念。
2. TaoToken 前置准备与 MCP 服务端点规划
在写代码之前,先把「AI 客户端怎么找到你的服务」这件事想清楚。MCP 服务本质是一个 HTTP 端点,客户端通过它拉取工具列表(tools/list)并执行工具(tools/call)。本地开发时你直接连http://localhost:8080/mcp就行,但一旦要跨网络、多客户端共用、或者做统一鉴权和用量观测,就需要一个稳定的统一通道。
我实测下来,把 MCP 服务端点接到 TaoToken 的统一通道上,能省掉不少重复配置。原因是:多个 AI 客户端(Claude Desktop、Cline、CherryStudio)各自要填 Base URL、Key、Model ID,如果每个客户端都直连你本地服务,鉴权、日志、限流都得自己再写一遍。走统一通道后,客户端只认一个地址,你的 Spring Boot 服务也只需要暴露标准 MCP 端点。
前置准备分三步。第一步,拿到访问凭证。打开 https://taotoken.net/api-keys 创建 API Key,这个 Key 后面会同时用于 MCP 服务鉴权和模型调用。第二步,确认你的 Spring Boot 项目版本,建议 Spring Boot 3.x + JDK 17 以上,因为 MCP 的 Java 实现依赖较新的语言特性。第三步,规划端点路径,我习惯用/mcp作为 MCP 服务的根路径,和原有/api/**业务接口完全隔离,互不影响。
这里要强调一个容易踩的坑:MCP 服务和普通 REST 接口的请求体格式不同。普通接口是{"name":"张三"},MCP 走的是 JSON-RPC 2.0,形如{"jsonrpc":"2.0","method":"tools/list","id":1}。所以你不能直接把现有 Controller 的方法签名套上去,而是要在新增的类里做一层「协议适配」——把 MCP 的tools/call请求翻译成对你现有 Service 的调用。这也是为什么「加一个类」就够了:这个类承担协议转换职责,业务逻辑一行不改。
关于统一通道的地址,模型对话入口在 https://taotoken.net/api 对应的对话能力,编码类长期任务可以看 Coding Plan,接入文档在 https://taotoken.net/doc。MCP 服务本身作为你自建的服务,端点由你的 Spring Boot 应用提供,TaoToken 通道负责的是客户端侧的模型调用与统一接入。两者配合起来,AI 客户端既能发现你的工具,又能通过统一通道完成推理。
3. 可复制的 MCPController 配置类与依赖坐标
这一节是核心,直接给能跑的代码。先加依赖,Maven 坐标如下,放在pom.xml的<dependencies>里:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency>如果你用的是 Gradle,对应写法:
implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'com.fasterxml.jackson.core:jackson-databind'接下来是新增的MCPController.java。它的职责有三个:暴露/mcp端点、处理 JSON-RPC 的tools/list和tools/call、把工具调用转发到你现有的 Service。下面这段可以直接复制,把your-secret-api-key-2025换成你自己的 Key:
package com.example.demo.mcp; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import java.util.*; @RestController @RequestMapping("/mcp") public class MCPController { private static final String API_KEY = "your-secret-api-key-2025"; private final ObjectMapper mapper = new ObjectMapper(); @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public ObjectNode handle(@RequestBody JsonNode request, @RequestHeader(value = "Authorization", required = false) String auth) { ObjectNode response = mapper.createObjectNode(); response.put("jsonrpc", "2.0"); response.put("id", request.path("id").asInt()); if (auth == null || !auth.equals("Bearer " + API_KEY)) { ObjectNode error = mapper.createObjectNode(); error.put("code", -32001); error.put("message", "Unauthorized"); response.set("error", error); return response; } String method = request.path("method").asText(); switch (method) { case "tools/list" -> response.set("result", buildToolsList()); case "tools/call" -> response.set("result", executeToolCall(request.path("params"))); default -> { ObjectNode error = mapper.createObjectNode(); error.put("code", -32601); error.put("message", "Method not found: " + method); response.set("error", error); } } return response; } private ObjectNode buildToolsList() { ObjectNode result = mapper.createObjectNode(); ArrayNode tools = mapper.createArrayNode(); tools.add(createTool("query_products", "Query Products", "Search and filter products with various criteria")); tools.add(createTool("create_order", "Create Order", "Create a new order in the system")); tools.add(createTool("generate_report", "Generate Business Report", "Generate comprehensive business reports and analytics")); result.set("tools", tools); return result; } private ObjectNode createTool(String name, String title, String description) { ObjectNode tool = mapper.createObjectNode(); tool.put("name", name); tool.put("description", description); ObjectNode schema = mapper.createObjectNode(); schema.put("type", "object"); schema.set("properties", mapper.createObjectNode()); tool.set("inputSchema", schema); return tool; } private ObjectNode executeToolCall(JsonNode params) { String toolName = params.path("name").asText(); JsonNode arguments = params.path("arguments"); String output = switch (toolName) { case "query_products" -> "query_products executed with " + arguments; case "create_order" -> "create_order executed with " + arguments; case "generate_report" -> "generate_report executed with " + arguments; default -> "ERROR: Unknown tool: " + toolName; }; ObjectNode result = mapper.createObjectNode(); ArrayNode content = mapper.createArrayNode(); ObjectNode text = mapper.createObjectNode(); text.put("type", "text"); text.put("text", output); content.add(text); result.set("content", content); return result; } }上面executeToolCall里的三个 case 是占位实现,实际使用时替换成对你现有 Service 的调用即可,比如productService.query(arguments)。这就是「零侵入」的含义:你的ProductService、OrderService完全不用改,MCPController 只做协议翻译。
如果你用 Claude Code 或 Cline 这类客户端,配置片段(settings 风格)如下,注意 Base URL、Key、Model ID 三件套要写全:
{ "mcpServers": { "spring-boot-mcp": { "url": "http://localhost:8080/mcp", "headers": { "Authorization": "Bearer your-secret-api-key-2025" } } } }Codex 的auth.json风格配置则是:
{ "base_url": "https://taotoken.net/api", "api_key": "your-secret-api-key-2025", "model": "claude-sonnet-4-5" }注意base_url指向 TaoToken 的 API 地址,model填你实际使用的模型 ID。MCP 服务地址和模型地址是两个概念,别混在一起填。
4. 启动验证与一次真实的工具调用
代码写完,启动项目。控制台看到Tomcat started on port(s): 8080就说明服务起来了。先用 curl 验证tools/list能不能正常返回:
curl -X POST http://localhost:8080/mcp \ -H "Content-Type: application/json" \ -H "Authorization: Bearer your-secret-api-key-2025" \ -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'预期返回里能看到query_products、create_order、generate_report三个工具,说明 MCP 服务已经能被发现。如果返回Unauthorized,检查 Authorization 头是否带了Bearer前缀。
接着验证tools/call:
curl -X POST http://localhost:8080/mcp \ -H "Content-Type: application/json" \ -H "Authorization: Bearer your-secret-api-key-2025" \ -d '{"jsonrpc":"2.0","method":"tools/call","id":2,"params":{"name":"query_products","arguments":{"keyword":"手机"}}}'返回的result.content[0].text里应该包含你传入的参数,说明调用链路通了。这一步成功后,再去 CherryStudio 或 Claude Desktop 里配置 MCP 服务,地址填http://localhost:8080/mcp,鉴权头填上 Key。客户端刷新后,工具列表里会出现你定义的三个工具,点开对话窗口让 AI 调用query_products,能看到它真的发起了请求并拿到返回。
我试过在 CherryStudio 里做这个验证,第一次没成功,原因是客户端把 MCP 服务当成了 SSE 长连接,而我的实现是普通 POST。解决办法是在客户端配置里明确指定传输方式为 HTTP,或者确认客户端版本支持 streamable HTTP。这个细节在文档里往往一笔带过,但实际配置时很容易卡住。
验证通过后,把服务端点改到 TaoToken 统一通道。做法是在客户端侧把模型调用的 Base URL 指向https://taotoken.net/api,MCP 服务地址仍指向你的 Spring Boot 应用。这样 AI 的推理走统一通道,工具调用走你的本地服务,两边各司其职。如果你要做长期编码或 Agent 任务,可以看 Coding Plan,它更适合高频、长会话的场景。
5. 常见报错排查:401、local proxy failed 与 reading choices
接入过程中最容易撞上的几类报错,我按实际遇到的频率排一下。
401 Unauthorized。这个最常见,两种原因:一是 MCPController 里的 API_KEY 和客户端配置的 Key 不一致,二是 Authorization 头格式不对。注意必须是Bearer加空格再加 Key,少一个空格都会 401。排查方法是用 curl 直接打端点,排除客户端配置干扰。
local proxy failed。这个报错通常出现在客户端侧,意思是客户端连不上你配置的地址。先确认 Spring Boot 服务是否真的在监听 8080,用netstat -ano | findstr 8080(Windows)或lsof -i:8080(Mac/Linux)看一眼。如果服务正常,检查客户端填的地址是不是localhost——有些客户端在容器或远程环境里跑,localhost指向的是它自己而不是你的宿主机,这时要换成实际 IP。
reading choices 相关报错。这类报错一般出现在模型调用侧,说明请求发出去了但响应格式不符合预期。常见原因是 Base URL 填错,比如把 MCP 服务地址填到了模型 Base URL 的位置。记住:模型 Base URL 是https://taotoken.net/api,MCP 服务地址是你自己的http://localhost:8080/mcp,两者不能互换。另外 Model ID 要填对,填一个不存在的模型名也会导致解析失败。
OAuth 相关报错。如果你的客户端要求 OAuth 流程而你的 MCP 服务只做了 Bearer 鉴权,就会报这个。解决办法是在客户端里选择 API Key 鉴权方式,而不是 OAuth。如果客户端强制 OAuth,那就需要额外实现一个 OAuth 端点,这超出了「加一个类」的范围,建议先用支持 API Key 的客户端验证。
工具列表为空。客户端连上了但看不到工具,检查tools/list返回的 JSON 结构是否符合 MCP 规范。result.tools必须是数组,每个工具要有name、description、inputSchema三个字段。少一个字段客户端可能直接忽略。
排查时有个通用思路:先用 curl 确认服务端没问题,再排查客户端配置。服务端和客户端之间的问题,九成出在地址、Key、格式这三样上。把这三样对齐,基本都能通。
6. 把 MCP 服务接到 TaoToken 统一通道的完整配置
最后把客户端侧的完整配置给全,方便你直接复制。以 Cline 的 MCP 配置为例,cline_mcp_settings.json内容如下:
{ "mcpServers": { "spring-boot-mcp": { "url": "http://localhost:8080/mcp", "headers": { "Authorization": "Bearer your-secret-api-key-2025" }, "disabled": false, "autoApprove": ["query_products"] } } }模型侧的配置,Base URL 填https://taotoken.net/api,API Key 填你在 https://taotoken.net/api-keys 创建的 Key,Model ID 填你实际使用的模型。这样一套配置下来,AI 客户端通过统一通道做推理,通过你的 Spring Boot 服务做工具调用,两边解耦,互不干扰。
如果你想让 AI 直接对话验证模型通道是否正常,可以用模型对话入口先测一轮,确认 Key 和 Base URL 没问题,再回来配 MCP。接入文档在 https://taotoken.net/doc,里面有各客户端的详细配置说明。长期跑编码 Agent 的话,Coding Plan 的额度模型更适合持续调用,不用每次担心额度。
配置完成后,回到对话窗口,让 AI 执行一次query_products,看到它返回你 Service 的真实数据,整条链路就闭环了。你的 Spring Boot 项目一行业务代码没改,却多了一个能被 AI Agent 调用的 MCP 服务能力。后续要加新工具,只需要在buildToolsList里加一行createTool,在executeToolCall里加一个 case,转发到对应的 Service 方法即可。