1. 从零搭建 MCP Server 前,先搞清楚要解决什么问题
如果你是一名 Java 开发者,最近大概率被 MCP(Model Context Protocol)刷过屏。简单说,MCP 是一套把「AI 应用」和「外部工具、数据源」连接起来的开放协议,你可以把它理解成 AI 世界的 USB-C 接口:客户端按统一格式发 JSON-RPC 请求,服务端按统一格式返回结果,双方不用关心对方内部怎么实现。对 Java 团队来说,这意味着你写好的业务方法,只要按 MCP 规范暴露出去,Claude Code、各类 Agent 客户端就能直接调用,不用再靠拼 prompt 去「哄」模型。
这篇要交付的,是一个基于 Spring Boot 3.5.3 + Spring AI 1.1.8 的 MCP Server 骨架,重点不在工具本身多花哨,而在于把「多模型 Key 分散管理」这个真实痛点一次性解决掉。很多团队一开始把 OpenAI、Claude、国产模型的 Key 分别写在不同的 yml、环境变量甚至硬编码里,工具一多、模型一换,配置就乱成一锅粥。我的做法是:MCP Server 只负责暴露工具,模型调用统一走 TaoToken 的 API 通道,用一把 Key 管住所有模型请求。这样工具服务和模型供应商彻底解耦,换模型不用改工具代码,加模型不用改配置文件结构。
适合谁看:有 Java 基础、想快速跑通本地 MCP Server 的后端开发;正在把内部系统改造成 AI 可调用能力的团队;以及被多套 Key 管理折磨过的同学。下面从环境准备一路写到验证调用,命令和配置都可以直接复制。
2. 环境准备与 TaoToken 统一 Key 前置配置
先把地基打好。JDK 17+ 是硬要求,Spring AI 1.x 支持 Java 17,但 2.x 强制 Java 21 + Spring Boot 4.0,生产环境如果还在 17,就老老实实用 1.1.8 这个稳定版。Maven 用 3.9 以上,java -version和mvn -version各敲一遍确认。
国内拉依赖慢是常态,建议在~/.m2/settings.xml里配阿里云镜像,省得卡在下载上:
<mirror> <id>aliyun-maven</id> <mirrorOf>central</mirrorOf> <url>https://maven.aliyun.com/repository/public</url> </mirror>接下来是这篇的重点——统一 Key。TaoToken 提供的是一个兼容主流模型调用格式的 API 通道,官网在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 入口是 https://taotoken.net/api 。你需要先去控制台创建一个 API Key,地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,Key 的创建和管理在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。
拿到 Key 之后,不要写死在代码里,用环境变量注入:
export TAOTOKEN_API_KEY="sk-你的key"注意:Key 属于敏感凭证,提交代码前确认
.gitignore里排除了本地配置文件,团队协作时用 CI 的 secret 管理,别直接贴进仓库。
为什么要在 MCP Server 里接统一通道?因为 MCP 工具经常需要「工具内部再调模型」,比如一个总结工具、一个翻译工具。如果每个工具各自持有不同厂商的 Key,配置会迅速失控。统一走 TaoToken 后,工具代码里只认一个 base URL 和一把 Key,模型名作为参数传入即可切换。想先直观感受模型对话效果,可以打开 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite 试一下,确认 Key 可用再往下走。
3. Maven 依赖与 application.yml 配置骨架
项目结构保持干净,一个启动类加几个工具类就够:
mcp-demo/ ├── pom.xml └── src/main/ ├── java/com/example/mcpdemo/ │ ├── McpDemoApplication.java │ └── tools/ │ ├── CalculatorTools.java │ └── DateTimeTools.java └── resources/ └── application.ymlpom.xml的核心就一个 MCP starter,版本用 BOM 统一管理:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.5.3</version> </parent> <properties> <java.version>17</java.version> <spring-ai.version>1.1.8</spring-ai.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>${spring-ai.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>spring-ai-starter-mcp-server-webmvc会自动注册 MCP 协议端点,JSON-RPC 的解析、路由、响应封装全都不用你写。启动类就是最普通的 Spring Boot 入口,没有任何 MCP 代码:
@SpringBootApplication public class McpDemoApplication { public static void main(String[] args) { SpringApplication.run(McpDemoApplication.class, args); } }application.yml是配置骨架的核心,把 MCP 协议参数和 TaoToken 通道放在一起:
spring: application: name: mcp-demo ai: mcp: server: name: mcp-demo-server version: 1.0.0 protocol: STREAMABLE type: SYNC annotation-scanner: enabled: true openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: claude-sonnet-4-5 server: port: 8080三个 MCP 配置项的作用:protocol: STREAMABLE启用 Streamable HTTP 传输,单个POST /mcp端点完成全部交互,比早期的 SSE 更简单,也兼容网关和负载均衡;type: SYNC表示同步注册@McpTool方法;annotation-scanner.enabled: true让容器里带注解的 Bean 自动注册,新增工具零配置。base-url指向 TaoToken 的 API 入口,api-key从环境变量读取,模型名放在chat.options.model里,想换模型改这一行就行。
提示:如果你用的是 Spring AI 2.x,配置前缀和部分字段会有变化,且需要 Java 21。本文所有配置针对 1.1.8,升级前先对照官方迁移说明。
4. 用 @McpTool 声明工具并验证注册与调用
工具类的写法是这套框架最舒服的地方:普通方法加注解就是 MCP 工具。以计算器为例:
@Component public class CalculatorTools { @McpTool(name = "calculate_add", description = "将两个数字相加,返回它们的和。支持整数和小数。") public double calculateAdd( @McpToolParam(description = "第一个加数") double a, @McpToolParam(description = "第二个加数") double b) { return a + b; } @McpTool(name = "calculate_divide", description = "用第一个数字除以第二个数字,返回商。除数不能为 0。") public double calculateDivide( @McpToolParam(description = "被除数") double a, @McpToolParam(description = "除数,不能为 0") double b) { if (b == 0) { throw new IllegalArgumentException("除数不能为 0,请传入非零的除数。"); } return a / b; } }几个细节值得注意。参数上的description是给 AI 客户端看的,写得越具体,模型决定传什么参数就越准,别偷懒写「参数 a」这种废话。校验放在方法开头,抛出带提示的IllegalArgumentException,框架会自动转成isError=true的 MCP 错误响应,模型能读懂并自我纠正。可空参数用@McpToolParam(required = false)显式声明,避免客户端必传校验失败。工具命名用 snake_case 加动词开头,比如calculate_add,符合社区惯例。
再补一个日期工具,演示可空参数:
@Component public class DateTimeTools { @McpTool(name = "get_current_time", description = "获取指定时区的当前日期时间,返回 yyyy-MM-dd HH:mm:ss 格式。timezone 留空时使用系统默认时区。") public String getCurrentTime( @McpToolParam(required = false, description = "时区 ID,例如 Asia/Shanghai、UTC") String timezone) { ZoneId zone = (timezone == null || timezone.isBlank()) ? ZoneId.systemDefault() : ZoneId.of(timezone); return LocalDateTime.now(zone) .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); } }启动服务:
mvn spring-boot:run看到下面这类日志就说明工具注册成功:
Enable tools capabilities, notification: true Registered tools: 15 Tomcat started on port 8080 (http) with context path '/' Started McpDemoApplication in 2.586 seconds验证调用有两种方式。第一种用 MCP Inspector,npx @modelcontextprotocol/inspector启动后连接http://localhost:8080/mcp,在 tools 列表里能看到所有注册的工具,点进去填参数直接调用,返回结果会显示在面板上。第二种直接发 HTTP 请求,用 curl 模拟tools/list:
curl -X POST http://localhost:8080/mcp \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'返回的 JSON 里result.tools数组会列出所有工具名和描述。调用具体工具时把 method 换成tools/call,params 里带上工具名和参数即可。如果工具内部需要调模型,它会走application.yml里配的 TaoToken 通道,你不需要在工具代码里再管 Key。
5. 本篇常见错误排查
跑不通的时候,按下面几个方向查,基本能覆盖九成问题。
启动报No tool found或工具数为 0。先确认工具类上有@Component,方法上有@McpTool,并且annotation-scanner.enabled是 true。如果工具类在启动类的同级或子包下,扫描没问题;如果放到了别的包,检查@SpringBootApplication的扫描范围。
端口冲突或 404。server.port默认 8080,被占用就换一个。请求路径必须是/mcp,这是 starter 自动注册的端点,别自己改成/api/mcp之类。用 Streamable HTTP 时,请求方法用 POST,Content-Type 必须是application/json。
调用模型时报 401 或鉴权失败。检查环境变量TAOTOKEN_API_KEY是否真的注入到了运行进程里,echo $TAOTOKEN_API_KEY确认一下。如果是在 IDE 里跑,注意 IDE 的 Run Configuration 可能没继承 shell 的环境变量,需要手动加。base-url 确认是https://taotoken.net/api,末尾不要多加斜杠。
版本不兼容。Spring AI 2.x 要求 Java 21 和 Spring Boot 4.0,如果你 pom 里 BOM 版本写成了 2.x 但 JDK 还是 17,启动会直接报错。反过来,1.1.8 配 Spring Boot 3.5.3 是验证过的组合,别随意混搭。
工具参数校验失败。可空参数没标required = false,客户端会强制要求传值。另外基本类型如double不能传 null,需要可空就用包装类型Double。
注意:调试协议交互时,在 yml 里打开
logging.level.io.modelcontextprotocol: DEBUG,能看到完整的 JSON-RPC 请求和响应,定位问题非常快,但生产环境记得关掉。
6. 长期编码与 Agent 场景的接入建议
本地 MCP Server 跑通只是第一步。如果你打算把它用在长期的编码辅助或 Agent 工作流里,比如让 Claude Code 持续调用你的工具,建议把模型调用统一收敛到 TaoToken 的 Coding Plan,地址是 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite ,这样工具服务和模型额度分开管理,团队里谁用多少一目了然。Claude Code 相关的接入说明在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,需要配置 Anthropic 兼容端点的可以参考 https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=ClaudeCodeAnthropic&utm_campaign=rewrite 。
扩展新工具就是三步:新建@Component类或往现有类加方法,标上@McpTool和@McpToolParam,重启服务自动注册。真正要花心思的是工具描述和参数校验——描述写得好,模型调用就准;校验写得清楚,模型出错后能自己纠正。这两点做好了,你的 MCP Server 才算是能进生产环境的状态。