拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

大模型之Spring AI实战系列(二十三):Spring AI + MCP + 自定义MCP服务开发实战(TaoToken 统一 Key 接入篇)

大模型之Spring AI实战系列(二十三):Spring AI + MCP + 自定义MCP服务开发实战(TaoToken 统一 Key 接入篇)

1. 从零开发自定义 MCP 服务:Spring AI 工具注册与 TaoToken 统一 Key 接入

Spring AI 集成 MCP 协议这件事,真正卡住大多数人的不是协议本身,而是两件事:一是自定义 MCP 服务怎么把普通 Java 方法变成 LLM 能识别的工具,二是模型调用通道怎么配才能稳定跑通。这篇就围绕 Spring AI + MCP + 自定义 MCP 服务开发实战,把天气查询服务封装成 LLM 可调用工具,并用 TaoToken 统一 Key 完成模型调用配置,最后验证自定义工具能被 Spring AI 正确发现并触发。

如果你之前跟着系列文章做过 Spring AI 的 Tool Calling,会发现 MCP 的思路其实很像:都是把方法暴露给模型。区别在于 MCP 把工具能力标准化成了协议,服务端和客户端可以跨进程、跨语言通信。自定义 MCP 服务的价值就在这里——你写的天气查询、订单查询、内部 API 封装,只要注册成 MCP 工具,任何支持 MCP 的客户端都能调用。

适合谁看:已经会用 Spring Boot 写接口、想把自己的业务能力接进大模型工具链的开发者;正在做 Agent 或智能助手、需要动态扩展工具集的团队;以及被模型通道配置反复折腾、想用统一 Key 简化接入的人。下面从环境准备开始,每一步都给可复制的配置和代码。

2. TaoToken 前置准备:统一 Key 与 API 通道配置

在写 MCP 服务之前,先把模型调用通道配好。传统做法是每个项目单独申请模型厂商 Key,散落在各个配置文件里,换模型就要改代码。TaoToken 的思路是提供一个统一的 API 通道,你只需要一个 Key,就能在 Spring AI 里通过 OpenAI 兼容协议调用不同模型。

先拿到 Key。访问 https://taotoken.net/api-keys 创建 API Key,建议按项目命名,方便后续排查。创建后在控制台 https://taotoken.net/console 能看到用量和调用记录。如果你还没决定用哪个模型,可以先在模型对话 https://taotoken.net/model-chat 里试一下效果,确认响应格式符合预期再写进配置。

Spring AI 的 OpenAI starter 默认走 OpenAI 官方地址,我们要做的是把 base-url 指向 TaoToken 的 API 地址,Key 用刚创建的。这样 Spring AI 的 ChatClient、Embedding、Tool Calling 全部走同一条通道,MCP 客户端调用模型时也复用这套配置。

这里有个容易踩的坑:Spring AI 不同版本对 base-url 的拼接方式不一样。1.0.0 版本里spring.ai.openai.base-url需要写到/v1这一层,而有些 starter 会自动补/v1。实测下来,写成https://taotoken.net/api让 starter 自己拼/v1/chat/completions最稳。如果你遇到 404,先检查这个路径。

另外,MCP 服务端本身不调用模型,它只暴露工具;真正调用模型的是 MCP 客户端。所以 TaoToken 的配置主要写在客户端项目里。但服务端如果也要做工具内部的自检或补全,同样可以复用这套 Key。下面配置片段两个项目都会用到。

注意:Key 不要硬编码进代码或提交到仓库。用环境变量或配置中心管理,本地开发可以用.env或 IDE 的运行配置注入。

3. 可复制配置:application.yml 与 MCP Server 注册代码

这一节给完整可复制的配置。先看 MCP 服务端的application.yml:

server: port: 8080 spring: application: name: my-mcp-weather-server main: banner-mode: off web-application-type: servlet ai: mcp: server: name: my-mcp-weather-server version: 0.0.1 stdio: false logging: level: io.modelcontextprotocol: WARN file: name: ./logs/spring-ai-mcp-weather-server.log

关键点:spring.ai.mcp.server.name和version是 MCP 协议握手时返回给客户端的标识,客户端配置里要对应。stdio: false表示走 SSE 传输,服务端以 Web 应用方式启动。如果你要用 STDIO 传输,改成true并把web-application-type设为none。

Maven 依赖部分,服务端需要 MCP server starter:

<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>1.0.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-mcp-server-webmvc-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>

工具注册的核心代码,启动类里把 WeatherService 注册为工具提供者:

@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @Bean public ToolCallbackProvider weatherTools(WeatherService weatherService) { return MethodToolCallbackProvider.builder() .toolObjects(weatherService) .build(); } }

WeatherService 用@Tool注解暴露方法:

@Service public class WeatherService { private final RestClient restClient; public WeatherService() { this.restClient = RestClient.builder() .baseUrl("https://api.weather.gov") .defaultHeader("Accept", "application/geo+json") .defaultHeader("User-Agent", "WeatherApiClient/1.0") .build(); } @Tool(description = "Get weather forecast for a specific latitude/longitude") public String getWeatherForecastByLocation(double latitude, double longitude) { var points = restClient.get() .uri("/points/{lat},{lon}", latitude, longitude) .retrieve() .body(Points.class); var forecast = restClient.get() .uri(points.properties().forecast()) .retrieve() .body(Forecast.class); return formatForecast(forecast); } @Tool(description = "Get weather alerts for a US state, input is two-letter state code") public String getAlerts(String state) { Alert alert = restClient.get() .uri("/alerts/active/area/{state}", state) .retrieve() .body(Alert.class); return formatAlert(alert); } }

客户端项目的application.yml要同时配 TaoToken 通道和 MCP 连接:

server: port: 8001 spring: application: name: mcp-weather-client main: web-application-type: none banner-mode: off ai: openai: api-key: ${TAOTOKEN_API_KEY} base-url: https://taotoken.net/api chat: options: model: gpt-4o-mini mcp: client: toolcallback: enabled: true sse: connections: my-mcp-weather-server: url: http://localhost:8080 ai: user: input: What tools are available?

这里spring.ai.openai.api-key用环境变量注入,base-url指向 TaoToken API 地址,model指定模型 ID。MCP 客户端通过 SSE 连到服务端的 8080 端口。三件套齐了:Base URL、Key、Model ID。

4. 验证请求:启动服务并确认自定义工具被正确发现

配置写完,先启动 MCP 服务端:

mvn clean package -DskipTests java -jar target/spring-ai-mcp-weather-server-0.0.1-SNAPSHOT.jar

看到Tomcat started on port 8080和 MCP server 注册日志就说明服务端起来了。可以用 curl 探一下 SSE 端点:

curl -N http://localhost:8080/sse

正常会返回event: endpoint和一条带 sessionId 的 data。如果返回 404,检查spring-ai-mcp-server-webmvc-spring-boot-starter是否引入,以及web-application-type是否为 servlet。

接着启动客户端,先验证工具发现:

export TAOTOKEN_API_KEY="你的Key" java -Dai.user.input="What tools are available?" \ -jar target/spring-ai-mcp-weather-client-0.0.1-SNAPSHOT.jar

控制台会输出类似:

>>> QUESTION: What tools are available? >>> ASSISTANT: 当前可用的工具包括: 1. getWeatherForecastByLocation - 根据经纬度获取天气预报 2. getAlerts - 获取指定州的天气警报

这说明 Spring AI 已经通过 MCP 协议从服务端拉到了工具列表,并注入到 ChatClient 的 tool callbacks 里。再测一次真实调用:

java -Dai.user.input="纽约的天气怎么样?" \ -jar target/spring-ai-mcp-weather-client-0.0.1-SNAPSHOT.jar

模型会先决定调用getWeatherForecastByLocation,传入纽约的经纬度,MCP 客户端把请求转发给服务端,服务端执行 RestClient 调用天气 API,结果回传给模型,模型再组织成自然语言。控制台能看到完整的天气预报文本。这一步跑通,说明自定义 MCP 服务开发链路完整闭环。

如果你用 STDIO 传输,客户端配置改成:

spring: ai: mcp: client: stdio: servers-configuration: classpath:/mcp-servers-config.json

mcp-servers-config.json内容:

{ "mcpServers": { "my-mcp-weather-server": { "command": "java", "args": [ "-Dspring.ai.mcp.server.stdio=true", "-Dspring.main.web-application-type=none", "-Dspring.main.banner-mode=off", "-jar", "/absolute/path/to/spring-ai-mcp-weather-server-0.0.1-SNAPSHOT.jar" ] } } }

STDIO 模式下服务端不占端口,由客户端拉起子进程通信,适合本地工具和 CLI 场景。

5. 本篇常见错排查:401、local proxy failed、reading choices 与 OAuth

跑 MCP + Spring AI 这套组合,报错集中在几个地方。下面按真实报错对照排查。

401 Unauthorized:客户端调用模型时返回 401,说明 TaoToken Key 没生效。检查spring.ai.openai.api-key是否读到了环境变量,base-url是否写成https://taotoken.net/api。如果 Key 正确但还 401,看是不是把 Key 写到了spring.ai.openai.api-key之外的地方,或者环境变量名拼错。用echo $TAOTOKEN_API_KEY确认。

local proxy failed / Connection refused:MCP 客户端连不上服务端。SSE 模式下检查服务端是否真的在 8080 监听,curl http://localhost:8080/sse能不能通。如果服务端日志显示启动成功但客户端报连接失败,多半是端口被占或防火墙拦截。STDIO 模式下这个错通常是mcp-servers-config.json里的 jar 路径不对,或者 java 命令不在 PATH 里。

reading choices / choices 字段为空:模型返回体解析失败。常见原因是 base-url 拼接多了或少了/v1,导致请求打到了错误端点,返回的不是标准 chat completion 格式。把base-url改成https://taotoken.net/api再试。另一个原因是模型 ID 写错,比如用了 TaoToken 不支持的模型名,返回体里没有 choices 字段。

OAuth / 认证失败:如果你在 MCP 服务端配了 OAuth 保护,客户端连接时需要带 token。Spring AI MCP 客户端目前对 OAuth 的支持需要手动配置spring.ai.mcp.client.sse.connections.<name>.headers注入 Authorization 头。本地开发建议先关掉服务端鉴权,跑通链路再加。

工具没被发现:客户端日志里没有工具列表。检查服务端@Tool注解的方法是否是 public,MethodToolCallbackProvider是否注册了正确的 bean。另外spring.ai.mcp.client.toolcallback.enabled=true必须显式打开,否则客户端不会把 MCP 工具注入 ChatClient。

模型不调用工具:工具列表有了,但模型直接回答不调工具。这通常是模型能力问题,换一个 tool calling 支持更好的模型 ID。另外@Tool的 description 要写清楚用途和参数格式,模型靠这个决定是否调用。

6. 语义一致 CTA:把自定义 MCP 服务接进你的工具链

到这里,Spring AI + MCP + 自定义 MCP 服务的完整链路就跑通了。你手里有一套可复用的模式:任何 Spring Boot 服务,只要把方法加上@Tool注解,注册成ToolCallbackProvider,就能变成 MCP 工具被大模型调用。天气查询只是示例,换成订单查询、库存检查、内部 API 封装,代码结构完全一样。

模型通道这块,TaoToken 的统一 Key 省掉了多厂商配置的麻烦。Spring AI 的 OpenAI starter 直接指向 https://taotoken.net/api,一个 Key 跑通 ChatClient、Tool Calling 和 MCP 客户端。如果你要长期做编码类 Agent,可以看 Coding Plan https://taotoken.net/coding-plan;需要查接入细节看文档 https://taotoken.net/doc;Key 管理在 https://taotoken.net/api-keys。

下一步可以尝试的方向:把多个 MCP 服务端注册到同一个客户端,让模型在多个工具集之间选择;给 MCP 服务端加动态工具注册,根据配置决定暴露哪些工具;或者把 MCP 服务端部署到内网,客户端通过 SSE 跨网络调用。这些都是在今天这套代码基础上扩展,核心的注册和调用机制不变。

返回列表