1. 为什么 Java 开发者需要关注 MCP 服务器
如果你是一名写惯了 Controller、Service、Repository 的 Java 开发者,第一次听到 Model Context Protocol(MCP)可能会觉得这又是哪个新瓶装旧酒的协议。但实际接触下来你会发现,MCP 解决的是一个非常具体的问题:让 AI 模型能够以标准化的方式调用你已有的业务能力,而不是把数据复制粘贴到对话框里。
MCP 本质上是一套通信规范,它定义了 AI 客户端(比如 Claude Desktop、各类 IDE 插件、Agent 框架)如何发现并调用外部工具。你写好的 Java 方法,加上一个注解,就能变成 AI 可以理解并调用的工具。这对 Java 后端来说意义很大——你不需要重写业务逻辑,只需要把现有的 Service 暴露成 MCP 工具,AI 就能帮你查询数据、触发计算、串联流程。
Spring AI 在这个基础上做了进一步封装。它把 MCP 规范里那些繁琐的握手、序列化、传输层细节都藏了起来,留给你的就是@Tool注解和几个配置项。你可以用写 Spring Bean 的方式写 MCP 工具,用application.yml管理服务器行为,用依赖注入组织服务层。对于熟悉 Spring 生态的人来说,上手成本几乎为零。
这篇文章会带你从零搭一个可运行的 MCP 服务器,包含完整的pom.xml依赖、application.yml配置、工具类实现,以及如何通过 TaoToken 统一管理模型调用的 Key 和 API 通道。最后会给出验证 MCP 端点连通性的具体命令和预期返回,确保你搭完就能确认它真的在工作。
适合谁看:有 Spring Boot 基础、想把自己的 Java 服务接入 AI 工作流的后端开发者;正在做 Agent 工具链、需要标准化工具暴露方式的架构同学;以及想理解 MCP 服务端到底怎么落地的人。
2. 前置准备:TaoToken 统一 Key 与 API 通道
在开始写代码之前,先把模型调用的通道准备好。MCP 服务器本身负责暴露工具,但工具背后如果需要调用大模型(比如做意图理解、参数补全、结果总结),就需要一个稳定的 API 入口。TaoToken 在这里的角色是统一管理 Key 和请求通道,避免你在多个模型供应商之间来回切换配置。
你可以先到官网了解整体能力:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。注册后在控制台创建一个 API Key,这个 Key 会用于后续application.yml里的模型调用配置。
控制台地址: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
API 基础地址(不带 UTM,直接用于代码配置):https://taotoken.net/api
如果你后续要做长期编码或 Agent 类项目,可以关注 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite
需要说明的是,MCP 服务器本身不强制依赖某个模型通道。但实际项目里,工具调用往往伴随模型推理,提前把 Key 和 Base URL 配好,后面切换模型或调整参数时不用改代码结构。我建议把 Key 放在环境变量里,application.yml通过占位符引用,这样本地和线上环境可以共用同一份配置。
3. 可复制配置:pom 依赖与 application.yml
3.1 Maven 依赖
Spring AI 的 MCP Server 支持多种传输方式,本地开发最常用的是 STDIO(标准输入输出),适合被 Claude Desktop 这类客户端直接拉起。如果你要做远程服务,可以选 HTTP/SSE。下面这份pom.xml片段覆盖了 MCP Server 和模型调用两部分依赖。
<properties> <java.version>17</java.version> <spring-ai.version>1.0.0-M6</spring-ai.version> </properties> <dependencies> <!-- Spring Boot 基础 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!-- Spring AI MCP Server --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId> <version>${spring-ai.version}</version> </dependency> <!-- 模型调用通道(OpenAI 兼容协议) --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-openai-spring-boot-starter</artifactId> <version>${spring-ai.version}</version> </dependency> </dependencies> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> </repository> </repositories>注意 Spring AI 的版本迭代较快,1.0.0-M6是里程碑版本,正式版发布后可以替换为稳定版本号。如果你的项目用的是 Gradle,把对应依赖换成implementation写法即可,坐标不变。
3.2 application.yml 配置骨架
配置文件分三块:MCP 服务器标识、STDIO 传输要求、模型通道。STDIO 模式下必须关闭 Web 容器和 banner,否则标准输出会被日志污染,客户端解析会失败。
spring: main: web-application-type: none banner-mode: off ai: mcp: server: name: java-mcp-demo version: 0.0.1 type: SYNC openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: gpt-4o-mini temperature: 0.7 logging: pattern: console: "" level: root: WARN org.springframework.ai: INFO这里有几个容易踩的点。web-application-type: none是必须的,因为 STDIO 传输不需要 HTTP 端口。logging.pattern.console置空是为了避免日志混入标准输出,但如果你需要调试,可以临时改成%d{HH:mm:ss} %-5level %msg%n并输出到文件。api-key用环境变量注入,启动前执行export TAOTOKEN_API_KEY=你的Key即可。
4. 服务端骨架:工具类与注册
4.1 数据模型
用一个 record 表示课程数据,简洁且不可变。Java 17 的 record 在这里很合适,序列化时字段名直接对应 JSON key。
public record Course(String title, String url) { }4.2 工具服务类
核心是@Tool注解。Spring AI 会扫描被注解的方法,把方法名、描述、参数签名注册成 MCP 工具。AI 客户端拿到工具列表后,会根据描述决定调用哪个方法、传什么参数。
@Service public class CourseService { private static final Logger log = LoggerFactory.getLogger(CourseService.class); private final List<Course> courses = new ArrayList<>(); @Tool(name = "get_courses", description = "获取所有可用课程列表") public List<Course> getCourses() { log.info("MCP tool invoked: get_courses"); return courses; } @Tool(name = "get_course_by_title", description = "根据课程标题精确查找单门课程") public Course getCourseByTitle(String title) { log.info("MCP tool invoked: get_course_by_title, title={}", title); return courses.stream() .filter(c -> c.title().equalsIgnoreCase(title)) .findFirst() .orElse(null); } @PostConstruct public void init() { courses.addAll(List.of( new Course("Spring Boot 实战入门", "https://example.com/spring-boot"), new Course("Spring AI 与 MCP 开发", "https://example.com/spring-ai-mcp") )); } }@Tool的name建议用英文小写下划线,避免客户端解析异常。description要写清楚用途,这是模型判断是否调用该工具的主要依据。参数名会直接暴露给模型,所以title这种语义明确的命名比t更好。
4.3 主类注册工具
在主应用类里把服务类的方法注册成ToolCallback,Spring 的组件扫描会自动注入CourseService。
@SpringBootApplication public class McpServerApplication { public static void main(String[] args) { SpringApplication.run(McpServerApplication.class, args); } @Bean public List<ToolCallback> courseTools(CourseService courseService) { return List.of(ToolCallbacks.from(courseService)); } }ToolCallbacks.from()会反射扫描传入对象上的@Tool方法,生成对应的回调。如果你有多个服务类,可以多次调用from()然后合并到一个 List 里。
5. 验证请求与预期结果
5.1 启动服务器
打包后直接运行:
mvn clean package -DskipTests java -jar target/mcp-server-0.0.1-SNAPSHOT.jarSTDIO 模式下,进程启动后不会输出任何内容,这是正常的。它在等待标准输入传入 JSON-RPC 消息。
5.2 用 JSON-RPC 手动验证
打开另一个终端,用echo管道发送初始化请求。MCP 协议基于 JSON-RPC 2.0,先发initialize,再发tools/list。
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test-client","version":"1.0"}}}' | java -jar target/mcp-server-0.0.1-SNAPSHOT.jar预期返回类似:
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{"tools":{}},"serverInfo":{"name":"java-mcp-demo","version":"0.0.1"}}}接着验证工具列表:
echo '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' | java -jar target/mcp-server-0.0.1-SNAPSHOT.jar预期能看到get_courses和get_course_by_title两个工具,包含各自的inputSchema。如果返回里tools数组为空,说明ToolCallbacks.from()没有扫描到注解,检查服务类是否被@Service标注、主类是否注册了 Bean。
5.3 调用工具
echo '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_courses","arguments":{}}}' | java -jar target/mcp-server-0.0.1-SNAPSHOT.jar预期返回课程列表的 JSON 数组。到这里,一个最小可用的 MCP 服务器就跑通了。
如果你更想先在对话界面里验证模型通道是否正常,可以打开模型对话页面:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite ,确认 Key 和 Base URL 配置无误后再回到代码调试。
6. 常见报错排查
启动后无任何输出,客户端连接超时。检查web-application-type是否为none,以及banner-mode是否关闭。如果控制台有 Spring Boot 的启动日志,说明配置没生效,日志会污染 STDIO 流。
tools/list 返回空数组。最常见的原因是@Tool方法所在的类没有被 Spring 管理,或者主类里没有注册ToolCallbackBean。另外确认spring-ai-mcp-server-spring-boot-starter依赖已正确引入,版本与 Spring AI 其他模块一致。
调用工具时报参数反序列化失败。MCP 客户端传参是 JSON,Java 方法参数如果是复杂对象,需要保证字段名和 JSON key 一致。record 类型默认支持,普通 POJO 需要 getter/setter 或@JsonProperty标注。
模型调用返回 401 或 403。检查TAOTOKEN_API_KEY环境变量是否在当前 shell 生效,base-url是否写成https://taotoken.net/api(注意不要多加路径)。如果用的是 IDE 启动,需要在 Run Configuration 里单独配置环境变量。
STDIO 模式下日志干扰。把logging.pattern.console设为空字符串,或者把日志输出重定向到文件。调试阶段可以临时开启,但接入客户端前必须关掉。
接入相关的文档和 Key 管理入口在这里:接入文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,API Keys https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。如果你用的是 Claude Code 这类编码工具,可以参考 ClaudeCodeAnthropic 配置说明:https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claudecode&utm_campaign=rewrite 。
7. 下一步:从骨架到可用服务
跑通上面的骨架后,你可以按这几个方向扩展。把CourseService里的内存列表换成真实的 Repository,接数据库或远程 API;给工具方法加上参数校验和异常处理,避免模型传入非法参数导致 500;如果要做远程 MCP 服务,把传输方式从 STDIO 换成 HTTP/SSE,配置spring.ai.mcp.server.type和对应端口。
长期做 Agent 或编码辅助的话,建议把模型调用统一走 TaoToken 的 Coding Plan,Key 和额度集中管理,切换模型时只改application.yml里的model字段。工具注册这块,随着服务类增多,可以按业务域拆成多个@Bean方法,每个方法负责一组工具的注册,保持主类清爽。
最后提醒一点:MCP 工具的描述文本会直接影响模型调用准确率。description写得太模糊,模型可能该调不调;写得太长,又会占用上下文。建议用「动词 + 对象 + 返回内容」的结构,比如「根据课程标题查询单门课程的详细信息」,比单纯写「查询课程」效果好很多。