Spring AI Alibaba实战训练营-20 基于注解驱动的MCP快速开发入门指南:把MCP Server配置改到TaoToken
发布时间:2026/10/2 5:58:40 锦皓数字建站

1. 从零跑通一个注解驱动的 MCP Server 到底卡在哪如果你已经写过 Spring Boot 的 Controller那理解 MCP Server 其实不难它本质上就是把你写好的 Java 方法通过一套标准协议暴露给 AI 模型去调用。区别在于Controller 的调用方是前端或者另一个服务而 MCP Server 的调用方是大模型。模型会根据你写在注解里的 description自己判断该不该调、调哪个、传什么参数。Spring AI Alibaba 提供的注解驱动方式把这件事压缩到了两个注解McpTool定义工具McpToolParam定义参数。你不用手写 JSON Schema不用手动注册工具描述框架会扫描注解自动生成。这对有 Spring Boot 基础的 Java 开发者来说学习曲线非常平缓。但真正动手时卡点往往不在注解本身而在三个地方一是依赖版本和启动器选型选错了启动就报错二是模型侧的 Base URL 和 Key 怎么填很多人习惯性去翻各家厂商的文档结果配置项对不上三是启动之后怎么验证工具真的被模型识别到了而不是自己写了个方法却没人调。这篇就按「能跑起来、能调通、能排错」的顺序走一遍。我会用一个获取指定城市当前时间的工具作为示例服务端用 WebFlux 启动器客户端接一个 OpenAI 兼容协议的模型通道。整个流程你可以在本地完整复现不需要额外的中间件。先说清楚适合谁看有 Spring Boot 基础能看懂 pom 依赖和 application.yml知道什么是 Bean 和注解但对 MCP 协议还停留在「听说过」阶段的 Java 开发者。如果你已经写过 MCP Server这篇的排错部分可能对你更有用。核心检索词先摆出来Spring AI Alibaba 注解驱动 MCP 开发本质是用McpTool把普通 Spring Bean 方法变成 AI 可调用的工具适合想快速验证 MCP 链路的 Java 后端。下面从依赖开始一步步来。2. TaoToken 前置统一 Key 与 API 通道的填写位置在写代码之前先把模型通道这件事定下来。MCP 客户端需要调用一个大模型来决定是否触发工具调用这个模型通道需要三个东西Base URL、API Key、Model ID。很多人在这里会绕弯路因为不同厂商的兼容端点、鉴权头、模型命名都不一样。我这边统一走 TaoToken 的 API 通道它的好处是 OpenAI 兼容协议配置项和 Spring AI 的spring.ai.openai前缀天然对齐不需要额外写适配层。你需要提前准备的是一个 API Key在控制台的 API Keys 页面创建即可。具体来说三个参数的填写位置如下Base URL 填https://taotoken.net/api注意这里不加任何路径后缀Spring AI 的 OpenAI 自动配置会自己拼接/v1/chat/completions这类端点。API Key 通过环境变量注入不要硬编码在 yml 里。Model ID 填你实际要用的模型名比如claude-sonnet-4-5或者gpt-4o这类具体以你账号下可用的模型为准。这里有个容易踩的坑Spring AI 的spring.ai.openai.base-url期望的是不带/v1的根地址如果你填成https://taotoken.net/api/v1最终请求会变成/api/v1/v1/chat/completions直接 404。所以记住Base URL 就到/api为止。环境变量的设置方式Linux 和 Mac 下export TAOTOKEN_API_KEY你的KeyWindows PowerShell$env:TAOTOKEN_API_KEY你的Key然后在 yml 里用${TAOTOKEN_API_KEY}引用。这样做的好处是代码和配置可以进版本库Key 不会泄露。如果你在 IDE 里跑记得在 Run Configuration 的 Environment variables 里也加上否则启动时会报占位符解析失败。另外提醒一点MCP 客户端本身不产生模型调用费用费用发生在模型根据工具描述决定调用哪个工具的那一步。所以 Key 的额度是消耗在模型推理上的工具执行本身是本地 Java 代码不花钱。这个认知对后面排查「为什么没调用工具」很关键——如果模型压根没返回 tool_calls那问题在模型侧或者工具描述不在 MCP 传输层。准备好 Key 之后就可以进入依赖配置了。下一节给出完整的 pom 片段和注解代码你可以直接复制。3. 可复制配置pom 依赖、注解工具类与 application.yml这一节是整篇的核心所有片段都可以直接复制到你的项目里。我按服务端和客户端两个模块来组织先服务端。服务端的 pom 需要两个关键依赖注解支持模块和 WebFlux 启动器。注意启动器选型如果你选了spring-ai-starter-mcp-server-webmvc那传输层走的是 SSE 的另一种实现配置项会不一样。这里统一用 WebFlux 版本和后面的客户端配置匹配。dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-mcp-annotations/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-server-webflux/artifactId /dependency /dependencies工具类就是普通的Service方法上挂McpTool。这里的关键是 description 要写清楚因为模型就是靠这句话判断要不要调用。我见过有人把 description 写成「获取时间」结果模型在用户问「现在几点」时反而不调因为描述太模糊。写成「Get the current time of a specified city by time zone id」这种命中率会高很多。Service public class TimeTool { private static final Logger logger LoggerFactory.getLogger(TimeTool.class); McpTool(name getCityTime, description Get the current time of a specified city by time zone id, such as Asia/Shanghai) public String getCityTime( McpToolParam(description Time zone id, such as Asia/Shanghai, required true) String timeZoneId) { logger.info(Tool invoked with timeZoneId{}, timeZoneId); ZoneId zid ZoneId.of(timeZoneId); ZonedDateTime now ZonedDateTime.now(zid); return now.format(DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss z)); } }启动类什么都不用加一个SpringBootApplication就够了。框架会自动扫描McpTool并注册。SpringBootApplication public class AnnotationServerApplication { public static void main(String[] args) { SpringApplication.run(AnnotationServerApplication.class, args); } }服务端默认监听 8080MCP 的 SSE 端点在/sse。这个路径后面客户端要填。客户端的 pom 稍微多一点需要 OpenAI 自动配置、ChatClient、Web 支持以及 MCP 客户端启动器。dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-autoconfigure-model-openai/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-autoconfigure-model-chat-client/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-mcp-annotations/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-client-webflux/artifactId /dependency /dependencies客户端的 application.yml 是配置的重头戏Base URL、Key、Model ID 三件套都在这里。注意web-application-type: none因为客户端是个命令行程序不需要起 Web 容器。server: port: 19100 spring: application: name: mcp-annotation-client main: web-application-type: none ai: openai: api-key: ${TAOTOKEN_API_KEY} base-url: https://taotoken.net/api chat: options: model: claude-sonnet-4-5 mcp: client: enabled: true name: my-mcp-client version: 1.0.0 request-timeout: 600s type: SYNC sse: connections: server1: url: http://localhost:8080 annotation-scanner: enabled: true这里逐项说明。api-key引用环境变量base-url就是上一节说的https://taotoken.net/apimodel填你实际可用的模型 ID。mcp.client.sse.connections.server1.url指向本地服务端的根地址框架会自动拼/sse。annotation-scanner.enabled: true是让客户端也扫描注解如果你只在客户端做工具调用而不定义工具这个可以不开但开了不影响。客户端主程序用CommandLineRunner起一个交互循环把 MCP 工具注册进 ChatClient。SpringBootApplication public class AnnotationClientApplication { public static void main(String[] args) { SpringApplication.run(AnnotationClientApplication.class, args); } Bean public CommandLineRunner chatLoop(ChatClient.Builder builder, ToolCallbackProvider tools, ConfigurableApplicationContext ctx) { return args - { var chatClient builder.defaultToolCallbacks(tools.getToolCallbacks()).build(); System.out.println(Available tools:); for (ToolCallback cb : tools.getToolCallbacks()) { System.out.println( cb.getToolDefinition().name()); } Scanner scanner new Scanner(System.in); while (true) { System.out.print(\n QUESTION: ); String input scanner.nextLine(); if (exit.equalsIgnoreCase(input)) break; System.out.println( ASSISTANT: chatClient.prompt(input).call().content()); } scanner.close(); ctx.close(); }; } }到这里配置片段就齐了。下一节讲怎么启动和验证。4. 验证请求启动顺序、工具列表与一次成功调用启动顺序很重要先服务端后客户端。服务端起来之后你可以先用浏览器或者 curl 探一下 SSE 端点是否活着。cd mcp-annotation-server mvn spring-boot:run看到Netty started on port 8080之类的日志就说明服务端 OK 了。这时候访问http://localhost:8080/sse浏览器会挂起一个长连接这是正常的SSE 就是长连接。你可以直接 CtrlC 掉这个请求不影响服务端。然后启动客户端cd mcp-annotation-client mvn spring-boot:run客户端启动后控制台会先打印可用工具列表。如果你看到Available tools: getCityTime说明 MCP 客户端已经成功连上服务端并且发现了工具。这一步是整个链路里最关键的验证点。如果这里没有输出工具后面模型再聪明也没用因为工具根本没注册进来。接下来在 QUESTION:后面输入上海现在几点了模型会先做一次推理判断需要调用getCityTime参数是Asia/Shanghai。然后 MCP 客户端通过 SSE 把调用请求发给服务端服务端执行 Java 方法把结果返回模型再组织成自然语言输出。你最终会看到类似 ASSISTANT: 上海现在的时间是 2025-01-15 14:32:08 CST。同时服务端的日志里会出现INFO TimeTool - Tool invoked with timeZoneIdAsia/Shanghai这条日志是工具真的被执行了的铁证。如果客户端有回复但服务端没这条日志说明模型是「编」了一个时间并没有真正调用工具。这种情况通常是工具描述不够清晰或者模型本身对 tool calling 的支持不好。再测一个边界情况输入一个模型可能不认识的时区纽约现在几点模型应该会传America/New_York服务端返回对应时间。如果模型传了个不存在的时区 IDZoneId.of会抛异常这时候你会看到工具调用失败。这正好引出下一节的排错。5. 本篇常见错排查401、连接失败与工具未识别排错这部分我按报错现象来组织你遇到哪个直接对号入座。401 Unauthorized 或 invalid api key这个几乎都是 Key 的问题。先确认环境变量在当前 shell 里真的存在echo $TAOTOKEN_API_KEY如果输出为空说明没 export 成功或者你在 IDE 里跑但没配 Environment variables。另一个常见原因是 Key 复制时带了空格或者换行建议重新复制一次。还有一种情况是 Base URL 填错比如填成了https://taotoken.net/api/v1导致请求打到了不存在的路径有些网关会返回 401 而不是 404容易误导。local proxy failed 或 connection refused这个报错说明客户端连不上服务端。检查三件事服务端是否真的在 8080 端口监听spring.ai.mcp.client.sse.connections.server1.url是否写成了http://localhost:8080不要带/sse框架会自己拼以及有没有防火墙拦截本地回环。如果你改了服务端的server.port客户端这里的 url 也要同步改。reading choices 相关报错或返回空这个通常出现在模型响应解析阶段。如果模型返回的 JSON 结构不符合 OpenAI 兼容格式Spring AI 在解析choices字段时会失败。排查方向是确认你用的模型 ID 确实支持 OpenAI 兼容协议。有些模型只支持原生协议走兼容端点会返回非标准结构。换一个明确支持兼容协议的模型 ID 再试。工具未被识别Available tools 为空三个检查点工具类上有没有Service或Component方法上有没有McpTool以及客户端的annotation-scanner.enabled是否为 true。还有一个隐蔽的坑如果服务端和客户端在同一个 JVM 里跑比如你写了个单模块项目注解扫描可能会冲突建议还是分成两个模块。OAuth 或鉴权头相关报错如果你用的是需要 OAuth 的通道Spring AI 的 OpenAI 自动配置默认只发Authorization: Bearer头。TaoToken 的 API 通道用的就是 Bearer 鉴权所以只要 Key 对不会出这个问题。如果你看到 OAuth 相关的报错大概率是 Base URL 指向了需要 OAuth 的端点换回https://taotoken.net/api即可。工具调用超时默认request-timeout是 600s一般够用。如果你的工具方法里有耗时操作比如查数据库或者调外部 API可以适当延长。但更推荐的做法是把耗时逻辑做成异步避免阻塞 MCP 会话。排错的核心思路是分层先确认传输层通不通工具列表能不能出来再确认模型层通不通模型有没有返回 tool_calls最后确认执行层通不通服务端日志有没有打印。这三层任何一层断了现象都不一样按层排查比盲目改配置快得多。6. 语义一致 CTA把这条链路用到真实编码场景跑通这个最小示例之后你手里其实已经有了一个可扩展的 MCP Server 骨架。接下来无非是往里加工具查数据库的、调内部 API 的、读文件的每个工具就是一个带McpTool的 Spring Bean 方法。注解驱动的价值就在这里加工具的成本极低不用改任何注册代码。如果你想把这条链路接到日常编码里比如让 AI 在写代码时能调用你的内部工具那重点会从「能不能跑通」转向「怎么稳定地用」。这时候模型通道的稳定性、Key 的额度管理、以及工具描述的准确性就变得更重要。我自己的做法是把模型通道统一到 TaoToken 的 API 上Base URL 固定https://taotoken.net/apiKey 走环境变量这样换模型只需要改一个 model 字段不用动代码。对于需要长期跑 Agent 或者频繁做工具调用的场景可以看一下 Coding Plan 这类方案它在额度上比按次调用更划算。如果你只是想先验证模型对某个工具描述的理解能力可以直接在模型对话里试不用每次都起本地服务。接入文档里有更完整的参数说明和端点列表遇到配置项对不上的时候翻一下比猜快。API Keys 页面则是你创建和管理 Key 的地方建议给不同项目建不同的 Key方便排查问题时定位是哪个项目在消耗额度。最后留一个实用技巧工具方法的返回值尽量用字符串或者简单的 JSON 结构不要返回复杂的嵌套对象。模型对返回值的解析能力有限结构越简单它组织自然语言回答时越不容易出错。这个坑我在做数据库查询工具时踩过返回了一个深层嵌套的 Map模型直接懵了改成扁平结构之后就好了。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。