1. 为什么后端团队需要一个自己的 MCP Server
MCP Server 说白了就是一层“工具网关”:它把你们内部已有的能力(查订单、读配置、跑脚本、拉监控指标)包装成 AI 客户端能看懂的结构化接口,让 Claude、Cursor、各类 Agent 框架在对话里直接调用,而不是靠人肉复制粘贴。Spring Boot 做这件事有天然优势——你团队现有的 Service、Mapper、鉴权体系几乎可以原样复用,不用为了接 AI 再学一套新语言。
这篇面向的是已经写过 Spring Boot、想把本地工具能力暴露给 AI 客户端的后端开发者。我会从零给出一套能跑通的工程骨架:pom.xml依赖、application.yml配置、工具注册中心、/mcp/tools与/mcp/call-tool两个核心端点,最后用 TaoToken 的统一 Key 通道把模型侧接入补齐,并用curl验证整条链路。目标很明确:服务能启动、工具能列出、调用能返回结果、AI 客户端能连上。
技术栈固定为 Spring Boot 3 + Java 17 + Maven + Spring Web + Jackson,不引入额外重型框架,保证你复制过去就能编译。
2. TaoToken 前置准备:统一 Key 与通道配置
在写代码之前先把模型侧的入口准备好,否则后面验证工具调用时没有可用的对话通道。TaoToken 在这里扮演的是统一 Key 与 API 通道的角色:你只需要申请一个 Key,就能在模型对话、编码 Agent、接口调试之间共用同一套凭证,不用为每个客户端单独配一遍。
第一步,打开官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 注册账号,进入控制台。控制台地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,在里面找到 API Keys 页面:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite ,新建一个 Key 并复制保存。这个 Key 后面会同时用在 MCP 客户端的settings.json和curl验证里。
第二步,确认你的 API 基地址。TaoToken 的 API 入口是 https://taotoken.net/api ,注意这个地址不带任何查询参数,配置时直接填这一行即可。如果你用的是 Claude Code 这类编码 Agent,可以参考 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 里的接入说明;如果只是想在网页里先验证模型是否通,直接进模型对话页 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite 发一条消息即可。
注意:Key 只显示一次,建议存进密码管理器。不要把它硬编码进 Git 仓库,后面我会用环境变量注入。
到这里前置就绪:一个 Key、一个 API 基地址、一个可用的对话入口。接下来进入 Spring Boot 工程本身。
3. 可复制配置:pom.xml 与 application.yml 骨架
先建一个标准 Maven 工程,groupId用com.example,artifactId用mcp-server。pom.xml里只需要 Web、Lombok、Jackson 三块,父级用 Spring Boot 3.2.x:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.5</version> <relativePath/> </parent> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> </dependencies>application.yml保持极简,端口和日志级别够用就行,同时把 TaoToken 的 Key 通过环境变量占位,避免明文:
server: port: 8080 mcp: server: name: my-company-tools version: 1.0.0 taotoken: api-base: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY:} logging: level: com.example.mcpserver: DEBUG启动时用export TAOTOKEN_API_KEY=你的Key注入,Windows 下用set。这样配置骨架就完成了,接下来写工具模型和注册中心。
4. 工具注册与 MCP Controller 实现
MCP 的核心抽象就两个动作:列出工具、调用工具。先定义工具接口,任何实现类只要被 Spring 扫描到就会自动进注册中心:
public interface McpTool { String getName(); String getDescription(); Map<String, Object> getInputSchema(); Object invoke(Map<String, Object> input); }写一个示例工具,返回当前时间,方便验证链路:
@Component public class TimeTool implements McpTool { @Override public String getName() { return "get_current_time"; } @Override public String getDescription() { return "Returns the current time in ISO8601 format."; } @Override public Map<String, Object> getInputSchema() { return Map.of("type", "object", "properties", Map.of()); } @Override public Object invoke(Map<String, Object> input) { return Map.of( "time", Instant.now().toString(), "timezone", ZoneId.systemDefault().getId() ); } }注册中心用构造器注入收集所有McpToolBean:
@Service public class ToolRegistry { private final Map<String, McpTool> tools = new ConcurrentHashMap<>(); public ToolRegistry(List<McpTool> toolList) { for (McpTool tool : toolList) { tools.put(tool.getName(), tool); } System.out.println("registered tools: " + tools.size()); } public Collection<McpTool> getTools() { return tools.values(); } public McpTool getTool(String name) { return tools.get(name); } }Controller 暴露两个端点,GET /mcp/tools返回工具清单,POST /mcp/call-tool接收调用请求:
@RestController @RequestMapping("/mcp") @RequiredArgsConstructor public class McpController { private final ToolRegistry toolRegistry; @GetMapping("/tools") public ResponseEntity<List<McpTool>> listTools() { return ResponseEntity.ok(toolRegistry.getTools().stream().toList()); } @PostMapping("/call-tool") public ResponseEntity<List<ToolResult>> callTools(@RequestBody ToolCallRequest request) { List<ToolResult> results = request.getToolCalls().stream().map(call -> { String name = (String) call.get("name"); Map<String, Object> input = (Map<String, Object>) call.getOrDefault("input", Map.of()); McpTool tool = toolRegistry.getTool(name); if (tool == null) { return new ToolResult(name, "tool not found", true); } try { return new ToolResult(name, tool.invoke(input), false); } catch (Exception e) { return new ToolResult(name, e.getMessage(), true); } }).toList(); return ResponseEntity.ok(results); } }ToolCallRequest里放一个List<Map<String, Object>> toolCalls,ToolResult放toolName、result、isError三个字段,用 Lombok 的@Data即可。到这里服务端骨架完整,可以启动了。
5. 验证请求:curl 跑通工具调用链路
先启动应用:
mvn spring-boot:run看到控制台打印registered tools: 1说明工具注册成功。第一个验证动作是列出工具:
curl http://localhost:8080/mcp/tools预期返回一个 JSON 数组,里面包含get_current_time及其inputSchema。第二个动作是真正调用:
curl -X POST http://localhost:8080/mcp/call-tool \ -H "Content-Type: application/json" \ -d '{ "toolCalls": [ { "name": "get_current_time", "input": {} } ] }'返回结果里isError为false,result.time是 ISO8601 时间戳,result.timezone是你机器的时区。如果这两步都通了,说明 MCP Server 本身没问题。
接下来把服务注册到 AI 客户端。以支持 MCP 的客户端为例,settings.json片段如下:
{ "mcpServers": { "my-company-tools": { "url": "http://localhost:8080/mcp", "apiKey": "${TAOTOKEN_API_KEY}" } } }如果你用的是编码类 Agent,长期跑建议走 Coding Plan 通道,配置方式见 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。想先在网页里确认模型侧能正常对话,直接进 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite 发一条消息即可。Claude Code 的接入细节在 https://taotoken.net/claude-code?utm_source=taotoken_aicg_blog_end&utm_content=claudecode&utm_campaign=rewrite 有完整说明。
6. 本篇常见错排查
启动报 8080 端口占用:改application.yml里的server.port,或者先lsof -i:8080找到进程杀掉。别硬扛,端口冲突是最常见的第一次失败原因。
/mcp/tools返回空数组:说明McpTool实现类没被扫描到。检查实现类是否加了@Component,以及是否在com.example.mcpserver包路径下。Spring 默认只扫描主启动类所在包及其子包。
调用返回tool not found:请求体里的name必须和getName()返回值完全一致,大小写敏感。建议先用/mcp/tools拿到准确名称再拼请求。
JSON 反序列化报错:ToolCallRequest的字段名要和请求体对齐。如果你把字段写成tool_calls,请求里也得用下划线,Jackson 默认不做驼峰转换。
客户端连不上本地服务:localhost在容器或远程客户端里指向的是它自己。本地调试用127.0.0.1,跨机访问用局域网 IP,并确认防火墙放行。
Key 没生效:检查环境变量是否在启动进程的 shell 里导出,echo $TAOTOKEN_API_KEY确认非空。如果为空,application.yml里的占位会解析成空字符串,后续请求会鉴权失败。
中文返回乱码:Spring Boot 3 默认 UTF-8,一般不会出问题。如果客户端显示乱码,检查Content-Type是否带了charset=UTF-8。
排查顺序建议固定:先确认服务启动日志、再确认工具列表、最后确认调用返回。三步定位,比盲目改代码快得多。
7. 接入文档与后续扩展
服务跑通之后,下一步通常是把真实业务工具接进来。做法和TimeTool一样:实现McpTool接口,在invoke里调用你现有的 Service,getInputSchema里描述参数结构。Spring 会自动把它注册进ToolRegistry,不需要改 Controller。
接入过程中如果遇到鉴权、通道、Key 相关的问题,优先查接入文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,里面覆盖了 API 基地址、Key 管理和常见错误码。需要新建或轮换 Key 时去 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。长期跑编码 Agent 或自动化任务,Coding Plan 通道 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 更合适,配额和稳定性都更可控。
一个实用建议:给每个工具加独立的超时和异常兜底,别让某个慢查询把整个/mcp/call-tool拖死。工具粒度尽量小,一个工具只做一件事,AI 客户端编排起来更灵活。